@lobehub/lobehub 2.0.0-next.287 → 2.0.0-next.288

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 (59) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/locales/en-US/plugin.json +3 -5
  4. package/locales/zh-CN/plugin.json +3 -5
  5. package/locales/zh-CN/tool.json +2 -0
  6. package/package.json +1 -1
  7. package/packages/builtin-agents/src/agents/group-supervisor/index.ts +12 -1
  8. package/packages/builtin-agents/src/agents/group-supervisor/systemRole.ts +0 -7
  9. package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/EditLocalFile/index.tsx +93 -0
  10. package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/GlobLocalFiles/index.tsx +73 -0
  11. package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/GrepContent/index.tsx +69 -0
  12. package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/ListLocalFiles/index.tsx +68 -0
  13. package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/ReadLocalFile/index.tsx +74 -0
  14. package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/SearchLocalFiles/index.tsx +70 -0
  15. package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/WriteLocalFile/index.tsx +57 -0
  16. package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/index.ts +14 -0
  17. package/packages/builtin-tool-cloud-sandbox/src/client/Render/WriteFile/index.tsx +54 -35
  18. package/packages/builtin-tool-cloud-sandbox/src/client/components/FilePathDisplay.tsx +52 -0
  19. package/packages/builtin-tool-group-management/src/client/Inspector/ExecuteTasks/index.tsx +90 -0
  20. package/packages/builtin-tool-group-management/src/client/Inspector/index.ts +2 -0
  21. package/packages/builtin-tool-group-management/src/client/Intervention/ExecuteTasks.tsx +237 -0
  22. package/packages/builtin-tool-group-management/src/client/Intervention/index.ts +4 -1
  23. package/packages/builtin-tool-group-management/src/client/Render/index.ts +1 -1
  24. package/packages/builtin-tool-group-management/src/client/Streaming/ExecuteTask/index.tsx +69 -0
  25. package/packages/builtin-tool-group-management/src/client/Streaming/ExecuteTasks/index.tsx +87 -0
  26. package/packages/builtin-tool-group-management/src/client/Streaming/index.ts +4 -0
  27. package/packages/builtin-tool-group-management/src/executor.test.ts +8 -311
  28. package/packages/builtin-tool-group-management/src/executor.ts +5 -160
  29. package/packages/builtin-tool-group-management/src/manifest.ts +50 -94
  30. package/packages/builtin-tool-group-management/src/systemRole.ts +251 -172
  31. package/packages/builtin-tool-group-management/src/types.ts +29 -40
  32. package/packages/context-engine/src/engine/messages/MessagesEngine.ts +6 -4
  33. package/packages/context-engine/src/engine/messages/types.ts +4 -4
  34. package/packages/context-engine/src/processors/GroupRoleTransform.ts +261 -0
  35. package/packages/context-engine/src/processors/__tests__/GroupRoleTransform.test.ts +553 -0
  36. package/packages/context-engine/src/processors/index.ts +2 -2
  37. package/packages/context-engine/src/providers/__tests__/GroupContextInjector.test.ts +4 -16
  38. package/packages/context-engine/src/providers/__tests__/__snapshots__/GroupContextInjector.test.ts.snap +23 -28
  39. package/packages/prompts/src/prompts/agentGroup/__snapshots__/index.test.ts.snap +0 -7
  40. package/packages/prompts/src/prompts/agentGroup/groupContext.ts +0 -7
  41. package/src/app/[variants]/(main)/group/features/Conversation/AgentWelcome/OpeningQuestions.tsx +4 -8
  42. package/src/app/[variants]/(main)/group/features/Conversation/MainChatInput/GroupChat.tsx +0 -3
  43. package/src/app/[variants]/(main)/group/features/Conversation/useGroupContext.ts +3 -0
  44. package/src/features/ChatInput/Desktop/index.tsx +1 -3
  45. package/src/features/Conversation/store/slices/message/action/crud.ts +2 -2
  46. package/src/locales/default/plugin.ts +3 -5
  47. package/src/locales/default/tool.ts +3 -0
  48. package/src/services/chat/mecha/agentConfigResolver.test.ts +160 -0
  49. package/src/services/chat/mecha/agentConfigResolver.ts +15 -3
  50. package/src/services/chat/mecha/contextEngineering.ts +2 -1
  51. package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +4 -2
  52. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +2 -0
  53. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +1 -18
  54. package/src/store/chat/slices/message/selectors/displayMessage.test.ts +24 -0
  55. package/src/store/chat/slices/message/selectors/displayMessage.ts +6 -1
  56. package/src/store/chat/slices/topic/action.test.ts +10 -4
  57. package/src/store/chat/slices/topic/action.ts +3 -2
  58. package/packages/context-engine/src/processors/GroupMessageSender.ts +0 -138
  59. package/packages/context-engine/src/processors/__tests__/GroupMessageSender.test.ts +0 -274
@@ -0,0 +1,553 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import type { PipelineContext } from '../../types';
4
+ import { GroupRoleTransformProcessor } from '../GroupRoleTransform';
5
+
6
+ describe('GroupRoleTransformProcessor', () => {
7
+ const createContext = (messages: any[]): PipelineContext => ({
8
+ initialState: { messages: [] },
9
+ isAborted: false,
10
+ messages,
11
+ metadata: {},
12
+ });
13
+
14
+ const defaultConfig = {
15
+ agentMap: {
16
+ 'agent-a': { name: 'Agent A', role: 'participant' as const },
17
+ 'agent-b': { name: 'Agent B', role: 'participant' as const },
18
+ 'supervisor': { name: 'Supervisor', role: 'supervisor' as const },
19
+ },
20
+ currentAgentId: 'agent-a',
21
+ };
22
+
23
+ describe('assistant message transformation', () => {
24
+ it('should keep current agent messages as assistant', async () => {
25
+ const processor = new GroupRoleTransformProcessor(defaultConfig);
26
+ const context = createContext([
27
+ { content: 'Hello', role: 'user' },
28
+ { agentId: 'agent-a', content: 'Response from current agent', role: 'assistant' },
29
+ ]);
30
+
31
+ const result = await processor.process(context);
32
+
33
+ expect(result.messages).toHaveLength(2);
34
+ expect(result.messages[1].role).toBe('assistant');
35
+ expect(result.messages[1].content).toBe('Response from current agent');
36
+ });
37
+
38
+ it('should transform other agent messages to user with speaker tag', async () => {
39
+ const processor = new GroupRoleTransformProcessor(defaultConfig);
40
+ const context = createContext([
41
+ { content: 'Hello', role: 'user' },
42
+ { agentId: 'agent-b', content: 'Response from other agent', role: 'assistant' },
43
+ ]);
44
+
45
+ const result = await processor.process(context);
46
+
47
+ expect(result.messages).toHaveLength(2);
48
+ expect(result.messages[1].role).toBe('user');
49
+ expect(result.messages[1].content).toContain('<speaker name="Agent B" />');
50
+ expect(result.messages[1].content).toContain('Response from other agent');
51
+ });
52
+
53
+ it('should transform supervisor messages to user when current agent is participant', async () => {
54
+ const processor = new GroupRoleTransformProcessor(defaultConfig);
55
+ const context = createContext([
56
+ { content: 'Hello', role: 'user' },
57
+ { agentId: 'supervisor', content: 'Supervisor response', role: 'assistant' },
58
+ ]);
59
+
60
+ const result = await processor.process(context);
61
+
62
+ expect(result.messages).toHaveLength(2);
63
+ expect(result.messages[1].role).toBe('user');
64
+ expect(result.messages[1].content).toContain('<speaker name="Supervisor" />');
65
+ });
66
+
67
+ it('should keep assistant messages without agentId unchanged', async () => {
68
+ const processor = new GroupRoleTransformProcessor(defaultConfig);
69
+ const context = createContext([
70
+ { content: 'Hello', role: 'user' },
71
+ { content: 'Response without agentId', role: 'assistant' },
72
+ ]);
73
+
74
+ const result = await processor.process(context);
75
+
76
+ expect(result.messages).toHaveLength(2);
77
+ expect(result.messages[1].role).toBe('assistant');
78
+ expect(result.messages[1].content).toBe('Response without agentId');
79
+ });
80
+ });
81
+
82
+ describe('assistant message with tools', () => {
83
+ it('should transform other agent message with tools to user with tool_use section', async () => {
84
+ const processor = new GroupRoleTransformProcessor(defaultConfig);
85
+ const context = createContext([
86
+ { content: 'Hello', role: 'user' },
87
+ {
88
+ agentId: 'agent-b',
89
+ content: 'Let me check the weather',
90
+ role: 'assistant',
91
+ tools: [
92
+ {
93
+ apiName: 'getWeather',
94
+ arguments: '{"city": "Beijing"}',
95
+ id: 'call_123',
96
+ identifier: 'weather-plugin',
97
+ },
98
+ ],
99
+ },
100
+ ]);
101
+
102
+ const result = await processor.process(context);
103
+
104
+ expect(result.messages).toHaveLength(2);
105
+ expect(result.messages[1].role).toBe('user');
106
+ expect(result.messages[1].content).toContain('<speaker name="Agent B" />');
107
+ expect(result.messages[1].content).toContain('Let me check the weather');
108
+ expect(result.messages[1].content).toContain('<tool_use>');
109
+ expect(result.messages[1].content).toContain(
110
+ '<tool id="call_123" name="weather-plugin.getWeather">',
111
+ );
112
+ expect(result.messages[1].content).toContain('{"city": "Beijing"}');
113
+ expect(result.messages[1].content).toContain('</tool>');
114
+ expect(result.messages[1].content).toContain('</tool_use>');
115
+ expect(result.messages[1].tools).toBeUndefined();
116
+ });
117
+
118
+ it('should transform other agent message with multiple tools', async () => {
119
+ const processor = new GroupRoleTransformProcessor(defaultConfig);
120
+ const context = createContext([
121
+ {
122
+ agentId: 'agent-b',
123
+ content: 'Checking multiple things',
124
+ role: 'assistant',
125
+ tools: [
126
+ {
127
+ apiName: 'getWeather',
128
+ arguments: '{"city": "Beijing"}',
129
+ id: 'call_1',
130
+ identifier: 'weather',
131
+ },
132
+ {
133
+ apiName: 'getTime',
134
+ arguments: '{"timezone": "UTC"}',
135
+ id: 'call_2',
136
+ identifier: 'time',
137
+ },
138
+ ],
139
+ },
140
+ ]);
141
+
142
+ const result = await processor.process(context);
143
+
144
+ expect(result.messages[0].role).toBe('user');
145
+ expect(result.messages[0].content).toContain('<tool id="call_1" name="weather.getWeather">');
146
+ expect(result.messages[0].content).toContain('<tool id="call_2" name="time.getTime">');
147
+ });
148
+
149
+ it('should keep current agent message with tools as assistant', async () => {
150
+ const processor = new GroupRoleTransformProcessor(defaultConfig);
151
+ const context = createContext([
152
+ {
153
+ agentId: 'agent-a',
154
+ content: 'Current agent using tools',
155
+ role: 'assistant',
156
+ tools: [
157
+ {
158
+ apiName: 'search',
159
+ arguments: '{"q": "test"}',
160
+ id: 'call_xyz',
161
+ identifier: 'search-plugin',
162
+ },
163
+ ],
164
+ },
165
+ ]);
166
+
167
+ const result = await processor.process(context);
168
+
169
+ expect(result.messages[0].role).toBe('assistant');
170
+ expect(result.messages[0].tools).toBeDefined();
171
+ expect(result.messages[0].content).not.toContain('<speaker');
172
+ });
173
+ });
174
+
175
+ describe('tool message transformation', () => {
176
+ it('should transform other agent tool messages to user with tool_result', async () => {
177
+ const processor = new GroupRoleTransformProcessor(defaultConfig);
178
+ const context = createContext([
179
+ {
180
+ agentId: 'agent-b',
181
+ content: '{"temperature": 25}',
182
+ plugin: {
183
+ apiName: 'getWeather',
184
+ identifier: 'weather-plugin',
185
+ },
186
+ role: 'tool',
187
+ tool_call_id: 'call_123',
188
+ },
189
+ ]);
190
+
191
+ const result = await processor.process(context);
192
+
193
+ expect(result.messages[0].role).toBe('user');
194
+ expect(result.messages[0].content).toContain('<speaker name="Agent B" />');
195
+ expect(result.messages[0].content).toContain(
196
+ '<tool_result id="call_123" name="weather-plugin.getWeather">',
197
+ );
198
+ expect(result.messages[0].content).toContain('{"temperature": 25}');
199
+ expect(result.messages[0].content).toContain('</tool_result>');
200
+ expect(result.messages[0].tool_call_id).toBeUndefined();
201
+ expect(result.messages[0].plugin).toBeUndefined();
202
+ });
203
+
204
+ it('should keep current agent tool messages unchanged', async () => {
205
+ const processor = new GroupRoleTransformProcessor(defaultConfig);
206
+ const context = createContext([
207
+ {
208
+ agentId: 'agent-a',
209
+ content: '{"result": "data"}',
210
+ plugin: {
211
+ apiName: 'search',
212
+ identifier: 'search-plugin',
213
+ },
214
+ role: 'tool',
215
+ tool_call_id: 'call_abc',
216
+ },
217
+ ]);
218
+
219
+ const result = await processor.process(context);
220
+
221
+ expect(result.messages[0].role).toBe('tool');
222
+ expect(result.messages[0].tool_call_id).toBe('call_abc');
223
+ expect(result.messages[0].content).not.toContain('<speaker');
224
+ });
225
+
226
+ it('should keep tool messages without agentId unchanged', async () => {
227
+ const processor = new GroupRoleTransformProcessor(defaultConfig);
228
+ const context = createContext([
229
+ {
230
+ content: '{"result": "data"}',
231
+ role: 'tool',
232
+ tool_call_id: 'call_abc',
233
+ },
234
+ ]);
235
+
236
+ const result = await processor.process(context);
237
+
238
+ expect(result.messages[0].role).toBe('tool');
239
+ expect(result.messages[0].tool_call_id).toBe('call_abc');
240
+ });
241
+ });
242
+
243
+ describe('mixed conversation', () => {
244
+ it('should handle mixed conversation with multiple agents', async () => {
245
+ const processor = new GroupRoleTransformProcessor(defaultConfig);
246
+ const context = createContext([
247
+ { content: 'User question', role: 'user' },
248
+ { agentId: 'supervisor', content: 'Supervisor intro', role: 'assistant' },
249
+ { agentId: 'agent-b', content: 'Agent B response', role: 'assistant' },
250
+ { agentId: 'agent-a', content: 'Current agent response', role: 'assistant' },
251
+ ]);
252
+
253
+ const result = await processor.process(context);
254
+
255
+ expect(result.messages).toHaveLength(4);
256
+ // User message unchanged
257
+ expect(result.messages[0].role).toBe('user');
258
+ expect(result.messages[0].content).toBe('User question');
259
+ // Supervisor transformed to user
260
+ expect(result.messages[1].role).toBe('user');
261
+ expect(result.messages[1].content).toContain('<speaker name="Supervisor" />');
262
+ // Agent B transformed to user
263
+ expect(result.messages[2].role).toBe('user');
264
+ expect(result.messages[2].content).toContain('<speaker name="Agent B" />');
265
+ // Current agent stays as assistant
266
+ expect(result.messages[3].role).toBe('assistant');
267
+ expect(result.messages[3].content).toBe('Current agent response');
268
+ });
269
+
270
+ it('should handle tool call and result from other agent', async () => {
271
+ const processor = new GroupRoleTransformProcessor(defaultConfig);
272
+ const context = createContext([
273
+ { content: 'Check weather', role: 'user' },
274
+ {
275
+ agentId: 'agent-b',
276
+ content: 'Checking...',
277
+ role: 'assistant',
278
+ tools: [{ apiName: 'get', arguments: '{}', id: 'call_1', identifier: 'weather' }],
279
+ },
280
+ {
281
+ agentId: 'agent-b',
282
+ content: '{"temp": 25}',
283
+ plugin: { apiName: 'get', identifier: 'weather' },
284
+ role: 'tool',
285
+ tool_call_id: 'call_1',
286
+ },
287
+ { agentId: 'agent-a', content: 'Based on the weather...', role: 'assistant' },
288
+ ]);
289
+
290
+ const result = await processor.process(context);
291
+
292
+ expect(result.messages).toHaveLength(4);
293
+ // User unchanged
294
+ expect(result.messages[0].role).toBe('user');
295
+ // Agent B assistant with tool -> user
296
+ expect(result.messages[1].role).toBe('user');
297
+ expect(result.messages[1].content).toContain('<tool_use>');
298
+ // Agent B tool result -> user
299
+ expect(result.messages[2].role).toBe('user');
300
+ expect(result.messages[2].content).toContain('<tool_result');
301
+ // Current agent -> assistant
302
+ expect(result.messages[3].role).toBe('assistant');
303
+ });
304
+ });
305
+
306
+ describe('edge cases', () => {
307
+ it('should skip processing when no currentAgentId provided', async () => {
308
+ const processor = new GroupRoleTransformProcessor({
309
+ agentMap: defaultConfig.agentMap,
310
+ currentAgentId: '',
311
+ });
312
+ const context = createContext([{ agentId: 'agent-b', content: 'Test', role: 'assistant' }]);
313
+
314
+ const result = await processor.process(context);
315
+
316
+ expect(result.messages[0].role).toBe('assistant');
317
+ });
318
+
319
+ it('should skip processing when no agentMap provided', async () => {
320
+ const processor = new GroupRoleTransformProcessor({
321
+ agentMap: {},
322
+ currentAgentId: 'agent-a',
323
+ });
324
+ const context = createContext([{ agentId: 'agent-b', content: 'Test', role: 'assistant' }]);
325
+
326
+ const result = await processor.process(context);
327
+
328
+ // No agentMap entry for agent-b, so message is unchanged
329
+ expect(result.messages[0].role).toBe('assistant');
330
+ });
331
+
332
+ it('should handle empty messages array', async () => {
333
+ const processor = new GroupRoleTransformProcessor(defaultConfig);
334
+ const context = createContext([]);
335
+
336
+ const result = await processor.process(context);
337
+
338
+ expect(result.messages).toHaveLength(0);
339
+ });
340
+
341
+ it('should handle message with empty content', async () => {
342
+ const processor = new GroupRoleTransformProcessor(defaultConfig);
343
+ const context = createContext([{ agentId: 'agent-b', content: '', role: 'assistant' }]);
344
+
345
+ const result = await processor.process(context);
346
+
347
+ expect(result.messages[0].role).toBe('user');
348
+ expect(result.messages[0].content).toContain('<speaker name="Agent B" />');
349
+ });
350
+
351
+ it('should handle array content (multimodal)', async () => {
352
+ const processor = new GroupRoleTransformProcessor(defaultConfig);
353
+ const context = createContext([
354
+ {
355
+ agentId: 'agent-b',
356
+ content: [
357
+ { text: 'Here is the image analysis', type: 'text' },
358
+ { image_url: 'data:image/png;base64,...', type: 'image_url' },
359
+ ],
360
+ role: 'assistant',
361
+ },
362
+ ]);
363
+
364
+ const result = await processor.process(context);
365
+
366
+ expect(result.messages[0].role).toBe('user');
367
+ expect(result.messages[0].content).toContain('<speaker name="Agent B" />');
368
+ expect(result.messages[0].content).toContain('Here is the image analysis');
369
+ });
370
+
371
+ it('should track transformation counts in metadata', async () => {
372
+ const processor = new GroupRoleTransformProcessor(defaultConfig);
373
+ const context = createContext([
374
+ { agentId: 'agent-b', content: 'Msg 1', role: 'assistant' },
375
+ { agentId: 'agent-b', content: 'Result', role: 'tool', tool_call_id: 'call_1' },
376
+ { agentId: 'agent-a', content: 'Current', role: 'assistant' },
377
+ ]);
378
+
379
+ const result = await processor.process(context);
380
+
381
+ expect(result.metadata.groupRoleTransformProcessed).toEqual({
382
+ assistantTransformed: 1,
383
+ toolTransformed: 1,
384
+ });
385
+ });
386
+ });
387
+
388
+ describe('custom tool name generator', () => {
389
+ it('should use custom genToolName function', async () => {
390
+ const processor = new GroupRoleTransformProcessor({
391
+ ...defaultConfig,
392
+ genToolName: (identifier, apiName) => `custom_${identifier}_${apiName}`,
393
+ });
394
+ const context = createContext([
395
+ {
396
+ agentId: 'agent-b',
397
+ content: 'Test',
398
+ role: 'assistant',
399
+ tools: [{ apiName: 'search', arguments: '{}', id: 'call_1', identifier: 'plugin' }],
400
+ },
401
+ ]);
402
+
403
+ const result = await processor.process(context);
404
+
405
+ expect(result.messages[0].content).toContain('name="custom_plugin_search"');
406
+ });
407
+ });
408
+
409
+ describe('comprehensive end-to-end transformation', () => {
410
+ it('should correctly transform a full group conversation with 8 messages', async () => {
411
+ const processor = new GroupRoleTransformProcessor({
412
+ agentMap: {
413
+ 'agent-a': { name: 'Travel Advisor', role: 'participant' },
414
+ 'agent-b': { name: 'Weather Expert', role: 'participant' },
415
+ 'agent-c': { name: 'Food Critic', role: 'participant' },
416
+ 'supervisor': { name: 'Supervisor', role: 'supervisor' },
417
+ },
418
+ currentAgentId: 'agent-a',
419
+ });
420
+
421
+ const inputMessages = [
422
+ // 1. User's original question
423
+ { content: '帮我规划一下明天去杭州的行程', id: 'msg_1', role: 'user' },
424
+ // 2. Supervisor's broadcast instruction (from orchestration)
425
+ {
426
+ agentId: 'supervisor',
427
+ content: '请各位专家从自己的角度给出建议',
428
+ id: 'msg_2',
429
+ role: 'assistant',
430
+ },
431
+ // 3. Weather Expert's response with tool
432
+ {
433
+ agentId: 'agent-b',
434
+ content: '让我查一下杭州明天的天气',
435
+ id: 'msg_3',
436
+ role: 'assistant',
437
+ tools: [
438
+ {
439
+ apiName: 'getWeather',
440
+ arguments: '{"city": "杭州", "date": "tomorrow"}',
441
+ id: 'call_weather_1',
442
+ identifier: 'weather-plugin',
443
+ },
444
+ ],
445
+ },
446
+ // 4. Weather tool result
447
+ {
448
+ agentId: 'agent-b',
449
+ content: '{"temperature": 22, "weather": "多云", "humidity": 65}',
450
+ id: 'msg_4',
451
+ plugin: { apiName: 'getWeather', identifier: 'weather-plugin' },
452
+ role: 'tool',
453
+ tool_call_id: 'call_weather_1',
454
+ },
455
+ // 5. Weather Expert's final response
456
+ {
457
+ agentId: 'agent-b',
458
+ content: '明天杭州天气不错,22度多云,适合出行',
459
+ id: 'msg_5',
460
+ role: 'assistant',
461
+ },
462
+ // 6. Food Critic's response
463
+ {
464
+ agentId: 'agent-c',
465
+ content: '推荐去楼外楼吃正宗的西湖醋鱼',
466
+ id: 'msg_6',
467
+ role: 'assistant',
468
+ },
469
+ // 7. Current agent (Travel Advisor)'s response - should stay as assistant
470
+ {
471
+ agentId: 'agent-a',
472
+ content: '综合以上建议,我来帮你规划具体行程',
473
+ id: 'msg_7',
474
+ role: 'assistant',
475
+ },
476
+ // 8. Follow-up user question
477
+ { content: '还有什么景点推荐吗?', id: 'msg_8', role: 'user' },
478
+ ];
479
+
480
+ const context = createContext(inputMessages);
481
+ const result = await processor.process(context);
482
+
483
+ // Verify the entire output array
484
+ expect(result.messages).toEqual([
485
+ // 1. User message - unchanged
486
+ { content: '帮我规划一下明天去杭州的行程', id: 'msg_1', role: 'user' },
487
+ // 2. Supervisor -> user with speaker tag
488
+ {
489
+ agentId: 'supervisor',
490
+ content: '<speaker name="Supervisor" />\n请各位专家从自己的角度给出建议',
491
+ id: 'msg_2',
492
+ role: 'user',
493
+ },
494
+ // 3. Weather Expert with tool -> user with speaker + tool_use
495
+ {
496
+ agentId: 'agent-b',
497
+ content: `<speaker name="Weather Expert" />
498
+ 让我查一下杭州明天的天气
499
+
500
+ <tool_use>
501
+ <tool id="call_weather_1" name="weather-plugin.getWeather">
502
+ {"city": "杭州", "date": "tomorrow"}
503
+ </tool>
504
+ </tool_use>`,
505
+ id: 'msg_3',
506
+ role: 'user',
507
+ tools: undefined,
508
+ },
509
+ // 4. Tool result -> user with speaker + tool_result
510
+ {
511
+ agentId: 'agent-b',
512
+ content: `<speaker name="Weather Expert" />
513
+ <tool_result id="call_weather_1" name="weather-plugin.getWeather">
514
+ {"temperature": 22, "weather": "多云", "humidity": 65}
515
+ </tool_result>`,
516
+ id: 'msg_4',
517
+ plugin: undefined,
518
+ role: 'user',
519
+ tool_call_id: undefined,
520
+ },
521
+ // 5. Weather Expert's final response -> user with speaker
522
+ {
523
+ agentId: 'agent-b',
524
+ content: '<speaker name="Weather Expert" />\n明天杭州天气不错,22度多云,适合出行',
525
+ id: 'msg_5',
526
+ role: 'user',
527
+ },
528
+ // 6. Food Critic -> user with speaker
529
+ {
530
+ agentId: 'agent-c',
531
+ content: '<speaker name="Food Critic" />\n推荐去楼外楼吃正宗的西湖醋鱼',
532
+ id: 'msg_6',
533
+ role: 'user',
534
+ },
535
+ // 7. Current agent (Travel Advisor) - stays as assistant, no speaker tag
536
+ {
537
+ agentId: 'agent-a',
538
+ content: '综合以上建议,我来帮你规划具体行程',
539
+ id: 'msg_7',
540
+ role: 'assistant',
541
+ },
542
+ // 8. User message - unchanged
543
+ { content: '还有什么景点推荐吗?', id: 'msg_8', role: 'user' },
544
+ ]);
545
+
546
+ // Verify metadata
547
+ expect(result.metadata.groupRoleTransformProcessed).toEqual({
548
+ assistantTransformed: 4, // supervisor, agent-b x2, agent-c
549
+ toolTransformed: 1, // weather tool result
550
+ });
551
+ });
552
+ });
553
+ });
@@ -1,7 +1,7 @@
1
1
  // Transformer processors
2
2
  export { AgentCouncilFlattenProcessor } from './AgentCouncilFlatten';
3
3
  export { GroupMessageFlattenProcessor } from './GroupMessageFlatten';
4
- export { GroupMessageSenderProcessor } from './GroupMessageSender';
4
+ export { GroupRoleTransformProcessor } from './GroupRoleTransform';
5
5
  export { HistoryTruncateProcessor } from './HistoryTruncate';
6
6
  export { InputTemplateProcessor } from './InputTemplate';
7
7
  export { MessageCleanupProcessor } from './MessageCleanup';
@@ -19,7 +19,7 @@ export { ToolCallProcessor } from './ToolCall';
19
19
  export { ToolMessageReorder } from './ToolMessageReorder';
20
20
 
21
21
  // Re-export types
22
- export type { AgentInfo, GroupMessageSenderConfig } from './GroupMessageSender';
22
+ export type { AgentInfo, GroupRoleTransformConfig } from './GroupRoleTransform';
23
23
  export type { HistoryTruncateConfig } from './HistoryTruncate';
24
24
  export type { InputTemplateConfig } from './InputTemplate';
25
25
  export type { MessageContentConfig, UserMessageContentPart } from './MessageContent';
@@ -57,11 +57,6 @@ describe('GroupContextInjector', () => {
57
57
  expect(systemContent).toContain('<member name="Writer" id="agt_writer" />');
58
58
  expect(systemContent).toContain('<member name="Editor" id="agt_editor" you="true" />');
59
59
 
60
- // Output rules section (important for LOBE-1866)
61
- expect(systemContent).toContain('<critical_output_rules>');
62
- expect(systemContent).toContain('<group_context>');
63
- expect(systemContent).toContain('NEVER start your response with');
64
-
65
60
  // Identity rules
66
61
  expect(systemContent).toContain('<identity_rules>');
67
62
  expect(systemContent).toContain('NEVER expose or display agent IDs');
@@ -156,8 +151,8 @@ describe('GroupContextInjector', () => {
156
151
  });
157
152
  });
158
153
 
159
- describe('Output Rules Section (LOBE-1866)', () => {
160
- it('should always include critical output rules', async () => {
154
+ describe('Identity Rules Section', () => {
155
+ it('should always include identity rules', async () => {
161
156
  const injector = new GroupContextInjector({
162
157
  enabled: true,
163
158
  // Minimal config
@@ -170,15 +165,8 @@ describe('GroupContextInjector', () => {
170
165
 
171
166
  const systemContent = result.messages[0].content;
172
167
 
173
- // Even with minimal config, critical output rules should be present
174
- expect(systemContent).toContain('<critical_output_rules>');
175
- expect(systemContent).toContain('Your responses must contain ONLY your actual reply content');
176
- expect(systemContent).toContain('NEVER start your response with');
177
- expect(systemContent).toContain('<speaker');
178
-
179
- // Identity rules should also be present
180
- expect(systemContent).toContain('<identity_rules>');
181
- expect(systemContent).toContain('NEVER expose or display agent IDs');
168
+ // Even with minimal config, identity rules should be present
169
+ expect(systemContent).toMatchSnapshot();
182
170
  });
183
171
  });
184
172