@xagent-ai/cli 1.0.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 (537) hide show
  1. package/.eslintrc.js +25 -0
  2. package/.gitmodules +3 -0
  3. package/.prettierrc.json +8 -0
  4. package/CONTRIBUTING.md +167 -0
  5. package/LICENSE +21 -0
  6. package/README.md +280 -0
  7. package/README_CN.md +280 -0
  8. package/dist/agents.d.ts +21 -0
  9. package/dist/agents.d.ts.map +1 -0
  10. package/dist/agents.js +463 -0
  11. package/dist/agents.js.map +1 -0
  12. package/dist/ai-client.d.ts +83 -0
  13. package/dist/ai-client.d.ts.map +1 -0
  14. package/dist/ai-client.js +1280 -0
  15. package/dist/ai-client.js.map +1 -0
  16. package/dist/auth.d.ts +25 -0
  17. package/dist/auth.d.ts.map +1 -0
  18. package/dist/auth.js +573 -0
  19. package/dist/auth.js.map +1 -0
  20. package/dist/cancellation.d.ts +46 -0
  21. package/dist/cancellation.d.ts.map +1 -0
  22. package/dist/cancellation.js +154 -0
  23. package/dist/cancellation.js.map +1 -0
  24. package/dist/checkpoint.d.ts +28 -0
  25. package/dist/checkpoint.d.ts.map +1 -0
  26. package/dist/checkpoint.js +186 -0
  27. package/dist/checkpoint.js.map +1 -0
  28. package/dist/cli.d.ts +3 -0
  29. package/dist/cli.d.ts.map +1 -0
  30. package/dist/cli.js +364 -0
  31. package/dist/cli.js.map +1 -0
  32. package/dist/config.d.ts +49 -0
  33. package/dist/config.d.ts.map +1 -0
  34. package/dist/config.js +205 -0
  35. package/dist/config.js.map +1 -0
  36. package/dist/context-compressor.d.ts +51 -0
  37. package/dist/context-compressor.d.ts.map +1 -0
  38. package/dist/context-compressor.js +231 -0
  39. package/dist/context-compressor.js.map +1 -0
  40. package/dist/conversation.d.ts +34 -0
  41. package/dist/conversation.d.ts.map +1 -0
  42. package/dist/conversation.js +221 -0
  43. package/dist/conversation.js.map +1 -0
  44. package/dist/gui-subagent/action-parser/actionParser.d.ts +19 -0
  45. package/dist/gui-subagent/action-parser/actionParser.d.ts.map +1 -0
  46. package/dist/gui-subagent/action-parser/actionParser.js +203 -0
  47. package/dist/gui-subagent/action-parser/actionParser.js.map +1 -0
  48. package/dist/gui-subagent/action-parser/constants.d.ts +8 -0
  49. package/dist/gui-subagent/action-parser/constants.d.ts.map +1 -0
  50. package/dist/gui-subagent/action-parser/constants.js +12 -0
  51. package/dist/gui-subagent/action-parser/constants.js.map +1 -0
  52. package/dist/gui-subagent/action-parser/index.d.ts +3 -0
  53. package/dist/gui-subagent/action-parser/index.d.ts.map +1 -0
  54. package/dist/gui-subagent/action-parser/index.js +6 -0
  55. package/dist/gui-subagent/action-parser/index.js.map +1 -0
  56. package/dist/gui-subagent/action-parser/types.d.ts +24 -0
  57. package/dist/gui-subagent/action-parser/types.d.ts.map +1 -0
  58. package/dist/gui-subagent/action-parser/types.js +12 -0
  59. package/dist/gui-subagent/action-parser/types.js.map +1 -0
  60. package/dist/gui-subagent/agent/gui-agent.d.ts +126 -0
  61. package/dist/gui-subagent/agent/gui-agent.d.ts.map +1 -0
  62. package/dist/gui-subagent/agent/gui-agent.js +820 -0
  63. package/dist/gui-subagent/agent/gui-agent.js.map +1 -0
  64. package/dist/gui-subagent/agent/index.d.ts +5 -0
  65. package/dist/gui-subagent/agent/index.d.ts.map +1 -0
  66. package/dist/gui-subagent/agent/index.js +5 -0
  67. package/dist/gui-subagent/agent/index.js.map +1 -0
  68. package/dist/gui-subagent/index.d.ts +43 -0
  69. package/dist/gui-subagent/index.d.ts.map +1 -0
  70. package/dist/gui-subagent/index.js +96 -0
  71. package/dist/gui-subagent/index.js.map +1 -0
  72. package/dist/gui-subagent/operator/base-operator.d.ts +108 -0
  73. package/dist/gui-subagent/operator/base-operator.d.ts.map +1 -0
  74. package/dist/gui-subagent/operator/base-operator.js +172 -0
  75. package/dist/gui-subagent/operator/base-operator.js.map +1 -0
  76. package/dist/gui-subagent/operator/browser-operator.d.ts +36 -0
  77. package/dist/gui-subagent/operator/browser-operator.d.ts.map +1 -0
  78. package/dist/gui-subagent/operator/browser-operator.js +306 -0
  79. package/dist/gui-subagent/operator/browser-operator.js.map +1 -0
  80. package/dist/gui-subagent/operator/computer-operator.d.ts +31 -0
  81. package/dist/gui-subagent/operator/computer-operator.d.ts.map +1 -0
  82. package/dist/gui-subagent/operator/computer-operator.js +441 -0
  83. package/dist/gui-subagent/operator/computer-operator.js.map +1 -0
  84. package/dist/gui-subagent/operator/desktop-operator.d.ts +55 -0
  85. package/dist/gui-subagent/operator/desktop-operator.d.ts.map +1 -0
  86. package/dist/gui-subagent/operator/desktop-operator.js +527 -0
  87. package/dist/gui-subagent/operator/desktop-operator.js.map +1 -0
  88. package/dist/gui-subagent/operator/index.d.ts +7 -0
  89. package/dist/gui-subagent/operator/index.d.ts.map +1 -0
  90. package/dist/gui-subagent/operator/index.js +6 -0
  91. package/dist/gui-subagent/operator/index.js.map +1 -0
  92. package/dist/gui-subagent/types/actions.d.ts +108 -0
  93. package/dist/gui-subagent/types/actions.d.ts.map +1 -0
  94. package/dist/gui-subagent/types/actions.js +39 -0
  95. package/dist/gui-subagent/types/actions.js.map +1 -0
  96. package/dist/gui-subagent/types/index.d.ts +6 -0
  97. package/dist/gui-subagent/types/index.d.ts.map +1 -0
  98. package/dist/gui-subagent/types/index.js +6 -0
  99. package/dist/gui-subagent/types/index.js.map +1 -0
  100. package/dist/gui-subagent/types/operator.d.ts +95 -0
  101. package/dist/gui-subagent/types/operator.d.ts.map +1 -0
  102. package/dist/gui-subagent/types/operator.js +16 -0
  103. package/dist/gui-subagent/types/operator.js.map +1 -0
  104. package/dist/gui-subagent/utils.d.ts +19 -0
  105. package/dist/gui-subagent/utils.d.ts.map +1 -0
  106. package/dist/gui-subagent/utils.js +42 -0
  107. package/dist/gui-subagent/utils.js.map +1 -0
  108. package/dist/hook.d.ts +73 -0
  109. package/dist/hook.d.ts.map +1 -0
  110. package/dist/hook.js +156 -0
  111. package/dist/hook.js.map +1 -0
  112. package/dist/index.d.ts +19 -0
  113. package/dist/index.d.ts.map +1 -0
  114. package/dist/index.js +19 -0
  115. package/dist/index.js.map +1 -0
  116. package/dist/input-history.d.ts +24 -0
  117. package/dist/input-history.d.ts.map +1 -0
  118. package/dist/input-history.js +94 -0
  119. package/dist/input-history.js.map +1 -0
  120. package/dist/input-processor.d.ts +31 -0
  121. package/dist/input-processor.d.ts.map +1 -0
  122. package/dist/input-processor.js +233 -0
  123. package/dist/input-processor.js.map +1 -0
  124. package/dist/keyboard-manager.d.ts +151 -0
  125. package/dist/keyboard-manager.d.ts.map +1 -0
  126. package/dist/keyboard-manager.js +396 -0
  127. package/dist/keyboard-manager.js.map +1 -0
  128. package/dist/logger.d.ts +75 -0
  129. package/dist/logger.d.ts.map +1 -0
  130. package/dist/logger.js +339 -0
  131. package/dist/logger.js.map +1 -0
  132. package/dist/mcp.d.ts +57 -0
  133. package/dist/mcp.d.ts.map +1 -0
  134. package/dist/mcp.js +483 -0
  135. package/dist/mcp.js.map +1 -0
  136. package/dist/memory.d.ts +25 -0
  137. package/dist/memory.d.ts.map +1 -0
  138. package/dist/memory.js +250 -0
  139. package/dist/memory.js.map +1 -0
  140. package/dist/print-system-prompt.d.ts +2 -0
  141. package/dist/print-system-prompt.d.ts.map +1 -0
  142. package/dist/print-system-prompt.js +40 -0
  143. package/dist/print-system-prompt.js.map +1 -0
  144. package/dist/session-manager.d.ts +41 -0
  145. package/dist/session-manager.d.ts.map +1 -0
  146. package/dist/session-manager.js +234 -0
  147. package/dist/session-manager.js.map +1 -0
  148. package/dist/session.d.ts +77 -0
  149. package/dist/session.d.ts.map +1 -0
  150. package/dist/session.js +1081 -0
  151. package/dist/session.js.map +1 -0
  152. package/dist/skill-invoker.d.ts +177 -0
  153. package/dist/skill-invoker.d.ts.map +1 -0
  154. package/dist/skill-invoker.js +1643 -0
  155. package/dist/skill-invoker.js.map +1 -0
  156. package/dist/skill-loader.d.ts +76 -0
  157. package/dist/skill-loader.d.ts.map +1 -0
  158. package/dist/skill-loader.js +407 -0
  159. package/dist/skill-loader.js.map +1 -0
  160. package/dist/slash-commands.d.ts +60 -0
  161. package/dist/slash-commands.d.ts.map +1 -0
  162. package/dist/slash-commands.js +1021 -0
  163. package/dist/slash-commands.js.map +1 -0
  164. package/dist/smart-approval.d.ts +137 -0
  165. package/dist/smart-approval.d.ts.map +1 -0
  166. package/dist/smart-approval.js +512 -0
  167. package/dist/smart-approval.js.map +1 -0
  168. package/dist/system-prompt-generator.d.ts +35 -0
  169. package/dist/system-prompt-generator.d.ts.map +1 -0
  170. package/dist/system-prompt-generator.js +729 -0
  171. package/dist/system-prompt-generator.js.map +1 -0
  172. package/dist/test-boundary-conditions.d.ts.map +1 -0
  173. package/dist/test-boundary-conditions.js.map +1 -0
  174. package/dist/test-cancellation-fix.d.ts.map +1 -0
  175. package/dist/test-cancellation-fix.js.map +1 -0
  176. package/dist/test-input-history.d.ts.map +1 -0
  177. package/dist/test-input-history.js.map +1 -0
  178. package/dist/test-interaction-flow.d.ts.map +1 -0
  179. package/dist/test-interaction-flow.js.map +1 -0
  180. package/dist/test-quick.d.ts.map +1 -0
  181. package/dist/test-quick.js.map +1 -0
  182. package/dist/test-user-interaction.d.ts.map +1 -0
  183. package/dist/test-user-interaction.js.map +1 -0
  184. package/dist/theme.d.ts +353 -0
  185. package/dist/theme.d.ts.map +1 -0
  186. package/dist/theme.js +383 -0
  187. package/dist/theme.js.map +1 -0
  188. package/dist/tools.d.ts +373 -0
  189. package/dist/tools.d.ts.map +1 -0
  190. package/dist/tools.js +2906 -0
  191. package/dist/tools.js.map +1 -0
  192. package/dist/types.d.ts +180 -0
  193. package/dist/types.d.ts.map +1 -0
  194. package/dist/types.js +23 -0
  195. package/dist/types.js.map +1 -0
  196. package/dist/unified-session.d.ts +42 -0
  197. package/dist/unified-session.d.ts.map +1 -0
  198. package/dist/unified-session.js +271 -0
  199. package/dist/unified-session.js.map +1 -0
  200. package/dist/update.d.ts +30 -0
  201. package/dist/update.d.ts.map +1 -0
  202. package/dist/update.js +211 -0
  203. package/dist/update.js.map +1 -0
  204. package/dist/workflow.d.ts +53 -0
  205. package/dist/workflow.d.ts.map +1 -0
  206. package/dist/workflow.js +405 -0
  207. package/dist/workflow.js.map +1 -0
  208. package/docs/architecture/mcp-integration-guide.md +131 -0
  209. package/docs/architecture/overview.md +93 -0
  210. package/docs/architecture/tool-system-design.md +89 -0
  211. package/docs/cli/commands.md +189 -0
  212. package/docs/smart-mode.md +257 -0
  213. package/docs/third-party-models.md +449 -0
  214. package/package.json +85 -0
  215. package/scripts/init-skills-path.js +58 -0
  216. package/skills/.claude-plugin/marketplace.json +45 -0
  217. package/skills/README.md +94 -0
  218. package/skills/THIRD_PARTY_NOTICES.md +405 -0
  219. package/skills/skills/algorithmic-art/LICENSE.txt +202 -0
  220. package/skills/skills/algorithmic-art/SKILL.md +405 -0
  221. package/skills/skills/algorithmic-art/templates/generator_template.js +223 -0
  222. package/skills/skills/algorithmic-art/templates/viewer.html +599 -0
  223. package/skills/skills/brand-guidelines/LICENSE.txt +202 -0
  224. package/skills/skills/brand-guidelines/SKILL.md +73 -0
  225. package/skills/skills/canvas-design/LICENSE.txt +202 -0
  226. package/skills/skills/canvas-design/SKILL.md +130 -0
  227. package/skills/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +93 -0
  228. package/skills/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
  229. package/skills/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
  230. package/skills/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +93 -0
  231. package/skills/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
  232. package/skills/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +93 -0
  233. package/skills/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
  234. package/skills/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
  235. package/skills/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
  236. package/skills/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
  237. package/skills/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
  238. package/skills/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
  239. package/skills/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +93 -0
  240. package/skills/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
  241. package/skills/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +93 -0
  242. package/skills/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
  243. package/skills/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +94 -0
  244. package/skills/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
  245. package/skills/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
  246. package/skills/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +93 -0
  247. package/skills/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
  248. package/skills/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +93 -0
  249. package/skills/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
  250. package/skills/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
  251. package/skills/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
  252. package/skills/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
  253. package/skills/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
  254. package/skills/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
  255. package/skills/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
  256. package/skills/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
  257. package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
  258. package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
  259. package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
  260. package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
  261. package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
  262. package/skills/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
  263. package/skills/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
  264. package/skills/skills/canvas-design/canvas-fonts/Italiana-OFL.txt +93 -0
  265. package/skills/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
  266. package/skills/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
  267. package/skills/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
  268. package/skills/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
  269. package/skills/skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
  270. package/skills/skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
  271. package/skills/skills/canvas-design/canvas-fonts/Jura-OFL.txt +93 -0
  272. package/skills/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
  273. package/skills/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
  274. package/skills/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
  275. package/skills/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
  276. package/skills/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
  277. package/skills/skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
  278. package/skills/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
  279. package/skills/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
  280. package/skills/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +93 -0
  281. package/skills/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
  282. package/skills/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
  283. package/skills/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
  284. package/skills/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
  285. package/skills/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
  286. package/skills/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
  287. package/skills/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
  288. package/skills/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +93 -0
  289. package/skills/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +93 -0
  290. package/skills/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
  291. package/skills/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
  292. package/skills/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +93 -0
  293. package/skills/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
  294. package/skills/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +93 -0
  295. package/skills/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
  296. package/skills/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
  297. package/skills/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +93 -0
  298. package/skills/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
  299. package/skills/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
  300. package/skills/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
  301. package/skills/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
  302. package/skills/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
  303. package/skills/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
  304. package/skills/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +93 -0
  305. package/skills/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
  306. package/skills/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
  307. package/skills/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
  308. package/skills/skills/doc-coauthoring/SKILL.md +375 -0
  309. package/skills/skills/docx/LICENSE.txt +30 -0
  310. package/skills/skills/docx/SKILL.md +197 -0
  311. package/skills/skills/docx/docx-js.md +350 -0
  312. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  313. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  314. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  315. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  316. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  317. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  318. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  319. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  320. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  321. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  322. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  323. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  324. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  325. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  326. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  327. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  328. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  329. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  330. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  331. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  332. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  333. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  334. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  335. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  336. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  337. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  338. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  339. package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  340. package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  341. package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  342. package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  343. package/skills/skills/docx/ooxml/schemas/mce/mc.xsd +75 -0
  344. package/skills/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  345. package/skills/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  346. package/skills/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  347. package/skills/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  348. package/skills/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  349. package/skills/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  350. package/skills/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  351. package/skills/skills/docx/ooxml/scripts/pack.py +159 -0
  352. package/skills/skills/docx/ooxml/scripts/unpack.py +29 -0
  353. package/skills/skills/docx/ooxml/scripts/validate.py +69 -0
  354. package/skills/skills/docx/ooxml/scripts/validation/__init__.py +15 -0
  355. package/skills/skills/docx/ooxml/scripts/validation/base.py +951 -0
  356. package/skills/skills/docx/ooxml/scripts/validation/docx.py +274 -0
  357. package/skills/skills/docx/ooxml/scripts/validation/pptx.py +315 -0
  358. package/skills/skills/docx/ooxml/scripts/validation/redlining.py +279 -0
  359. package/skills/skills/docx/ooxml.md +610 -0
  360. package/skills/skills/docx/scripts/__init__.py +1 -0
  361. package/skills/skills/docx/scripts/document.py +1276 -0
  362. package/skills/skills/docx/scripts/templates/comments.xml +3 -0
  363. package/skills/skills/docx/scripts/templates/commentsExtended.xml +3 -0
  364. package/skills/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
  365. package/skills/skills/docx/scripts/templates/commentsIds.xml +3 -0
  366. package/skills/skills/docx/scripts/templates/people.xml +3 -0
  367. package/skills/skills/docx/scripts/utilities.py +374 -0
  368. package/skills/skills/frontend-design/LICENSE.txt +177 -0
  369. package/skills/skills/frontend-design/SKILL.md +42 -0
  370. package/skills/skills/internal-comms/LICENSE.txt +202 -0
  371. package/skills/skills/internal-comms/SKILL.md +32 -0
  372. package/skills/skills/internal-comms/examples/3p-updates.md +47 -0
  373. package/skills/skills/internal-comms/examples/company-newsletter.md +65 -0
  374. package/skills/skills/internal-comms/examples/faq-answers.md +30 -0
  375. package/skills/skills/internal-comms/examples/general-comms.md +16 -0
  376. package/skills/skills/mcp-builder/LICENSE.txt +202 -0
  377. package/skills/skills/mcp-builder/SKILL.md +236 -0
  378. package/skills/skills/mcp-builder/reference/evaluation.md +602 -0
  379. package/skills/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
  380. package/skills/skills/mcp-builder/reference/node_mcp_server.md +970 -0
  381. package/skills/skills/mcp-builder/reference/python_mcp_server.md +719 -0
  382. package/skills/skills/mcp-builder/scripts/connections.py +151 -0
  383. package/skills/skills/mcp-builder/scripts/evaluation.py +373 -0
  384. package/skills/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  385. package/skills/skills/mcp-builder/scripts/requirements.txt +2 -0
  386. package/skills/skills/pdf/LICENSE.txt +30 -0
  387. package/skills/skills/pdf/SKILL.md +294 -0
  388. package/skills/skills/pdf/forms.md +205 -0
  389. package/skills/skills/pdf/reference.md +612 -0
  390. package/skills/skills/pdf/scripts/check_bounding_boxes.py +70 -0
  391. package/skills/skills/pdf/scripts/check_bounding_boxes_test.py +226 -0
  392. package/skills/skills/pdf/scripts/check_fillable_fields.py +12 -0
  393. package/skills/skills/pdf/scripts/convert_pdf_to_images.py +35 -0
  394. package/skills/skills/pdf/scripts/create_validation_image.py +41 -0
  395. package/skills/skills/pdf/scripts/extract_form_field_info.py +152 -0
  396. package/skills/skills/pdf/scripts/fill_fillable_fields.py +114 -0
  397. package/skills/skills/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
  398. package/skills/skills/pptx/LICENSE.txt +30 -0
  399. package/skills/skills/pptx/SKILL.md +484 -0
  400. package/skills/skills/pptx/html2pptx.md +625 -0
  401. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  402. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  403. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  404. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  405. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  406. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  407. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  408. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  409. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  410. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  411. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  412. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  413. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  414. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  415. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  416. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  417. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  418. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  419. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  420. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  421. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  422. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  423. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  424. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  425. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  426. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  427. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  428. package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  429. package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  430. package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  431. package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  432. package/skills/skills/pptx/ooxml/schemas/mce/mc.xsd +75 -0
  433. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  434. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  435. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  436. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  437. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  438. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  439. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  440. package/skills/skills/pptx/ooxml/scripts/pack.py +159 -0
  441. package/skills/skills/pptx/ooxml/scripts/unpack.py +29 -0
  442. package/skills/skills/pptx/ooxml/scripts/validate.py +69 -0
  443. package/skills/skills/pptx/ooxml/scripts/validation/__init__.py +15 -0
  444. package/skills/skills/pptx/ooxml/scripts/validation/base.py +951 -0
  445. package/skills/skills/pptx/ooxml/scripts/validation/docx.py +274 -0
  446. package/skills/skills/pptx/ooxml/scripts/validation/pptx.py +315 -0
  447. package/skills/skills/pptx/ooxml/scripts/validation/redlining.py +279 -0
  448. package/skills/skills/pptx/ooxml.md +427 -0
  449. package/skills/skills/pptx/scripts/html2pptx.js +979 -0
  450. package/skills/skills/pptx/scripts/inventory.py +1020 -0
  451. package/skills/skills/pptx/scripts/rearrange.py +231 -0
  452. package/skills/skills/pptx/scripts/replace.py +385 -0
  453. package/skills/skills/pptx/scripts/thumbnail.py +450 -0
  454. package/skills/skills/skill-creator/LICENSE.txt +202 -0
  455. package/skills/skills/skill-creator/SKILL.md +356 -0
  456. package/skills/skills/skill-creator/references/output-patterns.md +82 -0
  457. package/skills/skills/skill-creator/references/workflows.md +28 -0
  458. package/skills/skills/skill-creator/scripts/init_skill.py +303 -0
  459. package/skills/skills/skill-creator/scripts/package_skill.py +110 -0
  460. package/skills/skills/skill-creator/scripts/quick_validate.py +95 -0
  461. package/skills/skills/slack-gif-creator/LICENSE.txt +202 -0
  462. package/skills/skills/slack-gif-creator/SKILL.md +254 -0
  463. package/skills/skills/slack-gif-creator/core/easing.py +234 -0
  464. package/skills/skills/slack-gif-creator/core/frame_composer.py +176 -0
  465. package/skills/skills/slack-gif-creator/core/gif_builder.py +269 -0
  466. package/skills/skills/slack-gif-creator/core/validators.py +136 -0
  467. package/skills/skills/slack-gif-creator/requirements.txt +4 -0
  468. package/skills/skills/theme-factory/LICENSE.txt +202 -0
  469. package/skills/skills/theme-factory/SKILL.md +59 -0
  470. package/skills/skills/theme-factory/theme-showcase.pdf +0 -0
  471. package/skills/skills/theme-factory/themes/arctic-frost.md +19 -0
  472. package/skills/skills/theme-factory/themes/botanical-garden.md +19 -0
  473. package/skills/skills/theme-factory/themes/desert-rose.md +19 -0
  474. package/skills/skills/theme-factory/themes/forest-canopy.md +19 -0
  475. package/skills/skills/theme-factory/themes/golden-hour.md +19 -0
  476. package/skills/skills/theme-factory/themes/midnight-galaxy.md +19 -0
  477. package/skills/skills/theme-factory/themes/modern-minimalist.md +19 -0
  478. package/skills/skills/theme-factory/themes/ocean-depths.md +19 -0
  479. package/skills/skills/theme-factory/themes/sunset-boulevard.md +19 -0
  480. package/skills/skills/theme-factory/themes/tech-innovation.md +19 -0
  481. package/skills/skills/web-artifacts-builder/LICENSE.txt +202 -0
  482. package/skills/skills/web-artifacts-builder/SKILL.md +74 -0
  483. package/skills/skills/web-artifacts-builder/scripts/bundle-artifact.sh +54 -0
  484. package/skills/skills/web-artifacts-builder/scripts/init-artifact.sh +322 -0
  485. package/skills/skills/webapp-testing/LICENSE.txt +202 -0
  486. package/skills/skills/webapp-testing/SKILL.md +96 -0
  487. package/skills/skills/webapp-testing/examples/console_logging.py +35 -0
  488. package/skills/skills/webapp-testing/examples/element_discovery.py +40 -0
  489. package/skills/skills/webapp-testing/examples/static_html_automation.py +33 -0
  490. package/skills/skills/webapp-testing/scripts/with_server.py +106 -0
  491. package/skills/skills/xlsx/LICENSE.txt +30 -0
  492. package/skills/skills/xlsx/SKILL.md +289 -0
  493. package/skills/skills/xlsx/recalc.py +178 -0
  494. package/skills/spec/agent-skills-spec.md +3 -0
  495. package/skills/template/SKILL.md +6 -0
  496. package/src/agents.ts +504 -0
  497. package/src/ai-client.ts +1456 -0
  498. package/src/auth.ts +648 -0
  499. package/src/cancellation.ts +176 -0
  500. package/src/checkpoint.ts +219 -0
  501. package/src/cli.ts +384 -0
  502. package/src/config.ts +248 -0
  503. package/src/context-compressor.ts +290 -0
  504. package/src/conversation.ts +288 -0
  505. package/src/gui-subagent/action-parser/actionParser.ts +312 -0
  506. package/src/gui-subagent/action-parser/constants.ts +12 -0
  507. package/src/gui-subagent/action-parser/index.ts +6 -0
  508. package/src/gui-subagent/action-parser/types.ts +31 -0
  509. package/src/gui-subagent/agent/gui-agent.ts +982 -0
  510. package/src/gui-subagent/agent/index.ts +5 -0
  511. package/src/gui-subagent/index.ts +139 -0
  512. package/src/gui-subagent/operator/base-operator.ts +246 -0
  513. package/src/gui-subagent/operator/computer-operator.ts +520 -0
  514. package/src/gui-subagent/operator/index.ts +7 -0
  515. package/src/gui-subagent/types/actions.ts +263 -0
  516. package/src/gui-subagent/types/index.ts +6 -0
  517. package/src/gui-subagent/types/operator.ts +106 -0
  518. package/src/gui-subagent/utils.ts +51 -0
  519. package/src/index.ts +18 -0
  520. package/src/input-processor.ts +282 -0
  521. package/src/logger.ts +438 -0
  522. package/src/mcp.ts +563 -0
  523. package/src/memory.ts +303 -0
  524. package/src/session-manager.ts +308 -0
  525. package/src/session.ts +1280 -0
  526. package/src/skill-invoker.ts +1888 -0
  527. package/src/skill-loader.ts +476 -0
  528. package/src/slash-commands.ts +1150 -0
  529. package/src/smart-approval.ts +595 -0
  530. package/src/system-prompt-generator.ts +786 -0
  531. package/src/theme.ts +455 -0
  532. package/src/tools.ts +3398 -0
  533. package/src/types.ts +198 -0
  534. package/src/update.ts +270 -0
  535. package/src/workflow.ts +508 -0
  536. package/tsconfig.json +22 -0
  537. package/vitest.config.ts +19 -0
package/src/tools.ts ADDED
@@ -0,0 +1,3398 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import readline from 'readline';
4
+ import { exec, spawn } from 'child_process';
5
+ import { promisify } from 'util';
6
+ import { glob } from 'glob';
7
+ import axios from 'axios';
8
+ import inquirer from 'inquirer';
9
+ import { Tool, ExecutionMode, AuthType } from './types.js';
10
+ import type { Message, ToolDefinition } from './ai-client.js';
11
+ import { colors, icons, styleHelpers } from './theme.js';
12
+ import { getCancellationManager } from './cancellation.js';
13
+ import { getLogger } from './logger.js';
14
+ import { SystemPromptGenerator } from './system-prompt-generator.js';
15
+ import { InteractiveSession } from './session.js';
16
+
17
+ const execAsync = promisify(exec);
18
+
19
+ export class ReadTool implements Tool {
20
+ name = 'Read';
21
+ description = `Read the contents of a file. This is your PRIMARY tool for understanding existing code, configuration, and documentation.
22
+
23
+ # When to Use
24
+ - When you need to understand existing code before making changes
25
+ - When user asks you to "read", "show", "view", or "check" a file
26
+ - When debugging and need to inspect source files
27
+ - When analyzing project structure by reading key files
28
+ - When examining configuration files (package.json, tsconfig.json, etc.)
29
+ - When checking documentation or README files
30
+
31
+ # When NOT to Use
32
+ - For files you've already read in the same conversation (use memory instead)
33
+ - When you only need file metadata (use ListDirectory or Bash with ls instead)
34
+ - For binary files that cannot be read as text
35
+
36
+ # Parameters
37
+ - \`filePath\`: Absolute path or path relative to project root
38
+ - \`offset\`: (Optional) Line number to start reading from (0-based)
39
+ - \`limit\`: (Optional) Maximum number of lines to read
40
+
41
+ # Examples
42
+ - Read specific file: Read(filePath="/path/to/file.ts")
43
+ - Read with pagination: Read(filePath="src/app.ts", offset=0, limit=100)
44
+
45
+ # Best Practices
46
+ - Use absolute paths or paths relative to the project root
47
+ - Use offset and limit for large files to avoid loading entire content
48
+ - Combine with ListDirectory to explore project structure first
49
+ - Don't re-read files unnecessarily`;
50
+
51
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
52
+
53
+ async execute(params: { filePath: string; offset?: number; limit?: number }): Promise<string> {
54
+ const { filePath, offset = 0, limit } = params;
55
+
56
+ try {
57
+ // Handle ~ (user home directory) in file paths
58
+ let resolvedPath = filePath;
59
+ if (filePath.startsWith('~')) {
60
+ // On Windows, prefer USERPROFILE over HOME to avoid POSIX path issues
61
+ // Some tools like Git Bash may set HOME to a POSIX path on Windows
62
+ let homeDir = process.env.USERPROFILE || '';
63
+ if (!homeDir || homeDir.startsWith('/')) {
64
+ homeDir = process.env.HOME || process.env.USERPROFILE || '';
65
+ }
66
+ resolvedPath = path.join(homeDir, filePath.slice(1));
67
+ }
68
+ const absolutePath = path.resolve(resolvedPath);
69
+ const content = await fs.readFile(absolutePath, 'utf-8');
70
+
71
+ const lines = content.split('\n');
72
+ const startLine = Math.max(0, offset);
73
+ const endLine = limit !== undefined ? Math.min(lines.length, startLine + limit) : lines.length;
74
+ const selectedLines = lines.slice(startLine, endLine);
75
+
76
+ return selectedLines.join('\n');
77
+ } catch (error: any) {
78
+ // Show user-friendly path in error message
79
+ let displayPath = filePath;
80
+ if (filePath.startsWith('~')) {
81
+ // On Windows, prefer USERPROFILE over HOME to avoid POSIX path issues
82
+ let homeDir = process.env.USERPROFILE || '';
83
+ if (!homeDir || homeDir.startsWith('/')) {
84
+ homeDir = process.env.HOME || process.env.USERPROFILE || '';
85
+ }
86
+ displayPath = path.join(homeDir, filePath.slice(1));
87
+ }
88
+ throw new Error(`Failed to read file ${displayPath}: ${error.message}`);
89
+ }
90
+ }
91
+ }
92
+
93
+ export class WriteTool implements Tool {
94
+ name = 'Write';
95
+ description = `Create a new file or completely overwrite an existing file with new content.
96
+
97
+ # When to Use
98
+ - Creating new files (source code, configuration, documentation)
99
+ - Completely replacing file content (not partial edits)
100
+ - Generating files from templates or scratch
101
+ - When user explicitly asks to "create", "write", or "generate" a file
102
+
103
+ # When NOT to Use
104
+ - For making small edits to existing files (use Replace instead)
105
+ - When you only need to append content (read file first, then write)
106
+ - For creating directories (use CreateDirectory instead)
107
+
108
+ # Parameters
109
+ - \`filePath\`: Absolute path or path relative to project root
110
+ - \`content\`: The complete content to write to the file
111
+
112
+ # Examples
113
+ - Create new file: Write(filePath="src/utils.ts", content="...")
114
+ - Create config file: Write(filePath=".env.example", content="API_KEY=...")
115
+
116
+ # Best Practices
117
+ - Parent directories are created automatically
118
+ - Use appropriate file extensions
119
+ - Ensure content is complete and syntactically correct
120
+ - For partial edits, use Replace tool instead`;
121
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
122
+
123
+ async execute(params: { filePath: string; content: string }): Promise<{ success: boolean; message: string }> {
124
+ const { filePath, content } = params;
125
+
126
+ try {
127
+ const absolutePath = path.resolve(filePath);
128
+ const dir = path.dirname(absolutePath);
129
+
130
+ await fs.mkdir(dir, { recursive: true });
131
+ await fs.writeFile(absolutePath, content, 'utf-8');
132
+
133
+ return {
134
+ success: true,
135
+ message: `Successfully wrote to ${filePath}`
136
+ };
137
+ } catch (error: any) {
138
+ throw new Error(`Failed to write file ${filePath}: ${error.message}`);
139
+ }
140
+ }
141
+ }
142
+
143
+ export class GrepTool implements Tool {
144
+ name = 'Grep';
145
+ 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.
146
+
147
+ # When to Use
148
+ - Finding specific function definitions or calls
149
+ - Searching for variable usages or imports
150
+ - Locating error messages or log statements
151
+ - Finding all occurrences of a pattern across the codebase
152
+ - When you need line-by-line results with context
153
+
154
+ # When NOT to Use
155
+ - When you only need to find files containing text (use SearchCodebase instead)
156
+ - When searching by file pattern rather than content (use SearchCodebase)
157
+ - For very large codebases where you only need file names (SearchCodebase is faster)
158
+
159
+ # Parameters
160
+ - \`pattern\`: Regex or literal string to search for
161
+ - \`path\`: (Optional) Directory to search in, default: "."
162
+ - \`include\`: (Optional) File glob pattern to include
163
+ - \`exclude\`: (Optional) File glob pattern to exclude
164
+ - \`case_sensitive\`: (Optional) Case-sensitive search, default: false
165
+ - \`fixed_strings\`: (Optional) Treat pattern as literal string, default: false
166
+ - \`context\`: (Optional) Lines of context before/after matches
167
+ - \`no_ignore\`: (Optional) Don't ignore node_modules/.git, default: false
168
+
169
+ # Examples
170
+ - Find function: Grep(pattern="function myFunction")
171
+ - Find with context: Grep(pattern="TODO", context=3)
172
+ - TypeScript only: Grep(pattern="interface", include="*.ts")
173
+
174
+ # Best Practices
175
+ - Use case_sensitive=true for short patterns to reduce false positives
176
+ - Use fixed_strings=true if your pattern has special regex characters
177
+ - Use context to see the surrounding code for each match
178
+ - Combine with include/exclude to narrow down file types`;
179
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
180
+
181
+ async execute(params: {
182
+ pattern: string;
183
+ path?: string;
184
+ include?: string;
185
+ exclude?: string;
186
+ case_sensitive?: boolean;
187
+ fixed_strings?: boolean;
188
+ context?: number;
189
+ after?: number;
190
+ before?: number;
191
+ no_ignore?: boolean;
192
+ }): Promise<string[]> {
193
+ const {
194
+ pattern,
195
+ path: searchPath = '.',
196
+ include,
197
+ exclude,
198
+ case_sensitive = false,
199
+ fixed_strings = false,
200
+ context,
201
+ after,
202
+ before,
203
+ no_ignore = false
204
+ } = params;
205
+
206
+ try {
207
+ const ignorePatterns = no_ignore ? [] : ['node_modules/**', '.git/**', 'dist/**', 'build/**'];
208
+ if (exclude) {
209
+ ignorePatterns.push(exclude);
210
+ }
211
+
212
+ const absolutePath = path.resolve(searchPath);
213
+ const files = await glob('**/*', {
214
+ cwd: absolutePath,
215
+ nodir: true,
216
+ ignore: ignorePatterns
217
+ });
218
+
219
+ const results: string[] = [];
220
+
221
+ for (const file of files) {
222
+ const fullPath = path.join(absolutePath, file);
223
+ if (include && !file.match(include)) {
224
+ continue;
225
+ }
226
+
227
+ try {
228
+ const content = await fs.readFile(fullPath, 'utf-8');
229
+ const lines = content.split('\n');
230
+
231
+ lines.forEach((line, index) => {
232
+ let matches = false;
233
+
234
+ if (fixed_strings) {
235
+ matches = case_sensitive
236
+ ? line.includes(pattern)
237
+ : line.toLowerCase().includes(pattern.toLowerCase());
238
+ } else {
239
+ try {
240
+ const flags = case_sensitive ? 'g' : 'gi';
241
+ const regex = new RegExp(pattern, flags);
242
+ matches = regex.test(line);
243
+ } catch (e) {
244
+ matches = case_sensitive
245
+ ? line.includes(pattern)
246
+ : line.toLowerCase().includes(pattern.toLowerCase());
247
+ }
248
+ }
249
+
250
+ if (matches) {
251
+ const contextLines: string[] = [];
252
+
253
+ if (before || context) {
254
+ const beforeCount = before || context || 0;
255
+ for (let i = Math.max(0, index - beforeCount); i < index; i++) {
256
+ contextLines.push(`${fullPath}:${i + 1}:${lines[i].trim()}`);
257
+ }
258
+ }
259
+
260
+ contextLines.push(`${fullPath}:${index + 1}:${line.trim()}`);
261
+
262
+ if (after || context) {
263
+ const afterCount = after || context || 0;
264
+ for (let i = index + 1; i < Math.min(lines.length, index + 1 + afterCount); i++) {
265
+ contextLines.push(`${fullPath}:${i + 1}:${lines[i].trim()}`);
266
+ }
267
+ }
268
+
269
+ results.push(...contextLines);
270
+ }
271
+ });
272
+ } catch (error) {
273
+ continue;
274
+ }
275
+ }
276
+
277
+ return results;
278
+ } catch (error: any) {
279
+ throw new Error(`Grep failed: ${error.message}`);
280
+ }
281
+ }
282
+ }
283
+
284
+ export class BashTool implements Tool {
285
+ name = 'Bash';
286
+ description = `Execute shell commands in the terminal. This is your PRIMARY tool for running commands, scripts, and system operations.
287
+
288
+ # When to Use
289
+ - Running build commands (npm run build, tsc, etc.)
290
+ - Installing dependencies (npm install, pip install, etc.)
291
+ - Running tests (npm test, pytest, etc.)
292
+ - Git operations (git commit, git push, etc.)
293
+ - Running linters or formatters
294
+ - Any command-line operations
295
+
296
+ # When NOT to Use
297
+ - For file operations (use Read/Write/Replace/CreateDirectory instead)
298
+ - For searching file content (use Grep instead)
299
+ - For finding files (use SearchCodebase or ListDirectory instead)
300
+ - For commands that require user interaction (non-interactive only)
301
+ - For dangerous commands without understanding the impact
302
+
303
+ # Parameters
304
+ - \`command\`: The shell command to execute
305
+ - \`cwd\`: (Optional) Working directory for the command
306
+ - \`description\`: (Optional) Description of what the command does
307
+ - \`timeout\`: (Optional) Timeout in seconds, default: 120
308
+ - \`run_in_bg\`: (Optional) Run in background, default: false
309
+
310
+ # Examples
311
+ - Install dependencies: Bash(command="npm install", description="Install npm dependencies")
312
+ - Run tests: Bash(command="npm test", description="Run unit tests")
313
+ - Build project: Bash(command="npm run build", description="Build the project")
314
+
315
+ # Best Practices
316
+ - Always provide a description for context
317
+ - Set appropriate timeout for long-running commands
318
+ - Use run_in_bg=true for commands that take a long time
319
+ - Check the command is safe before executing
320
+ - Use absolute paths or paths relative to project root`;
321
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
322
+
323
+ async execute(params: {
324
+ command: string;
325
+ cwd?: string;
326
+ description?: string;
327
+ timeout?: number;
328
+ run_in_bg?: boolean;
329
+ }): Promise<{ stdout: string; stderr: string; exitCode: number; taskId?: string }> {
330
+ const { command, cwd, description, timeout = 120, run_in_bg = false } = params;
331
+
332
+ // Determine effective working directory
333
+ // Only use cwd if the command doesn't contain 'cd' (let LLM control directory)
334
+ let effectiveCwd: string | undefined;
335
+ const hasCdCommand = /cd\s+["']?[^"&|;]+["']?/.test(command);
336
+
337
+ if (cwd && !hasCdCommand) {
338
+ // Command doesn't control its own directory, use provided cwd
339
+ effectiveCwd = cwd;
340
+ } else if (cwd && hasCdCommand) {
341
+ // Command uses cd, ignore cwd to let cd take effect
342
+ effectiveCwd = undefined;
343
+ } else {
344
+ // No cwd provided, use default
345
+ effectiveCwd = undefined;
346
+ }
347
+
348
+ try {
349
+ if (run_in_bg) {
350
+ const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
351
+
352
+ const childProcess = spawn(command, {
353
+ cwd: effectiveCwd || process.cwd(),
354
+ shell: true,
355
+ detached: true
356
+ });
357
+
358
+ const output: string[] = [];
359
+
360
+ childProcess.stdout?.on('data', (data: Buffer) => {
361
+ const text = data.toString();
362
+ output.push(text);
363
+ });
364
+
365
+ childProcess.stderr?.on('data', (data: Buffer) => {
366
+ const text = data.toString();
367
+ output.push(text);
368
+ });
369
+
370
+ childProcess.on('close', (code: number) => {
371
+ console.log(`Background task ${taskId} exited with code ${code}`);
372
+ });
373
+
374
+ const toolRegistry = getToolRegistry();
375
+ (toolRegistry as any).addBackgroundTask(taskId, {
376
+ process: childProcess,
377
+ startTime: Date.now(),
378
+ output
379
+ });
380
+
381
+ return {
382
+ stdout: '',
383
+ stderr: '',
384
+ exitCode: 0,
385
+ taskId
386
+ };
387
+ } else {
388
+ const { stdout, stderr } = await execAsync(command, {
389
+ cwd: effectiveCwd || process.cwd(),
390
+ maxBuffer: 1024 * 1024 * 10,
391
+ timeout: timeout * 1000
392
+ });
393
+
394
+ return {
395
+ stdout,
396
+ stderr,
397
+ exitCode: 0
398
+ };
399
+ }
400
+ } catch (error: any) {
401
+ return {
402
+ stdout: error.stdout || '',
403
+ stderr: error.stderr || error.message,
404
+ exitCode: error.code || 1
405
+ };
406
+ }
407
+ }
408
+ }
409
+
410
+ export class ListDirectoryTool implements Tool {
411
+ name = 'ListDirectory';
412
+ description = `List files and directories in a path. This is your PRIMARY tool for exploring project structure.
413
+
414
+ # When to Use
415
+ - Exploring project structure and organization
416
+ - Finding what files exist in a directory
417
+ - Getting an overview of the codebase layout
418
+ - When user asks to "list files" or "show directory contents"
419
+ - Navigating through project directories
420
+
421
+ # When NOT to Use
422
+ - When you need to read file contents (use Read instead)
423
+ - For recursive exploration of entire codebase (use recursive=true)
424
+ - When you need to search for specific files (use SearchCodebase instead)
425
+
426
+ # Parameters
427
+ - \`path\`: (Optional) Directory path, default: "."
428
+ - \`recursive\`: (Optional) List recursively, default: false
429
+
430
+ # Examples
431
+ - List current directory: ListDirectory(path=".")
432
+ - List src directory: ListDirectory(path="src")
433
+ - List all files recursively: ListDirectory(path=".", recursive=true)
434
+
435
+ # Best Practices
436
+ - Use recursive=true to see entire subtree
437
+ - Results are absolute paths
438
+ - Ignores node_modules and .git by default
439
+ - Combine with Read to examine file contents`;
440
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
441
+
442
+ async execute(params: { path?: string; recursive?: boolean }): Promise<string[]> {
443
+ const { path: dirPath = '.', recursive = false } = params;
444
+
445
+ try {
446
+ const absolutePath = path.resolve(dirPath);
447
+
448
+ const stats = await fs.stat(absolutePath).catch(() => null);
449
+ if (!stats || !stats.isDirectory()) {
450
+ throw new Error(`Directory does not exist: ${dirPath}`);
451
+ }
452
+
453
+ const pattern = recursive ? '**/*' : '*';
454
+ const files = await glob(pattern, {
455
+ cwd: absolutePath,
456
+ nodir: false,
457
+ ignore: ['node_modules/**', '.git/**']
458
+ });
459
+
460
+ return files.map(file => path.join(absolutePath, file));
461
+ } catch (error: any) {
462
+ throw new Error(`Failed to list directory: ${error.message}`);
463
+ }
464
+ }
465
+ }
466
+
467
+ export class SearchCodebaseTool implements Tool {
468
+ name = 'SearchCodebase';
469
+ description = `Search for files matching a glob pattern. This is your PRIMARY tool for finding files by name or extension.
470
+
471
+ # When to Use
472
+ - Finding all files of a certain type (*.ts, *.json, *.md)
473
+ - Locating files in specific directories or subdirectories
474
+ - Finding configuration files, test files, or source files
475
+ - When you need a list of file paths, not content
476
+
477
+ # When NOT to Use
478
+ - When you need to search file contents (use Grep instead)
479
+ - When you need to find specific text within files (use Grep instead)
480
+ - For searching non-file patterns (use Grep or Bash)
481
+
482
+ # Parameters
483
+ - \`pattern\`: Glob pattern (e.g., "**/*.ts", "src/**/*.test.ts")
484
+ - \`path\`: (Optional) Directory to search in, default: "."
485
+
486
+ # Examples
487
+ - Find all TypeScript files: SearchCodebase(pattern="**/*.ts")
488
+ - Find test files: SearchCodebase(pattern="**/*.test.ts")
489
+ - Find config files: SearchCodebase(pattern="**/config.*")
490
+
491
+ # Glob Patterns
492
+ - \`*\` matches any characters except /
493
+ - \`**\` matches any characters including /
494
+ - \`?\` matches single character
495
+ - Use brackets for character classes: [abc]
496
+
497
+ # Best Practices
498
+ - Use **/*.ts for recursive search in all directories
499
+ - Combine with path parameter to search specific directories
500
+ - Results are file paths, not content (use Grep on results if needed)`;
501
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
502
+
503
+ async execute(params: { pattern: string; path?: string }): Promise<string[]> {
504
+ const { pattern, path: searchPath = '.' } = params;
505
+
506
+ try {
507
+ const files = await glob(pattern, {
508
+ cwd: searchPath,
509
+ ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**']
510
+ });
511
+
512
+ return files;
513
+ } catch (error: any) {
514
+ throw new Error(`Search failed: ${error.message}`);
515
+ }
516
+ }
517
+ }
518
+
519
+ export class DeleteFileTool implements Tool {
520
+ name = 'DeleteFile';
521
+ description = `Delete a file from the filesystem.
522
+
523
+ # When to Use
524
+ - Removing temporary or debug files
525
+ - Cleaning up generated files
526
+ - Removing files as part of a refactoring task
527
+ - When user explicitly requests file deletion
528
+
529
+ # When NOT to Use
530
+ - For removing directories (use Bash with rm -rf instead)
531
+ - When uncertain if a file should be deleted (confirm with user first)
532
+ - For removing important source files without explicit user request
533
+
534
+ # Parameters
535
+ - \`filePath\`: Absolute path to the file to delete
536
+
537
+ # Examples
538
+ - Delete temporary file: DeleteFile(filePath="debug.log")
539
+ - Remove unused file: DeleteFile(filePath="src/old-component.tsx")
540
+
541
+ # Best Practices
542
+ - Ensure you have the correct file path
543
+ - Consider if the file might be needed later
544
+ - This action is irreversible - be certain before executing`;
545
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
546
+
547
+ async execute(params: { filePath: string }): Promise<{ success: boolean; message: string }> {
548
+ const { filePath } = params;
549
+
550
+ try {
551
+ const absolutePath = path.resolve(filePath);
552
+ await fs.unlink(absolutePath);
553
+
554
+ return {
555
+ success: true,
556
+ message: `Successfully deleted ${filePath}`
557
+ };
558
+ } catch (error: any) {
559
+ throw new Error(`Failed to delete file ${filePath}: ${error.message}`);
560
+ }
561
+ }
562
+ }
563
+
564
+ export class CreateDirectoryTool implements Tool {
565
+ name = 'CreateDirectory';
566
+ description = `Create a new directory (folder) in the filesystem.
567
+
568
+ # When to Use
569
+ - Creating project structure (src/components, tests/unit, etc.)
570
+ - Setting up directories for new features or modules
571
+ - Organizing files into appropriate folders
572
+ - When user requests to create a folder structure
573
+
574
+ # When NOT to Use
575
+ - For creating parent directories while writing files (Write tool does this automatically)
576
+ - For creating multiple nested directories at once (create step by step or use Bash)
577
+
578
+ # Parameters
579
+ - \`dirPath\`: Path of the directory to create
580
+ - \`recursive\`: (Optional, default: true) Create parent directories if they don't exist
581
+
582
+ # Examples
583
+ - Create single directory: CreateDirectory(dirPath="src/utils")
584
+ - Create nested structure: CreateDirectory(dirPath="src/components/buttons", recursive=true)
585
+
586
+ # Best Practices
587
+ - recursive=true (default) creates all intermediate parent directories
588
+ - Use appropriate naming conventions (kebab-case for directories)
589
+ - Consider the overall project structure before creating`;
590
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
591
+
592
+ async execute(params: { dirPath: string; recursive?: boolean }): Promise<{ success: boolean; message: string }> {
593
+ const { dirPath, recursive = true } = params;
594
+
595
+ try {
596
+ const absolutePath = path.resolve(dirPath);
597
+ await fs.mkdir(absolutePath, { recursive });
598
+
599
+ return {
600
+ success: true,
601
+ message: `Successfully created directory ${dirPath}`
602
+ };
603
+ } catch (error: any) {
604
+ throw new Error(`Failed to create directory ${dirPath}: ${error.message}`);
605
+ }
606
+ }
607
+ }
608
+
609
+ export class ReplaceTool implements Tool {
610
+ name = 'replace';
611
+ description = `Replace specific text within an existing file. This is your PRIMARY tool for making targeted edits to code.
612
+
613
+ # When to Use
614
+ - Modifying specific code sections without rewriting entire files
615
+ - Changing function implementations, variable values, or configurations
616
+ - Fixing bugs by editing specific lines
617
+ - Updating imports, exports, or references
618
+
619
+ # When NOT to Use
620
+ - When you need to create a completely new file (use Write instead)
621
+ - When you want to append content to a file (read first, then Write)
622
+ - When making changes across multiple files (use Grep to find, then Replace individually)
623
+
624
+ # Parameters
625
+ - \`file_path\`: Path to the file to edit
626
+ - \`instruction\`: Description of what to change (for your own tracking)
627
+ - \`old_string\`: The exact text to find and replace (must match exactly)
628
+ - \`new_string\`: The new text to replace with
629
+
630
+ # Critical Requirements
631
+ - \`old_string\` MUST be an EXACT match, including whitespace and indentation
632
+ - Include at least 3 lines of context before and after the target text
633
+ - Ensure unique matching to avoid unintended replacements
634
+
635
+ # Examples
636
+ replace(
637
+ file_path="src/app.ts",
638
+ instruction="Update API endpoint",
639
+ old_string="const API_URL = 'https://api.old.com';",
640
+ new_string="const API_URL = 'https://api.new.com';"
641
+ )
642
+
643
+ # Best Practices
644
+ - Read the file first to understand the exact content
645
+ - Include sufficient context in old_string to ensure unique match
646
+ - Be careful with special regex characters in old_string (they're escaped automatically)
647
+ - If multiple occurrences exist, all will be replaced`;
648
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
649
+
650
+ async execute(params: {
651
+ file_path: string;
652
+ instruction: string;
653
+ old_string: string;
654
+ new_string: string;
655
+ }): Promise<{ success: boolean; message: string; changes: number }> {
656
+ const { file_path, instruction, old_string, new_string } = params;
657
+
658
+ try {
659
+ const absolutePath = path.resolve(file_path);
660
+ const content = await fs.readFile(absolutePath, 'utf-8');
661
+
662
+ const occurrences = (content.match(new RegExp(this.escapeRegExp(old_string), 'g')) || []).length;
663
+
664
+ if (occurrences === 0) {
665
+ return {
666
+ success: false,
667
+ message: `No occurrences found to replace in ${file_path}`,
668
+ changes: 0
669
+ };
670
+ }
671
+
672
+ const newContent = content.replace(new RegExp(this.escapeRegExp(old_string), 'g'), new_string);
673
+ await fs.writeFile(absolutePath, newContent, 'utf-8');
674
+
675
+ return {
676
+ success: true,
677
+ message: `Successfully replaced ${occurrences} occurrence(s) in ${file_path}`,
678
+ changes: occurrences
679
+ };
680
+ } catch (error: any) {
681
+ throw new Error(`Failed to replace in file ${file_path}: ${error.message}`);
682
+ }
683
+ }
684
+
685
+ private escapeRegExp(string: string): string {
686
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
687
+ }
688
+ }
689
+
690
+ export class WebSearchTool implements Tool {
691
+ name = 'web_search';
692
+ description = `Search the web for information. This tool queries a search API to find relevant results.
693
+
694
+ # When to Use
695
+ - When you need current information not in your training data
696
+ - Finding documentation, tutorials, or guides
697
+ - Researching APIs, libraries, or tools
698
+ - Getting up-to-date information on technical topics
699
+ - When user asks for "latest", "recent", or "current" information
700
+
701
+ # When NOT to Use
702
+ - When information is likely in the codebase or project files
703
+ - For information that doesn't change frequently (check docs first)
704
+ - When you can use web_fetch with a known URL instead
705
+ - For purely conversational queries
706
+
707
+ # Parameters
708
+ - \`query\`: Search query string
709
+
710
+ # Examples
711
+ - Find React documentation: web_search(query="React useEffect documentation")
712
+ - Get latest Node.js version: web_search(query="Node.js latest LTS version 2024")
713
+
714
+ # Best Practices
715
+ - Be specific in your query for better results
716
+ - Combine with web_fetch to get full content from relevant URLs
717
+ - Use quotes for exact phrase matching
718
+ - Consider adding context like year or version in query`;
719
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
720
+
721
+ async execute(params: { query: string }): Promise<{ results: any[]; message: string }> {
722
+ const { query } = params;
723
+
724
+ try {
725
+ const configManager = await import('./config.js');
726
+ const { getConfigManager } = configManager;
727
+ const config = getConfigManager();
728
+
729
+ const searchApiKey = config.get('searchApiKey');
730
+ const baseUrl = config.get('baseUrl') || 'https://apis.xagent.cn/v1';
731
+
732
+ if (!searchApiKey) {
733
+ throw new Error('Search API key not configured. Please set searchApiKey in settings.');
734
+ }
735
+
736
+ const response = await axios.post(
737
+ `${baseUrl}/search`,
738
+ { query },
739
+ {
740
+ headers: {
741
+ 'Authorization': `Bearer ${searchApiKey}`,
742
+ 'Content-Type': 'application/json'
743
+ },
744
+ timeout: 30000
745
+ }
746
+ );
747
+
748
+ return {
749
+ results: response.data.results || [],
750
+ message: `Found ${response.data.results?.length || 0} results for "${query}"`
751
+ };
752
+ } catch (error: any) {
753
+ throw new Error(`Web search failed: ${error.message}`);
754
+ }
755
+ }
756
+ }
757
+
758
+ export class TodoWriteTool implements Tool {
759
+ name = 'todo_write';
760
+ description = `Create and manage structured task todo lists. Use this tool VERY frequently to track your progress and give users visibility into what needs to be done.
761
+
762
+ # When to Use
763
+ - Complex, multi-step tasks (3+ steps)
764
+ - User explicitly requests a todo list
765
+ - User provides multiple tasks to accomplish
766
+ - Immediately when starting work on a new feature
767
+ - After completing a task (update status immediately)
768
+ - Breaking down large features into smaller steps
769
+ - Tracking independent subtasks that can be worked on
770
+
771
+ # When NOT to Use
772
+ - Single, straightforward task
773
+ - Trivial operations in less than 3 steps
774
+ - Purely conversational or informational responses
775
+ - When you already have an up-to-date todo list
776
+
777
+ # Task States
778
+ - **pending** - Not started, waiting to be worked on
779
+ - **in_progress** - Currently working on (limit ONE at a time)
780
+ - **completed** - Finished successfully
781
+ - **failed** - Could not complete due to errors
782
+
783
+ # Task Descriptions
784
+ Each task needs:
785
+ - \`id\`: Unique identifier
786
+ - \`task\`: Clear, actionable description in imperative form (e.g., "Run tests")
787
+ - \`status\`: Current state
788
+ - \`priority\`: high/medium/low
789
+
790
+ # Examples
791
+ \`\`\`json
792
+ {
793
+ "todos": [
794
+ { "id": "1", "task": "Run the build and check for errors", "status": "in_progress", "priority": "high" },
795
+ { "id": "2", "task": "Fix any type errors found", "status": "pending", "priority": "high" },
796
+ { "id": "3", "task": "Write unit tests for new feature", "status": "pending", "priority": "medium" }
797
+ ]
798
+ }
799
+ \`\`\`
800
+
801
+ # Best Practices
802
+ - Mark tasks as completed IMMEDIATELY after finishing
803
+ - Don't batch multiple completions - update as you go
804
+ - Keep task descriptions clear and actionable
805
+ - Use appropriate priority levels to indicate urgency`;
806
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
807
+
808
+ private todoList: Array<{ id: string; task: string; status: 'pending' | 'in_progress' | 'completed' | 'failed'; priority: 'high' | 'medium' | 'low' }> = [];
809
+
810
+ async execute(params: {
811
+ todos: Array<{ id: string; task: string; status: 'pending' | 'in_progress' | 'completed' | 'failed'; priority: 'high' | 'medium' | 'low' }>;
812
+ }): Promise<{ success: boolean; message: string; todos: any[] }> {
813
+ const { todos } = params;
814
+
815
+ try {
816
+ this.todoList = todos;
817
+
818
+ const summary = {
819
+ pending: todos.filter(t => t.status === 'pending').length,
820
+ in_progress: todos.filter(t => t.status === 'in_progress').length,
821
+ completed: todos.filter(t => t.status === 'completed').length,
822
+ failed: todos.filter(t => t.status === 'failed').length
823
+ };
824
+
825
+ return {
826
+ success: true,
827
+ message: `Updated todo list: ${summary.pending} pending, ${summary.in_progress} in progress, ${summary.completed} completed, ${summary.failed} failed`,
828
+ todos: this.todoList
829
+ };
830
+ } catch (error: any) {
831
+ throw new Error(`Failed to update todo list: ${error.message}`);
832
+ }
833
+ }
834
+
835
+ getTodos(): any[] {
836
+ return this.todoList;
837
+ }
838
+ }
839
+
840
+ export class TodoReadTool implements Tool {
841
+ name = 'todo_read';
842
+ description = `Read the current session's todo list and get a summary of all tasks. Use this to check what tasks remain and their current status.
843
+
844
+ # When to Use
845
+ - Before starting work to understand what needs to be done
846
+ - After completing a task to verify the todo list is updated
847
+ - When user asks about progress or remaining tasks
848
+ - To get an overview of task distribution (pending, in_progress, completed)
849
+
850
+ # What It Returns
851
+ - Full list of all todos with their IDs, tasks, statuses, and priorities
852
+ - Summary counts: total, pending, in_progress, completed, failed
853
+
854
+ # Examples
855
+ - User asks: "What are we working on right now?" → Use todo_read to show current state
856
+ - After a task completes → Check todo_read to confirm the list is accurate
857
+
858
+ # Best Practices
859
+ - Use todo_write to modify the list, not todo_read
860
+ - Check todo_read after todo_write to verify updates`;
861
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
862
+
863
+ private todoWriteTool: TodoWriteTool;
864
+
865
+ constructor(todoWriteTool: TodoWriteTool) {
866
+ this.todoWriteTool = todoWriteTool;
867
+ }
868
+
869
+ async execute(): Promise<{ todos: any[]; summary: any }> {
870
+ try {
871
+ const todos = this.todoWriteTool.getTodos();
872
+
873
+ const summary = {
874
+ total: todos.length,
875
+ pending: todos.filter(t => t.status === 'pending').length,
876
+ in_progress: todos.filter(t => t.status === 'in_progress').length,
877
+ completed: todos.filter(t => t.status === 'completed').length,
878
+ failed: todos.filter(t => t.status === 'failed').length
879
+ };
880
+
881
+ return {
882
+ todos,
883
+ summary
884
+ };
885
+ } catch (error: any) {
886
+ throw new Error(`Failed to read todo list: ${error.message}`);
887
+ }
888
+ }
889
+ }
890
+
891
+ export interface SubAgentTask {
892
+ description: string;
893
+ prompt: string;
894
+ subagent_type: 'general-purpose' | 'plan-agent' | 'explore-agent' | 'frontend-tester' | 'code-reviewer' | 'frontend-developer' | 'backend-developer' | 'gui-subagent';
895
+ useContext?: boolean;
896
+ outputFormat?: string;
897
+ constraints?: string[];
898
+ }
899
+
900
+ export interface ToolCallOptions {
901
+ indentLevel?: number;
902
+ agentName?: string;
903
+ }
904
+
905
+ export class TaskTool implements Tool {
906
+ name = 'task';
907
+ description = `Launch specialized AI subagents to handle complex, multi-step tasks. Subagents are expert agents designed for specific domains like planning, code exploration, frontend testing, and more.
908
+
909
+ # When to Use
910
+ - Complex tasks requiring specialized expertise (planning, analysis, testing)
911
+ - Multi-step workflows that benefit from dedicated focus
912
+ - When you need to delegate work to avoid context overload
913
+ - Parallel execution of independent tasks across different domains
914
+ - User explicitly requests a specific type of agent (e.g., "use the frontend tester")
915
+
916
+ # Available SubAgents
917
+ 1. **plan-agent** - Task planning and breakdown, risk analysis, implementation roadmaps
918
+ 2. **explore-agent** - Codebase exploration, architecture analysis, finding specific code
919
+ 3. **frontend-tester** - Writing and running frontend tests, UI validation
920
+ 4. **code-reviewer** - Code review, security checks, bug detection
921
+ 5. **frontend-developer** - Frontend development (React, TypeScript, modern web)
922
+ 6. **backend-developer** - Backend development (Node.js, APIs, databases)
923
+ 7. **gui-subagent** - Browser automation, visual web interactions, desktop application automation
924
+
925
+ # When NOT to Use
926
+ - Simple, straightforward tasks you can handle directly
927
+ - Tasks that don't require specialized expertise
928
+ - Single-step operations (use other tools instead)
929
+
930
+ # Examples
931
+ - "Analyze the authentication module and create a security report" → explore-agent
932
+ - "Create a detailed implementation plan for feature X" → plan-agent
933
+ - "Write unit tests for this React component" → frontend-tester
934
+ - "Review my changes for potential bugs" → code-reviewer
935
+ - "Automatically fill out this form and navigate the website" → gui-subagent
936
+ - "Test the login process on the desktop application" → gui-subagent
937
+ - "send a message to the my mom on the desktop application wechat" → gui-subagent
938
+
939
+ # Best Practices
940
+ - Provide clear, specific prompts to subagents
941
+ - Include relevant context (file paths, requirements, constraints)
942
+ - Set appropriate executionMode if needed
943
+ - For parallel execution, ensure tasks are truly independent`;
944
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
945
+
946
+ async execute(params: {
947
+ description: string;
948
+ prompt?: string;
949
+ subagent_type?: 'general-purpose' | 'plan-agent' | 'explore-agent' | 'frontend-tester' | 'code-reviewer' | 'frontend-developer' | 'backend-developer' | 'gui-subagent';
950
+ agents?: SubAgentTask[];
951
+ useContext?: boolean;
952
+ outputFormat?: string;
953
+ constraints?: string[];
954
+ executionMode?: ExecutionMode;
955
+ parallel?: boolean;
956
+ }, _executionMode?: ExecutionMode): Promise<{ success: boolean; message: string; result?: any }> {
957
+ const mode = params.executionMode || _executionMode || ExecutionMode.YOLO;
958
+
959
+ try {
960
+ const { getAgentManager } = await import('./agents.js');
961
+ const agentManager = getAgentManager(process.cwd());
962
+
963
+ const { getConfigManager } = await import('./config.js');
964
+ const config = getConfigManager();
965
+
966
+ const { AIClient } = await import('./ai-client.js');
967
+ const aiClient = new AIClient({
968
+ type: AuthType.API_KEY,
969
+ apiKey: config.get('apiKey'),
970
+ baseUrl: config.get('baseUrl'),
971
+ modelName: config.get('modelName') || 'Qwen3-Coder'
972
+ });
973
+
974
+ const toolRegistry = getToolRegistry();
975
+
976
+ if (params.agents && params.agents.length > 0) {
977
+ return await this.executeParallelAgents(
978
+ params.agents,
979
+ params.description,
980
+ mode,
981
+ agentManager,
982
+ toolRegistry,
983
+ aiClient
984
+ );
985
+ }
986
+
987
+ if (!params.subagent_type || !params.prompt) {
988
+ throw new Error('Either subagent_type and prompt, or agents array must be provided');
989
+ }
990
+
991
+ const result = await this.executeSingleAgent(
992
+ params.subagent_type,
993
+ params.prompt,
994
+ params.description,
995
+ params.useContext ?? true,
996
+ params.constraints || [],
997
+ mode,
998
+ agentManager,
999
+ toolRegistry,
1000
+ aiClient,
1001
+ config
1002
+ );
1003
+
1004
+ return result;
1005
+ } catch (error: any) {
1006
+ throw new Error(`Task execution failed: ${error.message}`);
1007
+ }
1008
+ }
1009
+
1010
+ /**
1011
+ * Execute GUI subagent by directly calling GUIAgent.run()
1012
+ * This bypasses the normal subagent message loop for better GUI control
1013
+ */
1014
+ private async executeGUIAgent(
1015
+ prompt: string,
1016
+ description: string,
1017
+ agent: any,
1018
+ mode: ExecutionMode,
1019
+ config: any,
1020
+ indentLevel: number = 1
1021
+ ): Promise<{ success: boolean; cancelled?: boolean; message: string; result?: any }> {
1022
+ const indent = ' '.repeat(indentLevel);
1023
+ const cancellationManager = getCancellationManager();
1024
+ const logger = getLogger();
1025
+
1026
+ console.log(`${indent}${colors.primaryBright(`${icons.robot} GUI Agent`)}: ${description}`);
1027
+ console.log(`${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`);
1028
+ console.log('');
1029
+
1030
+ // Get model config for GUI agent
1031
+ // Priority: guiSubagentBaseUrl (test first) -> baseUrl (fallback)
1032
+ // When falling back to baseUrl, also use the corresponding modelName and apiKey
1033
+ const primaryBaseUrl = config.get('guiSubagentBaseUrl') || '';
1034
+ const fallbackBaseUrl = config.get('baseUrl') || '';
1035
+ const primaryApiKey = config.get('guiSubagentApiKey') || '';
1036
+ const fallbackApiKey = config.get('apiKey') || '';
1037
+ const primaryModelName = config.get('guiSubagentModel') || '';
1038
+ const fallbackModelName = config.get('modelName') || '';
1039
+
1040
+ let baseUrl = primaryBaseUrl;
1041
+ let modelName = primaryModelName;
1042
+ let apiKey = primaryApiKey;
1043
+
1044
+ // Test API availability (like curl) and choose the right baseUrl
1045
+ if (primaryBaseUrl) {
1046
+ try {
1047
+ const controller = new AbortController();
1048
+ const timeoutId = setTimeout(() => controller.abort(), 5000);
1049
+ const response = await fetch(`${primaryBaseUrl.replace(/\/v1\/?$/, '')}/models`, {
1050
+ method: 'GET',
1051
+ headers: primaryApiKey ? { 'Authorization': `Bearer ${primaryApiKey}` } : {},
1052
+ signal: controller.signal
1053
+ });
1054
+ clearTimeout(timeoutId);
1055
+ if (!response.ok) {
1056
+ // Fallback to baseUrl with its corresponding model and API key
1057
+ baseUrl = fallbackBaseUrl;
1058
+ modelName = fallbackModelName;
1059
+ apiKey = fallbackApiKey;
1060
+ }
1061
+ } catch {
1062
+ // Fallback to baseUrl with its corresponding model and API key
1063
+ baseUrl = fallbackBaseUrl;
1064
+ modelName = fallbackModelName;
1065
+ apiKey = fallbackApiKey;
1066
+ }
1067
+ } else {
1068
+ baseUrl = fallbackBaseUrl;
1069
+ modelName = fallbackModelName;
1070
+ apiKey = fallbackApiKey;
1071
+ }
1072
+
1073
+ if (!baseUrl) {
1074
+ return {
1075
+ success: false,
1076
+ message: `GUI task "${description}" failed: No valid API URL configured`
1077
+ };
1078
+ }
1079
+
1080
+ // Set up stdin polling for ESC cancellation
1081
+ let rawModeEnabled = false;
1082
+ let stdinPollingInterval: NodeJS.Timeout | null = null;
1083
+
1084
+ const setupStdinPolling = () => {
1085
+ if (process.stdin.isTTY) {
1086
+ try {
1087
+ process.stdin.setRawMode(true);
1088
+ rawModeEnabled = true;
1089
+ process.stdin.resume();
1090
+ readline.emitKeypressEvents(process.stdin);
1091
+ } catch (e) {
1092
+ logger.debug(`[GUIAgent] Could not set raw mode: ${e}`);
1093
+ }
1094
+
1095
+ stdinPollingInterval = setInterval(() => {
1096
+ try {
1097
+ if (rawModeEnabled) {
1098
+ const chunk = process.stdin.read(1);
1099
+ if (chunk && chunk.length > 0) {
1100
+ const code = chunk[0];
1101
+ if (code === 0x1B) { // ESC
1102
+ logger.debug('[GUIAgent] ESC detected!');
1103
+ cancellationManager.cancel();
1104
+ }
1105
+ }
1106
+ }
1107
+ } catch (e) {
1108
+ // Ignore polling errors
1109
+ }
1110
+ }, 10);
1111
+ }
1112
+ };
1113
+
1114
+ const cleanupStdinPolling = () => {
1115
+ if (stdinPollingInterval) {
1116
+ clearInterval(stdinPollingInterval);
1117
+ stdinPollingInterval = null;
1118
+ }
1119
+ };
1120
+
1121
+ // Set up cancellation
1122
+ let cancelled = false;
1123
+ const cancelHandler = () => {
1124
+ cancelled = true;
1125
+ };
1126
+ cancellationManager.on('cancelled', cancelHandler);
1127
+
1128
+ // Start polling for ESC
1129
+ setupStdinPolling();
1130
+
1131
+ try {
1132
+ // Import and create GUIAgent
1133
+ const { createGUISubAgent } = await import('./gui-subagent/index.js');
1134
+
1135
+ const guiAgent = await createGUISubAgent({
1136
+ model: modelName,
1137
+ modelBaseUrl: baseUrl || undefined,
1138
+ modelApiKey: apiKey || undefined,
1139
+ maxLoopCount: 30,
1140
+ loopIntervalInMs: 500,
1141
+ showAIDebugInfo: config.get('showAIDebugInfo') || false,
1142
+ });
1143
+
1144
+ // Add constraints to prompt if any
1145
+ const fullPrompt = prompt;
1146
+
1147
+ // Execute GUI task - this will run autonomously until completion
1148
+ const result = await guiAgent.run(fullPrompt);
1149
+
1150
+ // Cleanup
1151
+ await guiAgent.cleanup();
1152
+
1153
+ // Check cancellation
1154
+ if (cancelled || cancellationManager.isOperationCancelled()) {
1155
+ cleanupStdinPolling();
1156
+ cancellationManager.off('cancelled', cancelHandler);
1157
+ // Flush stdout to prevent residual output after prompt
1158
+ process.stdout.write('\n');
1159
+ return {
1160
+ success: true,
1161
+ cancelled: true, // Mark as cancelled so main agent won't continue
1162
+ message: `GUI task "${description}" cancelled by user`,
1163
+ result: 'Task cancelled'
1164
+ };
1165
+ }
1166
+
1167
+ cleanupStdinPolling();
1168
+ cancellationManager.off('cancelled', cancelHandler);
1169
+ // Flush stdout to ensure all output is displayed before returning
1170
+ process.stdout.write('\n');
1171
+
1172
+ // Return result based on GUIAgent status
1173
+ if (result.status === 'end') {
1174
+ const iterations = result.conversations.filter(c => c.from === 'human' && c.screenshotBase64).length;
1175
+ console.log(`${indent}${colors.success(`${icons.check} GUI task completed in ${iterations} iterations`)}`);
1176
+ return {
1177
+ success: true,
1178
+ message: `GUI task "${description}" completed`,
1179
+ result: `Completed in ${iterations} iterations`
1180
+ };
1181
+ } else if (result.status === 'user_stopped') {
1182
+ return {
1183
+ success: true,
1184
+ message: `GUI task "${description}" stopped by user`,
1185
+ result: 'User stopped'
1186
+ };
1187
+ } else {
1188
+ // status is 'error' or other non-success status
1189
+ const errorMsg = result.error || 'Unknown error';
1190
+ return {
1191
+ success: false,
1192
+ message: `GUI task "${description}" failed: ${errorMsg}`
1193
+ };
1194
+ }
1195
+ } catch (error: any) {
1196
+ cleanupStdinPolling();
1197
+ cancellationManager.off('cancelled', cancelHandler);
1198
+
1199
+ // Flush stdout to prevent residual output
1200
+ process.stdout.write('\n');
1201
+
1202
+ // If the user cancelled the task, ignore any API errors (like 429)
1203
+ // and return cancelled status instead
1204
+ if (cancelled || cancellationManager.isOperationCancelled()) {
1205
+ return {
1206
+ success: true,
1207
+ cancelled: true, // Mark as cancelled so main agent won't continue
1208
+ message: `GUI task "${description}" cancelled by user`,
1209
+ result: 'Task cancelled'
1210
+ };
1211
+ }
1212
+
1213
+ if (error.message === 'Operation cancelled by user') {
1214
+ return {
1215
+ success: true,
1216
+ message: `GUI task "${description}" cancelled by user`,
1217
+ result: 'Task cancelled'
1218
+ };
1219
+ }
1220
+
1221
+ // Return failure without throwing - let the main agent handle it
1222
+ return {
1223
+ success: false,
1224
+ message: `GUI task "${description}" failed: ${error.message}`
1225
+ };
1226
+ }
1227
+ }
1228
+
1229
+ private async executeSingleAgent(
1230
+ subagent_type: string,
1231
+ prompt: string,
1232
+ description: string,
1233
+ useContext: boolean,
1234
+ constraints: string[],
1235
+ mode: ExecutionMode,
1236
+ agentManager: any,
1237
+ toolRegistry: any,
1238
+ aiClient: any,
1239
+ config: any,
1240
+ indentLevel: number = 1
1241
+ ): Promise<{ success: boolean; message: string; result?: any }> {
1242
+ const agent = agentManager.getAgent(subagent_type);
1243
+
1244
+ if (!agent) {
1245
+ throw new Error(`Agent ${subagent_type} not found`);
1246
+ }
1247
+
1248
+ // Special handling for gui-subagent: directly call GUIAgent.run() instead of subagent message loop
1249
+ if (subagent_type === 'gui-subagent') {
1250
+ return this.executeGUIAgent(
1251
+ prompt,
1252
+ description,
1253
+ agent,
1254
+ mode,
1255
+ config,
1256
+ indentLevel
1257
+ );
1258
+ }
1259
+
1260
+ // Determine the model to use for this subagent
1261
+ let modelName = config.get('modelName') || 'Qwen3-Coder';
1262
+ let baseUrl = config.get('baseUrl') || 'https://apis.xagent.cn/v1';
1263
+ let apiKey = config.get('apiKey') || '';
1264
+
1265
+ if (agent.model) {
1266
+ // If agent has a model field, it can be a model name or a config reference like 'guiSubagentModel'
1267
+ if (typeof agent.model === 'string' && agent.model.endsWith('Model')) {
1268
+ // It's a config reference, use corresponding config values
1269
+ modelName = config.get(agent.model) || modelName;
1270
+ const baseUrlKey = agent.model.replace('Model', 'BaseUrl');
1271
+ const apiKeyKey = agent.model.replace('Model', 'ApiKey');
1272
+ if (config.get(baseUrlKey)) {
1273
+ baseUrl = config.get(baseUrlKey);
1274
+ }
1275
+ if (config.get(apiKeyKey)) {
1276
+ apiKey = config.get(apiKeyKey);
1277
+ }
1278
+ } else if (typeof agent.model === 'string') {
1279
+ // It's an explicit model name
1280
+ modelName = agent.model;
1281
+ }
1282
+ }
1283
+
1284
+ // Create a new AIClient for this subagent with its specific model
1285
+ const { AIClient: SubAgentAIClient } = await import('./ai-client.js');
1286
+ const subAgentClient = new SubAgentAIClient({
1287
+ type: AuthType.API_KEY,
1288
+ apiKey: apiKey,
1289
+ baseUrl: baseUrl,
1290
+ modelName: modelName,
1291
+ showAIDebugInfo: config.get('showAIDebugInfo') || false
1292
+ });
1293
+
1294
+ const indent = ' '.repeat(indentLevel);
1295
+ const indentNext = ' '.repeat(indentLevel + 1);
1296
+ const agentName = agent.name || subagent_type;
1297
+ const cancellationManager = getCancellationManager();
1298
+ const logger = getLogger();
1299
+ let cancelled = false;
1300
+
1301
+ // Set up raw mode and stdin polling for ESC detection
1302
+ let rawModeEnabled = false;
1303
+ let stdinPollingInterval: NodeJS.Timeout | null = null;
1304
+
1305
+ const setupStdinPolling = () => {
1306
+ if (process.stdin.isTTY) {
1307
+ try {
1308
+ process.stdin.setRawMode(true);
1309
+ rawModeEnabled = true;
1310
+ process.stdin.resume();
1311
+ readline.emitKeypressEvents(process.stdin);
1312
+ } catch (e) {
1313
+ logger.debug(`[TaskTool] Could not set raw mode: ${e}`);
1314
+ }
1315
+
1316
+ // Start polling for ESC key (10ms interval for faster response)
1317
+ stdinPollingInterval = setInterval(() => {
1318
+ try {
1319
+ if (rawModeEnabled) {
1320
+ const chunk = process.stdin.read(1);
1321
+ if (chunk && chunk.length > 0) {
1322
+ const code = chunk[0];
1323
+ if (code === 0x1B) { // ESC
1324
+ logger.debug('[TaskTool] ESC detected via polling!');
1325
+ cancellationManager.cancel();
1326
+ }
1327
+ }
1328
+ }
1329
+ } catch (e) {
1330
+ // Ignore polling errors
1331
+ }
1332
+ }, 10);
1333
+ }
1334
+ };
1335
+
1336
+ const cleanupStdinPolling = () => {
1337
+ if (stdinPollingInterval) {
1338
+ clearInterval(stdinPollingInterval);
1339
+ stdinPollingInterval = null;
1340
+ }
1341
+ };
1342
+
1343
+ // Start polling for ESC
1344
+ setupStdinPolling();
1345
+
1346
+ // Listen for cancellation
1347
+ const cancelHandler = () => {
1348
+ cancelled = true;
1349
+ };
1350
+ cancellationManager.on('cancelled', cancelHandler);
1351
+
1352
+ // Helper function to indent multi-line content
1353
+ const indentMultiline = (content: string, baseIndent: string): string => {
1354
+ return content.split('\n').map(line => `${baseIndent} ${line}`).join('\n');
1355
+ };
1356
+
1357
+ // Check if operation is cancelled
1358
+ const checkCancellation = () => {
1359
+ if (cancelled || cancellationManager.isOperationCancelled()) {
1360
+ cancellationManager.off('cancelled', cancelHandler);
1361
+ cleanupStdinPolling();
1362
+ throw new Error('Operation cancelled by user');
1363
+ }
1364
+ };
1365
+
1366
+ const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, mode, agent);
1367
+ const enhancedSystemPrompt = await systemPromptGenerator.generateEnhancedSystemPrompt(agent.systemPrompt);
1368
+
1369
+ const fullPrompt = constraints.length > 0
1370
+ ? `${prompt}\n\nConstraints:\n${constraints.map(c => `- ${c}`).join('\n')}`
1371
+ : prompt;
1372
+
1373
+ let messages: Message[] = [
1374
+ { role: 'system', content: enhancedSystemPrompt },
1375
+ { role: 'user', content: fullPrompt }
1376
+ ];
1377
+
1378
+ const availableTools = agentManager.getAvailableToolsForAgent(agent, mode);
1379
+ const allToolDefinitions = toolRegistry.getToolDefinitions();
1380
+
1381
+ const toolDefinitions: ToolDefinition[] = availableTools.map((toolName: string) => {
1382
+ const fullDef = allToolDefinitions.find((def: any) => def.function.name === toolName);
1383
+ if (fullDef) {
1384
+ return fullDef;
1385
+ }
1386
+ return {
1387
+ type: 'function' as const,
1388
+ function: {
1389
+ name: toolName,
1390
+ description: `Tool: ${toolName}`,
1391
+ parameters: { type: 'object', properties: {}, required: [] }
1392
+ }
1393
+ };
1394
+ });
1395
+
1396
+ let iteration = 0;
1397
+ const maxIterations = 10;
1398
+
1399
+ while (iteration < maxIterations) {
1400
+ iteration++;
1401
+
1402
+ // Check for cancellation before each iteration
1403
+ checkCancellation();
1404
+
1405
+ // Use withCancellation to make API call cancellable
1406
+ const result = await cancellationManager.withCancellation(
1407
+ subAgentClient.chatCompletion(messages, {
1408
+ tools: toolDefinitions,
1409
+ temperature: 0.7
1410
+ }),
1411
+ `api-${subagent_type}-${iteration}`
1412
+ ) as any;
1413
+
1414
+ // Check for cancellation after API call
1415
+ checkCancellation();
1416
+
1417
+ if (!result || !result.choices || result.choices.length === 0) {
1418
+ throw new Error(`Sub-agent ${subagent_type} returned empty response`);
1419
+ }
1420
+
1421
+ const choice = result.choices[0];
1422
+ const messageContent = choice.message?.content;
1423
+ const toolCalls = choice.message.tool_calls;
1424
+
1425
+ let contentStr: string;
1426
+ let hasValidContent = false;
1427
+
1428
+ if (typeof messageContent === 'string') {
1429
+ contentStr = messageContent;
1430
+ hasValidContent = messageContent.trim() !== '';
1431
+ } else if (Array.isArray(messageContent)) {
1432
+ const textParts = messageContent
1433
+ .filter(item => typeof item?.text === 'string' && item.text.trim() !== '')
1434
+ .map(item => item.text);
1435
+ contentStr = textParts.join('');
1436
+ hasValidContent = textParts.length > 0;
1437
+ } else {
1438
+ contentStr = '';
1439
+ hasValidContent = false;
1440
+ }
1441
+
1442
+ // Only throw empty content error if there's no text content AND no tool calls
1443
+ // When AI model returns tool_calls, message.content can be null/empty, which is valid
1444
+ if (!hasValidContent && (!toolCalls || toolCalls.length === 0)) {
1445
+ throw new Error(`Sub-agent ${subagent_type} returned empty content`);
1446
+ }
1447
+
1448
+ if (choice.finish_reason === 'length') {
1449
+ throw new Error(`Sub-agent ${subagent_type} response truncated due to length limits`);
1450
+ }
1451
+
1452
+ // Add assistant message to conversation
1453
+ messages.push({ role: 'assistant', content: contentStr });
1454
+
1455
+ // Display assistant response (if there's any text content) with proper indentation
1456
+ if (contentStr) {
1457
+ console.log(`\n${indent}${colors.primaryBright(agentName)}: ${description}`);
1458
+ const truncatedContent = contentStr.length > 500 ? contentStr.substring(0, 500) + '...' : contentStr;
1459
+ const indentedContent = indentMultiline(truncatedContent, indent);
1460
+ console.log(`${indentedContent}\n`);
1461
+ }
1462
+
1463
+ // Process tool calls with proper indentation
1464
+ if (toolCalls && toolCalls.length > 0) {
1465
+ for (const toolCall of toolCalls) {
1466
+ const { name, arguments: params } = toolCall.function;
1467
+
1468
+ let parsedParams: any;
1469
+ try {
1470
+ parsedParams = typeof params === 'string' ? JSON.parse(params) : params;
1471
+ } catch (e) {
1472
+ parsedParams = params;
1473
+ }
1474
+
1475
+ console.log(`${indent}${colors.textMuted(`${icons.loading} Tool: ${name}`)}`);
1476
+
1477
+ try {
1478
+ // Check cancellation before tool execution
1479
+ checkCancellation();
1480
+
1481
+ const toolResult = await cancellationManager.withCancellation(
1482
+ toolRegistry.execute(name, parsedParams, mode, indent),
1483
+ `subagent-${subagent_type}-${name}-${iteration}`
1484
+ );
1485
+
1486
+ // Display tool result with proper indentation for multi-line content
1487
+ const resultPreview = typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult, null, 2);
1488
+ const truncatedPreview = resultPreview.length > 200 ? resultPreview.substring(0, 200) + '...' : resultPreview;
1489
+ const indentedPreview = indentMultiline(truncatedPreview, indent);
1490
+ console.log(`${indent}${colors.success(`${icons.check} Completed`)}\n${indentedPreview}\n`);
1491
+
1492
+ messages.push({
1493
+ role: 'tool',
1494
+ content: JSON.stringify(toolResult),
1495
+ tool_call_id: toolCall.id
1496
+ });
1497
+ } catch (error: any) {
1498
+ if (error.message === 'Operation cancelled by user') {
1499
+ console.log(`${indent}${colors.warning(`⚠️ Operation cancelled`)}\n`);
1500
+ cancellationManager.off('cancelled', cancelHandler);
1501
+ cleanupStdinPolling();
1502
+ return {
1503
+ success: false,
1504
+ message: `Task "${description}" cancelled by user`,
1505
+ result: contentStr
1506
+ };
1507
+ }
1508
+ console.log(`${indent}${colors.error(`${icons.cross} Error:`)} ${error.message}\n`);
1509
+
1510
+ messages.push({
1511
+ role: 'tool',
1512
+ content: JSON.stringify({ error: error.message }),
1513
+ tool_call_id: toolCall.id
1514
+ });
1515
+ }
1516
+ }
1517
+ console.log('');
1518
+ continue; // Continue to next iteration to get final response
1519
+ }
1520
+
1521
+ // No more tool calls, return the result
1522
+ cancellationManager.off('cancelled', cancelHandler);
1523
+ cleanupStdinPolling();
1524
+ return {
1525
+ success: true,
1526
+ message: `Task "${description}" completed by ${subagent_type}`,
1527
+ result: contentStr
1528
+ };
1529
+ }
1530
+
1531
+ // Max iterations reached - return accumulated results instead of throwing error
1532
+ cancellationManager.off('cancelled', cancelHandler);
1533
+ cleanupStdinPolling();
1534
+ // Get the last assistant message content
1535
+ const lastAssistantMsg = messages.filter(m => m.role === 'assistant').pop();
1536
+ const lastContent = lastAssistantMsg?.content || '';
1537
+
1538
+ return {
1539
+ success: true,
1540
+ message: `Task "${description}" completed (max iterations reached) by ${subagent_type}`,
1541
+ result: lastContent
1542
+ };
1543
+ }
1544
+
1545
+ private async executeParallelAgents(
1546
+ agents: SubAgentTask[],
1547
+ description: string,
1548
+ mode: ExecutionMode,
1549
+ agentManager: any,
1550
+ toolRegistry: any,
1551
+ aiClient: any,
1552
+ indentLevel: number = 1
1553
+ ): Promise<{ success: boolean; message: string; results: any[]; errors: any[] }> {
1554
+ const indent = ' '.repeat(indentLevel);
1555
+ const indentNext = ' '.repeat(indentLevel + 1);
1556
+ const cancellationManager = getCancellationManager();
1557
+ const logger = getLogger();
1558
+
1559
+ // Set up raw mode and stdin polling for ESC detection
1560
+ let rawModeEnabled = false;
1561
+ let stdinPollingInterval: NodeJS.Timeout | null = null;
1562
+
1563
+ const setupStdinPolling = () => {
1564
+ if (process.stdin.isTTY) {
1565
+ try {
1566
+ process.stdin.setRawMode(true);
1567
+ rawModeEnabled = true;
1568
+ process.stdin.resume();
1569
+ readline.emitKeypressEvents(process.stdin);
1570
+ } catch (e) {
1571
+ logger.debug(`[ParallelAgents] Could not set raw mode: ${e}`);
1572
+ }
1573
+
1574
+ stdinPollingInterval = setInterval(() => {
1575
+ try {
1576
+ if (rawModeEnabled) {
1577
+ const chunk = process.stdin.read(1);
1578
+ if (chunk && chunk.length > 0) {
1579
+ const code = chunk[0];
1580
+ if (code === 0x1B) { // ESC
1581
+ logger.debug('[ParallelAgents] ESC detected via polling!');
1582
+ cancellationManager.cancel();
1583
+ }
1584
+ }
1585
+ }
1586
+ } catch (e) {
1587
+ // Ignore polling errors
1588
+ }
1589
+ }, 10);
1590
+ }
1591
+ };
1592
+
1593
+ const cleanupStdinPolling = () => {
1594
+ if (stdinPollingInterval) {
1595
+ clearInterval(stdinPollingInterval);
1596
+ stdinPollingInterval = null;
1597
+ }
1598
+ };
1599
+
1600
+ // Start polling for ESC
1601
+ setupStdinPolling();
1602
+
1603
+ // Listen for cancellation to stop parallel execution
1604
+ let cancelled = false;
1605
+ const cancelHandler = () => {
1606
+ cancelled = true;
1607
+ };
1608
+ cancellationManager.on('cancelled', cancelHandler);
1609
+
1610
+ console.log(`\n${indent}${colors.accent('◆')} ${colors.primaryBright('Parallel Agents')}: ${agents.length} running...`);
1611
+
1612
+ const startTime = Date.now();
1613
+
1614
+ const agentPromises = agents.map(async (agentTask, index) => {
1615
+ // Check if cancelled
1616
+ if (cancelled || cancellationManager.isOperationCancelled()) {
1617
+ return {
1618
+ success: false,
1619
+ agent: agentTask.subagent_type,
1620
+ description: agentTask.description,
1621
+ error: 'Operation cancelled by user'
1622
+ };
1623
+ }
1624
+
1625
+ try {
1626
+ const result = await this.executeSingleAgent(
1627
+ agentTask.subagent_type,
1628
+ agentTask.prompt,
1629
+ agentTask.description,
1630
+ agentTask.useContext ?? true,
1631
+ agentTask.constraints || [],
1632
+ mode,
1633
+ agentManager,
1634
+ toolRegistry,
1635
+ aiClient,
1636
+ indentLevel + 1
1637
+ );
1638
+
1639
+ return {
1640
+ success: true,
1641
+ agent: agentTask.subagent_type,
1642
+ description: agentTask.description,
1643
+ result: result.result
1644
+ };
1645
+ } catch (error: any) {
1646
+ return {
1647
+ success: false,
1648
+ agent: agentTask.subagent_type,
1649
+ description: agentTask.description,
1650
+ error: error.message
1651
+ };
1652
+ }
1653
+ });
1654
+
1655
+ const results = await Promise.all(agentPromises);
1656
+
1657
+ const duration = Date.now() - startTime;
1658
+
1659
+ const successfulAgents = results.filter(r => r.success);
1660
+ const failedAgents = results.filter(r => !r.success);
1661
+
1662
+ console.log(`${indent}${colors.success('✔')} Parallel task completed in ${colors.textMuted(duration + 'ms')}`);
1663
+ console.log(`${indent}${colors.info('ℹ')} Success: ${successfulAgents.length}/${agents.length} agents\n`);
1664
+
1665
+ if (failedAgents.length > 0) {
1666
+ console.log(`${indent}${colors.error('✖')} Failed agents:`);
1667
+ for (const failed of failedAgents) {
1668
+ console.log(`${indentNext} ${colors.error('•')} ${failed.agent}: ${failed.error}`);
1669
+ }
1670
+ console.log('');
1671
+ }
1672
+
1673
+ // Cleanup
1674
+ cancellationManager.off('cancelled', cancelHandler);
1675
+ cleanupStdinPolling();
1676
+
1677
+ return {
1678
+ success: failedAgents.length === 0,
1679
+ message: `Parallel task "${description}" completed: ${successfulAgents.length}/${agents.length} successful`,
1680
+ results: successfulAgents.map(r => ({
1681
+ agent: r.agent,
1682
+ description: r.description,
1683
+ result: r.result
1684
+ })),
1685
+ errors: failedAgents.map(r => ({
1686
+ agent: r.agent,
1687
+ description: r.description,
1688
+ error: r.error
1689
+ }))
1690
+ };
1691
+ }
1692
+ }
1693
+
1694
+ export class ReadBashOutputTool implements Tool {
1695
+ name = 'ReadBashOutput';
1696
+ description = `Retrieve output from a background task that was started with Bash(run_in_bg=true).
1697
+
1698
+ # When to Use
1699
+ - Checking the output of a long-running background process
1700
+ - Monitoring progress of builds, tests, or servers
1701
+ - Retrieving logs from background tasks
1702
+ - When you started a task with run_in_bg=true and need results
1703
+
1704
+ # When NOT to Use
1705
+ - For synchronous commands (they return output directly)
1706
+ - When the background task hasn't been started yet
1707
+ - For tasks that have already completed (use Bash directly)
1708
+
1709
+ # Parameters
1710
+ - \`task_id\`: The ID returned from the background Bash command
1711
+ - \`poll_interval\`: (Optional) Seconds to wait before checking, default: 10
1712
+
1713
+ # Examples
1714
+ - Check build output: ReadBashOutput(task_id="task_1234567890")
1715
+ - Wait and check: ReadBashOutput(task_id="task_123", poll_interval=5)
1716
+
1717
+ # Best Practices
1718
+ - Save the task_id from Bash response for later use
1719
+ - Use appropriate poll_interval based on expected task duration
1720
+ - Check status to see if task is still running or completed
1721
+ - Combine with todo_write to track background task progress`;
1722
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
1723
+
1724
+ async execute(params: {
1725
+ task_id: string;
1726
+ poll_interval?: number;
1727
+ }): Promise<{ taskId: string; output: string; status: string; duration: number }> {
1728
+ const { task_id, poll_interval = 10 } = params;
1729
+
1730
+ try {
1731
+ const toolRegistry = getToolRegistry();
1732
+ const task = (toolRegistry as any).getBackgroundTask(task_id);
1733
+
1734
+ if (!task) {
1735
+ throw new Error(`Task ${task_id} not found`);
1736
+ }
1737
+
1738
+ const interval = Math.min(Math.max(poll_interval, 1), 120);
1739
+ await new Promise(resolve => setTimeout(resolve, interval * 1000));
1740
+
1741
+ const duration = Date.now() - task.startTime;
1742
+ const output = task.output.join('');
1743
+ const status = task.process.exitCode === null ? 'running' : 'completed';
1744
+
1745
+ return {
1746
+ taskId: task_id,
1747
+ output,
1748
+ status,
1749
+ duration: Math.floor(duration / 1000)
1750
+ };
1751
+ } catch (error: any) {
1752
+ throw new Error(`Failed to read bash output: ${error.message}`);
1753
+ }
1754
+ }
1755
+ }
1756
+
1757
+ export class WebFetchTool implements Tool {
1758
+ name = 'web_fetch';
1759
+ description = `Fetch and extract content from a specific URL. This tool retrieves the full content of a webpage.
1760
+
1761
+ # When to Use
1762
+ - When you have a specific URL and need its content
1763
+ - Extracting documentation from web pages
1764
+ - Fetching API documentation or guides
1765
+ - Getting content from known URLs (not for searching)
1766
+
1767
+ # When NOT to Use
1768
+ - When you need to search but don't have a specific URL (use web_search first)
1769
+ - For pages requiring authentication or login
1770
+ - For very large files or pages (may timeout)
1771
+ - When the URL format is unknown (use web_search first)
1772
+
1773
+ # Parameters
1774
+ - \`prompt\`: A prompt containing the URL to fetch (e.g., "Summarize https://example.com/docs")
1775
+
1776
+ # Examples
1777
+ - Fetch documentation: web_fetch(prompt="Extract key points from https://react.dev/docs")
1778
+ - Get API spec: web_fetch(prompt="Fetch the OpenAPI spec from https://api.example.com/openapi.json")
1779
+
1780
+ # Best Practices
1781
+ - Ensure the URL is accessible and doesn't require authentication
1782
+ - Use specific prompts to extract relevant information
1783
+ - Check if the page is accessible if you get errors
1784
+ - Large pages may be truncated due to size limits`;
1785
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
1786
+
1787
+ async execute(params: { prompt: string }): Promise<{ content: string; url: string; status: number }> {
1788
+ const { prompt } = params;
1789
+
1790
+ try {
1791
+ const urlMatch = prompt.match(/https?:\/\/[^\s]+/i);
1792
+
1793
+ if (!urlMatch) {
1794
+ throw new Error('No URL found in prompt');
1795
+ }
1796
+
1797
+ const url = urlMatch[0];
1798
+
1799
+ const response = await axios.get(url, {
1800
+ timeout: 30000,
1801
+ maxContentLength: 10 * 1024 * 1024,
1802
+ validateStatus: () => true
1803
+ });
1804
+
1805
+ let content = response.data;
1806
+
1807
+ if (typeof content === 'object') {
1808
+ content = JSON.stringify(content, null, 2);
1809
+ }
1810
+
1811
+ return {
1812
+ content,
1813
+ url,
1814
+ status: response.status
1815
+ };
1816
+ } catch (error: any) {
1817
+ throw new Error(`Failed to fetch URL: ${error.message}`);
1818
+ }
1819
+ }
1820
+ }
1821
+
1822
+ export class AskUserQuestionTool implements Tool {
1823
+ name = 'ask_user_question';
1824
+ description = `Ask the user questions during execution to gather input, preferences, or clarifications.
1825
+
1826
+ # When to Use
1827
+ - When you need user input or preferences to proceed
1828
+ - When a task has multiple options and user should choose
1829
+ - When clarification is needed for ambiguous requests
1830
+ - When user explicitly asks to be prompted
1831
+
1832
+ # When NOT to Use
1833
+ - When you can make reasonable assumptions
1834
+ - For simple confirmations (just proceed with reasonable default)
1835
+ - When the information is already available in context
1836
+ - For information you should know or can infer
1837
+
1838
+ # Parameters
1839
+ - \`questions\`: Array of questions with:
1840
+ - \`question\`: The question text
1841
+ - \`header\`: (Optional) Short label for the question
1842
+ - \`options\`: (Optional) Multiple choice options
1843
+ - \`multiSelect\`: (Optional) Allow multiple selections
1844
+
1845
+ # Examples
1846
+ - Simple input: Ask user their preferred name
1847
+ - Multiple choice: Ask which framework to use (React, Vue, Angular)
1848
+ - Multi-select: Ask which features to include (with checkboxes)
1849
+
1850
+ # Best Practices
1851
+ - Limit to 1-4 questions at a time
1852
+ - Provide options when possible for faster response
1853
+ - Use multiSelect=true when multiple answers are valid
1854
+ - Be clear and concise in question wording`;
1855
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
1856
+
1857
+ async execute(params: {
1858
+ questions: Array<{
1859
+ question: string;
1860
+ header?: string;
1861
+ options?: string[];
1862
+ multiSelect?: boolean;
1863
+ }>;
1864
+ }): Promise<{ answers: string[] }> {
1865
+ const { questions } = params;
1866
+
1867
+ try {
1868
+ if (questions.length === 0 || questions.length > 4) {
1869
+ throw new Error('Must provide 1-4 questions');
1870
+ }
1871
+
1872
+ const answers: string[] = [];
1873
+
1874
+ for (const q of questions) {
1875
+ if (q.options && q.options.length > 0) {
1876
+ const result = await inquirer.prompt([
1877
+ {
1878
+ type: q.multiSelect ? 'checkbox' : 'list',
1879
+ name: 'answer',
1880
+ message: q.question,
1881
+ choices: q.options,
1882
+ default: q.multiSelect ? [] : q.options[0]
1883
+ }
1884
+ ]);
1885
+
1886
+ answers.push(Array.isArray(result.answer) ? result.answer.join(', ') : result.answer);
1887
+ } else {
1888
+ const result = await inquirer.prompt([
1889
+ {
1890
+ type: 'input',
1891
+ name: 'answer',
1892
+ message: q.question
1893
+ }
1894
+ ]);
1895
+
1896
+ answers.push(result.answer);
1897
+ }
1898
+ }
1899
+
1900
+ return { answers };
1901
+ } catch (error: any) {
1902
+ throw new Error(`Failed to ask user questions: ${error.message}`);
1903
+ }
1904
+ }
1905
+ }
1906
+
1907
+ export class SaveMemoryTool implements Tool {
1908
+ name = 'save_memory';
1909
+ description = `Save specific information to long-term memory for future sessions. Useful for remembering user preferences, project conventions, or important facts.
1910
+
1911
+ # When to Use
1912
+ - User explicitly asks to "remember" something
1913
+ - User provides preferences or configuration details
1914
+ - Important project conventions or patterns to remember
1915
+ - Information that should persist across sessions
1916
+
1917
+ # When NOT to Use
1918
+ - For temporary information only needed in current session
1919
+ - For information already in project files or configuration
1920
+ - For obvious or trivial facts
1921
+ - When user doesn't explicitly want information saved
1922
+
1923
+ # Parameters
1924
+ - \`fact\`: The specific fact or information to remember
1925
+
1926
+ # Examples
1927
+ - Remember user preference: save_memory(fact="User prefers TypeScript over JavaScript")
1928
+ - Remember project convention: save_memory(fact="Project uses kebab-case for component files")
1929
+ - Remember important context: save_memory(fact="API endpoint is https://api.example.com/v2")
1930
+
1931
+ # Best Practices
1932
+ - Save only when user explicitly requests or provides clear preference
1933
+ - Keep facts concise and specific
1934
+ - Remember project-specific conventions for consistency
1935
+ - This persists across sessions (global memory)`;
1936
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
1937
+
1938
+ async execute(params: { fact: string }): Promise<{ success: boolean; message: string }> {
1939
+ const { fact } = params;
1940
+
1941
+ try {
1942
+ const { getMemoryManager } = await import('./memory.js');
1943
+ const memoryManager = getMemoryManager(process.cwd());
1944
+
1945
+ await memoryManager.saveMemory(fact, 'global');
1946
+
1947
+ return {
1948
+ success: true,
1949
+ message: `Successfully saved fact to memory`
1950
+ };
1951
+ } catch (error: any) {
1952
+ throw new Error(`Failed to save memory: ${error.message}`);
1953
+ }
1954
+ }
1955
+ }
1956
+
1957
+ export class ExitPlanModeTool implements Tool {
1958
+ name = 'exit_plan_mode';
1959
+ description = `Complete plan presentation in plan mode and transition to execution. This tool is used when you have finished planning and are ready to implement.
1960
+
1961
+ # When to Use
1962
+ - When you have completed creating a plan or design document
1963
+ - When the plan is ready for review and execution
1964
+ - After presenting the full implementation plan to the user
1965
+ - When ready to transition from planning to coding
1966
+
1967
+ # When NOT to Use
1968
+ - When still in the middle of planning (continue planning first)
1969
+ - When the plan needs revision based on feedback
1970
+ - When user hasn't reviewed the plan yet
1971
+ - In non-plan execution modes
1972
+
1973
+ # Parameters
1974
+ - \`plan\`: The complete plan text to be saved and executed
1975
+
1976
+ # Examples
1977
+ - Exit after creating implementation plan
1978
+ - Present final design and exit to implementation
1979
+
1980
+ # Best Practices
1981
+ - Ensure the plan is complete and comprehensive
1982
+ - Include all necessary steps and considerations
1983
+ - The plan will be saved for reference during execution
1984
+ - Use this only when truly ready to start coding`;
1985
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
1986
+
1987
+ async execute(params: { plan: string }): Promise<{ success: boolean; message: string; plan: string }> {
1988
+ const { plan } = params;
1989
+
1990
+ try {
1991
+ return {
1992
+ success: true,
1993
+ message: 'Plan completed and ready for execution',
1994
+ plan
1995
+ };
1996
+ } catch (error: any) {
1997
+ throw new Error(`Failed to exit plan mode: ${error.message}`);
1998
+ }
1999
+ }
2000
+ }
2001
+
2002
+ export class XmlEscapeTool implements Tool {
2003
+ name = 'xml_escape';
2004
+ description = `Automatically escape special characters in XML/HTML files to make them valid.
2005
+
2006
+ # When to Use
2007
+ - When content contains special XML characters (<, >, &, ", ')
2008
+ - When generating XML/HTML from raw content
2009
+ - When fixing encoding issues in markup files
2010
+
2011
+ # When NOT to Use
2012
+ - For files that should contain raw XML/HTML
2013
+ - For JavaScript, CSS, or other non-XML files
2014
+ - When escaping should be done manually
2015
+
2016
+ # Parameters
2017
+ - \`file_path\`: Path to the file to escape
2018
+ - \`escape_all\`: (Optional) Also escape additional entities (©, ®, €)
2019
+
2020
+ # Examples
2021
+ - Escape XML content in HTML file
2022
+ - Fix special characters in generated markup
2023
+
2024
+ # Best Practices
2025
+ - Backup files before escaping if unsure
2026
+ - escape_all=true adds common HTML entities`;
2027
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
2028
+
2029
+ async execute(params: {
2030
+ file_path: string;
2031
+ escape_all?: boolean;
2032
+ }): Promise<{ success: boolean; message: string; changes: number }> {
2033
+ const { file_path, escape_all = false } = params;
2034
+
2035
+ try {
2036
+ const absolutePath = path.resolve(file_path);
2037
+ let content = await fs.readFile(absolutePath, 'utf-8');
2038
+
2039
+ const specialChars = [
2040
+ { char: '&', replacement: '&amp;' },
2041
+ { char: '<', replacement: '&lt;' },
2042
+ { char: '>', replacement: '&gt;' },
2043
+ { char: '"', replacement: '&quot;' },
2044
+ { char: "'", replacement: '&apos;' }
2045
+ ];
2046
+
2047
+ let changes = 0;
2048
+
2049
+ for (const { char, replacement } of specialChars) {
2050
+ const regex = new RegExp(this.escapeRegExp(char), 'g');
2051
+ const matches = content.match(regex);
2052
+ if (matches) {
2053
+ changes += matches.length;
2054
+ content = content.replace(regex, replacement);
2055
+ }
2056
+ }
2057
+
2058
+ if (escape_all) {
2059
+ const additionalChars = [
2060
+ { char: '©', replacement: '&copy;' },
2061
+ { char: '®', replacement: '&reg;' },
2062
+ { char: '€', replacement: '&euro;' }
2063
+ ];
2064
+
2065
+ for (const { char, replacement } of additionalChars) {
2066
+ const regex = new RegExp(this.escapeRegExp(char), 'g');
2067
+ const matches = content.match(regex);
2068
+ if (matches) {
2069
+ changes += matches.length;
2070
+ content = content.replace(regex, replacement);
2071
+ }
2072
+ }
2073
+ }
2074
+
2075
+ await fs.writeFile(absolutePath, content, 'utf-8');
2076
+
2077
+ return {
2078
+ success: true,
2079
+ message: `Successfully escaped ${changes} character(s) in ${file_path}`,
2080
+ changes
2081
+ };
2082
+ } catch (error: any) {
2083
+ throw new Error(`Failed to escape XML/HTML in file ${file_path}: ${error.message}`);
2084
+ }
2085
+ }
2086
+
2087
+ private escapeRegExp(string: string): string {
2088
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2089
+ }
2090
+ }
2091
+
2092
+ export class ImageReadTool implements Tool {
2093
+ name = 'image_read';
2094
+ description = `Read image files and generate detailed analysis using a vision-language model.
2095
+
2096
+ # When to Use
2097
+ - Analyzing UI designs or mockups
2098
+ - Examining screenshots or diagrams
2099
+ - Extracting information from images
2100
+ - Validating visual content or assets
2101
+
2102
+ # When NOT to Use
2103
+ - For text-based file analysis (use Read instead)
2104
+ - When the image is not relevant to the task
2105
+ - For very large images (may have size limits)
2106
+
2107
+ # Parameters
2108
+ - \`image_input\`: Path to image or base64 data
2109
+ - \`prompt\`: Instructions for what to analyze
2110
+ - \`input_type\`: (Optional) 'file_path' or 'base64'
2111
+ - \`task_brief\`: (Optional) Brief task description
2112
+
2113
+ # Examples
2114
+ - Analyze UI mockup: image_read(image_input="design.png", prompt="Describe the UI components")
2115
+ - Validate screenshot: image_read(image_input="screenshot.jpg", prompt="Check if login form is visible")
2116
+
2117
+ # Best Practices
2118
+ - Provide clear prompts for what to look for
2119
+ - Use task_brief for context
2120
+ - Supports PNG, JPG, GIF, WEBP, SVG, BMP`;
2121
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
2122
+
2123
+ async execute(params: {
2124
+ image_input: string;
2125
+ prompt: string;
2126
+ task_brief?: string;
2127
+ input_type?: 'file_path' | 'base64';
2128
+ mime_type?: string;
2129
+ }): Promise<{ analysis: string; image_info: any }> {
2130
+ const { image_input, prompt, task_brief, input_type = 'file_path', mime_type } = params;
2131
+
2132
+ try {
2133
+ let imageData: string;
2134
+
2135
+ if (input_type === 'file_path') {
2136
+ const absolutePath = path.resolve(image_input);
2137
+ const imageBuffer = await fs.readFile(absolutePath);
2138
+ imageData = imageBuffer.toString('base64');
2139
+ } else {
2140
+ imageData = image_input;
2141
+ }
2142
+
2143
+ const { AIClient } = await import('./ai-client.js');
2144
+ const configManager = await import('./config.js');
2145
+ const { getConfigManager } = configManager;
2146
+ const config = getConfigManager();
2147
+
2148
+ const aiClient = new AIClient({
2149
+ type: AuthType.API_KEY,
2150
+ apiKey: config.get('apiKey'),
2151
+ baseUrl: config.get('baseUrl'),
2152
+ modelName: config.get('modelName') || 'Qwen3-Coder'
2153
+ });
2154
+
2155
+ const textContent = task_brief ? `${task_brief}\n\n${prompt}` : prompt;
2156
+ const messages: Message[] = [
2157
+ {
2158
+ role: 'user',
2159
+ content: [
2160
+ {
2161
+ type: 'text',
2162
+ text: textContent
2163
+ },
2164
+ {
2165
+ type: 'image_url' as const,
2166
+ image_url: {
2167
+ url: `data:${mime_type || 'image/jpeg'};base64,${imageData}`
2168
+ }
2169
+ }
2170
+ ]
2171
+ }
2172
+ ];
2173
+
2174
+ const result = await aiClient.chatCompletion(messages, {
2175
+ temperature: 0.7
2176
+ });
2177
+
2178
+ const messageContent = result.choices[0]?.message?.content;
2179
+ const analysis = typeof messageContent === 'string' ? messageContent : '';
2180
+
2181
+ return {
2182
+ analysis,
2183
+ image_info: {
2184
+ input_type,
2185
+ prompt,
2186
+ task_brief
2187
+ }
2188
+ };
2189
+ } catch (error: any) {
2190
+ throw new Error(`Failed to read image: ${error.message}`);
2191
+ }
2192
+ }
2193
+ }
2194
+
2195
+ // export class SkillTool implements Tool {
2196
+ // name = 'Skill';
2197
+ // description = `Execute pre-defined workflows (skills) from the xAgent marketplace. Skills are reusable workflows that automate common tasks.
2198
+
2199
+ // # When to Use
2200
+ // - When a skill exists for the requested task
2201
+ // - When you need to run a multi-step workflow
2202
+ // - When the task matches a marketplace workflow
2203
+
2204
+ // # When NOT to Use
2205
+ // - When a simple tool can accomplish the task
2206
+ // - When creating new functionality from scratch
2207
+ // - When skill doesn't exist for the specific task
2208
+
2209
+ // # Parameters
2210
+ // - \`skill\`: The skill/workflow name to execute
2211
+
2212
+ // # Examples
2213
+ // - Execute a PDF processing skill
2214
+ // - Run a data analysis workflow
2215
+
2216
+ // # Best Practices
2217
+ // - Skills are pre-configured workflows from the marketplace
2218
+ // - Check if a relevant skill exists first`;
2219
+ // allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
2220
+
2221
+ // async execute(params: { skill: string }): Promise<{ success: boolean; message: string; result?: any }> {
2222
+ // const { skill } = params;
2223
+
2224
+ // try {
2225
+ // const { getWorkflowManager } = await import('./workflow.js');
2226
+ // const workflowManager = getWorkflowManager(process.cwd());
2227
+
2228
+ // const workflow = workflowManager.getWorkflow(skill);
2229
+
2230
+ // if (!workflow) {
2231
+ // throw new Error(`Skill ${skill} not found`);
2232
+ // }
2233
+
2234
+ // await workflowManager.executeWorkflow(skill, 'Execute skill');
2235
+
2236
+ // return {
2237
+ // success: true,
2238
+ // message: `Successfully executed skill: ${skill}`,
2239
+ // result: workflow
2240
+ // };
2241
+ // } catch (error: any) {
2242
+ // throw new Error(`Failed to execute skill: ${error.message}`);
2243
+ // }
2244
+ // }
2245
+ // }
2246
+
2247
+ export class InvokeSkillTool implements Tool {
2248
+ name = 'InvokeSkill';
2249
+ description = `Invoke a specialized skill to handle domain-specific tasks. Skills are AI-powered capabilities that understand complex requirements and generate high-quality outputs.
2250
+
2251
+ # When to Use
2252
+ - When user requests involve document processing (Word, PDF, PowerPoint)
2253
+ - When user wants to create frontend interfaces or web applications
2254
+ - When user needs visual design, posters, or generative art
2255
+ - When user asks for documentation or internal communications
2256
+ - When the task matches a specific skill domain
2257
+
2258
+ # When NOT to Use
2259
+ - For simple file operations (use Read/Write instead)
2260
+ - For basic code changes (use Replace/Write instead)
2261
+ - When a regular tool can accomplish the task
2262
+
2263
+ # Parameters
2264
+ - \`skillId\`: The skill identifier (e.g., "docx", "frontend-design", "canvas-design")
2265
+ - \`taskDescription\`: Detailed description of what to accomplish
2266
+ - \`inputFile\`: (Optional) Path to input file if applicable
2267
+ - \`outputFile\`: (Optional) Desired output file path
2268
+ - \`options\`: (Optional) Additional options for the skill
2269
+
2270
+ # Examples
2271
+ - "Create a Word document with contract terms" → InvokeSkill(skillId="docx", taskDescription="Create a professional Word document with contract terms, including numbered sections, signature blocks, and professional formatting")
2272
+ - "Build a landing page for a product" → InvokeSkill(skillId="frontend-design", taskDescription="Create a visually striking landing page with hero section, features, pricing, and footer. Use bold typography and animations.")
2273
+ - "Create a poster for a music festival" → InvokeSkill(skillId="canvas-design", taskDescription="Create a poster for an electronic music festival. The topic is subtle reference to techno culture and underground rave scene.")
2274
+ - "Write API documentation" → InvokeSkill(skillId="doc-coauthoring", taskDescription="Write comprehensive API documentation for a REST API including endpoints, request/response examples, and error handling")
2275
+
2276
+ # Best Practices
2277
+ - Provide detailed task descriptions for better results
2278
+ - Include relevant file paths when working with existing files
2279
+ - Match the skill to the domain (e.g., don't use frontend-design for Word docs)
2280
+ - Skills will guide you through their specific workflows`;
2281
+ allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
2282
+
2283
+ async execute(params: {
2284
+ skillId: string;
2285
+ taskDescription: string;
2286
+ inputFile?: string;
2287
+ outputFile?: string;
2288
+ options?: Record<string, any>;
2289
+ }, _executionMode?: ExecutionMode): Promise<{
2290
+ success: boolean;
2291
+ message: string;
2292
+ skill: string;
2293
+ task: string;
2294
+ result?: any;
2295
+ files?: string[];
2296
+ /** 告诉 Agent 接下来要做什么 */
2297
+ nextSteps?: Array<{
2298
+ step: number;
2299
+ action: string;
2300
+ description: string;
2301
+ command?: string;
2302
+ file?: string;
2303
+ reason: string;
2304
+ }>;
2305
+ guidance?: string;
2306
+ }> {
2307
+ const { skillId, taskDescription, inputFile, outputFile, options } = params;
2308
+
2309
+ try {
2310
+ const { getSkillInvoker } = await import('./skill-invoker.js');
2311
+ const { SkillExecutionParams } = await import('./skill-invoker.js') as any;
2312
+ const skillInvoker = getSkillInvoker();
2313
+
2314
+ await skillInvoker.initialize();
2315
+
2316
+ // Verify skill exists
2317
+ const skillDetails = await skillInvoker.getSkillDetails(skillId);
2318
+ if (!skillDetails) {
2319
+ // Try to auto-match the skill
2320
+ const match = await skillInvoker.matchSkill(taskDescription);
2321
+ if (match) {
2322
+ return {
2323
+ success: true,
2324
+ message: `Auto-matched skill: ${match.skill.name} (${match.category})`,
2325
+ skill: match.skill.id,
2326
+ task: taskDescription,
2327
+ result: {
2328
+ category: match.category,
2329
+ confidence: match.confidence,
2330
+ matchedKeywords: match.matchedKeywords
2331
+ },
2332
+ guidance: '请按照匹配到的技能继续执行任务。'
2333
+ };
2334
+ }
2335
+ throw new Error(`Skill not found: ${skillId}`);
2336
+ }
2337
+
2338
+ const result = await skillInvoker.executeSkill({
2339
+ skillId,
2340
+ taskDescription,
2341
+ inputFile,
2342
+ outputFile,
2343
+ options
2344
+ });
2345
+
2346
+ if (result.success) {
2347
+ // 生成指导信息,告诉 Agent 接下来要做什么
2348
+ let guidance = '';
2349
+ if (result.nextSteps && result.nextSteps.length > 0) {
2350
+ guidance = `\n## 🎯 下一步操作\n\n请按照以下步骤继续执行任务:\n\n`;
2351
+ for (const step of result.nextSteps) {
2352
+ guidance += `### 步骤 ${step.step}: ${step.action}\n`;
2353
+ guidance += `- **描述**: ${step.description}\n`;
2354
+ guidance += `- **原因**: ${step.reason}\n`;
2355
+ if (step.command) {
2356
+ guidance += `- **命令**: \`${step.command}\`\n`;
2357
+ }
2358
+ if (step.file) {
2359
+ guidance += `- **文件**: ${step.file}\n`;
2360
+ }
2361
+ guidance += '\n';
2362
+ }
2363
+ guidance += `---\n**重要**: 上述步骤是根据 SKILL.md 自动生成的执行指南。请按照这些步骤继续完成任务,而不是结束对话。\n`;
2364
+ }
2365
+
2366
+ return {
2367
+ success: true,
2368
+ message: `技能已激活: ${skillDetails.name}`,
2369
+ skill: skillId,
2370
+ task: taskDescription,
2371
+ result: result.output + (guidance ? guidance : ''),
2372
+ files: result.files,
2373
+ nextSteps: result.nextSteps,
2374
+ guidance: guidance
2375
+ };
2376
+ } else {
2377
+ throw new Error(result.error);
2378
+ }
2379
+ } catch (error: any) {
2380
+ throw new Error(`Failed to invoke skill: ${error.message}`);
2381
+ }
2382
+ }
2383
+ }
2384
+
2385
+ // export class ListSkillsTool implements Tool {
2386
+ // name = 'ListSkills';
2387
+ // description = `List all available skills from the xAgent skills library. Use this tool when you need to:
2388
+ // - See what skills are available
2389
+ // - Find skills that match a user's request
2390
+ // - Get an overview of capabilities
2391
+
2392
+ // This returns a list of all skills with their names, descriptions, and categories.`;
2393
+
2394
+ // allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
2395
+
2396
+ // async execute(): Promise<{ success: boolean; skills: any[] }> {
2397
+ // try {
2398
+ // const { getWorkflowManager } = await import('./workflow.js');
2399
+ // const workflowManager = getWorkflowManager(process.cwd());
2400
+ // const skills = await workflowManager.listSkills();
2401
+
2402
+ // return {
2403
+ // success: true,
2404
+ // skills: skills.map(s => ({
2405
+ // id: s.id,
2406
+ // name: s.name,
2407
+ // description: s.description,
2408
+ // category: s.category
2409
+ // }))
2410
+ // };
2411
+ // } catch (error: any) {
2412
+ // throw new Error(`Failed to list skills: ${error.message}`);
2413
+ // }
2414
+ // }
2415
+ // }
2416
+
2417
+ // export class GetSkillDetailsTool implements Tool {
2418
+ // name = 'GetSkillDetails';
2419
+ // description = `Get detailed information about a specific skill. Use this tool when:
2420
+ // - You want to understand what a skill does before executing it
2421
+ // - You need the full skill documentation to help the user
2422
+ // - You need to verify a skill exists before using it
2423
+
2424
+ // # Parameters
2425
+ // - \`skill\`: The skill name/id to get details for
2426
+
2427
+ // # Returns
2428
+ // The full skill documentation including instructions, examples, and guidelines.`;
2429
+
2430
+ // allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
2431
+
2432
+ // async execute(params: { skill: string }): Promise<{ success: boolean; details: any }> {
2433
+ // const { skill } = params;
2434
+
2435
+ // if (!skill) {
2436
+ // throw new Error('Skill parameter is required');
2437
+ // }
2438
+
2439
+ // try {
2440
+ // const { getWorkflowManager } = await import('./workflow.js');
2441
+ // const workflowManager = getWorkflowManager(process.cwd());
2442
+ // const details = await workflowManager.getSkillDetails(skill);
2443
+
2444
+ // if (!details) {
2445
+ // throw new Error(`Skill '${skill}' not found`);
2446
+ // }
2447
+
2448
+ // return {
2449
+ // success: true,
2450
+ // details: {
2451
+ // id: details.id,
2452
+ // name: details.name,
2453
+ // description: details.description,
2454
+ // category: details.category,
2455
+ // content: details.content
2456
+ // }
2457
+ // };
2458
+ // } catch (error: any) {
2459
+ // throw new Error(`Failed to get skill details: ${error.message}`);
2460
+ // }
2461
+ // }
2462
+ // }
2463
+
2464
+ export class ToolRegistry {
2465
+ private tools: Map<string, Tool> = new Map();
2466
+ private todoWriteTool: TodoWriteTool;
2467
+ private backgroundTasks: Map<string, { process: any; startTime: number; output: string[] }> = new Map();
2468
+
2469
+ constructor() {
2470
+ this.todoWriteTool = new TodoWriteTool();
2471
+ this.registerDefaultTools();
2472
+ }
2473
+
2474
+ private registerDefaultTools(): void {
2475
+ this.register(new ReadTool());
2476
+ this.register(new WriteTool());
2477
+ this.register(new GrepTool());
2478
+ this.register(new BashTool());
2479
+ this.register(new ListDirectoryTool());
2480
+ this.register(new SearchCodebaseTool());
2481
+ this.register(new DeleteFileTool());
2482
+ this.register(new CreateDirectoryTool());
2483
+ this.register(new ReplaceTool());
2484
+ this.register(new WebSearchTool());
2485
+ this.register(this.todoWriteTool);
2486
+ this.register(new TodoReadTool(this.todoWriteTool));
2487
+ this.register(new TaskTool());
2488
+ this.register(new ReadBashOutputTool());
2489
+ this.register(new WebFetchTool());
2490
+ this.register(new AskUserQuestionTool());
2491
+ this.register(new SaveMemoryTool());
2492
+ this.register(new ExitPlanModeTool());
2493
+ this.register(new XmlEscapeTool());
2494
+ this.register(new ImageReadTool());
2495
+ // Deprecated: Use InvokeSkillTool instead (2026-01-17)
2496
+ // this.register(new SkillTool());
2497
+ // this.register(new ListSkillsTool());
2498
+ // this.register(new GetSkillDetailsTool());
2499
+ this.register(new InvokeSkillTool());
2500
+ // GUI Subagent Tools
2501
+ // this.register(new GUIOperateTool());
2502
+ // this.register(new GUIScreenshotTool());
2503
+ // this.register(new GUICleanupTool());
2504
+ }
2505
+
2506
+ register(tool: Tool): void {
2507
+ this.tools.set(tool.name, tool);
2508
+ }
2509
+
2510
+ /**
2511
+ * Register MCP tools with their simple names (without server prefix)
2512
+ * This allows the LLM to call MCP tools using simple names like "create_issue"
2513
+ * instead of "github__create_issue"
2514
+ */
2515
+ registerMCPTools(mcpTools: Map<string, any>): void {
2516
+ let registeredCount = 0;
2517
+
2518
+ for (const [fullName, tool] of mcpTools) {
2519
+ const [serverName, originalName] = fullName.split('__');
2520
+ if (!originalName) {
2521
+ continue;
2522
+ }
2523
+
2524
+ // Auto-rename if conflict, ensure unique name
2525
+ let toolName = originalName;
2526
+ let suffix = 1;
2527
+
2528
+ while (this.tools.has(toolName)) {
2529
+ const existingTool = this.tools.get(toolName);
2530
+ const existingIsMcp = (existingTool as any)._isMcpTool;
2531
+
2532
+ if (existingIsMcp && (existingTool as any)._mcpFullName === fullName) {
2533
+ // Same MCP tool already registered, skip silently
2534
+ break;
2535
+ }
2536
+
2537
+ // Conflict - auto-rename with suffix
2538
+ toolName = `${originalName}_mcp${suffix}`;
2539
+ suffix++;
2540
+ }
2541
+
2542
+ if (!this.tools.has(toolName)) {
2543
+ // Create a wrapper tool for the MCP tool - hide MCP origin from LLM
2544
+ const mcpTool: any = {
2545
+ name: toolName,
2546
+ description: tool.description || 'MCP tool',
2547
+ allowedModes: [ExecutionMode.YOLO, ExecutionMode.SMART, ExecutionMode.ACCEPT_EDITS],
2548
+ inputSchema: tool.inputSchema,
2549
+ _isMcpTool: true,
2550
+ _mcpServerName: serverName,
2551
+ _mcpFullName: fullName,
2552
+ execute: async (params: any) => {
2553
+ const { getMCPManager } = await import('./mcp.js');
2554
+ const mcpManager = getMCPManager();
2555
+ return await mcpManager.callTool(fullName, params);
2556
+ }
2557
+ };
2558
+ this.tools.set(toolName, mcpTool);
2559
+ registeredCount++;
2560
+
2561
+ if (toolName !== originalName) {
2562
+ console.log(`[MCP] Tool '${originalName}' renamed to '${toolName}' to avoid conflict`);
2563
+ }
2564
+ }
2565
+ }
2566
+
2567
+ if (registeredCount > 0) {
2568
+ console.log(`[MCP] Registered ${registeredCount} tool(s)`);
2569
+ }
2570
+ }
2571
+
2572
+ /**
2573
+ * Remove all MCP tool wrappers (useful when MCP servers are removed)
2574
+ */
2575
+ unregisterMCPTools(serverName?: string): void {
2576
+ for (const [name, tool] of this.tools) {
2577
+ // Remove MCP tool wrappers by checking marker
2578
+ const mcpTool = tool as any;
2579
+ if (mcpTool._isMcpTool && (!serverName || mcpTool._mcpServerName === serverName)) {
2580
+ this.tools.delete(name);
2581
+ }
2582
+ }
2583
+ }
2584
+
2585
+ unregister(toolName: string): void {
2586
+ this.tools.delete(toolName);
2587
+ }
2588
+
2589
+ get(toolName: string): Tool | undefined {
2590
+ return this.tools.get(toolName);
2591
+ }
2592
+
2593
+ getAll(): Tool[] {
2594
+ return Array.from(this.tools.values());
2595
+ }
2596
+
2597
+ addBackgroundTask(taskId: string, task: { process: any; startTime: number; output: string[] }): void {
2598
+ this.backgroundTasks.set(taskId, task);
2599
+ }
2600
+
2601
+ getBackgroundTask(taskId: string): { process: any; startTime: number; output: string[] } | undefined {
2602
+ return this.backgroundTasks.get(taskId);
2603
+ }
2604
+
2605
+ removeBackgroundTask(taskId: string): void {
2606
+ this.backgroundTasks.delete(taskId);
2607
+ }
2608
+
2609
+ getToolDefinitions(): any[] {
2610
+ return Array.from(this.tools.values()).map(tool => {
2611
+ let parameters: any = {
2612
+ type: 'object',
2613
+ properties: {},
2614
+ required: []
2615
+ };
2616
+
2617
+ // Define specific parameters for each tool
2618
+ switch (tool.name) {
2619
+ case 'Read':
2620
+ parameters = {
2621
+ type: 'object',
2622
+ properties: {
2623
+ filePath: {
2624
+ type: 'string',
2625
+ description: 'The absolute path to the file to read'
2626
+ },
2627
+ offset: {
2628
+ type: 'number',
2629
+ description: 'Optional: Line number to start reading from (0-based)'
2630
+ },
2631
+ limit: {
2632
+ type: 'number',
2633
+ description: 'Optional: Maximum number of lines to read'
2634
+ }
2635
+ },
2636
+ required: ['filePath']
2637
+ };
2638
+ break;
2639
+
2640
+ case 'Write':
2641
+ parameters = {
2642
+ type: 'object',
2643
+ properties: {
2644
+ filePath: {
2645
+ type: 'string',
2646
+ description: 'The absolute path to the file to write'
2647
+ },
2648
+ content: {
2649
+ type: 'string',
2650
+ description: 'The content to write to the file'
2651
+ }
2652
+ },
2653
+ required: ['filePath', 'content']
2654
+ };
2655
+ break;
2656
+
2657
+ case 'Grep':
2658
+ parameters = {
2659
+ type: 'object',
2660
+ properties: {
2661
+ pattern: {
2662
+ type: 'string',
2663
+ description: 'The regex pattern to search for'
2664
+ },
2665
+ path: {
2666
+ type: 'string',
2667
+ description: 'Optional: The path to search in (default: current directory)'
2668
+ },
2669
+ include: {
2670
+ type: 'string',
2671
+ description: 'Optional: Glob pattern to filter files'
2672
+ },
2673
+ case_sensitive: {
2674
+ type: 'boolean',
2675
+ description: 'Optional: Case-sensitive search (default: false)'
2676
+ },
2677
+ context: {
2678
+ type: 'number',
2679
+ description: 'Optional: Number of context lines to show'
2680
+ }
2681
+ },
2682
+ required: ['pattern']
2683
+ };
2684
+ break;
2685
+
2686
+ case 'Bash':
2687
+ parameters = {
2688
+ type: 'object',
2689
+ properties: {
2690
+ command: {
2691
+ type: 'string',
2692
+ description: 'The shell command to execute'
2693
+ },
2694
+ cwd: {
2695
+ type: 'string',
2696
+ description: 'Optional: Working directory'
2697
+ },
2698
+ description: {
2699
+ type: 'string',
2700
+ description: 'Optional: Brief description of the command'
2701
+ },
2702
+ timeout: {
2703
+ type: 'number',
2704
+ description: 'Optional: Timeout in seconds (default: 120)'
2705
+ },
2706
+ run_in_bg: {
2707
+ type: 'boolean',
2708
+ description: 'Optional: Run in background (default: false)'
2709
+ }
2710
+ },
2711
+ required: ['command']
2712
+ };
2713
+ break;
2714
+
2715
+ case 'ListDirectory':
2716
+ parameters = {
2717
+ type: 'object',
2718
+ properties: {
2719
+ path: {
2720
+ type: 'string',
2721
+ description: 'Optional: The directory path to list (default: current directory)'
2722
+ },
2723
+ recursive: {
2724
+ type: 'boolean',
2725
+ description: 'Optional: List recursively (default: false)'
2726
+ }
2727
+ },
2728
+ required: []
2729
+ };
2730
+ break;
2731
+
2732
+ case 'SearchCodebase':
2733
+ parameters = {
2734
+ type: 'object',
2735
+ properties: {
2736
+ pattern: {
2737
+ type: 'string',
2738
+ description: 'The glob pattern to match files'
2739
+ },
2740
+ path: {
2741
+ type: 'string',
2742
+ description: 'Optional: The path to search in (default: current directory)'
2743
+ }
2744
+ },
2745
+ required: ['pattern']
2746
+ };
2747
+ break;
2748
+
2749
+ case 'DeleteFile':
2750
+ parameters = {
2751
+ type: 'object',
2752
+ properties: {
2753
+ filePath: {
2754
+ type: 'string',
2755
+ description: 'The path to the file to delete'
2756
+ }
2757
+ },
2758
+ required: ['filePath']
2759
+ };
2760
+ break;
2761
+
2762
+ case 'CreateDirectory':
2763
+ parameters = {
2764
+ type: 'object',
2765
+ properties: {
2766
+ dirPath: {
2767
+ type: 'string',
2768
+ description: 'The directory path to create'
2769
+ },
2770
+ recursive: {
2771
+ type: 'boolean',
2772
+ description: 'Optional: Create parent directories (default: true)'
2773
+ }
2774
+ },
2775
+ required: ['dirPath']
2776
+ };
2777
+ break;
2778
+
2779
+ case 'replace':
2780
+ parameters = {
2781
+ type: 'object',
2782
+ properties: {
2783
+ file_path: {
2784
+ type: 'string',
2785
+ description: 'The absolute path to the file'
2786
+ },
2787
+ instruction: {
2788
+ type: 'string',
2789
+ description: 'Description of what needs to be changed'
2790
+ },
2791
+ old_string: {
2792
+ type: 'string',
2793
+ description: 'The exact text to replace'
2794
+ },
2795
+ new_string: {
2796
+ type: 'string',
2797
+ description: 'The exact text to replace with'
2798
+ }
2799
+ },
2800
+ required: ['file_path', 'instruction', 'old_string', 'new_string']
2801
+ };
2802
+ break;
2803
+
2804
+ case 'web_search':
2805
+ parameters = {
2806
+ type: 'object',
2807
+ properties: {
2808
+ query: {
2809
+ type: 'string',
2810
+ description: 'The search query'
2811
+ }
2812
+ },
2813
+ required: ['query']
2814
+ };
2815
+ break;
2816
+
2817
+ case 'todo_write':
2818
+ parameters = {
2819
+ type: 'object',
2820
+ properties: {
2821
+ todos: {
2822
+ type: 'array',
2823
+ description: 'Array of todo items',
2824
+ items: {
2825
+ type: 'object',
2826
+ properties: {
2827
+ id: { type: 'string' },
2828
+ task: { type: 'string' },
2829
+ status: { type: 'string', enum: ['pending', 'in_progress', 'completed', 'failed'] },
2830
+ priority: { type: 'string', enum: ['high', 'medium', 'low'] }
2831
+ },
2832
+ required: ['id', 'task', 'status']
2833
+ }
2834
+ }
2835
+ },
2836
+ required: ['todos']
2837
+ };
2838
+ break;
2839
+
2840
+ case 'todo_read':
2841
+ parameters = {
2842
+ type: 'object',
2843
+ properties: {},
2844
+ required: []
2845
+ };
2846
+ break;
2847
+
2848
+ case 'task':
2849
+ parameters = {
2850
+ type: 'object',
2851
+ properties: {
2852
+ description: {
2853
+ type: 'string',
2854
+ description: 'Brief description of the task (3-5 words)'
2855
+ },
2856
+ agents: {
2857
+ type: 'array',
2858
+ description: 'Optional: Array of agents to run in parallel for comprehensive analysis',
2859
+ items: {
2860
+ type: 'object',
2861
+ properties: {
2862
+ description: {
2863
+ type: 'string',
2864
+ description: 'Brief description of the sub-agent task'
2865
+ },
2866
+ prompt: {
2867
+ type: 'string',
2868
+ description: 'The task for the sub-agent to perform'
2869
+ },
2870
+ subagent_type: {
2871
+ type: 'string',
2872
+ enum: ['general-purpose', 'plan-agent', 'explore-agent', 'frontend-tester', 'code-reviewer', 'frontend-developer', 'backend-developer'],
2873
+ description: 'The type of specialized agent'
2874
+ },
2875
+ constraints: {
2876
+ type: 'array',
2877
+ items: { type: 'string' },
2878
+ description: 'Optional: Constraints or limitations'
2879
+ }
2880
+ },
2881
+ required: ['description', 'prompt', 'subagent_type']
2882
+ }
2883
+ },
2884
+ prompt: {
2885
+ type: 'string',
2886
+ description: 'Optional: The task for the agent to perform (use agents for parallel execution)'
2887
+ },
2888
+ subagent_type: {
2889
+ type: 'string',
2890
+ enum: ['general-purpose', 'plan-agent', 'explore-agent', 'frontend-tester', 'code-reviewer', 'frontend-developer', 'backend-developer'],
2891
+ description: 'Optional: The type of specialized agent (use agents for parallel execution)'
2892
+ },
2893
+ useContext: {
2894
+ type: 'boolean',
2895
+ description: 'Optional: Include main agent context'
2896
+ },
2897
+ outputFormat: {
2898
+ type: 'string',
2899
+ description: 'Optional: Output format template'
2900
+ },
2901
+ constraints: {
2902
+ type: 'array',
2903
+ items: { type: 'string' },
2904
+ description: 'Optional: Constraints or limitations'
2905
+ }
2906
+ },
2907
+ required: ['description']
2908
+ };
2909
+ break;
2910
+
2911
+ case 'ReadBashOutput':
2912
+ parameters = {
2913
+ type: 'object',
2914
+ properties: {
2915
+ task_id: {
2916
+ type: 'string',
2917
+ description: 'The ID of the task'
2918
+ },
2919
+ poll_interval: {
2920
+ type: 'number',
2921
+ description: 'Optional: Polling interval in seconds (default: 10)'
2922
+ }
2923
+ },
2924
+ required: ['task_id']
2925
+ };
2926
+ break;
2927
+
2928
+ case 'web_fetch':
2929
+ parameters = {
2930
+ type: 'object',
2931
+ properties: {
2932
+ prompt: {
2933
+ type: 'string',
2934
+ description: 'Prompt containing URL(s) and processing instructions'
2935
+ }
2936
+ },
2937
+ required: ['prompt']
2938
+ };
2939
+ break;
2940
+
2941
+ case 'ask_user_question':
2942
+ parameters = {
2943
+ type: 'object',
2944
+ properties: {
2945
+ questions: {
2946
+ type: 'array',
2947
+ description: 'Array of questions to ask',
2948
+ items: {
2949
+ type: 'object',
2950
+ properties: {
2951
+ question: { type: 'string' },
2952
+ header: { type: 'string', description: 'Short label (max 12 chars)' },
2953
+ options: {
2954
+ type: 'array',
2955
+ items: { type: 'string' },
2956
+ description: 'Available choices (2-4 options)'
2957
+ },
2958
+ multiSelect: { type: 'boolean' }
2959
+ },
2960
+ required: ['question', 'header', 'options', 'multiSelect']
2961
+ }
2962
+ }
2963
+ },
2964
+ required: ['questions']
2965
+ };
2966
+ break;
2967
+
2968
+ case 'save_memory':
2969
+ parameters = {
2970
+ type: 'object',
2971
+ properties: {
2972
+ fact: {
2973
+ type: 'string',
2974
+ description: 'The specific fact to remember'
2975
+ }
2976
+ },
2977
+ required: ['fact']
2978
+ };
2979
+ break;
2980
+
2981
+ case 'exit_plan_mode':
2982
+ parameters = {
2983
+ type: 'object',
2984
+ properties: {
2985
+ plan: {
2986
+ type: 'string',
2987
+ description: 'The plan to present'
2988
+ }
2989
+ },
2990
+ required: ['plan']
2991
+ };
2992
+ break;
2993
+
2994
+ case 'xml_escape':
2995
+ parameters = {
2996
+ type: 'object',
2997
+ properties: {
2998
+ file_path: {
2999
+ type: 'string',
3000
+ description: 'The absolute path to the XML/HTML file'
3001
+ },
3002
+ escape_all: {
3003
+ type: 'boolean',
3004
+ description: 'Optional: Escape all special characters (default: false)'
3005
+ }
3006
+ },
3007
+ required: ['file_path']
3008
+ };
3009
+ break;
3010
+
3011
+ case 'image_read':
3012
+ parameters = {
3013
+ type: 'object',
3014
+ properties: {
3015
+ image_input: {
3016
+ type: 'string',
3017
+ description: 'Image file path or base64 data'
3018
+ },
3019
+ prompt: {
3020
+ type: 'string',
3021
+ description: 'Comprehensive VLM instruction'
3022
+ },
3023
+ task_brief: {
3024
+ type: 'string',
3025
+ description: 'Brief task description (max 15 words)'
3026
+ },
3027
+ input_type: {
3028
+ type: 'string',
3029
+ enum: ['file_path', 'base64'],
3030
+ description: 'Input type (default: file_path)'
3031
+ },
3032
+ mime_type: {
3033
+ type: 'string',
3034
+ description: 'Optional: MIME type for base64 input'
3035
+ }
3036
+ },
3037
+ required: ['image_input', 'prompt']
3038
+ };
3039
+ break;
3040
+
3041
+ case 'Skill':
3042
+ parameters = {
3043
+ type: 'object',
3044
+ properties: {
3045
+ skill: {
3046
+ type: 'string',
3047
+ description: 'The skill name to execute'
3048
+ }
3049
+ },
3050
+ required: ['skill']
3051
+ };
3052
+ break;
3053
+
3054
+ case 'ListSkills':
3055
+ parameters = {
3056
+ type: 'object',
3057
+ properties: {},
3058
+ required: []
3059
+ };
3060
+ break;
3061
+
3062
+ case 'GetSkillDetails':
3063
+ parameters = {
3064
+ type: 'object',
3065
+ properties: {
3066
+ skill: {
3067
+ type: 'string',
3068
+ description: 'The skill name/id to get details for'
3069
+ }
3070
+ },
3071
+ required: ['skill']
3072
+ };
3073
+ break;
3074
+
3075
+ default:
3076
+ // For MCP tools, use their inputSchema; for other unknown tools, keep empty schema
3077
+ const mcpTool = tool as any;
3078
+ if (mcpTool._isMcpTool && mcpTool.inputSchema) {
3079
+ // Use MCP tool's inputSchema directly
3080
+ parameters = {
3081
+ type: 'object',
3082
+ properties: {},
3083
+ required: []
3084
+ };
3085
+ if (mcpTool.inputSchema.properties) {
3086
+ for (const [paramName, paramDef] of Object.entries<any>(mcpTool.inputSchema.properties)) {
3087
+ parameters.properties[paramName] = {
3088
+ type: paramDef.type || 'string',
3089
+ description: paramDef.description || ''
3090
+ };
3091
+ }
3092
+ }
3093
+ if (mcpTool.inputSchema.required) {
3094
+ parameters.required = mcpTool.inputSchema.required;
3095
+ }
3096
+ } else {
3097
+ parameters = {
3098
+ type: 'object',
3099
+ properties: {},
3100
+ required: []
3101
+ };
3102
+ }
3103
+ }
3104
+
3105
+ return {
3106
+ type: 'function',
3107
+ function: {
3108
+ name: tool.name,
3109
+ description: tool.description,
3110
+ parameters
3111
+ }
3112
+ };
3113
+ });
3114
+ }
3115
+
3116
+ async execute(toolName: string, params: any, executionMode: ExecutionMode, indent: string = ''): Promise<any> {
3117
+ // First try to execute as local tool
3118
+ const localTool = this.tools.get(toolName);
3119
+ if (localTool) {
3120
+ return await this.executeLocalTool(toolName, params, executionMode, indent);
3121
+ }
3122
+
3123
+ // Fall back to MCP tool if local tool doesn't exist
3124
+ const { getMCPManager } = await import('./mcp.js');
3125
+ const mcpManager = getMCPManager();
3126
+ const allMcpTools = mcpManager.getAllTools();
3127
+
3128
+ // Check if this is an MCP tool (format: serverName__toolName)
3129
+ if (toolName.includes('__') && allMcpTools.has(toolName)) {
3130
+ return await this.executeMCPTool(toolName, params, executionMode, indent);
3131
+ }
3132
+
3133
+ // Try to find MCP tool with just the tool name (try each server)
3134
+ for (const [fullName, tool] of allMcpTools) {
3135
+ const [serverName, actualToolName] = fullName.split('__');
3136
+ if (actualToolName === toolName) {
3137
+ return await this.executeMCPTool(fullName, params, executionMode, indent);
3138
+ }
3139
+ }
3140
+
3141
+ // Tool not found anywhere
3142
+ throw new Error(`Tool not found: ${toolName}`);
3143
+ }
3144
+
3145
+ /**
3146
+ * Generate guidance for LLM to choose between local and MCP tools
3147
+ * This returns a message that the LLM can analyze to decide which tool to use
3148
+ */
3149
+ private generateLlmToolChoiceGuidance(toolName: string, params: any, executionMode: ExecutionMode, indent: string): Promise<any> {
3150
+ const modeLabel = executionMode === ExecutionMode.YOLO ? 'YOLO' :
3151
+ executionMode === ExecutionMode.SMART ? 'SMART' :
3152
+ executionMode === ExecutionMode.ACCEPT_EDITS ? 'ACCEPT_EDITS' : 'DEFAULT';
3153
+
3154
+ return Promise.resolve({
3155
+ success: false,
3156
+ needsLlmDecision: true,
3157
+ toolName,
3158
+ mode: modeLabel,
3159
+ message: `
3160
+ ## Tool Choice Decision Required (${modeLabel} Mode)
3161
+
3162
+ Both **local** and **MCP** versions of "${toolName}" are available. Please analyze the requirements and decide which tool to use.
3163
+
3164
+ ### Local Tool
3165
+ - **Pros**: No network latency, always available, no external dependencies
3166
+ - **Best for**: Filesystem operations, project-specific tasks, command execution
3167
+
3168
+ ### MCP Tool
3169
+ - **Pros**: Extended capabilities (e.g., GitHub API, database access, cloud services)
3170
+ - **Best for**: External API calls, database operations, cloud service integration
3171
+
3172
+ ### How to Specify Your Choice
3173
+ Retry the tool call with the \`_useTool\` parameter:
3174
+
3175
+ **To use MCP tool:**
3176
+ \`\`\`json
3177
+ {"_useTool": "mcp", ...otherParams}
3178
+ \`\`\`
3179
+
3180
+ **To use local tool:**
3181
+ \`\`\`json
3182
+ {"_useTool": "local", ...otherParams}
3183
+ \`\`\`
3184
+
3185
+ ### Decision Criteria
3186
+ Consider:
3187
+ 1. Does the task require external API access?
3188
+ 2. Is network latency a concern?
3189
+ 3. Does the MCP tool have features not available locally?
3190
+ 4. Is the local tool sufficient for the task?
3191
+
3192
+ Make your decision based on the user's request and the above criteria.`
3193
+ });
3194
+ }
3195
+
3196
+ /**
3197
+ * Execute local tool (extracted for reuse)
3198
+ */
3199
+ private async executeLocalTool(toolName: string, params: any, executionMode: ExecutionMode, indent: string): Promise<any> {
3200
+ const tool = this.get(toolName);
3201
+
3202
+ if (!tool) {
3203
+ throw new Error(`Tool not found: ${toolName}`);
3204
+ }
3205
+
3206
+ if (!tool.allowedModes.includes(executionMode)) {
3207
+ throw new Error(
3208
+ `Tool ${toolName} is not allowed in ${executionMode} mode`
3209
+ );
3210
+ }
3211
+
3212
+ // Smart approval mode
3213
+ if (executionMode === ExecutionMode.SMART) {
3214
+ const debugMode = process.env.DEBUG === 'smart-approval';
3215
+ const cancellationManager = getCancellationManager();
3216
+
3217
+ // task tool bypasses smart approval entirely
3218
+ if (toolName === 'task') {
3219
+ if (debugMode) {
3220
+ const { getLogger } = await import('./logger.js');
3221
+ const logger = getLogger();
3222
+ logger.debug(`[SmartApprovalEngine] Tool '${toolName}' bypassed smart approval completely`);
3223
+ }
3224
+ return await cancellationManager.withCancellation(
3225
+ tool.execute(params, executionMode),
3226
+ `tool-${toolName}`
3227
+ );
3228
+ }
3229
+
3230
+ const { getSmartApprovalEngine } = await import('./smart-approval.js');
3231
+ const { getConfigManager } = await import('./config.js');
3232
+ const configManager = getConfigManager();
3233
+
3234
+ const approvalEngine = getSmartApprovalEngine(debugMode);
3235
+
3236
+ // Evaluate tool call
3237
+ const result = await approvalEngine.evaluate({
3238
+ toolName,
3239
+ params,
3240
+ timestamp: Date.now()
3241
+ });
3242
+
3243
+ // Decide whether to execute based on approval result
3244
+ if (result.decision === 'approved') {
3245
+ // Whitelist or AI approval passed, execute directly
3246
+ console.log('');
3247
+ console.log(`${indent}${colors.success(`✅ [Smart Mode] Tool '${toolName}' passed approval, executing directly`)}`);
3248
+ console.log(`${indent}${colors.textDim(` Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`)}`);
3249
+ console.log(`${indent}${colors.textDim(` Latency: ${result.latency}ms`)}`);
3250
+ console.log('');
3251
+ return await cancellationManager.withCancellation(
3252
+ tool.execute(params, executionMode),
3253
+ `tool-${toolName}`
3254
+ );
3255
+ } else if (result.decision === 'requires_confirmation') {
3256
+ // Requires user confirmation
3257
+ const confirmed = await approvalEngine.requestConfirmation(result);
3258
+
3259
+ if (confirmed) {
3260
+ console.log('');
3261
+ console.log(`${indent}${colors.success(`✅ [Smart Mode] User confirmed execution of tool '${toolName}'`)}`);
3262
+ console.log('');
3263
+ return await cancellationManager.withCancellation(
3264
+ tool.execute(params, executionMode),
3265
+ `tool-${toolName}`
3266
+ );
3267
+ } else {
3268
+ console.log('');
3269
+ console.log(`${indent}${colors.warning(`⚠️ [Smart Mode] User cancelled execution of tool '${toolName}'`)}`);
3270
+ console.log('');
3271
+ throw new Error(`Tool execution cancelled by user: ${toolName}`);
3272
+ }
3273
+ } else {
3274
+ // Rejected execution
3275
+ console.log('');
3276
+ console.log(`${indent}${colors.error(`❌ [Smart Mode] Tool '${toolName}' execution rejected`)}`);
3277
+ console.log(`${indent}${colors.textDim(` Reason: ${result.description}`)}`);
3278
+ console.log('');
3279
+ throw new Error(`Tool execution rejected: ${toolName}`);
3280
+ }
3281
+ }
3282
+
3283
+ // Other modes execute directly
3284
+ return await tool.execute(params, executionMode);
3285
+ }
3286
+
3287
+ /**
3288
+ * Execute an MCP tool call
3289
+ */
3290
+ private async executeMCPTool(toolName: string, params: any, executionMode: ExecutionMode, indent: string = ''): Promise<any> {
3291
+ const { getMCPManager } = await import('./mcp.js');
3292
+ const cancellationManager = getCancellationManager();
3293
+ const mcpManager = getMCPManager();
3294
+
3295
+ // Parse the tool name (format: serverName__toolName)
3296
+ const [serverName, actualToolName] = toolName.split('__');
3297
+
3298
+ // Get server info for display
3299
+ const server = mcpManager.getServer(serverName);
3300
+ const serverTools = server?.getToolNames() || [];
3301
+
3302
+ // Display tool call info
3303
+ console.log('');
3304
+ console.log(`${indent}${colors.warning(`${icons.tool} MCP Tool Call: ${serverName}::${actualToolName}`)}`);
3305
+
3306
+ // Smart approval mode for MCP tools
3307
+ if (executionMode === ExecutionMode.SMART) {
3308
+ const debugMode = process.env.DEBUG === 'smart-approval';
3309
+ const { getSmartApprovalEngine } = await import('./smart-approval.js');
3310
+ const approvalEngine = getSmartApprovalEngine(debugMode);
3311
+
3312
+ // Evaluate MCP tool call
3313
+ const result = await approvalEngine.evaluate({
3314
+ toolName: `MCP[${serverName}]::${actualToolName}`,
3315
+ params,
3316
+ timestamp: Date.now()
3317
+ });
3318
+
3319
+ if (result.decision === 'approved') {
3320
+ console.log(`${indent}${colors.success(`✅ [Smart Mode] MCP tool '${serverName}::${actualToolName}' passed approval`)}`);
3321
+ console.log(`${indent}${colors.textDim(` Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`)}`);
3322
+ } else if (result.decision === 'requires_confirmation') {
3323
+ const confirmed = await approvalEngine.requestConfirmation(result);
3324
+ if (!confirmed) {
3325
+ console.log(`${indent}${colors.warning(`⚠️ [Smart Mode] User cancelled MCP tool execution`)}`);
3326
+ throw new Error(`Tool execution cancelled by user: ${toolName}`);
3327
+ }
3328
+ } else {
3329
+ console.log(`${indent}${colors.error(`❌ [Smart Mode] MCP tool execution rejected`)}`);
3330
+ console.log(`${indent}${colors.textDim(` Reason: ${result.description}`)}`);
3331
+ throw new Error(`Tool execution rejected: ${toolName}`);
3332
+ }
3333
+ }
3334
+
3335
+ // Execute the MCP tool call with cancellation support
3336
+ const operationId = `mcp-${serverName}-${actualToolName}-${Date.now()}`;
3337
+ return await cancellationManager.withCancellation(
3338
+ mcpManager.callTool(toolName, params),
3339
+ operationId
3340
+ );
3341
+ }
3342
+
3343
+ async executeAll(
3344
+ toolCalls: Array<{ name: string; params: any }>,
3345
+ executionMode: ExecutionMode
3346
+ ): Promise<Array<{ tool: string; result: any; error?: string }>> {
3347
+ const results: Array<{ tool: string; result: any; error?: string }> = [];
3348
+ const cancellationManager = getCancellationManager();
3349
+ let cancelled = false;
3350
+
3351
+ // Listen for cancellation
3352
+ const cancelHandler = () => {
3353
+ cancelled = true;
3354
+ };
3355
+ cancellationManager.on('cancelled', cancelHandler);
3356
+
3357
+ const executePromises = toolCalls.map(async (toolCall, index) => {
3358
+ const { name, params } = toolCall;
3359
+ const operationId = `tool-${name}-${index}-${Date.now()}`;
3360
+
3361
+ try {
3362
+ const result = await cancellationManager.withCancellation(
3363
+ this.execute(name, params, executionMode),
3364
+ operationId
3365
+ );
3366
+ return { tool: name, result, error: undefined };
3367
+ } catch (error: any) {
3368
+ if (error.message === 'Operation cancelled by user') {
3369
+ return { tool: name, result: undefined, error: 'Cancelled' };
3370
+ }
3371
+ return { tool: name, result: undefined, error: error.message };
3372
+ }
3373
+ });
3374
+
3375
+ const settledResults = await Promise.all(executePromises);
3376
+ cancellationManager.off('cancelled', cancelHandler);
3377
+
3378
+ // Filter out cancelled tools and mark them appropriately
3379
+ for (const result of settledResults) {
3380
+ if (result.error === 'Cancelled' && cancelled) {
3381
+ // Don't add cancelled results to the final output
3382
+ continue;
3383
+ }
3384
+ results.push(result);
3385
+ }
3386
+
3387
+ return results;
3388
+ }
3389
+ }
3390
+
3391
+ let toolRegistryInstance: ToolRegistry | null = null;
3392
+
3393
+ export function getToolRegistry(): ToolRegistry {
3394
+ if (!toolRegistryInstance) {
3395
+ toolRegistryInstance = new ToolRegistry();
3396
+ }
3397
+ return toolRegistryInstance;
3398
+ }