♻️ refactor: add backgroundColor to TaskParticipant and rename name to title (#13877)

* ♻️ refactor: add backgroundColor to TaskParticipant and rename name to title

Add backgroundColor field and rename name→title in TaskParticipant interface
to match agent avatar data. Add LobeAI fallback for inbox agent in
getAgentAvatarsByIds when avatar/title are missing.
This commit is contained in:
Arvin Xu 2026-04-16 17:06:51 +08:00 committed by GitHub
parent 85227cf467
commit 1476cd86ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 65 additions and 5 deletions

View file

@ -73,6 +73,50 @@ describe('AgentModel.getAgentAvatarsByIds', () => {
expect(result[0].id).toBe('agent-mine');
});
it('should fallback to LobeAI defaults for inbox agent without avatar/title', async () => {
await serverDB.insert(agents).values({
avatar: null,
backgroundColor: null,
id: 'agent-inbox',
slug: 'inbox',
title: null,
userId,
});
const model = new AgentModel(serverDB, userId);
const result = await model.getAgentAvatarsByIds(['agent-inbox']);
expect(result).toHaveLength(1);
expect(result[0]).toEqual({
avatar: '/avatars/lobe-ai.png',
backgroundColor: null,
id: 'agent-inbox',
title: 'LobeAI',
});
});
it('should not override inbox agent avatar/title when they are set', async () => {
await serverDB.insert(agents).values({
avatar: '🤖',
backgroundColor: '#123456',
id: 'agent-inbox-custom',
slug: 'inbox',
title: 'Custom Inbox',
userId,
});
const model = new AgentModel(serverDB, userId);
const result = await model.getAgentAvatarsByIds(['agent-inbox-custom']);
expect(result).toHaveLength(1);
expect(result[0]).toEqual({
avatar: '🤖',
backgroundColor: '#123456',
id: 'agent-inbox-custom',
title: 'Custom Inbox',
});
});
it('should return only selected fields', async () => {
await serverDB.insert(agents).values({
avatar: '🤖',

View file

@ -1,5 +1,5 @@
import { getAgentPersistConfig } from '@lobechat/builtin-agents';
import { INBOX_SESSION_ID } from '@lobechat/const';
import { DEFAULT_INBOX_AVATAR, INBOX_SESSION_ID } from '@lobechat/const';
import { and, desc, eq, ilike, inArray, isNull, or, sql } from 'drizzle-orm';
import type { PartialDeep } from 'type-fest';
@ -75,19 +75,27 @@ export class AgentModel {
/**
* Get minimal agent info (avatar, title, backgroundColor) by IDs.
* For inbox agent (slug='inbox'), falls back to LobeAI defaults when avatar/title are missing.
*/
getAgentAvatarsByIds = async (ids: string[]) => {
if (ids.length === 0) return [];
return this.db
const rows = await this.db
.select({
avatar: agents.avatar,
backgroundColor: agents.backgroundColor,
id: agents.id,
slug: agents.slug,
title: agents.title,
})
.from(agents)
.where(and(eq(agents.userId, this.userId), inArray(agents.id, ids)));
return rows.map(({ slug, ...row }) => ({
...row,
avatar: row.avatar || (slug === INBOX_SESSION_ID ? DEFAULT_INBOX_AVATAR : null),
title: row.title || (slug === INBOX_SESSION_ID ? 'LobeAI' : null),
}));
};
/**

View file

@ -42,8 +42,9 @@ export interface TaskTopicHandoff {
export interface TaskParticipant {
avatar: string | null;
backgroundColor: string | null;
id: string;
name: string;
title: string;
type: 'user' | 'agent';
}

View file

@ -436,7 +436,13 @@ describe('Task Router Integration', () => {
const assigned = list.data.find((t) => t.assigneeAgentId === testAgentId)!;
expect(assigned.participants).toEqual([
{ avatar: 'avatar.png', id: testAgentId, name: 'Agent One', type: 'agent' },
{
avatar: 'avatar.png',
backgroundColor: null,
id: testAgentId,
title: 'Agent One',
type: 'agent',
},
]);
const unassigned = list.data.find((t) => !t.assigneeAgentId)!;

View file

@ -743,8 +743,9 @@ export const taskRouter = router({
if (agent) {
participants.push({
avatar: agent.avatar,
backgroundColor: agent.backgroundColor,
id: agent.id,
name: agent.title ?? '',
title: agent.title ?? '',
type: 'agent',
});
}