@lobehub/lobehub 2.0.0-next.305 → 2.0.0-next.307
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/.vscode/settings.json +18 -3
- package/CHANGELOG.md +53 -0
- package/changelog/v1.json +18 -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 +2 -2
- package/packages/builtin-agents/src/agents/group-supervisor/index.ts +1 -7
- package/packages/builtin-tool-group-agent-builder/src/ExecutionRuntime/index.ts +29 -0
- package/packages/builtin-tool-group-agent-builder/src/executor.ts +18 -0
- package/packages/builtin-tool-group-agent-builder/src/manifest.ts +17 -0
- package/packages/builtin-tool-group-agent-builder/src/types.ts +10 -0
- package/packages/builtin-tool-group-management/src/executor.test.ts +0 -12
- package/packages/builtin-tool-group-management/src/executor.ts +8 -47
- package/packages/builtin-tool-group-management/src/manifest.ts +0 -17
- package/packages/builtin-tool-group-management/src/systemRole.ts +1 -8
- package/packages/builtin-tool-group-management/src/types.ts +0 -10
- package/packages/builtin-tool-local-system/src/ExecutionRuntime/index.ts +70 -31
- package/packages/builtin-tool-local-system/src/executor/index.ts +94 -60
- 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/database/src/repositories/agentGroup/index.ts +23 -0
- package/packages/prompts/src/prompts/fileSystem/formatCommandOutput.test.ts +61 -0
- package/packages/prompts/src/prompts/fileSystem/formatCommandOutput.ts +21 -0
- package/packages/prompts/src/prompts/fileSystem/formatCommandResult.test.ts +87 -0
- package/packages/prompts/src/prompts/fileSystem/formatCommandResult.ts +35 -0
- package/packages/prompts/src/prompts/fileSystem/formatEditResult.test.ts +57 -0
- package/packages/prompts/src/prompts/fileSystem/formatEditResult.ts +17 -0
- package/packages/prompts/src/prompts/fileSystem/formatFileContent.test.ts +59 -0
- package/packages/prompts/src/prompts/fileSystem/formatFileContent.ts +14 -0
- package/packages/prompts/src/prompts/fileSystem/formatFileList.test.ts +62 -0
- package/packages/prompts/src/prompts/fileSystem/formatFileList.ts +13 -0
- package/packages/prompts/src/prompts/fileSystem/formatFileSearchResults.test.ts +34 -0
- package/packages/prompts/src/prompts/fileSystem/formatFileSearchResults.ts +12 -0
- package/packages/prompts/src/prompts/fileSystem/formatGlobResults.test.ts +64 -0
- package/packages/prompts/src/prompts/fileSystem/formatGlobResults.ts +23 -0
- package/packages/prompts/src/prompts/fileSystem/formatGrepResults.test.ts +85 -0
- package/packages/prompts/src/prompts/fileSystem/formatGrepResults.ts +24 -0
- package/packages/prompts/src/prompts/fileSystem/formatKillResult.test.ts +30 -0
- package/packages/prompts/src/prompts/fileSystem/formatKillResult.ts +9 -0
- package/packages/prompts/src/prompts/fileSystem/formatMoveResults.test.ts +37 -0
- package/packages/prompts/src/prompts/fileSystem/formatMoveResults.ts +20 -0
- package/packages/prompts/src/prompts/fileSystem/formatMultipleFiles.test.ts +54 -0
- package/packages/prompts/src/prompts/fileSystem/formatMultipleFiles.ts +9 -0
- package/packages/prompts/src/prompts/fileSystem/formatRenameResult.test.ts +35 -0
- package/packages/prompts/src/prompts/fileSystem/formatRenameResult.ts +17 -0
- package/packages/prompts/src/prompts/fileSystem/formatWriteResult.test.ts +30 -0
- package/packages/prompts/src/prompts/fileSystem/formatWriteResult.ts +11 -0
- package/packages/prompts/src/prompts/fileSystem/index.ts +13 -0
- package/packages/prompts/src/prompts/index.ts +1 -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)/agent/_layout/Sidebar/Topic/Actions.tsx +4 -3
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/useDropdownMenu.tsx +12 -2
- package/src/app/[variants]/(main)/community/(detail)/assistant/index.tsx +1 -1
- package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/ActionButton/AddGroupAgent.tsx +69 -17
- package/src/app/[variants]/(main)/community/(detail)/mcp/index.tsx +1 -1
- package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/Actions.tsx +4 -3
- package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/useDropdownMenu.tsx +12 -2
- 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/ChatInput/ActionBar/Upload/ServerMode.tsx +13 -3
- package/src/features/ChatInput/ActionBar/components/ActionDropdown.tsx +26 -3
- package/src/features/Conversation/Messages/Supervisor/index.tsx +2 -1
- package/src/features/Conversation/Messages/components/ContentLoading.tsx +8 -2
- package/src/features/ResourceManager/components/Header/AddButton.tsx +20 -3
- package/src/server/routers/lambda/__tests__/agentGroup.test.ts +1 -0
- package/src/server/routers/lambda/agentGroup.ts +22 -0
- package/src/services/chat/index.ts +1 -0
- package/src/services/chat/mecha/agentConfigResolver.test.ts +62 -45
- package/src/services/chat/mecha/agentConfigResolver.ts +77 -10
- package/src/services/chat/mecha/modelParamsResolver.test.ts +211 -0
- package/src/services/chatGroup/index.ts +14 -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/aiChat/actions/streamingExecutor.ts +6 -2
- package/src/store/chat/slices/operation/__tests__/selectors.test.ts +124 -0
- package/src/store/chat/slices/operation/selectors.ts +22 -0
|
@@ -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
|
+
});
|
|
@@ -15,8 +15,14 @@ import {
|
|
|
15
15
|
import { LobeChatDatabase } from '../../type';
|
|
16
16
|
|
|
17
17
|
export interface SupervisorAgentConfig {
|
|
18
|
+
avatar?: string;
|
|
19
|
+
backgroundColor?: string;
|
|
20
|
+
description?: string;
|
|
18
21
|
model?: string;
|
|
22
|
+
params?: any;
|
|
19
23
|
provider?: string;
|
|
24
|
+
systemRole?: string;
|
|
25
|
+
tags?: string[];
|
|
20
26
|
title?: string;
|
|
21
27
|
}
|
|
22
28
|
|
|
@@ -164,8 +170,14 @@ export class AgentGroupRepository {
|
|
|
164
170
|
const [supervisorAgent] = await this.db
|
|
165
171
|
.insert(agents)
|
|
166
172
|
.values({
|
|
173
|
+
avatar: supervisorConfig?.avatar,
|
|
174
|
+
backgroundColor: supervisorConfig?.backgroundColor,
|
|
175
|
+
description: supervisorConfig?.description,
|
|
167
176
|
model: supervisorConfig?.model,
|
|
177
|
+
params: supervisorConfig?.params,
|
|
168
178
|
provider: supervisorConfig?.provider,
|
|
179
|
+
systemRole: supervisorConfig?.systemRole,
|
|
180
|
+
tags: supervisorConfig?.tags,
|
|
169
181
|
title: supervisorConfig?.title ?? 'Supervisor',
|
|
170
182
|
userId: this.userId,
|
|
171
183
|
virtual: true,
|
|
@@ -356,7 +368,12 @@ export class AgentGroupRepository {
|
|
|
356
368
|
const [newGroup] = await trx
|
|
357
369
|
.insert(chatGroups)
|
|
358
370
|
.values({
|
|
371
|
+
avatar: sourceGroup.avatar,
|
|
372
|
+
backgroundColor: sourceGroup.backgroundColor,
|
|
359
373
|
config: sourceGroup.config,
|
|
374
|
+
content: sourceGroup.content,
|
|
375
|
+
description: sourceGroup.description,
|
|
376
|
+
editorData: sourceGroup.editorData,
|
|
360
377
|
pinned: sourceGroup.pinned,
|
|
361
378
|
title: newTitle || (sourceGroup.title ? `${sourceGroup.title} (Copy)` : 'Copy'),
|
|
362
379
|
userId: this.userId,
|
|
@@ -368,8 +385,14 @@ export class AgentGroupRepository {
|
|
|
368
385
|
const [newSupervisor] = await trx
|
|
369
386
|
.insert(agents)
|
|
370
387
|
.values({
|
|
388
|
+
avatar: supervisorAgent?.avatar,
|
|
389
|
+
backgroundColor: supervisorAgent?.backgroundColor,
|
|
390
|
+
description: supervisorAgent?.description,
|
|
371
391
|
model: supervisorAgent?.model,
|
|
392
|
+
params: supervisorAgent?.params,
|
|
372
393
|
provider: supervisorAgent?.provider,
|
|
394
|
+
systemRole: supervisorAgent?.systemRole,
|
|
395
|
+
tags: supervisorAgent?.tags,
|
|
373
396
|
title: supervisorAgent?.title || 'Supervisor',
|
|
374
397
|
userId: this.userId,
|
|
375
398
|
virtual: true,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { formatCommandOutput } from './formatCommandOutput';
|
|
4
|
+
|
|
5
|
+
describe('formatCommandOutput', () => {
|
|
6
|
+
it('should format successful output while running', () => {
|
|
7
|
+
const result = formatCommandOutput({
|
|
8
|
+
running: true,
|
|
9
|
+
success: true,
|
|
10
|
+
});
|
|
11
|
+
expect(result).toMatchInlineSnapshot(`"Output retrieved. Running: true"`);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should format successful output when not running', () => {
|
|
15
|
+
const result = formatCommandOutput({
|
|
16
|
+
running: false,
|
|
17
|
+
success: true,
|
|
18
|
+
});
|
|
19
|
+
expect(result).toMatchInlineSnapshot(`"Output retrieved. Running: false"`);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should format successful output with content', () => {
|
|
23
|
+
const result = formatCommandOutput({
|
|
24
|
+
output: 'Process output here',
|
|
25
|
+
running: true,
|
|
26
|
+
success: true,
|
|
27
|
+
});
|
|
28
|
+
expect(result).toMatchInlineSnapshot(`
|
|
29
|
+
"Output retrieved. Running: true
|
|
30
|
+
|
|
31
|
+
Output:
|
|
32
|
+
Process output here"
|
|
33
|
+
`);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should format failed output', () => {
|
|
37
|
+
const result = formatCommandOutput({
|
|
38
|
+
error: 'Process not found',
|
|
39
|
+
running: false,
|
|
40
|
+
success: false,
|
|
41
|
+
});
|
|
42
|
+
expect(result).toMatchInlineSnapshot(`"Failed: Process not found"`);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should format successful output with error info', () => {
|
|
46
|
+
const result = formatCommandOutput({
|
|
47
|
+
error: 'Warning message',
|
|
48
|
+
output: 'Some output',
|
|
49
|
+
running: false,
|
|
50
|
+
success: true,
|
|
51
|
+
});
|
|
52
|
+
expect(result).toMatchInlineSnapshot(`
|
|
53
|
+
"Output retrieved. Running: false
|
|
54
|
+
|
|
55
|
+
Output:
|
|
56
|
+
Some output
|
|
57
|
+
|
|
58
|
+
Error: Warning message"
|
|
59
|
+
`);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface FormatCommandOutputParams {
|
|
2
|
+
error?: string;
|
|
3
|
+
output?: string;
|
|
4
|
+
running: boolean;
|
|
5
|
+
success: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const formatCommandOutput = ({
|
|
9
|
+
success,
|
|
10
|
+
running,
|
|
11
|
+
output,
|
|
12
|
+
error,
|
|
13
|
+
}: FormatCommandOutputParams): string => {
|
|
14
|
+
const message = success ? `Output retrieved. Running: ${running}` : `Failed: ${error}`;
|
|
15
|
+
|
|
16
|
+
const parts: string[] = [message];
|
|
17
|
+
if (output) parts.push(`Output:\n${output}`);
|
|
18
|
+
if (error && success) parts.push(`Error: ${error}`);
|
|
19
|
+
|
|
20
|
+
return parts.join('\n\n');
|
|
21
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { formatCommandResult } from './formatCommandResult';
|
|
4
|
+
|
|
5
|
+
describe('formatCommandResult', () => {
|
|
6
|
+
it('should format successful command without output', () => {
|
|
7
|
+
const result = formatCommandResult({ success: true });
|
|
8
|
+
expect(result).toMatchInlineSnapshot(`"Command completed successfully."`);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should format successful background command', () => {
|
|
12
|
+
const result = formatCommandResult({
|
|
13
|
+
shellId: 'shell-123',
|
|
14
|
+
success: true,
|
|
15
|
+
});
|
|
16
|
+
expect(result).toMatchInlineSnapshot(
|
|
17
|
+
`"Command started in background with shell_id: shell-123"`,
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should format successful command with stdout', () => {
|
|
22
|
+
const result = formatCommandResult({
|
|
23
|
+
stdout: 'Hello World',
|
|
24
|
+
success: true,
|
|
25
|
+
});
|
|
26
|
+
expect(result).toMatchInlineSnapshot(`
|
|
27
|
+
"Command completed successfully.
|
|
28
|
+
|
|
29
|
+
Output:
|
|
30
|
+
Hello World"
|
|
31
|
+
`);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should format successful command with stderr', () => {
|
|
35
|
+
const result = formatCommandResult({
|
|
36
|
+
stderr: 'Warning: deprecated',
|
|
37
|
+
success: true,
|
|
38
|
+
});
|
|
39
|
+
expect(result).toMatchInlineSnapshot(`
|
|
40
|
+
"Command completed successfully.
|
|
41
|
+
|
|
42
|
+
Stderr:
|
|
43
|
+
Warning: deprecated"
|
|
44
|
+
`);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should format successful command with exit code', () => {
|
|
48
|
+
const result = formatCommandResult({
|
|
49
|
+
exitCode: 0,
|
|
50
|
+
success: true,
|
|
51
|
+
});
|
|
52
|
+
expect(result).toMatchInlineSnapshot(`
|
|
53
|
+
"Command completed successfully.
|
|
54
|
+
|
|
55
|
+
Exit code: 0"
|
|
56
|
+
`);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should format failed command', () => {
|
|
60
|
+
const result = formatCommandResult({
|
|
61
|
+
error: 'Permission denied',
|
|
62
|
+
success: false,
|
|
63
|
+
});
|
|
64
|
+
expect(result).toMatchInlineSnapshot(`"Command failed: Permission denied"`);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should format command with all fields', () => {
|
|
68
|
+
const result = formatCommandResult({
|
|
69
|
+
error: 'Command error',
|
|
70
|
+
exitCode: 1,
|
|
71
|
+
stderr: 'Error occurred',
|
|
72
|
+
stdout: 'Some output',
|
|
73
|
+
success: false,
|
|
74
|
+
});
|
|
75
|
+
expect(result).toMatchInlineSnapshot(`
|
|
76
|
+
"Command failed: Command error
|
|
77
|
+
|
|
78
|
+
Output:
|
|
79
|
+
Some output
|
|
80
|
+
|
|
81
|
+
Stderr:
|
|
82
|
+
Error occurred
|
|
83
|
+
|
|
84
|
+
Exit code: 1"
|
|
85
|
+
`);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface FormatCommandResultParams {
|
|
2
|
+
error?: string;
|
|
3
|
+
exitCode?: number;
|
|
4
|
+
shellId?: string;
|
|
5
|
+
stderr?: string;
|
|
6
|
+
stdout?: string;
|
|
7
|
+
success: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const formatCommandResult = ({
|
|
11
|
+
success,
|
|
12
|
+
shellId,
|
|
13
|
+
error,
|
|
14
|
+
stdout,
|
|
15
|
+
stderr,
|
|
16
|
+
exitCode,
|
|
17
|
+
}: FormatCommandResultParams): string => {
|
|
18
|
+
const parts: string[] = [];
|
|
19
|
+
|
|
20
|
+
if (success) {
|
|
21
|
+
if (shellId) {
|
|
22
|
+
parts.push(`Command started in background with shell_id: ${shellId}`);
|
|
23
|
+
} else {
|
|
24
|
+
parts.push('Command completed successfully.');
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
parts.push(`Command failed: ${error}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (stdout) parts.push(`Output:\n${stdout}`);
|
|
31
|
+
if (stderr) parts.push(`Stderr:\n${stderr}`);
|
|
32
|
+
if (exitCode !== undefined) parts.push(`Exit code: ${exitCode}`);
|
|
33
|
+
|
|
34
|
+
return parts.join('\n\n');
|
|
35
|
+
};
|