@lobehub/lobehub 2.0.0-next.306 → 2.0.0-next.308

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 (254) hide show
  1. package/.vscode/settings.json +18 -3
  2. package/CHANGELOG.md +61 -0
  3. package/changelog/v1.json +21 -0
  4. package/locales/ar/agentGroup.json +5 -0
  5. package/locales/ar/chat.json +26 -0
  6. package/locales/ar/models.json +43 -5
  7. package/locales/ar/plugin.json +4 -5
  8. package/locales/ar/setting.json +11 -0
  9. package/locales/ar/subscription.json +2 -0
  10. package/locales/ar/tool.json +2 -0
  11. package/locales/bg-BG/agentGroup.json +5 -0
  12. package/locales/bg-BG/chat.json +26 -0
  13. package/locales/bg-BG/models.json +49 -3
  14. package/locales/bg-BG/plugin.json +4 -5
  15. package/locales/bg-BG/setting.json +11 -0
  16. package/locales/bg-BG/subscription.json +2 -0
  17. package/locales/bg-BG/tool.json +2 -0
  18. package/locales/de-DE/agentGroup.json +5 -0
  19. package/locales/de-DE/chat.json +26 -0
  20. package/locales/de-DE/models.json +48 -5
  21. package/locales/de-DE/plugin.json +4 -5
  22. package/locales/de-DE/setting.json +11 -0
  23. package/locales/de-DE/subscription.json +2 -0
  24. package/locales/de-DE/tool.json +2 -0
  25. package/locales/en-US/models.json +8 -6
  26. package/locales/en-US/plugin.json +2 -4
  27. package/locales/en-US/setting.json +10 -11
  28. package/locales/en-US/tool.json +2 -0
  29. package/locales/es-ES/agentGroup.json +5 -0
  30. package/locales/es-ES/chat.json +26 -0
  31. package/locales/es-ES/models.json +43 -5
  32. package/locales/es-ES/plugin.json +4 -5
  33. package/locales/es-ES/setting.json +11 -0
  34. package/locales/es-ES/subscription.json +2 -0
  35. package/locales/es-ES/tool.json +2 -0
  36. package/locales/fa-IR/agentGroup.json +5 -0
  37. package/locales/fa-IR/chat.json +26 -0
  38. package/locales/fa-IR/models.json +42 -5
  39. package/locales/fa-IR/plugin.json +4 -5
  40. package/locales/fa-IR/setting.json +11 -0
  41. package/locales/fa-IR/subscription.json +2 -0
  42. package/locales/fa-IR/tool.json +2 -0
  43. package/locales/fr-FR/agentGroup.json +5 -0
  44. package/locales/fr-FR/chat.json +26 -0
  45. package/locales/fr-FR/models.json +5 -5
  46. package/locales/fr-FR/plugin.json +4 -5
  47. package/locales/fr-FR/setting.json +11 -0
  48. package/locales/fr-FR/subscription.json +2 -0
  49. package/locales/fr-FR/tool.json +2 -0
  50. package/locales/it-IT/agentGroup.json +5 -0
  51. package/locales/it-IT/chat.json +26 -0
  52. package/locales/it-IT/models.json +1 -3
  53. package/locales/it-IT/plugin.json +4 -5
  54. package/locales/it-IT/setting.json +11 -0
  55. package/locales/it-IT/subscription.json +2 -0
  56. package/locales/it-IT/tool.json +2 -0
  57. package/locales/ja-JP/agentGroup.json +5 -0
  58. package/locales/ja-JP/chat.json +26 -0
  59. package/locales/ja-JP/models.json +1 -5
  60. package/locales/ja-JP/plugin.json +4 -5
  61. package/locales/ja-JP/setting.json +11 -0
  62. package/locales/ja-JP/subscription.json +2 -0
  63. package/locales/ja-JP/tool.json +2 -0
  64. package/locales/ko-KR/agentGroup.json +5 -0
  65. package/locales/ko-KR/chat.json +26 -0
  66. package/locales/ko-KR/models.json +1 -3
  67. package/locales/ko-KR/plugin.json +4 -5
  68. package/locales/ko-KR/setting.json +11 -0
  69. package/locales/ko-KR/subscription.json +2 -0
  70. package/locales/ko-KR/tool.json +2 -0
  71. package/locales/nl-NL/agentGroup.json +5 -0
  72. package/locales/nl-NL/chat.json +26 -0
  73. package/locales/nl-NL/models.json +35 -3
  74. package/locales/nl-NL/plugin.json +4 -5
  75. package/locales/nl-NL/setting.json +11 -0
  76. package/locales/nl-NL/subscription.json +2 -0
  77. package/locales/nl-NL/tool.json +2 -0
  78. package/locales/pl-PL/agentGroup.json +5 -0
  79. package/locales/pl-PL/chat.json +26 -0
  80. package/locales/pl-PL/models.json +1 -3
  81. package/locales/pl-PL/plugin.json +4 -5
  82. package/locales/pl-PL/setting.json +11 -0
  83. package/locales/pl-PL/subscription.json +2 -0
  84. package/locales/pl-PL/tool.json +2 -0
  85. package/locales/pt-BR/agentGroup.json +5 -0
  86. package/locales/pt-BR/chat.json +26 -0
  87. package/locales/pt-BR/models.json +50 -5
  88. package/locales/pt-BR/plugin.json +4 -5
  89. package/locales/pt-BR/setting.json +11 -0
  90. package/locales/pt-BR/subscription.json +2 -0
  91. package/locales/pt-BR/tool.json +2 -0
  92. package/locales/ru-RU/agentGroup.json +5 -0
  93. package/locales/ru-RU/chat.json +26 -0
  94. package/locales/ru-RU/models.json +22 -3
  95. package/locales/ru-RU/plugin.json +4 -5
  96. package/locales/ru-RU/setting.json +11 -0
  97. package/locales/ru-RU/subscription.json +2 -0
  98. package/locales/ru-RU/tool.json +2 -0
  99. package/locales/tr-TR/agentGroup.json +5 -0
  100. package/locales/tr-TR/chat.json +26 -0
  101. package/locales/tr-TR/models.json +36 -3
  102. package/locales/tr-TR/plugin.json +4 -5
  103. package/locales/tr-TR/setting.json +11 -0
  104. package/locales/tr-TR/subscription.json +2 -0
  105. package/locales/tr-TR/tool.json +2 -0
  106. package/locales/vi-VN/agentGroup.json +5 -0
  107. package/locales/vi-VN/chat.json +26 -0
  108. package/locales/vi-VN/models.json +1 -1
  109. package/locales/vi-VN/plugin.json +4 -5
  110. package/locales/vi-VN/setting.json +11 -0
  111. package/locales/vi-VN/subscription.json +2 -0
  112. package/locales/vi-VN/tool.json +2 -0
  113. package/locales/zh-CN/models.json +52 -5
  114. package/locales/zh-CN/plugin.json +5 -7
  115. package/locales/zh-CN/setting.json +10 -11
  116. package/locales/zh-CN/tool.json +2 -2
  117. package/locales/zh-TW/agentGroup.json +5 -0
  118. package/locales/zh-TW/chat.json +26 -0
  119. package/locales/zh-TW/models.json +54 -5
  120. package/locales/zh-TW/plugin.json +4 -5
  121. package/locales/zh-TW/setting.json +11 -0
  122. package/locales/zh-TW/subscription.json +2 -0
  123. package/locales/zh-TW/tool.json +2 -0
  124. package/package.json +2 -2
  125. package/packages/builtin-agents/src/agents/group-supervisor/index.ts +1 -7
  126. package/packages/builtin-tool-group-agent-builder/src/ExecutionRuntime/index.ts +29 -0
  127. package/packages/builtin-tool-group-agent-builder/src/executor.ts +18 -0
  128. package/packages/builtin-tool-group-agent-builder/src/manifest.ts +17 -0
  129. package/packages/builtin-tool-group-agent-builder/src/types.ts +10 -0
  130. package/packages/builtin-tool-group-management/src/client/Inspector/ExecuteAgentTask/index.tsx +52 -8
  131. package/packages/builtin-tool-group-management/src/client/Render/ExecuteTask/index.tsx +2 -21
  132. package/packages/builtin-tool-group-management/src/executor.test.ts +6 -16
  133. package/packages/builtin-tool-group-management/src/executor.ts +8 -47
  134. package/packages/builtin-tool-group-management/src/manifest.ts +5 -18
  135. package/packages/builtin-tool-group-management/src/systemRole.ts +1 -8
  136. package/packages/builtin-tool-group-management/src/types.ts +2 -10
  137. package/packages/builtin-tool-local-system/src/ExecutionRuntime/index.ts +70 -31
  138. package/packages/builtin-tool-local-system/src/client/Render/WriteFile/index.tsx +48 -5
  139. package/packages/builtin-tool-local-system/src/client/Streaming/WriteFile/index.tsx +39 -0
  140. package/packages/builtin-tool-local-system/src/client/Streaming/index.ts +2 -0
  141. package/packages/builtin-tool-local-system/src/executor/index.ts +94 -60
  142. package/packages/database/src/repositories/agentGroup/index.ts +23 -0
  143. package/packages/model-bank/src/aiModels/qiniu.ts +24 -0
  144. package/packages/prompts/src/prompts/fileSystem/formatCommandOutput.test.ts +61 -0
  145. package/packages/prompts/src/prompts/fileSystem/formatCommandOutput.ts +21 -0
  146. package/packages/prompts/src/prompts/fileSystem/formatCommandResult.test.ts +87 -0
  147. package/packages/prompts/src/prompts/fileSystem/formatCommandResult.ts +35 -0
  148. package/packages/prompts/src/prompts/fileSystem/formatEditResult.test.ts +57 -0
  149. package/packages/prompts/src/prompts/fileSystem/formatEditResult.ts +17 -0
  150. package/packages/prompts/src/prompts/fileSystem/formatFileContent.test.ts +59 -0
  151. package/packages/prompts/src/prompts/fileSystem/formatFileContent.ts +14 -0
  152. package/packages/prompts/src/prompts/fileSystem/formatFileList.test.ts +62 -0
  153. package/packages/prompts/src/prompts/fileSystem/formatFileList.ts +13 -0
  154. package/packages/prompts/src/prompts/fileSystem/formatFileSearchResults.test.ts +34 -0
  155. package/packages/prompts/src/prompts/fileSystem/formatFileSearchResults.ts +12 -0
  156. package/packages/prompts/src/prompts/fileSystem/formatGlobResults.test.ts +64 -0
  157. package/packages/prompts/src/prompts/fileSystem/formatGlobResults.ts +23 -0
  158. package/packages/prompts/src/prompts/fileSystem/formatGrepResults.test.ts +85 -0
  159. package/packages/prompts/src/prompts/fileSystem/formatGrepResults.ts +24 -0
  160. package/packages/prompts/src/prompts/fileSystem/formatKillResult.test.ts +30 -0
  161. package/packages/prompts/src/prompts/fileSystem/formatKillResult.ts +9 -0
  162. package/packages/prompts/src/prompts/fileSystem/formatMoveResults.test.ts +37 -0
  163. package/packages/prompts/src/prompts/fileSystem/formatMoveResults.ts +20 -0
  164. package/packages/prompts/src/prompts/fileSystem/formatMultipleFiles.test.ts +54 -0
  165. package/packages/prompts/src/prompts/fileSystem/formatMultipleFiles.ts +9 -0
  166. package/packages/prompts/src/prompts/fileSystem/formatRenameResult.test.ts +35 -0
  167. package/packages/prompts/src/prompts/fileSystem/formatRenameResult.ts +17 -0
  168. package/packages/prompts/src/prompts/fileSystem/formatWriteResult.test.ts +30 -0
  169. package/packages/prompts/src/prompts/fileSystem/formatWriteResult.ts +11 -0
  170. package/packages/prompts/src/prompts/fileSystem/index.ts +13 -0
  171. package/packages/prompts/src/prompts/index.ts +1 -0
  172. package/src/app/[variants]/(auth)/_layout/index.tsx +0 -2
  173. package/src/app/[variants]/(auth)/layout.tsx +0 -2
  174. package/src/app/[variants]/(auth)/login/[[...login]]/page.tsx +1 -3
  175. package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +1 -3
  176. package/src/app/[variants]/(desktop)/desktop-onboarding/_layout/index.tsx +0 -2
  177. package/src/app/[variants]/(main)/_layout/index.tsx +0 -2
  178. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/Actions.tsx +4 -3
  179. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/useDropdownMenu.tsx +12 -2
  180. package/src/app/[variants]/(main)/agent/_layout/index.tsx +0 -2
  181. package/src/app/[variants]/(main)/agent/features/Portal/index.tsx +0 -2
  182. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/ActionButton/AddGroupAgent.tsx +69 -17
  183. package/src/app/[variants]/(main)/community/(list)/_layout/index.tsx +0 -2
  184. package/src/app/[variants]/(main)/community/(list)/assistant/_layout/index.tsx +0 -2
  185. package/src/app/[variants]/(main)/community/(list)/mcp/_layout/index.tsx +0 -2
  186. package/src/app/[variants]/(main)/community/(list)/model/_layout/index.tsx +0 -2
  187. package/src/app/[variants]/(main)/community/_layout/index.tsx +0 -2
  188. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/Actions.tsx +4 -3
  189. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/useDropdownMenu.tsx +12 -2
  190. package/src/app/[variants]/(main)/group/_layout/index.tsx +0 -2
  191. package/src/app/[variants]/(main)/group/features/Conversation/Header/index.tsx +4 -2
  192. package/src/app/[variants]/(main)/group/features/Portal/index.tsx +0 -2
  193. package/src/app/[variants]/(main)/home/_layout/index.tsx +0 -2
  194. package/src/app/[variants]/(main)/home/index.tsx +0 -2
  195. package/src/app/[variants]/(main)/image/_layout/Topics/TopicUrlSync.tsx +0 -2
  196. package/src/app/[variants]/(main)/image/_layout/index.tsx +0 -2
  197. package/src/app/[variants]/(main)/memory/_layout/index.tsx +0 -2
  198. package/src/app/[variants]/(main)/page/_layout/index.tsx +0 -2
  199. package/src/app/[variants]/(main)/resource/(home)/_layout/index.tsx +0 -2
  200. package/src/app/[variants]/(main)/resource/_layout/index.tsx +0 -2
  201. package/src/app/[variants]/(main)/resource/library/_layout/index.tsx +0 -2
  202. package/src/app/[variants]/(main)/resource/library/features/Container.tsx +0 -2
  203. package/src/app/[variants]/(main)/settings/_layout/index.tsx +0 -2
  204. package/src/app/[variants]/(main)/settings/about/index.tsx +0 -2
  205. package/src/app/[variants]/(main)/settings/agent/index.tsx +0 -2
  206. package/src/app/[variants]/(main)/settings/apikey/index.tsx +0 -2
  207. package/src/app/[variants]/(main)/settings/chat-appearance/index.tsx +0 -2
  208. package/src/app/[variants]/(main)/settings/common/index.tsx +0 -2
  209. package/src/app/[variants]/(main)/settings/hotkey/index.tsx +0 -2
  210. package/src/app/[variants]/(main)/settings/image/index.tsx +0 -2
  211. package/src/app/[variants]/(main)/settings/memory/index.tsx +0 -2
  212. package/src/app/[variants]/(main)/settings/provider/(list)/index.tsx +0 -2
  213. package/src/app/[variants]/(main)/settings/proxy/index.tsx +0 -2
  214. package/src/app/[variants]/(main)/settings/security/index.tsx +1 -3
  215. package/src/app/[variants]/(main)/settings/storage/index.tsx +0 -2
  216. package/src/app/[variants]/(main)/settings/tts/index.tsx +0 -2
  217. package/src/app/[variants]/(mobile)/(home)/_layout/index.tsx +0 -2
  218. package/src/app/[variants]/(mobile)/_layout/index.tsx +1 -3
  219. package/src/app/[variants]/(mobile)/chat/_layout/index.tsx +0 -2
  220. package/src/app/[variants]/(mobile)/chat/settings/_layout/index.tsx +0 -2
  221. package/src/app/[variants]/(mobile)/community/(detail)/_layout/index.tsx +0 -2
  222. package/src/app/[variants]/(mobile)/community/(list)/_layout/index.tsx +0 -2
  223. package/src/app/[variants]/(mobile)/community/_layout/index.tsx +0 -2
  224. package/src/app/[variants]/(mobile)/router/MobileClientRouter.tsx +0 -2
  225. package/src/app/[variants]/(mobile)/settings/index.tsx +0 -2
  226. package/src/app/[variants]/onboarding/_layout/index.tsx +0 -2
  227. package/src/app/[variants]/router/DesktopClientRouter.tsx +0 -2
  228. package/src/components/ModelSelect/index.tsx +6 -56
  229. package/src/components/server/MobileNavLayout.tsx +0 -2
  230. package/src/components/server/ServerLayout.tsx +0 -2
  231. package/src/features/ChatInput/ActionBar/Upload/ServerMode.tsx +13 -3
  232. package/src/features/ChatInput/ActionBar/components/ActionDropdown.tsx +26 -3
  233. package/src/features/ModelSwitchPanel/components/Footer.tsx +0 -2
  234. package/src/features/ModelSwitchPanel/components/List/MultipleProvidersModelItem.tsx +0 -1
  235. package/src/features/ModelSwitchPanel/components/List/SingleProviderModelItem.tsx +0 -1
  236. package/src/features/ModelSwitchPanel/components/List/VirtualItemRenderer.tsx +0 -1
  237. package/src/features/ModelSwitchPanel/components/List/index.tsx +15 -13
  238. package/src/features/ModelSwitchPanel/components/PanelContent.tsx +0 -2
  239. package/src/features/ModelSwitchPanel/index.tsx +21 -23
  240. package/src/features/ResourceManager/components/Explorer/MasonryView/index.tsx +0 -2
  241. package/src/features/ResourceManager/components/Header/AddButton.tsx +20 -3
  242. package/src/features/User/UserAvatar.tsx +0 -2
  243. package/src/locales/default/plugin.ts +2 -1
  244. package/src/server/routers/lambda/__tests__/agentGroup.test.ts +1 -0
  245. package/src/server/routers/lambda/agentGroup.ts +22 -0
  246. package/src/services/chat/index.ts +1 -0
  247. package/src/services/chat/mecha/agentConfigResolver.test.ts +62 -45
  248. package/src/services/chat/mecha/agentConfigResolver.ts +29 -27
  249. package/src/services/chatGroup/index.ts +14 -0
  250. package/src/store/chat/agents/GroupOrchestration/__tests__/call-supervisor.test.ts +305 -0
  251. package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +2 -1
  252. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +6 -2
  253. package/src/store/chat/slices/plugin/actions/exector.ts +92 -0
  254. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +82 -177
@@ -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
+ };
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { formatEditResult } from './formatEditResult';
4
+
5
+ describe('formatEditResult', () => {
6
+ it('should format edit result without line stats', () => {
7
+ const result = formatEditResult({
8
+ filePath: '/src/index.ts',
9
+ replacements: 3,
10
+ });
11
+ expect(result).toMatchInlineSnapshot(
12
+ `"Successfully replaced 3 occurrence(s) in /src/index.ts"`,
13
+ );
14
+ });
15
+
16
+ it('should format edit result with lines added', () => {
17
+ const result = formatEditResult({
18
+ filePath: '/src/index.ts',
19
+ linesAdded: 5,
20
+ replacements: 1,
21
+ });
22
+ expect(result).toMatchInlineSnapshot(
23
+ `"Successfully replaced 1 occurrence(s) in /src/index.ts (+5 -0)"`,
24
+ );
25
+ });
26
+
27
+ it('should format edit result with lines deleted', () => {
28
+ const result = formatEditResult({
29
+ filePath: '/src/index.ts',
30
+ linesDeleted: 3,
31
+ replacements: 2,
32
+ });
33
+ expect(result).toMatchInlineSnapshot(
34
+ `"Successfully replaced 2 occurrence(s) in /src/index.ts (+0 -3)"`,
35
+ );
36
+ });
37
+
38
+ it('should format edit result with both lines added and deleted', () => {
39
+ const result = formatEditResult({
40
+ filePath: '/src/utils.ts',
41
+ linesAdded: 10,
42
+ linesDeleted: 5,
43
+ replacements: 4,
44
+ });
45
+ expect(result).toMatchInlineSnapshot(
46
+ `"Successfully replaced 4 occurrence(s) in /src/utils.ts (+10 -5)"`,
47
+ );
48
+ });
49
+
50
+ it('should handle zero replacements', () => {
51
+ const result = formatEditResult({
52
+ filePath: '/test.txt',
53
+ replacements: 0,
54
+ });
55
+ expect(result).toMatchInlineSnapshot(`"Successfully replaced 0 occurrence(s) in /test.txt"`);
56
+ });
57
+ });
@@ -0,0 +1,17 @@
1
+ export interface FormatEditResultParams {
2
+ filePath: string;
3
+ linesAdded?: number;
4
+ linesDeleted?: number;
5
+ replacements: number;
6
+ }
7
+
8
+ export const formatEditResult = ({
9
+ replacements,
10
+ filePath,
11
+ linesAdded,
12
+ linesDeleted,
13
+ }: FormatEditResultParams): string => {
14
+ const statsText =
15
+ linesAdded || linesDeleted ? ` (+${linesAdded || 0} -${linesDeleted || 0})` : '';
16
+ return `Successfully replaced ${replacements} occurrence(s) in ${filePath}${statsText}`;
17
+ };
@@ -0,0 +1,59 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { formatFileContent } from './formatFileContent';
4
+
5
+ describe('formatFileContent', () => {
6
+ it('should format file content without line range', () => {
7
+ const result = formatFileContent({
8
+ content: 'console.log("hello");',
9
+ path: '/src/index.ts',
10
+ });
11
+ expect(result).toMatchInlineSnapshot(`
12
+ "File: /src/index.ts
13
+
14
+ console.log("hello");"
15
+ `);
16
+ });
17
+
18
+ it('should format file content with line range', () => {
19
+ const result = formatFileContent({
20
+ content: 'function test() {\n return true;\n}',
21
+ lineRange: [10, 12],
22
+ path: '/src/utils.ts',
23
+ });
24
+ expect(result).toMatchInlineSnapshot(`
25
+ "File: /src/utils.ts (lines 10-12)
26
+
27
+ function test() {
28
+ return true;
29
+ }"
30
+ `);
31
+ });
32
+
33
+ it('should handle empty content', () => {
34
+ const result = formatFileContent({
35
+ content: '',
36
+ path: '/empty.txt',
37
+ });
38
+ expect(result).toMatchInlineSnapshot(`
39
+ "File: /empty.txt
40
+
41
+ "
42
+ `);
43
+ });
44
+
45
+ it('should handle multiline content', () => {
46
+ const content = 'line 1\nline 2\nline 3';
47
+ const result = formatFileContent({
48
+ content,
49
+ path: '/test.txt',
50
+ });
51
+ expect(result).toMatchInlineSnapshot(`
52
+ "File: /test.txt
53
+
54
+ line 1
55
+ line 2
56
+ line 3"
57
+ `);
58
+ });
59
+ });
@@ -0,0 +1,14 @@
1
+ export interface FormatFileContentParams {
2
+ content: string;
3
+ lineRange?: [number, number];
4
+ path: string;
5
+ }
6
+
7
+ export const formatFileContent = ({
8
+ path,
9
+ content,
10
+ lineRange,
11
+ }: FormatFileContentParams): string => {
12
+ const lineInfo = lineRange ? ` (lines ${lineRange[0]}-${lineRange[1]})` : '';
13
+ return `File: ${path}${lineInfo}\n\n${content}`;
14
+ };
@@ -0,0 +1,62 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { formatFileList } from './formatFileList';
4
+
5
+ describe('formatFileList', () => {
6
+ it('should format empty directory', () => {
7
+ const result = formatFileList([], '/home/user');
8
+ expect(result).toMatchInlineSnapshot(`"Directory /home/user is empty"`);
9
+ });
10
+
11
+ it('should format directory with files only', () => {
12
+ const files = [
13
+ { isDirectory: false, name: 'file1.txt' },
14
+ { isDirectory: false, name: 'file2.js' },
15
+ ];
16
+ const result = formatFileList(files, '/home/user');
17
+ expect(result).toMatchInlineSnapshot(`
18
+ "Found 2 item(s) in /home/user:
19
+ [F] file1.txt
20
+ [F] file2.js"
21
+ `);
22
+ });
23
+
24
+ it('should format directory with directories only', () => {
25
+ const files = [
26
+ { isDirectory: true, name: 'src' },
27
+ { isDirectory: true, name: 'dist' },
28
+ ];
29
+ const result = formatFileList(files, '/project');
30
+ expect(result).toMatchInlineSnapshot(`
31
+ "Found 2 item(s) in /project:
32
+ [D] src
33
+ [D] dist"
34
+ `);
35
+ });
36
+
37
+ it('should format directory with mixed content', () => {
38
+ const files = [
39
+ { isDirectory: true, name: 'src' },
40
+ { isDirectory: false, name: 'package.json' },
41
+ { isDirectory: true, name: 'node_modules' },
42
+ { isDirectory: false, name: 'README.md' },
43
+ ];
44
+ const result = formatFileList(files, '/project');
45
+ expect(result).toMatchInlineSnapshot(`
46
+ "Found 4 item(s) in /project:
47
+ [D] src
48
+ [F] package.json
49
+ [D] node_modules
50
+ [F] README.md"
51
+ `);
52
+ });
53
+
54
+ it('should format single item', () => {
55
+ const files = [{ isDirectory: false, name: 'index.ts' }];
56
+ const result = formatFileList(files, '/src');
57
+ expect(result).toMatchInlineSnapshot(`
58
+ "Found 1 item(s) in /src:
59
+ [F] index.ts"
60
+ `);
61
+ });
62
+ });
@@ -0,0 +1,13 @@
1
+ export interface FileListItem {
2
+ isDirectory: boolean;
3
+ name: string;
4
+ }
5
+
6
+ export const formatFileList = (files: FileListItem[], directory: string): string => {
7
+ if (files.length === 0) {
8
+ return `Directory ${directory} is empty`;
9
+ }
10
+
11
+ const fileList = files.map((f) => ` ${f.isDirectory ? '[D]' : '[F]'} ${f.name}`).join('\n');
12
+ return `Found ${files.length} item(s) in ${directory}:\n${fileList}`;
13
+ };
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { formatFileSearchResults } from './formatFileSearchResults';
4
+
5
+ describe('formatFileSearchResults', () => {
6
+ it('should format empty results', () => {
7
+ const result = formatFileSearchResults([]);
8
+ expect(result).toMatchInlineSnapshot(`"No files found"`);
9
+ });
10
+
11
+ it('should format single result', () => {
12
+ const results = [{ path: '/src/index.ts' }];
13
+ const result = formatFileSearchResults(results);
14
+ expect(result).toMatchInlineSnapshot(`
15
+ "Found 1 file(s):
16
+ /src/index.ts"
17
+ `);
18
+ });
19
+
20
+ it('should format multiple results', () => {
21
+ const results = [
22
+ { path: '/src/index.ts' },
23
+ { path: '/src/utils.ts' },
24
+ { path: '/src/types.ts' },
25
+ ];
26
+ const result = formatFileSearchResults(results);
27
+ expect(result).toMatchInlineSnapshot(`
28
+ "Found 3 file(s):
29
+ /src/index.ts
30
+ /src/utils.ts
31
+ /src/types.ts"
32
+ `);
33
+ });
34
+ });
@@ -0,0 +1,12 @@
1
+ export interface FileSearchResultItem {
2
+ path: string;
3
+ }
4
+
5
+ export const formatFileSearchResults = (results: FileSearchResultItem[]): string => {
6
+ if (results.length === 0) {
7
+ return 'No files found';
8
+ }
9
+
10
+ const fileList = results.map((f) => ` ${f.path}`).join('\n');
11
+ return `Found ${results.length} file(s):\n${fileList}`;
12
+ };
@@ -0,0 +1,64 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { formatGlobResults } from './formatGlobResults';
4
+
5
+ describe('formatGlobResults', () => {
6
+ it('should format empty results', () => {
7
+ const result = formatGlobResults({
8
+ files: [],
9
+ totalFiles: 0,
10
+ });
11
+ expect(result).toMatchInlineSnapshot(`"Found 0 files"`);
12
+ });
13
+
14
+ it('should format single file', () => {
15
+ const result = formatGlobResults({
16
+ files: ['/src/index.ts'],
17
+ totalFiles: 1,
18
+ });
19
+ expect(result).toMatchInlineSnapshot(`
20
+ "Found 1 files:
21
+ /src/index.ts"
22
+ `);
23
+ });
24
+
25
+ it('should format multiple files', () => {
26
+ const result = formatGlobResults({
27
+ files: ['/src/index.ts', '/src/utils.ts', '/src/types.ts'],
28
+ totalFiles: 3,
29
+ });
30
+ expect(result).toMatchInlineSnapshot(`
31
+ "Found 3 files:
32
+ /src/index.ts
33
+ /src/utils.ts
34
+ /src/types.ts"
35
+ `);
36
+ });
37
+
38
+ it('should truncate files exceeding maxDisplay (default 50)', () => {
39
+ const files = Array.from({ length: 55 }, (_, i) => `/file${i + 1}.ts`);
40
+ const result = formatGlobResults({
41
+ files,
42
+ totalFiles: 55,
43
+ });
44
+ expect(result).toContain('Found 55 files:');
45
+ expect(result).toContain('... and 5 more');
46
+ expect(result).not.toContain('file51');
47
+ });
48
+
49
+ it('should respect custom maxDisplay', () => {
50
+ const files = ['/a.ts', '/b.ts', '/c.ts', '/d.ts', '/e.ts'];
51
+ const result = formatGlobResults({
52
+ files,
53
+ maxDisplay: 3,
54
+ totalFiles: 5,
55
+ });
56
+ expect(result).toMatchInlineSnapshot(`
57
+ "Found 5 files:
58
+ /a.ts
59
+ /b.ts
60
+ /c.ts
61
+ ... and 2 more"
62
+ `);
63
+ });
64
+ });
@@ -0,0 +1,23 @@
1
+ export interface FormatGlobResultsParams {
2
+ files: string[];
3
+ maxDisplay?: number;
4
+ totalFiles: number;
5
+ }
6
+
7
+ export const formatGlobResults = ({
8
+ totalFiles,
9
+ files,
10
+ maxDisplay = 50,
11
+ }: FormatGlobResultsParams): string => {
12
+ const message = `Found ${totalFiles} files`;
13
+
14
+ if (files.length === 0) {
15
+ return message;
16
+ }
17
+
18
+ const displayFiles = files.slice(0, maxDisplay);
19
+ const fileList = displayFiles.map((f) => ` ${f}`).join('\n');
20
+ const moreInfo = files.length > maxDisplay ? `\n ... and ${files.length - maxDisplay} more` : '';
21
+
22
+ return `${message}:\n${fileList}${moreInfo}`;
23
+ };
@@ -0,0 +1,85 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { formatGrepResults } from './formatGrepResults';
4
+
5
+ describe('formatGrepResults', () => {
6
+ it('should format empty results', () => {
7
+ const result = formatGrepResults({
8
+ matches: [],
9
+ totalMatches: 0,
10
+ });
11
+ expect(result).toMatchInlineSnapshot(`"Found 0 matches in 0 locations"`);
12
+ });
13
+
14
+ it('should format single match', () => {
15
+ const result = formatGrepResults({
16
+ matches: ['/src/index.ts:10: const foo = "bar"'],
17
+ totalMatches: 1,
18
+ });
19
+ expect(result).toMatchInlineSnapshot(`
20
+ "Found 1 matches in 1 locations:
21
+ /src/index.ts:10: const foo = "bar""
22
+ `);
23
+ });
24
+
25
+ it('should format multiple matches', () => {
26
+ const result = formatGrepResults({
27
+ matches: ['/src/index.ts:10: match1', '/src/utils.ts:20: match2', '/src/types.ts:30: match3'],
28
+ totalMatches: 5,
29
+ });
30
+ expect(result).toMatchInlineSnapshot(`
31
+ "Found 5 matches in 3 locations:
32
+ /src/index.ts:10: match1
33
+ /src/utils.ts:20: match2
34
+ /src/types.ts:30: match3"
35
+ `);
36
+ });
37
+
38
+ it('should truncate matches exceeding maxDisplay', () => {
39
+ const matches = Array.from({ length: 25 }, (_, i) => `match${i + 1}`);
40
+ const result = formatGrepResults({
41
+ matches,
42
+ totalMatches: 100,
43
+ });
44
+ expect(result).toMatchInlineSnapshot(`
45
+ "Found 100 matches in 25 locations:
46
+ match1
47
+ match2
48
+ match3
49
+ match4
50
+ match5
51
+ match6
52
+ match7
53
+ match8
54
+ match9
55
+ match10
56
+ match11
57
+ match12
58
+ match13
59
+ match14
60
+ match15
61
+ match16
62
+ match17
63
+ match18
64
+ match19
65
+ match20
66
+ ... and 5 more"
67
+ `);
68
+ });
69
+
70
+ it('should respect custom maxDisplay', () => {
71
+ const matches = ['match1', 'match2', 'match3', 'match4', 'match5'];
72
+ const result = formatGrepResults({
73
+ matches,
74
+ maxDisplay: 3,
75
+ totalMatches: 10,
76
+ });
77
+ expect(result).toMatchInlineSnapshot(`
78
+ "Found 10 matches in 5 locations:
79
+ match1
80
+ match2
81
+ match3
82
+ ... and 2 more"
83
+ `);
84
+ });
85
+ });
@@ -0,0 +1,24 @@
1
+ export interface FormatGrepResultsParams {
2
+ matches: string[];
3
+ maxDisplay?: number;
4
+ totalMatches: number;
5
+ }
6
+
7
+ export const formatGrepResults = ({
8
+ totalMatches,
9
+ matches,
10
+ maxDisplay = 20,
11
+ }: FormatGrepResultsParams): string => {
12
+ const message = `Found ${totalMatches} matches in ${matches.length} locations`;
13
+
14
+ if (matches.length === 0) {
15
+ return message;
16
+ }
17
+
18
+ const displayMatches = matches.slice(0, maxDisplay);
19
+ const matchList = displayMatches.map((m) => ` ${m}`).join('\n');
20
+ const moreInfo =
21
+ matches.length > maxDisplay ? `\n ... and ${matches.length - maxDisplay} more` : '';
22
+
23
+ return `${message}:\n${matchList}${moreInfo}`;
24
+ };
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { formatKillResult } from './formatKillResult';
4
+
5
+ describe('formatKillResult', () => {
6
+ it('should format successful kill', () => {
7
+ const result = formatKillResult({
8
+ shellId: 'shell-123',
9
+ success: true,
10
+ });
11
+ expect(result).toMatchInlineSnapshot(`"Successfully killed shell: shell-123"`);
12
+ });
13
+
14
+ it('should format failed kill', () => {
15
+ const result = formatKillResult({
16
+ error: 'Process not found',
17
+ shellId: 'shell-456',
18
+ success: false,
19
+ });
20
+ expect(result).toMatchInlineSnapshot(`"Failed to kill shell: Process not found"`);
21
+ });
22
+
23
+ it('should format failed kill without error message', () => {
24
+ const result = formatKillResult({
25
+ shellId: 'shell-789',
26
+ success: false,
27
+ });
28
+ expect(result).toMatchInlineSnapshot(`"Failed to kill shell: undefined"`);
29
+ });
30
+ });
@@ -0,0 +1,9 @@
1
+ export interface FormatKillResultParams {
2
+ error?: string;
3
+ shellId: string;
4
+ success: boolean;
5
+ }
6
+
7
+ export const formatKillResult = ({ success, shellId, error }: FormatKillResultParams): string => {
8
+ return success ? `Successfully killed shell: ${shellId}` : `Failed to kill shell: ${error}`;
9
+ };
@@ -0,0 +1,37 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { formatMoveResults } from './formatMoveResults';
4
+
5
+ describe('formatMoveResults', () => {
6
+ it('should format all successful moves', () => {
7
+ const results = [{ success: true }, { success: true }, { success: true }];
8
+ const result = formatMoveResults(results);
9
+ expect(result).toMatchInlineSnapshot(`"Successfully moved 3 item(s)."`);
10
+ });
11
+
12
+ it('should format all failed moves', () => {
13
+ const results = [{ success: false }, { success: false }];
14
+ const result = formatMoveResults(results);
15
+ expect(result).toMatchInlineSnapshot(`"Failed to move all 2 item(s)."`);
16
+ });
17
+
18
+ it('should format partial success', () => {
19
+ const results = [{ success: true }, { success: false }, { success: true }];
20
+ const result = formatMoveResults(results);
21
+ expect(result).toMatchInlineSnapshot(
22
+ `"Moved 2 item(s) successfully. Failed to move 1 item(s)."`,
23
+ );
24
+ });
25
+
26
+ it('should handle single item success', () => {
27
+ const results = [{ success: true }];
28
+ const result = formatMoveResults(results);
29
+ expect(result).toMatchInlineSnapshot(`"Successfully moved 1 item(s)."`);
30
+ });
31
+
32
+ it('should handle single item failure', () => {
33
+ const results = [{ success: false }];
34
+ const result = formatMoveResults(results);
35
+ expect(result).toMatchInlineSnapshot(`"Failed to move all 1 item(s)."`);
36
+ });
37
+ });
@@ -0,0 +1,20 @@
1
+ export interface MoveResultItem {
2
+ success: boolean;
3
+ }
4
+
5
+ export const formatMoveResults = (results: MoveResultItem[]): string => {
6
+ const successCount = results.filter((r) => r.success).length;
7
+ const failedCount = results.length - successCount;
8
+ const allSucceeded = failedCount === 0;
9
+ const allFailed = successCount === 0;
10
+
11
+ if (allSucceeded) {
12
+ return `Successfully moved ${results.length} item(s).`;
13
+ }
14
+
15
+ if (allFailed) {
16
+ return `Failed to move all ${results.length} item(s).`;
17
+ }
18
+
19
+ return `Moved ${successCount} item(s) successfully. Failed to move ${failedCount} item(s).`;
20
+ };
@@ -0,0 +1,54 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { formatMultipleFiles } from './formatMultipleFiles';
4
+
5
+ describe('formatMultipleFiles', () => {
6
+ it('should format single file', () => {
7
+ const files = [{ content: 'hello world', filename: 'test.txt' }];
8
+ const result = formatMultipleFiles(files);
9
+ expect(result).toMatchInlineSnapshot(`
10
+ "Read 1 file(s):
11
+
12
+ === test.txt ===
13
+ hello world"
14
+ `);
15
+ });
16
+
17
+ it('should format multiple files', () => {
18
+ const files = [
19
+ { content: 'content 1', filename: 'file1.txt' },
20
+ { content: 'content 2', filename: 'file2.txt' },
21
+ ];
22
+ const result = formatMultipleFiles(files);
23
+ expect(result).toMatchInlineSnapshot(`
24
+ "Read 2 file(s):
25
+
26
+ === file1.txt ===
27
+ content 1
28
+
29
+ === file2.txt ===
30
+ content 2"
31
+ `);
32
+ });
33
+
34
+ it('should handle empty files array', () => {
35
+ const result = formatMultipleFiles([]);
36
+ expect(result).toMatchInlineSnapshot(`
37
+ "Read 0 file(s):
38
+
39
+ "
40
+ `);
41
+ });
42
+
43
+ it('should handle files with multiline content', () => {
44
+ const files = [{ content: 'line 1\nline 2', filename: 'multi.txt' }];
45
+ const result = formatMultipleFiles(files);
46
+ expect(result).toMatchInlineSnapshot(`
47
+ "Read 1 file(s):
48
+
49
+ === multi.txt ===
50
+ line 1
51
+ line 2"
52
+ `);
53
+ });
54
+ });
@@ -0,0 +1,9 @@
1
+ export interface FileContentItem {
2
+ content: string;
3
+ filename: string;
4
+ }
5
+
6
+ export const formatMultipleFiles = (files: FileContentItem[]): string => {
7
+ const fileContents = files.map((f) => `=== ${f.filename} ===\n${f.content}`).join('\n\n');
8
+ return `Read ${files.length} file(s):\n\n${fileContents}`;
9
+ };