@lobehub/lobehub 2.0.0-next.304 → 2.0.0-next.306

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/.github/workflows/manual-build-desktop.yml +11 -1
  2. package/CHANGELOG.md +50 -0
  3. package/apps/desktop/.i18nrc.js +3 -3
  4. package/apps/desktop/electron.vite.config.ts +0 -2
  5. package/apps/desktop/resources/locales/ar/dialog.json +5 -1
  6. package/apps/desktop/resources/locales/ar/menu.json +16 -0
  7. package/apps/desktop/resources/locales/bg-BG/dialog.json +5 -1
  8. package/apps/desktop/resources/locales/bg-BG/menu.json +16 -0
  9. package/apps/desktop/resources/locales/de-DE/dialog.json +5 -1
  10. package/apps/desktop/resources/locales/de-DE/menu.json +16 -0
  11. package/apps/desktop/resources/locales/en/common.json +26 -0
  12. package/apps/desktop/resources/locales/en/dialog.json +27 -0
  13. package/apps/desktop/resources/locales/en/menu.json +73 -0
  14. package/apps/desktop/resources/locales/es-ES/dialog.json +5 -1
  15. package/apps/desktop/resources/locales/es-ES/menu.json +16 -0
  16. package/apps/desktop/resources/locales/fa-IR/dialog.json +5 -1
  17. package/apps/desktop/resources/locales/fa-IR/menu.json +16 -0
  18. package/apps/desktop/resources/locales/fr-FR/dialog.json +5 -1
  19. package/apps/desktop/resources/locales/fr-FR/menu.json +16 -0
  20. package/apps/desktop/resources/locales/it-IT/dialog.json +5 -1
  21. package/apps/desktop/resources/locales/it-IT/menu.json +16 -0
  22. package/apps/desktop/resources/locales/ja-JP/dialog.json +5 -1
  23. package/apps/desktop/resources/locales/ja-JP/menu.json +16 -0
  24. package/apps/desktop/resources/locales/ko-KR/dialog.json +5 -1
  25. package/apps/desktop/resources/locales/ko-KR/menu.json +16 -0
  26. package/apps/desktop/resources/locales/nl-NL/dialog.json +5 -1
  27. package/apps/desktop/resources/locales/nl-NL/menu.json +16 -0
  28. package/apps/desktop/resources/locales/pl-PL/dialog.json +5 -1
  29. package/apps/desktop/resources/locales/pl-PL/menu.json +16 -0
  30. package/apps/desktop/resources/locales/pt-BR/dialog.json +5 -1
  31. package/apps/desktop/resources/locales/pt-BR/menu.json +16 -0
  32. package/apps/desktop/resources/locales/ru-RU/dialog.json +5 -1
  33. package/apps/desktop/resources/locales/ru-RU/menu.json +16 -0
  34. package/apps/desktop/resources/locales/tr-TR/dialog.json +5 -1
  35. package/apps/desktop/resources/locales/tr-TR/menu.json +16 -0
  36. package/apps/desktop/resources/locales/vi-VN/dialog.json +5 -1
  37. package/apps/desktop/resources/locales/vi-VN/menu.json +16 -0
  38. package/apps/desktop/resources/locales/zh-TW/dialog.json +5 -1
  39. package/apps/desktop/resources/locales/zh-TW/menu.json +16 -0
  40. package/apps/desktop/scripts/update-test/README.md +15 -0
  41. package/apps/desktop/src/main/core/infrastructure/BackendProxyProtocolManager.ts +7 -6
  42. package/apps/desktop/src/main/core/infrastructure/UpdaterManager.ts +38 -5
  43. package/apps/desktop/src/main/utils/logger.ts +2 -2
  44. package/changelog/v1.json +14 -0
  45. package/e2e/src/steps/community/detail-pages.steps.ts +3 -1
  46. package/e2e/src/steps/community/interactions.steps.ts +4 -4
  47. package/locales/en-US/auth.json +5 -0
  48. package/locales/zh-CN/auth.json +5 -0
  49. package/package.json +6 -5
  50. package/packages/builtin-tool-agent-builder/src/ExecutionRuntime/index.ts +362 -30
  51. package/packages/builtin-tool-agent-builder/src/client/Intervention/InstallPlugin.tsx +28 -4
  52. package/packages/context-engine/src/processors/GroupMessageFlatten.ts +9 -6
  53. package/packages/context-engine/src/processors/__tests__/GroupMessageFlatten.test.ts +103 -0
  54. package/packages/context-engine/src/providers/GroupAgentBuilderContextInjector.ts +18 -31
  55. package/packages/context-engine/src/providers/__tests__/GroupAgentBuilderContextInjector.test.ts +307 -0
  56. package/packages/prompts/src/prompts/userMemory/__snapshots__/index.test.ts.snap +14 -38
  57. package/packages/prompts/src/prompts/userMemory/index.ts +5 -24
  58. package/scripts/electronWorkflow/buildDesktopChannel.ts +135 -0
  59. package/src/app/[variants]/(main)/_layout/index.tsx +2 -0
  60. package/src/app/[variants]/(main)/community/(detail)/assistant/index.tsx +1 -1
  61. package/src/app/[variants]/(main)/community/(detail)/mcp/index.tsx +1 -1
  62. package/src/app/[variants]/(main)/group/features/Conversation/MainChatInput/index.tsx +2 -2
  63. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +2 -2
  64. package/src/features/Conversation/Messages/Supervisor/index.tsx +2 -1
  65. package/src/features/Conversation/Messages/components/ContentLoading.tsx +8 -2
  66. package/src/features/DesktopNavigationBridge/index.tsx +0 -9
  67. package/src/features/Electron/AuthRequiredModal/index.tsx +151 -0
  68. package/src/locales/default/auth.ts +6 -0
  69. package/src/services/chat/mecha/agentConfigResolver.ts +65 -0
  70. package/src/services/chat/mecha/modelParamsResolver.test.ts +211 -0
  71. package/src/store/agentGroup/action.ts +30 -0
  72. package/src/store/agentGroup/slices/lifecycle.test.ts +77 -18
  73. package/src/store/agentGroup/slices/lifecycle.ts +7 -9
  74. package/src/store/chat/slices/operation/__tests__/selectors.test.ts +124 -0
  75. package/src/store/chat/slices/operation/selectors.ts +22 -0
  76. package/src/utils/errorResponse.ts +21 -1
@@ -489,6 +489,109 @@ describe('GroupMessageFlattenProcessor', () => {
489
489
  });
490
490
  });
491
491
 
492
+ describe('Supervisor Messages', () => {
493
+ it('should flatten supervisor message with children', async () => {
494
+ const processor = new GroupMessageFlattenProcessor();
495
+
496
+ const input: any[] = [
497
+ {
498
+ id: 'msg-supervisor-1',
499
+ role: 'supervisor',
500
+ content: '',
501
+ createdAt: '2025-10-27T10:00:00.000Z',
502
+ updatedAt: '2025-10-27T10:00:10.000Z',
503
+ meta: { title: 'Supervisor Agent' },
504
+ children: [
505
+ {
506
+ id: 'msg-1',
507
+ content: 'Let me coordinate the agents',
508
+ tools: [
509
+ {
510
+ id: 'tool-1',
511
+ type: 'builtin',
512
+ apiName: 'broadcast',
513
+ arguments: '{"message":"Hello agents"}',
514
+ identifier: 'lobe-group-management',
515
+ result: {
516
+ id: 'msg-tool-1',
517
+ content: 'Broadcast sent',
518
+ error: null,
519
+ state: {},
520
+ },
521
+ },
522
+ ],
523
+ usage: { totalTokens: 100 },
524
+ },
525
+ ],
526
+ },
527
+ ];
528
+
529
+ const context = createContext(input);
530
+ const result = await processor.process(context);
531
+
532
+ // Should create 2 messages: 1 assistant + 1 tool
533
+ expect(result.messages).toHaveLength(2);
534
+
535
+ // Check assistant message (supervisor gets flattened to assistant)
536
+ const assistantMsg = result.messages[0];
537
+ expect(assistantMsg.role).toBe('assistant');
538
+ expect(assistantMsg.id).toBe('msg-1');
539
+ expect(assistantMsg.content).toBe('Let me coordinate the agents');
540
+ expect(assistantMsg.tools).toHaveLength(1);
541
+
542
+ // Check tool message
543
+ const toolMsg = result.messages[1];
544
+ expect(toolMsg.role).toBe('tool');
545
+ expect(toolMsg.id).toBe('msg-tool-1');
546
+ expect(toolMsg.content).toBe('Broadcast sent');
547
+ });
548
+
549
+ it('should flatten supervisor message with content only (no tools)', async () => {
550
+ const processor = new GroupMessageFlattenProcessor();
551
+
552
+ const input: any[] = [
553
+ {
554
+ id: 'msg-supervisor-1',
555
+ role: 'supervisor',
556
+ content: '',
557
+ children: [
558
+ {
559
+ id: 'msg-1',
560
+ content: 'Anthropic cowork',
561
+ },
562
+ ],
563
+ },
564
+ ];
565
+
566
+ const context = createContext(input);
567
+ const result = await processor.process(context);
568
+
569
+ // Should create 1 assistant message
570
+ expect(result.messages).toHaveLength(1);
571
+ expect(result.messages[0].role).toBe('assistant');
572
+ expect(result.messages[0].content).toBe('Anthropic cowork');
573
+ });
574
+
575
+ it('should handle supervisor message with empty children', async () => {
576
+ const processor = new GroupMessageFlattenProcessor();
577
+
578
+ const input: any[] = [
579
+ {
580
+ id: 'msg-supervisor-1',
581
+ role: 'supervisor',
582
+ content: '',
583
+ children: [],
584
+ },
585
+ ];
586
+
587
+ const context = createContext(input);
588
+ const result = await processor.process(context);
589
+
590
+ // Empty children means no messages created
591
+ expect(result.messages).toHaveLength(0);
592
+ });
593
+ });
594
+
492
595
  describe('Real-world Test Case', () => {
493
596
  it('should flatten the provided real-world group message', async () => {
494
597
  const processor = new GroupMessageFlattenProcessor();
@@ -1,6 +1,6 @@
1
1
  import debug from 'debug';
2
2
 
3
- import { BaseProvider } from '../base/BaseProvider';
3
+ import { BaseFirstUserContentProvider } from '../base/BaseFirstUserContentProvider';
4
4
  import type { PipelineContext, ProcessorOptions } from '../types';
5
5
 
6
6
  const log = debug('context-engine:provider:GroupAgentBuilderContextInjector');
@@ -262,8 +262,10 @@ ${parts.join('\n')}
262
262
  /**
263
263
  * Group Agent Builder Context Injector
264
264
  * Responsible for injecting current group context when Group Agent Builder tool is enabled
265
+ *
266
+ * Extends BaseFirstUserContentProvider to consolidate with other first-user-message injectors
265
267
  */
266
- export class GroupAgentBuilderContextInjector extends BaseProvider {
268
+ export class GroupAgentBuilderContextInjector extends BaseFirstUserContentProvider {
267
269
  readonly name = 'GroupAgentBuilderContextInjector';
268
270
 
269
271
  constructor(
@@ -273,19 +275,17 @@ export class GroupAgentBuilderContextInjector extends BaseProvider {
273
275
  super(options);
274
276
  }
275
277
 
276
- protected async doProcess(context: PipelineContext): Promise<PipelineContext> {
277
- const clonedContext = this.cloneContext(context);
278
-
278
+ protected buildContent(): string | null {
279
279
  // Skip if Group Agent Builder is not enabled
280
280
  if (!this.config.enabled) {
281
281
  log('Group Agent Builder not enabled, skipping injection');
282
- return this.markAsExecuted(clonedContext);
282
+ return null;
283
283
  }
284
284
 
285
285
  // Skip if no group context
286
286
  if (!this.config.groupContext) {
287
287
  log('No group context provided, skipping injection');
288
- return this.markAsExecuted(clonedContext);
288
+ return null;
289
289
  }
290
290
 
291
291
  // Format group context
@@ -295,34 +295,21 @@ export class GroupAgentBuilderContextInjector extends BaseProvider {
295
295
  // Skip if no content to inject
296
296
  if (!formattedContent) {
297
297
  log('No content to inject after formatting');
298
- return this.markAsExecuted(clonedContext);
299
- }
300
-
301
- // Find the first user message index
302
- const firstUserIndex = clonedContext.messages.findIndex((msg) => msg.role === 'user');
303
-
304
- if (firstUserIndex === -1) {
305
- log('No user messages found, skipping injection');
306
- return this.markAsExecuted(clonedContext);
298
+ return null;
307
299
  }
308
300
 
309
- // Insert a new user message with group context before the first user message
310
- const groupContextMessage = {
311
- content: formattedContent,
312
- createdAt: Date.now(),
313
- id: `group-agent-builder-context-${Date.now()}`,
314
- meta: { injectType: 'group-agent-builder-context', systemInjection: true },
315
- role: 'user' as const,
316
- updatedAt: Date.now(),
317
- };
318
-
319
- clonedContext.messages.splice(firstUserIndex, 0, groupContextMessage);
301
+ log('Group Agent Builder context prepared for injection');
302
+ return formattedContent;
303
+ }
320
304
 
321
- // Update metadata
322
- clonedContext.metadata.groupAgentBuilderContextInjected = true;
305
+ protected async doProcess(context: PipelineContext): Promise<PipelineContext> {
306
+ const result = await super.doProcess(context);
323
307
 
324
- log('Group Agent Builder context injected as new user message');
308
+ // Update metadata if content was injected
309
+ if (this.config.enabled && this.config.groupContext) {
310
+ result.metadata.groupAgentBuilderContextInjected = true;
311
+ }
325
312
 
326
- return this.markAsExecuted(clonedContext);
313
+ return result;
327
314
  }
328
315
  }
@@ -0,0 +1,307 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import type { PipelineContext } from '../../types';
4
+ import { GroupAgentBuilderContextInjector } from '../GroupAgentBuilderContextInjector';
5
+ import { UserMemoryInjector } from '../UserMemoryInjector';
6
+
7
+ describe('GroupAgentBuilderContextInjector', () => {
8
+ const createContext = (messages: any[]): PipelineContext => ({
9
+ initialState: { messages: [] },
10
+ isAborted: false,
11
+ messages,
12
+ metadata: {},
13
+ });
14
+
15
+ describe('Basic Injection', () => {
16
+ it('should inject group context before first user message', async () => {
17
+ const injector = new GroupAgentBuilderContextInjector({
18
+ enabled: true,
19
+ groupContext: {
20
+ groupId: 'grp_123',
21
+ groupTitle: 'Test Group',
22
+ members: [
23
+ { id: 'agt_1', title: 'Agent 1', isSupervisor: true },
24
+ { id: 'agt_2', title: 'Agent 2', isSupervisor: false },
25
+ ],
26
+ },
27
+ });
28
+
29
+ const context = createContext([
30
+ { role: 'system', content: 'You are a helpful assistant' },
31
+ { role: 'user', content: 'Hello' },
32
+ ]);
33
+
34
+ const result = await injector.process(context);
35
+
36
+ // Should have 3 messages now (system + injected user + original user)
37
+ expect(result.messages).toHaveLength(3);
38
+ expect(result.messages[0].role).toBe('system');
39
+ expect(result.messages[1].role).toBe('user');
40
+ expect(result.messages[1].content).toContain('<current_group_context>');
41
+ expect(result.messages[1].content).toContain('grp_123');
42
+ expect(result.messages[1].content).toContain('Test Group');
43
+ expect(result.messages[2].role).toBe('user');
44
+ expect(result.messages[2].content).toBe('Hello');
45
+ });
46
+
47
+ it('should skip injection when not enabled', async () => {
48
+ const injector = new GroupAgentBuilderContextInjector({
49
+ enabled: false,
50
+ groupContext: {
51
+ groupId: 'grp_123',
52
+ },
53
+ });
54
+
55
+ const context = createContext([
56
+ { role: 'system', content: 'You are a helpful assistant' },
57
+ { role: 'user', content: 'Hello' },
58
+ ]);
59
+
60
+ const result = await injector.process(context);
61
+
62
+ expect(result.messages).toHaveLength(2);
63
+ });
64
+
65
+ it('should skip injection when no group context provided', async () => {
66
+ const injector = new GroupAgentBuilderContextInjector({
67
+ enabled: true,
68
+ });
69
+
70
+ const context = createContext([
71
+ { role: 'system', content: 'You are a helpful assistant' },
72
+ { role: 'user', content: 'Hello' },
73
+ ]);
74
+
75
+ const result = await injector.process(context);
76
+
77
+ expect(result.messages).toHaveLength(2);
78
+ });
79
+ });
80
+
81
+ describe('Content Consolidation with UserMemoryInjector', () => {
82
+ it('should consolidate content into single user message when both injectors are used', async () => {
83
+ // First injector: UserMemoryInjector
84
+ const memoryInjector = new UserMemoryInjector({
85
+ memories: {
86
+ identities: [{ description: 'User is a developer', id: 'id_1' }],
87
+ },
88
+ });
89
+
90
+ // Second injector: GroupAgentBuilderContextInjector
91
+ const groupInjector = new GroupAgentBuilderContextInjector({
92
+ enabled: true,
93
+ groupContext: {
94
+ groupId: 'grp_123',
95
+ groupTitle: 'Dev Team',
96
+ },
97
+ });
98
+
99
+ const context = createContext([
100
+ { role: 'system', content: 'You are a helpful assistant' },
101
+ { role: 'user', content: 'Hello' },
102
+ { role: 'assistant', content: 'Hi there!' },
103
+ ]);
104
+
105
+ // Process through both injectors in order
106
+ const afterMemory = await memoryInjector.process(context);
107
+ const afterGroup = await groupInjector.process(afterMemory);
108
+
109
+ // Should have 4 messages: system + SINGLE injected user + original user + assistant
110
+ expect(afterGroup.messages).toHaveLength(4);
111
+
112
+ // Check message order
113
+ expect(afterGroup.messages[0].role).toBe('system');
114
+ expect(afterGroup.messages[1].role).toBe('user'); // Consolidated injection
115
+ expect(afterGroup.messages[2].role).toBe('user'); // Original user message
116
+ expect(afterGroup.messages[3].role).toBe('assistant');
117
+
118
+ // The consolidated message should contain BOTH user memory AND group context
119
+ const injectedMessage = afterGroup.messages[1];
120
+ expect(injectedMessage.content).toContain('<user_memory>'); // From UserMemoryInjector
121
+ expect(injectedMessage.content).toContain('<current_group_context>'); // From GroupAgentBuilderContextInjector
122
+ expect(injectedMessage.content).toContain('User is a developer');
123
+ expect(injectedMessage.content).toContain('grp_123');
124
+ expect(injectedMessage.content).toContain('Dev Team');
125
+ });
126
+
127
+ it('should work correctly when only GroupAgentBuilderContextInjector is used', async () => {
128
+ const groupInjector = new GroupAgentBuilderContextInjector({
129
+ enabled: true,
130
+ groupContext: {
131
+ groupId: 'grp_123',
132
+ groupTitle: 'Test Group',
133
+ },
134
+ });
135
+
136
+ const context = createContext([
137
+ { role: 'system', content: 'You are a helpful assistant' },
138
+ { role: 'user', content: 'Hello' },
139
+ ]);
140
+
141
+ const result = await groupInjector.process(context);
142
+
143
+ expect(result.messages).toHaveLength(3);
144
+ expect(result.messages[1].content).toContain('<current_group_context>');
145
+ });
146
+
147
+ it('should work correctly when only UserMemoryInjector is used', async () => {
148
+ const memoryInjector = new UserMemoryInjector({
149
+ memories: {
150
+ identities: [{ description: 'User is a developer', id: 'id_1' }],
151
+ },
152
+ });
153
+
154
+ const context = createContext([
155
+ { role: 'system', content: 'You are a helpful assistant' },
156
+ { role: 'user', content: 'Hello' },
157
+ ]);
158
+
159
+ const result = await memoryInjector.process(context);
160
+
161
+ expect(result.messages).toHaveLength(3);
162
+ expect(result.messages[1].content).toContain('<user_memory>');
163
+ });
164
+
165
+ it('should NOT create duplicate user messages when injectors run in sequence', async () => {
166
+ const memoryInjector = new UserMemoryInjector({
167
+ memories: {
168
+ identities: [{ description: 'Identity 1', id: 'id_1' }],
169
+ contexts: [{ title: 'Context 1', id: 'ctx_1' }],
170
+ },
171
+ });
172
+
173
+ const groupInjector = new GroupAgentBuilderContextInjector({
174
+ enabled: true,
175
+ groupContext: {
176
+ groupId: 'grp_123',
177
+ groupTitle: 'Team Alpha',
178
+ members: [
179
+ { id: 'agt_1', title: 'Alice', isSupervisor: true },
180
+ { id: 'agt_2', title: 'Bob', isSupervisor: false },
181
+ ],
182
+ config: {
183
+ systemPrompt: 'Collaborate effectively',
184
+ },
185
+ },
186
+ });
187
+
188
+ const context = createContext([
189
+ { role: 'system', content: 'System prompt' },
190
+ { role: 'user', content: 'First user message' },
191
+ { role: 'assistant', content: 'First response' },
192
+ { role: 'user', content: 'Second user message' },
193
+ ]);
194
+
195
+ // Process through both injectors
196
+ const afterMemory = await memoryInjector.process(context);
197
+ const afterGroup = await groupInjector.process(afterMemory);
198
+
199
+ // Count user messages
200
+ const userMessages = afterGroup.messages.filter((m) => m.role === 'user');
201
+
202
+ // Should have 3 user messages: 1 consolidated injection + 2 original
203
+ expect(userMessages).toHaveLength(3);
204
+
205
+ // The first user message should be the consolidated injection
206
+ expect(userMessages[0].content).toContain('<user_memory>');
207
+ expect(userMessages[0].content).toContain('<current_group_context>');
208
+
209
+ // Original messages should remain unchanged
210
+ expect(userMessages[1].content).toBe('First user message');
211
+ expect(userMessages[2].content).toBe('Second user message');
212
+ });
213
+
214
+ it('should preserve order: first injector content comes first in the consolidated message', async () => {
215
+ // UserMemoryInjector runs first
216
+ const memoryInjector = new UserMemoryInjector({
217
+ memories: {
218
+ identities: [{ description: 'Dev identity', id: 'id_1' }],
219
+ },
220
+ });
221
+
222
+ // GroupAgentBuilderContextInjector runs second
223
+ const groupInjector = new GroupAgentBuilderContextInjector({
224
+ enabled: true,
225
+ groupContext: {
226
+ groupId: 'grp_order_test',
227
+ groupTitle: 'Order Test Group',
228
+ },
229
+ });
230
+
231
+ const context = createContext([{ role: 'user', content: 'Hello' }]);
232
+
233
+ // Process in order: memory first, then group
234
+ const afterMemory = await memoryInjector.process(context);
235
+ const afterGroup = await groupInjector.process(afterMemory);
236
+
237
+ const injectedContent = afterGroup.messages[0].content as string;
238
+
239
+ // user_memory should appear BEFORE current_group_context
240
+ const memoryIndex = injectedContent.indexOf('<user_memory>');
241
+ const groupIndex = injectedContent.indexOf('<current_group_context>');
242
+
243
+ expect(memoryIndex).toBeLessThan(groupIndex);
244
+ });
245
+ });
246
+
247
+ describe('Group Context Formatting', () => {
248
+ it('should format members correctly', async () => {
249
+ const injector = new GroupAgentBuilderContextInjector({
250
+ enabled: true,
251
+ groupContext: {
252
+ members: [
253
+ {
254
+ id: 'agt_1',
255
+ title: 'Supervisor Agent',
256
+ description: 'Manages the team',
257
+ isSupervisor: true,
258
+ },
259
+ {
260
+ id: 'agt_2',
261
+ title: 'Worker Agent',
262
+ description: 'Does the work',
263
+ isSupervisor: false,
264
+ },
265
+ ],
266
+ },
267
+ });
268
+
269
+ const context = createContext([{ role: 'user', content: 'Hello' }]);
270
+
271
+ const result = await injector.process(context);
272
+
273
+ const injectedContent = result.messages[0].content;
274
+ expect(injectedContent).toContain('<group_members count="2">');
275
+ expect(injectedContent).toContain('role="supervisor"');
276
+ expect(injectedContent).toContain('role="participant"');
277
+ expect(injectedContent).toContain('Supervisor Agent');
278
+ expect(injectedContent).toContain('Worker Agent');
279
+ });
280
+
281
+ it('should format config correctly', async () => {
282
+ const injector = new GroupAgentBuilderContextInjector({
283
+ enabled: true,
284
+ groupContext: {
285
+ config: {
286
+ scene: 'collaborative',
287
+ enableSupervisor: true,
288
+ systemPrompt: 'Work together as a team',
289
+ openingMessage: 'Welcome to the team!',
290
+ openingQuestions: ['How can I help?', 'What would you like to do?'],
291
+ },
292
+ },
293
+ });
294
+
295
+ const context = createContext([{ role: 'user', content: 'Hello' }]);
296
+
297
+ const result = await injector.process(context);
298
+
299
+ const injectedContent = result.messages[0].content;
300
+ expect(injectedContent).toContain('<group_config>');
301
+ expect(injectedContent).toContain('<scene>collaborative</scene>');
302
+ expect(injectedContent).toContain('<enableSupervisor>true</enableSupervisor>');
303
+ expect(injectedContent).toContain('<openingMessage>Welcome to the team!</openingMessage>');
304
+ expect(injectedContent).toContain('<openingQuestions count="2">');
305
+ });
306
+ });
307
+ });
@@ -120,15 +120,9 @@ exports[`promptUserMemory > identities only > should format identities grouped b
120
120
  "<user_memory>
121
121
  <instruction>The following are memories about this user retrieved from previous conversations. Use this information to personalize your responses and maintain continuity.</instruction>
122
122
  <identities count="3">
123
- <personal count="1">
124
- <identity role="Father">User is a father of two children</identity>
125
- </personal>
126
- <professional count="1">
127
- <identity role="Software Engineer">User is a senior software engineer</identity>
128
- </professional>
129
- <demographic count="1">
130
- <identity>User is based in Shanghai</identity>
131
- </demographic>
123
+ <identity type="personal" role="Father" id="id-1">User is a father of two children</identity>
124
+ <identity type="professional" role="Software Engineer" id="id-2">User is a senior software engineer</identity>
125
+ <identity type="demographic" id="id-3">User is based in Shanghai</identity>
132
126
  </identities>
133
127
  </user_memory>"
134
128
  `;
@@ -137,10 +131,8 @@ exports[`promptUserMemory > identities only > should format single type identiti
137
131
  "<user_memory>
138
132
  <instruction>The following are memories about this user retrieved from previous conversations. Use this information to personalize your responses and maintain continuity.</instruction>
139
133
  <identities count="2">
140
- <demographic count="2">
141
- <identity>User is 35 years old</identity>
142
- <identity>User speaks Mandarin and English</identity>
143
- </demographic>
134
+ <identity type="demographic" id="id-1">User is 35 years old</identity>
135
+ <identity type="demographic" id="id-2">User speaks Mandarin and English</identity>
144
136
  </identities>
145
137
  </user_memory>"
146
138
  `;
@@ -149,10 +141,8 @@ exports[`promptUserMemory > identities only > should format single type identiti
149
141
  "<user_memory>
150
142
  <instruction>The following are memories about this user retrieved from previous conversations. Use this information to personalize your responses and maintain continuity.</instruction>
151
143
  <identities count="2">
152
- <personal count="2">
153
- <identity>User is married</identity>
154
- <identity>User has a pet dog</identity>
155
- </personal>
144
+ <identity type="personal" id="id-1">User is married</identity>
145
+ <identity type="personal" id="id-2">User has a pet dog</identity>
156
146
  </identities>
157
147
  </user_memory>"
158
148
  `;
@@ -161,9 +151,7 @@ exports[`promptUserMemory > identities only > should format single type identiti
161
151
  "<user_memory>
162
152
  <instruction>The following are memories about this user retrieved from previous conversations. Use this information to personalize your responses and maintain continuity.</instruction>
163
153
  <identities count="1">
164
- <professional count="1">
165
- <identity role="CTO">User works at a tech startup</identity>
166
- </professional>
154
+ <identity type="professional" role="CTO" id="id-1">User works at a tech startup</identity>
167
155
  </identities>
168
156
  </user_memory>"
169
157
  `;
@@ -172,9 +160,7 @@ exports[`promptUserMemory > identities only > should handle identity with null v
172
160
  "<user_memory>
173
161
  <instruction>The following are memories about this user retrieved from previous conversations. Use this information to personalize your responses and maintain continuity.</instruction>
174
162
  <identities count="1">
175
- <personal count="1">
176
- <identity></identity>
177
- </personal>
163
+ <identity type="personal" id="id-1"></identity>
178
164
  </identities>
179
165
  </user_memory>"
180
166
  `;
@@ -183,9 +169,7 @@ exports[`promptUserMemory > identities only > should handle identity without rol
183
169
  "<user_memory>
184
170
  <instruction>The following are memories about this user retrieved from previous conversations. Use this information to personalize your responses and maintain continuity.</instruction>
185
171
  <identities count="1">
186
- <personal count="1">
187
- <identity>User enjoys hiking</identity>
188
- </personal>
172
+ <identity type="personal" id="id-1">User enjoys hiking</identity>
189
173
  </identities>
190
174
  </user_memory>"
191
175
  `;
@@ -194,9 +178,7 @@ exports[`promptUserMemory > mixed memory types > should format all memory types
194
178
  "<user_memory>
195
179
  <instruction>The following are memories about this user retrieved from previous conversations. Use this information to personalize your responses and maintain continuity.</instruction>
196
180
  <identities count="1">
197
- <professional count="1">
198
- <identity role="Tech Lead">User is a tech lead at a startup</identity>
199
- </professional>
181
+ <identity type="professional" role="Tech Lead" id="id-1">User is a tech lead at a startup</identity>
200
182
  </identities>
201
183
  <contexts count="1">
202
184
  <context id="ctx-1" title="Experience Level">Senior developer with 10 years experience</context>
@@ -217,15 +199,9 @@ exports[`promptUserMemory > mixed memory types > should format all memory types
217
199
  "<user_memory>
218
200
  <instruction>The following are memories about this user retrieved from previous conversations. Use this information to personalize your responses and maintain continuity.</instruction>
219
201
  <identities count="3">
220
- <personal count="1">
221
- <identity role="Father">User is a father</identity>
222
- </personal>
223
- <professional count="1">
224
- <identity role="Senior Engineer">User is a senior engineer</identity>
225
- </professional>
226
- <demographic count="1">
227
- <identity>User lives in Beijing</identity>
228
- </demographic>
202
+ <identity type="personal" role="Father" id="id-1">User is a father</identity>
203
+ <identity type="professional" role="Senior Engineer" id="id-2">User is a senior engineer</identity>
204
+ <identity type="demographic" id="id-3">User lives in Beijing</identity>
229
205
  </identities>
230
206
  <contexts count="1">
231
207
  <context id="ctx-1" title="Current Work">Working on AI products</context>
@@ -98,29 +98,10 @@ const isValidIdentityItem = (item: UserMemoryIdentityItem): boolean => {
98
98
  * Formats a single identity memory item
99
99
  */
100
100
  const formatIdentityItem = (item: UserMemoryIdentityItem): string => {
101
+ const typeAttr = item.type ? ` type="${item.type}"` : '';
101
102
  const roleAttr = item.role ? ` role="${item.role}"` : '';
102
- return ` <identity${roleAttr}>${item.description || ''}</identity>`;
103
- };
104
-
105
- /**
106
- * Format identities grouped by type as XML
107
- * Types: personal (角色), professional (职业), demographic (属性)
108
- */
109
- const formatIdentitiesSection = (identities: UserMemoryIdentityItem[]): string => {
110
- const personal = identities.filter((i) => i.type === 'personal');
111
- const professional = identities.filter((i) => i.type === 'professional');
112
- const demographic = identities.filter((i) => i.type === 'demographic');
113
-
114
- return [
115
- personal.length > 0 &&
116
- ` <personal count="${personal.length}">\n${personal.map(formatIdentityItem).join('\n')}\n </personal>`,
117
- professional.length > 0 &&
118
- ` <professional count="${professional.length}">\n${professional.map(formatIdentityItem).join('\n')}\n </professional>`,
119
- demographic.length > 0 &&
120
- ` <demographic count="${demographic.length}">\n${demographic.map(formatIdentityItem).join('\n')}\n </demographic>`,
121
- ]
122
- .filter(Boolean)
123
- .join('\n');
103
+ const idAttr = item.id ? ` id="${item.id}"` : '';
104
+ return ` <identity${typeAttr}${roleAttr}${idAttr}>${item.description || ''}</identity>`;
124
105
  };
125
106
 
126
107
  /**
@@ -156,9 +137,9 @@ export const promptUserMemory = ({ memories }: PromptUserMemoryOptions): string
156
137
  '<instruction>The following are memories about this user retrieved from previous conversations. Use this information to personalize your responses and maintain continuity.</instruction>',
157
138
  );
158
139
 
159
- // Add identities section (user's identity information, grouped by type)
140
+ // Add identities section (user's identity information)
160
141
  if (hasIdentities) {
161
- const identitiesXml = formatIdentitiesSection(identities);
142
+ const identitiesXml = identities.map((item) => formatIdentityItem(item)).join('\n');
162
143
  contentParts.push(`<identities count="${identities.length}">
163
144
  ${identitiesXml}
164
145
  </identities>`);