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

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 (62) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -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 +22 -4
  33. package/packages/context-engine/src/engine/messages/types.ts +4 -4
  34. package/packages/context-engine/src/processors/GroupOrchestrationFilter.ts +211 -0
  35. package/packages/context-engine/src/processors/GroupRoleTransform.ts +261 -0
  36. package/packages/context-engine/src/processors/__tests__/GroupOrchestrationFilter.test.ts +770 -0
  37. package/packages/context-engine/src/processors/__tests__/GroupRoleTransform.test.ts +553 -0
  38. package/packages/context-engine/src/processors/index.ts +7 -2
  39. package/packages/context-engine/src/providers/__tests__/GroupContextInjector.test.ts +4 -16
  40. package/packages/context-engine/src/providers/__tests__/__snapshots__/GroupContextInjector.test.ts.snap +23 -28
  41. package/packages/prompts/src/prompts/agentGroup/__snapshots__/index.test.ts.snap +0 -7
  42. package/packages/prompts/src/prompts/agentGroup/groupContext.ts +0 -7
  43. package/src/app/[variants]/(main)/group/features/Conversation/AgentWelcome/OpeningQuestions.tsx +4 -8
  44. package/src/app/[variants]/(main)/group/features/Conversation/MainChatInput/GroupChat.tsx +0 -3
  45. package/src/app/[variants]/(main)/group/features/Conversation/useGroupContext.ts +3 -0
  46. package/src/features/ChatInput/Desktop/index.tsx +1 -3
  47. package/src/features/Conversation/store/slices/message/action/crud.ts +2 -2
  48. package/src/locales/default/plugin.ts +3 -5
  49. package/src/locales/default/tool.ts +3 -0
  50. package/src/services/chat/mecha/agentConfigResolver.test.ts +160 -0
  51. package/src/services/chat/mecha/agentConfigResolver.ts +15 -3
  52. package/src/services/chat/mecha/contextEngineering.ts +2 -1
  53. package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +4 -2
  54. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +2 -0
  55. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +1 -18
  56. package/src/store/chat/slices/message/selectors/displayMessage.test.ts +24 -0
  57. package/src/store/chat/slices/message/selectors/displayMessage.ts +6 -1
  58. package/src/store/chat/slices/topic/action.test.ts +10 -4
  59. package/src/store/chat/slices/topic/action.ts +3 -2
  60. package/src/store/document/slices/document/action.ts +8 -0
  61. package/packages/context-engine/src/processors/GroupMessageSender.ts +0 -138
  62. package/packages/context-engine/src/processors/__tests__/GroupMessageSender.test.ts +0 -274
@@ -0,0 +1,770 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import type { PipelineContext } from '../../types';
4
+ import { GroupOrchestrationFilterProcessor } from '../GroupOrchestrationFilter';
5
+
6
+ describe('GroupOrchestrationFilterProcessor', () => {
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': { role: 'participant' as const },
17
+ 'agent-b': { role: 'participant' as const },
18
+ 'supervisor': { role: 'supervisor' as const },
19
+ },
20
+ currentAgentId: 'agent-a', // Default to participant agent
21
+ };
22
+
23
+ describe('filtering supervisor orchestration messages', () => {
24
+ it('should filter supervisor assistant message with broadcast tool', async () => {
25
+ const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
26
+ const context = createContext([
27
+ { content: 'User question', id: 'msg_1', role: 'user' },
28
+ {
29
+ agentId: 'supervisor',
30
+ content: 'Let me coordinate the agents...',
31
+ id: 'msg_2',
32
+ role: 'assistant',
33
+ tools: [
34
+ {
35
+ apiName: 'broadcast',
36
+ arguments: '{"agentIds": ["agent-a", "agent-b"], "instruction": "Please respond"}',
37
+ id: 'call_1',
38
+ identifier: 'lobe-group-management',
39
+ },
40
+ ],
41
+ },
42
+ { content: 'Agent response', id: 'msg_3', role: 'assistant' },
43
+ ]);
44
+
45
+ const result = await processor.process(context);
46
+
47
+ expect(result.messages).toHaveLength(2);
48
+ expect(result.messages[0].id).toBe('msg_1');
49
+ expect(result.messages[1].id).toBe('msg_3');
50
+ });
51
+
52
+ it('should filter supervisor assistant message with speak tool', async () => {
53
+ const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
54
+ const context = createContext([
55
+ {
56
+ agentId: 'supervisor',
57
+ content: 'Asking agent-a to respond',
58
+ id: 'msg_1',
59
+ role: 'assistant',
60
+ tools: [
61
+ {
62
+ apiName: 'speak',
63
+ arguments: '{"agentId": "agent-a", "instruction": "Please help"}',
64
+ id: 'call_1',
65
+ identifier: 'lobe-group-management',
66
+ },
67
+ ],
68
+ },
69
+ ]);
70
+
71
+ const result = await processor.process(context);
72
+
73
+ expect(result.messages).toHaveLength(0);
74
+ });
75
+
76
+ it('should filter supervisor assistant message with executeTask tool', async () => {
77
+ const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
78
+ const context = createContext([
79
+ {
80
+ agentId: 'supervisor',
81
+ content: 'Executing task',
82
+ id: 'msg_1',
83
+ role: 'assistant',
84
+ tools: [
85
+ {
86
+ apiName: 'executeTask',
87
+ arguments: '{"task": "do something"}',
88
+ id: 'call_1',
89
+ identifier: 'lobe-group-management',
90
+ },
91
+ ],
92
+ },
93
+ ]);
94
+
95
+ const result = await processor.process(context);
96
+
97
+ expect(result.messages).toHaveLength(0);
98
+ });
99
+
100
+ it('should filter supervisor assistant message with executeTasks tool', async () => {
101
+ const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
102
+ const context = createContext([
103
+ {
104
+ agentId: 'supervisor',
105
+ content: 'Executing multiple tasks',
106
+ id: 'msg_1',
107
+ role: 'assistant',
108
+ tools: [
109
+ {
110
+ apiName: 'executeTasks',
111
+ arguments: '{"tasks": ["task1", "task2"]}',
112
+ id: 'call_1',
113
+ identifier: 'lobe-group-management',
114
+ },
115
+ ],
116
+ },
117
+ ]);
118
+
119
+ const result = await processor.process(context);
120
+
121
+ expect(result.messages).toHaveLength(0);
122
+ });
123
+ });
124
+
125
+ describe('filtering supervisor tool results', () => {
126
+ it('should filter supervisor tool result for broadcast', async () => {
127
+ const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
128
+ const context = createContext([
129
+ { content: 'User question', id: 'msg_1', role: 'user' },
130
+ {
131
+ agentId: 'supervisor',
132
+ content: 'Triggered broadcast to agents: agent-a, agent-b',
133
+ id: 'msg_2',
134
+ plugin: {
135
+ apiName: 'broadcast',
136
+ identifier: 'lobe-group-management',
137
+ },
138
+ role: 'tool',
139
+ tool_call_id: 'call_1',
140
+ },
141
+ { content: 'Instruction from supervisor', id: 'msg_3', role: 'user' },
142
+ ]);
143
+
144
+ const result = await processor.process(context);
145
+
146
+ expect(result.messages).toHaveLength(2);
147
+ expect(result.messages[0].id).toBe('msg_1');
148
+ expect(result.messages[1].id).toBe('msg_3');
149
+ });
150
+
151
+ it('should filter supervisor tool result for speak', async () => {
152
+ const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
153
+ const context = createContext([
154
+ {
155
+ agentId: 'supervisor',
156
+ content: 'Triggered speak to agent-a',
157
+ id: 'msg_1',
158
+ plugin: {
159
+ apiName: 'speak',
160
+ identifier: 'lobe-group-management',
161
+ },
162
+ role: 'tool',
163
+ tool_call_id: 'call_1',
164
+ },
165
+ ]);
166
+
167
+ const result = await processor.process(context);
168
+
169
+ expect(result.messages).toHaveLength(0);
170
+ });
171
+ });
172
+
173
+ describe('keeping non-orchestration messages', () => {
174
+ it('should keep supervisor assistant message without tools', async () => {
175
+ const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
176
+ const context = createContext([
177
+ {
178
+ agentId: 'supervisor',
179
+ content: 'Here is a summary of the discussion...',
180
+ id: 'msg_1',
181
+ role: 'assistant',
182
+ },
183
+ ]);
184
+
185
+ const result = await processor.process(context);
186
+
187
+ expect(result.messages).toHaveLength(1);
188
+ expect(result.messages[0].content).toBe('Here is a summary of the discussion...');
189
+ });
190
+
191
+ it('should keep supervisor assistant message with non-orchestration tools', async () => {
192
+ const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
193
+ const context = createContext([
194
+ {
195
+ agentId: 'supervisor',
196
+ content: 'Let me search for information',
197
+ id: 'msg_1',
198
+ role: 'assistant',
199
+ tools: [
200
+ {
201
+ apiName: 'search',
202
+ arguments: '{"query": "test"}',
203
+ id: 'call_1',
204
+ identifier: 'web-search',
205
+ },
206
+ ],
207
+ },
208
+ ]);
209
+
210
+ const result = await processor.process(context);
211
+
212
+ expect(result.messages).toHaveLength(1);
213
+ expect(result.messages[0].id).toBe('msg_1');
214
+ });
215
+
216
+ it('should keep supervisor tool result for non-orchestration tools', async () => {
217
+ const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
218
+ const context = createContext([
219
+ {
220
+ agentId: 'supervisor',
221
+ content: '{"results": ["item1", "item2"]}',
222
+ id: 'msg_1',
223
+ plugin: {
224
+ apiName: 'search',
225
+ identifier: 'web-search',
226
+ },
227
+ role: 'tool',
228
+ tool_call_id: 'call_1',
229
+ },
230
+ ]);
231
+
232
+ const result = await processor.process(context);
233
+
234
+ expect(result.messages).toHaveLength(1);
235
+ });
236
+
237
+ it('should keep all participant agent messages', async () => {
238
+ const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
239
+ const context = createContext([
240
+ {
241
+ agentId: 'agent-a',
242
+ content: 'Participant response',
243
+ id: 'msg_1',
244
+ role: 'assistant',
245
+ tools: [
246
+ {
247
+ apiName: 'broadcast',
248
+ arguments: '{}',
249
+ id: 'call_1',
250
+ identifier: 'lobe-group-management',
251
+ },
252
+ ],
253
+ },
254
+ {
255
+ agentId: 'agent-b',
256
+ content: 'Tool result',
257
+ id: 'msg_2',
258
+ plugin: {
259
+ apiName: 'broadcast',
260
+ identifier: 'lobe-group-management',
261
+ },
262
+ role: 'tool',
263
+ tool_call_id: 'call_1',
264
+ },
265
+ ]);
266
+
267
+ const result = await processor.process(context);
268
+
269
+ // Participant messages are never filtered, even with orchestration tools
270
+ expect(result.messages).toHaveLength(2);
271
+ });
272
+
273
+ it('should keep user messages unchanged', async () => {
274
+ const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
275
+ const context = createContext([
276
+ { content: 'User question 1', id: 'msg_1', role: 'user' },
277
+ { content: 'User question 2', id: 'msg_2', role: 'user' },
278
+ ]);
279
+
280
+ const result = await processor.process(context);
281
+
282
+ expect(result.messages).toHaveLength(2);
283
+ });
284
+
285
+ it('should keep messages without agentId', async () => {
286
+ const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
287
+ const context = createContext([
288
+ {
289
+ content: 'System message',
290
+ id: 'msg_1',
291
+ role: 'system',
292
+ },
293
+ {
294
+ content: 'Assistant without agentId',
295
+ id: 'msg_2',
296
+ role: 'assistant',
297
+ tools: [
298
+ {
299
+ apiName: 'broadcast',
300
+ arguments: '{}',
301
+ id: 'call_1',
302
+ identifier: 'lobe-group-management',
303
+ },
304
+ ],
305
+ },
306
+ ]);
307
+
308
+ const result = await processor.process(context);
309
+
310
+ expect(result.messages).toHaveLength(2);
311
+ });
312
+ });
313
+
314
+ describe('configuration options', () => {
315
+ it('should skip processing when disabled', async () => {
316
+ const processor = new GroupOrchestrationFilterProcessor({
317
+ ...defaultConfig,
318
+ enabled: false,
319
+ });
320
+ const context = createContext([
321
+ {
322
+ agentId: 'supervisor',
323
+ content: 'Orchestration message',
324
+ id: 'msg_1',
325
+ role: 'assistant',
326
+ tools: [
327
+ {
328
+ apiName: 'broadcast',
329
+ arguments: '{}',
330
+ id: 'call_1',
331
+ identifier: 'lobe-group-management',
332
+ },
333
+ ],
334
+ },
335
+ ]);
336
+
337
+ const result = await processor.process(context);
338
+
339
+ expect(result.messages).toHaveLength(1);
340
+ });
341
+
342
+ it('should skip processing when current agent is supervisor', async () => {
343
+ const processor = new GroupOrchestrationFilterProcessor({
344
+ ...defaultConfig,
345
+ currentAgentId: 'supervisor', // Supervisor as current agent
346
+ });
347
+ const context = createContext([
348
+ {
349
+ agentId: 'supervisor',
350
+ content: 'Orchestration message',
351
+ id: 'msg_1',
352
+ role: 'assistant',
353
+ tools: [
354
+ {
355
+ apiName: 'broadcast',
356
+ arguments: '{}',
357
+ id: 'call_1',
358
+ identifier: 'lobe-group-management',
359
+ },
360
+ ],
361
+ },
362
+ {
363
+ agentId: 'supervisor',
364
+ content: 'Tool result',
365
+ id: 'msg_2',
366
+ plugin: {
367
+ apiName: 'broadcast',
368
+ identifier: 'lobe-group-management',
369
+ },
370
+ role: 'tool',
371
+ tool_call_id: 'call_1',
372
+ },
373
+ ]);
374
+
375
+ const result = await processor.process(context);
376
+
377
+ // Supervisor should see all messages including orchestration ones
378
+ expect(result.messages).toHaveLength(2);
379
+ });
380
+
381
+ it('should skip processing when no agentMap provided', async () => {
382
+ const processor = new GroupOrchestrationFilterProcessor({});
383
+ const context = createContext([
384
+ {
385
+ agentId: 'supervisor',
386
+ content: 'Orchestration message',
387
+ id: 'msg_1',
388
+ role: 'assistant',
389
+ tools: [
390
+ {
391
+ apiName: 'broadcast',
392
+ arguments: '{}',
393
+ id: 'call_1',
394
+ identifier: 'lobe-group-management',
395
+ },
396
+ ],
397
+ },
398
+ ]);
399
+
400
+ const result = await processor.process(context);
401
+
402
+ expect(result.messages).toHaveLength(1);
403
+ });
404
+
405
+ it('should skip processing when no currentAgentId provided', async () => {
406
+ const processor = new GroupOrchestrationFilterProcessor({
407
+ agentMap: defaultConfig.agentMap,
408
+ // No currentAgentId
409
+ });
410
+ const context = createContext([
411
+ {
412
+ agentId: 'supervisor',
413
+ content: 'Orchestration message',
414
+ id: 'msg_1',
415
+ role: 'assistant',
416
+ tools: [
417
+ {
418
+ apiName: 'broadcast',
419
+ arguments: '{}',
420
+ id: 'call_1',
421
+ identifier: 'lobe-group-management',
422
+ },
423
+ ],
424
+ },
425
+ ]);
426
+
427
+ const result = await processor.process(context);
428
+
429
+ // Without currentAgentId, can't determine if supervisor, so treat as participant and filter
430
+ // Actually, isCurrentAgentSupervisor returns false when no currentAgentId, so filtering happens
431
+ expect(result.messages).toHaveLength(0);
432
+ });
433
+
434
+ it('should use custom orchestration tool identifiers', async () => {
435
+ const processor = new GroupOrchestrationFilterProcessor({
436
+ ...defaultConfig,
437
+ orchestrationToolIdentifiers: ['custom-orchestration'],
438
+ });
439
+ const context = createContext([
440
+ {
441
+ agentId: 'supervisor',
442
+ content: 'Custom orchestration',
443
+ id: 'msg_1',
444
+ role: 'assistant',
445
+ tools: [
446
+ {
447
+ apiName: 'broadcast',
448
+ arguments: '{}',
449
+ id: 'call_1',
450
+ identifier: 'custom-orchestration',
451
+ },
452
+ ],
453
+ },
454
+ {
455
+ agentId: 'supervisor',
456
+ content: 'Default orchestration - should not be filtered',
457
+ id: 'msg_2',
458
+ role: 'assistant',
459
+ tools: [
460
+ {
461
+ apiName: 'broadcast',
462
+ arguments: '{}',
463
+ id: 'call_2',
464
+ identifier: 'lobe-group-management',
465
+ },
466
+ ],
467
+ },
468
+ ]);
469
+
470
+ const result = await processor.process(context);
471
+
472
+ expect(result.messages).toHaveLength(1);
473
+ expect(result.messages[0].id).toBe('msg_2');
474
+ });
475
+
476
+ it('should use custom orchestration api names', async () => {
477
+ const processor = new GroupOrchestrationFilterProcessor({
478
+ ...defaultConfig,
479
+ orchestrationApiNames: ['customBroadcast'],
480
+ });
481
+ const context = createContext([
482
+ {
483
+ agentId: 'supervisor',
484
+ content: 'Custom api name',
485
+ id: 'msg_1',
486
+ role: 'assistant',
487
+ tools: [
488
+ {
489
+ apiName: 'customBroadcast',
490
+ arguments: '{}',
491
+ id: 'call_1',
492
+ identifier: 'lobe-group-management',
493
+ },
494
+ ],
495
+ },
496
+ {
497
+ agentId: 'supervisor',
498
+ content: 'Default broadcast - should not be filtered',
499
+ id: 'msg_2',
500
+ role: 'assistant',
501
+ tools: [
502
+ {
503
+ apiName: 'broadcast',
504
+ arguments: '{}',
505
+ id: 'call_2',
506
+ identifier: 'lobe-group-management',
507
+ },
508
+ ],
509
+ },
510
+ ]);
511
+
512
+ const result = await processor.process(context);
513
+
514
+ expect(result.messages).toHaveLength(1);
515
+ expect(result.messages[0].id).toBe('msg_2');
516
+ });
517
+ });
518
+
519
+ describe('edge cases', () => {
520
+ it('should handle empty messages array', async () => {
521
+ const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
522
+ const context = createContext([]);
523
+
524
+ const result = await processor.process(context);
525
+
526
+ expect(result.messages).toHaveLength(0);
527
+ });
528
+
529
+ it('should handle message with empty tools array', async () => {
530
+ const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
531
+ const context = createContext([
532
+ {
533
+ agentId: 'supervisor',
534
+ content: 'Message with empty tools',
535
+ id: 'msg_1',
536
+ role: 'assistant',
537
+ tools: [],
538
+ },
539
+ ]);
540
+
541
+ const result = await processor.process(context);
542
+
543
+ // Empty tools array means no orchestration tools, so message is kept
544
+ expect(result.messages).toHaveLength(1);
545
+ });
546
+
547
+ it('should handle tool without identifier', async () => {
548
+ const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
549
+ const context = createContext([
550
+ {
551
+ agentId: 'supervisor',
552
+ content: 'Tool without identifier',
553
+ id: 'msg_1',
554
+ role: 'assistant',
555
+ tools: [
556
+ {
557
+ apiName: 'broadcast',
558
+ arguments: '{}',
559
+ id: 'call_1',
560
+ // Missing identifier
561
+ },
562
+ ],
563
+ },
564
+ ]);
565
+
566
+ const result = await processor.process(context);
567
+
568
+ // Tool without identifier doesn't match orchestration pattern
569
+ expect(result.messages).toHaveLength(1);
570
+ });
571
+
572
+ it('should handle tool without apiName', async () => {
573
+ const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
574
+ const context = createContext([
575
+ {
576
+ agentId: 'supervisor',
577
+ content: 'Tool without apiName',
578
+ id: 'msg_1',
579
+ role: 'assistant',
580
+ tools: [
581
+ {
582
+ arguments: '{}',
583
+ id: 'call_1',
584
+ identifier: 'lobe-group-management',
585
+ // Missing apiName
586
+ },
587
+ ],
588
+ },
589
+ ]);
590
+
591
+ const result = await processor.process(context);
592
+
593
+ // Tool without apiName doesn't match orchestration pattern
594
+ expect(result.messages).toHaveLength(1);
595
+ });
596
+
597
+ it('should track filter counts in metadata', async () => {
598
+ const processor = new GroupOrchestrationFilterProcessor(defaultConfig);
599
+ const context = createContext([
600
+ {
601
+ agentId: 'supervisor',
602
+ content: 'Broadcast',
603
+ id: 'msg_1',
604
+ role: 'assistant',
605
+ tools: [
606
+ {
607
+ apiName: 'broadcast',
608
+ arguments: '{}',
609
+ id: 'call_1',
610
+ identifier: 'lobe-group-management',
611
+ },
612
+ ],
613
+ },
614
+ {
615
+ agentId: 'supervisor',
616
+ content: 'Tool result',
617
+ id: 'msg_2',
618
+ plugin: {
619
+ apiName: 'broadcast',
620
+ identifier: 'lobe-group-management',
621
+ },
622
+ role: 'tool',
623
+ tool_call_id: 'call_1',
624
+ },
625
+ {
626
+ agentId: 'supervisor',
627
+ content: 'Speak',
628
+ id: 'msg_3',
629
+ role: 'assistant',
630
+ tools: [
631
+ {
632
+ apiName: 'speak',
633
+ arguments: '{}',
634
+ id: 'call_2',
635
+ identifier: 'lobe-group-management',
636
+ },
637
+ ],
638
+ },
639
+ ]);
640
+
641
+ const result = await processor.process(context);
642
+
643
+ expect(result.metadata.orchestrationFilterProcessed).toEqual({
644
+ assistantFiltered: 2,
645
+ filteredCount: 3,
646
+ toolFiltered: 1,
647
+ });
648
+ });
649
+ });
650
+
651
+ describe('comprehensive end-to-end filtering', () => {
652
+ it('should correctly filter a full group conversation with orchestration messages', async () => {
653
+ const processor = new GroupOrchestrationFilterProcessor({
654
+ agentMap: {
655
+ 'agent-a': { role: 'participant' },
656
+ 'agent-b': { role: 'participant' },
657
+ 'supervisor': { role: 'supervisor' },
658
+ },
659
+ });
660
+
661
+ const inputMessages = [
662
+ // 1. User's original question
663
+ { content: '帮我规划杭州行程', id: 'msg_1', role: 'user' },
664
+ // 2. Supervisor broadcasts - SHOULD BE FILTERED
665
+ {
666
+ agentId: 'supervisor',
667
+ content: '好的,让我协调专家们...',
668
+ id: 'msg_2',
669
+ role: 'assistant',
670
+ tools: [
671
+ {
672
+ apiName: 'broadcast',
673
+ arguments: '{"agentIds": ["agent-a", "agent-b"], "instruction": "请给建议"}',
674
+ id: 'call_1',
675
+ identifier: 'lobe-group-management',
676
+ },
677
+ ],
678
+ },
679
+ // 3. Broadcast tool result - SHOULD BE FILTERED
680
+ {
681
+ agentId: 'supervisor',
682
+ content: 'Triggered broadcast to agents: agent-a, agent-b',
683
+ id: 'msg_3',
684
+ plugin: {
685
+ apiName: 'broadcast',
686
+ identifier: 'lobe-group-management',
687
+ },
688
+ role: 'tool',
689
+ tool_call_id: 'call_1',
690
+ },
691
+ // 4. Actual instruction (injected by broadcast) - SHOULD BE KEPT
692
+ { content: '请各位专家给出杭州行程建议', id: 'msg_4', role: 'user' },
693
+ // 5. Agent A response - SHOULD BE KEPT
694
+ { agentId: 'agent-a', content: '推荐西湖景区', id: 'msg_5', role: 'assistant' },
695
+ // 6. Agent B response - SHOULD BE KEPT
696
+ { agentId: 'agent-b', content: '推荐楼外楼', id: 'msg_6', role: 'assistant' },
697
+ // 7. Supervisor uses speak - SHOULD BE FILTERED
698
+ {
699
+ agentId: 'supervisor',
700
+ content: '让 agent-a 总结一下',
701
+ id: 'msg_7',
702
+ role: 'assistant',
703
+ tools: [
704
+ {
705
+ apiName: 'speak',
706
+ arguments: '{"agentId": "agent-a", "instruction": "请总结"}',
707
+ id: 'call_2',
708
+ identifier: 'lobe-group-management',
709
+ },
710
+ ],
711
+ },
712
+ // 8. Speak tool result - SHOULD BE FILTERED
713
+ {
714
+ agentId: 'supervisor',
715
+ content: 'Triggered speak to agent-a',
716
+ id: 'msg_8',
717
+ plugin: {
718
+ apiName: 'speak',
719
+ identifier: 'lobe-group-management',
720
+ },
721
+ role: 'tool',
722
+ tool_call_id: 'call_2',
723
+ },
724
+ // 9. Supervisor's summary (pure text, no tools) - SHOULD BE KEPT
725
+ {
726
+ agentId: 'supervisor',
727
+ content: '以上就是专家们的建议汇总',
728
+ id: 'msg_9',
729
+ role: 'assistant',
730
+ },
731
+ // 10. Supervisor uses search tool - SHOULD BE KEPT
732
+ {
733
+ agentId: 'supervisor',
734
+ content: '让我搜索一下更多信息',
735
+ id: 'msg_10',
736
+ role: 'assistant',
737
+ tools: [
738
+ {
739
+ apiName: 'search',
740
+ arguments: '{"query": "杭州景点"}',
741
+ id: 'call_3',
742
+ identifier: 'web-search',
743
+ },
744
+ ],
745
+ },
746
+ ];
747
+
748
+ const context = createContext(inputMessages);
749
+ const result = await processor.process(context);
750
+
751
+ // Should have: msg_1, msg_4, msg_5, msg_6, msg_9, msg_10 (6 messages)
752
+ expect(result.messages).toHaveLength(6);
753
+ expect(result.messages.map((m) => m.id)).toEqual([
754
+ 'msg_1',
755
+ 'msg_4',
756
+ 'msg_5',
757
+ 'msg_6',
758
+ 'msg_9',
759
+ 'msg_10',
760
+ ]);
761
+
762
+ // Verify metadata
763
+ expect(result.metadata.orchestrationFilterProcessed).toEqual({
764
+ assistantFiltered: 2, // msg_2, msg_7
765
+ filteredCount: 4, // msg_2, msg_3, msg_7, msg_8
766
+ toolFiltered: 2, // msg_3, msg_8
767
+ });
768
+ });
769
+ });
770
+ });