minimal-agent 0.11.0 → 0.12.0

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 (128) hide show
  1. package/package.json +4 -1
  2. package/plugins/workflow-runner/plugin.js +27 -15
  3. package/plugins/workflow-runner/plugin.ts +34 -20
  4. package/plugins/workflow-runner/src/agentCommon.js +13 -12
  5. package/plugins/workflow-runner/src/agentCommon.ts +22 -13
  6. package/plugins/workflow-runner/src/index.js +5 -3
  7. package/plugins/workflow-runner/src/index.ts +31 -13
  8. package/plugins/workflow-runner/src/multimodal.js +2 -1
  9. package/plugins/workflow-runner/src/multimodal.ts +2 -1
  10. package/plugins/workflow-runner/src/stepExecutors/skill.js +5 -6
  11. package/plugins/workflow-runner/src/stepExecutors/skill.ts +12 -7
  12. package/plugins/workflow-runner/src/stepExecutors/tool.js +68 -57
  13. package/plugins/workflow-runner/src/stepExecutors/tool.ts +88 -66
  14. package/plugins/workflow-runner/src/toolRuntime.js +48 -0
  15. package/plugins/workflow-runner/src/toolRuntime.ts +64 -0
  16. package/plugins/workflow-runner/src/types.ts +26 -7
  17. package/plugins/workflow-runner/test/context-files.test.ts +47 -16
  18. package/plugins/workflow-runner/test/hostCapability.test.ts +9 -3
  19. package/plugins/workflow-runner/test/multimodal.test.ts +61 -10
  20. package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +168 -0
  21. package/src/cli/print.js +19 -4
  22. package/src/cli/print.ts +17 -4
  23. package/src/cli/streamJson.js +19 -4
  24. package/src/cli/streamJson.ts +17 -4
  25. package/src/config/configDefaults.js +25 -0
  26. package/src/config/configDefaults.ts +33 -0
  27. package/src/config/configFile.js +2 -21
  28. package/src/config/configFile.ts +11 -24
  29. package/src/config/configManagement.js +1 -1
  30. package/src/config/configManagement.ts +2 -2
  31. package/src/context/compact.js +4 -0
  32. package/src/context/compact.ts +4 -0
  33. package/src/context/contextBundle.js +38 -13
  34. package/src/context/contextBundle.ts +47 -13
  35. package/src/harness/stallGuard.js +19 -4
  36. package/src/harness/stallGuard.ts +30 -7
  37. package/src/loop.js +95 -11
  38. package/src/loop.ts +125 -16
  39. package/src/main.js +8 -1
  40. package/src/main.tsx +7 -1
  41. package/src/memory/constants.js +10 -0
  42. package/src/memory/constants.ts +13 -0
  43. package/src/memory/episodicMemory.js +274 -2
  44. package/src/memory/episodicMemory.ts +306 -1
  45. package/src/memory/fileStore.js +24 -4
  46. package/src/memory/fileStore.ts +22 -2
  47. package/src/memory/memoryService.js +79 -12
  48. package/src/memory/memoryService.ts +108 -9
  49. package/src/memory/repository.js +415 -85
  50. package/src/memory/repository.ts +437 -86
  51. package/src/memory/turnJournal.js +95 -0
  52. package/src/memory/turnJournal.ts +120 -0
  53. package/src/memory/types.ts +16 -0
  54. package/src/plugins/pluginApi.ts +30 -6
  55. package/src/plugins/pluginRunner.js +6 -2
  56. package/src/plugins/pluginRunner.ts +12 -7
  57. package/src/prompts/policyRegistry.js +27 -0
  58. package/src/prompts/policyRegistry.ts +27 -0
  59. package/src/prompts/promptAssembly.js +39 -7
  60. package/src/prompts/promptAssembly.ts +145 -59
  61. package/src/session/runAgentTurn.js +43 -21
  62. package/src/session/runAgentTurn.ts +68 -26
  63. package/src/session/runTurn.js +11 -46
  64. package/src/session/runTurn.ts +12 -46
  65. package/src/session/runtimeStore.js +149 -0
  66. package/src/session/runtimeStore.ts +197 -0
  67. package/src/session/sessionState.js +5 -0
  68. package/src/session/sessionState.ts +14 -0
  69. package/src/tools/agent/agent.js +143 -14
  70. package/src/tools/agent/agent.ts +149 -14
  71. package/src/tools/agent/agentTaskService.js +60 -0
  72. package/src/tools/agent/agentTaskService.ts +113 -0
  73. package/src/tools/agent/agentTypes.js +5 -0
  74. package/src/tools/agent/agentTypes.ts +5 -0
  75. package/src/tools/bash/bash.js +31 -11
  76. package/src/tools/bash/bash.ts +29 -10
  77. package/src/tools/capabilities.js +5 -0
  78. package/src/tools/capabilities.ts +11 -1
  79. package/src/tools/edit/edit.js +7 -4
  80. package/src/tools/edit/edit.ts +7 -4
  81. package/src/tools/edit/multi-edit.js +4 -2
  82. package/src/tools/edit/multi-edit.ts +4 -2
  83. package/src/tools/glob/glob.js +156 -40
  84. package/src/tools/glob/glob.ts +185 -46
  85. package/src/tools/index.js +35 -17
  86. package/src/tools/index.ts +81 -20
  87. package/src/tools/lsp/lsp.js +165 -22
  88. package/src/tools/lsp/lsp.ts +206 -36
  89. package/src/tools/powershell/powershell.js +116 -26
  90. package/src/tools/powershell/powershell.ts +155 -30
  91. package/src/tools/presentresult/presentresult.js +10 -0
  92. package/src/tools/presentresult/presentresult.ts +10 -0
  93. package/src/tools/read/read.js +244 -29
  94. package/src/tools/read/read.ts +391 -59
  95. package/src/tools/shared/diffSummary.js +44 -0
  96. package/src/tools/shared/diffSummary.ts +63 -0
  97. package/src/tools/shared/fileActivity.js +2 -1
  98. package/src/tools/shared/fileActivity.ts +3 -0
  99. package/src/tools/shared/permanentDeleteGuard.js +26 -0
  100. package/src/tools/shared/permanentDeleteGuard.ts +34 -0
  101. package/src/tools/shared/toolOutputStore.js +97 -0
  102. package/src/tools/shared/toolOutputStore.ts +110 -0
  103. package/src/tools/skill/skill.js +86 -0
  104. package/src/tools/skill/skill.ts +94 -0
  105. package/src/tools/snip/snip.js +67 -0
  106. package/src/tools/snip/snip.ts +80 -0
  107. package/src/tools/tasks/backgroundTaskService.js +79 -28
  108. package/src/tools/tasks/backgroundTaskService.ts +165 -49
  109. package/src/tools/tasks/taskManagement.js +163 -0
  110. package/src/tools/tasks/taskManagement.ts +180 -0
  111. package/src/tools/tasks/taskOutput.js +64 -14
  112. package/src/tools/tasks/taskOutput.ts +102 -23
  113. package/src/tools/tasks/taskStop.js +48 -8
  114. package/src/tools/tasks/taskStop.ts +69 -9
  115. package/src/tools/tasks/taskStore.js +46 -0
  116. package/src/tools/tasks/taskStore.ts +77 -0
  117. package/src/tools/terminal/terminalCapture.js +167 -0
  118. package/src/tools/terminal/terminalCapture.ts +179 -0
  119. package/src/tools/trash/trash.js +233 -0
  120. package/src/tools/trash/trash.ts +283 -0
  121. package/src/tools/worktree/sessionWorktree.js +128 -0
  122. package/src/tools/worktree/sessionWorktree.ts +158 -0
  123. package/src/tools/write/write.js +6 -2
  124. package/src/tools/write/write.ts +6 -2
  125. package/src/types.ts +12 -0
  126. package/src/ui/hooks/useChat.js +31 -11
  127. package/src/ui/hooks/useChat.ts +31 -13
  128. package/src/web/protocol.ts +78 -3
@@ -19,6 +19,8 @@ import { advisorTool } from './advisor/advisor.js';
19
19
  import { agentTool } from './agent/agent.js';
20
20
  import { askUserQuestionTool } from './ask/ask.js';
21
21
  import { bashTool } from './bash/bash.js';
22
+ import { assertCapabilityRegistry } from './capabilities.js';
23
+ import { computerUseTool } from './computeruse/computeruse.js';
22
24
  import { downloadTool } from './download/download.js';
23
25
  import { editTool } from './edit/edit.js';
24
26
  import { multiEditTool } from './edit/multi-edit.js';
@@ -26,20 +28,24 @@ import { genImageTool } from './genimage/genimage.js';
26
28
  import { generateSpeechTool } from './genspeech/genspeech.js';
27
29
  import { globTool } from './glob/glob.js';
28
30
  import { grepTool } from './grep/grep.js';
29
- import { readTool } from './read/read.js';
30
- import { presentResultTool } from './presentresult/presentresult.js';
31
- import { clearPersonaTool, confirmMemoryTool, forgetMemoryTool, listMemoriesTool, recallMemoryTool, rememberMemoryTool, reviewMemoriesTool, searchConversationsTool, setPersonaTool, updateMemoryTool, } from './memory/memory.js';
32
- import { webfetchTool } from './webfetch/webfetch.js';
33
- import { webbrowserTool } from './webbrowser/webbrowser.js';
34
- import { webSearchTool } from './websearch/websearch.js';
35
- import { writeTool } from './write/write.js';
36
- import { computerUseTool } from './computeruse/computeruse.js';
37
- import { assertCapabilityRegistry } from './capabilities.js';
38
31
  import { lspTool } from './lsp/lsp.js';
32
+ import { clearPersonaTool, confirmMemoryTool, forgetMemoryTool, listMemoriesTool, recallMemoryTool, rememberMemoryTool, reviewMemoriesTool, searchConversationsTool, setPersonaTool, updateMemoryTool, } from './memory/memory.js';
39
33
  import { notebookEditTool } from './notebook/notebookEdit.js';
40
34
  import { powerShellTool } from './powershell/powershell.js';
35
+ import { presentResultTool } from './presentresult/presentresult.js';
36
+ import { readTool } from './read/read.js';
37
+ import { skillTool } from './skill/skill.js';
38
+ import { snipTool } from './snip/snip.js';
39
+ import { taskCreateTool, taskGetTool, taskListTool, taskUpdateTool, } from './tasks/taskManagement.js';
41
40
  import { taskOutputTool } from './tasks/taskOutput.js';
42
41
  import { taskStopTool } from './tasks/taskStop.js';
42
+ import { terminalCaptureTool } from './terminal/terminalCapture.js';
43
+ import { trashTool } from './trash/trash.js';
44
+ import { webbrowserTool } from './webbrowser/webbrowser.js';
45
+ import { webfetchTool } from './webfetch/webfetch.js';
46
+ import { webSearchTool } from './websearch/websearch.js';
47
+ import { enterWorktreeTool, exitWorktreeTool, } from './worktree/sessionWorktree.js';
48
+ import { writeTool } from './write/write.js';
43
49
  /** 所有内置工具(顺序 = 给 LLM 呈现的顺序,不影响代码执行,但会影响模型的优先级直觉) */
44
50
  export const ALL_TOOLS = [
45
51
  readTool,
@@ -76,6 +82,17 @@ export const ALL_TOOLS = [
76
82
  computerUseTool,
77
83
  // 追加在末尾:主循环 tools schema 的原有前缀字节不动(提示词缓存友好)
78
84
  agentTool,
85
+ // Product tools append at the end to preserve the existing schema prefix cache.
86
+ trashTool,
87
+ taskCreateTool,
88
+ taskGetTool,
89
+ taskUpdateTool,
90
+ taskListTool,
91
+ skillTool,
92
+ enterWorktreeTool,
93
+ exitWorktreeTool,
94
+ terminalCaptureTool,
95
+ snipTool,
79
96
  ];
80
97
  assertCapabilityRegistry(ALL_TOOLS.map((tool) => tool.name));
81
98
  /** 按名字查表 */
@@ -157,12 +174,7 @@ export async function executeTool(name, rawArguments, signal, ctx) {
157
174
  .join('\n');
158
175
  return {
159
176
  ok: false,
160
- error: `工具 ${name} 参数校验失败,**请按下面逐条改正后重新调用**:\n${issues}\n\n`
161
- + '通用修法:\n'
162
- + ' · **只填你这次确实要用的字段**。可选字段不要用 ""、0、{} 之类的默认值占位 ——\n'
163
- + ' 留空不填即可;填了占位值反而会被当成"你要用它"而触发校验。\n'
164
- + ' · 枚举字段只能取列出的值;数值字段注意上下限。\n'
165
- + ' · 改完直接重发这次调用,不要因为这个错误就换一条完全不同的路子。',
177
+ error: `工具 ${name} 参数校验失败,**请按下面逐条改正后重新调用**:\n${issues}\n\n通用修法:\n · **只填你这次确实要用的字段**。可选字段不要用 ""、0、{} 之类的默认值占位 ——\n 留空不填即可;填了占位值反而会被当成"你要用它"而触发校验。\n · 枚举字段只能取列出的值;数值字段注意上下限。\n · 改完直接重发这次调用,不要因为这个错误就换一条完全不同的路子。`,
166
178
  };
167
179
  }
168
180
  // 4. 调用 call(),捕获意外异常
@@ -180,16 +192,22 @@ export async function executeTool(name, rawArguments, signal, ctx) {
180
192
  }
181
193
  }
182
194
  // 导出每个工具,方便测试单独 import
183
- export { readTool, editTool, writeTool, globTool, grepTool, webSearchTool, webfetchTool, webbrowserTool, computerUseTool, downloadTool, presentResultTool, bashTool, askUserQuestionTool };
195
+ export { readTool, editTool, writeTool, globTool, grepTool, webSearchTool, webfetchTool, webbrowserTool, computerUseTool, downloadTool, presentResultTool, bashTool, askUserQuestionTool, };
184
196
  export { clearPersonaTool, confirmMemoryTool, forgetMemoryTool, listMemoriesTool, recallMemoryTool, rememberMemoryTool, reviewMemoriesTool, searchConversationsTool, setPersonaTool, updateMemoryTool, } from './memory/memory.js';
185
197
  export { multiEditTool } from './edit/multi-edit.js';
186
198
  export { genImageTool } from './genimage/genimage.js';
187
199
  export { advisorTool } from './advisor/advisor.js';
188
200
  export { agentTool } from './agent/agent.js';
189
- export { createGenerateSpeechTool, generateSpeechTool } from './genspeech/genspeech.js';
201
+ export { createGenerateSpeechTool, generateSpeechTool, } from './genspeech/genspeech.js';
190
202
  export { createWebSearchTool } from './websearch/websearch.js';
191
203
  export { lspTool } from './lsp/lsp.js';
192
204
  export { notebookEditTool } from './notebook/notebookEdit.js';
193
205
  export { powerShellTool } from './powershell/powershell.js';
194
206
  export { taskOutputTool } from './tasks/taskOutput.js';
195
207
  export { taskStopTool } from './tasks/taskStop.js';
208
+ export { taskCreateTool, taskGetTool, taskUpdateTool, taskListTool, } from './tasks/taskManagement.js';
209
+ export { trashTool } from './trash/trash.js';
210
+ export { skillTool } from './skill/skill.js';
211
+ export { snipTool } from './snip/snip.js';
212
+ export { enterWorktreeTool, exitWorktreeTool, } from './worktree/sessionWorktree.js';
213
+ export { terminalCaptureTool, installTerminalCapture, } from './terminal/terminalCapture.js';
@@ -21,6 +21,8 @@ import { advisorTool } from './advisor/advisor.js';
21
21
  import { agentTool } from './agent/agent.js';
22
22
  import { askUserQuestionTool } from './ask/ask.js';
23
23
  import { bashTool } from './bash/bash.js';
24
+ import { assertCapabilityRegistry } from './capabilities.js';
25
+ import { computerUseTool } from './computeruse/computeruse.js';
24
26
  import { downloadTool } from './download/download.js';
25
27
  import { editTool } from './edit/edit.js';
26
28
  import { multiEditTool } from './edit/multi-edit.js';
@@ -28,8 +30,7 @@ import { genImageTool } from './genimage/genimage.js';
28
30
  import { generateSpeechTool } from './genspeech/genspeech.js';
29
31
  import { globTool } from './glob/glob.js';
30
32
  import { grepTool } from './grep/grep.js';
31
- import { readTool } from './read/read.js';
32
- import { presentResultTool } from './presentresult/presentresult.js';
33
+ import { lspTool } from './lsp/lsp.js';
33
34
  import {
34
35
  clearPersonaTool,
35
36
  confirmMemoryTool,
@@ -42,17 +43,30 @@ import {
42
43
  setPersonaTool,
43
44
  updateMemoryTool,
44
45
  } from './memory/memory.js';
45
- import { webfetchTool } from './webfetch/webfetch.js';
46
- import { webbrowserTool } from './webbrowser/webbrowser.js';
47
- import { webSearchTool } from './websearch/websearch.js';
48
- import { writeTool } from './write/write.js';
49
- import { computerUseTool } from './computeruse/computeruse.js';
50
- import { assertCapabilityRegistry } from './capabilities.js';
51
- import { lspTool } from './lsp/lsp.js';
52
46
  import { notebookEditTool } from './notebook/notebookEdit.js';
53
47
  import { powerShellTool } from './powershell/powershell.js';
48
+ import { presentResultTool } from './presentresult/presentresult.js';
49
+ import { readTool } from './read/read.js';
50
+ import { skillTool } from './skill/skill.js';
51
+ import { snipTool } from './snip/snip.js';
52
+ import {
53
+ taskCreateTool,
54
+ taskGetTool,
55
+ taskListTool,
56
+ taskUpdateTool,
57
+ } from './tasks/taskManagement.js';
54
58
  import { taskOutputTool } from './tasks/taskOutput.js';
55
59
  import { taskStopTool } from './tasks/taskStop.js';
60
+ import { terminalCaptureTool } from './terminal/terminalCapture.js';
61
+ import { trashTool } from './trash/trash.js';
62
+ import { webbrowserTool } from './webbrowser/webbrowser.js';
63
+ import { webfetchTool } from './webfetch/webfetch.js';
64
+ import { webSearchTool } from './websearch/websearch.js';
65
+ import {
66
+ enterWorktreeTool,
67
+ exitWorktreeTool,
68
+ } from './worktree/sessionWorktree.js';
69
+ import { writeTool } from './write/write.js';
56
70
 
57
71
  /** 所有内置工具(顺序 = 给 LLM 呈现的顺序,不影响代码执行,但会影响模型的优先级直觉) */
58
72
  export const ALL_TOOLS: Tool[] = [
@@ -90,6 +104,17 @@ export const ALL_TOOLS: Tool[] = [
90
104
  computerUseTool,
91
105
  // 追加在末尾:主循环 tools schema 的原有前缀字节不动(提示词缓存友好)
92
106
  agentTool,
107
+ // Product tools append at the end to preserve the existing schema prefix cache.
108
+ trashTool,
109
+ taskCreateTool,
110
+ taskGetTool,
111
+ taskUpdateTool,
112
+ taskListTool,
113
+ skillTool,
114
+ enterWorktreeTool,
115
+ exitWorktreeTool,
116
+ terminalCaptureTool,
117
+ snipTool,
93
118
  ];
94
119
 
95
120
  assertCapabilityRegistry(ALL_TOOLS.map((tool) => tool.name));
@@ -103,7 +128,14 @@ export function getToolGateDetails(
103
128
  name: string,
104
129
  rawArguments: string,
105
130
  context: ToolCallContext,
106
- ): Partial<Pick<import('../toolGate.js').ToolGateRequest, 'isReadOnly' | 'runContext' | 'action'>> | undefined {
131
+ ):
132
+ | Partial<
133
+ Pick<
134
+ import('../toolGate.js').ToolGateRequest,
135
+ 'isReadOnly' | 'runContext' | 'action'
136
+ >
137
+ >
138
+ | undefined {
107
139
  const tool = getToolByName(name);
108
140
  if (!tool?.gateRequest) return undefined;
109
141
  let parsed: unknown;
@@ -182,20 +214,15 @@ export async function executeTool(
182
214
  .join('\n');
183
215
  return {
184
216
  ok: false,
185
- error:
186
- `工具 ${name} 参数校验失败,**请按下面逐条改正后重新调用**:\n${issues}\n\n`
187
- + '通用修法:\n'
188
- + ' · **只填你这次确实要用的字段**。可选字段不要用 ""、0、{} 之类的默认值占位 ——\n'
189
- + ' 留空不填即可;填了占位值反而会被当成"你要用它"而触发校验。\n'
190
- + ' · 枚举字段只能取列出的值;数值字段注意上下限。\n'
191
- + ' · 改完直接重发这次调用,不要因为这个错误就换一条完全不同的路子。',
217
+ error: `工具 ${name} 参数校验失败,**请按下面逐条改正后重新调用**:\n${issues}\n\n通用修法:\n · **只填你这次确实要用的字段**。可选字段不要用 ""、0、{} 之类的默认值占位 ——\n 留空不填即可;填了占位值反而会被当成"你要用它"而触发校验。\n · 枚举字段只能取列出的值;数值字段注意上下限。\n · 改完直接重发这次调用,不要因为这个错误就换一条完全不同的路子。`,
192
218
  };
193
219
  }
194
220
 
195
221
  // 4. 调用 call(),捕获意外异常
196
222
  //
197
223
  try {
198
- if (tool.callWithContext) return await tool.callWithContext(checked.data, signal, ctx ?? {});
224
+ if (tool.callWithContext)
225
+ return await tool.callWithContext(checked.data, signal, ctx ?? {});
199
226
  return await tool.call(checked.data, signal);
200
227
  } catch (e) {
201
228
  return {
@@ -206,7 +233,21 @@ export async function executeTool(
206
233
  }
207
234
 
208
235
  // 导出每个工具,方便测试单独 import
209
- export { readTool, editTool, writeTool, globTool, grepTool, webSearchTool, webfetchTool, webbrowserTool, computerUseTool, downloadTool, presentResultTool, bashTool, askUserQuestionTool };
236
+ export {
237
+ readTool,
238
+ editTool,
239
+ writeTool,
240
+ globTool,
241
+ grepTool,
242
+ webSearchTool,
243
+ webfetchTool,
244
+ webbrowserTool,
245
+ computerUseTool,
246
+ downloadTool,
247
+ presentResultTool,
248
+ bashTool,
249
+ askUserQuestionTool,
250
+ };
210
251
  export {
211
252
  clearPersonaTool,
212
253
  confirmMemoryTool,
@@ -223,10 +264,30 @@ export { multiEditTool } from './edit/multi-edit.js';
223
264
  export { genImageTool } from './genimage/genimage.js';
224
265
  export { advisorTool } from './advisor/advisor.js';
225
266
  export { agentTool } from './agent/agent.js';
226
- export { createGenerateSpeechTool, generateSpeechTool } from './genspeech/genspeech.js';
267
+ export {
268
+ createGenerateSpeechTool,
269
+ generateSpeechTool,
270
+ } from './genspeech/genspeech.js';
227
271
  export { createWebSearchTool } from './websearch/websearch.js';
228
272
  export { lspTool } from './lsp/lsp.js';
229
273
  export { notebookEditTool } from './notebook/notebookEdit.js';
230
274
  export { powerShellTool } from './powershell/powershell.js';
231
275
  export { taskOutputTool } from './tasks/taskOutput.js';
232
276
  export { taskStopTool } from './tasks/taskStop.js';
277
+ export {
278
+ taskCreateTool,
279
+ taskGetTool,
280
+ taskUpdateTool,
281
+ taskListTool,
282
+ } from './tasks/taskManagement.js';
283
+ export { trashTool } from './trash/trash.js';
284
+ export { skillTool } from './skill/skill.js';
285
+ export { snipTool } from './snip/snip.js';
286
+ export {
287
+ enterWorktreeTool,
288
+ exitWorktreeTool,
289
+ } from './worktree/sessionWorktree.js';
290
+ export {
291
+ terminalCaptureTool,
292
+ installTerminalCapture,
293
+ } from './terminal/terminalCapture.js';
@@ -1,29 +1,71 @@
1
1
  import { stat } from 'node:fs/promises';
2
2
  import { isAbsolute, relative, resolve, sep } from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
+ import { fileURLToPath } from 'node:url';
4
5
  import { z } from 'zod';
5
6
  import { getWorkingDir } from '../../bootstrap/workingDir.js';
6
7
  import { toToolParameters } from '../../utils/zodToJson.js';
7
8
  import { DEFAULT_MAX_RESULT_SIZE_CHARS } from '../types.js';
8
9
  import { discoverLspServers, lspAvailabilitySignature } from './config.js';
9
10
  import { getLspManager } from './service.js';
10
- const positionOperations = new Set(['goToDefinition', 'findReferences', 'hover', 'goToImplementation', 'incomingCalls', 'outgoingCalls']);
11
- const inputSchema = z.object({
11
+ const positionOperations = new Set([
12
+ 'goToDefinition',
13
+ 'findReferences',
14
+ 'hover',
15
+ 'goToImplementation',
16
+ 'incomingCalls',
17
+ 'outgoingCalls',
18
+ ]);
19
+ const inputSchema = z
20
+ .object({
12
21
  operation: z.enum([
13
- 'goToDefinition', 'findReferences', 'hover', 'documentSymbols', 'workspaceSymbols',
14
- 'goToImplementation', 'incomingCalls', 'outgoingCalls', 'diagnostics',
22
+ 'goToDefinition',
23
+ 'findReferences',
24
+ 'hover',
25
+ 'documentSymbols',
26
+ 'workspaceSymbols',
27
+ 'goToImplementation',
28
+ 'incomingCalls',
29
+ 'outgoingCalls',
30
+ 'diagnostics',
15
31
  ]),
16
32
  file_path: z.string().min(1),
17
- line: z.number().int().positive().optional().describe('1-based source line for position operations'),
18
- character: z.number().int().positive().optional().describe('1-based UTF-16 character for position operations'),
33
+ line: z
34
+ .number()
35
+ .int()
36
+ .positive()
37
+ .optional()
38
+ .describe('1-based source line for position operations'),
39
+ character: z
40
+ .number()
41
+ .int()
42
+ .positive()
43
+ .optional()
44
+ .describe('1-based UTF-16 character for position operations'),
19
45
  query: z.string().optional().describe('Symbol query for workspaceSymbols'),
20
- limit: z.number().int().min(1).max(500).optional().describe('Maximum formatted results, default 200'),
21
- }).superRefine((input, context) => {
22
- if (positionOperations.has(input.operation) && (!input.line || !input.character)) {
23
- context.addIssue({ code: z.ZodIssueCode.custom, message: `${input.operation} requires line and character` });
46
+ limit: z
47
+ .number()
48
+ .int()
49
+ .min(1)
50
+ .max(500)
51
+ .optional()
52
+ .describe('Maximum formatted results, default 200'),
53
+ output_format: z.enum(['compact', 'json']).default('compact').optional(),
54
+ })
55
+ .superRefine((input, context) => {
56
+ if (positionOperations.has(input.operation) &&
57
+ (!input.line || !input.character)) {
58
+ context.addIssue({
59
+ code: z.ZodIssueCode.custom,
60
+ message: `${input.operation} requires line and character`,
61
+ });
24
62
  }
25
63
  if (input.operation === 'workspaceSymbols' && !input.query?.trim()) {
26
- context.addIssue({ code: z.ZodIssueCode.custom, path: ['query'], message: 'workspaceSymbols requires a non-empty query' });
64
+ context.addIssue({
65
+ code: z.ZodIssueCode.custom,
66
+ path: ['query'],
67
+ message: 'workspaceSymbols requires a non-empty query',
68
+ });
27
69
  }
28
70
  });
29
71
  function compact(value, limit) {
@@ -35,6 +77,74 @@ function compact(value, limit) {
35
77
  ? `${text.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS)}\n... (LSP result truncated)`
36
78
  : text;
37
79
  }
80
+ function compactSemantic(value, cwd, limit) {
81
+ if (value == null)
82
+ return 'No results.';
83
+ const rows = [];
84
+ const walk = (item, label) => {
85
+ if (item == null)
86
+ return;
87
+ if (Array.isArray(item)) {
88
+ item.forEach((v) => walk(v, label));
89
+ return;
90
+ }
91
+ if (typeof item !== 'object') {
92
+ rows.push(String(item));
93
+ return;
94
+ }
95
+ const location = item.location ?? item.fromRanges?.[0] ?? item;
96
+ const uri = location.uri ?? location.targetUri ?? item.uri;
97
+ const range = location.range ??
98
+ location.targetSelectionRange ??
99
+ item.range ??
100
+ item.selectionRange;
101
+ let where = '';
102
+ if (typeof uri === 'string') {
103
+ let path = uri;
104
+ try {
105
+ path = uri.startsWith('file:') ? fileURLToPath(uri) : uri;
106
+ }
107
+ catch { }
108
+ const rel = relative(resolve(cwd), path);
109
+ where = (rel && !rel.startsWith('..') ? rel : path).split(sep).join('/');
110
+ }
111
+ if (range?.start)
112
+ where += `:${Number(range.start.line) + 1}:${Number(range.start.character) + 1}`;
113
+ const message = item.message ?? item.name ?? item.detail ?? item.kind ?? label;
114
+ const severity = item.severity
115
+ ? `[${['', 'error', 'warning', 'info', 'hint'][item.severity] ?? item.severity}] `
116
+ : '';
117
+ const code = item.code !== undefined ? `(${String(item.code)}) ` : '';
118
+ if (where || message)
119
+ rows.push(`${severity}${where || '<symbol>'} — ${code}${typeof message === 'string' ? message : JSON.stringify(message)}`);
120
+ if (item.contents) {
121
+ const contents = Array.isArray(item.contents)
122
+ ? item.contents
123
+ : [item.contents];
124
+ for (const c of contents)
125
+ rows.push(typeof c === 'string'
126
+ ? c
127
+ : typeof c?.value === 'string'
128
+ ? c.value
129
+ : JSON.stringify(c));
130
+ }
131
+ if (item.from)
132
+ walk(item.from, 'caller');
133
+ if (item.to)
134
+ walk(item.to, 'callee');
135
+ if (Array.isArray(item.children))
136
+ item.children.forEach((v) => walk(v));
137
+ };
138
+ walk(value);
139
+ const unique = [...new Set(rows)].sort();
140
+ const selected = unique.slice(0, limit);
141
+ let text = selected.join('\n') || 'No results.';
142
+ if (unique.length > selected.length)
143
+ text += `\n... (${unique.length - selected.length} more; total=${unique.length})`;
144
+ return text.length > DEFAULT_MAX_RESULT_SIZE_CHARS
145
+ ? `${text.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS)}\n... (LSP result truncated)`
146
+ : text;
147
+ }
38
148
  async function call(input, cwd = getWorkingDir()) {
39
149
  const filePath = resolve(cwd, input.file_path);
40
150
  const rel = relative(resolve(cwd), filePath);
@@ -52,32 +162,54 @@ async function call(input, cwd = getWorkingDir()) {
52
162
  try {
53
163
  await server.openFile(filePath);
54
164
  const uri = pathToFileURL(filePath).href;
55
- const position = { line: (input.line ?? 1) - 1, character: (input.character ?? 1) - 1 };
165
+ const position = {
166
+ line: (input.line ?? 1) - 1,
167
+ character: (input.character ?? 1) - 1,
168
+ };
56
169
  let result;
57
170
  switch (input.operation) {
58
171
  case 'goToDefinition':
59
- result = await server.request('textDocument/definition', { textDocument: { uri }, position });
172
+ result = await server.request('textDocument/definition', {
173
+ textDocument: { uri },
174
+ position,
175
+ });
60
176
  break;
61
177
  case 'findReferences':
62
- result = await server.request('textDocument/references', { textDocument: { uri }, position, context: { includeDeclaration: true } });
178
+ result = await server.request('textDocument/references', {
179
+ textDocument: { uri },
180
+ position,
181
+ context: { includeDeclaration: true },
182
+ });
63
183
  break;
64
184
  case 'hover':
65
- result = await server.request('textDocument/hover', { textDocument: { uri }, position });
185
+ result = await server.request('textDocument/hover', {
186
+ textDocument: { uri },
187
+ position,
188
+ });
66
189
  break;
67
190
  case 'documentSymbols':
68
- result = await server.request('textDocument/documentSymbol', { textDocument: { uri } });
191
+ result = await server.request('textDocument/documentSymbol', {
192
+ textDocument: { uri },
193
+ });
69
194
  break;
70
195
  case 'workspaceSymbols':
71
- result = await server.request('workspace/symbol', { query: input.query });
196
+ result = await server.request('workspace/symbol', {
197
+ query: input.query,
198
+ });
72
199
  break;
73
200
  case 'goToImplementation':
74
- result = await server.request('textDocument/implementation', { textDocument: { uri }, position });
201
+ result = await server.request('textDocument/implementation', {
202
+ textDocument: { uri },
203
+ position,
204
+ });
75
205
  break;
76
206
  case 'incomingCalls':
77
207
  case 'outgoingCalls': {
78
208
  const prepared = await server.request('textDocument/prepareCallHierarchy', { textDocument: { uri }, position });
79
209
  const item = prepared?.[0];
80
- result = item ? await server.request(`callHierarchy/${input.operation}`, { item }) : [];
210
+ result = item
211
+ ? await server.request(`callHierarchy/${input.operation}`, { item })
212
+ : [];
81
213
  break;
82
214
  }
83
215
  case 'diagnostics': {
@@ -87,10 +219,18 @@ async function call(input, cwd = getWorkingDir()) {
87
219
  break;
88
220
  }
89
221
  }
90
- return { ok: true, content: compact(result, input.limit ?? 200) };
222
+ return {
223
+ ok: true,
224
+ content: input.output_format === 'json'
225
+ ? compact(result, input.limit ?? 200)
226
+ : compactSemantic(result, cwd, input.limit ?? 200),
227
+ };
91
228
  }
92
229
  catch (error) {
93
- return { ok: false, error: `LSP ${input.operation} failed: ${error.message}` };
230
+ return {
231
+ ok: false,
232
+ error: `LSP ${input.operation} failed: ${error.message}`,
233
+ };
94
234
  }
95
235
  }
96
236
  export const lspTool = {
@@ -106,7 +246,10 @@ export const lspTool = {
106
246
  maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
107
247
  resolveAvailability(runtime) {
108
248
  const servers = discoverLspServers(runtime.cwd);
109
- return { available: servers.length > 0, signature: lspAvailabilitySignature(runtime.cwd, servers) };
249
+ return {
250
+ available: servers.length > 0,
251
+ signature: lspAvailabilitySignature(runtime.cwd, servers),
252
+ };
110
253
  },
111
254
  call: (input) => call(input),
112
255
  callWithContext: (input, _signal, context) => call(input, context.cwd),