@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
@@ -71,8 +71,14 @@ export class LocalSystemExecutionRuntime {
71
71
 
72
72
  const state: LocalFileListState = { listResults: result };
73
73
 
74
+ const fileList = result.map((f) => ` ${f.isDirectory ? '[D]' : '[F]'} ${f.name}`).join('\n');
75
+ const content =
76
+ result.length > 0
77
+ ? `Found ${result.length} item(s) in ${args.path}:\n${fileList}`
78
+ : `Directory ${args.path} is empty`;
79
+
74
80
  return {
75
- content: JSON.stringify(result),
81
+ content,
76
82
  state,
77
83
  success: true,
78
84
  };
@@ -91,8 +97,11 @@ export class LocalSystemExecutionRuntime {
91
97
 
92
98
  const state: LocalReadFileState = { fileContent: result };
93
99
 
100
+ const lineInfo = args.loc ? ` (lines ${args.loc[0]}-${args.loc[1]})` : '';
101
+ const content = `File: ${args.path}${lineInfo}\n\n${result.content}`;
102
+
94
103
  return {
95
- content: JSON.stringify(result),
104
+ content,
96
105
  state,
97
106
  success: true,
98
107
  };
@@ -111,8 +120,11 @@ export class LocalSystemExecutionRuntime {
111
120
 
112
121
  const state: LocalReadFilesState = { filesContent: results };
113
122
 
123
+ const fileContents = results.map((r) => `=== ${r.filename} ===\n${r.content}`).join('\n\n');
124
+ const content = `Read ${results.length} file(s):\n\n${fileContents}`;
125
+
114
126
  return {
115
- content: JSON.stringify(results),
127
+ content,
116
128
  state,
117
129
  success: true,
118
130
  };
@@ -131,8 +143,12 @@ export class LocalSystemExecutionRuntime {
131
143
 
132
144
  const state: LocalFileSearchState = { searchResults: result };
133
145
 
146
+ const fileList = result.map((f) => ` ${f.path}`).join('\n');
147
+ const content =
148
+ result.length > 0 ? `Found ${result.length} file(s):\n${fileList}` : 'No files found';
149
+
134
150
  return {
135
- content: JSON.stringify(result),
151
+ content,
136
152
  state,
137
153
  success: true,
138
154
  };
@@ -154,14 +170,14 @@ export class LocalSystemExecutionRuntime {
154
170
  const successCount = results.filter((r) => r.success).length;
155
171
  const failedCount = results.length - successCount;
156
172
 
157
- let message = '';
173
+ let content = '';
158
174
 
159
175
  if (allSucceeded) {
160
- message = `Successfully moved ${results.length} item(s).`;
176
+ content = `Successfully moved ${results.length} item(s).`;
161
177
  } else if (someFailed) {
162
- message = `Moved ${successCount} item(s) successfully. Failed to move ${failedCount} item(s).`;
178
+ content = `Moved ${successCount} item(s) successfully. Failed to move ${failedCount} item(s).`;
163
179
  } else {
164
- message = `Failed to move all ${results.length} item(s).`;
180
+ content = `Failed to move all ${results.length} item(s).`;
165
181
  }
166
182
 
167
183
  const state: LocalMoveFilesState = {
@@ -171,7 +187,7 @@ export class LocalSystemExecutionRuntime {
171
187
  };
172
188
 
173
189
  return {
174
- content: JSON.stringify({ message, results }),
190
+ content,
175
191
  state,
176
192
  success: true,
177
193
  };
@@ -197,7 +213,7 @@ export class LocalSystemExecutionRuntime {
197
213
  };
198
214
 
199
215
  return {
200
- content: JSON.stringify({ message: result.error, success: false }),
216
+ content: `Failed to rename file: ${result.error}`,
201
217
  state,
202
218
  success: false,
203
219
  };
@@ -210,10 +226,7 @@ export class LocalSystemExecutionRuntime {
210
226
  };
211
227
 
212
228
  return {
213
- content: JSON.stringify({
214
- message: `Successfully renamed file ${args.path} to ${args.newName}.`,
215
- success: true,
216
- }),
229
+ content: `Successfully renamed file ${args.path} to ${args.newName}`,
217
230
  state,
218
231
  success: true,
219
232
  };
@@ -232,20 +245,14 @@ export class LocalSystemExecutionRuntime {
232
245
 
233
246
  if (!result.success) {
234
247
  return {
235
- content: JSON.stringify({
236
- message: result.error || '写入文件失败',
237
- success: false,
238
- }),
248
+ content: `Failed to write file: ${result.error || 'Unknown error'}`,
239
249
  error: result.error,
240
250
  success: false,
241
251
  };
242
252
  }
243
253
 
244
254
  return {
245
- content: JSON.stringify({
246
- message: `成功写入文件 ${args.path}`,
247
- success: true,
248
- }),
255
+ content: `Successfully wrote to ${args.path}`,
249
256
  success: true,
250
257
  };
251
258
  } catch (error) {
@@ -301,22 +308,28 @@ export class LocalSystemExecutionRuntime {
301
308
  try {
302
309
  const result: RunCommandResult = await this.localFileService.runCommand(args);
303
310
 
304
- let message: string;
311
+ const parts: string[] = [];
305
312
 
306
313
  if (result.success) {
307
314
  if (result.shell_id) {
308
- message = `Command started in background with shell_id: ${result.shell_id}`;
315
+ parts.push(`Command started in background with shell_id: ${result.shell_id}`);
309
316
  } else {
310
- message = `Command completed successfully.`;
317
+ parts.push('Command completed successfully.');
311
318
  }
312
319
  } else {
313
- message = `Command failed: ${result.error}`;
320
+ parts.push(`Command failed: ${result.error}`);
314
321
  }
315
322
 
323
+ if (result.stdout) parts.push(`Output:\n${result.stdout}`);
324
+ if (result.stderr) parts.push(`Stderr:\n${result.stderr}`);
325
+ if (result.exit_code !== undefined) parts.push(`Exit code: ${result.exit_code}`);
326
+
327
+ const message = parts[0];
328
+ const content = parts.join('\n\n');
316
329
  const state: RunCommandState = { message, result };
317
330
 
318
331
  return {
319
- content: JSON.stringify(result),
332
+ content,
320
333
  state,
321
334
  success: result.success,
322
335
  };
@@ -337,10 +350,14 @@ export class LocalSystemExecutionRuntime {
337
350
  ? `Output retrieved. Running: ${result.running}`
338
351
  : `Failed: ${result.error}`;
339
352
 
353
+ const parts: string[] = [message];
354
+ if (result.output) parts.push(`Output:\n${result.output}`);
355
+ if (result.error) parts.push(`Error: ${result.error}`);
356
+
340
357
  const state: GetCommandOutputState = { message, result };
341
358
 
342
359
  return {
343
- content: JSON.stringify(result),
360
+ content: parts.join('\n\n'),
344
361
  state,
345
362
  success: result.success,
346
363
  };
@@ -364,7 +381,7 @@ export class LocalSystemExecutionRuntime {
364
381
  const state: KillCommandState = { message, result };
365
382
 
366
383
  return {
367
- content: JSON.stringify(result),
384
+ content: message,
368
385
  state,
369
386
  success: result.success,
370
387
  };
@@ -389,8 +406,19 @@ export class LocalSystemExecutionRuntime {
389
406
 
390
407
  const state: GrepContentState = { message, result };
391
408
 
409
+ let content = message;
410
+ if (result.success && result.matches.length > 0) {
411
+ const matchList = result.matches
412
+ .slice(0, 20)
413
+ .map((m) => ` ${m}`)
414
+ .join('\n');
415
+ const moreInfo =
416
+ result.matches.length > 20 ? `\n ... and ${result.matches.length - 20} more` : '';
417
+ content = `${message}:\n${matchList}${moreInfo}`;
418
+ }
419
+
392
420
  return {
393
- content: JSON.stringify(result),
421
+ content,
394
422
  state,
395
423
  success: result.success,
396
424
  };
@@ -411,8 +439,19 @@ export class LocalSystemExecutionRuntime {
411
439
 
412
440
  const state: GlobFilesState = { message, result };
413
441
 
442
+ let content = message;
443
+ if (result.success && result.files.length > 0) {
444
+ const fileList = result.files
445
+ .slice(0, 50)
446
+ .map((f) => ` ${f}`)
447
+ .join('\n');
448
+ const moreInfo =
449
+ result.files.length > 50 ? `\n ... and ${result.files.length - 50} more` : '';
450
+ content = `${message}:\n${fileList}${moreInfo}`;
451
+ }
452
+
414
453
  return {
415
- content: JSON.stringify(result),
454
+ content,
416
455
  state,
417
456
  success: result.success,
418
457
  };
@@ -23,6 +23,21 @@ import type {
23
23
  RunCommandResult,
24
24
  WriteLocalFileParams,
25
25
  } from '@lobechat/electron-client-ipc';
26
+ import {
27
+ formatCommandOutput,
28
+ formatCommandResult,
29
+ formatEditResult,
30
+ formatFileContent,
31
+ formatFileList,
32
+ formatFileSearchResults,
33
+ formatGlobResults,
34
+ formatGrepResults,
35
+ formatKillResult,
36
+ formatMoveResults,
37
+ formatMultipleFiles,
38
+ formatRenameResult,
39
+ formatWriteResult,
40
+ } from '@lobechat/prompts';
26
41
  import { BaseExecutor, type BuiltinToolResult } from '@lobechat/types';
27
42
 
28
43
  import { localFileService } from '@/services/electron/localFileService';
@@ -76,8 +91,10 @@ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
76
91
 
77
92
  const state: LocalFileListState = { listResults: result };
78
93
 
94
+ const content = formatFileList(result, params.path);
95
+
79
96
  return {
80
- content: JSON.stringify(result),
97
+ content,
81
98
  state,
82
99
  success: true,
83
100
  };
@@ -96,8 +113,14 @@ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
96
113
 
97
114
  const state: LocalReadFileState = { fileContent: result };
98
115
 
116
+ const content = formatFileContent({
117
+ content: result.content,
118
+ lineRange: params.loc,
119
+ path: params.path,
120
+ });
121
+
99
122
  return {
100
- content: JSON.stringify(result),
123
+ content,
101
124
  state,
102
125
  success: true,
103
126
  };
@@ -116,8 +139,10 @@ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
116
139
 
117
140
  const state: LocalReadFilesState = { filesContent: results };
118
141
 
142
+ const content = formatMultipleFiles(results);
143
+
119
144
  return {
120
- content: JSON.stringify(results),
145
+ content,
121
146
  state,
122
147
  success: true,
123
148
  };
@@ -136,8 +161,10 @@ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
136
161
 
137
162
  const state: LocalFileSearchState = { searchResults: result };
138
163
 
164
+ const content = formatFileSearchResults(result);
165
+
139
166
  return {
140
- content: JSON.stringify(result),
167
+ content,
141
168
  state,
142
169
  success: true,
143
170
  };
@@ -154,20 +181,9 @@ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
154
181
  try {
155
182
  const results: LocalMoveFilesResultItem[] = await localFileService.moveLocalFiles(params);
156
183
 
157
- const allSucceeded = results.every((r) => r.success);
158
- const someFailed = results.some((r) => !r.success);
159
184
  const successCount = results.filter((r) => r.success).length;
160
- const failedCount = results.length - successCount;
161
185
 
162
- let message = '';
163
-
164
- if (allSucceeded) {
165
- message = `Successfully moved ${results.length} item(s).`;
166
- } else if (someFailed) {
167
- message = `Moved ${successCount} item(s) successfully. Failed to move ${failedCount} item(s).`;
168
- } else {
169
- message = `Failed to move all ${results.length} item(s).`;
170
- }
186
+ const content = formatMoveResults(results);
171
187
 
172
188
  const state: LocalMoveFilesState = {
173
189
  results,
@@ -176,7 +192,7 @@ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
176
192
  };
177
193
 
178
194
  return {
179
- content: JSON.stringify({ message, results }),
195
+ content,
180
196
  state,
181
197
  success: true,
182
198
  };
@@ -202,7 +218,12 @@ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
202
218
  };
203
219
 
204
220
  return {
205
- content: JSON.stringify({ message: result.error, success: false }),
221
+ content: formatRenameResult({
222
+ error: result.error,
223
+ newName: params.newName,
224
+ oldPath: params.path,
225
+ success: false,
226
+ }),
206
227
  state,
207
228
  success: false,
208
229
  };
@@ -215,8 +236,9 @@ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
215
236
  };
216
237
 
217
238
  return {
218
- content: JSON.stringify({
219
- message: `Successfully renamed file ${params.path} to ${params.newName}.`,
239
+ content: formatRenameResult({
240
+ newName: params.newName,
241
+ oldPath: params.path,
220
242
  success: true,
221
243
  }),
222
244
  state,
@@ -237,8 +259,9 @@ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
237
259
 
238
260
  if (!result.success) {
239
261
  return {
240
- content: JSON.stringify({
241
- message: result.error || 'Failed to write file',
262
+ content: formatWriteResult({
263
+ error: result.error,
264
+ path: params.path,
242
265
  success: false,
243
266
  }),
244
267
  error: { message: result.error || 'Failed to write file', type: 'PluginServerError' },
@@ -247,8 +270,8 @@ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
247
270
  }
248
271
 
249
272
  return {
250
- content: JSON.stringify({
251
- message: `Successfully wrote file ${params.path}`,
273
+ content: formatWriteResult({
274
+ path: params.path,
252
275
  success: true,
253
276
  }),
254
277
  success: true,
@@ -273,11 +296,12 @@ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
273
296
  };
274
297
  }
275
298
 
276
- const statsText =
277
- result.linesAdded || result.linesDeleted
278
- ? ` (+${result.linesAdded || 0} -${result.linesDeleted || 0})`
279
- : '';
280
- const message = `Successfully replaced ${result.replacements} occurrence(s) in ${params.file_path}${statsText}`;
299
+ const content = formatEditResult({
300
+ filePath: params.file_path,
301
+ linesAdded: result.linesAdded,
302
+ linesDeleted: result.linesDeleted,
303
+ replacements: result.replacements,
304
+ });
281
305
 
282
306
  const state: EditLocalFileState = {
283
307
  diffText: result.diffText,
@@ -287,7 +311,7 @@ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
287
311
  };
288
312
 
289
313
  return {
290
- content: message,
314
+ content,
291
315
  state,
292
316
  success: true,
293
317
  };
@@ -306,22 +330,19 @@ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
306
330
  try {
307
331
  const result: RunCommandResult = await localFileService.runCommand(params);
308
332
 
309
- let message: string;
310
-
311
- if (result.success) {
312
- if (result.shell_id) {
313
- message = `Command started in background with shell_id: ${result.shell_id}`;
314
- } else {
315
- message = `Command completed successfully.`;
316
- }
317
- } else {
318
- message = `Command failed: ${result.error}`;
319
- }
333
+ const content = formatCommandResult({
334
+ error: result.error,
335
+ exitCode: result.exit_code,
336
+ shellId: result.shell_id,
337
+ stderr: result.stderr,
338
+ stdout: result.stdout,
339
+ success: result.success,
340
+ });
320
341
 
321
- const state: RunCommandState = { message, result };
342
+ const state: RunCommandState = { message: content.split('\n\n')[0], result };
322
343
 
323
344
  return {
324
- content: JSON.stringify(result),
345
+ content,
325
346
  state,
326
347
  success: result.success,
327
348
  };
@@ -338,14 +359,17 @@ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
338
359
  try {
339
360
  const result: GetCommandOutputResult = await localFileService.getCommandOutput(params);
340
361
 
341
- const message = result.success
342
- ? `Output retrieved. Running: ${result.running}`
343
- : `Failed: ${result.error}`;
362
+ const content = formatCommandOutput({
363
+ error: result.error,
364
+ output: result.output,
365
+ running: result.running,
366
+ success: result.success,
367
+ });
344
368
 
345
- const state: GetCommandOutputState = { message, result };
369
+ const state: GetCommandOutputState = { message: content.split('\n\n')[0], result };
346
370
 
347
371
  return {
348
- content: JSON.stringify(result),
372
+ content,
349
373
  state,
350
374
  success: result.success,
351
375
  };
@@ -362,14 +386,16 @@ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
362
386
  try {
363
387
  const result: KillCommandResult = await localFileService.killCommand(params);
364
388
 
365
- const message = result.success
366
- ? `Successfully killed shell: ${params.shell_id}`
367
- : `Failed to kill shell: ${result.error}`;
389
+ const content = formatKillResult({
390
+ error: result.error,
391
+ shellId: params.shell_id,
392
+ success: result.success,
393
+ });
368
394
 
369
- const state: KillCommandState = { message, result };
395
+ const state: KillCommandState = { message: content, result };
370
396
 
371
397
  return {
372
- content: JSON.stringify(result),
398
+ content,
373
399
  state,
374
400
  success: result.success,
375
401
  };
@@ -388,14 +414,17 @@ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
388
414
  try {
389
415
  const result: GrepContentResult = await localFileService.grepContent(params);
390
416
 
391
- const message = result.success
392
- ? `Found ${result.total_matches} matches in ${result.matches.length} locations`
417
+ const content = result.success
418
+ ? formatGrepResults({
419
+ matches: result.matches,
420
+ totalMatches: result.total_matches,
421
+ })
393
422
  : 'Search failed';
394
423
 
395
- const state: GrepContentState = { message, result };
424
+ const state: GrepContentState = { message: content.split('\n')[0], result };
396
425
 
397
426
  return {
398
- content: JSON.stringify(result),
427
+ content,
399
428
  state,
400
429
  success: result.success,
401
430
  };
@@ -412,12 +441,17 @@ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
412
441
  try {
413
442
  const result: GlobFilesResult = await localFileService.globFiles(params);
414
443
 
415
- const message = result.success ? `Found ${result.total_files} files` : 'Glob search failed';
444
+ const content = result.success
445
+ ? formatGlobResults({
446
+ files: result.files,
447
+ totalFiles: result.total_files,
448
+ })
449
+ : 'Glob search failed';
416
450
 
417
- const state: GlobFilesState = { message, result };
451
+ const state: GlobFilesState = { message: content.split('\n')[0], result };
418
452
 
419
453
  return {
420
- content: JSON.stringify(result),
454
+ content,
421
455
  state,
422
456
  success: result.success,
423
457
  };
@@ -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 (message.role === 'assistantGroup' && message.children) {
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 assistantGroup message ${message.id} with ${message.children.length} children`,
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();