@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.
Files changed (80) hide show
  1. package/.vscode/settings.json +18 -3
  2. package/CHANGELOG.md +53 -0
  3. package/changelog/v1.json +18 -0
  4. package/e2e/src/steps/community/detail-pages.steps.ts +3 -1
  5. package/e2e/src/steps/community/interactions.steps.ts +4 -4
  6. package/package.json +2 -2
  7. package/packages/builtin-agents/src/agents/group-supervisor/index.ts +1 -7
  8. package/packages/builtin-tool-group-agent-builder/src/ExecutionRuntime/index.ts +29 -0
  9. package/packages/builtin-tool-group-agent-builder/src/executor.ts +18 -0
  10. package/packages/builtin-tool-group-agent-builder/src/manifest.ts +17 -0
  11. package/packages/builtin-tool-group-agent-builder/src/types.ts +10 -0
  12. package/packages/builtin-tool-group-management/src/executor.test.ts +0 -12
  13. package/packages/builtin-tool-group-management/src/executor.ts +8 -47
  14. package/packages/builtin-tool-group-management/src/manifest.ts +0 -17
  15. package/packages/builtin-tool-group-management/src/systemRole.ts +1 -8
  16. package/packages/builtin-tool-group-management/src/types.ts +0 -10
  17. package/packages/builtin-tool-local-system/src/ExecutionRuntime/index.ts +70 -31
  18. package/packages/builtin-tool-local-system/src/executor/index.ts +94 -60
  19. package/packages/context-engine/src/processors/GroupMessageFlatten.ts +9 -6
  20. package/packages/context-engine/src/processors/__tests__/GroupMessageFlatten.test.ts +103 -0
  21. package/packages/context-engine/src/providers/GroupAgentBuilderContextInjector.ts +18 -31
  22. package/packages/context-engine/src/providers/__tests__/GroupAgentBuilderContextInjector.test.ts +307 -0
  23. package/packages/database/src/repositories/agentGroup/index.ts +23 -0
  24. package/packages/prompts/src/prompts/fileSystem/formatCommandOutput.test.ts +61 -0
  25. package/packages/prompts/src/prompts/fileSystem/formatCommandOutput.ts +21 -0
  26. package/packages/prompts/src/prompts/fileSystem/formatCommandResult.test.ts +87 -0
  27. package/packages/prompts/src/prompts/fileSystem/formatCommandResult.ts +35 -0
  28. package/packages/prompts/src/prompts/fileSystem/formatEditResult.test.ts +57 -0
  29. package/packages/prompts/src/prompts/fileSystem/formatEditResult.ts +17 -0
  30. package/packages/prompts/src/prompts/fileSystem/formatFileContent.test.ts +59 -0
  31. package/packages/prompts/src/prompts/fileSystem/formatFileContent.ts +14 -0
  32. package/packages/prompts/src/prompts/fileSystem/formatFileList.test.ts +62 -0
  33. package/packages/prompts/src/prompts/fileSystem/formatFileList.ts +13 -0
  34. package/packages/prompts/src/prompts/fileSystem/formatFileSearchResults.test.ts +34 -0
  35. package/packages/prompts/src/prompts/fileSystem/formatFileSearchResults.ts +12 -0
  36. package/packages/prompts/src/prompts/fileSystem/formatGlobResults.test.ts +64 -0
  37. package/packages/prompts/src/prompts/fileSystem/formatGlobResults.ts +23 -0
  38. package/packages/prompts/src/prompts/fileSystem/formatGrepResults.test.ts +85 -0
  39. package/packages/prompts/src/prompts/fileSystem/formatGrepResults.ts +24 -0
  40. package/packages/prompts/src/prompts/fileSystem/formatKillResult.test.ts +30 -0
  41. package/packages/prompts/src/prompts/fileSystem/formatKillResult.ts +9 -0
  42. package/packages/prompts/src/prompts/fileSystem/formatMoveResults.test.ts +37 -0
  43. package/packages/prompts/src/prompts/fileSystem/formatMoveResults.ts +20 -0
  44. package/packages/prompts/src/prompts/fileSystem/formatMultipleFiles.test.ts +54 -0
  45. package/packages/prompts/src/prompts/fileSystem/formatMultipleFiles.ts +9 -0
  46. package/packages/prompts/src/prompts/fileSystem/formatRenameResult.test.ts +35 -0
  47. package/packages/prompts/src/prompts/fileSystem/formatRenameResult.ts +17 -0
  48. package/packages/prompts/src/prompts/fileSystem/formatWriteResult.test.ts +30 -0
  49. package/packages/prompts/src/prompts/fileSystem/formatWriteResult.ts +11 -0
  50. package/packages/prompts/src/prompts/fileSystem/index.ts +13 -0
  51. package/packages/prompts/src/prompts/index.ts +1 -0
  52. package/packages/prompts/src/prompts/userMemory/__snapshots__/index.test.ts.snap +14 -38
  53. package/packages/prompts/src/prompts/userMemory/index.ts +5 -24
  54. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/Actions.tsx +4 -3
  55. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/useDropdownMenu.tsx +12 -2
  56. package/src/app/[variants]/(main)/community/(detail)/assistant/index.tsx +1 -1
  57. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/ActionButton/AddGroupAgent.tsx +69 -17
  58. package/src/app/[variants]/(main)/community/(detail)/mcp/index.tsx +1 -1
  59. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/Actions.tsx +4 -3
  60. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/useDropdownMenu.tsx +12 -2
  61. package/src/app/[variants]/(main)/group/features/Conversation/MainChatInput/index.tsx +2 -2
  62. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +2 -2
  63. package/src/features/ChatInput/ActionBar/Upload/ServerMode.tsx +13 -3
  64. package/src/features/ChatInput/ActionBar/components/ActionDropdown.tsx +26 -3
  65. package/src/features/Conversation/Messages/Supervisor/index.tsx +2 -1
  66. package/src/features/Conversation/Messages/components/ContentLoading.tsx +8 -2
  67. package/src/features/ResourceManager/components/Header/AddButton.tsx +20 -3
  68. package/src/server/routers/lambda/__tests__/agentGroup.test.ts +1 -0
  69. package/src/server/routers/lambda/agentGroup.ts +22 -0
  70. package/src/services/chat/index.ts +1 -0
  71. package/src/services/chat/mecha/agentConfigResolver.test.ts +62 -45
  72. package/src/services/chat/mecha/agentConfigResolver.ts +77 -10
  73. package/src/services/chat/mecha/modelParamsResolver.test.ts +211 -0
  74. package/src/services/chatGroup/index.ts +14 -0
  75. package/src/store/agentGroup/action.ts +30 -0
  76. package/src/store/agentGroup/slices/lifecycle.test.ts +77 -18
  77. package/src/store/agentGroup/slices/lifecycle.ts +7 -9
  78. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +6 -2
  79. package/src/store/chat/slices/operation/__tests__/selectors.test.ts +124 -0
  80. package/src/store/chat/slices/operation/selectors.ts +22 -0
@@ -1,6 +1,6 @@
1
1
  import debug from 'debug';
2
2
 
3
- import { BaseProvider } from '../base/BaseProvider';
3
+ import { BaseFirstUserContentProvider } from '../base/BaseFirstUserContentProvider';
4
4
  import type { PipelineContext, ProcessorOptions } from '../types';
5
5
 
6
6
  const log = debug('context-engine:provider:GroupAgentBuilderContextInjector');
@@ -262,8 +262,10 @@ ${parts.join('\n')}
262
262
  /**
263
263
  * Group Agent Builder Context Injector
264
264
  * Responsible for injecting current group context when Group Agent Builder tool is enabled
265
+ *
266
+ * Extends BaseFirstUserContentProvider to consolidate with other first-user-message injectors
265
267
  */
266
- export class GroupAgentBuilderContextInjector extends BaseProvider {
268
+ export class GroupAgentBuilderContextInjector extends BaseFirstUserContentProvider {
267
269
  readonly name = 'GroupAgentBuilderContextInjector';
268
270
 
269
271
  constructor(
@@ -273,19 +275,17 @@ export class GroupAgentBuilderContextInjector extends BaseProvider {
273
275
  super(options);
274
276
  }
275
277
 
276
- protected async doProcess(context: PipelineContext): Promise<PipelineContext> {
277
- const clonedContext = this.cloneContext(context);
278
-
278
+ protected buildContent(): string | null {
279
279
  // Skip if Group Agent Builder is not enabled
280
280
  if (!this.config.enabled) {
281
281
  log('Group Agent Builder not enabled, skipping injection');
282
- return this.markAsExecuted(clonedContext);
282
+ return null;
283
283
  }
284
284
 
285
285
  // Skip if no group context
286
286
  if (!this.config.groupContext) {
287
287
  log('No group context provided, skipping injection');
288
- return this.markAsExecuted(clonedContext);
288
+ return null;
289
289
  }
290
290
 
291
291
  // Format group context
@@ -295,34 +295,21 @@ export class GroupAgentBuilderContextInjector extends BaseProvider {
295
295
  // Skip if no content to inject
296
296
  if (!formattedContent) {
297
297
  log('No content to inject after formatting');
298
- return this.markAsExecuted(clonedContext);
299
- }
300
-
301
- // Find the first user message index
302
- const firstUserIndex = clonedContext.messages.findIndex((msg) => msg.role === 'user');
303
-
304
- if (firstUserIndex === -1) {
305
- log('No user messages found, skipping injection');
306
- return this.markAsExecuted(clonedContext);
298
+ return null;
307
299
  }
308
300
 
309
- // Insert a new user message with group context before the first user message
310
- const groupContextMessage = {
311
- content: formattedContent,
312
- createdAt: Date.now(),
313
- id: `group-agent-builder-context-${Date.now()}`,
314
- meta: { injectType: 'group-agent-builder-context', systemInjection: true },
315
- role: 'user' as const,
316
- updatedAt: Date.now(),
317
- };
318
-
319
- clonedContext.messages.splice(firstUserIndex, 0, groupContextMessage);
301
+ log('Group Agent Builder context prepared for injection');
302
+ return formattedContent;
303
+ }
320
304
 
321
- // Update metadata
322
- clonedContext.metadata.groupAgentBuilderContextInjected = true;
305
+ protected async doProcess(context: PipelineContext): Promise<PipelineContext> {
306
+ const result = await super.doProcess(context);
323
307
 
324
- log('Group Agent Builder context injected as new user message');
308
+ // Update metadata if content was injected
309
+ if (this.config.enabled && this.config.groupContext) {
310
+ result.metadata.groupAgentBuilderContextInjected = true;
311
+ }
325
312
 
326
- return this.markAsExecuted(clonedContext);
313
+ return result;
327
314
  }
328
315
  }
@@ -0,0 +1,307 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import type { PipelineContext } from '../../types';
4
+ import { GroupAgentBuilderContextInjector } from '../GroupAgentBuilderContextInjector';
5
+ import { UserMemoryInjector } from '../UserMemoryInjector';
6
+
7
+ describe('GroupAgentBuilderContextInjector', () => {
8
+ const createContext = (messages: any[]): PipelineContext => ({
9
+ initialState: { messages: [] },
10
+ isAborted: false,
11
+ messages,
12
+ metadata: {},
13
+ });
14
+
15
+ describe('Basic Injection', () => {
16
+ it('should inject group context before first user message', async () => {
17
+ const injector = new GroupAgentBuilderContextInjector({
18
+ enabled: true,
19
+ groupContext: {
20
+ groupId: 'grp_123',
21
+ groupTitle: 'Test Group',
22
+ members: [
23
+ { id: 'agt_1', title: 'Agent 1', isSupervisor: true },
24
+ { id: 'agt_2', title: 'Agent 2', isSupervisor: false },
25
+ ],
26
+ },
27
+ });
28
+
29
+ const context = createContext([
30
+ { role: 'system', content: 'You are a helpful assistant' },
31
+ { role: 'user', content: 'Hello' },
32
+ ]);
33
+
34
+ const result = await injector.process(context);
35
+
36
+ // Should have 3 messages now (system + injected user + original user)
37
+ expect(result.messages).toHaveLength(3);
38
+ expect(result.messages[0].role).toBe('system');
39
+ expect(result.messages[1].role).toBe('user');
40
+ expect(result.messages[1].content).toContain('<current_group_context>');
41
+ expect(result.messages[1].content).toContain('grp_123');
42
+ expect(result.messages[1].content).toContain('Test Group');
43
+ expect(result.messages[2].role).toBe('user');
44
+ expect(result.messages[2].content).toBe('Hello');
45
+ });
46
+
47
+ it('should skip injection when not enabled', async () => {
48
+ const injector = new GroupAgentBuilderContextInjector({
49
+ enabled: false,
50
+ groupContext: {
51
+ groupId: 'grp_123',
52
+ },
53
+ });
54
+
55
+ const context = createContext([
56
+ { role: 'system', content: 'You are a helpful assistant' },
57
+ { role: 'user', content: 'Hello' },
58
+ ]);
59
+
60
+ const result = await injector.process(context);
61
+
62
+ expect(result.messages).toHaveLength(2);
63
+ });
64
+
65
+ it('should skip injection when no group context provided', async () => {
66
+ const injector = new GroupAgentBuilderContextInjector({
67
+ enabled: true,
68
+ });
69
+
70
+ const context = createContext([
71
+ { role: 'system', content: 'You are a helpful assistant' },
72
+ { role: 'user', content: 'Hello' },
73
+ ]);
74
+
75
+ const result = await injector.process(context);
76
+
77
+ expect(result.messages).toHaveLength(2);
78
+ });
79
+ });
80
+
81
+ describe('Content Consolidation with UserMemoryInjector', () => {
82
+ it('should consolidate content into single user message when both injectors are used', async () => {
83
+ // First injector: UserMemoryInjector
84
+ const memoryInjector = new UserMemoryInjector({
85
+ memories: {
86
+ identities: [{ description: 'User is a developer', id: 'id_1' }],
87
+ },
88
+ });
89
+
90
+ // Second injector: GroupAgentBuilderContextInjector
91
+ const groupInjector = new GroupAgentBuilderContextInjector({
92
+ enabled: true,
93
+ groupContext: {
94
+ groupId: 'grp_123',
95
+ groupTitle: 'Dev Team',
96
+ },
97
+ });
98
+
99
+ const context = createContext([
100
+ { role: 'system', content: 'You are a helpful assistant' },
101
+ { role: 'user', content: 'Hello' },
102
+ { role: 'assistant', content: 'Hi there!' },
103
+ ]);
104
+
105
+ // Process through both injectors in order
106
+ const afterMemory = await memoryInjector.process(context);
107
+ const afterGroup = await groupInjector.process(afterMemory);
108
+
109
+ // Should have 4 messages: system + SINGLE injected user + original user + assistant
110
+ expect(afterGroup.messages).toHaveLength(4);
111
+
112
+ // Check message order
113
+ expect(afterGroup.messages[0].role).toBe('system');
114
+ expect(afterGroup.messages[1].role).toBe('user'); // Consolidated injection
115
+ expect(afterGroup.messages[2].role).toBe('user'); // Original user message
116
+ expect(afterGroup.messages[3].role).toBe('assistant');
117
+
118
+ // The consolidated message should contain BOTH user memory AND group context
119
+ const injectedMessage = afterGroup.messages[1];
120
+ expect(injectedMessage.content).toContain('<user_memory>'); // From UserMemoryInjector
121
+ expect(injectedMessage.content).toContain('<current_group_context>'); // From GroupAgentBuilderContextInjector
122
+ expect(injectedMessage.content).toContain('User is a developer');
123
+ expect(injectedMessage.content).toContain('grp_123');
124
+ expect(injectedMessage.content).toContain('Dev Team');
125
+ });
126
+
127
+ it('should work correctly when only GroupAgentBuilderContextInjector is used', async () => {
128
+ const groupInjector = new GroupAgentBuilderContextInjector({
129
+ enabled: true,
130
+ groupContext: {
131
+ groupId: 'grp_123',
132
+ groupTitle: 'Test Group',
133
+ },
134
+ });
135
+
136
+ const context = createContext([
137
+ { role: 'system', content: 'You are a helpful assistant' },
138
+ { role: 'user', content: 'Hello' },
139
+ ]);
140
+
141
+ const result = await groupInjector.process(context);
142
+
143
+ expect(result.messages).toHaveLength(3);
144
+ expect(result.messages[1].content).toContain('<current_group_context>');
145
+ });
146
+
147
+ it('should work correctly when only UserMemoryInjector is used', async () => {
148
+ const memoryInjector = new UserMemoryInjector({
149
+ memories: {
150
+ identities: [{ description: 'User is a developer', id: 'id_1' }],
151
+ },
152
+ });
153
+
154
+ const context = createContext([
155
+ { role: 'system', content: 'You are a helpful assistant' },
156
+ { role: 'user', content: 'Hello' },
157
+ ]);
158
+
159
+ const result = await memoryInjector.process(context);
160
+
161
+ expect(result.messages).toHaveLength(3);
162
+ expect(result.messages[1].content).toContain('<user_memory>');
163
+ });
164
+
165
+ it('should NOT create duplicate user messages when injectors run in sequence', async () => {
166
+ const memoryInjector = new UserMemoryInjector({
167
+ memories: {
168
+ identities: [{ description: 'Identity 1', id: 'id_1' }],
169
+ contexts: [{ title: 'Context 1', id: 'ctx_1' }],
170
+ },
171
+ });
172
+
173
+ const groupInjector = new GroupAgentBuilderContextInjector({
174
+ enabled: true,
175
+ groupContext: {
176
+ groupId: 'grp_123',
177
+ groupTitle: 'Team Alpha',
178
+ members: [
179
+ { id: 'agt_1', title: 'Alice', isSupervisor: true },
180
+ { id: 'agt_2', title: 'Bob', isSupervisor: false },
181
+ ],
182
+ config: {
183
+ systemPrompt: 'Collaborate effectively',
184
+ },
185
+ },
186
+ });
187
+
188
+ const context = createContext([
189
+ { role: 'system', content: 'System prompt' },
190
+ { role: 'user', content: 'First user message' },
191
+ { role: 'assistant', content: 'First response' },
192
+ { role: 'user', content: 'Second user message' },
193
+ ]);
194
+
195
+ // Process through both injectors
196
+ const afterMemory = await memoryInjector.process(context);
197
+ const afterGroup = await groupInjector.process(afterMemory);
198
+
199
+ // Count user messages
200
+ const userMessages = afterGroup.messages.filter((m) => m.role === 'user');
201
+
202
+ // Should have 3 user messages: 1 consolidated injection + 2 original
203
+ expect(userMessages).toHaveLength(3);
204
+
205
+ // The first user message should be the consolidated injection
206
+ expect(userMessages[0].content).toContain('<user_memory>');
207
+ expect(userMessages[0].content).toContain('<current_group_context>');
208
+
209
+ // Original messages should remain unchanged
210
+ expect(userMessages[1].content).toBe('First user message');
211
+ expect(userMessages[2].content).toBe('Second user message');
212
+ });
213
+
214
+ it('should preserve order: first injector content comes first in the consolidated message', async () => {
215
+ // UserMemoryInjector runs first
216
+ const memoryInjector = new UserMemoryInjector({
217
+ memories: {
218
+ identities: [{ description: 'Dev identity', id: 'id_1' }],
219
+ },
220
+ });
221
+
222
+ // GroupAgentBuilderContextInjector runs second
223
+ const groupInjector = new GroupAgentBuilderContextInjector({
224
+ enabled: true,
225
+ groupContext: {
226
+ groupId: 'grp_order_test',
227
+ groupTitle: 'Order Test Group',
228
+ },
229
+ });
230
+
231
+ const context = createContext([{ role: 'user', content: 'Hello' }]);
232
+
233
+ // Process in order: memory first, then group
234
+ const afterMemory = await memoryInjector.process(context);
235
+ const afterGroup = await groupInjector.process(afterMemory);
236
+
237
+ const injectedContent = afterGroup.messages[0].content as string;
238
+
239
+ // user_memory should appear BEFORE current_group_context
240
+ const memoryIndex = injectedContent.indexOf('<user_memory>');
241
+ const groupIndex = injectedContent.indexOf('<current_group_context>');
242
+
243
+ expect(memoryIndex).toBeLessThan(groupIndex);
244
+ });
245
+ });
246
+
247
+ describe('Group Context Formatting', () => {
248
+ it('should format members correctly', async () => {
249
+ const injector = new GroupAgentBuilderContextInjector({
250
+ enabled: true,
251
+ groupContext: {
252
+ members: [
253
+ {
254
+ id: 'agt_1',
255
+ title: 'Supervisor Agent',
256
+ description: 'Manages the team',
257
+ isSupervisor: true,
258
+ },
259
+ {
260
+ id: 'agt_2',
261
+ title: 'Worker Agent',
262
+ description: 'Does the work',
263
+ isSupervisor: false,
264
+ },
265
+ ],
266
+ },
267
+ });
268
+
269
+ const context = createContext([{ role: 'user', content: 'Hello' }]);
270
+
271
+ const result = await injector.process(context);
272
+
273
+ const injectedContent = result.messages[0].content;
274
+ expect(injectedContent).toContain('<group_members count="2">');
275
+ expect(injectedContent).toContain('role="supervisor"');
276
+ expect(injectedContent).toContain('role="participant"');
277
+ expect(injectedContent).toContain('Supervisor Agent');
278
+ expect(injectedContent).toContain('Worker Agent');
279
+ });
280
+
281
+ it('should format config correctly', async () => {
282
+ const injector = new GroupAgentBuilderContextInjector({
283
+ enabled: true,
284
+ groupContext: {
285
+ config: {
286
+ scene: 'collaborative',
287
+ enableSupervisor: true,
288
+ systemPrompt: 'Work together as a team',
289
+ openingMessage: 'Welcome to the team!',
290
+ openingQuestions: ['How can I help?', 'What would you like to do?'],
291
+ },
292
+ },
293
+ });
294
+
295
+ const context = createContext([{ role: 'user', content: 'Hello' }]);
296
+
297
+ const result = await injector.process(context);
298
+
299
+ const injectedContent = result.messages[0].content;
300
+ expect(injectedContent).toContain('<group_config>');
301
+ expect(injectedContent).toContain('<scene>collaborative</scene>');
302
+ expect(injectedContent).toContain('<enableSupervisor>true</enableSupervisor>');
303
+ expect(injectedContent).toContain('<openingMessage>Welcome to the team!</openingMessage>');
304
+ expect(injectedContent).toContain('<openingQuestions count="2">');
305
+ });
306
+ });
307
+ });
@@ -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
+ };