@lobehub/lobehub 2.0.0-next.305 → 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.
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/e2e/src/steps/community/detail-pages.steps.ts +3 -1
- package/e2e/src/steps/community/interactions.steps.ts +4 -4
- package/package.json +1 -1
- package/packages/context-engine/src/processors/GroupMessageFlatten.ts +9 -6
- package/packages/context-engine/src/processors/__tests__/GroupMessageFlatten.test.ts +103 -0
- package/packages/context-engine/src/providers/GroupAgentBuilderContextInjector.ts +18 -31
- package/packages/context-engine/src/providers/__tests__/GroupAgentBuilderContextInjector.test.ts +307 -0
- package/packages/prompts/src/prompts/userMemory/__snapshots__/index.test.ts.snap +14 -38
- package/packages/prompts/src/prompts/userMemory/index.ts +5 -24
- package/src/app/[variants]/(main)/community/(detail)/assistant/index.tsx +1 -1
- package/src/app/[variants]/(main)/community/(detail)/mcp/index.tsx +1 -1
- package/src/app/[variants]/(main)/group/features/Conversation/MainChatInput/index.tsx +2 -2
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +2 -2
- package/src/features/Conversation/Messages/Supervisor/index.tsx +2 -1
- package/src/features/Conversation/Messages/components/ContentLoading.tsx +8 -2
- package/src/services/chat/mecha/agentConfigResolver.ts +65 -0
- package/src/services/chat/mecha/modelParamsResolver.test.ts +211 -0
- package/src/store/agentGroup/action.ts +30 -0
- package/src/store/agentGroup/slices/lifecycle.test.ts +77 -18
- package/src/store/agentGroup/slices/lifecycle.ts +7 -9
- package/src/store/chat/slices/operation/__tests__/selectors.test.ts +124 -0
- package/src/store/chat/slices/operation/selectors.ts +22 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.306](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.305...v2.0.0-next.306)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2026-01-18**</sup>
|
|
8
|
+
|
|
9
|
+
#### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **misc**: Fix supervisor id issue.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's fixed
|
|
19
|
+
|
|
20
|
+
- **misc**: Fix supervisor id issue, closes [#11584](https://github.com/lobehub/lobe-chat/issues/11584) ([c097584](https://github.com/lobehub/lobe-chat/commit/c097584))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
## [Version 2.0.0-next.305](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.304...v2.0.0-next.305)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2026-01-18**</sup>
|
package/changelog/v1.json
CHANGED
|
@@ -8,7 +8,9 @@ import { CustomWorld } from '../../support/world';
|
|
|
8
8
|
// ============================================
|
|
9
9
|
|
|
10
10
|
Given('I wait for the page to fully load', async function (this: CustomWorld) {
|
|
11
|
-
|
|
11
|
+
// Use domcontentloaded instead of networkidle to avoid hanging on persistent connections
|
|
12
|
+
await this.page.waitForLoadState('domcontentloaded', { timeout: 10_000 });
|
|
13
|
+
// Short wait for React hydration
|
|
12
14
|
await this.page.waitForTimeout(1000);
|
|
13
15
|
});
|
|
14
16
|
|
|
@@ -446,8 +446,8 @@ Then('I should be navigated to the assistant detail page', async function (this:
|
|
|
446
446
|
Then('I should see the assistant detail content', async function (this: CustomWorld) {
|
|
447
447
|
await this.page.waitForLoadState('networkidle', { timeout: 30_000 });
|
|
448
448
|
|
|
449
|
-
// Look for detail page
|
|
450
|
-
const detailContent = this.page.locator('[data-testid="detail-content"]
|
|
449
|
+
// Look for assistant detail page content
|
|
450
|
+
const detailContent = this.page.locator('[data-testid="assistant-detail-content"]');
|
|
451
451
|
await expect(detailContent).toBeVisible({ timeout: 30_000 });
|
|
452
452
|
});
|
|
453
453
|
|
|
@@ -561,8 +561,8 @@ Then('I should be navigated to the MCP detail page', async function (this: Custo
|
|
|
561
561
|
Then('I should see the MCP detail content', async function (this: CustomWorld) {
|
|
562
562
|
await this.page.waitForLoadState('networkidle', { timeout: 30_000 });
|
|
563
563
|
|
|
564
|
-
// Look for detail page
|
|
565
|
-
const detailContent = this.page.locator('[data-testid="detail-content"]
|
|
564
|
+
// Look for MCP detail page content
|
|
565
|
+
const detailContent = this.page.locator('[data-testid="mcp-detail-content"]');
|
|
566
566
|
await expect(detailContent).toBeVisible({ timeout: 30_000 });
|
|
567
567
|
});
|
|
568
568
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.306",
|
|
4
4
|
"description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -7,9 +7,9 @@ const log = debug('context-engine:processor:GroupMessageFlattenProcessor');
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Group Message Flatten Processor
|
|
10
|
-
* Responsible for flattening role=assistantGroup messages into standard assistant + tool message sequences
|
|
10
|
+
* Responsible for flattening role=assistantGroup and role=supervisor messages into standard assistant + tool message sequences
|
|
11
11
|
*
|
|
12
|
-
* AssistantGroup messages are created when assistant messages with tools are merged with their tool results.
|
|
12
|
+
* AssistantGroup/Supervisor messages are created when assistant messages with tools are merged with their tool results.
|
|
13
13
|
* This processor converts them back to a flat structure that AI models can understand.
|
|
14
14
|
*/
|
|
15
15
|
export class GroupMessageFlattenProcessor extends BaseProcessor {
|
|
@@ -31,8 +31,11 @@ export class GroupMessageFlattenProcessor extends BaseProcessor {
|
|
|
31
31
|
|
|
32
32
|
// Process each message
|
|
33
33
|
for (const message of clonedContext.messages) {
|
|
34
|
-
// Check if this is an assistantGroup message with children field
|
|
35
|
-
if (
|
|
34
|
+
// Check if this is an assistantGroup or supervisor message with children field
|
|
35
|
+
if (
|
|
36
|
+
(message.role === 'assistantGroup' || message.role === 'supervisor') &&
|
|
37
|
+
message.children
|
|
38
|
+
) {
|
|
36
39
|
// If children array is empty, skip this message entirely (no content to flatten)
|
|
37
40
|
if (message.children.length === 0) {
|
|
38
41
|
continue;
|
|
@@ -42,7 +45,7 @@ export class GroupMessageFlattenProcessor extends BaseProcessor {
|
|
|
42
45
|
groupMessagesFlattened++;
|
|
43
46
|
|
|
44
47
|
log(
|
|
45
|
-
`Flattening
|
|
48
|
+
`Flattening ${message.role} message ${message.id} with ${message.children.length} children`,
|
|
46
49
|
);
|
|
47
50
|
|
|
48
51
|
// Flatten each child
|
|
@@ -148,7 +151,7 @@ export class GroupMessageFlattenProcessor extends BaseProcessor {
|
|
|
148
151
|
clonedContext.metadata.toolMessagesCreated = toolMessagesCreated;
|
|
149
152
|
|
|
150
153
|
log(
|
|
151
|
-
`AssistantGroup message flatten processing completed: ${groupMessagesFlattened} groups flattened, ${assistantMessagesCreated} assistant messages created, ${toolMessagesCreated} tool messages created`,
|
|
154
|
+
`AssistantGroup/Supervisor message flatten processing completed: ${groupMessagesFlattened} groups flattened, ${assistantMessagesCreated} assistant messages created, ${toolMessagesCreated} tool messages created`,
|
|
152
155
|
);
|
|
153
156
|
|
|
154
157
|
return this.markAsExecuted(clonedContext);
|
|
@@ -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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
322
|
-
|
|
305
|
+
protected async doProcess(context: PipelineContext): Promise<PipelineContext> {
|
|
306
|
+
const result = await super.doProcess(context);
|
|
323
307
|
|
|
324
|
-
|
|
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
|
|
313
|
+
return result;
|
|
327
314
|
}
|
|
328
315
|
}
|
package/packages/context-engine/src/providers/__tests__/GroupAgentBuilderContextInjector.test.ts
ADDED
|
@@ -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
|
+
});
|