@xagent-ai/cli 1.2.2 → 1.3.1

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 (602) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +38 -38
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
  3. package/.github/release.yml +76 -0
  4. package/.github/workflows/ci.yml +75 -0
  5. package/.github/workflows/release.yml +103 -0
  6. package/.gitmodules +3 -3
  7. package/README.md +326 -280
  8. package/README_CN.md +325 -279
  9. package/dist/agents.d.ts.map +1 -1
  10. package/dist/agents.js +7 -3
  11. package/dist/agents.js.map +1 -1
  12. package/dist/ai-client/factory.d.ts +40 -0
  13. package/dist/ai-client/factory.d.ts.map +1 -0
  14. package/dist/ai-client/factory.js +100 -0
  15. package/dist/ai-client/factory.js.map +1 -0
  16. package/dist/ai-client/index.d.ts +20 -0
  17. package/dist/ai-client/index.d.ts.map +1 -0
  18. package/dist/ai-client/index.js +49 -0
  19. package/dist/ai-client/index.js.map +1 -0
  20. package/dist/ai-client/providers/anthropic.d.ts +57 -0
  21. package/dist/ai-client/providers/anthropic.d.ts.map +1 -0
  22. package/dist/ai-client/providers/anthropic.js +406 -0
  23. package/dist/ai-client/providers/anthropic.js.map +1 -0
  24. package/dist/ai-client/providers/openai.d.ts +57 -0
  25. package/dist/ai-client/providers/openai.d.ts.map +1 -0
  26. package/dist/ai-client/providers/openai.js +290 -0
  27. package/dist/ai-client/providers/openai.js.map +1 -0
  28. package/dist/ai-client/providers/remote.d.ts +110 -0
  29. package/dist/ai-client/providers/remote.d.ts.map +1 -0
  30. package/dist/ai-client/providers/remote.js +352 -0
  31. package/dist/ai-client/providers/remote.js.map +1 -0
  32. package/dist/ai-client/registry.d.ts +51 -0
  33. package/dist/ai-client/registry.d.ts.map +1 -0
  34. package/dist/ai-client/registry.js +81 -0
  35. package/dist/ai-client/registry.js.map +1 -0
  36. package/dist/ai-client/types.d.ts +274 -0
  37. package/dist/ai-client/types.d.ts.map +1 -0
  38. package/dist/ai-client/types.js +90 -0
  39. package/dist/ai-client/types.js.map +1 -0
  40. package/dist/ai-client-factory.d.ts +62 -0
  41. package/dist/ai-client-factory.d.ts.map +1 -0
  42. package/dist/ai-client-factory.js +157 -0
  43. package/dist/ai-client-factory.js.map +1 -0
  44. package/dist/auth.d.ts +23 -1
  45. package/dist/auth.d.ts.map +1 -1
  46. package/dist/auth.js +164 -174
  47. package/dist/auth.js.map +1 -1
  48. package/dist/cancellation.d.ts +5 -4
  49. package/dist/cancellation.d.ts.map +1 -1
  50. package/dist/cancellation.js +53 -32
  51. package/dist/cancellation.js.map +1 -1
  52. package/dist/checkpoint.d.ts +2 -1
  53. package/dist/checkpoint.d.ts.map +1 -1
  54. package/dist/checkpoint.js +39 -6
  55. package/dist/checkpoint.js.map +1 -1
  56. package/dist/cli.js +742 -29
  57. package/dist/cli.js.map +1 -1
  58. package/dist/config.d.ts +10 -4
  59. package/dist/config.d.ts.map +1 -1
  60. package/dist/config.js +62 -25
  61. package/dist/config.js.map +1 -1
  62. package/dist/context-compressor.d.ts +82 -18
  63. package/dist/context-compressor.d.ts.map +1 -1
  64. package/dist/context-compressor.js +718 -154
  65. package/dist/context-compressor.js.map +1 -1
  66. package/dist/conversation.d.ts +1 -1
  67. package/dist/conversation.d.ts.map +1 -1
  68. package/dist/conversation.js +8 -7
  69. package/dist/conversation.js.map +1 -1
  70. package/dist/gui-subagent/action-parser/actionParser.d.ts.map +1 -1
  71. package/dist/gui-subagent/action-parser/actionParser.js +6 -4
  72. package/dist/gui-subagent/action-parser/actionParser.js.map +1 -1
  73. package/dist/gui-subagent/agent/gui-agent.d.ts +39 -2
  74. package/dist/gui-subagent/agent/gui-agent.d.ts.map +1 -1
  75. package/dist/gui-subagent/agent/gui-agent.js +189 -74
  76. package/dist/gui-subagent/agent/gui-agent.js.map +1 -1
  77. package/dist/gui-subagent/index.d.ts +23 -1
  78. package/dist/gui-subagent/index.d.ts.map +1 -1
  79. package/dist/gui-subagent/index.js +6 -0
  80. package/dist/gui-subagent/index.js.map +1 -1
  81. package/dist/gui-subagent/operator/base-operator.d.ts.map +1 -1
  82. package/dist/gui-subagent/operator/base-operator.js +0 -1
  83. package/dist/gui-subagent/operator/base-operator.js.map +1 -1
  84. package/dist/gui-subagent/operator/computer-operator.d.ts.map +1 -1
  85. package/dist/gui-subagent/operator/computer-operator.js +31 -8
  86. package/dist/gui-subagent/operator/computer-operator.js.map +1 -1
  87. package/dist/gui-subagent/types/actions.d.ts +1 -1
  88. package/dist/gui-subagent/types/actions.d.ts.map +1 -1
  89. package/dist/gui-subagent/types/actions.js +0 -1
  90. package/dist/gui-subagent/types/actions.js.map +1 -1
  91. package/dist/gui-subagent/types/operator.d.ts +1 -1
  92. package/dist/gui-subagent/types/operator.d.ts.map +1 -1
  93. package/dist/index.d.ts +1 -2
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/index.js +1 -2
  96. package/dist/index.js.map +1 -1
  97. package/dist/input-processor.d.ts.map +1 -1
  98. package/dist/input-processor.js +8 -5
  99. package/dist/input-processor.js.map +1 -1
  100. package/dist/logger.d.ts.map +1 -1
  101. package/dist/logger.js +1 -1
  102. package/dist/logger.js.map +1 -1
  103. package/dist/mcp.d.ts +7 -1
  104. package/dist/mcp.d.ts.map +1 -1
  105. package/dist/mcp.js +157 -49
  106. package/dist/mcp.js.map +1 -1
  107. package/dist/memory.d.ts.map +1 -1
  108. package/dist/memory.js +3 -3
  109. package/dist/memory.js.map +1 -1
  110. package/dist/output-util.d.ts +27 -0
  111. package/dist/output-util.d.ts.map +1 -0
  112. package/dist/output-util.js +74 -0
  113. package/dist/output-util.js.map +1 -0
  114. package/dist/retry.js +1 -1
  115. package/dist/retry.js.map +1 -1
  116. package/dist/ripgrep.d.ts +29 -0
  117. package/dist/ripgrep.d.ts.map +1 -0
  118. package/dist/ripgrep.js +294 -0
  119. package/dist/ripgrep.js.map +1 -0
  120. package/dist/sdk-output-adapter.d.ts +34 -1
  121. package/dist/sdk-output-adapter.d.ts.map +1 -1
  122. package/dist/sdk-output-adapter.js +67 -2
  123. package/dist/sdk-output-adapter.js.map +1 -1
  124. package/dist/sdk-session.d.ts.map +1 -1
  125. package/dist/sdk-session.js +2 -0
  126. package/dist/sdk-session.js.map +1 -1
  127. package/dist/session-manager.js +3 -3
  128. package/dist/session-manager.js.map +1 -1
  129. package/dist/session.d.ts +116 -6
  130. package/dist/session.d.ts.map +1 -1
  131. package/dist/session.js +1416 -448
  132. package/dist/session.js.map +1 -1
  133. package/dist/shell.d.ts +33 -0
  134. package/dist/shell.d.ts.map +1 -0
  135. package/dist/shell.js +126 -0
  136. package/dist/shell.js.map +1 -0
  137. package/dist/skill-installer.d.ts +38 -0
  138. package/dist/skill-installer.d.ts.map +1 -0
  139. package/dist/skill-installer.js +447 -0
  140. package/dist/skill-installer.js.map +1 -0
  141. package/dist/skill-invoker.d.ts +8 -2
  142. package/dist/skill-invoker.d.ts.map +1 -1
  143. package/dist/skill-invoker.js +36 -15
  144. package/dist/skill-invoker.js.map +1 -1
  145. package/dist/skill-loader.d.ts +8 -3
  146. package/dist/skill-loader.d.ts.map +1 -1
  147. package/dist/skill-loader.js +51 -48
  148. package/dist/skill-loader.js.map +1 -1
  149. package/dist/skill-manager.d.ts +85 -0
  150. package/dist/skill-manager.d.ts.map +1 -0
  151. package/dist/skill-manager.js +341 -0
  152. package/dist/skill-manager.js.map +1 -0
  153. package/dist/slash-commands.d.ts +39 -2
  154. package/dist/slash-commands.d.ts.map +1 -1
  155. package/dist/slash-commands.js +934 -305
  156. package/dist/slash-commands.js.map +1 -1
  157. package/dist/smart-approval.d.ts +20 -1
  158. package/dist/smart-approval.d.ts.map +1 -1
  159. package/dist/smart-approval.js +125 -56
  160. package/dist/smart-approval.js.map +1 -1
  161. package/dist/system-prompt-generator.d.ts +6 -0
  162. package/dist/system-prompt-generator.d.ts.map +1 -1
  163. package/dist/system-prompt-generator.js +86 -36
  164. package/dist/system-prompt-generator.js.map +1 -1
  165. package/dist/terminal.d.ts +28 -0
  166. package/dist/terminal.d.ts.map +1 -0
  167. package/dist/terminal.js +82 -0
  168. package/dist/terminal.js.map +1 -0
  169. package/dist/theme.d.ts.map +1 -1
  170. package/dist/theme.js +8 -7
  171. package/dist/theme.js.map +1 -1
  172. package/dist/tools.d.ts +38 -7
  173. package/dist/tools.d.ts.map +1 -1
  174. package/dist/tools.js +1249 -617
  175. package/dist/tools.js.map +1 -1
  176. package/dist/truncate.d.ts +55 -0
  177. package/dist/truncate.d.ts.map +1 -0
  178. package/dist/truncate.js +130 -0
  179. package/dist/truncate.js.map +1 -0
  180. package/dist/types.d.ts +84 -9
  181. package/dist/types.d.ts.map +1 -1
  182. package/dist/types.js +49 -0
  183. package/dist/types.js.map +1 -1
  184. package/dist/update.d.ts.map +1 -1
  185. package/dist/update.js +28 -36
  186. package/dist/update.js.map +1 -1
  187. package/dist/workflow.d.ts +5 -1
  188. package/dist/workflow.d.ts.map +1 -1
  189. package/dist/workflow.js +61 -49
  190. package/dist/workflow.js.map +1 -1
  191. package/docs/architecture/mcp-integration-guide.md +304 -194
  192. package/docs/architecture/overview.md +169 -169
  193. package/docs/architecture/tool-system-design.md +134 -134
  194. package/docs/cli/commands.md +349 -238
  195. package/docs/smart-mode.md +281 -281
  196. package/docs/third-party-models.md +440 -439
  197. package/find-skills/SKILL.md +133 -0
  198. package/package.json +91 -90
  199. package/scripts/install-ripgrep.js +241 -0
  200. package/src/agents.ts +7 -3
  201. package/src/ai-client/factory.ts +116 -0
  202. package/src/ai-client/index.ts +61 -0
  203. package/src/ai-client/providers/anthropic.ts +475 -0
  204. package/src/ai-client/providers/openai.ts +348 -0
  205. package/src/ai-client/providers/remote.ts +439 -0
  206. package/src/ai-client/registry.ts +97 -0
  207. package/src/ai-client/types.ts +364 -0
  208. package/src/ai-client-factory.ts +204 -0
  209. package/src/auth.ts +661 -614
  210. package/src/cancellation.ts +202 -176
  211. package/src/checkpoint.ts +255 -219
  212. package/src/cli.ts +1523 -743
  213. package/src/config.ts +341 -297
  214. package/src/context-compressor.ts +987 -290
  215. package/src/conversation.ts +290 -288
  216. package/src/gui-subagent/action-parser/actionParser.ts +318 -315
  217. package/src/gui-subagent/action-parser/constants.ts +14 -14
  218. package/src/gui-subagent/action-parser/index.ts +8 -8
  219. package/src/gui-subagent/action-parser/types.ts +31 -31
  220. package/src/gui-subagent/agent/gui-agent.ts +1234 -1089
  221. package/src/gui-subagent/agent/index.ts +5 -5
  222. package/src/gui-subagent/index.ts +185 -163
  223. package/src/gui-subagent/operator/base-operator.ts +244 -245
  224. package/src/gui-subagent/operator/computer-operator.ts +541 -520
  225. package/src/gui-subagent/operator/index.ts +6 -6
  226. package/src/gui-subagent/types/actions.ts +260 -262
  227. package/src/gui-subagent/types/index.ts +6 -6
  228. package/src/gui-subagent/types/operator.ts +106 -106
  229. package/src/gui-subagent/utils.ts +51 -51
  230. package/src/index.ts +17 -18
  231. package/src/input-processor.ts +8 -5
  232. package/src/logger.ts +436 -438
  233. package/src/mcp.ts +793 -682
  234. package/src/memory.ts +343 -344
  235. package/src/output-util.ts +80 -0
  236. package/src/retry.ts +1 -1
  237. package/src/ripgrep.ts +370 -0
  238. package/src/sdk-output-adapter.ts +842 -0
  239. package/src/sdk-session.ts +62 -0
  240. package/src/session-manager.ts +308 -308
  241. package/src/session.ts +1775 -573
  242. package/src/shell.ts +134 -0
  243. package/src/skill-installer.ts +518 -0
  244. package/src/skill-invoker.ts +959 -935
  245. package/src/skill-loader.ts +501 -496
  246. package/src/skill-manager.ts +385 -0
  247. package/src/slash-commands.ts +2189 -1389
  248. package/src/smart-approval.ts +193 -74
  249. package/src/system-prompt-generator.ts +91 -36
  250. package/src/terminal.ts +96 -0
  251. package/src/theme.ts +739 -738
  252. package/src/tools.ts +1790 -931
  253. package/src/truncate.ts +173 -0
  254. package/src/types.ts +337 -198
  255. package/src/update.ts +33 -40
  256. package/src/workflow.ts +521 -508
  257. package/test/cli-launch.test.ts +279 -0
  258. package/tsconfig.json +22 -22
  259. package/vitest.config.ts +21 -19
  260. package/dist/ai-client.d.ts +0 -86
  261. package/dist/ai-client.d.ts.map +0 -1
  262. package/dist/ai-client.js +0 -1372
  263. package/dist/ai-client.js.map +0 -1
  264. package/dist/gui-subagent/operator/browser-operator.d.ts +0 -36
  265. package/dist/gui-subagent/operator/browser-operator.d.ts.map +0 -1
  266. package/dist/gui-subagent/operator/browser-operator.js +0 -306
  267. package/dist/gui-subagent/operator/browser-operator.js.map +0 -1
  268. package/dist/gui-subagent/operator/desktop-operator.d.ts +0 -55
  269. package/dist/gui-subagent/operator/desktop-operator.d.ts.map +0 -1
  270. package/dist/gui-subagent/operator/desktop-operator.js +0 -527
  271. package/dist/gui-subagent/operator/desktop-operator.js.map +0 -1
  272. package/dist/hook.d.ts +0 -73
  273. package/dist/hook.d.ts.map +0 -1
  274. package/dist/hook.js +0 -156
  275. package/dist/hook.js.map +0 -1
  276. package/dist/input-history.d.ts +0 -24
  277. package/dist/input-history.d.ts.map +0 -1
  278. package/dist/input-history.js +0 -94
  279. package/dist/input-history.js.map +0 -1
  280. package/dist/keyboard-manager.d.ts +0 -151
  281. package/dist/keyboard-manager.d.ts.map +0 -1
  282. package/dist/keyboard-manager.js +0 -396
  283. package/dist/keyboard-manager.js.map +0 -1
  284. package/dist/print-system-prompt.d.ts +0 -2
  285. package/dist/print-system-prompt.d.ts.map +0 -1
  286. package/dist/print-system-prompt.js +0 -40
  287. package/dist/print-system-prompt.js.map +0 -1
  288. package/dist/remote-ai-client.d.ts +0 -104
  289. package/dist/remote-ai-client.d.ts.map +0 -1
  290. package/dist/remote-ai-client.js +0 -552
  291. package/dist/remote-ai-client.js.map +0 -1
  292. package/dist/sdk-session-v2.d.ts +0 -13
  293. package/dist/sdk-session-v2.d.ts.map +0 -1
  294. package/dist/sdk-session-v2.js +0 -46
  295. package/dist/sdk-session-v2.js.map +0 -1
  296. package/dist/test-boundary-conditions.d.ts.map +0 -1
  297. package/dist/test-boundary-conditions.js.map +0 -1
  298. package/dist/test-cancellation-fix.d.ts.map +0 -1
  299. package/dist/test-cancellation-fix.js.map +0 -1
  300. package/dist/test-input-history.d.ts.map +0 -1
  301. package/dist/test-input-history.js.map +0 -1
  302. package/dist/test-interaction-flow.d.ts.map +0 -1
  303. package/dist/test-interaction-flow.js.map +0 -1
  304. package/dist/test-quick.d.ts.map +0 -1
  305. package/dist/test-quick.js.map +0 -1
  306. package/dist/test-user-interaction.d.ts.map +0 -1
  307. package/dist/test-user-interaction.js.map +0 -1
  308. package/dist/tools/edit-diff.d.ts +0 -32
  309. package/dist/tools/edit-diff.d.ts.map +0 -1
  310. package/dist/tools/edit-diff.js +0 -185
  311. package/dist/tools/edit-diff.js.map +0 -1
  312. package/dist/tools/edit.d.ts +0 -11
  313. package/dist/tools/edit.d.ts.map +0 -1
  314. package/dist/tools/edit.js +0 -129
  315. package/dist/tools/edit.js.map +0 -1
  316. package/dist/unified-session.d.ts +0 -42
  317. package/dist/unified-session.d.ts.map +0 -1
  318. package/dist/unified-session.js +0 -271
  319. package/dist/unified-session.js.map +0 -1
  320. package/skills/.claude-plugin/marketplace.json +0 -45
  321. package/skills/README.md +0 -94
  322. package/skills/THIRD_PARTY_NOTICES.md +0 -405
  323. package/skills/skills/algorithmic-art/LICENSE.txt +0 -202
  324. package/skills/skills/algorithmic-art/SKILL.md +0 -405
  325. package/skills/skills/algorithmic-art/templates/generator_template.js +0 -223
  326. package/skills/skills/algorithmic-art/templates/viewer.html +0 -599
  327. package/skills/skills/brand-guidelines/LICENSE.txt +0 -202
  328. package/skills/skills/brand-guidelines/SKILL.md +0 -73
  329. package/skills/skills/canvas-design/LICENSE.txt +0 -202
  330. package/skills/skills/canvas-design/SKILL.md +0 -130
  331. package/skills/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +0 -93
  332. package/skills/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
  333. package/skills/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
  334. package/skills/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +0 -93
  335. package/skills/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
  336. package/skills/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +0 -93
  337. package/skills/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
  338. package/skills/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
  339. package/skills/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +0 -93
  340. package/skills/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
  341. package/skills/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
  342. package/skills/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
  343. package/skills/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +0 -93
  344. package/skills/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
  345. package/skills/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +0 -93
  346. package/skills/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
  347. package/skills/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +0 -94
  348. package/skills/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
  349. package/skills/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
  350. package/skills/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +0 -93
  351. package/skills/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
  352. package/skills/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +0 -93
  353. package/skills/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
  354. package/skills/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
  355. package/skills/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +0 -93
  356. package/skills/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
  357. package/skills/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
  358. package/skills/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
  359. package/skills/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
  360. package/skills/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
  361. package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
  362. package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
  363. package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
  364. package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +0 -93
  365. package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
  366. package/skills/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
  367. package/skills/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
  368. package/skills/skills/canvas-design/canvas-fonts/Italiana-OFL.txt +0 -93
  369. package/skills/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
  370. package/skills/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
  371. package/skills/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +0 -93
  372. package/skills/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
  373. package/skills/skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
  374. package/skills/skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
  375. package/skills/skills/canvas-design/canvas-fonts/Jura-OFL.txt +0 -93
  376. package/skills/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +0 -93
  377. package/skills/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
  378. package/skills/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
  379. package/skills/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
  380. package/skills/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
  381. package/skills/skills/canvas-design/canvas-fonts/Lora-OFL.txt +0 -93
  382. package/skills/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
  383. package/skills/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
  384. package/skills/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +0 -93
  385. package/skills/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
  386. package/skills/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +0 -93
  387. package/skills/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
  388. package/skills/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
  389. package/skills/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +0 -93
  390. package/skills/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
  391. package/skills/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
  392. package/skills/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +0 -93
  393. package/skills/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +0 -93
  394. package/skills/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
  395. package/skills/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
  396. package/skills/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +0 -93
  397. package/skills/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
  398. package/skills/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +0 -93
  399. package/skills/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
  400. package/skills/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
  401. package/skills/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +0 -93
  402. package/skills/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
  403. package/skills/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +0 -93
  404. package/skills/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
  405. package/skills/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
  406. package/skills/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
  407. package/skills/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
  408. package/skills/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +0 -93
  409. package/skills/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
  410. package/skills/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +0 -93
  411. package/skills/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
  412. package/skills/skills/doc-coauthoring/SKILL.md +0 -375
  413. package/skills/skills/docx/LICENSE.txt +0 -30
  414. package/skills/skills/docx/SKILL.md +0 -197
  415. package/skills/skills/docx/docx-js.md +0 -350
  416. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
  417. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
  418. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
  419. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
  420. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
  421. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
  422. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
  423. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
  424. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
  425. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
  426. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
  427. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
  428. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
  429. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
  430. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
  431. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
  432. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
  433. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
  434. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
  435. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
  436. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
  437. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
  438. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
  439. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
  440. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
  441. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
  442. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
  443. package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
  444. package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
  445. package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
  446. package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
  447. package/skills/skills/docx/ooxml/schemas/mce/mc.xsd +0 -75
  448. package/skills/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +0 -560
  449. package/skills/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +0 -67
  450. package/skills/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +0 -14
  451. package/skills/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +0 -20
  452. package/skills/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +0 -13
  453. package/skills/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
  454. package/skills/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +0 -8
  455. package/skills/skills/docx/ooxml/scripts/pack.py +0 -159
  456. package/skills/skills/docx/ooxml/scripts/unpack.py +0 -29
  457. package/skills/skills/docx/ooxml/scripts/validate.py +0 -69
  458. package/skills/skills/docx/ooxml/scripts/validation/__init__.py +0 -15
  459. package/skills/skills/docx/ooxml/scripts/validation/base.py +0 -951
  460. package/skills/skills/docx/ooxml/scripts/validation/docx.py +0 -274
  461. package/skills/skills/docx/ooxml/scripts/validation/pptx.py +0 -315
  462. package/skills/skills/docx/ooxml/scripts/validation/redlining.py +0 -279
  463. package/skills/skills/docx/ooxml.md +0 -610
  464. package/skills/skills/docx/scripts/__init__.py +0 -1
  465. package/skills/skills/docx/scripts/document.py +0 -1276
  466. package/skills/skills/docx/scripts/templates/comments.xml +0 -3
  467. package/skills/skills/docx/scripts/templates/commentsExtended.xml +0 -3
  468. package/skills/skills/docx/scripts/templates/commentsExtensible.xml +0 -3
  469. package/skills/skills/docx/scripts/templates/commentsIds.xml +0 -3
  470. package/skills/skills/docx/scripts/templates/people.xml +0 -3
  471. package/skills/skills/docx/scripts/utilities.py +0 -374
  472. package/skills/skills/frontend-design/LICENSE.txt +0 -177
  473. package/skills/skills/frontend-design/SKILL.md +0 -42
  474. package/skills/skills/internal-comms/LICENSE.txt +0 -202
  475. package/skills/skills/internal-comms/SKILL.md +0 -32
  476. package/skills/skills/internal-comms/examples/3p-updates.md +0 -47
  477. package/skills/skills/internal-comms/examples/company-newsletter.md +0 -65
  478. package/skills/skills/internal-comms/examples/faq-answers.md +0 -30
  479. package/skills/skills/internal-comms/examples/general-comms.md +0 -16
  480. package/skills/skills/mcp-builder/LICENSE.txt +0 -202
  481. package/skills/skills/mcp-builder/SKILL.md +0 -236
  482. package/skills/skills/mcp-builder/reference/evaluation.md +0 -602
  483. package/skills/skills/mcp-builder/reference/mcp_best_practices.md +0 -249
  484. package/skills/skills/mcp-builder/reference/node_mcp_server.md +0 -970
  485. package/skills/skills/mcp-builder/reference/python_mcp_server.md +0 -719
  486. package/skills/skills/mcp-builder/scripts/connections.py +0 -151
  487. package/skills/skills/mcp-builder/scripts/evaluation.py +0 -373
  488. package/skills/skills/mcp-builder/scripts/example_evaluation.xml +0 -22
  489. package/skills/skills/mcp-builder/scripts/requirements.txt +0 -2
  490. package/skills/skills/pdf/LICENSE.txt +0 -30
  491. package/skills/skills/pdf/SKILL.md +0 -294
  492. package/skills/skills/pdf/forms.md +0 -205
  493. package/skills/skills/pdf/reference.md +0 -612
  494. package/skills/skills/pdf/scripts/check_bounding_boxes.py +0 -70
  495. package/skills/skills/pdf/scripts/check_bounding_boxes_test.py +0 -226
  496. package/skills/skills/pdf/scripts/check_fillable_fields.py +0 -12
  497. package/skills/skills/pdf/scripts/convert_pdf_to_images.py +0 -35
  498. package/skills/skills/pdf/scripts/create_validation_image.py +0 -41
  499. package/skills/skills/pdf/scripts/extract_form_field_info.py +0 -152
  500. package/skills/skills/pdf/scripts/fill_fillable_fields.py +0 -114
  501. package/skills/skills/pdf/scripts/fill_pdf_form_with_annotations.py +0 -108
  502. package/skills/skills/pptx/LICENSE.txt +0 -30
  503. package/skills/skills/pptx/SKILL.md +0 -484
  504. package/skills/skills/pptx/html2pptx.md +0 -625
  505. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
  506. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
  507. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
  508. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
  509. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
  510. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
  511. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
  512. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
  513. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
  514. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
  515. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
  516. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
  517. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
  518. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
  519. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
  520. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
  521. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
  522. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
  523. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
  524. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
  525. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
  526. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
  527. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
  528. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
  529. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
  530. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
  531. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
  532. package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
  533. package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
  534. package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
  535. package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
  536. package/skills/skills/pptx/ooxml/schemas/mce/mc.xsd +0 -75
  537. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +0 -560
  538. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +0 -67
  539. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +0 -14
  540. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +0 -20
  541. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +0 -13
  542. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
  543. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +0 -8
  544. package/skills/skills/pptx/ooxml/scripts/pack.py +0 -159
  545. package/skills/skills/pptx/ooxml/scripts/unpack.py +0 -29
  546. package/skills/skills/pptx/ooxml/scripts/validate.py +0 -69
  547. package/skills/skills/pptx/ooxml/scripts/validation/__init__.py +0 -15
  548. package/skills/skills/pptx/ooxml/scripts/validation/base.py +0 -951
  549. package/skills/skills/pptx/ooxml/scripts/validation/docx.py +0 -274
  550. package/skills/skills/pptx/ooxml/scripts/validation/pptx.py +0 -315
  551. package/skills/skills/pptx/ooxml/scripts/validation/redlining.py +0 -279
  552. package/skills/skills/pptx/ooxml.md +0 -427
  553. package/skills/skills/pptx/scripts/html2pptx.js +0 -979
  554. package/skills/skills/pptx/scripts/inventory.py +0 -1020
  555. package/skills/skills/pptx/scripts/rearrange.py +0 -231
  556. package/skills/skills/pptx/scripts/replace.py +0 -385
  557. package/skills/skills/pptx/scripts/thumbnail.py +0 -450
  558. package/skills/skills/skill-creator/LICENSE.txt +0 -202
  559. package/skills/skills/skill-creator/SKILL.md +0 -356
  560. package/skills/skills/skill-creator/references/output-patterns.md +0 -82
  561. package/skills/skills/skill-creator/references/workflows.md +0 -28
  562. package/skills/skills/skill-creator/scripts/init_skill.py +0 -303
  563. package/skills/skills/skill-creator/scripts/package_skill.py +0 -110
  564. package/skills/skills/skill-creator/scripts/quick_validate.py +0 -95
  565. package/skills/skills/slack-gif-creator/LICENSE.txt +0 -202
  566. package/skills/skills/slack-gif-creator/SKILL.md +0 -254
  567. package/skills/skills/slack-gif-creator/core/easing.py +0 -234
  568. package/skills/skills/slack-gif-creator/core/frame_composer.py +0 -176
  569. package/skills/skills/slack-gif-creator/core/gif_builder.py +0 -269
  570. package/skills/skills/slack-gif-creator/core/validators.py +0 -136
  571. package/skills/skills/slack-gif-creator/requirements.txt +0 -4
  572. package/skills/skills/theme-factory/LICENSE.txt +0 -202
  573. package/skills/skills/theme-factory/SKILL.md +0 -59
  574. package/skills/skills/theme-factory/theme-showcase.pdf +0 -0
  575. package/skills/skills/theme-factory/themes/arctic-frost.md +0 -19
  576. package/skills/skills/theme-factory/themes/botanical-garden.md +0 -19
  577. package/skills/skills/theme-factory/themes/desert-rose.md +0 -19
  578. package/skills/skills/theme-factory/themes/forest-canopy.md +0 -19
  579. package/skills/skills/theme-factory/themes/golden-hour.md +0 -19
  580. package/skills/skills/theme-factory/themes/midnight-galaxy.md +0 -19
  581. package/skills/skills/theme-factory/themes/modern-minimalist.md +0 -19
  582. package/skills/skills/theme-factory/themes/ocean-depths.md +0 -19
  583. package/skills/skills/theme-factory/themes/sunset-boulevard.md +0 -19
  584. package/skills/skills/theme-factory/themes/tech-innovation.md +0 -19
  585. package/skills/skills/web-artifacts-builder/LICENSE.txt +0 -202
  586. package/skills/skills/web-artifacts-builder/SKILL.md +0 -74
  587. package/skills/skills/web-artifacts-builder/scripts/bundle-artifact.sh +0 -54
  588. package/skills/skills/web-artifacts-builder/scripts/init-artifact.sh +0 -322
  589. package/skills/skills/webapp-testing/LICENSE.txt +0 -202
  590. package/skills/skills/webapp-testing/SKILL.md +0 -96
  591. package/skills/skills/webapp-testing/examples/console_logging.py +0 -35
  592. package/skills/skills/webapp-testing/examples/element_discovery.py +0 -40
  593. package/skills/skills/webapp-testing/examples/static_html_automation.py +0 -33
  594. package/skills/skills/webapp-testing/scripts/with_server.py +0 -106
  595. package/skills/skills/xlsx/LICENSE.txt +0 -30
  596. package/skills/skills/xlsx/SKILL.md +0 -289
  597. package/skills/skills/xlsx/recalc.py +0 -178
  598. package/skills/spec/agent-skills-spec.md +0 -3
  599. package/skills/template/SKILL.md +0 -6
  600. package/src/ai-client.ts +0 -1560
  601. package/src/remote-ai-client.ts +0 -664
  602. /package/{.eslintrc.js → .eslintrc.cjs} +0 -0
package/src/tools.ts CHANGED
@@ -1,21 +1,22 @@
1
1
  import fs from 'fs/promises';
2
+ import { select, text } from '@clack/prompts';
2
3
  import path from 'path';
3
4
  import { fileURLToPath } from 'url';
4
5
  import readline from 'readline';
5
- import { exec, spawn } from 'child_process';
6
- import { promisify } from 'util';
6
+ import { spawn } from 'child_process';
7
7
  import { glob } from 'glob';
8
8
  import axios from 'axios';
9
- import inquirer from 'inquirer';
10
9
  import { Tool, ExecutionMode, AuthType } from './types.js';
11
- import type { Message, ToolDefinition } from './ai-client.js';
12
- import { colors, icons, styleHelpers } from './theme.js';
10
+ import type { Message, ToolDefinition } from './ai-client/types.js';
11
+ import { colors, icons } from './theme.js';
13
12
  import { getLogger } from './logger.js';
14
13
  import { getCancellationManager } from './cancellation.js';
15
14
  import { SystemPromptGenerator } from './system-prompt-generator.js';
16
- import { InteractiveSession } from './session.js';
17
-
18
- const execAsync = promisify(exec);
15
+ import { getSingletonSession } from './session.js';
16
+ import { ripgrep, fdFind } from './ripgrep.js';
17
+ import { getShellConfig, killProcessTree, quoteShellCommand } from './shell.js';
18
+ import { truncateTail, buildTruncationNotice } from './truncate.js';
19
+ import { createAIClient } from './ai-client-factory.js';
19
20
 
20
21
  //
21
22
  // Tool Description Pattern
@@ -70,9 +71,17 @@ export class ReadTool implements Tool {
70
71
  - Combine with ListDirectory to explore project structure first
71
72
  - Don't re-read files unnecessarily`;
72
73
 
73
- allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
74
+ allowedModes = [
75
+ ExecutionMode.YOLO,
76
+ ExecutionMode.ACCEPT_EDITS,
77
+ ExecutionMode.PLAN,
78
+ ExecutionMode.SMART,
79
+ ];
74
80
 
75
81
  async execute(params: { filePath: string; offset?: number; limit?: number }): Promise<string> {
82
+ if (!params || typeof params.filePath !== 'string') {
83
+ throw new Error('filePath is required and must be a string');
84
+ }
76
85
  const { filePath, offset = 0, limit } = params;
77
86
 
78
87
  try {
@@ -89,13 +98,24 @@ export class ReadTool implements Tool {
89
98
  }
90
99
  const absolutePath = path.resolve(resolvedPath);
91
100
  const content = await fs.readFile(absolutePath, 'utf-8');
92
-
101
+
93
102
  const lines = content.split('\n');
103
+ const totalLines = lines.length;
94
104
  const startLine = Math.max(0, offset);
95
- const endLine = limit !== undefined ? Math.min(lines.length, startLine + limit) : lines.length;
105
+ const endLine = limit !== undefined ? Math.min(totalLines, startLine + limit) : totalLines;
96
106
  const selectedLines = lines.slice(startLine, endLine);
97
-
98
- return selectedLines.join('\n');
107
+ const result = selectedLines.join('\n');
108
+
109
+ // Add truncation notice if content is limited
110
+ if (limit !== undefined && endLine < totalLines) {
111
+ const remaining = totalLines - endLine;
112
+ const nextOffset = endLine;
113
+ return (
114
+ result + `\n\n[${remaining} more lines in file. Use offset=${nextOffset} to continue]`
115
+ );
116
+ }
117
+
118
+ return result;
99
119
  } catch (error: any) {
100
120
  // Show user-friendly path in error message
101
121
  let displayPath = filePath;
@@ -142,7 +162,16 @@ export class WriteTool implements Tool {
142
162
  - For partial edits, use Edit tool instead`;
143
163
  allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
144
164
 
145
- async execute(params: { filePath: string; content: string }): Promise<{ success: boolean; message: string; filePath: string; lineCount: number; preview?: string }> {
165
+ async execute(params: {
166
+ filePath: string;
167
+ content: string;
168
+ }): Promise<{
169
+ success: boolean;
170
+ message: string;
171
+ filePath: string;
172
+ lineCount: number;
173
+ preview?: string;
174
+ }> {
146
175
  const { filePath, content } = params;
147
176
 
148
177
  try {
@@ -161,7 +190,7 @@ export class WriteTool implements Tool {
161
190
  message: `Successfully wrote to ${filePath}`,
162
191
  filePath,
163
192
  lineCount,
164
- preview: isTruncated ? preview + '\n...' : preview
193
+ preview: isTruncated ? preview + '\n...' : preview,
165
194
  };
166
195
  } catch (error: any) {
167
196
  throw new Error(`Failed to write file ${filePath}: ${error.message}`);
@@ -171,7 +200,7 @@ export class WriteTool implements Tool {
171
200
 
172
201
  export class GrepTool implements Tool {
173
202
  name = 'Grep';
174
- description = `Search for text patterns within files using regex or literal string matching. This is your PRIMARY tool for finding specific code, functions, or content.
203
+ description = `Search for text patterns within files using ripgrep. This is your PRIMARY tool for finding specific code, functions, or content.
175
204
 
176
205
  # When to Use
177
206
  - Finding specific function definitions or calls
@@ -188,122 +217,60 @@ export class GrepTool implements Tool {
188
217
  # Parameters
189
218
  - \`pattern\`: Regex or literal string to search for
190
219
  - \`path\`: (Optional) Directory to search in, default: "."
191
- - \`include\`: (Optional) File glob pattern to include
192
- - \`exclude\`: (Optional) File glob pattern to exclude
193
- - \`case_sensitive\`: (Optional) Case-sensitive search, default: false
194
- - \`fixed_strings\`: (Optional) Treat pattern as literal string, default: false
220
+ - \`glob\`: (Optional) File glob pattern to include (e.g., "*.ts", "**/*.js")
221
+ - \`ignoreCase\`: (Optional) Case-insensitive search, default: false
222
+ - \`literal\`: (Optional) Treat pattern as literal string, default: false
195
223
  - \`context\`: (Optional) Lines of context before/after matches
196
- - \`no_ignore\`: (Optional) Don't ignore node_modules/.git, default: false
197
224
 
198
225
  # Examples
199
226
  - Find function: Grep(pattern="function myFunction")
200
227
  - Find with context: Grep(pattern="TODO", context=3)
201
- - TypeScript only: Grep(pattern="interface", include="*.ts")
228
+ - TypeScript only: Grep(pattern="interface", glob="*.ts")
229
+ - Case-insensitive: Grep(pattern="error", ignoreCase=true)
202
230
 
203
231
  # Best Practices
204
- - Use case_sensitive=true for short patterns to reduce false positives
205
- - Use fixed_strings=true if your pattern has special regex characters
232
+ - Use ignoreCase=true for short patterns to reduce false positives
233
+ - Use literal=true if your pattern has special regex characters
206
234
  - Use context to see the surrounding code for each match
207
- - Combine with include/exclude to narrow down file types`;
208
- allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
235
+ - Combine with glob to narrow down file types`;
236
+ allowedModes = [
237
+ ExecutionMode.YOLO,
238
+ ExecutionMode.ACCEPT_EDITS,
239
+ ExecutionMode.PLAN,
240
+ ExecutionMode.SMART,
241
+ ];
209
242
 
210
243
  async execute(params: {
211
244
  pattern: string;
212
245
  path?: string;
213
- include?: string;
214
- exclude?: string;
215
- case_sensitive?: boolean;
216
- fixed_strings?: boolean;
246
+ glob?: string;
247
+ ignoreCase?: boolean;
248
+ literal?: boolean;
217
249
  context?: number;
218
- after?: number;
219
- before?: number;
220
- no_ignore?: boolean;
250
+ limit?: number;
221
251
  }): Promise<string[]> {
222
252
  const {
223
253
  pattern,
224
254
  path: searchPath = '.',
225
- include,
226
- exclude,
227
- case_sensitive = false,
228
- fixed_strings = false,
255
+ glob: includeGlob,
256
+ ignoreCase = false,
257
+ literal = false,
229
258
  context,
230
- after,
231
- before,
232
- no_ignore = false
259
+ limit,
233
260
  } = params;
234
-
261
+
235
262
  try {
236
- const ignorePatterns = no_ignore ? [] : ['node_modules/**', '.git/**', 'dist/**', 'build/**'];
237
- if (exclude) {
238
- ignorePatterns.push(exclude);
239
- }
240
-
241
- const absolutePath = path.resolve(searchPath);
242
- const files = await glob('**/*', {
243
- cwd: absolutePath,
244
- nodir: true,
245
- ignore: ignorePatterns
263
+ const result = await ripgrep({
264
+ pattern,
265
+ path: searchPath,
266
+ glob: includeGlob,
267
+ ignoreCase,
268
+ literal,
269
+ context,
270
+ limit,
246
271
  });
247
272
 
248
- const results: string[] = [];
249
-
250
- for (const file of files) {
251
- const fullPath = path.join(absolutePath, file);
252
- if (include && !file.match(include)) {
253
- continue;
254
- }
255
-
256
- try {
257
- const content = await fs.readFile(fullPath, 'utf-8');
258
- const lines = content.split('\n');
259
-
260
- lines.forEach((line, index) => {
261
- let matches = false;
262
-
263
- if (fixed_strings) {
264
- matches = case_sensitive
265
- ? line.includes(pattern)
266
- : line.toLowerCase().includes(pattern.toLowerCase());
267
- } else {
268
- try {
269
- const flags = case_sensitive ? 'g' : 'gi';
270
- const regex = new RegExp(pattern, flags);
271
- matches = regex.test(line);
272
- } catch (e) {
273
- matches = case_sensitive
274
- ? line.includes(pattern)
275
- : line.toLowerCase().includes(pattern.toLowerCase());
276
- }
277
- }
278
-
279
- if (matches) {
280
- const contextLines: string[] = [];
281
-
282
- if (before || context) {
283
- const beforeCount = before || context || 0;
284
- for (let i = Math.max(0, index - beforeCount); i < index; i++) {
285
- contextLines.push(`${fullPath}:${i + 1}:${lines[i].trim()}`);
286
- }
287
- }
288
-
289
- contextLines.push(`${fullPath}:${index + 1}:${line.trim()}`);
290
-
291
- if (after || context) {
292
- const afterCount = after || context || 0;
293
- for (let i = index + 1; i < Math.min(lines.length, index + 1 + afterCount); i++) {
294
- contextLines.push(`${fullPath}:${i + 1}:${lines[i].trim()}`);
295
- }
296
- }
297
-
298
- results.push(...contextLines);
299
- }
300
- });
301
- } catch (error) {
302
- continue;
303
- }
304
- }
305
-
306
- return results;
273
+ return result.split('\n').filter((line) => line.trim());
307
274
  } catch (error: any) {
308
275
  throw new Error(`Grep failed: ${error.message}`);
309
276
  }
@@ -335,13 +302,23 @@ export class BashTool implements Tool {
335
302
  - \`description\`: (Optional) Description of what the command does
336
303
  - \`timeout\`: (Optional) Timeout in seconds, default: 120
337
304
  - \`run_in_bg\`: (Optional) Run in background, default: false
305
+ - \`skillPath\`: (Optional) Skill directory path - when provided, NODE_PATH will include the skill's node_modules for dependency resolution
338
306
 
339
307
  # Examples
340
308
  - Install dependencies: Bash(command="npm install", description="Install npm dependencies")
309
+ - Run in skill directory with local deps: Bash(command="npm install docx", skillPath="~/.xagent/skills/docx")
310
+
311
+ # NODE_PATH Resolution
312
+ When \`skillPath\` is provided, the command will have access to:
313
+ - \`<skillPath>/node_modules\` (skill's local dependencies)
314
+ - xAgent's global node_modules
315
+
316
+ This is useful when working with skills that have local dependencies.
341
317
  - Run tests: Bash(command="npm test", description="Run unit tests")
342
318
  - Build project: Bash(command="npm run build", description="Build the project")
343
319
 
344
320
  # Best Practices
321
+ - To install npm packages that persist across sessions, use: \`XAGENT_USER_NPM=1 npm install <package>\`
345
322
  - Always provide a description for context
346
323
  - Set appropriate timeout for long-running commands
347
324
  - Use run_in_bg=true for commands that take a long time
@@ -355,14 +332,25 @@ export class BashTool implements Tool {
355
332
  description?: string;
356
333
  timeout?: number;
357
334
  run_in_bg?: boolean;
358
- }): Promise<{ stdout: string; stderr: string; exitCode: number; taskId?: string }> {
359
- const { command, cwd, description, timeout = 120, run_in_bg = false } = params;
360
-
335
+ skillPath?: string; // Skill directory path for NODE_PATH resolution
336
+ }): Promise<{
337
+ stdout: string;
338
+ stderr: string;
339
+ exitCode: number;
340
+ taskId?: string;
341
+ truncated?: boolean;
342
+ truncationNotice?: string;
343
+ skillPath?: string;
344
+ }> {
345
+ const { command, cwd, description, timeout = 120, run_in_bg = false, skillPath } = params;
346
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
347
+ void description;
348
+
361
349
  // Determine effective working directory
362
350
  // Only use cwd if the command doesn't contain 'cd' (let LLM control directory)
363
351
  let effectiveCwd: string | undefined;
364
352
  const hasCdCommand = /cd\s+["']?[^"&|;]+["']?/.test(command);
365
-
353
+
366
354
  if (cwd && !hasCdCommand) {
367
355
  // Command doesn't control its own directory, use provided cwd
368
356
  effectiveCwd = cwd;
@@ -373,82 +361,270 @@ export class BashTool implements Tool {
373
361
  // No cwd provided, use default
374
362
  effectiveCwd = undefined;
375
363
  }
376
-
377
- // Set up environment with NODE_PATH only for node commands
378
- const nodeModulesPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'node_modules');
379
- const env = {
364
+
365
+ // Resolve actual working directory
366
+ const actualCwd = effectiveCwd || process.cwd();
367
+
368
+ // Set up environment with NODE_PATH for node commands
369
+ const builtinNodeModulesPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'node_modules');
370
+
371
+ // Get user skills path from config (unified path: ~/.xagent/skills)
372
+ const { getConfigManager } = await import('./config.js');
373
+ const configManager = getConfigManager();
374
+ const userSkillsPath = configManager.getUserSkillsPath();
375
+
376
+ // Skill deps path: ~/.xagent/skills/{skillName}/node_modules
377
+ const builtinDepsPath = userSkillsPath ? path.join(userSkillsPath, 'builtin-deps') : null;
378
+
379
+ // Determine which node_modules to use
380
+ let skillNodeModulesPath: string | null = null;
381
+
382
+ // Priority 1: skillPath parameter (workspace scenario - LLM works in workspace, not skill dir)
383
+ if (skillPath) {
384
+ if (skillPath.includes('/builtin-deps/')) {
385
+ // Skill with deps in builtin-deps directory
386
+ const match = skillPath.match(/\/builtin-deps\/([^/]+)/);
387
+ if (match) {
388
+ skillNodeModulesPath = path.join(builtinDepsPath!, match[1], 'node_modules');
389
+ }
390
+ } else {
391
+ // Regular skill
392
+ skillNodeModulesPath = path.join(skillPath, 'node_modules');
393
+ }
394
+ }
395
+ // Priority 2: Check if we're inside a skill directory
396
+ else if (userSkillsPath && userSkillsPath.trim() && actualCwd.startsWith(userSkillsPath)) {
397
+ const relativePath = actualCwd.substring(userSkillsPath.length);
398
+ const pathParts = relativePath.split(path.sep).filter(Boolean);
399
+
400
+ if (pathParts.length > 0) {
401
+ if (pathParts[0] === 'builtin-deps' && pathParts.length > 1) {
402
+ // Skill with local deps in builtin-deps
403
+ const skillName = pathParts[1];
404
+ skillNodeModulesPath = path.join(builtinDepsPath!, skillName, 'node_modules');
405
+ } else {
406
+ // Regular skill
407
+ const skillName = pathParts[0];
408
+ const skillRoot = path.join(userSkillsPath, skillName);
409
+ try {
410
+ const skillMdPath = path.join(skillRoot, 'SKILL.md');
411
+ await fs.access(skillMdPath);
412
+ skillNodeModulesPath = path.join(skillRoot, 'node_modules');
413
+ } catch {
414
+ // Not a skill directory, skip
415
+ }
416
+ }
417
+ }
418
+ }
419
+
420
+ // Build NODE_PATH - skill's node_modules takes precedence (last-wins)
421
+ let nodePath: string;
422
+ if (skillNodeModulesPath) {
423
+ nodePath = `${skillNodeModulesPath}${path.delimiter}${builtinNodeModulesPath}`;
424
+ } else {
425
+ nodePath = builtinNodeModulesPath;
426
+ }
427
+
428
+ const env: Record<string, string> = {
380
429
  ...process.env,
381
- NODE_PATH: nodeModulesPath
430
+ NODE_PATH: nodePath
382
431
  };
383
-
384
- // Only add NODE_PATH prefix for node commands
385
- const isNodeCommand = /\bnode\b/.test(command);
386
- const finalCommand = isNodeCommand
387
- ? `set NODE_PATH=${nodeModulesPath} && ${command}`
388
- : command;
389
-
432
+
433
+ // Handle npm install commands
434
+ const isNpmInstall = /\bnpm\s+install\b/i.test(command);
435
+ let finalCommand = command;
436
+
437
+ if (isNpmInstall && skillNodeModulesPath) {
438
+ // Install to skill's own node_modules
439
+ await fs.mkdir(skillNodeModulesPath, { recursive: true }).catch(() => {});
440
+ finalCommand = command.replace(/\bnpm\s+install\b/i, `npm install --prefix "${skillNodeModulesPath}"`);
441
+ }
442
+
443
+ // Get shell configuration (Windows Git Bash detection, etc.)
444
+ const { shell, args } = getShellConfig();
445
+
446
+ // Set up cross-platform encoding environment for command execution
447
+ if (process.platform === 'win32') {
448
+ // Windows: set code page to UTF-8 and ensure console output encoding
449
+ // chcp 65001 sets the console code page to UTF-8
450
+ // Use *>$null to suppress output (PowerShell-style, not CMD-style)
451
+ finalCommand = `chcp 65001 *>$null; [Console]::OutputEncoding = [System.Text.Encoding]::UTF8; [System.Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ${finalCommand}`;
452
+ } else {
453
+ // Unix/macOS: set locale to UTF-8 for proper encoding handling
454
+ finalCommand = `export LC_ALL=C.UTF-8; export LANG=C.UTF-8; export PYTHONIOENCODING=utf-8; ${finalCommand}`;
455
+ }
456
+
457
+ const shellArgs = [...args, quoteShellCommand(finalCommand)];
458
+
390
459
  try {
391
460
  if (run_in_bg) {
392
461
  const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
393
-
394
- const childProcess = spawn(finalCommand, {
462
+
463
+ const spawnOptions: any = {
395
464
  cwd: effectiveCwd || process.cwd(),
396
- shell: true,
397
- detached: true,
398
- env
399
- });
400
-
465
+ env,
466
+ stdio: ['pipe', 'pipe', 'pipe'],
467
+ };
468
+
469
+ // On Windows, don't use detached mode for PowerShell as it breaks output piping
470
+ if (process.platform !== 'win32') {
471
+ spawnOptions.detached = true;
472
+ }
473
+
474
+ const childProcess = spawn(shell, shellArgs, spawnOptions);
475
+
401
476
  const output: string[] = [];
402
-
477
+
403
478
  childProcess.stdout?.on('data', (data: Buffer) => {
404
479
  const text = data.toString();
405
480
  output.push(text);
406
481
  });
407
-
482
+
408
483
  childProcess.stderr?.on('data', (data: Buffer) => {
409
484
  const text = data.toString();
410
485
  output.push(text);
411
486
  });
412
-
413
- childProcess.on('close', (code: number) => {
414
- console.log(`Background task ${taskId} exited with code ${code}`);
487
+
488
+ childProcess.on('close', (_code: number) => {
489
+ // Silent cleanup - don't log to avoid noise during normal operation
490
+ // Note: On Windows with PowerShell, the shell process exits after
491
+ // the command completes
415
492
  });
416
-
493
+
417
494
  const toolRegistry = getToolRegistry();
418
495
  (toolRegistry as any).addBackgroundTask(taskId, {
419
496
  process: childProcess,
420
497
  startTime: Date.now(),
421
- output
498
+ output,
422
499
  });
423
-
500
+
424
501
  return {
425
502
  stdout: '',
426
503
  stderr: '',
427
504
  exitCode: 0,
428
- taskId
505
+ taskId,
429
506
  };
430
507
  } else {
431
- const { stdout, stderr } = await execAsync(finalCommand, {
508
+ // Execute command with spawn for better control
509
+ const result = await this.spawnWithTimeout(shell, shellArgs, {
432
510
  cwd: effectiveCwd || process.cwd(),
433
- maxBuffer: 1024 * 1024 * 10,
434
- timeout: timeout * 1000,
435
- env
511
+ env,
512
+ timeout,
436
513
  });
437
514
 
515
+ // Apply truncation to stdout and stderr separately
516
+ const stdoutResult = truncateTail(result.stdout);
517
+ const stderrResult = truncateTail(result.stderr);
518
+
519
+ const stdout = stdoutResult.content;
520
+ const stderr = stderrResult.content;
521
+ let truncationNotice = '';
522
+
523
+ if (stdoutResult.truncated) {
524
+ truncationNotice += buildTruncationNotice(stdoutResult) + '\n';
525
+ }
526
+ if (stderrResult.truncated) {
527
+ truncationNotice += buildTruncationNotice(stderrResult) + '\n';
528
+ }
529
+
438
530
  return {
439
531
  stdout,
440
532
  stderr,
441
- exitCode: 0
533
+ exitCode: result.exitCode,
534
+ truncated: stdoutResult.truncated || stderrResult.truncated,
535
+ truncationNotice: truncationNotice || undefined,
442
536
  };
443
537
  }
444
538
  } catch (error: any) {
539
+ // Check if this was a timeout
540
+ if (error.message === 'timeout') {
541
+ return {
542
+ stdout: '',
543
+ stderr: 'Command timed out',
544
+ exitCode: -1,
545
+ truncated: false,
546
+ };
547
+ }
445
548
  return {
446
549
  stdout: error.stdout || '',
447
550
  stderr: error.stderr || error.message,
448
- exitCode: error.code || 1
551
+ exitCode: error.code || 1,
449
552
  };
450
553
  }
451
554
  }
555
+
556
+ /**
557
+ * Execute a command with timeout support and proper process termination.
558
+ */
559
+ private spawnWithTimeout(
560
+ shell: string,
561
+ args: string[],
562
+ options: { cwd: string; env: NodeJS.ProcessEnv; timeout: number }
563
+ ): Promise<{ stdout: string; stderr: string; exitCode: number }> {
564
+ return new Promise((resolve, reject) => {
565
+ const { cwd, env, timeout } = options;
566
+ let timedOut = false;
567
+ let timeoutHandle: NodeJS.Timeout | undefined;
568
+
569
+ const spawnOptions: any = {
570
+ cwd,
571
+ env,
572
+ stdio: ['pipe', 'pipe', 'pipe'],
573
+ };
574
+
575
+ // On Windows, don't use detached mode for PowerShell as it breaks output piping
576
+ if (process.platform !== 'win32') {
577
+ spawnOptions.detached = true;
578
+ }
579
+
580
+ const child = spawn(shell, args, spawnOptions);
581
+
582
+ const stdoutChunks: Buffer[] = [];
583
+ const stderrChunks: Buffer[] = [];
584
+
585
+ // Set timeout if provided
586
+ if (timeout > 0) {
587
+ timeoutHandle = setTimeout(() => {
588
+ timedOut = true;
589
+ if (child.pid) {
590
+ killProcessTree(child.pid);
591
+ }
592
+ }, timeout * 1000);
593
+ }
594
+
595
+ // Stream stdout
596
+ child.stdout?.on('data', (data: Buffer) => {
597
+ stdoutChunks.push(data);
598
+ });
599
+
600
+ // Stream stderr
601
+ child.stderr?.on('data', (data: Buffer) => {
602
+ stderrChunks.push(data);
603
+ });
604
+
605
+ // Handle process exit
606
+ child.on('close', (code: number) => {
607
+ if (timeoutHandle) clearTimeout(timeoutHandle);
608
+
609
+ if (timedOut) {
610
+ reject(new Error('timeout'));
611
+ return;
612
+ }
613
+
614
+ resolve({
615
+ stdout: Buffer.concat(stdoutChunks).toString('utf-8'),
616
+ stderr: Buffer.concat(stderrChunks).toString('utf-8'),
617
+ exitCode: code ?? -1,
618
+ });
619
+ });
620
+
621
+ // Handle spawn errors
622
+ child.on('error', (err) => {
623
+ if (timeoutHandle) clearTimeout(timeoutHandle);
624
+ reject(err);
625
+ });
626
+ });
627
+ }
452
628
  }
453
629
 
454
630
  export class ListDirectoryTool implements Tool {
@@ -481,27 +657,32 @@ export class ListDirectoryTool implements Tool {
481
657
  - Results are absolute paths
482
658
  - Ignores node_modules and .git by default
483
659
  - Combine with Read to examine file contents`;
484
- allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
660
+ allowedModes = [
661
+ ExecutionMode.YOLO,
662
+ ExecutionMode.ACCEPT_EDITS,
663
+ ExecutionMode.PLAN,
664
+ ExecutionMode.SMART,
665
+ ];
485
666
 
486
667
  async execute(params: { path?: string; recursive?: boolean }): Promise<string[]> {
487
668
  const { path: dirPath = '.', recursive = false } = params;
488
-
669
+
489
670
  try {
490
671
  const absolutePath = path.resolve(dirPath);
491
-
672
+
492
673
  const stats = await fs.stat(absolutePath).catch(() => null);
493
674
  if (!stats || !stats.isDirectory()) {
494
675
  throw new Error(`Directory does not exist: ${dirPath}`);
495
676
  }
496
-
677
+
497
678
  const pattern = recursive ? '**/*' : '*';
498
679
  const files = await glob(pattern, {
499
680
  cwd: absolutePath,
500
681
  nodir: false,
501
- ignore: ['node_modules/**', '.git/**']
682
+ ignore: ['node_modules/**', '.git/**'],
502
683
  });
503
684
 
504
- return files.map(file => path.join(absolutePath, file));
685
+ return files.map((file) => path.join(absolutePath, file));
505
686
  } catch (error: any) {
506
687
  throw new Error(`Failed to list directory: ${error.message}`);
507
688
  }
@@ -519,7 +700,7 @@ export interface SearchFilesResult {
519
700
 
520
701
  export class SearchFilesTool implements Tool {
521
702
  name = 'SearchFiles';
522
- description = `Search for files matching a glob pattern. This is your PRIMARY tool for finding files by name or extension.
703
+ description = `Search for files matching a glob pattern using fd. This is your PRIMARY tool for finding files by name or extension.
523
704
 
524
705
  # When to Use
525
706
  - Finding all files of a certain type (*.ts, *.json, *.md)
@@ -554,17 +735,37 @@ export class SearchFilesTool implements Tool {
554
735
  - Combine with path parameter to search specific directories
555
736
  - Use limit parameter to avoid huge result sets
556
737
  - Results are file paths, not content (use Grep on results if needed)`;
557
- allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
738
+ allowedModes = [
739
+ ExecutionMode.YOLO,
740
+ ExecutionMode.ACCEPT_EDITS,
741
+ ExecutionMode.PLAN,
742
+ ExecutionMode.SMART,
743
+ ];
558
744
 
559
- async execute(params: { pattern: string; path?: string; limit?: number }): Promise<SearchFilesResult> {
745
+ async execute(params: {
746
+ pattern: string;
747
+ path?: string;
748
+ limit?: number;
749
+ }): Promise<SearchFilesResult> {
560
750
  const { pattern, path: searchPath = '.', limit = 1000 } = params;
561
751
 
562
752
  try {
563
- const files = await glob(pattern, {
564
- cwd: searchPath,
565
- ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**']
753
+ const output = await fdFind({
754
+ pattern,
755
+ path: searchPath,
756
+ limit,
566
757
  });
567
758
 
759
+ if (output === 'No files found') {
760
+ return {
761
+ files: [],
762
+ total: 0,
763
+ truncated: false,
764
+ };
765
+ }
766
+
767
+ const files = output.split('\n').filter((line) => line.trim());
768
+
568
769
  const total = files.length;
569
770
  const truncated = total > limit;
570
771
  const result = truncated ? files.slice(0, limit) : files;
@@ -572,7 +773,7 @@ export class SearchFilesTool implements Tool {
572
773
  return {
573
774
  files: result,
574
775
  total,
575
- truncated
776
+ truncated,
576
777
  };
577
778
  } catch (error: any) {
578
779
  throw new Error(`Search failed: ${error.message}`);
@@ -608,7 +809,9 @@ export class DeleteFileTool implements Tool {
608
809
  - This action is irreversible - be certain before executing`;
609
810
  allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
610
811
 
611
- async execute(params: { filePath: string }): Promise<{ success: boolean; message: string; filePath: string }> {
812
+ async execute(params: {
813
+ filePath: string;
814
+ }): Promise<{ success: boolean; message: string; filePath: string }> {
612
815
  const { filePath } = params;
613
816
 
614
817
  try {
@@ -618,7 +821,7 @@ export class DeleteFileTool implements Tool {
618
821
  return {
619
822
  success: true,
620
823
  message: `Successfully deleted ${filePath}`,
621
- filePath
824
+ filePath,
622
825
  };
623
826
  } catch (error: any) {
624
827
  throw new Error(`Failed to delete file ${filePath}: ${error.message}`);
@@ -654,16 +857,19 @@ export class CreateDirectoryTool implements Tool {
654
857
  - Consider the overall project structure before creating`;
655
858
  allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
656
859
 
657
- async execute(params: { dirPath: string; recursive?: boolean }): Promise<{ success: boolean; message: string }> {
860
+ async execute(params: {
861
+ dirPath: string;
862
+ recursive?: boolean;
863
+ }): Promise<{ success: boolean; message: string }> {
658
864
  const { dirPath, recursive = true } = params;
659
-
865
+
660
866
  try {
661
867
  const absolutePath = path.resolve(dirPath);
662
868
  await fs.mkdir(absolutePath, { recursive });
663
-
869
+
664
870
  return {
665
871
  success: true,
666
- message: `Successfully created directory ${dirPath}`
872
+ message: `Successfully created directory ${dirPath}`,
667
873
  };
668
874
  } catch (error: any) {
669
875
  throw new Error(`Failed to create directory ${dirPath}: ${error.message}`);
@@ -672,165 +878,169 @@ export class CreateDirectoryTool implements Tool {
672
878
  }
673
879
 
674
880
  // 编辑工具辅助函数
675
- function detectLineEnding(content: string): "\r\n" | "\n" {
676
- const crlfIdx = content.indexOf("\r\n");
677
- const lfIdx = content.indexOf("\n");
678
- if (lfIdx === -1) return "\n";
679
- if (crlfIdx === -1) return "\n";
680
- return crlfIdx < lfIdx ? "\r\n" : "\n";
881
+ function detectLineEnding(content: string): '\r\n' | '\n' {
882
+ const crlfIdx = content.indexOf('\r\n');
883
+ const lfIdx = content.indexOf('\n');
884
+ if (lfIdx === -1) return '\n';
885
+ if (crlfIdx === -1) return '\n';
886
+ return crlfIdx < lfIdx ? '\r\n' : '\n';
681
887
  }
682
888
 
683
889
  function normalizeToLF(text: string): string {
684
- return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
890
+ return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
685
891
  }
686
892
 
687
- function restoreLineEndings(text: string, ending: "\r\n" | "\n"): string {
688
- return ending === "\r\n" ? text.replace(/\n/g, "\r\n") : text;
893
+ function restoreLineEndings(text: string, ending: '\r\n' | '\n'): string {
894
+ return ending === '\r\n' ? text.replace(/\n/g, '\r\n') : text;
689
895
  }
690
896
 
691
897
  function normalizeForFuzzyMatch(text: string): string {
692
- return (
693
- text
694
- .split("\n")
695
- .map((line) => line.trimEnd())
696
- .join("\n")
697
- .replace(/['‘’""]/g, "'")
698
- .replace(/["""]/g, '"')
699
- .replace(/[—–‑−]/g, "-")
700
- .replace(/[\u00A0\u2002-\u200A\u202F\u205F\u3000]/g, " ")
701
- );
898
+ return text
899
+ .split('\n')
900
+ .map((line) => line.trimEnd())
901
+ .join('\n')
902
+ .replace(/['‘’""]/g, "'")
903
+ .replace(/["""]/g, '"')
904
+ .replace(/[—–‑−]/g, '-')
905
+ .replace(/[\u00A0\u2002-\u200A\u202F\u205F\u3000]/g, ' ');
702
906
  }
703
907
 
704
908
  interface FuzzyMatchResult {
705
- found: boolean;
706
- index: number;
707
- matchLength: number;
708
- usedFuzzyMatch: boolean;
709
- contentForReplacement: string;
909
+ found: boolean;
910
+ index: number;
911
+ matchLength: number;
912
+ usedFuzzyMatch: boolean;
913
+ contentForReplacement: string;
710
914
  }
711
915
 
712
916
  function fuzzyFindText(content: string, oldText: string): FuzzyMatchResult {
713
- const exactIndex = content.indexOf(oldText);
714
- if (exactIndex !== -1) {
715
- return {
716
- found: true,
717
- index: exactIndex,
718
- matchLength: oldText.length,
719
- usedFuzzyMatch: false,
720
- contentForReplacement: content,
721
- };
722
- }
723
-
724
- const fuzzyContent = normalizeForFuzzyMatch(content);
725
- const fuzzyOldText = normalizeForFuzzyMatch(oldText);
726
- const fuzzyIndex = fuzzyContent.indexOf(fuzzyOldText);
727
-
728
- if (fuzzyIndex === -1) {
729
- return {
730
- found: false,
731
- index: -1,
732
- matchLength: 0,
733
- usedFuzzyMatch: false,
734
- contentForReplacement: content,
735
- };
736
- }
737
-
738
- return {
739
- found: true,
740
- index: fuzzyIndex,
741
- matchLength: fuzzyOldText.length,
742
- usedFuzzyMatch: true,
743
- contentForReplacement: fuzzyContent,
744
- };
917
+ const exactIndex = content.indexOf(oldText);
918
+ if (exactIndex !== -1) {
919
+ return {
920
+ found: true,
921
+ index: exactIndex,
922
+ matchLength: oldText.length,
923
+ usedFuzzyMatch: false,
924
+ contentForReplacement: content,
925
+ };
926
+ }
927
+
928
+ const fuzzyContent = normalizeForFuzzyMatch(content);
929
+ const fuzzyOldText = normalizeForFuzzyMatch(oldText);
930
+ const fuzzyIndex = fuzzyContent.indexOf(fuzzyOldText);
931
+
932
+ if (fuzzyIndex === -1) {
933
+ return {
934
+ found: false,
935
+ index: -1,
936
+ matchLength: 0,
937
+ usedFuzzyMatch: false,
938
+ contentForReplacement: content,
939
+ };
940
+ }
941
+
942
+ return {
943
+ found: true,
944
+ index: fuzzyIndex,
945
+ matchLength: fuzzyOldText.length,
946
+ usedFuzzyMatch: true,
947
+ contentForReplacement: fuzzyContent,
948
+ };
745
949
  }
746
950
 
747
951
  function stripBom(content: string): { bom: string; text: string } {
748
- return content.startsWith("\uFEFF") ? { bom: "\uFEFF", text: content.slice(1) } : { bom: "", text: content };
952
+ return content.startsWith('\uFEFF')
953
+ ? { bom: '\uFEFF', text: content.slice(1) }
954
+ : { bom: '', text: content };
749
955
  }
750
956
 
751
- async function generateDiffString(oldContent: string, newContent: string, contextLines = 4): Promise<{ diff: string; firstChangedLine: number | undefined }> {
752
- const diffModule = await import("diff");
753
- const parts = diffModule.diffLines(oldContent, newContent);
754
- const output: string[] = [];
755
-
756
- const oldLines = oldContent.split("\n");
757
- const newLines = newContent.split("\n");
758
- const maxLineNum = Math.max(oldLines.length, newLines.length);
759
- const lineNumWidth = String(maxLineNum).length;
760
-
761
- let oldLineNum = 1;
762
- let newLineNum = 1;
763
- let lastWasChange = false;
764
- let firstChangedLine: number | undefined;
765
-
766
- for (let i = 0; i < parts.length; i++) {
767
- const part = parts[i];
768
- const raw = part.value.split("\n");
769
- if (raw[raw.length - 1] === "") {
770
- raw.pop();
771
- }
772
-
773
- if (part.added || part.removed) {
774
- if (firstChangedLine === undefined) {
775
- firstChangedLine = newLineNum;
776
- }
777
-
778
- for (const line of raw) {
779
- if (part.added) {
780
- const lineNum = String(newLineNum).padStart(lineNumWidth, " ");
781
- output.push(`+${lineNum} ${line}`);
782
- newLineNum++;
783
- } else {
784
- const lineNum = String(oldLineNum).padStart(lineNumWidth, " ");
785
- output.push(`-${lineNum} ${line}`);
786
- oldLineNum++;
787
- }
788
- }
789
- lastWasChange = true;
790
- } else {
791
- const nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);
792
-
793
- if (lastWasChange || nextPartIsChange) {
794
- let linesToShow = raw;
795
- let skipStart = 0;
796
- let skipEnd = 0;
797
-
798
- if (!lastWasChange) {
799
- skipStart = Math.max(0, raw.length - contextLines);
800
- linesToShow = raw.slice(skipStart);
801
- }
802
-
803
- if (!nextPartIsChange && linesToShow.length > contextLines) {
804
- skipEnd = linesToShow.length - contextLines;
805
- linesToShow = linesToShow.slice(0, contextLines);
806
- }
807
-
808
- if (skipStart > 0) {
809
- output.push(` ${"".padStart(lineNumWidth, " ")} ...`);
810
- oldLineNum += skipStart;
811
- newLineNum += skipStart;
812
- }
813
-
814
- for (const line of linesToShow) {
815
- const lineNum = String(oldLineNum).padStart(lineNumWidth, " ");
816
- output.push(` ${lineNum} ${line}`);
817
- oldLineNum++;
818
- newLineNum++;
819
- }
820
-
821
- if (skipEnd > 0) {
822
- output.push(` ${"".padStart(lineNumWidth, " ")} ...`);
823
- }
824
- } else {
825
- oldLineNum += raw.length;
826
- newLineNum += raw.length;
827
- }
828
-
829
- lastWasChange = false;
830
- }
831
- }
832
-
833
- return { diff: output.join("\n"), firstChangedLine };
957
+ async function generateDiffString(
958
+ oldContent: string,
959
+ newContent: string,
960
+ contextLines = 4
961
+ ): Promise<{ diff: string; firstChangedLine: number | undefined }> {
962
+ const diffModule = await import('diff');
963
+ const parts = diffModule.diffLines(oldContent, newContent);
964
+ const output: string[] = [];
965
+
966
+ const oldLines = oldContent.split('\n');
967
+ const newLines = newContent.split('\n');
968
+ const maxLineNum = Math.max(oldLines.length, newLines.length);
969
+ const lineNumWidth = String(maxLineNum).length;
970
+
971
+ let oldLineNum = 1;
972
+ let newLineNum = 1;
973
+ let lastWasChange = false;
974
+ let firstChangedLine: number | undefined;
975
+
976
+ for (let i = 0; i < parts.length; i++) {
977
+ const part = parts[i];
978
+ const raw = part.value.split('\n');
979
+ if (raw[raw.length - 1] === '') {
980
+ raw.pop();
981
+ }
982
+
983
+ if (part.added || part.removed) {
984
+ if (firstChangedLine === undefined) {
985
+ firstChangedLine = newLineNum;
986
+ }
987
+
988
+ for (const line of raw) {
989
+ if (part.added) {
990
+ const lineNum = String(newLineNum).padStart(lineNumWidth, ' ');
991
+ output.push(`+${lineNum} ${line}`);
992
+ newLineNum++;
993
+ } else {
994
+ const lineNum = String(oldLineNum).padStart(lineNumWidth, ' ');
995
+ output.push(`-${lineNum} ${line}`);
996
+ oldLineNum++;
997
+ }
998
+ }
999
+ lastWasChange = true;
1000
+ } else {
1001
+ const nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);
1002
+
1003
+ if (lastWasChange || nextPartIsChange) {
1004
+ let linesToShow = raw;
1005
+ let skipStart = 0;
1006
+ let skipEnd = 0;
1007
+
1008
+ if (!lastWasChange) {
1009
+ skipStart = Math.max(0, raw.length - contextLines);
1010
+ linesToShow = raw.slice(skipStart);
1011
+ }
1012
+
1013
+ if (!nextPartIsChange && linesToShow.length > contextLines) {
1014
+ skipEnd = linesToShow.length - contextLines;
1015
+ linesToShow = linesToShow.slice(0, contextLines);
1016
+ }
1017
+
1018
+ if (skipStart > 0) {
1019
+ output.push(` ${''.padStart(lineNumWidth, ' ')} ...`);
1020
+ oldLineNum += skipStart;
1021
+ newLineNum += skipStart;
1022
+ }
1023
+
1024
+ for (const line of linesToShow) {
1025
+ const lineNum = String(oldLineNum).padStart(lineNumWidth, ' ');
1026
+ output.push(` ${lineNum} ${line}`);
1027
+ oldLineNum++;
1028
+ newLineNum++;
1029
+ }
1030
+
1031
+ if (skipEnd > 0) {
1032
+ output.push(` ${''.padStart(lineNumWidth, ' ')} ...`);
1033
+ }
1034
+ } else {
1035
+ oldLineNum += raw.length;
1036
+ newLineNum += raw.length;
1037
+ }
1038
+
1039
+ lastWasChange = false;
1040
+ }
1041
+ }
1042
+
1043
+ return { diff: output.join('\n'), firstChangedLine };
834
1044
  }
835
1045
 
836
1046
  export class EditTool implements Tool {
@@ -888,10 +1098,12 @@ edit(
888
1098
  new_string: string;
889
1099
  }): Promise<{ success: boolean; message: string; diff?: string; firstChangedLine?: number }> {
890
1100
  const { file_path, instruction, old_string, new_string } = params;
891
-
1101
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1102
+ void instruction;
1103
+
892
1104
  try {
893
1105
  const absolutePath = path.resolve(file_path);
894
-
1106
+
895
1107
  // Check if file exists
896
1108
  try {
897
1109
  await fs.access(absolutePath);
@@ -904,7 +1116,7 @@ edit(
904
1116
 
905
1117
  // Read the file
906
1118
  const buffer = await fs.readFile(absolutePath);
907
- const rawContent = buffer.toString("utf-8");
1119
+ const rawContent = buffer.toString('utf-8');
908
1120
 
909
1121
  // Strip BOM before matching
910
1122
  const { bom, text: content } = stripBom(rawContent);
@@ -952,7 +1164,7 @@ edit(
952
1164
  }
953
1165
 
954
1166
  const finalContent = bom + restoreLineEndings(newContent, originalEnding);
955
- await fs.writeFile(absolutePath, finalContent, "utf-8");
1167
+ await fs.writeFile(absolutePath, finalContent, 'utf-8');
956
1168
 
957
1169
  const diffResult = await generateDiffString(baseContent, newContent);
958
1170
 
@@ -1000,38 +1212,43 @@ export class WebSearchTool implements Tool {
1000
1212
  - Combine with web_fetch to get full content from relevant URLs
1001
1213
  - Use quotes for exact phrase matching
1002
1214
  - Consider adding context like year or version in query`;
1003
- allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
1215
+ allowedModes = [
1216
+ ExecutionMode.YOLO,
1217
+ ExecutionMode.ACCEPT_EDITS,
1218
+ ExecutionMode.PLAN,
1219
+ ExecutionMode.SMART,
1220
+ ];
1004
1221
 
1005
1222
  async execute(params: { query: string }): Promise<{ results: any[]; message: string }> {
1006
1223
  const { query } = params;
1007
-
1224
+
1008
1225
  try {
1009
1226
  const configManager = await import('./config.js');
1010
1227
  const { getConfigManager } = configManager;
1011
1228
  const config = getConfigManager();
1012
-
1229
+
1013
1230
  const searchApiKey = config.get('searchApiKey');
1014
1231
  const baseUrl = config.get('baseUrl') || 'https://apis.xagent.cn/v1';
1015
-
1232
+
1016
1233
  if (!searchApiKey) {
1017
1234
  throw new Error('Search API key not configured. Please set searchApiKey in settings.');
1018
1235
  }
1019
-
1236
+
1020
1237
  const response = await axios.post(
1021
1238
  `${baseUrl}/search`,
1022
1239
  { query },
1023
1240
  {
1024
1241
  headers: {
1025
- 'Authorization': `Bearer ${searchApiKey}`,
1026
- 'Content-Type': 'application/json'
1242
+ Authorization: `Bearer ${searchApiKey}`,
1243
+ 'Content-Type': 'application/json',
1027
1244
  },
1028
- timeout: 30000
1245
+ timeout: 30000,
1029
1246
  }
1030
1247
  );
1031
-
1248
+
1032
1249
  return {
1033
1250
  results: response.data.results || [],
1034
- message: `Found ${response.data.results?.length || 0} results for "${query}"`
1251
+ message: `Found ${response.data.results?.length || 0} results for "${query}"`,
1035
1252
  };
1036
1253
  } catch (error: any) {
1037
1254
  throw new Error(`Web search failed: ${error.message}`);
@@ -1087,29 +1304,44 @@ Each task needs:
1087
1304
  - Don't batch multiple completions - update as you go
1088
1305
  - Keep task descriptions clear and actionable
1089
1306
  - Use appropriate priority levels to indicate urgency`;
1090
- allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
1091
-
1092
- private todoList: Array<{ id: string; task: string; status: 'pending' | 'in_progress' | 'completed' | 'failed'; priority: 'high' | 'medium' | 'low' }> = [];
1307
+ allowedModes = [
1308
+ ExecutionMode.YOLO,
1309
+ ExecutionMode.ACCEPT_EDITS,
1310
+ ExecutionMode.PLAN,
1311
+ ExecutionMode.SMART,
1312
+ ];
1313
+
1314
+ private todoList: Array<{
1315
+ id: string;
1316
+ task: string;
1317
+ status: 'pending' | 'in_progress' | 'completed' | 'failed';
1318
+ priority: 'high' | 'medium' | 'low';
1319
+ }> = [];
1093
1320
 
1094
1321
  async execute(params: {
1095
- todos: Array<{ id: string; task: string; status: 'pending' | 'in_progress' | 'completed' | 'failed'; priority: 'high' | 'medium' | 'low' }>;
1322
+ todos: Array<{
1323
+ id: string;
1324
+ task: string;
1325
+ status: 'pending' | 'in_progress' | 'completed' | 'failed';
1326
+ priority: 'high' | 'medium' | 'low';
1327
+ }>;
1096
1328
  }): Promise<{ success: boolean; message: string; todos: any[] }> {
1097
1329
  const { todos } = params;
1098
-
1330
+
1099
1331
  try {
1100
1332
  this.todoList = todos;
1101
-
1333
+
1102
1334
  const summary = {
1103
- pending: todos.filter(t => t.status === 'pending').length,
1104
- in_progress: todos.filter(t => t.status === 'in_progress').length,
1105
- completed: todos.filter(t => t.status === 'completed').length,
1106
- failed: todos.filter(t => t.status === 'failed').length
1335
+ pending: todos.filter((t) => t.status === 'pending').length,
1336
+ in_progress: todos.filter((t) => t.status === 'in_progress').length,
1337
+ completed: todos.filter((t) => t.status === 'completed').length,
1338
+ failed: todos.filter((t) => t.status === 'failed').length,
1107
1339
  };
1108
-
1340
+
1109
1341
  return {
1110
1342
  success: true,
1111
1343
  message: `Updated todo list: ${summary.pending} pending, ${summary.in_progress} in progress, ${summary.completed} completed, ${summary.failed} failed`,
1112
- todos: this.todoList
1344
+ todos: this.todoList,
1113
1345
  };
1114
1346
  } catch (error: any) {
1115
1347
  throw new Error(`Failed to update todo list: ${error.message}`);
@@ -1142,7 +1374,12 @@ export class TodoReadTool implements Tool {
1142
1374
  # Best Practices
1143
1375
  - Use todo_write to modify the list, not todo_read
1144
1376
  - Check todo_read after todo_write to verify updates`;
1145
- allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
1377
+ allowedModes = [
1378
+ ExecutionMode.YOLO,
1379
+ ExecutionMode.ACCEPT_EDITS,
1380
+ ExecutionMode.PLAN,
1381
+ ExecutionMode.SMART,
1382
+ ];
1146
1383
 
1147
1384
  private todoWriteTool: TodoWriteTool;
1148
1385
 
@@ -1153,18 +1390,18 @@ export class TodoReadTool implements Tool {
1153
1390
  async execute(): Promise<{ todos: any[]; summary: any }> {
1154
1391
  try {
1155
1392
  const todos = this.todoWriteTool.getTodos();
1156
-
1393
+
1157
1394
  const summary = {
1158
1395
  total: todos.length,
1159
- pending: todos.filter(t => t.status === 'pending').length,
1160
- in_progress: todos.filter(t => t.status === 'in_progress').length,
1161
- completed: todos.filter(t => t.status === 'completed').length,
1162
- failed: todos.filter(t => t.status === 'failed').length
1396
+ pending: todos.filter((t) => t.status === 'pending').length,
1397
+ in_progress: todos.filter((t) => t.status === 'in_progress').length,
1398
+ completed: todos.filter((t) => t.status === 'completed').length,
1399
+ failed: todos.filter((t) => t.status === 'failed').length,
1163
1400
  };
1164
-
1401
+
1165
1402
  return {
1166
1403
  todos,
1167
- summary
1404
+ summary,
1168
1405
  };
1169
1406
  } catch (error: any) {
1170
1407
  throw new Error(`Failed to read todo list: ${error.message}`);
@@ -1175,7 +1412,15 @@ export class TodoReadTool implements Tool {
1175
1412
  export interface SubAgentTask {
1176
1413
  description: string;
1177
1414
  prompt: string;
1178
- subagent_type: 'general-purpose' | 'plan-agent' | 'explore-agent' | 'frontend-tester' | 'code-reviewer' | 'frontend-developer' | 'backend-developer' | 'gui-subagent';
1415
+ subagent_type:
1416
+ | 'general-purpose'
1417
+ | 'plan-agent'
1418
+ | 'explore-agent'
1419
+ | 'frontend-tester'
1420
+ | 'code-reviewer'
1421
+ | 'frontend-developer'
1422
+ | 'backend-developer'
1423
+ | 'gui-subagent';
1179
1424
  useContext?: boolean;
1180
1425
  outputFormat?: string;
1181
1426
  constraints?: string[];
@@ -1225,39 +1470,52 @@ export class TaskTool implements Tool {
1225
1470
  - Include relevant context (file paths, requirements, constraints)
1226
1471
  - Set appropriate executionMode if needed
1227
1472
  - For parallel execution, ensure tasks are truly independent`;
1228
- allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
1229
-
1230
- async execute(params: {
1231
- description: string;
1232
- prompt?: string;
1233
- query?: string; // Support both prompt and query (tool definition uses query)
1234
- subagent_type?: 'general-purpose' | 'plan-agent' | 'explore-agent' | 'frontend-tester' | 'code-reviewer' | 'frontend-developer' | 'backend-developer' | 'gui-subagent';
1235
- agents?: SubAgentTask[];
1236
- useContext?: boolean;
1237
- outputFormat?: string;
1238
- constraints?: string[];
1239
- executionMode?: ExecutionMode;
1240
- parallel?: boolean;
1241
- }, _executionMode?: ExecutionMode): Promise<{ success: boolean; message: string; result?: any }> {
1473
+ allowedModes = [
1474
+ ExecutionMode.YOLO,
1475
+ ExecutionMode.ACCEPT_EDITS,
1476
+ ExecutionMode.PLAN,
1477
+ ExecutionMode.SMART,
1478
+ ];
1479
+
1480
+ async execute(
1481
+ params: {
1482
+ description: string;
1483
+ prompt?: string;
1484
+ query?: string; // Support both prompt and query (tool definition uses query)
1485
+ subagent_type?:
1486
+ | 'general-purpose'
1487
+ | 'plan-agent'
1488
+ | 'explore-agent'
1489
+ | 'frontend-tester'
1490
+ | 'code-reviewer'
1491
+ | 'frontend-developer'
1492
+ | 'backend-developer'
1493
+ | 'gui-subagent';
1494
+ agents?: SubAgentTask[];
1495
+ useContext?: boolean;
1496
+ outputFormat?: string;
1497
+ constraints?: string[];
1498
+ executionMode?: ExecutionMode;
1499
+ parallel?: boolean;
1500
+ },
1501
+ _executionMode?: ExecutionMode
1502
+ ): Promise<{ success: boolean; message: string; result?: any }> {
1242
1503
  const mode = params.executionMode || _executionMode || ExecutionMode.YOLO;
1243
-
1504
+
1244
1505
  try {
1245
1506
  const { getAgentManager } = await import('./agents.js');
1246
1507
  const agentManager = getAgentManager(process.cwd());
1247
-
1508
+
1248
1509
  const { getConfigManager } = await import('./config.js');
1249
1510
  const config = getConfigManager();
1250
-
1251
- const { AIClient } = await import('./ai-client.js');
1252
- const aiClient = new AIClient({
1253
- type: AuthType.OPENAI_COMPATIBLE,
1254
- apiKey: config.get('apiKey'),
1255
- baseUrl: config.get('baseUrl'),
1256
- modelName: config.get('modelName') || 'Qwen3-Coder'
1257
- });
1258
-
1511
+
1512
+ const authConfig = config.getAuthConfig();
1513
+ // aiClient is created for future use when executeParallelAgents supports it
1514
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1515
+ const _aiClient = createAIClient(authConfig);
1516
+
1259
1517
  const toolRegistry = getToolRegistry();
1260
-
1518
+
1261
1519
  if (params.agents && params.agents.length > 0) {
1262
1520
  return await this.executeParallelAgents(
1263
1521
  params.agents,
@@ -1265,10 +1523,10 @@ export class TaskTool implements Tool {
1265
1523
  mode,
1266
1524
  agentManager,
1267
1525
  toolRegistry,
1268
- aiClient
1526
+ config
1269
1527
  );
1270
1528
  }
1271
-
1529
+
1272
1530
  if (!params.subagent_type) {
1273
1531
  throw new Error('subagent_type is required for Task tool');
1274
1532
  }
@@ -1276,13 +1534,16 @@ export class TaskTool implements Tool {
1276
1534
  // Support both 'prompt' and 'query' parameter names (tool definition uses 'query')
1277
1535
  const prompt = params.prompt || params.query;
1278
1536
  if (!prompt) {
1279
- throw new Error('Task query/prompt is required. Received params: ' + JSON.stringify({
1280
- subagent_type: params.subagent_type,
1281
- prompt: params.prompt,
1282
- query: params.query,
1283
- description: params.description,
1284
- agents: params.agents?.length
1285
- }));
1537
+ throw new Error(
1538
+ 'Task query/prompt is required. Received params: ' +
1539
+ JSON.stringify({
1540
+ subagent_type: params.subagent_type,
1541
+ prompt: params.prompt,
1542
+ query: params.query,
1543
+ description: params.description,
1544
+ agents: params.agents?.length,
1545
+ })
1546
+ );
1286
1547
  }
1287
1548
 
1288
1549
  const result = await this.executeSingleAgent(
@@ -1294,10 +1555,9 @@ export class TaskTool implements Tool {
1294
1555
  mode,
1295
1556
  agentManager,
1296
1557
  toolRegistry,
1297
- aiClient,
1298
1558
  config
1299
1559
  );
1300
-
1560
+
1301
1561
  return result;
1302
1562
  } catch (error: any) {
1303
1563
  throw new Error(`Task execution failed: ${error.message}`);
@@ -1308,16 +1568,27 @@ export class TaskTool implements Tool {
1308
1568
  * Create unified VLM caller
1309
1569
  * Uses remote VLM if remoteAIClient is provided, otherwise uses local VLM
1310
1570
  * Both modes receive full messages array for consistent behavior
1571
+ * @param remoteAIClient - Remote AI client for VLM calls
1572
+ * @param taskId - Task identifier for backend tracking
1573
+ * @param localConfig - Local VLM configuration
1574
+ * @param isFirstVlmCallRef - Reference to boolean tracking if this is the first VLM call
1575
+ * @param signal - Abort signal for cancellation
1311
1576
  */
1312
1577
  private createRemoteVlmCaller(
1313
1578
  remoteAIClient: any,
1314
1579
  taskId: string | null,
1315
1580
  localConfig: { baseUrl: string; apiKey: string; modelName: string },
1581
+ isFirstVlmCallRef: { current: boolean },
1316
1582
  signal?: AbortSignal
1317
- ): (messages: any[], systemPrompt: string) => Promise<string> {
1583
+ ): (
1584
+ messages: any[],
1585
+ systemPrompt: string,
1586
+ taskId: string,
1587
+ isFirstVlmCallRef: { current: boolean }
1588
+ ) => Promise<string> {
1318
1589
  // Remote mode: use RemoteAIClient
1319
1590
  if (remoteAIClient) {
1320
- return this.createRemoteVLMCaller(remoteAIClient, taskId, signal);
1591
+ return this.createRemoteVLMCaller(remoteAIClient, taskId, isFirstVlmCallRef, signal);
1321
1592
  }
1322
1593
 
1323
1594
  // Local mode: use local API
@@ -1327,11 +1598,39 @@ export class TaskTool implements Tool {
1327
1598
  /**
1328
1599
  * Create remote VLM caller using RemoteAIClient
1329
1600
  * Now receives full messages array for consistent behavior with local mode
1601
+ * @param remoteAIClient - Remote AI client
1602
+ * @param taskId - Task identifier for backend tracking
1603
+ * @param isFirstVlmCallRef - Reference to boolean tracking if this is the first VLM call
1604
+ * @param signal - Abort signal for cancellation
1330
1605
  */
1331
- private createRemoteVLMCaller(remoteAIClient: any, taskId: string | null, signal?: AbortSignal) {
1332
- return async (messages: any[], systemPrompt: string): Promise<string> => {
1606
+ private createRemoteVLMCaller(
1607
+ remoteAIClient: any,
1608
+ taskId: string | null,
1609
+ isFirstVlmCallRef: { current: boolean },
1610
+ signal?: AbortSignal
1611
+ ): (
1612
+ messages: any[],
1613
+ systemPrompt: string,
1614
+ taskId: string,
1615
+ isFirstVlmCallRef: { current: boolean }
1616
+ ) => Promise<string> {
1617
+ return async (
1618
+ messages: any[],
1619
+ systemPrompt: string,
1620
+ _taskId: string,
1621
+ _isFirstVlmCallRef: { current: boolean }
1622
+ ): Promise<string> => {
1333
1623
  try {
1334
- return await remoteAIClient.invokeVLM(messages, systemPrompt, { signal, taskId });
1624
+ // Use the ref to track first call status for the backend
1625
+ const status = isFirstVlmCallRef.current ? 'begin' : 'continue';
1626
+ const result = await remoteAIClient.invokeVLM(messages, systemPrompt, {
1627
+ signal,
1628
+ taskId,
1629
+ status,
1630
+ });
1631
+ // Update ref after call so subsequent calls use 'continue'
1632
+ isFirstVlmCallRef.current = false;
1633
+ return result;
1335
1634
  } catch (error: any) {
1336
1635
  throw new Error(`Remote VLM call failed: ${error.message}`);
1337
1636
  }
@@ -1368,7 +1667,7 @@ export class TaskTool implements Tool {
1368
1667
  method: 'POST',
1369
1668
  headers: {
1370
1669
  'Content-Type': 'application/json',
1371
- 'Authorization': `Bearer ${apiKey}`,
1670
+ Authorization: `Bearer ${apiKey}`,
1372
1671
  },
1373
1672
  body: JSON.stringify(requestBody),
1374
1673
  signal: abortSignal,
@@ -1379,7 +1678,9 @@ export class TaskTool implements Tool {
1379
1678
  throw new Error(`VLM API error: ${errorText}`);
1380
1679
  }
1381
1680
 
1382
- const result = await response.json() as { choices?: Array<{ message?: { content?: string } }> };
1681
+ const result = (await response.json()) as {
1682
+ choices?: Array<{ message?: { content?: string } }>;
1683
+ };
1383
1684
  return result.choices?.[0]?.message?.content || '';
1384
1685
  };
1385
1686
  }
@@ -1399,32 +1700,65 @@ export class TaskTool implements Tool {
1399
1700
  ): Promise<{ success: boolean; cancelled?: boolean; message: string; result?: any }> {
1400
1701
  const indent = ' '.repeat(indentLevel);
1401
1702
 
1402
- console.log(`${indent}${colors.primaryBright(`${icons.robot} GUI Agent`)}: ${description}`);
1403
- console.log(`${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`);
1404
- console.log('');
1703
+ // Get SDK adapter from session for SDK mode output
1704
+ let sdkOutputAdapter: any = null;
1705
+ let isSdkMode = false;
1706
+ try {
1707
+ const { getSingletonSession } = await import('./session.js');
1708
+ const session = getSingletonSession();
1709
+ if (session) {
1710
+ isSdkMode = (session as any).isSdkMode;
1711
+ sdkOutputAdapter = (session as any).sdkOutputAdapter;
1712
+ }
1713
+ } catch {
1714
+ // Session not available
1715
+ }
1405
1716
 
1406
- // Get VLM configuration (used for local mode fallback)
1407
- const baseUrl = config.get('guiSubagentBaseUrl') || config.get('baseUrl') || '';
1408
- const apiKey = config.get('guiSubagentApiKey') || config.get('apiKey') || '';
1409
- const modelName = config.get('guiSubagentModel') || config.get('modelName') || '';
1717
+ // SDK mode: use adapter output (guiAgent.run() handles SDK output internally)
1718
+ // Only output console messages in non-SDK mode
1719
+ if (!isSdkMode) {
1720
+ console.log(`${indent}${colors.primaryBright(`${icons.robot} GUI Agent`)}: ${description}`);
1721
+ console.log(
1722
+ `${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`
1723
+ );
1724
+ console.log('');
1725
+ }
1726
+
1727
+ // Get VLM configuration for local mode
1728
+ // NOTE: guiSubagentBaseUrl must be explicitly configured, NOT fallback to baseUrl
1729
+ const baseUrl = config.get('guiSubagentBaseUrl') || '';
1730
+ const apiKey = config.get('guiSubagentApiKey') || '';
1731
+ const modelName = config.get('guiSubagentModel') || '';
1410
1732
 
1411
1733
  // Determine mode: remote if remoteAIClient exists, otherwise local
1412
1734
  const isRemoteMode = !!remoteAIClient;
1413
1735
 
1414
1736
  // Log mode information
1415
1737
  if (isRemoteMode) {
1416
- console.log(`${indent}${colors.info(`${icons.brain} Using remote VLM service`)}`);
1738
+ if (isSdkMode && sdkOutputAdapter) {
1739
+ // SDK mode: use adapter output
1740
+ sdkOutputAdapter.outputInfo('Using remote VLM service');
1741
+ } else {
1742
+ // Normal mode: console output
1743
+ console.log(`${indent}${colors.info(`${icons.brain} Using remote VLM service`)}`);
1744
+ }
1417
1745
  } else {
1418
- console.log(`${indent}${colors.info(`${icons.brain} Using local VLM configuration`)}`);
1419
- // Local mode requires configuration check
1420
- if (!baseUrl) {
1421
- return {
1422
- success: false,
1423
- message: `GUI task "${description}" failed: No valid API URL configured`
1424
- };
1746
+ if (isSdkMode && sdkOutputAdapter) {
1747
+ // SDK mode: use adapter output
1748
+ sdkOutputAdapter.outputInfo('Using local VLM configuration');
1749
+ } else {
1750
+ // Normal mode: console output
1751
+ console.log(`${indent}${colors.info(`${icons.brain} Using local VLM configuration`)}`);
1752
+ // Local mode requires explicit VLM configuration
1753
+ if (!baseUrl || !apiKey || !modelName) {
1754
+ return {
1755
+ success: false,
1756
+ message: `GUI task "${description}" failed: VLM not configured. Please run /model to configure Vision-Language Model first.`,
1757
+ };
1758
+ }
1759
+ console.log(`${indent}${colors.textMuted(` Model: ${modelName}`)}`);
1760
+ console.log(`${indent}${colors.textMuted(` Base URL: ${baseUrl}`)}`);
1425
1761
  }
1426
- console.log(`${indent}${colors.textMuted(` Model: ${modelName}`)}`);
1427
- console.log(`${indent}${colors.textMuted(` Base URL: ${baseUrl}`)}`);
1428
1762
  }
1429
1763
  console.log('');
1430
1764
 
@@ -1435,13 +1769,21 @@ export class TaskTool implements Tool {
1435
1769
  const { getSingletonSession } = await import('./session.js');
1436
1770
  const session = getSingletonSession();
1437
1771
  taskId = session?.getTaskId() || null;
1438
- } catch (e) {
1772
+ } catch {
1439
1773
  taskId = null;
1440
1774
  }
1441
1775
  }
1442
1776
 
1777
+ // Track first VLM call for proper status management
1778
+ const isFirstVlmCallRef = { current: true };
1779
+
1443
1780
  // Create remoteVlmCaller using the unified method (handles both local and remote modes)
1444
- const remoteVlmCaller = this.createRemoteVlmCaller(remoteAIClient, taskId, { baseUrl, apiKey, modelName });
1781
+ const remoteVlmCaller = this.createRemoteVlmCaller(
1782
+ remoteAIClient,
1783
+ taskId,
1784
+ { baseUrl, apiKey, modelName },
1785
+ isFirstVlmCallRef
1786
+ );
1445
1787
 
1446
1788
  // Set up stdin polling for ESC cancellation
1447
1789
  let rawModeEnabled = false;
@@ -1450,14 +1792,16 @@ export class TaskTool implements Tool {
1450
1792
  const logger = getLogger();
1451
1793
 
1452
1794
  const setupStdinPolling = () => {
1795
+ logger.debug(`[GUIAgent ESC] setupStdinPolling called, process.stdin.isTTY: ${process.stdin.isTTY}`);
1453
1796
  if (process.stdin.isTTY) {
1454
1797
  try {
1455
1798
  process.stdin.setRawMode(true);
1456
1799
  rawModeEnabled = true;
1457
1800
  process.stdin.resume();
1458
1801
  readline.emitKeypressEvents(process.stdin);
1459
- } catch (e) {
1460
- logger.debug(`[GUIAgent] Could not set raw mode: ${e}`);
1802
+ logger.debug(`[GUIAgent ESC] Raw mode enabled successfully`);
1803
+ } catch (e: any) {
1804
+ logger.debug(`[GUIAgent ESC] Could not set raw mode: ${e.message}`);
1461
1805
  }
1462
1806
 
1463
1807
  stdinPollingInterval = setInterval(() => {
@@ -1466,13 +1810,20 @@ export class TaskTool implements Tool {
1466
1810
  const chunk = process.stdin.read(1);
1467
1811
  if (chunk && chunk.length > 0) {
1468
1812
  const code = chunk[0];
1469
- if (code === 0x1B) { // ESC
1470
- logger.debug('[GUIAgent] ESC detected!');
1813
+ if (code === 0x1b) {
1814
+ // ESC
1815
+ logger.debug('[GUIAgent ESC Polling] ESC detected! Code: 0x1b');
1471
1816
  cancellationManager.cancel();
1817
+ } else {
1818
+ // Log other key codes for debugging
1819
+ logger.debug(`[GUIAgent ESC Polling] Key code: 0x${code.toString(16)}`);
1472
1820
  }
1473
1821
  }
1822
+ } else {
1823
+ logger.debug('[GUIAgent ESC Polling] rawModeEnabled is false');
1474
1824
  }
1475
- } catch (e) {
1825
+ } catch (e: any) {
1826
+ logger.debug(`[GUIAgent ESC Polling] Error: ${e.message}`);
1476
1827
  // Ignore polling errors
1477
1828
  }
1478
1829
  }, 10);
@@ -1494,7 +1845,9 @@ export class TaskTool implements Tool {
1494
1845
  cancellationManager.on('cancelled', cancelHandler);
1495
1846
 
1496
1847
  // Start polling for ESC
1848
+ logger.debug(`[GUIAgent ESC] About to call setupStdinPolling`);
1497
1849
  setupStdinPolling();
1850
+ logger.debug(`[GUIAgent ESC] setupStdinPolling called`);
1498
1851
 
1499
1852
  try {
1500
1853
  // Import and create GUIAgent
@@ -1504,11 +1857,15 @@ export class TaskTool implements Tool {
1504
1857
  model: !isRemoteMode ? modelName : undefined,
1505
1858
  modelBaseUrl: !isRemoteMode ? baseUrl : undefined,
1506
1859
  modelApiKey: !isRemoteMode ? apiKey : undefined,
1860
+ taskId: taskId || undefined,
1861
+ isFirstVlmCallRef,
1507
1862
  remoteVlmCaller,
1508
1863
  isLocalMode: !isRemoteMode,
1509
- maxLoopCount: 30,
1864
+ maxLoopCount: 100,
1510
1865
  loopIntervalInMs: 500,
1511
1866
  showAIDebugInfo: config.get('showAIDebugInfo') || false,
1867
+ indentLevel: indentLevel,
1868
+ sdkOutputAdapter: isSdkMode ? sdkOutputAdapter : null,
1512
1869
  });
1513
1870
 
1514
1871
  // Add constraints to prompt if any
@@ -1528,9 +1885,9 @@ export class TaskTool implements Tool {
1528
1885
  process.stdout.write('\n');
1529
1886
  return {
1530
1887
  success: true,
1531
- cancelled: true, // Mark as cancelled so main agent won't continue
1888
+ cancelled: true, // Mark as cancelled so main agent won't continue
1532
1889
  message: `GUI task "${description}" cancelled by user`,
1533
- result: 'Task cancelled'
1890
+ result: 'Task cancelled',
1534
1891
  };
1535
1892
  }
1536
1893
 
@@ -1541,26 +1898,106 @@ export class TaskTool implements Tool {
1541
1898
  process.stdout.write('\n');
1542
1899
 
1543
1900
  // Return result based on GUIAgent status
1901
+ // Always return all info except screenshots (base64) to avoid huge payload
1902
+ const conversationsWithoutScreenshots = result.conversations.map((conv: any) => ({
1903
+ ...conv,
1904
+ screenshotBase64: undefined, // Remove screenshots to avoid huge payload
1905
+ }));
1906
+
1544
1907
  if (result.status === 'end') {
1545
- const iterations = result.conversations.filter(c => c.from === 'human' && c.screenshotBase64).length;
1546
- console.log(`${indent}${colors.success(`${icons.check} GUI task completed in ${iterations} iterations`)}`);
1908
+ const iterations = conversationsWithoutScreenshots.filter(
1909
+ (c: any) => c.from === 'human' && c.screenshotContext
1910
+ ).length;
1911
+ // SDK mode: use adapter output
1912
+ if (isSdkMode && sdkOutputAdapter) {
1913
+ sdkOutputAdapter.outputGUIAgentComplete(description, iterations);
1914
+ } else {
1915
+ // Normal mode: console output
1916
+ console.log(
1917
+ `${indent}${colors.success(`${icons.check} GUI task completed in ${iterations} iterations`)}`
1918
+ );
1919
+ }
1547
1920
  return {
1548
1921
  success: true,
1549
1922
  message: `GUI task "${description}" completed`,
1550
- result: `Completed in ${iterations} iterations`
1923
+ result: {
1924
+ status: result.status,
1925
+ iterations,
1926
+ actions: conversationsWithoutScreenshots
1927
+ .filter((c: any) => c.from === 'assistant' && c.actionType)
1928
+ .map((c: any) => c.actionType),
1929
+ conversations: conversationsWithoutScreenshots,
1930
+ error: result.error,
1931
+ },
1932
+ };
1933
+ } else if (result.status === 'call_llm') {
1934
+ // SDK mode: use adapter output
1935
+ if (isSdkMode && sdkOutputAdapter) {
1936
+ sdkOutputAdapter.outputGUIAgentStatus('call_llm', conversationsWithoutScreenshots.filter(
1937
+ (c: any) => c.from === 'human' && c.screenshotContext
1938
+ ).length);
1939
+ } else {
1940
+ // Normal mode: console output
1941
+ console.log(
1942
+ `${indent}${colors.warning(`${icons.warning} GUI agent returned to main agent for LLM decision`)}`
1943
+ );
1944
+ }
1945
+ return {
1946
+ success: true,
1947
+ message: `GUI task "${description}" returned for LLM decision`,
1948
+ result: {
1949
+ status: result.status,
1950
+ iterations: conversationsWithoutScreenshots.filter(
1951
+ (c: any) => c.from === 'human' && c.screenshotContext
1952
+ ).length,
1953
+ actions: conversationsWithoutScreenshots
1954
+ .filter((c: any) => c.from === 'assistant' && c.actionType)
1955
+ .map((c: any) => c.actionType),
1956
+ conversations: conversationsWithoutScreenshots,
1957
+ error: result.error,
1958
+ },
1551
1959
  };
1552
1960
  } else if (result.status === 'user_stopped') {
1961
+ // SDK mode: use adapter output
1962
+ if (isSdkMode && sdkOutputAdapter) {
1963
+ sdkOutputAdapter.outputGUIAgentCancelled(description);
1964
+ }
1553
1965
  return {
1554
1966
  success: true,
1555
1967
  message: `GUI task "${description}" stopped by user`,
1556
- result: 'User stopped'
1968
+ result: {
1969
+ status: result.status,
1970
+ iterations: conversationsWithoutScreenshots.filter(
1971
+ (c: any) => c.from === 'human' && c.screenshotContext
1972
+ ).length,
1973
+ actions: conversationsWithoutScreenshots
1974
+ .filter((c: any) => c.from === 'assistant' && c.actionType)
1975
+ .map((c: any) => c.actionType),
1976
+ conversations: conversationsWithoutScreenshots,
1977
+ stopped: true,
1978
+ },
1557
1979
  };
1558
1980
  } else {
1559
1981
  // status is 'error' or other non-success status
1560
1982
  const errorMsg = result.error || 'Unknown error';
1983
+ // SDK mode: use adapter output
1984
+ if (isSdkMode && sdkOutputAdapter) {
1985
+ sdkOutputAdapter.outputGUIAgentError(description, errorMsg);
1986
+ }
1561
1987
  return {
1562
1988
  success: false,
1563
- message: `GUI task "${description}" failed: ${errorMsg}`
1989
+ message: `GUI task "${description}" failed: ${errorMsg}`,
1990
+ result: {
1991
+ status: result.status,
1992
+ iterations: conversationsWithoutScreenshots.filter(
1993
+ (c: any) => c.from === 'human' && c.screenshotContext
1994
+ ).length,
1995
+ actions: conversationsWithoutScreenshots
1996
+ .filter((c: any) => c.from === 'assistant' && c.actionType)
1997
+ .map((c: any) => c.actionType),
1998
+ conversations: conversationsWithoutScreenshots,
1999
+ error: result.error,
2000
+ },
1564
2001
  };
1565
2002
  }
1566
2003
  } catch (error: any) {
@@ -1573,26 +2010,38 @@ export class TaskTool implements Tool {
1573
2010
  // If the user cancelled the task, ignore any API errors (like 429)
1574
2011
  // and return cancelled status instead
1575
2012
  if (cancelled || cancellationManager.isOperationCancelled()) {
2013
+ // SDK mode: use adapter output
2014
+ if (isSdkMode && sdkOutputAdapter) {
2015
+ sdkOutputAdapter.outputGUIAgentCancelled(description);
2016
+ }
1576
2017
  return {
1577
2018
  success: true,
1578
- cancelled: true, // Mark as cancelled so main agent won't continue
2019
+ cancelled: true, // Mark as cancelled so main agent won't continue
1579
2020
  message: `GUI task "${description}" cancelled by user`,
1580
- result: 'Task cancelled'
2021
+ result: 'Task cancelled',
1581
2022
  };
1582
2023
  }
1583
2024
 
1584
2025
  if (error.message === 'Operation cancelled by user') {
2026
+ // SDK mode: use adapter output
2027
+ if (isSdkMode && sdkOutputAdapter) {
2028
+ sdkOutputAdapter.outputGUIAgentCancelled(description);
2029
+ }
1585
2030
  return {
1586
2031
  success: true,
1587
2032
  message: `GUI task "${description}" cancelled by user`,
1588
- result: 'Task cancelled'
2033
+ result: 'Task cancelled',
1589
2034
  };
1590
2035
  }
1591
2036
 
1592
2037
  // Return failure without throwing - let the main agent handle it
2038
+ // SDK mode: use adapter output
2039
+ if (isSdkMode && sdkOutputAdapter) {
2040
+ sdkOutputAdapter.outputGUIAgentError(description, error.message);
2041
+ }
1593
2042
  return {
1594
2043
  success: false,
1595
- message: `GUI task "${description}" failed: ${error.message}`
2044
+ message: `GUI task "${description}" failed: ${error.message}`,
1596
2045
  };
1597
2046
  }
1598
2047
  }
@@ -1606,7 +2055,6 @@ export class TaskTool implements Tool {
1606
2055
  mode: ExecutionMode,
1607
2056
  agentManager: any,
1608
2057
  toolRegistry: any,
1609
- aiClient: any,
1610
2058
  config: any,
1611
2059
  indentLevel: number = 1
1612
2060
  ): Promise<{ success: boolean; message: string; result?: any }> {
@@ -1616,6 +2064,20 @@ export class TaskTool implements Tool {
1616
2064
  throw new Error(`Agent ${subagent_type} not found`);
1617
2065
  }
1618
2066
 
2067
+ // Get SDK adapter from session for subagent output
2068
+ let sdkOutputAdapter: any = null;
2069
+ let isSdkMode = false;
2070
+ try {
2071
+ const { getSingletonSession } = await import('./session.js');
2072
+ const session = getSingletonSession();
2073
+ if (session) {
2074
+ isSdkMode = (session as any).isSdkMode;
2075
+ sdkOutputAdapter = (session as any).sdkOutputAdapter;
2076
+ }
2077
+ } catch {
2078
+ // Session not available
2079
+ }
2080
+
1619
2081
  // Special handling for gui-subagent: directly call GUIAgent.run() instead of subagent message loop
1620
2082
  if (subagent_type === 'gui-subagent') {
1621
2083
  // Get RemoteAIClient instance from session (if available)
@@ -1626,7 +2088,7 @@ export class TaskTool implements Tool {
1626
2088
  if (session) {
1627
2089
  remoteAIClient = session.getRemoteAIClient();
1628
2090
  }
1629
- } catch (e) {
2091
+ } catch {
1630
2092
  // Session not available, keep undefined
1631
2093
  remoteAIClient = undefined;
1632
2094
  }
@@ -1646,7 +2108,7 @@ export class TaskTool implements Tool {
1646
2108
  let modelName = config.get('modelName') || 'Qwen3-Coder';
1647
2109
  let baseUrl = config.get('baseUrl') || 'https://apis.xagent.cn/v1';
1648
2110
  let apiKey = config.get('apiKey') || '';
1649
-
2111
+
1650
2112
  if (agent.model) {
1651
2113
  // If agent has a model field, it can be a model name or a config reference like 'guiSubagentModel'
1652
2114
  if (typeof agent.model === 'string' && agent.model.endsWith('Model')) {
@@ -1666,41 +2128,71 @@ export class TaskTool implements Tool {
1666
2128
  }
1667
2129
  }
1668
2130
 
1669
- // Create a new AIClient for this subagent with its specific model
1670
- const { AIClient: SubAgentAIClient } = await import('./ai-client.js');
1671
- const subAgentClient = new SubAgentAIClient({
1672
- type: AuthType.OPENAI_COMPATIBLE,
1673
- apiKey: apiKey,
1674
- baseUrl: baseUrl,
1675
- modelName: modelName,
1676
- showAIDebugInfo: config.get('showAIDebugInfo') || false
1677
- });
1678
-
2131
+ // Create AI client for this subagent - each subagent gets its own independent client
2132
+ let subAgentClient;
2133
+ let isRemoteMode = false;
2134
+ let mainTaskId: string | null = null;
2135
+ const authConfig = config.getAuthConfig();
2136
+
2137
+ if (authConfig.type === AuthType.OAUTH_XAGENT) {
2138
+ // Remote mode: create independent RemoteAIClient for each subagent
2139
+ // This prevents message queue conflicts when multiple subagents run in parallel
2140
+ const session = getSingletonSession();
2141
+ const remoteAIClient = session?.getRemoteAIClient();
2142
+
2143
+ if (remoteAIClient) {
2144
+ // Clone or create independent client for this subagent
2145
+ // RemoteAIClient should be designed to handle concurrent requests
2146
+ subAgentClient = remoteAIClient;
2147
+ isRemoteMode = true;
2148
+ mainTaskId = session?.getTaskId() || null;
2149
+ } else {
2150
+ subAgentClient = createAIClient(authConfig);
2151
+ }
2152
+ } else {
2153
+ // Local mode: create client with subagent-specific model config
2154
+ const subAuthConfig = {
2155
+ ...authConfig,
2156
+ type: AuthType.OPENAI_COMPATIBLE,
2157
+ apiKey: apiKey,
2158
+ baseUrl: baseUrl,
2159
+ modelName: modelName,
2160
+ showAIDebugInfo: config.get('showAIDebugInfo') || false,
2161
+ };
2162
+ subAgentClient = createAIClient(subAuthConfig);
2163
+ }
2164
+
1679
2165
  const indent = ' '.repeat(indentLevel);
1680
- const indentNext = ' '.repeat(indentLevel + 1);
2166
+ const _indentNext = ' '.repeat(indentLevel + 1);
1681
2167
  const agentName = agent.name || subagent_type;
1682
2168
 
1683
2169
  // Track execution history for better reporting to main agent
1684
2170
  const executionHistory: Array<{
1685
2171
  tool: string;
1686
2172
  status: 'success' | 'error';
1687
- params: any; // 工具调用参数
1688
- result?: any; // 工具执行结果(成功时)
1689
- error?: string; // 错误信息(失败时)
2173
+ params: any; // 工具调用参数
2174
+ result?: any; // 工具执行结果(成功时)
2175
+ error?: string; // 错误信息(失败时)
1690
2176
  timestamp: string;
1691
2177
  }> = [];
1692
2178
 
1693
2179
  // Helper function to indent multi-line content
1694
2180
  const indentMultiline = (content: string, baseIndent: string): string => {
1695
- return content.split('\n').map(line => `${baseIndent} ${line}`).join('\n');
2181
+ return content
2182
+ .split('\n')
2183
+ .map((line) => `${baseIndent} ${line}`)
2184
+ .join('\n');
1696
2185
  };
1697
-
2186
+
1698
2187
  const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, mode, agent);
1699
- const enhancedSystemPrompt = await systemPromptGenerator.generateEnhancedSystemPrompt(agent.systemPrompt);
1700
-
1701
- const fullPrompt = constraints.length > 0
1702
- ? `${prompt}\n\nConstraints:\n${constraints.map(c => `- ${c}`).join('\n')}`
1703
- : prompt;
2188
+ const enhancedSystemPrompt = await systemPromptGenerator.generateEnhancedSystemPrompt(
2189
+ agent.systemPrompt
2190
+ );
2191
+
2192
+ const fullPrompt =
2193
+ constraints.length > 0
2194
+ ? `${prompt}\n\nConstraints:\n${constraints.map((c) => `- ${c}`).join('\n')}`
2195
+ : prompt;
1704
2196
 
1705
2197
  // Set up raw mode and stdin polling for ESC detection
1706
2198
  const cancellationManager = getCancellationManager();
@@ -1728,13 +2220,14 @@ export class TaskTool implements Tool {
1728
2220
  const chunk = process.stdin.read(1);
1729
2221
  if (chunk && chunk.length > 0) {
1730
2222
  const code = chunk[0];
1731
- if (code === 0x1B) { // ESC
2223
+ if (code === 0x1b) {
2224
+ // ESC
1732
2225
  logger.debug('[TaskTool] ESC detected via polling!');
1733
2226
  cancellationManager.cancel();
1734
2227
  }
1735
2228
  }
1736
2229
  }
1737
- } catch (e) {
2230
+ } catch {
1738
2231
  // Ignore polling errors
1739
2232
  }
1740
2233
  }, 10);
@@ -1765,15 +2258,15 @@ export class TaskTool implements Tool {
1765
2258
  throw new Error('Operation cancelled by user');
1766
2259
  }
1767
2260
  };
1768
-
1769
- let messages: Message[] = [
2261
+
2262
+ const messages: Message[] = [
1770
2263
  { role: 'system', content: enhancedSystemPrompt },
1771
- { role: 'user', content: fullPrompt }
2264
+ { role: 'user', content: fullPrompt },
1772
2265
  ];
1773
-
2266
+
1774
2267
  const availableTools = agentManager.getAvailableToolsForAgent(agent, mode);
1775
2268
  const allToolDefinitions = toolRegistry.getToolDefinitions();
1776
-
2269
+
1777
2270
  const toolDefinitions: ToolDefinition[] = availableTools.map((toolName: string) => {
1778
2271
  const fullDef = allToolDefinitions.find((def: any) => def.function.name === toolName);
1779
2272
  if (fullDef) {
@@ -1784,28 +2277,43 @@ export class TaskTool implements Tool {
1784
2277
  function: {
1785
2278
  name: toolName,
1786
2279
  description: `Tool: ${toolName}`,
1787
- parameters: { type: 'object', properties: {}, required: [] }
1788
- }
2280
+ parameters: { type: 'object', properties: {}, required: [] },
2281
+ },
1789
2282
  };
1790
2283
  });
1791
2284
 
1792
2285
  let iteration = 0;
1793
- const maxIterations = 10;
2286
+ let lastContentStr = ''; // Track last content for final result
1794
2287
 
1795
- while (iteration < maxIterations) {
2288
+ // Main agent style loop: continue until AI returns no more tool_calls
2289
+ // eslint-disable-next-line no-constant-condition
2290
+ while (true) {
1796
2291
  iteration++;
1797
-
2292
+
1798
2293
  // Check for cancellation before each iteration
1799
2294
  checkCancellation();
1800
-
2295
+
2296
+ // Prepare chat options with taskId and model names for remote mode
2297
+ const chatOptions: any = {
2298
+ tools: toolDefinitions,
2299
+ temperature: 0.7,
2300
+ };
2301
+
2302
+ // Pass taskId, status, and model names for remote mode subagent calls
2303
+ // Subagent shares the same taskId as the main task
2304
+ if (isRemoteMode && mainTaskId) {
2305
+ chatOptions.taskId = mainTaskId;
2306
+ chatOptions.status = iteration === 1 ? 'begin' : 'continue';
2307
+ // Pass model names to ensure subagent uses the same models as main task
2308
+ chatOptions.llmModelName = config.get('remote_llmModelName');
2309
+ chatOptions.vlmModelName = config.get('remote_vlmModelName');
2310
+ }
2311
+
1801
2312
  // Use withCancellation to make API call cancellable
1802
- const result = await cancellationManager.withCancellation(
1803
- subAgentClient.chatCompletion(messages, {
1804
- tools: toolDefinitions,
1805
- temperature: 0.7
1806
- }),
2313
+ const result = (await cancellationManager.withCancellation(
2314
+ subAgentClient.chatCompletion(messages, chatOptions),
1807
2315
  `api-${subagent_type}-${iteration}`
1808
- ) as any;
2316
+ )) as any;
1809
2317
 
1810
2318
  // Check for cancellation after API call
1811
2319
  checkCancellation();
@@ -1827,8 +2335,8 @@ export class TaskTool implements Tool {
1827
2335
  hasValidContent = messageContent.trim() !== '';
1828
2336
  } else if (Array.isArray(messageContent)) {
1829
2337
  const textParts = messageContent
1830
- .filter(item => typeof item?.text === 'string' && item.text.trim() !== '')
1831
- .map(item => item.text);
2338
+ .filter((item) => typeof item?.text === 'string' && item.text.trim() !== '')
2339
+ .map((item) => item.text);
1832
2340
  contentStr = textParts.join('');
1833
2341
  hasValidContent = textParts.length > 0;
1834
2342
  } else {
@@ -1846,143 +2354,87 @@ export class TaskTool implements Tool {
1846
2354
  throw new Error(`Sub-agent ${subagent_type} response truncated due to length limits`);
1847
2355
  }
1848
2356
 
1849
- // Add assistant message to conversation
1850
- messages.push({ role: 'assistant', content: contentStr });
2357
+ // Add assistant message to conversation (必须包含 tool_calls,否则 tool_result 无法匹配)
2358
+ const assistantMessage: any = { role: 'assistant', content: contentStr };
2359
+ if (toolCalls && toolCalls.length > 0) {
2360
+ assistantMessage.tool_calls = toolCalls;
2361
+ }
2362
+ if (reasoningContent) {
2363
+ assistantMessage.reasoning_content = reasoningContent;
2364
+ }
2365
+ messages.push(assistantMessage as Message);
1851
2366
 
1852
2367
  // Display reasoning content if present
1853
2368
  if (reasoningContent) {
1854
- console.log(`\n${indent}${colors.textDim(`${icons.brain} Thinking Process:`)}`);
1855
- const truncatedReasoning = reasoningContent.length > 500 ? reasoningContent.substring(0, 500) + '...' : reasoningContent;
1856
- const indentedReasoning = indentMultiline(truncatedReasoning, indent);
1857
- console.log(`${indentedReasoning}\n`);
2369
+ if (isSdkMode && sdkOutputAdapter) {
2370
+ sdkOutputAdapter.outputThinking(reasoningContent, 'compact');
2371
+ } else {
2372
+ console.log(`\n${indent}${colors.textDim(`${icons.brain} Thinking Process:`)}`);
2373
+ const truncatedReasoning =
2374
+ reasoningContent.length > 500
2375
+ ? reasoningContent.substring(0, 500) + '...'
2376
+ : reasoningContent;
2377
+ const indentedReasoning = indentMultiline(truncatedReasoning, indent);
2378
+ console.log(`${indentedReasoning}\n`);
2379
+ }
1858
2380
  }
1859
2381
 
1860
2382
  // Display assistant response (if there's any text content) with proper indentation
1861
2383
  if (contentStr) {
1862
- console.log(`\n${indent}${colors.primaryBright(agentName)}: ${description}`);
1863
- const truncatedContent = contentStr.length > 500 ? contentStr.substring(0, 500) + '...' : contentStr;
1864
- const indentedContent = indentMultiline(truncatedContent, indent);
1865
- console.log(`${indentedContent}\n`);
2384
+ if (isSdkMode && sdkOutputAdapter) {
2385
+ sdkOutputAdapter.outputAssistant(contentStr);
2386
+ } else {
2387
+ console.log(`\n${indent}${colors.primaryBright(agentName)}: ${description}`);
2388
+ const truncatedContent =
2389
+ contentStr.length > 500 ? contentStr.substring(0, 500) + '...' : contentStr;
2390
+ const indentedContent = indentMultiline(truncatedContent, indent);
2391
+ console.log(`${indentedContent}\n`);
2392
+ }
1866
2393
  }
1867
2394
 
1868
- // Process tool calls with proper indentation
2395
+ // Process tool calls in parallel (照搬 session 的实现)
1869
2396
  if (toolCalls && toolCalls.length > 0) {
1870
- for (const toolCall of toolCalls) {
2397
+ // Prepare all tool calls with their indices
2398
+ const preparedToolCalls = toolCalls.map((toolCall: any, index: number) => {
1871
2399
  const { name, arguments: params } = toolCall.function;
1872
-
1873
2400
  let parsedParams: any;
1874
2401
  try {
1875
2402
  parsedParams = typeof params === 'string' ? JSON.parse(params) : params;
1876
- } catch (e) {
2403
+ } catch {
1877
2404
  parsedParams = params;
1878
2405
  }
2406
+ return { name, params: parsedParams, id: toolCall.id, index };
2407
+ });
1879
2408
 
1880
- console.log(`${indent}${colors.textMuted(`${icons.loading} Tool: ${name}`)}`);
2409
+ // Display all tool call info first
2410
+ for (const tc of preparedToolCalls as Array<{ name: string; params: any; id: string }>) {
2411
+ if (isSdkMode && sdkOutputAdapter) {
2412
+ sdkOutputAdapter.outputToolStart(tc.name, tc.params);
2413
+ } else {
2414
+ console.log(`${indent}${colors.textMuted(`${icons.loading} Tool: ${tc.name}`)}`);
2415
+ }
2416
+ }
1881
2417
 
2418
+ // Execute all tool calls in parallel
2419
+ const executePromises = preparedToolCalls.map(async (tc: { name: string; params: any; id: string; index: number }) => {
1882
2420
  try {
1883
2421
  // Check cancellation before tool execution
1884
2422
  checkCancellation();
1885
-
2423
+
1886
2424
  const toolResult: any = await cancellationManager.withCancellation(
1887
- toolRegistry.execute(name, parsedParams, mode, indent),
1888
- `subagent-${subagent_type}-${name}-${iteration}`
2425
+ toolRegistry.execute(tc.name, tc.params, mode, indent),
2426
+ `subagent-${subagent_type}-${tc.name}-${iteration}`
1889
2427
  );
1890
-
1891
- // Get showToolDetails config to control result display
1892
- const showToolDetails = config.get('showToolDetails') || false;
1893
-
1894
- // Prepare result preview for history
1895
- const resultPreview = typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult, null, 2);
1896
- const truncatedPreview = resultPreview.length > 200 ? resultPreview.substring(0, 200) + '...' : resultPreview;
1897
-
1898
- // Special handling for different tools (consistent with session.ts display logic)
1899
- const isTodoTool = name === 'todo_write' || name === 'todo_read';
1900
- const isEditTool = name === 'Edit';
1901
- const isWriteTool = name === 'Write';
1902
- const isDeleteTool = name === 'DeleteFile';
1903
- const hasDiff = isEditTool && toolResult?.diff;
1904
- const hasFilePreview = isWriteTool && toolResult?.preview;
1905
- const hasDeleteInfo = isDeleteTool && toolResult?.filePath;
1906
-
1907
- // Import render functions for consistent display
1908
- const { renderDiff, renderLines } = await import('./theme.js');
1909
-
1910
- if (isTodoTool) {
1911
- // Display todo list
1912
- console.log(`${indent}${colors.success(`${icons.check} Todo List:`)}`);
1913
- const todos = toolResult?.todos || [];
1914
- if (todos.length === 0) {
1915
- console.log(`${indent} ${colors.textMuted('No tasks')}`);
1916
- } else {
1917
- const statusConfig: Record<string, { icon: string; color: (text: string) => string; label: string }> = {
1918
- 'pending': { icon: icons.circle, color: colors.textMuted, label: 'Pending' },
1919
- 'in_progress': { icon: icons.loading, color: colors.warning, label: 'In Progress' },
1920
- 'completed': { icon: icons.success, color: colors.success, label: 'Completed' },
1921
- 'failed': { icon: icons.error, color: colors.error, label: 'Failed' }
1922
- };
1923
- for (const todo of todos) {
1924
- const status = statusConfig[todo.status] || statusConfig['pending'];
1925
- console.log(`${indent} ${status.color(status.icon)} ${status.color(status.label)}: ${colors.text(todo.task)}`);
1926
- }
1927
- }
1928
- if (toolResult?.message) {
1929
- console.log(`${indent}${colors.textDim(toolResult.message)}`);
1930
- }
1931
- console.log('');
1932
- } else if (hasDiff) {
1933
- // Display edit result with diff
1934
- console.log('');
1935
- const diffOutput = renderDiff(toolResult.diff);
1936
- const indentedDiff = diffOutput.split('\n').map(line => `${indent} ${line}`).join('\n');
1937
- console.log(`${indentedDiff}\n`);
1938
- } else if (hasFilePreview) {
1939
- // Display new file content in preview style
1940
- console.log('');
1941
- console.log(`${indent}${colors.success(`${icons.file} ${toolResult.filePath}`)}`);
1942
- console.log(`${indent}${colors.textDim(` ${toolResult.lineCount} lines`)}`);
1943
- console.log('');
1944
- console.log(renderLines(toolResult.preview, { maxLines: 10, indent: indent + ' ' }));
1945
- console.log('');
1946
- } else if (hasDeleteInfo) {
1947
- // Display DeleteFile result
1948
- console.log('');
1949
- console.log(`${indent}${colors.success(`${icons.check} Deleted: ${toolResult.filePath}`)}`);
1950
- console.log('');
1951
- } else if (showToolDetails) {
1952
- // Show full result details
1953
- const indentedPreview = indentMultiline(resultPreview, indent);
1954
- console.log(`${indent}${colors.success(`${icons.check} Tool Result:`)}\n${indentedPreview}\n`);
1955
- } else if (toolResult && toolResult.success === false) {
1956
- // Tool failed
1957
- console.log(`${indent}${colors.error(`${icons.cross} ${toolResult.message || 'Failed'}`)}\n`);
1958
- } else if (toolResult) {
1959
- // Show brief preview by default
1960
- const indentedPreview = indentMultiline(truncatedPreview, indent);
1961
- console.log(`${indent}${colors.success(`${icons.check} Completed`)}\n${indentedPreview}\n`);
1962
- } else {
1963
- console.log(`${indent}${colors.textDim('(no result)')}\n`);
1964
- }
1965
-
1966
- // Record successful tool execution in history (use truncated preview to save memory)
1967
- executionHistory.push({
1968
- tool: name,
1969
- status: 'success',
1970
- params: parsedParams,
1971
- result: truncatedPreview,
1972
- timestamp: new Date().toISOString()
1973
- });
1974
-
1975
- messages.push({
1976
- role: 'tool',
1977
- content: JSON.stringify(toolResult),
1978
- tool_call_id: toolCall.id
1979
- });
2428
+ return { ...tc, toolResult, error: undefined };
1980
2429
  } catch (error: any) {
1981
2430
  if (error.message === 'Operation cancelled by user') {
1982
- console.log(`${indent}${colors.warning(`⚠️ Operation cancelled`)}\n`);
2431
+ if (!isSdkMode || !sdkOutputAdapter) {
2432
+ console.log(`${indent}${colors.warning(`⚠️ Operation cancelled`)}\n`);
2433
+ }
1983
2434
  cancellationManager.off('cancelled', cancelHandler);
1984
2435
  cleanupStdinPolling();
1985
- const summaryPreview = contentStr.length > 300 ? contentStr.substring(0, 300) + '...' : contentStr;
2436
+ const summaryPreview =
2437
+ contentStr.length > 300 ? contentStr.substring(0, 300) + '...' : contentStr;
1986
2438
  return {
1987
2439
  success: false,
1988
2440
  message: `Task "${description}" cancelled by user`,
@@ -1991,92 +2443,232 @@ export class TaskTool implements Tool {
1991
2443
  executionHistory: {
1992
2444
  totalIterations: iteration,
1993
2445
  toolsExecuted: executionHistory.length,
1994
- successfulTools: executionHistory.filter(t => t.status === 'success').length,
1995
- failedTools: executionHistory.filter(t => t.status === 'error').length,
2446
+ successfulTools: executionHistory.filter((t) => t.status === 'success').length,
2447
+ failedTools: executionHistory.filter((t) => t.status === 'error').length,
1996
2448
  history: executionHistory,
1997
- cancelled: true
2449
+ cancelled: true,
2450
+ },
2451
+ },
2452
+ };
2453
+ }
2454
+ return { ...tc, toolResult: undefined, error: error.message };
2455
+ }
2456
+ });
2457
+
2458
+ const settledResults = await Promise.all(executePromises);
2459
+
2460
+ // Check for cancellation in results
2461
+ const cancellationResult = settledResults.find(
2462
+ (r): r is { success: boolean; message: string; result: any } =>
2463
+ 'success' in r && r.success === false
2464
+ );
2465
+ if (cancellationResult) {
2466
+ return cancellationResult;
2467
+ }
2468
+
2469
+ // Create a map to store results by tool call index to maintain original order (match session implementation)
2470
+ type ToolResultType = { name: string; params: any; toolResult: any; error?: string; id: string; index: number };
2471
+ const resultsByIndex = new Map<number, ToolResultType>();
2472
+ const usedIndices = new Set<number>();
2473
+
2474
+ for (const result of settledResults as unknown as ToolResultType[]) {
2475
+ // Find the first unused original index that matches the tool name
2476
+ const originalIndex = preparedToolCalls.findIndex((tc: { name: string }, idx: number) =>
2477
+ tc.name === result.name && !usedIndices.has(idx)
2478
+ );
2479
+ if (originalIndex !== -1) {
2480
+ usedIndices.add(originalIndex);
2481
+ resultsByIndex.set(originalIndex, result);
2482
+ }
2483
+ }
2484
+
2485
+ // Import render functions for consistent display
2486
+ const { renderDiff, renderLines } = await import('./theme.js');
2487
+
2488
+ // Process results in the original tool_calls order
2489
+ for (let i = 0; i < preparedToolCalls.length; i++) {
2490
+ const result = resultsByIndex.get(i);
2491
+ if (!result) continue;
2492
+
2493
+ const { name, params: parsedParams, toolResult, error } = result;
2494
+
2495
+ // Get showToolDetails config to control result display
2496
+ const showToolDetails = config.get('showToolDetails') || false;
2497
+
2498
+ // Prepare result preview for history
2499
+ const resultPreview =
2500
+ typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult, null, 2);
2501
+ const truncatedPreview =
2502
+ resultPreview.length > 200 ? resultPreview.substring(0, 200) + '...' : resultPreview;
2503
+
2504
+ if (error) {
2505
+ // Handle error case
2506
+ if (isSdkMode && sdkOutputAdapter) {
2507
+ sdkOutputAdapter.outputToolError(name, error);
2508
+ } else {
2509
+ console.log(`${indent}${colors.error(`${icons.cross} Error:`)} ${error}\n`);
2510
+ }
2511
+
2512
+ // Record failed tool execution in history
2513
+ executionHistory.push({
2514
+ tool: name,
2515
+ status: 'error',
2516
+ params: parsedParams,
2517
+ error,
2518
+ timestamp: new Date().toISOString(),
2519
+ });
2520
+
2521
+ messages.push({
2522
+ role: 'tool',
2523
+ content: JSON.stringify({ error }),
2524
+ tool_call_id: result.id,
2525
+ });
2526
+ } else {
2527
+ // Handle success case - display result
2528
+ // SDK mode: output tool result via adapter
2529
+ if (isSdkMode && sdkOutputAdapter) {
2530
+ sdkOutputAdapter.outputToolResult(name, toolResult);
2531
+ }
2532
+ // Normal mode: console output (SDK mode already output via adapter above)
2533
+ if (!isSdkMode || !sdkOutputAdapter) {
2534
+ // Special handling for different tools (consistent with session.ts display logic)
2535
+ const isTodoTool = name === 'todo_write' || name === 'todo_read';
2536
+ const isEditTool = name === 'Edit';
2537
+ const isWriteTool = name === 'Write';
2538
+ const isDeleteTool = name === 'DeleteFile';
2539
+ const hasDiff = isEditTool && toolResult?.diff;
2540
+ const hasFilePreview = isWriteTool && toolResult?.preview;
2541
+ const hasDeleteInfo = isDeleteTool && toolResult?.filePath;
2542
+
2543
+ if (isTodoTool) {
2544
+ // Display todo list
2545
+ console.log(`${indent}${colors.success(`${icons.check} Todo List:`)}`);
2546
+ const todos = toolResult?.todos || [];
2547
+ if (todos.length === 0) {
2548
+ console.log(`${indent} ${colors.textMuted('No tasks')}`);
2549
+ } else {
2550
+ const statusConfig: Record<
2551
+ string,
2552
+ { icon: string; color: (text: string) => string; label: string }
2553
+ > = {
2554
+ pending: { icon: icons.circle, color: colors.textMuted, label: 'Pending' },
2555
+ in_progress: { icon: icons.loading, color: colors.warning, label: 'In Progress' },
2556
+ completed: { icon: icons.success, color: colors.success, label: 'Completed' },
2557
+ failed: { icon: icons.error, color: colors.error, label: 'Failed' },
2558
+ };
2559
+ for (const todo of todos) {
2560
+ const status = statusConfig[todo.status] || statusConfig['pending'];
2561
+ console.log(
2562
+ `${indent} ${status.color(status.icon)} ${status.color(status.label)}: ${colors.text(todo.task)}`
2563
+ );
1998
2564
  }
1999
2565
  }
2000
- };
2566
+ if (toolResult?.message) {
2567
+ console.log(`${indent}${colors.textDim(toolResult.message)}`);
2568
+ }
2569
+ console.log('');
2570
+ } else if (hasDiff) {
2571
+ // Display edit result with diff
2572
+ console.log('');
2573
+ const diffOutput = renderDiff(toolResult.diff);
2574
+ const indentedDiff = diffOutput
2575
+ .split('\n')
2576
+ .map((line) => `${indent} ${line}`)
2577
+ .join('\n');
2578
+ console.log(`${indentedDiff}\n`);
2579
+ } else if (hasFilePreview) {
2580
+ // Display new file content in preview style
2581
+ console.log('');
2582
+ console.log(`${indent}${colors.success(`${icons.file} ${toolResult.filePath}`)}`);
2583
+ console.log(`${indent}${colors.textDim(` ${toolResult.lineCount} lines`)}`);
2584
+ console.log('');
2585
+ console.log(renderLines(toolResult.preview, { maxLines: 10, indent: indent + ' ' }));
2586
+ console.log('');
2587
+ } else if (hasDeleteInfo) {
2588
+ // Display DeleteFile result
2589
+ console.log('');
2590
+ console.log(
2591
+ `${indent}${colors.success(`${icons.check} Deleted: ${toolResult.filePath}`)}`
2592
+ );
2593
+ console.log('');
2594
+ } else if (showToolDetails) {
2595
+ // Show full result details
2596
+ const indentedPreview = indentMultiline(resultPreview, indent);
2597
+ console.log(
2598
+ `${indent}${colors.success(`${icons.check} Tool Result:`)}\n${indentedPreview}\n`
2599
+ );
2600
+ } else if (toolResult && toolResult.success === false) {
2601
+ // Tool failed
2602
+ console.log(
2603
+ `${indent}${colors.error(`${icons.cross} ${toolResult.message || 'Failed'}`)}\n`
2604
+ );
2605
+ } else if (toolResult) {
2606
+ // Show brief preview by default
2607
+ const indentedPreview = indentMultiline(truncatedPreview, indent);
2608
+ console.log(
2609
+ `${indent}${colors.success(`${icons.check} Completed`)}\n${indentedPreview}\n`
2610
+ );
2611
+ } else {
2612
+ console.log(`${indent}${colors.textDim('(no result)')}\n`);
2613
+ }
2001
2614
  }
2002
- console.log(`${indent}${colors.error(`${icons.cross} Error:`)} ${error.message}\n`);
2003
2615
 
2004
- // Record failed tool execution in history
2616
+ // Record successful tool execution in history (use truncated preview to save memory)
2005
2617
  executionHistory.push({
2006
2618
  tool: name,
2007
- status: 'error',
2619
+ status: 'success',
2008
2620
  params: parsedParams,
2009
- error: error.message,
2010
- timestamp: new Date().toISOString()
2621
+ result: truncatedPreview,
2622
+ timestamp: new Date().toISOString(),
2011
2623
  });
2012
2624
 
2013
2625
  messages.push({
2014
2626
  role: 'tool',
2015
- content: JSON.stringify({ error: error.message }),
2016
- tool_call_id: toolCall.id
2627
+ content: JSON.stringify(toolResult),
2628
+ tool_call_id: result.id,
2017
2629
  });
2018
2630
  }
2019
2631
  }
2020
- console.log('');
2632
+ if (!isSdkMode || !sdkOutputAdapter) {
2633
+ console.log('');
2634
+ }
2021
2635
  continue; // Continue to next iteration to get final response
2022
2636
  }
2023
2637
 
2024
- // No more tool calls, return the result with execution history
2025
- cancellationManager.off('cancelled', cancelHandler);
2026
- cleanupStdinPolling();
2027
-
2028
- const summaryPreview = contentStr.length > 300 ? contentStr.substring(0, 300) + '...' : contentStr;
2029
- return {
2030
- success: true,
2031
- message: `Task "${description}" completed by ${subagent_type}`,
2032
- result: {
2033
- summary: summaryPreview,
2034
- executionHistory: {
2035
- totalIterations: iteration,
2036
- toolsExecuted: executionHistory.length,
2037
- successfulTools: executionHistory.filter(t => t.status === 'success').length,
2038
- failedTools: executionHistory.filter(t => t.status === 'error').length,
2039
- history: executionHistory
2040
- }
2041
- }
2042
- };
2638
+ // No more tool calls - break loop (same as main agent)
2639
+ lastContentStr = contentStr || '';
2640
+ break;
2043
2641
  }
2044
2642
 
2045
- // Max iterations reached - return accumulated results instead of throwing error
2046
- // Get the last assistant message content
2047
- const lastAssistantMsg = messages.filter(m => m.role === 'assistant').pop();
2048
- const lastContentStr = typeof lastAssistantMsg?.content === 'string'
2049
- ? lastAssistantMsg.content
2050
- : JSON.stringify(lastAssistantMsg?.content || '');
2051
-
2643
+ // Loop ended - return result (same as main agent pattern)
2052
2644
  cancellationManager.off('cancelled', cancelHandler);
2053
2645
  cleanupStdinPolling();
2054
2646
 
2055
- const summaryPreview = lastContentStr.length > 300 ? lastContentStr.substring(0, 300) + '...' : lastContentStr;
2647
+ const summaryPreview =
2648
+ lastContentStr.length > 300 ? lastContentStr.substring(0, 300) + '...' : lastContentStr;
2056
2649
  return {
2057
2650
  success: true,
2058
- message: `Task "${description}" completed (max iterations reached) by ${subagent_type}`,
2651
+ message: `Task "${description}" completed by ${subagent_type}`,
2059
2652
  result: {
2060
2653
  summary: summaryPreview,
2061
2654
  executionHistory: {
2062
2655
  totalIterations: iteration,
2063
2656
  toolsExecuted: executionHistory.length,
2064
- successfulTools: executionHistory.filter(t => t.status === 'success').length,
2065
- failedTools: executionHistory.filter(t => t.status === 'error').length,
2657
+ successfulTools: executionHistory.filter((t) => t.status === 'success').length,
2658
+ failedTools: executionHistory.filter((t) => t.status === 'error').length,
2066
2659
  history: executionHistory,
2067
- maxIterationsReached: true
2068
- }
2069
- }
2660
+ },
2661
+ },
2070
2662
  };
2071
2663
  }
2072
-
2664
+
2073
2665
  private async executeParallelAgents(
2074
2666
  agents: SubAgentTask[],
2075
2667
  description: string,
2076
2668
  mode: ExecutionMode,
2077
2669
  agentManager: any,
2078
2670
  toolRegistry: any,
2079
- aiClient: any,
2671
+ config: any,
2080
2672
  indentLevel: number = 1
2081
2673
  ): Promise<{ success: boolean; message: string; results: any[]; errors: any[] }> {
2082
2674
  const indent = ' '.repeat(indentLevel);
@@ -2105,13 +2697,14 @@ export class TaskTool implements Tool {
2105
2697
  const chunk = process.stdin.read(1);
2106
2698
  if (chunk && chunk.length > 0) {
2107
2699
  const code = chunk[0];
2108
- if (code === 0x1B) { // ESC
2700
+ if (code === 0x1b) {
2701
+ // ESC
2109
2702
  logger.debug('[ParallelAgents] ESC detected via polling!');
2110
2703
  cancellationManager.cancel();
2111
2704
  }
2112
2705
  }
2113
2706
  }
2114
- } catch (e) {
2707
+ } catch {
2115
2708
  // Ignore polling errors
2116
2709
  }
2117
2710
  }, 10);
@@ -2135,18 +2728,20 @@ export class TaskTool implements Tool {
2135
2728
  };
2136
2729
  cancellationManager.on('cancelled', cancelHandler);
2137
2730
 
2138
- console.log(`\n${indent}${colors.accent('◆')} ${colors.primaryBright('Parallel Agents')}: ${agents.length} running...`);
2731
+ console.log(
2732
+ `\n${indent}${colors.accent('◆')} ${colors.primaryBright('Parallel Agents')}: ${agents.length} running...`
2733
+ );
2139
2734
 
2140
2735
  const startTime = Date.now();
2141
2736
 
2142
- const agentPromises = agents.map(async (agentTask, index) => {
2737
+ const agentPromises = agents.map(async (agentTask, _index) => {
2143
2738
  // Check if cancelled
2144
2739
  if (cancelled || cancellationManager.isOperationCancelled()) {
2145
2740
  return {
2146
2741
  success: false,
2147
2742
  agent: agentTask.subagent_type,
2148
2743
  description: agentTask.description,
2149
- error: 'Operation cancelled by user'
2744
+ error: 'Operation cancelled by user',
2150
2745
  };
2151
2746
  }
2152
2747
 
@@ -2160,35 +2755,39 @@ export class TaskTool implements Tool {
2160
2755
  mode,
2161
2756
  agentManager,
2162
2757
  toolRegistry,
2163
- aiClient,
2758
+ config,
2164
2759
  indentLevel + 1
2165
2760
  );
2166
-
2761
+
2167
2762
  return {
2168
2763
  success: true,
2169
2764
  agent: agentTask.subagent_type,
2170
2765
  description: agentTask.description,
2171
- result: result.result
2766
+ result: result.result,
2172
2767
  };
2173
2768
  } catch (error: any) {
2174
2769
  return {
2175
2770
  success: false,
2176
2771
  agent: agentTask.subagent_type,
2177
2772
  description: agentTask.description,
2178
- error: error.message
2773
+ error: error.message,
2179
2774
  };
2180
2775
  }
2181
2776
  });
2182
-
2777
+
2183
2778
  const results = await Promise.all(agentPromises);
2184
-
2779
+
2185
2780
  const duration = Date.now() - startTime;
2186
-
2187
- const successfulAgents = results.filter(r => r.success);
2188
- const failedAgents = results.filter(r => !r.success);
2189
-
2190
- console.log(`${indent}${colors.success('✔')} Parallel task completed in ${colors.textMuted(duration + 'ms')}`);
2191
- console.log(`${indent}${colors.info('')} Success: ${successfulAgents.length}/${agents.length} agents\n`);
2781
+
2782
+ const successfulAgents = results.filter((r) => r.success);
2783
+ const failedAgents = results.filter((r) => !r.success);
2784
+
2785
+ console.log(
2786
+ `${indent}${colors.success('')} Parallel task completed in ${colors.textMuted(duration + 'ms')}`
2787
+ );
2788
+ console.log(
2789
+ `${indent}${colors.info('ℹ')} Success: ${successfulAgents.length}/${agents.length} agents\n`
2790
+ );
2192
2791
 
2193
2792
  if (failedAgents.length > 0) {
2194
2793
  console.log(`${indent}${colors.error('✖')} Failed agents:`);
@@ -2205,16 +2804,16 @@ export class TaskTool implements Tool {
2205
2804
  return {
2206
2805
  success: failedAgents.length === 0,
2207
2806
  message: `Parallel task "${description}" completed: ${successfulAgents.length}/${agents.length} successful`,
2208
- results: successfulAgents.map(r => ({
2807
+ results: successfulAgents.map((r) => ({
2209
2808
  agent: r.agent,
2210
2809
  description: r.description,
2211
- result: r.result
2810
+ result: r.result,
2212
2811
  })),
2213
- errors: failedAgents.map(r => ({
2812
+ errors: failedAgents.map((r) => ({
2214
2813
  agent: r.agent,
2215
2814
  description: r.description,
2216
- error: r.error
2217
- }))
2815
+ error: r.error,
2816
+ })),
2218
2817
  };
2219
2818
  }
2220
2819
  }
@@ -2247,34 +2846,39 @@ export class ReadBashOutputTool implements Tool {
2247
2846
  - Use appropriate poll_interval based on expected task duration
2248
2847
  - Check status to see if task is still running or completed
2249
2848
  - Combine with todo_write to track background task progress`;
2250
- allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
2849
+ allowedModes = [
2850
+ ExecutionMode.YOLO,
2851
+ ExecutionMode.ACCEPT_EDITS,
2852
+ ExecutionMode.PLAN,
2853
+ ExecutionMode.SMART,
2854
+ ];
2251
2855
 
2252
2856
  async execute(params: {
2253
2857
  task_id: string;
2254
2858
  poll_interval?: number;
2255
2859
  }): Promise<{ taskId: string; output: string; status: string; duration: number }> {
2256
2860
  const { task_id, poll_interval = 10 } = params;
2257
-
2861
+
2258
2862
  try {
2259
2863
  const toolRegistry = getToolRegistry();
2260
2864
  const task = (toolRegistry as any).getBackgroundTask(task_id);
2261
-
2865
+
2262
2866
  if (!task) {
2263
2867
  throw new Error(`Task ${task_id} not found`);
2264
2868
  }
2265
-
2869
+
2266
2870
  const interval = Math.min(Math.max(poll_interval, 1), 120);
2267
- await new Promise(resolve => setTimeout(resolve, interval * 1000));
2268
-
2871
+ await new Promise((resolve) => setTimeout(resolve, interval * 1000));
2872
+
2269
2873
  const duration = Date.now() - task.startTime;
2270
2874
  const output = task.output.join('');
2271
2875
  const status = task.process.exitCode === null ? 'running' : 'completed';
2272
-
2876
+
2273
2877
  return {
2274
2878
  taskId: task_id,
2275
2879
  output,
2276
2880
  status,
2277
- duration: Math.floor(duration / 1000)
2881
+ duration: Math.floor(duration / 1000),
2278
2882
  };
2279
2883
  } catch (error: any) {
2280
2884
  throw new Error(`Failed to read bash output: ${error.message}`);
@@ -2310,36 +2914,43 @@ export class WebFetchTool implements Tool {
2310
2914
  - Use specific prompts to extract relevant information
2311
2915
  - Check if the page is accessible if you get errors
2312
2916
  - Large pages may be truncated due to size limits`;
2313
- allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
2917
+ allowedModes = [
2918
+ ExecutionMode.YOLO,
2919
+ ExecutionMode.ACCEPT_EDITS,
2920
+ ExecutionMode.PLAN,
2921
+ ExecutionMode.SMART,
2922
+ ];
2314
2923
 
2315
- async execute(params: { prompt: string }): Promise<{ content: string; url: string; status: number }> {
2924
+ async execute(params: {
2925
+ prompt: string;
2926
+ }): Promise<{ content: string; url: string; status: number }> {
2316
2927
  const { prompt } = params;
2317
-
2928
+
2318
2929
  try {
2319
2930
  const urlMatch = prompt.match(/https?:\/\/[^\s]+/i);
2320
-
2931
+
2321
2932
  if (!urlMatch) {
2322
2933
  throw new Error('No URL found in prompt');
2323
2934
  }
2324
-
2935
+
2325
2936
  const url = urlMatch[0];
2326
-
2937
+
2327
2938
  const response = await axios.get(url, {
2328
2939
  timeout: 30000,
2329
2940
  maxContentLength: 10 * 1024 * 1024,
2330
- validateStatus: () => true
2941
+ validateStatus: () => true,
2331
2942
  });
2332
-
2943
+
2333
2944
  let content = response.data;
2334
-
2945
+
2335
2946
  if (typeof content === 'object') {
2336
2947
  content = JSON.stringify(content, null, 2);
2337
2948
  }
2338
-
2949
+
2339
2950
  return {
2340
2951
  content,
2341
2952
  url,
2342
- status: response.status
2953
+ status: response.status,
2343
2954
  };
2344
2955
  } catch (error: any) {
2345
2956
  throw new Error(`Failed to fetch URL: ${error.message}`);
@@ -2380,7 +2991,12 @@ export class AskUserQuestionTool implements Tool {
2380
2991
  - Provide options when possible for faster response
2381
2992
  - Use multiSelect=true when multiple answers are valid
2382
2993
  - Be clear and concise in question wording`;
2383
- allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
2994
+ allowedModes = [
2995
+ ExecutionMode.YOLO,
2996
+ ExecutionMode.ACCEPT_EDITS,
2997
+ ExecutionMode.PLAN,
2998
+ ExecutionMode.SMART,
2999
+ ];
2384
3000
 
2385
3001
  async execute(params: {
2386
3002
  questions: Array<{
@@ -2389,47 +3005,110 @@ export class AskUserQuestionTool implements Tool {
2389
3005
  options?: string[];
2390
3006
  multiSelect?: boolean;
2391
3007
  }>;
3008
+ }): Promise<{ answers: string[] }> {
3009
+ // Check if in SDK mode
3010
+ const sdkMode = (this as any)._sdkMode;
3011
+ const sdkAdapter = (this as any)._sdkOutputAdapter;
3012
+
3013
+ if (sdkMode && sdkAdapter) {
3014
+ return this.executeSdk(params, sdkAdapter);
3015
+ }
3016
+
3017
+ // Regular TUI mode
3018
+ return this.executeTui(params);
3019
+ }
3020
+
3021
+ /**
3022
+ * Execute in TUI mode using @clack/prompts
3023
+ */
3024
+ private async executeTui(params: {
3025
+ questions: Array<{
3026
+ question: string;
3027
+ header?: string;
3028
+ options?: string[];
3029
+ multiSelect?: boolean;
3030
+ }>;
2392
3031
  }): Promise<{ answers: string[] }> {
2393
3032
  const { questions } = params;
2394
-
3033
+
2395
3034
  try {
2396
3035
  if (questions.length === 0 || questions.length > 4) {
2397
3036
  throw new Error('Must provide 1-4 questions');
2398
3037
  }
2399
-
3038
+
2400
3039
  const answers: string[] = [];
2401
-
3040
+
2402
3041
  for (const q of questions) {
2403
3042
  if (q.options && q.options.length > 0) {
2404
- const result = await inquirer.prompt([
2405
- {
2406
- type: q.multiSelect ? 'checkbox' : 'list',
2407
- name: 'answer',
2408
- message: q.question,
2409
- choices: q.options,
2410
- default: q.multiSelect ? [] : q.options[0]
2411
- }
2412
- ]);
2413
-
2414
- answers.push(Array.isArray(result.answer) ? result.answer.join(', ') : result.answer);
3043
+ const options = q.options.map((opt) => ({ value: opt, label: opt }));
3044
+ const result = await select({
3045
+ message: q.question,
3046
+ options,
3047
+ });
3048
+
3049
+ answers.push(result as string);
2415
3050
  } else {
2416
- const result = await inquirer.prompt([
2417
- {
2418
- type: 'input',
2419
- name: 'answer',
2420
- message: q.question
2421
- }
2422
- ]);
2423
-
2424
- answers.push(result.answer);
3051
+ const result = (await text({
3052
+ message: q.question,
3053
+ })) as string;
3054
+
3055
+ answers.push(result);
2425
3056
  }
2426
3057
  }
2427
-
3058
+
2428
3059
  return { answers };
2429
3060
  } catch (error: any) {
2430
3061
  throw new Error(`Failed to ask user questions: ${error.message}`);
2431
3062
  }
2432
3063
  }
3064
+
3065
+ /**
3066
+ * Execute in SDK mode - output question request and wait for response
3067
+ */
3068
+ private async executeSdk(
3069
+ params: {
3070
+ questions: Array<{
3071
+ question: string;
3072
+ header?: string;
3073
+ options?: string[];
3074
+ multiSelect?: boolean;
3075
+ }>;
3076
+ },
3077
+ sdkAdapter: any
3078
+ ): Promise<{ answers: string[] }> {
3079
+ const { questions } = params;
3080
+
3081
+ if (questions.length === 0 || questions.length > 4) {
3082
+ throw new Error('Must provide 1-4 questions');
3083
+ }
3084
+
3085
+ const requestId = `question_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
3086
+
3087
+ // Output question request through SDK adapter
3088
+ sdkAdapter.outputQuestionRequest({
3089
+ requestId,
3090
+ questions
3091
+ });
3092
+
3093
+ // Wait for SDK question response
3094
+ // The response will be handled by session.ts which has access to the SDK input
3095
+ // For now, we use a polling mechanism or wait for a specific event
3096
+
3097
+ try {
3098
+ // Import the session to get response handling
3099
+ const { getSingletonSession } = await import('./session.js');
3100
+ const session = getSingletonSession();
3101
+ if (!session) {
3102
+ throw new Error('SDK session not available');
3103
+ }
3104
+ const answers = await session.waitForQuestionResponse(requestId);
3105
+
3106
+ sdkAdapter.outputQuestionResponse(requestId, answers);
3107
+ return { answers };
3108
+ } catch (error: any) {
3109
+ throw new Error(`Failed to get SDK question response: ${error.message}`);
3110
+ }
3111
+ }
2433
3112
  }
2434
3113
 
2435
3114
  export class SaveMemoryTool implements Tool {
@@ -2461,20 +3140,25 @@ export class SaveMemoryTool implements Tool {
2461
3140
  - Keep facts concise and specific
2462
3141
  - Remember project-specific conventions for consistency
2463
3142
  - This persists across sessions (global memory)`;
2464
- allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
3143
+ allowedModes = [
3144
+ ExecutionMode.YOLO,
3145
+ ExecutionMode.ACCEPT_EDITS,
3146
+ ExecutionMode.PLAN,
3147
+ ExecutionMode.SMART,
3148
+ ];
2465
3149
 
2466
3150
  async execute(params: { fact: string }): Promise<{ success: boolean; message: string }> {
2467
3151
  const { fact } = params;
2468
-
3152
+
2469
3153
  try {
2470
3154
  const { getMemoryManager } = await import('./memory.js');
2471
3155
  const memoryManager = getMemoryManager(process.cwd());
2472
-
3156
+
2473
3157
  await memoryManager.saveMemory(fact, 'global');
2474
-
3158
+
2475
3159
  return {
2476
3160
  success: true,
2477
- message: `Successfully saved fact to memory`
3161
+ message: `Successfully saved fact to memory`,
2478
3162
  };
2479
3163
  } catch (error: any) {
2480
3164
  throw new Error(`Failed to save memory: ${error.message}`);
@@ -2510,16 +3194,23 @@ export class ExitPlanModeTool implements Tool {
2510
3194
  - Include all necessary steps and considerations
2511
3195
  - The plan will be saved for reference during execution
2512
3196
  - Use this only when truly ready to start coding`;
2513
- allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
3197
+ allowedModes = [
3198
+ ExecutionMode.YOLO,
3199
+ ExecutionMode.ACCEPT_EDITS,
3200
+ ExecutionMode.PLAN,
3201
+ ExecutionMode.SMART,
3202
+ ];
2514
3203
 
2515
- async execute(params: { plan: string }): Promise<{ success: boolean; message: string; plan: string }> {
3204
+ async execute(params: {
3205
+ plan: string;
3206
+ }): Promise<{ success: boolean; message: string; plan: string }> {
2516
3207
  const { plan } = params;
2517
-
3208
+
2518
3209
  try {
2519
3210
  return {
2520
3211
  success: true,
2521
3212
  message: 'Plan completed and ready for execution',
2522
- plan
3213
+ plan,
2523
3214
  };
2524
3215
  } catch (error: any) {
2525
3216
  throw new Error(`Failed to exit plan mode: ${error.message}`);
@@ -2559,21 +3250,21 @@ export class XmlEscapeTool implements Tool {
2559
3250
  escape_all?: boolean;
2560
3251
  }): Promise<{ success: boolean; message: string; changes: number }> {
2561
3252
  const { file_path, escape_all = false } = params;
2562
-
3253
+
2563
3254
  try {
2564
3255
  const absolutePath = path.resolve(file_path);
2565
3256
  let content = await fs.readFile(absolutePath, 'utf-8');
2566
-
3257
+
2567
3258
  const specialChars = [
2568
3259
  { char: '&', replacement: '&amp;' },
2569
3260
  { char: '<', replacement: '&lt;' },
2570
3261
  { char: '>', replacement: '&gt;' },
2571
3262
  { char: '"', replacement: '&quot;' },
2572
- { char: "'", replacement: '&apos;' }
3263
+ { char: "'", replacement: '&apos;' },
2573
3264
  ];
2574
-
3265
+
2575
3266
  let changes = 0;
2576
-
3267
+
2577
3268
  for (const { char, replacement } of specialChars) {
2578
3269
  const regex = new RegExp(this.escapeRegExp(char), 'g');
2579
3270
  const matches = content.match(regex);
@@ -2582,14 +3273,14 @@ export class XmlEscapeTool implements Tool {
2582
3273
  content = content.replace(regex, replacement);
2583
3274
  }
2584
3275
  }
2585
-
3276
+
2586
3277
  if (escape_all) {
2587
3278
  const additionalChars = [
2588
3279
  { char: '©', replacement: '&copy;' },
2589
3280
  { char: '®', replacement: '&reg;' },
2590
- { char: '€', replacement: '&euro;' }
3281
+ { char: '€', replacement: '&euro;' },
2591
3282
  ];
2592
-
3283
+
2593
3284
  for (const { char, replacement } of additionalChars) {
2594
3285
  const regex = new RegExp(this.escapeRegExp(char), 'g');
2595
3286
  const matches = content.match(regex);
@@ -2599,13 +3290,13 @@ export class XmlEscapeTool implements Tool {
2599
3290
  }
2600
3291
  }
2601
3292
  }
2602
-
3293
+
2603
3294
  await fs.writeFile(absolutePath, content, 'utf-8');
2604
-
3295
+
2605
3296
  return {
2606
3297
  success: true,
2607
3298
  message: `Successfully escaped ${changes} character(s) in ${file_path}`,
2608
- changes
3299
+ changes,
2609
3300
  };
2610
3301
  } catch (error: any) {
2611
3302
  throw new Error(`Failed to escape XML/HTML in file ${file_path}: ${error.message}`);
@@ -2646,7 +3337,12 @@ export class ImageReadTool implements Tool {
2646
3337
  - Provide clear prompts for what to look for
2647
3338
  - Use task_brief for context
2648
3339
  - Supports PNG, JPG, GIF, WEBP, SVG, BMP`;
2649
- allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
3340
+ allowedModes = [
3341
+ ExecutionMode.YOLO,
3342
+ ExecutionMode.ACCEPT_EDITS,
3343
+ ExecutionMode.PLAN,
3344
+ ExecutionMode.SMART,
3345
+ ];
2650
3346
 
2651
3347
  async execute(params: {
2652
3348
  image_input: string;
@@ -2656,10 +3352,10 @@ export class ImageReadTool implements Tool {
2656
3352
  mime_type?: string;
2657
3353
  }): Promise<{ analysis: string; image_info: any }> {
2658
3354
  const { image_input, prompt, task_brief, input_type = 'file_path', mime_type } = params;
2659
-
3355
+
2660
3356
  try {
2661
3357
  let imageData: string;
2662
-
3358
+
2663
3359
  if (input_type === 'file_path') {
2664
3360
  const absolutePath = path.resolve(image_input);
2665
3361
  const imageBuffer = await fs.readFile(absolutePath);
@@ -2667,19 +3363,14 @@ export class ImageReadTool implements Tool {
2667
3363
  } else {
2668
3364
  imageData = image_input;
2669
3365
  }
2670
-
2671
- const { AIClient } = await import('./ai-client.js');
3366
+
2672
3367
  const configManager = await import('./config.js');
2673
3368
  const { getConfigManager } = configManager;
2674
3369
  const config = getConfigManager();
2675
-
2676
- const aiClient = new AIClient({
2677
- type: AuthType.OPENAI_COMPATIBLE,
2678
- apiKey: config.get('apiKey'),
2679
- baseUrl: config.get('baseUrl'),
2680
- modelName: config.get('modelName') || 'Qwen3-Coder'
2681
- });
2682
-
3370
+ const authConfig = config.getAuthConfig();
3371
+
3372
+ const aiClient = createAIClient(authConfig);
3373
+
2683
3374
  const textContent = task_brief ? `${task_brief}\n\n${prompt}` : prompt;
2684
3375
  const messages: Message[] = [
2685
3376
  {
@@ -2687,32 +3378,32 @@ export class ImageReadTool implements Tool {
2687
3378
  content: [
2688
3379
  {
2689
3380
  type: 'text',
2690
- text: textContent
3381
+ text: textContent,
2691
3382
  },
2692
3383
  {
2693
3384
  type: 'image_url' as const,
2694
3385
  image_url: {
2695
- url: `data:${mime_type || 'image/jpeg'};base64,${imageData}`
2696
- }
2697
- }
2698
- ]
2699
- }
3386
+ url: `data:${mime_type || 'image/jpeg'};base64,${imageData}`,
3387
+ },
3388
+ },
3389
+ ],
3390
+ },
2700
3391
  ];
2701
-
3392
+
2702
3393
  const result = await aiClient.chatCompletion(messages, {
2703
- temperature: 0.7
3394
+ temperature: 0.7,
2704
3395
  });
2705
-
3396
+
2706
3397
  const messageContent = result.choices[0]?.message?.content;
2707
3398
  const analysis = typeof messageContent === 'string' ? messageContent : '';
2708
-
3399
+
2709
3400
  return {
2710
3401
  analysis,
2711
3402
  image_info: {
2712
3403
  input_type,
2713
3404
  prompt,
2714
- task_brief
2715
- }
3405
+ task_brief,
3406
+ },
2716
3407
  };
2717
3408
  } catch (error: any) {
2718
3409
  throw new Error(`Failed to read image: ${error.message}`);
@@ -2802,13 +3493,16 @@ export class InvokeSkillTool implements Tool {
2802
3493
  - Skills will guide you through their specific workflows`;
2803
3494
  allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
2804
3495
 
2805
- async execute(params: {
2806
- skillId: string;
2807
- taskDescription: string;
2808
- inputFile?: string;
2809
- outputFile?: string;
2810
- options?: Record<string, any>;
2811
- }, _executionMode?: ExecutionMode): Promise<{
3496
+ async execute(
3497
+ params: {
3498
+ skillId: string;
3499
+ taskDescription: string;
3500
+ inputFile?: string;
3501
+ outputFile?: string;
3502
+ options?: Record<string, any>;
3503
+ },
3504
+ _executionMode?: ExecutionMode
3505
+ ): Promise<{
2812
3506
  success: boolean;
2813
3507
  message: string;
2814
3508
  skill: string;
@@ -2825,12 +3519,13 @@ export class InvokeSkillTool implements Tool {
2825
3519
  reason: string;
2826
3520
  }>;
2827
3521
  guidance?: string;
3522
+ /** Skill directory path for dependency management */
3523
+ skillPath?: string;
2828
3524
  }> {
2829
3525
  const { skillId, taskDescription, inputFile, outputFile, options } = params;
2830
3526
 
2831
3527
  try {
2832
3528
  const { getSkillInvoker } = await import('./skill-invoker.js');
2833
- const { SkillExecutionParams } = await import('./skill-invoker.js') as any;
2834
3529
  const skillInvoker = getSkillInvoker();
2835
3530
 
2836
3531
  await skillInvoker.initialize();
@@ -2847,7 +3542,7 @@ export class InvokeSkillTool implements Tool {
2847
3542
  taskDescription,
2848
3543
  inputFile,
2849
3544
  outputFile,
2850
- options
3545
+ options,
2851
3546
  });
2852
3547
 
2853
3548
  if (result.success) {
@@ -2858,7 +3553,7 @@ export class InvokeSkillTool implements Tool {
2858
3553
  task: taskDescription,
2859
3554
  result: result.output,
2860
3555
  files: result.files,
2861
- nextSteps: result.nextSteps
3556
+ nextSteps: result.nextSteps,
2862
3557
  };
2863
3558
  } else {
2864
3559
  throw new Error(result.error || 'Failed to execute matched skill');
@@ -2872,7 +3567,7 @@ export class InvokeSkillTool implements Tool {
2872
3567
  taskDescription,
2873
3568
  inputFile,
2874
3569
  outputFile,
2875
- options
3570
+ options,
2876
3571
  });
2877
3572
 
2878
3573
  if (result.success) {
@@ -2883,7 +3578,8 @@ export class InvokeSkillTool implements Tool {
2883
3578
  task: taskDescription,
2884
3579
  result: result.output,
2885
3580
  files: result.files,
2886
- nextSteps: result.nextSteps
3581
+ nextSteps: result.nextSteps,
3582
+ skillPath: result.skillPath
2887
3583
  };
2888
3584
  } else {
2889
3585
  throw new Error(result.error);
@@ -2943,7 +3639,7 @@ export class InvokeSkillTool implements Tool {
2943
3639
 
2944
3640
  // async execute(params: { skill: string }): Promise<{ success: boolean; details: any }> {
2945
3641
  // const { skill } = params;
2946
-
3642
+
2947
3643
  // if (!skill) {
2948
3644
  // throw new Error('Skill parameter is required');
2949
3645
  // }
@@ -2976,7 +3672,10 @@ export class InvokeSkillTool implements Tool {
2976
3672
  export class ToolRegistry {
2977
3673
  private tools: Map<string, Tool> = new Map();
2978
3674
  private todoWriteTool: TodoWriteTool;
2979
- private backgroundTasks: Map<string, { process: any; startTime: number; output: string[] }> = new Map();
3675
+ private backgroundTasks: Map<string, { process: any; startTime: number; output: string[] }> =
3676
+ new Map();
3677
+ private _isSdkMode: boolean = false;
3678
+ private _sdkOutputAdapter: any = null;
2980
3679
 
2981
3680
  constructor() {
2982
3681
  this.todoWriteTool = new TodoWriteTool();
@@ -2989,7 +3688,7 @@ export class ToolRegistry {
2989
3688
  this.register(new GrepTool());
2990
3689
  this.register(new BashTool());
2991
3690
  this.register(new ListDirectoryTool());
2992
- this.register(new SearchFilesTool());
3691
+ this.register(new SearchFilesTool());
2993
3692
  this.register(new DeleteFileTool());
2994
3693
  this.register(new CreateDirectoryTool());
2995
3694
  this.register(new EditTool());
@@ -3027,23 +3726,21 @@ export class ToolRegistry {
3027
3726
  registerMCPTools(mcpTools: Map<string, any>): void {
3028
3727
  let registeredCount = 0;
3029
3728
 
3030
- for (const [fullName, tool] of mcpTools) {
3031
-
3032
- const firstUnderscoreIndex = fullName.indexOf('__');
3033
-
3034
- if (firstUnderscoreIndex === -1 || firstUnderscoreIndex === 0 ||
3035
-
3036
- firstUnderscoreIndex === fullName.length - 2) continue;
3037
-
3038
-
3729
+ for (const [fullName, tool] of mcpTools) {
3730
+ const firstUnderscoreIndex = fullName.indexOf('__');
3039
3731
 
3040
- const serverName = fullName.substring(0, firstUnderscoreIndex);
3732
+ if (
3733
+ firstUnderscoreIndex === -1 ||
3734
+ firstUnderscoreIndex === 0 ||
3735
+ firstUnderscoreIndex === fullName.length - 2
3736
+ )
3737
+ continue;
3041
3738
 
3042
- const originalName = fullName.substring(firstUnderscoreIndex + 2);
3739
+ const serverName = fullName.substring(0, firstUnderscoreIndex);
3043
3740
 
3044
-
3741
+ const originalName = fullName.substring(firstUnderscoreIndex + 2);
3045
3742
 
3046
- if (!originalName || originalName.trim() === '') continue;
3743
+ if (!originalName || originalName.trim() === '') continue;
3047
3744
 
3048
3745
  // Auto-rename if conflict, ensure unique name
3049
3746
  let toolName = originalName;
@@ -3077,19 +3774,45 @@ export class ToolRegistry {
3077
3774
  const { getMCPManager } = await import('./mcp.js');
3078
3775
  const mcpManager = getMCPManager();
3079
3776
  return await mcpManager.callTool(fullName, params);
3080
- }
3777
+ },
3081
3778
  };
3082
3779
  this.tools.set(toolName, mcpTool);
3083
3780
  registeredCount++;
3084
3781
 
3085
3782
  if (toolName !== originalName) {
3086
- console.log(`[MCP] Tool '${originalName}' renamed to '${toolName}' to avoid conflict`);
3783
+ // SDK 模式下不输出重命名信息
3784
+ if (!this._isSdkMode) {
3785
+ console.log(`[MCP] Tool '${originalName}' renamed to '${toolName}' to avoid conflict`);
3786
+ }
3087
3787
  }
3088
3788
  }
3089
3789
  }
3090
3790
 
3091
3791
  if (registeredCount > 0) {
3092
- console.log(`[MCP] Registered ${registeredCount} tool(s)`);
3792
+ // 在 SDK 模式下不输出注册信息(MCP 相关输出已在 session 中处理)
3793
+ if (!this._isSdkMode) {
3794
+ console.log(`[MCP] Registered ${registeredCount} tool(s)`);
3795
+ }
3796
+ }
3797
+ }
3798
+
3799
+ /**
3800
+ * Set SDK mode for the tool registry.
3801
+ * In SDK mode, tool execution output is sent to the SDK output adapter.
3802
+ */
3803
+ async setSdkMode(enabled: boolean, adapter: any): Promise<void> {
3804
+ this._isSdkMode = enabled;
3805
+ this._sdkOutputAdapter = adapter;
3806
+ // Mark all tools as SDK mode enabled
3807
+ for (const [, tool] of this.tools) {
3808
+ (tool as any)._sdkMode = enabled;
3809
+ (tool as any)._sdkOutputAdapter = adapter;
3810
+ }
3811
+
3812
+ // Initialize SDK mode for TaskTool specifically
3813
+ const taskTool = this.tools.get('task') as any;
3814
+ if (taskTool) {
3815
+ await taskTool.setSdkMode?.(enabled, adapter);
3093
3816
  }
3094
3817
  }
3095
3818
 
@@ -3118,11 +3841,16 @@ export class ToolRegistry {
3118
3841
  return Array.from(this.tools.values());
3119
3842
  }
3120
3843
 
3121
- addBackgroundTask(taskId: string, task: { process: any; startTime: number; output: string[] }): void {
3844
+ addBackgroundTask(
3845
+ taskId: string,
3846
+ task: { process: any; startTime: number; output: string[] }
3847
+ ): void {
3122
3848
  this.backgroundTasks.set(taskId, task);
3123
3849
  }
3124
3850
 
3125
- getBackgroundTask(taskId: string): { process: any; startTime: number; output: string[] } | undefined {
3851
+ getBackgroundTask(
3852
+ taskId: string
3853
+ ): { process: any; startTime: number; output: string[] } | undefined {
3126
3854
  return this.backgroundTasks.get(taskId);
3127
3855
  }
3128
3856
 
@@ -3131,11 +3859,11 @@ export class ToolRegistry {
3131
3859
  }
3132
3860
 
3133
3861
  getToolDefinitions(): any[] {
3134
- return Array.from(this.tools.values()).map(tool => {
3862
+ return Array.from(this.tools.values()).map((tool) => {
3135
3863
  let parameters: any = {
3136
3864
  type: 'object',
3137
3865
  properties: {},
3138
- required: []
3866
+ required: [],
3139
3867
  };
3140
3868
 
3141
3869
  // Define specific parameters for each tool
@@ -3146,18 +3874,18 @@ export class ToolRegistry {
3146
3874
  properties: {
3147
3875
  filePath: {
3148
3876
  type: 'string',
3149
- description: 'The absolute path to the file to read'
3877
+ description: 'The absolute path to the file to read',
3150
3878
  },
3151
3879
  offset: {
3152
3880
  type: 'number',
3153
- description: 'Optional: Line number to start reading from (0-based)'
3881
+ description: 'Optional: Line number to start reading from (0-based)',
3154
3882
  },
3155
3883
  limit: {
3156
3884
  type: 'number',
3157
- description: 'Optional: Maximum number of lines to read'
3158
- }
3885
+ description: 'Optional: Maximum number of lines to read',
3886
+ },
3159
3887
  },
3160
- required: ['filePath']
3888
+ required: ['filePath'],
3161
3889
  };
3162
3890
  break;
3163
3891
 
@@ -3167,14 +3895,14 @@ export class ToolRegistry {
3167
3895
  properties: {
3168
3896
  filePath: {
3169
3897
  type: 'string',
3170
- description: 'The absolute path to the file to write'
3898
+ description: 'The absolute path to the file to write',
3171
3899
  },
3172
3900
  content: {
3173
3901
  type: 'string',
3174
- description: 'The content to write to the file'
3175
- }
3902
+ description: 'The content to write to the file',
3903
+ },
3176
3904
  },
3177
- required: ['filePath', 'content']
3905
+ required: ['filePath', 'content'],
3178
3906
  };
3179
3907
  break;
3180
3908
 
@@ -3184,26 +3912,34 @@ export class ToolRegistry {
3184
3912
  properties: {
3185
3913
  pattern: {
3186
3914
  type: 'string',
3187
- description: 'The regex pattern to search for'
3915
+ description: 'The regex pattern or literal string to search for',
3188
3916
  },
3189
3917
  path: {
3190
3918
  type: 'string',
3191
- description: 'Optional: The path to search in (default: current directory)'
3919
+ description: 'Optional: The path to search in (default: current directory)',
3192
3920
  },
3193
- include: {
3921
+ glob: {
3194
3922
  type: 'string',
3195
- description: 'Optional: Glob pattern to filter files'
3923
+ description: 'Optional: Glob pattern to filter files (e.g., "*.ts", "**/*.js")',
3924
+ },
3925
+ ignoreCase: {
3926
+ type: 'boolean',
3927
+ description: 'Optional: Case-insensitive search (default: false)',
3196
3928
  },
3197
- case_sensitive: {
3929
+ literal: {
3198
3930
  type: 'boolean',
3199
- description: 'Optional: Case-sensitive search (default: false)'
3931
+ description: 'Optional: Treat pattern as literal string (default: false)',
3200
3932
  },
3201
3933
  context: {
3202
3934
  type: 'number',
3203
- description: 'Optional: Number of context lines to show'
3204
- }
3935
+ description: 'Optional: Number of context lines to show before and after',
3936
+ },
3937
+ limit: {
3938
+ type: 'number',
3939
+ description: 'Optional: Maximum number of matches to return',
3940
+ },
3205
3941
  },
3206
- required: ['pattern']
3942
+ required: ['pattern'],
3207
3943
  };
3208
3944
  break;
3209
3945
 
@@ -3213,26 +3949,26 @@ export class ToolRegistry {
3213
3949
  properties: {
3214
3950
  command: {
3215
3951
  type: 'string',
3216
- description: 'The shell command to execute'
3952
+ description: 'The shell command to execute',
3217
3953
  },
3218
3954
  cwd: {
3219
3955
  type: 'string',
3220
- description: 'Optional: Working directory'
3956
+ description: 'Optional: Working directory',
3221
3957
  },
3222
3958
  description: {
3223
3959
  type: 'string',
3224
- description: 'Optional: Brief description of the command'
3960
+ description: 'Optional: Brief description of the command',
3225
3961
  },
3226
3962
  timeout: {
3227
3963
  type: 'number',
3228
- description: 'Optional: Timeout in seconds (default: 120)'
3964
+ description: 'Optional: Timeout in seconds (default: 120)',
3229
3965
  },
3230
3966
  run_in_bg: {
3231
3967
  type: 'boolean',
3232
- description: 'Optional: Run in background (default: false)'
3233
- }
3968
+ description: 'Optional: Run in background (default: false)',
3969
+ },
3234
3970
  },
3235
- required: ['command']
3971
+ required: ['command'],
3236
3972
  };
3237
3973
  break;
3238
3974
 
@@ -3242,14 +3978,14 @@ export class ToolRegistry {
3242
3978
  properties: {
3243
3979
  path: {
3244
3980
  type: 'string',
3245
- description: 'Optional: The directory path to list (default: current directory)'
3981
+ description: 'Optional: The directory path to list (default: current directory)',
3246
3982
  },
3247
3983
  recursive: {
3248
3984
  type: 'boolean',
3249
- description: 'Optional: List recursively (default: false)'
3250
- }
3985
+ description: 'Optional: List recursively (default: false)',
3986
+ },
3251
3987
  },
3252
- required: []
3988
+ required: [],
3253
3989
  };
3254
3990
  break;
3255
3991
 
@@ -3259,18 +3995,18 @@ export class ToolRegistry {
3259
3995
  properties: {
3260
3996
  pattern: {
3261
3997
  type: 'string',
3262
- description: 'The glob pattern to match files'
3998
+ description: 'The glob pattern to match files',
3263
3999
  },
3264
4000
  path: {
3265
4001
  type: 'string',
3266
- description: 'Optional: The path to search in (default: current directory)'
4002
+ description: 'Optional: The path to search in (default: current directory)',
3267
4003
  },
3268
4004
  limit: {
3269
4005
  type: 'integer',
3270
- description: 'Optional: Maximum number of results to return (default: 1000)'
3271
- }
4006
+ description: 'Optional: Maximum number of results to return (default: 1000)',
4007
+ },
3272
4008
  },
3273
- required: ['pattern']
4009
+ required: ['pattern'],
3274
4010
  };
3275
4011
  break;
3276
4012
 
@@ -3280,10 +4016,10 @@ export class ToolRegistry {
3280
4016
  properties: {
3281
4017
  filePath: {
3282
4018
  type: 'string',
3283
- description: 'The path to the file to delete'
3284
- }
4019
+ description: 'The path to the file to delete',
4020
+ },
3285
4021
  },
3286
- required: ['filePath']
4022
+ required: ['filePath'],
3287
4023
  };
3288
4024
  break;
3289
4025
 
@@ -3293,14 +4029,14 @@ export class ToolRegistry {
3293
4029
  properties: {
3294
4030
  dirPath: {
3295
4031
  type: 'string',
3296
- description: 'The directory path to create'
4032
+ description: 'The directory path to create',
3297
4033
  },
3298
4034
  recursive: {
3299
4035
  type: 'boolean',
3300
- description: 'Optional: Create parent directories (default: true)'
3301
- }
4036
+ description: 'Optional: Create parent directories (default: true)',
4037
+ },
3302
4038
  },
3303
- required: ['dirPath']
4039
+ required: ['dirPath'],
3304
4040
  };
3305
4041
  break;
3306
4042
 
@@ -3310,22 +4046,22 @@ export class ToolRegistry {
3310
4046
  properties: {
3311
4047
  file_path: {
3312
4048
  type: 'string',
3313
- description: 'The absolute path to the file to edit'
4049
+ description: 'The absolute path to the file to edit',
3314
4050
  },
3315
4051
  instruction: {
3316
4052
  type: 'string',
3317
- description: 'Description of what needs to be changed'
4053
+ description: 'Description of what needs to be changed',
3318
4054
  },
3319
4055
  old_string: {
3320
4056
  type: 'string',
3321
- description: 'The exact text to replace (supports fuzzy matching)'
4057
+ description: 'The exact text to replace (supports fuzzy matching)',
3322
4058
  },
3323
4059
  new_string: {
3324
4060
  type: 'string',
3325
- description: 'The new text to replace with'
3326
- }
4061
+ description: 'The new text to replace with',
4062
+ },
3327
4063
  },
3328
- required: ['file_path', 'instruction', 'old_string', 'new_string']
4064
+ required: ['file_path', 'instruction', 'old_string', 'new_string'],
3329
4065
  };
3330
4066
  break;
3331
4067
 
@@ -3335,10 +4071,10 @@ export class ToolRegistry {
3335
4071
  properties: {
3336
4072
  query: {
3337
4073
  type: 'string',
3338
- description: 'The search query'
3339
- }
4074
+ description: 'The search query',
4075
+ },
3340
4076
  },
3341
- required: ['query']
4077
+ required: ['query'],
3342
4078
  };
3343
4079
  break;
3344
4080
 
@@ -3354,14 +4090,17 @@ export class ToolRegistry {
3354
4090
  properties: {
3355
4091
  id: { type: 'string' },
3356
4092
  task: { type: 'string' },
3357
- status: { type: 'string', enum: ['pending', 'in_progress', 'completed', 'failed'] },
3358
- priority: { type: 'string', enum: ['high', 'medium', 'low'] }
4093
+ status: {
4094
+ type: 'string',
4095
+ enum: ['pending', 'in_progress', 'completed', 'failed'],
4096
+ },
4097
+ priority: { type: 'string', enum: ['high', 'medium', 'low'] },
3359
4098
  },
3360
- required: ['id', 'task', 'status']
3361
- }
3362
- }
4099
+ required: ['id', 'task', 'status'],
4100
+ },
4101
+ },
3363
4102
  },
3364
- required: ['todos']
4103
+ required: ['todos'],
3365
4104
  };
3366
4105
  break;
3367
4106
 
@@ -3369,7 +4108,7 @@ export class ToolRegistry {
3369
4108
  parameters = {
3370
4109
  type: 'object',
3371
4110
  properties: {},
3372
- required: []
4111
+ required: [],
3373
4112
  };
3374
4113
  break;
3375
4114
 
@@ -3379,60 +4118,79 @@ export class ToolRegistry {
3379
4118
  properties: {
3380
4119
  description: {
3381
4120
  type: 'string',
3382
- description: 'Brief description of the task (3-5 words)'
4121
+ description: 'Brief description of the task (3-5 words)',
3383
4122
  },
3384
4123
  agents: {
3385
4124
  type: 'array',
3386
- description: 'Optional: Array of agents to run in parallel for comprehensive analysis',
4125
+ description:
4126
+ 'Optional: Array of agents to run in parallel for comprehensive analysis',
3387
4127
  items: {
3388
4128
  type: 'object',
3389
4129
  properties: {
3390
4130
  description: {
3391
4131
  type: 'string',
3392
- description: 'Brief description of the sub-agent task'
4132
+ description: 'Brief description of the sub-agent task',
3393
4133
  },
3394
4134
  prompt: {
3395
4135
  type: 'string',
3396
- description: 'The task for the sub-agent to perform'
4136
+ description: 'The task for the sub-agent to perform',
3397
4137
  },
3398
4138
  subagent_type: {
3399
4139
  type: 'string',
3400
- enum: ['general-purpose', 'plan-agent', 'explore-agent', 'frontend-tester', 'code-reviewer', 'frontend-developer', 'backend-developer'],
3401
- description: 'The type of specialized agent'
4140
+ enum: [
4141
+ 'general-purpose',
4142
+ 'plan-agent',
4143
+ 'explore-agent',
4144
+ 'frontend-tester',
4145
+ 'code-reviewer',
4146
+ 'frontend-developer',
4147
+ 'backend-developer',
4148
+ ],
4149
+ description: 'The type of specialized agent',
3402
4150
  },
3403
4151
  constraints: {
3404
4152
  type: 'array',
3405
4153
  items: { type: 'string' },
3406
- description: 'Optional: Constraints or limitations'
3407
- }
4154
+ description: 'Optional: Constraints or limitations',
4155
+ },
3408
4156
  },
3409
- required: ['description', 'prompt', 'subagent_type']
3410
- }
4157
+ required: ['description', 'prompt', 'subagent_type'],
4158
+ },
3411
4159
  },
3412
4160
  prompt: {
3413
4161
  type: 'string',
3414
- description: 'Optional: The task for the agent to perform (use agents for parallel execution)'
4162
+ description:
4163
+ 'Optional: The task for the agent to perform (use agents for parallel execution)',
3415
4164
  },
3416
4165
  subagent_type: {
3417
4166
  type: 'string',
3418
- enum: ['general-purpose', 'plan-agent', 'explore-agent', 'frontend-tester', 'code-reviewer', 'frontend-developer', 'backend-developer'],
3419
- description: 'Optional: The type of specialized agent (use agents for parallel execution)'
4167
+ enum: [
4168
+ 'general-purpose',
4169
+ 'plan-agent',
4170
+ 'explore-agent',
4171
+ 'frontend-tester',
4172
+ 'code-reviewer',
4173
+ 'frontend-developer',
4174
+ 'backend-developer',
4175
+ ],
4176
+ description:
4177
+ 'Optional: The type of specialized agent (use agents for parallel execution)',
3420
4178
  },
3421
4179
  useContext: {
3422
4180
  type: 'boolean',
3423
- description: 'Optional: Include main agent context'
4181
+ description: 'Optional: Include main agent context',
3424
4182
  },
3425
4183
  outputFormat: {
3426
4184
  type: 'string',
3427
- description: 'Optional: Output format template'
4185
+ description: 'Optional: Output format template',
3428
4186
  },
3429
4187
  constraints: {
3430
4188
  type: 'array',
3431
4189
  items: { type: 'string' },
3432
- description: 'Optional: Constraints or limitations'
3433
- }
4190
+ description: 'Optional: Constraints or limitations',
4191
+ },
3434
4192
  },
3435
- required: ['description']
4193
+ required: ['description'],
3436
4194
  };
3437
4195
  break;
3438
4196
 
@@ -3442,14 +4200,14 @@ export class ToolRegistry {
3442
4200
  properties: {
3443
4201
  task_id: {
3444
4202
  type: 'string',
3445
- description: 'The ID of the task'
4203
+ description: 'The ID of the task',
3446
4204
  },
3447
4205
  poll_interval: {
3448
4206
  type: 'number',
3449
- description: 'Optional: Polling interval in seconds (default: 10)'
3450
- }
4207
+ description: 'Optional: Polling interval in seconds (default: 10)',
4208
+ },
3451
4209
  },
3452
- required: ['task_id']
4210
+ required: ['task_id'],
3453
4211
  };
3454
4212
  break;
3455
4213
 
@@ -3459,10 +4217,10 @@ export class ToolRegistry {
3459
4217
  properties: {
3460
4218
  prompt: {
3461
4219
  type: 'string',
3462
- description: 'Prompt containing URL(s) and processing instructions'
3463
- }
4220
+ description: 'Prompt containing URL(s) and processing instructions',
4221
+ },
3464
4222
  },
3465
- required: ['prompt']
4223
+ required: ['prompt'],
3466
4224
  };
3467
4225
  break;
3468
4226
 
@@ -3481,15 +4239,15 @@ export class ToolRegistry {
3481
4239
  options: {
3482
4240
  type: 'array',
3483
4241
  items: { type: 'string' },
3484
- description: 'Available choices (2-4 options)'
4242
+ description: 'Available choices (2-4 options)',
3485
4243
  },
3486
- multiSelect: { type: 'boolean' }
4244
+ multiSelect: { type: 'boolean' },
3487
4245
  },
3488
- required: ['question', 'header', 'options', 'multiSelect']
3489
- }
3490
- }
4246
+ required: ['question', 'header', 'options', 'multiSelect'],
4247
+ },
4248
+ },
3491
4249
  },
3492
- required: ['questions']
4250
+ required: ['questions'],
3493
4251
  };
3494
4252
  break;
3495
4253
 
@@ -3499,10 +4257,10 @@ export class ToolRegistry {
3499
4257
  properties: {
3500
4258
  fact: {
3501
4259
  type: 'string',
3502
- description: 'The specific fact to remember'
3503
- }
4260
+ description: 'The specific fact to remember',
4261
+ },
3504
4262
  },
3505
- required: ['fact']
4263
+ required: ['fact'],
3506
4264
  };
3507
4265
  break;
3508
4266
 
@@ -3512,10 +4270,10 @@ export class ToolRegistry {
3512
4270
  properties: {
3513
4271
  plan: {
3514
4272
  type: 'string',
3515
- description: 'The plan to present'
3516
- }
4273
+ description: 'The plan to present',
4274
+ },
3517
4275
  },
3518
- required: ['plan']
4276
+ required: ['plan'],
3519
4277
  };
3520
4278
  break;
3521
4279
 
@@ -3525,14 +4283,14 @@ export class ToolRegistry {
3525
4283
  properties: {
3526
4284
  file_path: {
3527
4285
  type: 'string',
3528
- description: 'The absolute path to the XML/HTML file'
4286
+ description: 'The absolute path to the XML/HTML file',
3529
4287
  },
3530
4288
  escape_all: {
3531
4289
  type: 'boolean',
3532
- description: 'Optional: Escape all special characters (default: false)'
3533
- }
4290
+ description: 'Optional: Escape all special characters (default: false)',
4291
+ },
3534
4292
  },
3535
- required: ['file_path']
4293
+ required: ['file_path'],
3536
4294
  };
3537
4295
  break;
3538
4296
 
@@ -3542,27 +4300,27 @@ export class ToolRegistry {
3542
4300
  properties: {
3543
4301
  image_input: {
3544
4302
  type: 'string',
3545
- description: 'Image file path or base64 data'
4303
+ description: 'Image file path or base64 data',
3546
4304
  },
3547
4305
  prompt: {
3548
4306
  type: 'string',
3549
- description: 'Comprehensive VLM instruction'
4307
+ description: 'Comprehensive VLM instruction',
3550
4308
  },
3551
4309
  task_brief: {
3552
4310
  type: 'string',
3553
- description: 'Brief task description (max 15 words)'
4311
+ description: 'Brief task description (max 15 words)',
3554
4312
  },
3555
4313
  input_type: {
3556
4314
  type: 'string',
3557
4315
  enum: ['file_path', 'base64'],
3558
- description: 'Input type (default: file_path)'
4316
+ description: 'Input type (default: file_path)',
3559
4317
  },
3560
4318
  mime_type: {
3561
4319
  type: 'string',
3562
- description: 'Optional: MIME type for base64 input'
3563
- }
4320
+ description: 'Optional: MIME type for base64 input',
4321
+ },
3564
4322
  },
3565
- required: ['image_input', 'prompt']
4323
+ required: ['image_input', 'prompt'],
3566
4324
  };
3567
4325
  break;
3568
4326
 
@@ -3572,10 +4330,10 @@ export class ToolRegistry {
3572
4330
  properties: {
3573
4331
  skill: {
3574
4332
  type: 'string',
3575
- description: 'The skill name to execute'
3576
- }
4333
+ description: 'The skill name to execute',
4334
+ },
3577
4335
  },
3578
- required: ['skill']
4336
+ required: ['skill'],
3579
4337
  };
3580
4338
  break;
3581
4339
 
@@ -3583,7 +4341,7 @@ export class ToolRegistry {
3583
4341
  parameters = {
3584
4342
  type: 'object',
3585
4343
  properties: {},
3586
- required: []
4344
+ required: [],
3587
4345
  };
3588
4346
  break;
3589
4347
 
@@ -3593,14 +4351,14 @@ export class ToolRegistry {
3593
4351
  properties: {
3594
4352
  skill: {
3595
4353
  type: 'string',
3596
- description: 'The skill name/id to get details for'
3597
- }
4354
+ description: 'The skill name/id to get details for',
4355
+ },
3598
4356
  },
3599
- required: ['skill']
4357
+ required: ['skill'],
3600
4358
  };
3601
4359
  break;
3602
4360
 
3603
- default:
4361
+ default: {
3604
4362
  // For MCP tools, use their inputSchema; for other unknown tools, keep empty schema
3605
4363
  const mcpTool = tool as any;
3606
4364
  if (mcpTool._isMcpTool && mcpTool.inputSchema) {
@@ -3608,13 +4366,15 @@ export class ToolRegistry {
3608
4366
  parameters = {
3609
4367
  type: 'object',
3610
4368
  properties: {},
3611
- required: []
4369
+ required: [],
3612
4370
  };
3613
4371
  if (mcpTool.inputSchema.properties) {
3614
- for (const [paramName, paramDef] of Object.entries<any>(mcpTool.inputSchema.properties)) {
4372
+ for (const [paramName, paramDef] of Object.entries<any>(
4373
+ mcpTool.inputSchema.properties
4374
+ )) {
3615
4375
  parameters.properties[paramName] = {
3616
4376
  type: paramDef.type || 'string',
3617
- description: paramDef.description || ''
4377
+ description: paramDef.description || '',
3618
4378
  };
3619
4379
  }
3620
4380
  }
@@ -3625,9 +4385,10 @@ export class ToolRegistry {
3625
4385
  parameters = {
3626
4386
  type: 'object',
3627
4387
  properties: {},
3628
- required: []
4388
+ required: [],
3629
4389
  };
3630
4390
  }
4391
+ }
3631
4392
  }
3632
4393
 
3633
4394
  return {
@@ -3635,13 +4396,18 @@ export class ToolRegistry {
3635
4396
  function: {
3636
4397
  name: tool.name,
3637
4398
  description: tool.description,
3638
- parameters
3639
- }
4399
+ parameters,
4400
+ },
3640
4401
  };
3641
4402
  });
3642
4403
  }
3643
4404
 
3644
- async execute(toolName: string, params: any, executionMode: ExecutionMode, indent: string = ''): Promise<any> {
4405
+ async execute(
4406
+ toolName: string,
4407
+ params: any,
4408
+ executionMode: ExecutionMode,
4409
+ indent: string = ''
4410
+ ): Promise<any> {
3645
4411
  // First try to execute as local tool
3646
4412
  const localTool = this.tools.get(toolName);
3647
4413
  if (localTool) {
@@ -3659,13 +4425,13 @@ export class ToolRegistry {
3659
4425
  }
3660
4426
 
3661
4427
  // Try to find MCP tool with just the tool name (try each server)
3662
- for (const [fullName, tool] of allMcpTools) {
4428
+ for (const [fullName, _tool] of allMcpTools) {
3663
4429
  // Split only on the first __ to preserve underscores in tool names
3664
4430
  const firstUnderscoreIndex = fullName.indexOf('__');
3665
4431
  if (firstUnderscoreIndex === -1) continue;
3666
- const [serverName, actualToolName] = [
4432
+ const [_serverName, actualToolName] = [
3667
4433
  fullName.substring(0, firstUnderscoreIndex),
3668
- fullName.substring(firstUnderscoreIndex + 2)
4434
+ fullName.substring(firstUnderscoreIndex + 2),
3669
4435
  ];
3670
4436
  if (actualToolName === toolName) {
3671
4437
  return await this.executeMCPTool(fullName, params, executionMode, indent);
@@ -3679,7 +4445,12 @@ export class ToolRegistry {
3679
4445
  /**
3680
4446
  * Execute local tool (extracted for reuse)
3681
4447
  */
3682
- private async executeLocalTool(toolName: string, params: any, executionMode: ExecutionMode, indent: string): Promise<any> {
4448
+ private async executeLocalTool(
4449
+ toolName: string,
4450
+ params: any,
4451
+ executionMode: ExecutionMode,
4452
+ indent: string
4453
+ ): Promise<any> {
3683
4454
  const tool = this.get(toolName);
3684
4455
  const cancellationManager = getCancellationManager();
3685
4456
 
@@ -3688,11 +4459,12 @@ export class ToolRegistry {
3688
4459
  }
3689
4460
 
3690
4461
  if (!tool.allowedModes.includes(executionMode)) {
3691
- throw new Error(
3692
- `Tool ${toolName} is not allowed in ${executionMode} mode`
3693
- );
4462
+ throw new Error(`Tool ${toolName} is not allowed in ${executionMode} mode`);
3694
4463
  }
3695
4464
 
4465
+ const isSdkMode = this._isSdkMode;
4466
+ const sdkOutputAdapter = this._sdkOutputAdapter;
4467
+
3696
4468
  // Smart approval mode
3697
4469
  if (executionMode === ExecutionMode.SMART) {
3698
4470
  const debugMode = process.env.DEBUG === 'smart-approval';
@@ -3702,7 +4474,9 @@ export class ToolRegistry {
3702
4474
  if (debugMode) {
3703
4475
  const { getLogger } = await import('./logger.js');
3704
4476
  const logger = getLogger();
3705
- logger.debug(`[SmartApprovalEngine] Tool '${toolName}' bypassed smart approval completely`);
4477
+ logger.debug(
4478
+ `[SmartApprovalEngine] Tool '${toolName}' bypassed smart approval completely`
4479
+ );
3706
4480
  }
3707
4481
  return await cancellationManager.withCancellation(
3708
4482
  tool.execute(params, executionMode),
@@ -3718,8 +4492,15 @@ export class ToolRegistry {
3718
4492
  const isRemoteMode = authConfig.type === AuthType.OAUTH_XAGENT;
3719
4493
  if (isRemoteMode && toolName === 'InvokeSkill') {
3720
4494
  console.log('');
3721
- console.log(`${indent}${colors.success(`✅ [Smart Mode] Remote mode: tool '${toolName}' auto-approved (remote LLM already approved)`)}`);
3722
- console.log('');
4495
+ if (isSdkMode && sdkOutputAdapter) {
4496
+ sdkOutputAdapter.outputInfo(`[Smart Mode] Remote mode: tool '${toolName}' auto-approved (remote LLM already approved)`);
4497
+ } else {
4498
+ console.log('');
4499
+ console.log(
4500
+ `${indent}${colors.success(`✅ [Smart Mode] Remote mode: tool '${toolName}' auto-approved (remote LLM already approved)`)}`
4501
+ );
4502
+ console.log('');
4503
+ }
3723
4504
  return await cancellationManager.withCancellation(
3724
4505
  tool.execute(params, executionMode),
3725
4506
  `tool-${toolName}`
@@ -3730,49 +4511,81 @@ export class ToolRegistry {
3730
4511
 
3731
4512
  const approvalEngine = getSmartApprovalEngine(debugMode);
3732
4513
 
4514
+ // Set SDK mode for approval engine if in SDK mode
4515
+ if (isSdkMode && sdkOutputAdapter) {
4516
+ approvalEngine.setSdkMode(true, sdkOutputAdapter);
4517
+ }
4518
+
3733
4519
  // Evaluate tool call
3734
4520
  const result = await approvalEngine.evaluate({
3735
4521
  toolName,
3736
4522
  params,
3737
- timestamp: Date.now()
4523
+ timestamp: Date.now(),
3738
4524
  });
3739
4525
 
3740
4526
  // Decide whether to execute based on approval result
3741
4527
  if (result.decision === 'approved') {
3742
4528
  // Whitelist or AI approval passed, execute directly
3743
- console.log('');
3744
- console.log(`${indent}${colors.success(`✅ [Smart Mode] Tool '${toolName}' passed approval, executing directly`)}`);
3745
- console.log(`${indent}${colors.textDim(` Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`)}`);
3746
- console.log(`${indent}${colors.textDim(` Latency: ${result.latency}ms`)}`);
3747
- console.log('');
4529
+ if (isSdkMode && sdkOutputAdapter) {
4530
+ sdkOutputAdapter.outputInfo(`[Smart Mode] Tool '${toolName}' passed approval, executing directly`);
4531
+ sdkOutputAdapter.outputInfo(`Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}, Latency: ${result.latency}ms`);
4532
+ } else {
4533
+ console.log('');
4534
+ console.log(
4535
+ `${indent}${colors.success(`✅ [Smart Mode] Tool '${toolName}' passed approval, executing directly`)}`
4536
+ );
4537
+ console.log(
4538
+ `${indent}${colors.textDim(` Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`)}`
4539
+ );
4540
+ console.log(`${indent}${colors.textDim(` Latency: ${result.latency}ms`)}`);
4541
+ console.log('');
4542
+ }
3748
4543
  return await cancellationManager.withCancellation(
3749
4544
  tool.execute(params, executionMode),
3750
4545
  `tool-${toolName}`
3751
4546
  );
3752
4547
  } else if (result.decision === 'requires_confirmation') {
3753
4548
  // Requires user confirmation
3754
- const confirmed = await approvalEngine.requestConfirmation(result);
4549
+ const confirmed = await approvalEngine.requestConfirmation(result, toolName, params);
3755
4550
 
3756
4551
  if (confirmed) {
3757
- console.log('');
3758
- console.log(`${indent}${colors.success(`✅ [Smart Mode] User confirmed execution of tool '${toolName}'`)}`);
3759
- console.log('');
4552
+ if (isSdkMode && sdkOutputAdapter) {
4553
+ sdkOutputAdapter.outputInfo(`[Smart Mode] User confirmed execution of tool '${toolName}'`);
4554
+ } else {
4555
+ console.log('');
4556
+ console.log(
4557
+ `${indent}${colors.success(`✅ [Smart Mode] User confirmed execution of tool '${toolName}'`)}`
4558
+ );
4559
+ console.log('');
4560
+ }
3760
4561
  return await cancellationManager.withCancellation(
3761
4562
  tool.execute(params, executionMode),
3762
4563
  `tool-${toolName}`
3763
4564
  );
3764
4565
  } else {
3765
- console.log('');
3766
- console.log(`${indent}${colors.warning(`⚠️ [Smart Mode] User cancelled execution of tool '${toolName}'`)}`);
3767
- console.log('');
4566
+ if (isSdkMode && sdkOutputAdapter) {
4567
+ sdkOutputAdapter.outputWarning(`[Smart Mode] User cancelled execution of tool '${toolName}'`);
4568
+ } else {
4569
+ console.log('');
4570
+ console.log(
4571
+ `${indent}${colors.warning(`⚠️ [Smart Mode] User cancelled execution of tool '${toolName}'`)}`
4572
+ );
4573
+ console.log('');
4574
+ }
3768
4575
  throw new Error(`Tool execution cancelled by user: ${toolName}`);
3769
4576
  }
3770
4577
  } else {
3771
4578
  // Rejected execution
3772
- console.log('');
3773
- console.log(`${indent}${colors.error(`❌ [Smart Mode] Tool '${toolName}' execution rejected`)}`);
3774
- console.log(`${indent}${colors.textDim(` Reason: ${result.description}`)}`);
3775
- console.log('');
4579
+ if (isSdkMode && sdkOutputAdapter) {
4580
+ sdkOutputAdapter.outputError(`[Smart Mode] Tool '${toolName}' execution rejected`, { reason: result.description });
4581
+ } else {
4582
+ console.log('');
4583
+ console.log(
4584
+ `${indent}${colors.error(`❌ [Smart Mode] Tool '${toolName}' execution rejected`)}`
4585
+ );
4586
+ console.log(`${indent}${colors.textDim(` Reason: ${result.description}`)}`);
4587
+ console.log('');
4588
+ }
3776
4589
  throw new Error(`Tool execution rejected: ${toolName}`);
3777
4590
  }
3778
4591
  }
@@ -3787,7 +4600,12 @@ export class ToolRegistry {
3787
4600
  /**
3788
4601
  * Execute an MCP tool call
3789
4602
  */
3790
- private async executeMCPTool(toolName: string, params: any, executionMode: ExecutionMode, indent: string = ''): Promise<any> {
4603
+ private async executeMCPTool(
4604
+ toolName: string,
4605
+ params: any,
4606
+ executionMode: ExecutionMode,
4607
+ indent: string = ''
4608
+ ): Promise<any> {
3791
4609
  const { getMCPManager } = await import('./mcp.js');
3792
4610
  const mcpManager = getMCPManager();
3793
4611
  const cancellationManager = getCancellationManager();
@@ -3796,14 +4614,21 @@ export class ToolRegistry {
3796
4614
  const firstUnderscoreIndex = toolName.indexOf('__');
3797
4615
  const serverName = toolName.substring(0, firstUnderscoreIndex);
3798
4616
  const actualToolName = toolName.substring(firstUnderscoreIndex + 2);
3799
-
4617
+
3800
4618
  // Get server info for display
3801
4619
  const server = mcpManager.getServer(serverName);
3802
- const serverTools = server?.getToolNames() || [];
3803
-
4620
+ const _serverTools = server?.getToolNames() || [];
4621
+
4622
+ const isSdkMode = this._isSdkMode;
4623
+ const sdkOutputAdapter = this._sdkOutputAdapter;
4624
+
3804
4625
  // Display tool call info
3805
- console.log('');
3806
- console.log(`${indent}${colors.warning(`${icons.tool} MCP Tool Call: ${serverName}::${actualToolName}`)}`);
4626
+ if (!isSdkMode || !sdkOutputAdapter) {
4627
+ console.log('');
4628
+ console.log(
4629
+ `${indent}${colors.warning(`${icons.tool} MCP Tool Call: ${serverName}::${actualToolName}`)}`
4630
+ );
4631
+ }
3807
4632
 
3808
4633
  // Smart approval mode for MCP tools
3809
4634
  if (executionMode === ExecutionMode.SMART) {
@@ -3816,29 +4641,63 @@ export class ToolRegistry {
3816
4641
 
3817
4642
  // Remote mode: remote LLM has already approved the tool, auto-approve
3818
4643
  if (isRemoteMode) {
3819
- console.log(`${indent}${colors.success(`✅ [Smart Mode] Remote mode: MCP tool '${serverName}::${actualToolName}' auto-approved`)}`);
4644
+ if (isSdkMode && sdkOutputAdapter) {
4645
+ sdkOutputAdapter.outputInfo(`[Smart Mode] Remote mode: MCP tool '${serverName}::${actualToolName}' auto-approved`);
4646
+ } else {
4647
+ console.log(
4648
+ `${indent}${colors.success(`✅ [Smart Mode] Remote mode: MCP tool '${serverName}::${actualToolName}' auto-approved`)}`
4649
+ );
4650
+ }
3820
4651
  } else {
3821
4652
  const approvalEngine = getSmartApprovalEngine(debugMode);
3822
4653
 
4654
+ // Set SDK mode for approval engine if in SDK mode
4655
+ if (isSdkMode && sdkOutputAdapter) {
4656
+ approvalEngine.setSdkMode(true, sdkOutputAdapter);
4657
+ }
4658
+
3823
4659
  // Evaluate MCP tool call
3824
4660
  const result = await approvalEngine.evaluate({
3825
4661
  toolName: `MCP[${serverName}]::${actualToolName}`,
3826
4662
  params,
3827
- timestamp: Date.now()
4663
+ timestamp: Date.now(),
3828
4664
  });
3829
4665
 
3830
4666
  if (result.decision === 'approved') {
3831
- console.log(`${indent}${colors.success(`✅ [Smart Mode] MCP tool '${serverName}::${actualToolName}' passed approval`)}`);
3832
- console.log(`${indent}${colors.textDim(` Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`)}`);
4667
+ if (isSdkMode && sdkOutputAdapter) {
4668
+ sdkOutputAdapter.outputInfo(`[Smart Mode] MCP tool '${serverName}::${actualToolName}' passed approval`);
4669
+ sdkOutputAdapter.outputInfo(`Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`);
4670
+ } else {
4671
+ console.log(
4672
+ `${indent}${colors.success(`✅ [Smart Mode] MCP tool '${serverName}::${actualToolName}' passed approval`)}`
4673
+ );
4674
+ console.log(
4675
+ `${indent}${colors.textDim(` Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`)}`
4676
+ );
4677
+ }
3833
4678
  } else if (result.decision === 'requires_confirmation') {
3834
- const confirmed = await approvalEngine.requestConfirmation(result);
4679
+ const confirmed = await approvalEngine.requestConfirmation(
4680
+ result,
4681
+ `MCP[${serverName}]::${actualToolName}`,
4682
+ params
4683
+ );
3835
4684
  if (!confirmed) {
3836
- console.log(`${indent}${colors.warning(`⚠️ [Smart Mode] User cancelled MCP tool execution`)}`);
4685
+ if (isSdkMode && sdkOutputAdapter) {
4686
+ sdkOutputAdapter.outputWarning(`[Smart Mode] User cancelled MCP tool execution`);
4687
+ } else {
4688
+ console.log(
4689
+ `${indent}${colors.warning(`⚠️ [Smart Mode] User cancelled MCP tool execution`)}`
4690
+ );
4691
+ }
3837
4692
  throw new Error(`Tool execution cancelled by user: ${toolName}`);
3838
4693
  }
3839
4694
  } else {
3840
- console.log(`${indent}${colors.error(`❌ [Smart Mode] MCP tool execution rejected`)}`);
3841
- console.log(`${indent}${colors.textDim(` Reason: ${result.description}`)}`);
4695
+ if (isSdkMode && sdkOutputAdapter) {
4696
+ sdkOutputAdapter.outputError(`[Smart Mode] MCP tool execution rejected`, { reason: result.description });
4697
+ } else {
4698
+ console.log(`${indent}${colors.error(`❌ [Smart Mode] MCP tool execution rejected`)}`);
4699
+ console.log(`${indent}${colors.textDim(` Reason: ${result.description}`)}`);
4700
+ }
3842
4701
  throw new Error(`Tool execution rejected: ${toolName}`);
3843
4702
  }
3844
4703
  }