@xagent-ai/cli 1.2.2 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (602) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +38 -38
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
  3. package/.github/release.yml +76 -0
  4. package/.github/workflows/ci.yml +75 -0
  5. package/.github/workflows/release.yml +103 -0
  6. package/.gitmodules +3 -3
  7. package/README.md +326 -280
  8. package/README_CN.md +325 -279
  9. package/dist/agents.d.ts.map +1 -1
  10. package/dist/agents.js +7 -3
  11. package/dist/agents.js.map +1 -1
  12. package/dist/ai-client/factory.d.ts +40 -0
  13. package/dist/ai-client/factory.d.ts.map +1 -0
  14. package/dist/ai-client/factory.js +100 -0
  15. package/dist/ai-client/factory.js.map +1 -0
  16. package/dist/ai-client/index.d.ts +20 -0
  17. package/dist/ai-client/index.d.ts.map +1 -0
  18. package/dist/ai-client/index.js +49 -0
  19. package/dist/ai-client/index.js.map +1 -0
  20. package/dist/ai-client/providers/anthropic.d.ts +57 -0
  21. package/dist/ai-client/providers/anthropic.d.ts.map +1 -0
  22. package/dist/ai-client/providers/anthropic.js +406 -0
  23. package/dist/ai-client/providers/anthropic.js.map +1 -0
  24. package/dist/ai-client/providers/openai.d.ts +57 -0
  25. package/dist/ai-client/providers/openai.d.ts.map +1 -0
  26. package/dist/ai-client/providers/openai.js +290 -0
  27. package/dist/ai-client/providers/openai.js.map +1 -0
  28. package/dist/ai-client/providers/remote.d.ts +110 -0
  29. package/dist/ai-client/providers/remote.d.ts.map +1 -0
  30. package/dist/ai-client/providers/remote.js +352 -0
  31. package/dist/ai-client/providers/remote.js.map +1 -0
  32. package/dist/ai-client/registry.d.ts +51 -0
  33. package/dist/ai-client/registry.d.ts.map +1 -0
  34. package/dist/ai-client/registry.js +81 -0
  35. package/dist/ai-client/registry.js.map +1 -0
  36. package/dist/ai-client/types.d.ts +274 -0
  37. package/dist/ai-client/types.d.ts.map +1 -0
  38. package/dist/ai-client/types.js +90 -0
  39. package/dist/ai-client/types.js.map +1 -0
  40. package/dist/ai-client-factory.d.ts +62 -0
  41. package/dist/ai-client-factory.d.ts.map +1 -0
  42. package/dist/ai-client-factory.js +157 -0
  43. package/dist/ai-client-factory.js.map +1 -0
  44. package/dist/auth.d.ts +23 -1
  45. package/dist/auth.d.ts.map +1 -1
  46. package/dist/auth.js +164 -174
  47. package/dist/auth.js.map +1 -1
  48. package/dist/cancellation.d.ts +5 -4
  49. package/dist/cancellation.d.ts.map +1 -1
  50. package/dist/cancellation.js +53 -32
  51. package/dist/cancellation.js.map +1 -1
  52. package/dist/checkpoint.d.ts +2 -1
  53. package/dist/checkpoint.d.ts.map +1 -1
  54. package/dist/checkpoint.js +39 -6
  55. package/dist/checkpoint.js.map +1 -1
  56. package/dist/cli.js +742 -29
  57. package/dist/cli.js.map +1 -1
  58. package/dist/config.d.ts +10 -4
  59. package/dist/config.d.ts.map +1 -1
  60. package/dist/config.js +62 -25
  61. package/dist/config.js.map +1 -1
  62. package/dist/context-compressor.d.ts +82 -18
  63. package/dist/context-compressor.d.ts.map +1 -1
  64. package/dist/context-compressor.js +718 -154
  65. package/dist/context-compressor.js.map +1 -1
  66. package/dist/conversation.d.ts +1 -1
  67. package/dist/conversation.d.ts.map +1 -1
  68. package/dist/conversation.js +8 -7
  69. package/dist/conversation.js.map +1 -1
  70. package/dist/gui-subagent/action-parser/actionParser.d.ts.map +1 -1
  71. package/dist/gui-subagent/action-parser/actionParser.js +6 -4
  72. package/dist/gui-subagent/action-parser/actionParser.js.map +1 -1
  73. package/dist/gui-subagent/agent/gui-agent.d.ts +39 -2
  74. package/dist/gui-subagent/agent/gui-agent.d.ts.map +1 -1
  75. package/dist/gui-subagent/agent/gui-agent.js +189 -74
  76. package/dist/gui-subagent/agent/gui-agent.js.map +1 -1
  77. package/dist/gui-subagent/index.d.ts +23 -1
  78. package/dist/gui-subagent/index.d.ts.map +1 -1
  79. package/dist/gui-subagent/index.js +6 -0
  80. package/dist/gui-subagent/index.js.map +1 -1
  81. package/dist/gui-subagent/operator/base-operator.d.ts.map +1 -1
  82. package/dist/gui-subagent/operator/base-operator.js +0 -1
  83. package/dist/gui-subagent/operator/base-operator.js.map +1 -1
  84. package/dist/gui-subagent/operator/computer-operator.d.ts.map +1 -1
  85. package/dist/gui-subagent/operator/computer-operator.js +31 -8
  86. package/dist/gui-subagent/operator/computer-operator.js.map +1 -1
  87. package/dist/gui-subagent/types/actions.d.ts +1 -1
  88. package/dist/gui-subagent/types/actions.d.ts.map +1 -1
  89. package/dist/gui-subagent/types/actions.js +0 -1
  90. package/dist/gui-subagent/types/actions.js.map +1 -1
  91. package/dist/gui-subagent/types/operator.d.ts +1 -1
  92. package/dist/gui-subagent/types/operator.d.ts.map +1 -1
  93. package/dist/index.d.ts +1 -2
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/index.js +1 -2
  96. package/dist/index.js.map +1 -1
  97. package/dist/input-processor.d.ts.map +1 -1
  98. package/dist/input-processor.js +8 -5
  99. package/dist/input-processor.js.map +1 -1
  100. package/dist/logger.d.ts.map +1 -1
  101. package/dist/logger.js +1 -1
  102. package/dist/logger.js.map +1 -1
  103. package/dist/mcp.d.ts +7 -1
  104. package/dist/mcp.d.ts.map +1 -1
  105. package/dist/mcp.js +157 -49
  106. package/dist/mcp.js.map +1 -1
  107. package/dist/memory.d.ts.map +1 -1
  108. package/dist/memory.js +3 -3
  109. package/dist/memory.js.map +1 -1
  110. package/dist/output-util.d.ts +27 -0
  111. package/dist/output-util.d.ts.map +1 -0
  112. package/dist/output-util.js +74 -0
  113. package/dist/output-util.js.map +1 -0
  114. package/dist/retry.js +1 -1
  115. package/dist/retry.js.map +1 -1
  116. package/dist/ripgrep.d.ts +29 -0
  117. package/dist/ripgrep.d.ts.map +1 -0
  118. package/dist/ripgrep.js +294 -0
  119. package/dist/ripgrep.js.map +1 -0
  120. package/dist/sdk-output-adapter.d.ts +34 -1
  121. package/dist/sdk-output-adapter.d.ts.map +1 -1
  122. package/dist/sdk-output-adapter.js +67 -2
  123. package/dist/sdk-output-adapter.js.map +1 -1
  124. package/dist/sdk-session.d.ts.map +1 -1
  125. package/dist/sdk-session.js +2 -0
  126. package/dist/sdk-session.js.map +1 -1
  127. package/dist/session-manager.js +3 -3
  128. package/dist/session-manager.js.map +1 -1
  129. package/dist/session.d.ts +116 -6
  130. package/dist/session.d.ts.map +1 -1
  131. package/dist/session.js +1416 -448
  132. package/dist/session.js.map +1 -1
  133. package/dist/shell.d.ts +33 -0
  134. package/dist/shell.d.ts.map +1 -0
  135. package/dist/shell.js +126 -0
  136. package/dist/shell.js.map +1 -0
  137. package/dist/skill-installer.d.ts +38 -0
  138. package/dist/skill-installer.d.ts.map +1 -0
  139. package/dist/skill-installer.js +447 -0
  140. package/dist/skill-installer.js.map +1 -0
  141. package/dist/skill-invoker.d.ts +8 -2
  142. package/dist/skill-invoker.d.ts.map +1 -1
  143. package/dist/skill-invoker.js +36 -15
  144. package/dist/skill-invoker.js.map +1 -1
  145. package/dist/skill-loader.d.ts +8 -3
  146. package/dist/skill-loader.d.ts.map +1 -1
  147. package/dist/skill-loader.js +51 -48
  148. package/dist/skill-loader.js.map +1 -1
  149. package/dist/skill-manager.d.ts +85 -0
  150. package/dist/skill-manager.d.ts.map +1 -0
  151. package/dist/skill-manager.js +341 -0
  152. package/dist/skill-manager.js.map +1 -0
  153. package/dist/slash-commands.d.ts +39 -2
  154. package/dist/slash-commands.d.ts.map +1 -1
  155. package/dist/slash-commands.js +934 -305
  156. package/dist/slash-commands.js.map +1 -1
  157. package/dist/smart-approval.d.ts +20 -1
  158. package/dist/smart-approval.d.ts.map +1 -1
  159. package/dist/smart-approval.js +125 -56
  160. package/dist/smart-approval.js.map +1 -1
  161. package/dist/system-prompt-generator.d.ts +6 -0
  162. package/dist/system-prompt-generator.d.ts.map +1 -1
  163. package/dist/system-prompt-generator.js +86 -36
  164. package/dist/system-prompt-generator.js.map +1 -1
  165. package/dist/terminal.d.ts +28 -0
  166. package/dist/terminal.d.ts.map +1 -0
  167. package/dist/terminal.js +82 -0
  168. package/dist/terminal.js.map +1 -0
  169. package/dist/theme.d.ts.map +1 -1
  170. package/dist/theme.js +8 -7
  171. package/dist/theme.js.map +1 -1
  172. package/dist/tools.d.ts +38 -7
  173. package/dist/tools.d.ts.map +1 -1
  174. package/dist/tools.js +1249 -617
  175. package/dist/tools.js.map +1 -1
  176. package/dist/truncate.d.ts +55 -0
  177. package/dist/truncate.d.ts.map +1 -0
  178. package/dist/truncate.js +130 -0
  179. package/dist/truncate.js.map +1 -0
  180. package/dist/types.d.ts +84 -9
  181. package/dist/types.d.ts.map +1 -1
  182. package/dist/types.js +49 -0
  183. package/dist/types.js.map +1 -1
  184. package/dist/update.d.ts.map +1 -1
  185. package/dist/update.js +28 -36
  186. package/dist/update.js.map +1 -1
  187. package/dist/workflow.d.ts +5 -1
  188. package/dist/workflow.d.ts.map +1 -1
  189. package/dist/workflow.js +61 -49
  190. package/dist/workflow.js.map +1 -1
  191. package/docs/architecture/mcp-integration-guide.md +304 -194
  192. package/docs/architecture/overview.md +169 -169
  193. package/docs/architecture/tool-system-design.md +134 -134
  194. package/docs/cli/commands.md +349 -238
  195. package/docs/smart-mode.md +281 -281
  196. package/docs/third-party-models.md +440 -439
  197. package/find-skills/SKILL.md +133 -0
  198. package/package.json +91 -90
  199. package/scripts/install-ripgrep.js +241 -0
  200. package/src/agents.ts +7 -3
  201. package/src/ai-client/factory.ts +116 -0
  202. package/src/ai-client/index.ts +61 -0
  203. package/src/ai-client/providers/anthropic.ts +475 -0
  204. package/src/ai-client/providers/openai.ts +348 -0
  205. package/src/ai-client/providers/remote.ts +439 -0
  206. package/src/ai-client/registry.ts +97 -0
  207. package/src/ai-client/types.ts +364 -0
  208. package/src/ai-client-factory.ts +204 -0
  209. package/src/auth.ts +661 -614
  210. package/src/cancellation.ts +202 -176
  211. package/src/checkpoint.ts +255 -219
  212. package/src/cli.ts +1523 -743
  213. package/src/config.ts +341 -297
  214. package/src/context-compressor.ts +987 -290
  215. package/src/conversation.ts +290 -288
  216. package/src/gui-subagent/action-parser/actionParser.ts +318 -315
  217. package/src/gui-subagent/action-parser/constants.ts +14 -14
  218. package/src/gui-subagent/action-parser/index.ts +8 -8
  219. package/src/gui-subagent/action-parser/types.ts +31 -31
  220. package/src/gui-subagent/agent/gui-agent.ts +1234 -1089
  221. package/src/gui-subagent/agent/index.ts +5 -5
  222. package/src/gui-subagent/index.ts +185 -163
  223. package/src/gui-subagent/operator/base-operator.ts +244 -245
  224. package/src/gui-subagent/operator/computer-operator.ts +541 -520
  225. package/src/gui-subagent/operator/index.ts +6 -6
  226. package/src/gui-subagent/types/actions.ts +260 -262
  227. package/src/gui-subagent/types/index.ts +6 -6
  228. package/src/gui-subagent/types/operator.ts +106 -106
  229. package/src/gui-subagent/utils.ts +51 -51
  230. package/src/index.ts +17 -18
  231. package/src/input-processor.ts +8 -5
  232. package/src/logger.ts +436 -438
  233. package/src/mcp.ts +793 -682
  234. package/src/memory.ts +343 -344
  235. package/src/output-util.ts +80 -0
  236. package/src/retry.ts +1 -1
  237. package/src/ripgrep.ts +370 -0
  238. package/src/sdk-output-adapter.ts +842 -0
  239. package/src/sdk-session.ts +62 -0
  240. package/src/session-manager.ts +308 -308
  241. package/src/session.ts +1775 -573
  242. package/src/shell.ts +134 -0
  243. package/src/skill-installer.ts +518 -0
  244. package/src/skill-invoker.ts +959 -935
  245. package/src/skill-loader.ts +501 -496
  246. package/src/skill-manager.ts +385 -0
  247. package/src/slash-commands.ts +2189 -1389
  248. package/src/smart-approval.ts +193 -74
  249. package/src/system-prompt-generator.ts +91 -36
  250. package/src/terminal.ts +96 -0
  251. package/src/theme.ts +739 -738
  252. package/src/tools.ts +1790 -931
  253. package/src/truncate.ts +173 -0
  254. package/src/types.ts +337 -198
  255. package/src/update.ts +33 -40
  256. package/src/workflow.ts +521 -508
  257. package/test/cli-launch.test.ts +279 -0
  258. package/tsconfig.json +22 -22
  259. package/vitest.config.ts +21 -19
  260. package/dist/ai-client.d.ts +0 -86
  261. package/dist/ai-client.d.ts.map +0 -1
  262. package/dist/ai-client.js +0 -1372
  263. package/dist/ai-client.js.map +0 -1
  264. package/dist/gui-subagent/operator/browser-operator.d.ts +0 -36
  265. package/dist/gui-subagent/operator/browser-operator.d.ts.map +0 -1
  266. package/dist/gui-subagent/operator/browser-operator.js +0 -306
  267. package/dist/gui-subagent/operator/browser-operator.js.map +0 -1
  268. package/dist/gui-subagent/operator/desktop-operator.d.ts +0 -55
  269. package/dist/gui-subagent/operator/desktop-operator.d.ts.map +0 -1
  270. package/dist/gui-subagent/operator/desktop-operator.js +0 -527
  271. package/dist/gui-subagent/operator/desktop-operator.js.map +0 -1
  272. package/dist/hook.d.ts +0 -73
  273. package/dist/hook.d.ts.map +0 -1
  274. package/dist/hook.js +0 -156
  275. package/dist/hook.js.map +0 -1
  276. package/dist/input-history.d.ts +0 -24
  277. package/dist/input-history.d.ts.map +0 -1
  278. package/dist/input-history.js +0 -94
  279. package/dist/input-history.js.map +0 -1
  280. package/dist/keyboard-manager.d.ts +0 -151
  281. package/dist/keyboard-manager.d.ts.map +0 -1
  282. package/dist/keyboard-manager.js +0 -396
  283. package/dist/keyboard-manager.js.map +0 -1
  284. package/dist/print-system-prompt.d.ts +0 -2
  285. package/dist/print-system-prompt.d.ts.map +0 -1
  286. package/dist/print-system-prompt.js +0 -40
  287. package/dist/print-system-prompt.js.map +0 -1
  288. package/dist/remote-ai-client.d.ts +0 -104
  289. package/dist/remote-ai-client.d.ts.map +0 -1
  290. package/dist/remote-ai-client.js +0 -552
  291. package/dist/remote-ai-client.js.map +0 -1
  292. package/dist/sdk-session-v2.d.ts +0 -13
  293. package/dist/sdk-session-v2.d.ts.map +0 -1
  294. package/dist/sdk-session-v2.js +0 -46
  295. package/dist/sdk-session-v2.js.map +0 -1
  296. package/dist/test-boundary-conditions.d.ts.map +0 -1
  297. package/dist/test-boundary-conditions.js.map +0 -1
  298. package/dist/test-cancellation-fix.d.ts.map +0 -1
  299. package/dist/test-cancellation-fix.js.map +0 -1
  300. package/dist/test-input-history.d.ts.map +0 -1
  301. package/dist/test-input-history.js.map +0 -1
  302. package/dist/test-interaction-flow.d.ts.map +0 -1
  303. package/dist/test-interaction-flow.js.map +0 -1
  304. package/dist/test-quick.d.ts.map +0 -1
  305. package/dist/test-quick.js.map +0 -1
  306. package/dist/test-user-interaction.d.ts.map +0 -1
  307. package/dist/test-user-interaction.js.map +0 -1
  308. package/dist/tools/edit-diff.d.ts +0 -32
  309. package/dist/tools/edit-diff.d.ts.map +0 -1
  310. package/dist/tools/edit-diff.js +0 -185
  311. package/dist/tools/edit-diff.js.map +0 -1
  312. package/dist/tools/edit.d.ts +0 -11
  313. package/dist/tools/edit.d.ts.map +0 -1
  314. package/dist/tools/edit.js +0 -129
  315. package/dist/tools/edit.js.map +0 -1
  316. package/dist/unified-session.d.ts +0 -42
  317. package/dist/unified-session.d.ts.map +0 -1
  318. package/dist/unified-session.js +0 -271
  319. package/dist/unified-session.js.map +0 -1
  320. package/skills/.claude-plugin/marketplace.json +0 -45
  321. package/skills/README.md +0 -94
  322. package/skills/THIRD_PARTY_NOTICES.md +0 -405
  323. package/skills/skills/algorithmic-art/LICENSE.txt +0 -202
  324. package/skills/skills/algorithmic-art/SKILL.md +0 -405
  325. package/skills/skills/algorithmic-art/templates/generator_template.js +0 -223
  326. package/skills/skills/algorithmic-art/templates/viewer.html +0 -599
  327. package/skills/skills/brand-guidelines/LICENSE.txt +0 -202
  328. package/skills/skills/brand-guidelines/SKILL.md +0 -73
  329. package/skills/skills/canvas-design/LICENSE.txt +0 -202
  330. package/skills/skills/canvas-design/SKILL.md +0 -130
  331. package/skills/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +0 -93
  332. package/skills/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
  333. package/skills/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
  334. package/skills/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +0 -93
  335. package/skills/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
  336. package/skills/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +0 -93
  337. package/skills/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
  338. package/skills/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
  339. package/skills/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +0 -93
  340. package/skills/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
  341. package/skills/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
  342. package/skills/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
  343. package/skills/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +0 -93
  344. package/skills/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
  345. package/skills/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +0 -93
  346. package/skills/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
  347. package/skills/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +0 -94
  348. package/skills/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
  349. package/skills/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
  350. package/skills/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +0 -93
  351. package/skills/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
  352. package/skills/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +0 -93
  353. package/skills/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
  354. package/skills/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
  355. package/skills/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +0 -93
  356. package/skills/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
  357. package/skills/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
  358. package/skills/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
  359. package/skills/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
  360. package/skills/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
  361. package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
  362. package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
  363. package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
  364. package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +0 -93
  365. package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
  366. package/skills/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
  367. package/skills/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
  368. package/skills/skills/canvas-design/canvas-fonts/Italiana-OFL.txt +0 -93
  369. package/skills/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
  370. package/skills/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
  371. package/skills/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +0 -93
  372. package/skills/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
  373. package/skills/skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
  374. package/skills/skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
  375. package/skills/skills/canvas-design/canvas-fonts/Jura-OFL.txt +0 -93
  376. package/skills/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +0 -93
  377. package/skills/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
  378. package/skills/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
  379. package/skills/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
  380. package/skills/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
  381. package/skills/skills/canvas-design/canvas-fonts/Lora-OFL.txt +0 -93
  382. package/skills/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
  383. package/skills/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
  384. package/skills/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +0 -93
  385. package/skills/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
  386. package/skills/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +0 -93
  387. package/skills/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
  388. package/skills/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
  389. package/skills/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +0 -93
  390. package/skills/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
  391. package/skills/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
  392. package/skills/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +0 -93
  393. package/skills/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +0 -93
  394. package/skills/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
  395. package/skills/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
  396. package/skills/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +0 -93
  397. package/skills/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
  398. package/skills/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +0 -93
  399. package/skills/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
  400. package/skills/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
  401. package/skills/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +0 -93
  402. package/skills/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
  403. package/skills/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +0 -93
  404. package/skills/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
  405. package/skills/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
  406. package/skills/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
  407. package/skills/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
  408. package/skills/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +0 -93
  409. package/skills/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
  410. package/skills/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +0 -93
  411. package/skills/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
  412. package/skills/skills/doc-coauthoring/SKILL.md +0 -375
  413. package/skills/skills/docx/LICENSE.txt +0 -30
  414. package/skills/skills/docx/SKILL.md +0 -197
  415. package/skills/skills/docx/docx-js.md +0 -350
  416. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
  417. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
  418. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
  419. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
  420. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
  421. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
  422. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
  423. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
  424. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
  425. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
  426. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
  427. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
  428. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
  429. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
  430. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
  431. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
  432. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
  433. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
  434. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
  435. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
  436. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
  437. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
  438. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
  439. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
  440. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
  441. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
  442. package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
  443. package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
  444. package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
  445. package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
  446. package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
  447. package/skills/skills/docx/ooxml/schemas/mce/mc.xsd +0 -75
  448. package/skills/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +0 -560
  449. package/skills/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +0 -67
  450. package/skills/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +0 -14
  451. package/skills/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +0 -20
  452. package/skills/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +0 -13
  453. package/skills/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
  454. package/skills/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +0 -8
  455. package/skills/skills/docx/ooxml/scripts/pack.py +0 -159
  456. package/skills/skills/docx/ooxml/scripts/unpack.py +0 -29
  457. package/skills/skills/docx/ooxml/scripts/validate.py +0 -69
  458. package/skills/skills/docx/ooxml/scripts/validation/__init__.py +0 -15
  459. package/skills/skills/docx/ooxml/scripts/validation/base.py +0 -951
  460. package/skills/skills/docx/ooxml/scripts/validation/docx.py +0 -274
  461. package/skills/skills/docx/ooxml/scripts/validation/pptx.py +0 -315
  462. package/skills/skills/docx/ooxml/scripts/validation/redlining.py +0 -279
  463. package/skills/skills/docx/ooxml.md +0 -610
  464. package/skills/skills/docx/scripts/__init__.py +0 -1
  465. package/skills/skills/docx/scripts/document.py +0 -1276
  466. package/skills/skills/docx/scripts/templates/comments.xml +0 -3
  467. package/skills/skills/docx/scripts/templates/commentsExtended.xml +0 -3
  468. package/skills/skills/docx/scripts/templates/commentsExtensible.xml +0 -3
  469. package/skills/skills/docx/scripts/templates/commentsIds.xml +0 -3
  470. package/skills/skills/docx/scripts/templates/people.xml +0 -3
  471. package/skills/skills/docx/scripts/utilities.py +0 -374
  472. package/skills/skills/frontend-design/LICENSE.txt +0 -177
  473. package/skills/skills/frontend-design/SKILL.md +0 -42
  474. package/skills/skills/internal-comms/LICENSE.txt +0 -202
  475. package/skills/skills/internal-comms/SKILL.md +0 -32
  476. package/skills/skills/internal-comms/examples/3p-updates.md +0 -47
  477. package/skills/skills/internal-comms/examples/company-newsletter.md +0 -65
  478. package/skills/skills/internal-comms/examples/faq-answers.md +0 -30
  479. package/skills/skills/internal-comms/examples/general-comms.md +0 -16
  480. package/skills/skills/mcp-builder/LICENSE.txt +0 -202
  481. package/skills/skills/mcp-builder/SKILL.md +0 -236
  482. package/skills/skills/mcp-builder/reference/evaluation.md +0 -602
  483. package/skills/skills/mcp-builder/reference/mcp_best_practices.md +0 -249
  484. package/skills/skills/mcp-builder/reference/node_mcp_server.md +0 -970
  485. package/skills/skills/mcp-builder/reference/python_mcp_server.md +0 -719
  486. package/skills/skills/mcp-builder/scripts/connections.py +0 -151
  487. package/skills/skills/mcp-builder/scripts/evaluation.py +0 -373
  488. package/skills/skills/mcp-builder/scripts/example_evaluation.xml +0 -22
  489. package/skills/skills/mcp-builder/scripts/requirements.txt +0 -2
  490. package/skills/skills/pdf/LICENSE.txt +0 -30
  491. package/skills/skills/pdf/SKILL.md +0 -294
  492. package/skills/skills/pdf/forms.md +0 -205
  493. package/skills/skills/pdf/reference.md +0 -612
  494. package/skills/skills/pdf/scripts/check_bounding_boxes.py +0 -70
  495. package/skills/skills/pdf/scripts/check_bounding_boxes_test.py +0 -226
  496. package/skills/skills/pdf/scripts/check_fillable_fields.py +0 -12
  497. package/skills/skills/pdf/scripts/convert_pdf_to_images.py +0 -35
  498. package/skills/skills/pdf/scripts/create_validation_image.py +0 -41
  499. package/skills/skills/pdf/scripts/extract_form_field_info.py +0 -152
  500. package/skills/skills/pdf/scripts/fill_fillable_fields.py +0 -114
  501. package/skills/skills/pdf/scripts/fill_pdf_form_with_annotations.py +0 -108
  502. package/skills/skills/pptx/LICENSE.txt +0 -30
  503. package/skills/skills/pptx/SKILL.md +0 -484
  504. package/skills/skills/pptx/html2pptx.md +0 -625
  505. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
  506. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
  507. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
  508. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
  509. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
  510. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
  511. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
  512. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
  513. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
  514. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
  515. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
  516. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
  517. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
  518. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
  519. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
  520. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
  521. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
  522. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
  523. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
  524. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
  525. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
  526. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
  527. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
  528. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
  529. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
  530. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
  531. package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
  532. package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
  533. package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
  534. package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
  535. package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
  536. package/skills/skills/pptx/ooxml/schemas/mce/mc.xsd +0 -75
  537. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +0 -560
  538. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +0 -67
  539. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +0 -14
  540. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +0 -20
  541. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +0 -13
  542. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
  543. package/skills/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +0 -8
  544. package/skills/skills/pptx/ooxml/scripts/pack.py +0 -159
  545. package/skills/skills/pptx/ooxml/scripts/unpack.py +0 -29
  546. package/skills/skills/pptx/ooxml/scripts/validate.py +0 -69
  547. package/skills/skills/pptx/ooxml/scripts/validation/__init__.py +0 -15
  548. package/skills/skills/pptx/ooxml/scripts/validation/base.py +0 -951
  549. package/skills/skills/pptx/ooxml/scripts/validation/docx.py +0 -274
  550. package/skills/skills/pptx/ooxml/scripts/validation/pptx.py +0 -315
  551. package/skills/skills/pptx/ooxml/scripts/validation/redlining.py +0 -279
  552. package/skills/skills/pptx/ooxml.md +0 -427
  553. package/skills/skills/pptx/scripts/html2pptx.js +0 -979
  554. package/skills/skills/pptx/scripts/inventory.py +0 -1020
  555. package/skills/skills/pptx/scripts/rearrange.py +0 -231
  556. package/skills/skills/pptx/scripts/replace.py +0 -385
  557. package/skills/skills/pptx/scripts/thumbnail.py +0 -450
  558. package/skills/skills/skill-creator/LICENSE.txt +0 -202
  559. package/skills/skills/skill-creator/SKILL.md +0 -356
  560. package/skills/skills/skill-creator/references/output-patterns.md +0 -82
  561. package/skills/skills/skill-creator/references/workflows.md +0 -28
  562. package/skills/skills/skill-creator/scripts/init_skill.py +0 -303
  563. package/skills/skills/skill-creator/scripts/package_skill.py +0 -110
  564. package/skills/skills/skill-creator/scripts/quick_validate.py +0 -95
  565. package/skills/skills/slack-gif-creator/LICENSE.txt +0 -202
  566. package/skills/skills/slack-gif-creator/SKILL.md +0 -254
  567. package/skills/skills/slack-gif-creator/core/easing.py +0 -234
  568. package/skills/skills/slack-gif-creator/core/frame_composer.py +0 -176
  569. package/skills/skills/slack-gif-creator/core/gif_builder.py +0 -269
  570. package/skills/skills/slack-gif-creator/core/validators.py +0 -136
  571. package/skills/skills/slack-gif-creator/requirements.txt +0 -4
  572. package/skills/skills/theme-factory/LICENSE.txt +0 -202
  573. package/skills/skills/theme-factory/SKILL.md +0 -59
  574. package/skills/skills/theme-factory/theme-showcase.pdf +0 -0
  575. package/skills/skills/theme-factory/themes/arctic-frost.md +0 -19
  576. package/skills/skills/theme-factory/themes/botanical-garden.md +0 -19
  577. package/skills/skills/theme-factory/themes/desert-rose.md +0 -19
  578. package/skills/skills/theme-factory/themes/forest-canopy.md +0 -19
  579. package/skills/skills/theme-factory/themes/golden-hour.md +0 -19
  580. package/skills/skills/theme-factory/themes/midnight-galaxy.md +0 -19
  581. package/skills/skills/theme-factory/themes/modern-minimalist.md +0 -19
  582. package/skills/skills/theme-factory/themes/ocean-depths.md +0 -19
  583. package/skills/skills/theme-factory/themes/sunset-boulevard.md +0 -19
  584. package/skills/skills/theme-factory/themes/tech-innovation.md +0 -19
  585. package/skills/skills/web-artifacts-builder/LICENSE.txt +0 -202
  586. package/skills/skills/web-artifacts-builder/SKILL.md +0 -74
  587. package/skills/skills/web-artifacts-builder/scripts/bundle-artifact.sh +0 -54
  588. package/skills/skills/web-artifacts-builder/scripts/init-artifact.sh +0 -322
  589. package/skills/skills/webapp-testing/LICENSE.txt +0 -202
  590. package/skills/skills/webapp-testing/SKILL.md +0 -96
  591. package/skills/skills/webapp-testing/examples/console_logging.py +0 -35
  592. package/skills/skills/webapp-testing/examples/element_discovery.py +0 -40
  593. package/skills/skills/webapp-testing/examples/static_html_automation.py +0 -33
  594. package/skills/skills/webapp-testing/scripts/with_server.py +0 -106
  595. package/skills/skills/xlsx/LICENSE.txt +0 -30
  596. package/skills/skills/xlsx/SKILL.md +0 -289
  597. package/skills/skills/xlsx/recalc.py +0 -178
  598. package/skills/spec/agent-skills-spec.md +0 -3
  599. package/skills/template/SKILL.md +0 -6
  600. package/src/ai-client.ts +0 -1560
  601. package/src/remote-ai-client.ts +0 -664
  602. /package/{.eslintrc.js → .eslintrc.cjs} +0 -0
package/dist/session.js CHANGED
@@ -5,11 +5,18 @@ import axios from 'axios';
5
5
  import crypto from 'crypto';
6
6
  import ora from 'ora';
7
7
  import { createRequire } from 'module';
8
+ import path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import fs from 'fs';
11
+ import fsPromises from 'fs/promises';
12
+ import os from 'os';
8
13
  const require = createRequire(import.meta.url);
9
14
  const packageJson = require('../package.json');
10
- import { ExecutionMode, AuthType } from './types.js';
11
- import { AIClient, detectThinkingKeywords, getThinkingTokens } from './ai-client.js';
12
- import { RemoteAIClient, TokenInvalidError } from './remote-ai-client.js';
15
+ import { ExecutionMode, AuthType, } from './types.js';
16
+ import { createAIClient } from './ai-client-factory.js';
17
+ import { detectThinkingKeywords, getThinkingTokens } from './ai-client/types.js';
18
+ import { TokenInvalidError } from './ai-client/types.js';
19
+ import { fetchDefaultModels } from './ai-client/providers/remote.js';
13
20
  import { getConfigManager } from './config.js';
14
21
  import { AuthService, selectAuthType } from './auth.js';
15
22
  import { getToolRegistry } from './tools.js';
@@ -21,10 +28,11 @@ import { getConversationManager } from './conversation.js';
21
28
  import { getSessionManager } from './session-manager.js';
22
29
  import { SlashCommandHandler, parseInput } from './slash-commands.js';
23
30
  import { SystemPromptGenerator } from './system-prompt-generator.js';
24
- import { theme, icons, colors, styleHelpers, renderMarkdown, renderDiff, renderLines } from './theme.js';
31
+ import { theme, icons, colors, styleHelpers, renderMarkdown, renderDiff, renderLines, } from './theme.js';
25
32
  import { getCancellationManager } from './cancellation.js';
26
- import { getContextCompressor } from './context-compressor.js';
33
+ import { getContextCompressor, } from './context-compressor.js';
27
34
  import { getLogger } from './logger.js';
35
+ import { ensureTtySane, setupEscKeyHandler } from './terminal.js';
28
36
  const logger = getLogger();
29
37
  export class InteractiveSession {
30
38
  conversationManager;
@@ -33,7 +41,7 @@ export class InteractiveSession {
33
41
  aiClient = null;
34
42
  remoteAIClient = null;
35
43
  conversation = [];
36
- toolCalls = [];
44
+ tool_calls = [];
37
45
  executionMode;
38
46
  slashCommandHandler;
39
47
  configManager;
@@ -50,10 +58,21 @@ export class InteractiveSession {
50
58
  currentTaskId = null;
51
59
  taskCompleted = false;
52
60
  isFirstApiCall = true;
61
+ sdkOutputAdapter = null;
62
+ isSdkMode = false;
63
+ sdkInputBuffer = [];
64
+ resolveInput = null;
65
+ _currentRequestId = null;
66
+ heartbeatTimeout = null;
67
+ heartbeatTimeoutMs = 300000; // 5 minutes timeout for long AI responses
68
+ lastActivityTime = Date.now();
69
+ // SDK response handling for approvals and questions
70
+ approvalPromises = new Map();
71
+ questionPromises = new Map();
53
72
  constructor(indentLevel = 0) {
54
73
  this.rl = readline.createInterface({
55
74
  input: process.stdin,
56
- output: process.stdout
75
+ output: process.stdout,
57
76
  });
58
77
  this.configManager = getConfigManager(process.cwd());
59
78
  this.agentManager = getAgentManager(process.cwd());
@@ -66,7 +85,7 @@ export class InteractiveSession {
66
85
  // Register /clear callback, clear local conversation when clearing dialogue
67
86
  this.slashCommandHandler.setClearCallback(() => {
68
87
  this.conversation = [];
69
- this.toolCalls = [];
88
+ this.tool_calls = [];
70
89
  this.currentTaskId = null;
71
90
  this.taskCompleted = false;
72
91
  this.isFirstApiCall = true;
@@ -76,6 +95,10 @@ export class InteractiveSession {
76
95
  this.slashCommandHandler.setSystemPromptUpdateCallback(async () => {
77
96
  await this.updateSystemPrompt();
78
97
  });
98
+ // Register config update callback, update aiClient config when /auth changes config
99
+ this.slashCommandHandler.setConfigUpdateCallback(() => {
100
+ this.updateAiClientConfig();
101
+ });
79
102
  this.executionMode = ExecutionMode.DEFAULT;
80
103
  this.cancellationManager = getCancellationManager();
81
104
  this.indentLevel = indentLevel;
@@ -88,59 +111,226 @@ export class InteractiveSession {
88
111
  setAIClient(aiClient) {
89
112
  this.aiClient = aiClient;
90
113
  }
114
+ /**
115
+ * Update aiClient config when /auth changes config (called from callback)
116
+ */
117
+ updateAiClientConfig() {
118
+ const authConfig = this.configManager.getAuthConfig();
119
+ const isRemote = authConfig.type === AuthType.OAUTH_XAGENT;
120
+ if (isRemote) {
121
+ // Already in remote mode, no change needed
122
+ if (this.remoteAIClient !== null) {
123
+ return;
124
+ }
125
+ // Switch to remote: clear local client, create remote client
126
+ this.aiClient = null;
127
+ this.remoteAIClient = createAIClient(authConfig);
128
+ }
129
+ else {
130
+ // Already in local mode, no change needed
131
+ if (this.aiClient !== null) {
132
+ return;
133
+ }
134
+ // Switch to local: clear remote client, create local client
135
+ this.remoteAIClient = null;
136
+ this.aiClient = createAIClient(authConfig);
137
+ }
138
+ this.slashCommandHandler.setRemoteAIClient(this.remoteAIClient);
139
+ }
91
140
  setExecutionMode(mode) {
92
141
  this.executionMode = mode;
93
142
  }
143
+ /**
144
+ * Set SDK mode for programmatic access.
145
+ * In SDK mode, output is formatted as JSON to stdout.
146
+ */
147
+ setSdkMode(adapter) {
148
+ this.isSdkMode = true;
149
+ this.sdkOutputAdapter = adapter;
150
+ // Initialize SDK mode for other modules using centralized output util
151
+ const { initOutputMode } = require('./output-util.js');
152
+ initOutputMode(true, adapter);
153
+ // Initialize SmartApprovalEngine in SDK mode
154
+ this.initSmartApprovalSdkMode(adapter).catch(() => {
155
+ // Silently ignore errors - not critical
156
+ });
157
+ // Initialize tool registry in SDK mode (fire and forget, doesn't need to await)
158
+ this.initToolRegistrySdkMode(adapter).catch(() => {
159
+ // Silently ignore errors - tool registry init is not critical
160
+ });
161
+ }
162
+ /**
163
+ * Initialize SmartApprovalEngine in SDK mode.
164
+ */
165
+ async initSmartApprovalSdkMode(adapter) {
166
+ const { getSmartApprovalEngine } = await import('./smart-approval.js');
167
+ const approvalEngine = getSmartApprovalEngine();
168
+ approvalEngine.setSdkMode(true, adapter);
169
+ }
170
+ /**
171
+ * Initialize tool registry in SDK mode.
172
+ */
173
+ async initToolRegistrySdkMode(adapter) {
174
+ const toolRegistry = getToolRegistry();
175
+ await toolRegistry.setSdkMode(true, adapter);
176
+ }
177
+ /**
178
+ * Get SDK mode status.
179
+ */
180
+ getIsSdkMode() {
181
+ return this.isSdkMode;
182
+ }
183
+ /**
184
+ * Output assistant response - handles SDK mode and normal mode differently.
185
+ */
186
+ outputAssistant(content, reasoningContent) {
187
+ if (this.isSdkMode && this.sdkOutputAdapter) {
188
+ this.sdkOutputAdapter.outputAssistant(content, reasoningContent);
189
+ }
190
+ else {
191
+ const indent = this.getIndent();
192
+ console.log('');
193
+ console.log(`${indent}${colors.primaryBright(`${icons.robot} Assistant:`)}`);
194
+ console.log(`${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`);
195
+ console.log('');
196
+ const renderedContent = renderMarkdown(content, (process.stdout.columns || 80) - indent.length * 2);
197
+ console.log(`${indent}${renderedContent.replace(/^/gm, indent)}`);
198
+ console.log('');
199
+ }
200
+ }
94
201
  /**
95
202
  * Update system prompt to reflect MCP changes (called after add/remove MCP)
96
203
  */
97
204
  async updateSystemPrompt() {
205
+ // Reload skills to pick up any newly added/removed skills
206
+ // First reset the SkillLoader to clear all cached skills
207
+ const { resetSkillLoader } = await import('./skill-loader.js');
208
+ resetSkillLoader();
209
+ // Then reload the skill invoker
210
+ const skillInvoker = (await import('./skill-invoker.js')).getSkillInvoker();
211
+ await skillInvoker.reload();
98
212
  const toolRegistry = getToolRegistry();
99
213
  const promptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode, undefined, this.mcpManager);
100
214
  // Use the current agent's original system prompt as base
101
215
  const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are xAgent, an AI-powered CLI tool.';
102
216
  const newSystemPrompt = await promptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
103
217
  // Replace old system prompt with new one
104
- this.conversation = this.conversation.filter(msg => msg.role !== 'system');
218
+ this.conversation = this.conversation.filter((msg) => msg.role !== 'system');
105
219
  this.conversation.unshift({
106
220
  role: 'system',
107
221
  content: newSystemPrompt,
108
- timestamp: Date.now()
222
+ timestamp: Date.now(),
109
223
  });
110
224
  // Sync to slashCommandHandler
111
225
  this.slashCommandHandler.setConversationHistory(this.conversation);
112
226
  }
227
+ /**
228
+ * Watch for skill updates from CLI and update system prompt accordingly
229
+ */
230
+ startSkillUpdateWatcher() {
231
+ const SKILL_STATE_FILE = '.skill-state.json';
232
+ const configManager = getConfigManager();
233
+ const userSkillsPath = configManager.getUserSkillsPath();
234
+ const stateFilePath = userSkillsPath
235
+ ? path.join(userSkillsPath, SKILL_STATE_FILE)
236
+ : null;
237
+ if (!stateFilePath) {
238
+ return; // No user skills path configured
239
+ }
240
+ let lastUpdateTime = 0;
241
+ // Initialize with current state to avoid triggering update on first check
242
+ try {
243
+ const { existsSync, readFileSync } = require('fs');
244
+ if (existsSync(stateFilePath)) {
245
+ const content = readFileSync(stateFilePath, 'utf-8');
246
+ const state = JSON.parse(content);
247
+ lastUpdateTime = state.lastSkillUpdate || 0;
248
+ }
249
+ }
250
+ catch {
251
+ // Silent fail
252
+ }
253
+ // Check for updates every 2 seconds
254
+ const checkInterval = setInterval(async () => {
255
+ try {
256
+ const { existsSync, readFileSync } = await import('fs');
257
+ if (existsSync(stateFilePath)) {
258
+ const content = readFileSync(stateFilePath, 'utf-8');
259
+ const state = JSON.parse(content);
260
+ if (state.lastSkillUpdate && state.lastSkillUpdate > lastUpdateTime) {
261
+ lastUpdateTime = state.lastSkillUpdate;
262
+ // Update system prompt with new skills
263
+ await this.updateSystemPrompt();
264
+ if (!this.isSdkMode) {
265
+ console.log(colors.textMuted(' 🔄 Skills updated from CLI'));
266
+ }
267
+ }
268
+ }
269
+ }
270
+ catch {
271
+ // Silent fail - watcher is optional
272
+ }
273
+ }, 2000);
274
+ // Clean up on session end
275
+ this._skillWatcherInterval = checkInterval;
276
+ }
113
277
  setAgent(agent) {
114
278
  this.currentAgent = agent;
115
279
  }
116
- async start() {
280
+ async start(initializedCount = 0) {
117
281
  // Set this session as the singleton for access from other modules
118
282
  setSingletonSession(this);
119
283
  // Initialize taskId for GUI operations
120
284
  this.currentTaskId = crypto.randomUUID();
121
- const separator = icons.separator.repeat(60);
122
- console.log('');
123
- console.log(colors.gradient('╔════════════════════════════════════════════════════════════╗'));
124
- console.log(colors.gradient('') + ' '.repeat(58) + colors.gradient(' ║'));
125
- console.log(colors.gradient('') + ' '.repeat(13) + '🤖 ' + colors.gradient('XAGENT CLI') + ' '.repeat(32) + colors.gradient(' ║'));
126
- console.log(colors.gradient('║') + ' '.repeat(16) + colors.textMuted(`v${packageJson.version}`) + ' '.repeat(36) + colors.gradient(' ║'));
127
- console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
128
- console.log(colors.gradient('╚════════════════════════════════════════════════════════════╝'));
129
- console.log(colors.textMuted(' AI-powered command-line assistant'));
130
- console.log('');
285
+ const _separator = icons.separator.repeat(60);
286
+ if (!this.isSdkMode) {
287
+ // Normal mode: show ASCII art welcome
288
+ console.log('');
289
+ console.log(colors.gradient('╔════════════════════════════════════════════════════════════╗'));
290
+ console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
291
+ console.log(colors.gradient('║') +
292
+ ' '.repeat(13) +
293
+ '🤖 ' +
294
+ colors.gradient('XAGENT CLI') +
295
+ ' '.repeat(32) +
296
+ colors.gradient(' ║'));
297
+ console.log(colors.gradient('║') +
298
+ ' '.repeat(16) +
299
+ colors.textMuted(`v${packageJson.version}`) +
300
+ ' '.repeat(36) +
301
+ colors.gradient(' ║'));
302
+ console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
303
+ console.log(colors.gradient('╚════════════════════════════════════════════════════════════╝'));
304
+ console.log(colors.textMuted(' AI-powered command-line assistant'));
305
+ // Show initialization message if skills were initialized
306
+ if (initializedCount > 0) {
307
+ console.log(colors.textMuted(` ✨ Initialized ${initializedCount} built-in skills`));
308
+ }
309
+ console.log('');
310
+ }
131
311
  await this.initialize();
132
312
  this.showWelcomeMessage();
313
+ // Start watching for skill updates from CLI
314
+ this.startSkillUpdateWatcher();
315
+ // SDK 模式初始化:设置输出适配器
316
+ if (this.isSdkMode) {
317
+ // Start heartbeat timeout monitoring in SDK mode
318
+ this.startHeartbeatMonitoring();
319
+ }
320
+ // Set up ESC key handler using the terminal module
321
+ // This avoids conflicts with readline and provides clean ESC detection
322
+ let _escCleanup;
323
+ if (process.stdin.isTTY) {
324
+ _escCleanup = setupEscKeyHandler(() => {
325
+ if (this._isOperationInProgress) {
326
+ // An operation is running, let it be cancelled
327
+ this.cancellationManager.cancel();
328
+ }
329
+ // No operation running, ignore ESC
330
+ });
331
+ }
133
332
  // Track if an operation is in progress
134
333
  this._isOperationInProgress = false;
135
- // Listen for ESC cancellation - only cancel operations, don't exit the program
136
- const cancelHandler = () => {
137
- if (this._isOperationInProgress) {
138
- // An operation is running, let it be cancelled
139
- return;
140
- }
141
- // No operation running, ignore ESC or show a message
142
- };
143
- this.cancellationManager.on('cancelled', cancelHandler);
144
334
  this.promptLoop();
145
335
  // Keep the promise pending until shutdown
146
336
  return new Promise((resolve) => {
@@ -159,7 +349,7 @@ export class InteractiveSession {
159
349
  frameIndex = (frameIndex + 1) % frames.length;
160
350
  }, 120);
161
351
  logger.debug('[SESSION] 调用 configManager.load()...');
162
- await this.configManager.load();
352
+ this.configManager.load();
163
353
  logger.debug('[SESSION] Config loaded');
164
354
  let authConfig = this.configManager.getAuthConfig();
165
355
  let selectedAuthType = this.configManager.get('selectedAuthType');
@@ -186,9 +376,8 @@ export class InteractiveSession {
186
376
  clearInterval(refreshInterval);
187
377
  process.stdout.write('\r' + ' '.repeat(50) + '\r');
188
378
  if (newToken) {
189
- // Save new token and persist
190
- await this.configManager.set('apiKey', newToken);
191
- await this.configManager.save('global');
379
+ this.configManager.set('apiKey', newToken);
380
+ this.configManager.save('global');
192
381
  authConfig.apiKey = newToken;
193
382
  isValid = true;
194
383
  }
@@ -198,20 +387,18 @@ export class InteractiveSession {
198
387
  console.log(colors.warning('Your xAgent session has expired or is not configured'));
199
388
  console.log(colors.info('Please select an authentication method to continue.'));
200
389
  console.log('');
201
- // Clear invalid credentials and persist
202
- // Note: Do NOT overwrite selectedAuthType - let user re-select their preferred auth method
203
- await this.configManager.set('apiKey', '');
204
- await this.configManager.set('refreshToken', '');
205
- await this.configManager.save('global');
206
- await this.configManager.load();
390
+ this.configManager.set('apiKey', '');
391
+ this.configManager.set('refreshToken', '');
392
+ this.configManager.save('global');
393
+ this.configManager.load();
207
394
  authConfig = this.configManager.getAuthConfig();
208
395
  await this.setupAuthentication();
209
396
  authConfig = this.configManager.getAuthConfig();
210
- // Recreate readline interface after inquirer
397
+ // Recreate readline interface after interactive prompt
211
398
  this.rl.close();
212
399
  this.rl = readline.createInterface({
213
400
  input: process.stdin,
214
- output: process.stdout
401
+ output: process.stdout,
215
402
  });
216
403
  this.rl.on('close', () => {
217
404
  // readline closed
@@ -226,11 +413,11 @@ export class InteractiveSession {
226
413
  authConfig = this.configManager.getAuthConfig();
227
414
  selectedAuthType = this.configManager.get('selectedAuthType');
228
415
  logger.debug('[SESSION] selectedAuthType (after setup):', String(selectedAuthType));
229
- // Recreate readline interface after inquirer
416
+ // Recreate readline interface after interactive prompt
230
417
  this.rl.close();
231
418
  this.rl = readline.createInterface({
232
419
  input: process.stdin,
233
- output: process.stdout
420
+ output: process.stdout,
234
421
  });
235
422
  this.rl.on('close', () => {
236
423
  // readline closed
@@ -241,21 +428,42 @@ export class InteractiveSession {
241
428
  process.stdout.write('\r' + ' '.repeat(50) + '\r');
242
429
  }
243
430
  // For OPENAI_COMPATIBLE with API key, skip validation and proceed directly
244
- this.aiClient = new AIClient(authConfig);
245
- this.contextCompressor.setAIClient(this.aiClient);
246
- // Initialize remote AI client for OAuth XAGENT mode
247
- logger.debug('[SESSION] Final selectedAuthType:', String(selectedAuthType));
248
- logger.debug('[SESSION] Creating RemoteAIClient?', String(selectedAuthType === AuthType.OAUTH_XAGENT));
431
+ // Initialize AI clients and set contextCompressor appropriately
249
432
  if (selectedAuthType === AuthType.OAUTH_XAGENT) {
250
- const webBaseUrl = authConfig.xagentApiBaseUrl || 'https://www.xagent-colife.net';
251
- // In OAuth XAGENT mode, we still pass apiKey (can be empty or used for other purposes)
252
- this.remoteAIClient = new RemoteAIClient(authConfig.apiKey || '', webBaseUrl, authConfig.showAIDebugInfo);
433
+ // Remote mode: fetch default models if not set
434
+ const currentLlm = this.configManager.get('remote_llmModelName');
435
+ const currentVlm = this.configManager.get('remote_vlmModelName');
436
+ if (!currentLlm || !currentVlm) {
437
+ const webBaseUrl = authConfig.xagentApiBaseUrl || 'https://www.xagent-colife.net';
438
+ try {
439
+ const defaults = await fetchDefaultModels(authConfig.apiKey || '', webBaseUrl);
440
+ if (!currentLlm && defaults.llm?.name) {
441
+ this.configManager.set('remote_llmModelName', defaults.llm.name);
442
+ }
443
+ if (!currentVlm && defaults.vlm?.name) {
444
+ this.configManager.set('remote_vlmModelName', defaults.vlm.name);
445
+ }
446
+ this.configManager.save('global');
447
+ }
448
+ catch (error) {
449
+ logger.debug('[SESSION] Failed to fetch default models:', error.message);
450
+ }
451
+ }
452
+ // Remote mode: create RemoteAIClient and use it for context compression
453
+ this.remoteAIClient = createAIClient(authConfig);
454
+ this.contextCompressor.setAIClient(this.remoteAIClient);
253
455
  logger.debug('[DEBUG Initialize] RemoteAIClient created successfully');
254
456
  }
255
457
  else {
458
+ // Local mode: create local AIClient
459
+ this.aiClient = createAIClient(authConfig);
460
+ this.contextCompressor.setAIClient(this.aiClient);
256
461
  logger.debug('[DEBUG Initialize] RemoteAIClient NOT created (not OAuth XAGENT mode)');
257
462
  }
258
- this.executionMode = this.configManager.getApprovalMode() || this.configManager.getExecutionMode();
463
+ // Sync remoteAIClient reference to slashCommandHandler for /provider command
464
+ this.slashCommandHandler.setRemoteAIClient(this.remoteAIClient);
465
+ this.executionMode =
466
+ this.configManager.getApprovalMode() || this.configManager.getExecutionMode();
259
467
  await this.agentManager.loadAgents();
260
468
  await this.memoryManager.loadMemory();
261
469
  await this.conversationManager.initialize();
@@ -267,24 +475,45 @@ export class InteractiveSession {
267
475
  this.slashCommandHandler.setConversationHistory(this.conversation);
268
476
  const mcpServers = this.configManager.getMcpServers();
269
477
  Object.entries(mcpServers).forEach(([name, config]) => {
270
- console.log(`📝 Registering MCP server: ${name} (${config.transport})`);
478
+ if (this.isSdkMode && this.sdkOutputAdapter) {
479
+ this.sdkOutputAdapter.outputMCPRegistering(name, config.transport || 'stdio');
480
+ }
481
+ else {
482
+ console.log(`📝 Registering MCP server: ${name} (${config.transport})`);
483
+ }
271
484
  this.mcpManager.registerServer(name, config);
272
485
  });
273
486
  // Eagerly connect to MCP servers to get tool definitions
274
487
  if (mcpServers && Object.keys(mcpServers).length > 0) {
275
488
  try {
276
- console.log(`${colors.info(`${icons.brain} Connecting to ${Object.keys(mcpServers).length} MCP server(s)...`)}`);
489
+ const serverCount = Object.keys(mcpServers).length;
490
+ if (this.isSdkMode && this.sdkOutputAdapter) {
491
+ this.sdkOutputAdapter.outputMCPLoading(serverCount);
492
+ }
493
+ else {
494
+ console.log(`${colors.info(`${icons.brain} Connecting to ${serverCount} MCP server(s)...`)}`);
495
+ }
277
496
  await this.mcpManager.connectAllServers();
278
497
  const connectedCount = Array.from(this.mcpManager.getAllServers()).filter((s) => s.isServerConnected()).length;
279
498
  const mcpTools = this.mcpManager.getToolDefinitions();
280
- console.log(`${colors.success(`✓ ${connectedCount}/${Object.keys(mcpServers).length} MCP server(s) connected (${mcpTools.length} tools available)`)}`);
499
+ if (this.isSdkMode && this.sdkOutputAdapter) {
500
+ this.sdkOutputAdapter.outputMCPConnected(serverCount, connectedCount, mcpTools.length);
501
+ }
502
+ else {
503
+ console.log(`${colors.success(`✓ ${connectedCount}/${serverCount} MCP server(s) connected (${mcpTools.length} tools available)`)}`);
504
+ }
281
505
  // Register MCP tools with the tool registry (hide MCP origin from LLM)
282
506
  const toolRegistry = getToolRegistry();
283
507
  const allMcpTools = this.mcpManager.getAllTools();
284
508
  toolRegistry.registerMCPTools(allMcpTools);
285
509
  }
286
510
  catch (error) {
287
- console.log(`${colors.warning(`⚠ MCP connection failed: ${error.message}`)}`);
511
+ if (this.isSdkMode && this.sdkOutputAdapter) {
512
+ this.sdkOutputAdapter.outputMCPConnectionFailed(error.message);
513
+ }
514
+ else {
515
+ console.log(`${colors.warning(`⚠ MCP connection failed: ${error.message}`)}`);
516
+ }
288
517
  }
289
518
  }
290
519
  const checkpointingConfig = this.configManager.getCheckpointingConfig();
@@ -292,8 +521,13 @@ export class InteractiveSession {
292
521
  this.checkpointManager = getCheckpointManager(process.cwd(), checkpointingConfig.enabled, checkpointingConfig.maxCheckpoints);
293
522
  await this.checkpointManager.initialize();
294
523
  }
295
- this.currentAgent = this.agentManager.getAgent('general-purpose');
296
- console.log(colors.success('✔ Initialization complete'));
524
+ this.currentAgent = this.agentManager.getAgent('general-purpose') ?? null;
525
+ if (this.isSdkMode && this.sdkOutputAdapter) {
526
+ this.sdkOutputAdapter.outputSuccess('Initialization complete');
527
+ }
528
+ else {
529
+ console.log(colors.success('✔ Initialization complete'));
530
+ }
297
531
  }
298
532
  catch (error) {
299
533
  const spinner = ora({ text: '', spinner: 'dots', color: 'red' }).start();
@@ -315,11 +549,11 @@ export class InteractiveSession {
315
549
  logger.debug('[SESSION] Sending validation request to:', url);
316
550
  const response = await axios.get(url, {
317
551
  headers: {
318
- 'Authorization': `Bearer ${apiKey}`,
319
- 'Content-Type': 'application/json'
552
+ Authorization: `Bearer ${apiKey}`,
553
+ 'Content-Type': 'application/json',
320
554
  },
321
555
  httpsAgent,
322
- timeout: 10000
556
+ timeout: 10000,
323
557
  });
324
558
  logger.debug('[SESSION] Validation response status:', String(response.status));
325
559
  return response.status === 200;
@@ -341,7 +575,7 @@ export class InteractiveSession {
341
575
  const httpsAgent = new https.Agent({ rejectUnauthorized: false });
342
576
  const response = await axios.post(url, { refreshToken }, {
343
577
  httpsAgent,
344
- timeout: 10000
578
+ timeout: 10000,
345
579
  });
346
580
  if (response.status === 200) {
347
581
  const data = response.data;
@@ -351,7 +585,7 @@ export class InteractiveSession {
351
585
  return null;
352
586
  }
353
587
  }
354
- catch (error) {
588
+ catch {
355
589
  return null;
356
590
  }
357
591
  }
@@ -363,11 +597,14 @@ export class InteractiveSession {
363
597
  console.log('');
364
598
  const authType = await selectAuthType();
365
599
  this.configManager.set('selectedAuthType', authType);
600
+ // Get xagentApiBaseUrl from config (respects XAGENT_BASE_URL env var)
601
+ const config = this.configManager.getAuthConfig();
366
602
  const authService = new AuthService({
367
603
  type: authType,
368
604
  apiKey: '',
369
605
  baseUrl: '',
370
- modelName: ''
606
+ modelName: '',
607
+ xagentApiBaseUrl: config.xagentApiBaseUrl,
371
608
  });
372
609
  const success = await authService.authenticate();
373
610
  if (!success) {
@@ -377,67 +614,175 @@ export class InteractiveSession {
377
614
  process.exit(1);
378
615
  }
379
616
  const authConfig = authService.getAuthConfig();
617
+ // Clear modelName for remote mode
618
+ if (authType === AuthType.OAUTH_XAGENT) {
619
+ authConfig.modelName = '';
620
+ }
380
621
  // VLM configuration is optional - only show for non-OAuth (local) mode
381
622
  // Remote mode uses backend VLM configuration
382
623
  if (authType !== AuthType.OAUTH_XAGENT) {
383
- console.log('');
384
- console.log(colors.info(`${icons.info} VLM configuration is optional.`));
385
- console.log(colors.info(`You can configure it later using the /vlm command if needed.`));
386
- console.log('');
624
+ if (this.isSdkMode && this.sdkOutputAdapter) {
625
+ this.sdkOutputAdapter.outputInfo('VLM configuration is optional. You can configure it later using the /model command if needed.');
626
+ }
627
+ else {
628
+ console.log('');
629
+ console.log(colors.info(`${icons.info} VLM configuration is optional.`));
630
+ console.log(colors.info(`You can configure it later using the /model command if needed.`));
631
+ console.log('');
632
+ }
633
+ }
634
+ this.configManager.setAuthConfig(authConfig);
635
+ // Set default remote model settings if not already set
636
+ if (authType === AuthType.OAUTH_XAGENT) {
637
+ if (!this.configManager.get('remote_llmModelName')) {
638
+ this.configManager.set('remote_llmModelName', '');
639
+ }
640
+ if (!this.configManager.get('remote_vlmModelName')) {
641
+ this.configManager.set('remote_vlmModelName', '');
642
+ }
643
+ this.configManager.save('global');
387
644
  }
388
- // Save LLM config only, skip VLM for now
389
- await this.configManager.setAuthConfig(authConfig);
390
645
  }
391
646
  showWelcomeMessage() {
392
647
  const language = this.configManager.getLanguage();
393
648
  const separator = icons.separator.repeat(40);
394
- console.log('');
395
- console.log(colors.border(separator));
396
- if (language === 'zh') {
397
- console.log(colors.primaryBright(`${icons.sparkles} Welcome to XAGENT CLI!`));
398
- console.log(colors.textMuted('Type /help to see available commands'));
399
- }
400
- else {
401
- console.log(colors.primaryBright(`${icons.sparkles} Welcome to XAGENT CLI!`));
402
- console.log(colors.textMuted('Type /help to see available commands'));
649
+ if (!this.isSdkMode) {
650
+ console.log('');
651
+ console.log(colors.border(separator));
652
+ if (language === 'zh') {
653
+ console.log(colors.primaryBright(`${icons.sparkles} Welcome to XAGENT CLI!`));
654
+ console.log(colors.textMuted('Type /help to see available commands'));
655
+ }
656
+ else {
657
+ console.log(colors.primaryBright(`${icons.sparkles} Welcome to XAGENT CLI!`));
658
+ console.log(colors.textMuted('Type /help to see available commands'));
659
+ }
660
+ console.log(colors.border(separator));
661
+ console.log('');
403
662
  }
404
- console.log(colors.border(separator));
405
- console.log('');
406
663
  this.showExecutionMode();
664
+ // In SDK mode, output ready signal
665
+ if (this.isSdkMode && this.sdkOutputAdapter) {
666
+ this.sdkOutputAdapter.outputReady();
667
+ }
668
+ }
669
+ /**
670
+ * Start heartbeat timeout monitoring in SDK mode
671
+ */
672
+ startHeartbeatMonitoring() {
673
+ this.stopHeartbeatMonitoring();
674
+ // Check heartbeat timeout periodically
675
+ this.heartbeatTimeout = setInterval(() => {
676
+ const elapsed = Date.now() - this.lastActivityTime;
677
+ if (elapsed > this.heartbeatTimeoutMs) {
678
+ // Heartbeat timeout - no activity for too long
679
+ this.sdkOutputAdapter?.output({
680
+ type: 'error',
681
+ subtype: 'heartbeat_timeout',
682
+ timestamp: Date.now(),
683
+ data: {
684
+ message: 'Heartbeat timeout - no activity detected',
685
+ elapsed_ms: elapsed,
686
+ timeout_ms: this.heartbeatTimeoutMs
687
+ }
688
+ });
689
+ // Stop monitoring
690
+ this.stopHeartbeatMonitoring();
691
+ }
692
+ }, 30000); // Check every 30 seconds
693
+ }
694
+ /**
695
+ * Stop heartbeat timeout monitoring
696
+ */
697
+ stopHeartbeatMonitoring() {
698
+ if (this.heartbeatTimeout) {
699
+ clearInterval(this.heartbeatTimeout);
700
+ this.heartbeatTimeout = null;
701
+ }
702
+ }
703
+ /**
704
+ * Reset heartbeat timeout (called on activity)
705
+ */
706
+ resetHeartbeatTimeout() {
707
+ this.lastActivityTime = Date.now();
708
+ }
709
+ /**
710
+ * Stop heartbeat monitoring (public method for cleanup)
711
+ * Used by SDK session to clean up when session ends
712
+ */
713
+ stopHeartbeatMonitor() {
714
+ this.stopHeartbeatMonitoring();
407
715
  }
408
716
  showExecutionMode() {
409
717
  const modeConfig = {
410
718
  [ExecutionMode.YOLO]: {
411
719
  color: colors.error,
412
720
  icon: icons.fire,
413
- description: 'Execute commands without confirmation'
721
+ description: 'Execute commands without confirmation',
414
722
  },
415
723
  [ExecutionMode.ACCEPT_EDITS]: {
416
724
  color: colors.warning,
417
725
  icon: icons.check,
418
- description: 'Accept all edits automatically'
726
+ description: 'Accept all edits automatically',
419
727
  },
420
728
  [ExecutionMode.PLAN]: {
421
729
  color: colors.info,
422
730
  icon: icons.brain,
423
- description: 'Plan before executing'
731
+ description: 'Plan before executing',
424
732
  },
425
733
  [ExecutionMode.DEFAULT]: {
426
734
  color: colors.success,
427
735
  icon: icons.bolt,
428
- description: 'Safe execution with confirmations'
736
+ description: 'Safe execution with confirmations',
429
737
  },
430
738
  [ExecutionMode.SMART]: {
431
739
  color: colors.primaryBright,
432
740
  icon: icons.sparkles,
433
- description: 'Smart approval with intelligent security checks'
434
- }
741
+ description: 'Smart approval with intelligent security checks',
742
+ },
435
743
  };
436
744
  const config = modeConfig[this.executionMode];
437
745
  const modeName = this.executionMode;
438
- console.log(colors.textMuted(`${icons.info} Current Mode:`));
439
- console.log(` ${config.color(config.icon)} ${styleHelpers.text.bold(config.color(modeName))}`);
440
- console.log(` ${colors.textDim(` ${config.description}`)}`);
746
+ if (!this.isSdkMode) {
747
+ console.log(colors.textMuted(`${icons.info} Current Mode:`));
748
+ console.log(` ${config.color(config.icon)} ${styleHelpers.text.bold(config.color(modeName))}`);
749
+ console.log(` ${colors.textDim(` ${config.description}`)}`);
750
+ console.log('');
751
+ }
752
+ this.showRemoteModelInfo();
753
+ }
754
+ showRemoteModelInfo() {
755
+ const authConfig = this.configManager.getAuthConfig();
756
+ const isRemote = authConfig.type === AuthType.OAUTH_XAGENT;
757
+ if (this.isSdkMode && this.sdkOutputAdapter) {
758
+ // SDK 模式:通过 adapter 输出
759
+ if (isRemote) {
760
+ const llmModel = authConfig.remote_llmModelName || 'Not set';
761
+ const vlmModel = authConfig.remote_vlmModelName || 'Not set';
762
+ this.sdkOutputAdapter.outputInfo(`Remote Models - LLM: ${llmModel}, VLM: ${vlmModel}`);
763
+ }
764
+ else {
765
+ const modelName = authConfig.modelName || 'Not set';
766
+ const guiSubagentModel = this.configManager.get('guiSubagentModel') || 'Not set';
767
+ this.sdkOutputAdapter.outputInfo(`Local Models - LLM: ${modelName}, VLM: ${guiSubagentModel}`);
768
+ }
769
+ return;
770
+ }
771
+ // 正常模式:控制台输出
772
+ if (isRemote) {
773
+ const llmModel = authConfig.remote_llmModelName || colors.textMuted('Not set');
774
+ const vlmModel = authConfig.remote_vlmModelName || colors.textMuted('Not set');
775
+ console.log(colors.textMuted(`${icons.brain} Remote Models:`));
776
+ console.log(` ${colors.primaryBright(icons.arrowRight)} ${colors.info('LLM:')} ${llmModel}`);
777
+ console.log(` ${colors.primaryBright(icons.arrowRight)} ${colors.info('VLM:')} ${vlmModel}`);
778
+ }
779
+ else {
780
+ const modelName = authConfig.modelName || colors.textMuted('Not set');
781
+ const guiSubagentModel = this.configManager.get('guiSubagentModel') || colors.textMuted('Not set');
782
+ console.log(colors.textMuted(`${icons.brain} Local Models:`));
783
+ console.log(` ${colors.primaryBright(icons.arrowRight)} ${colors.info('LLM:')} ${modelName}`);
784
+ console.log(` ${colors.primaryBright(icons.arrowRight)} ${colors.info('VLM:')} ${guiSubagentModel}`);
785
+ }
441
786
  console.log('');
442
787
  }
443
788
  async promptLoop() {
@@ -445,19 +790,21 @@ export class InteractiveSession {
445
790
  if (this._isShuttingDown) {
446
791
  return;
447
792
  }
793
+ // In SDK mode, use a different input loop
794
+ if (this.isSdkMode) {
795
+ await this.sdkPromptLoop();
796
+ return;
797
+ }
448
798
  // Recreate readline interface for input
449
799
  if (this.rl) {
450
800
  this.rl.close();
451
801
  }
452
- // Enable raw mode BEFORE emitKeypressEvents for better ESC detection
453
- if (process.stdin.isTTY) {
454
- process.stdin.setRawMode(true);
455
- }
456
- process.stdin.resume();
457
- readline.emitKeypressEvents(process.stdin);
802
+ // Ensure TTY is in proper state for input handling
803
+ // This handles any state left by @clack/prompts or other interactions
804
+ ensureTtySane();
458
805
  this.rl = readline.createInterface({
459
806
  input: process.stdin,
460
- output: process.stdout
807
+ output: process.stdout,
461
808
  });
462
809
  const prompt = `${colors.primaryBright('❯')} `;
463
810
  this.rl.question(prompt, async (input) => {
@@ -474,14 +821,58 @@ export class InteractiveSession {
474
821
  });
475
822
  }
476
823
  async handleInput(input) {
824
+ // Reset heartbeat timeout on any input activity
825
+ this.resetHeartbeatTimeout();
477
826
  const trimmedInput = input.trim();
478
827
  if (!trimmedInput) {
479
828
  return;
480
829
  }
830
+ // Check for SDK JSON message format
831
+ if (this.isSdkMode) {
832
+ const { isSdkMessage, parseSdkMessage } = await import('./types.js');
833
+ if (isSdkMessage(trimmedInput)) {
834
+ const sdkMessage = parseSdkMessage(trimmedInput);
835
+ if (sdkMessage) {
836
+ if (sdkMessage.type === 'ping') {
837
+ // Handle ping - respond with pong
838
+ await this.handlePing(sdkMessage);
839
+ return;
840
+ }
841
+ else if (sdkMessage.type === 'control_request') {
842
+ // Handle control request
843
+ await this.handleControlRequest(sdkMessage);
844
+ return;
845
+ }
846
+ else if (sdkMessage.type === 'user') {
847
+ // Store request_id for tracking
848
+ this._currentRequestId = sdkMessage.request_id || null;
849
+ // Handle user message from SDK
850
+ await this.processUserMessage(sdkMessage.content);
851
+ return;
852
+ }
853
+ else if (sdkMessage.type === 'approval_response') {
854
+ // Handle approval response
855
+ await this.handleApprovalResponse(sdkMessage);
856
+ return;
857
+ }
858
+ else if (sdkMessage.type === 'question_response') {
859
+ // Handle question response
860
+ await this.handleQuestionResponse(sdkMessage);
861
+ return;
862
+ }
863
+ }
864
+ }
865
+ else {
866
+ // Not a JSON SDK message, treat as regular text
867
+ await this.processUserMessage(trimmedInput);
868
+ return;
869
+ }
870
+ }
481
871
  if (trimmedInput.startsWith('/')) {
482
872
  const handled = await this.slashCommandHandler.handleCommand(trimmedInput);
483
873
  if (handled) {
484
- this.executionMode = this.configManager.getApprovalMode() || this.configManager.getExecutionMode();
874
+ this.executionMode =
875
+ this.configManager.getApprovalMode() || this.configManager.getExecutionMode();
485
876
  // Sync conversation history to slashCommandHandler
486
877
  this.slashCommandHandler.setConversationHistory(this.conversation);
487
878
  }
@@ -493,6 +884,297 @@ export class InteractiveSession {
493
884
  }
494
885
  await this.processUserMessage(trimmedInput);
495
886
  }
887
+ /**
888
+ * SDK prompt loop - reads input from stdin without showing prompt
889
+ */
890
+ async sdkPromptLoop() {
891
+ // Read input from stdin directly without outputting prompt
892
+ const input = await this.readSdkInput();
893
+ if (this._isShuttingDown || input === null) {
894
+ return;
895
+ }
896
+ try {
897
+ await this.handleInput(input);
898
+ }
899
+ catch (err) {
900
+ this.sdkOutputAdapter?.output({
901
+ type: 'error',
902
+ subtype: 'general',
903
+ timestamp: Date.now(),
904
+ data: { message: err.message }
905
+ });
906
+ }
907
+ // Continue the loop
908
+ this.sdkPromptLoop();
909
+ }
910
+ sdkRl = null;
911
+ sdkInputProcessing = false;
912
+ /**
913
+ * Check if a line is an SDK control message (approval_response, question_response)
914
+ */
915
+ isSdkControlMessage(line) {
916
+ try {
917
+ const parsed = JSON.parse(line);
918
+ return parsed.type === 'approval_response' || parsed.type === 'question_response';
919
+ }
920
+ catch {
921
+ return false;
922
+ }
923
+ }
924
+ /**
925
+ * Process an SDK input line (returns true if processed)
926
+ */
927
+ async processSdkInputLine(line) {
928
+ const { isSdkMessage, parseSdkMessage } = await import('./types.js');
929
+ if (isSdkMessage(line)) {
930
+ const sdkMessage = parseSdkMessage(line);
931
+ if (sdkMessage) {
932
+ if (sdkMessage.type === 'ping') {
933
+ await this.handlePing(sdkMessage);
934
+ return true;
935
+ }
936
+ else if (sdkMessage.type === 'control_request') {
937
+ await this.handleControlRequest(sdkMessage);
938
+ return true;
939
+ }
940
+ else if (sdkMessage.type === 'user') {
941
+ this._currentRequestId = sdkMessage.request_id || null;
942
+ await this.processUserMessage(sdkMessage.content);
943
+ return true;
944
+ }
945
+ else if (sdkMessage.type === 'approval_response') {
946
+ await this.handleApprovalResponse(sdkMessage);
947
+ return true;
948
+ }
949
+ else if (sdkMessage.type === 'question_response') {
950
+ await this.handleQuestionResponse(sdkMessage);
951
+ return true;
952
+ }
953
+ }
954
+ }
955
+ return false;
956
+ }
957
+ /**
958
+ * Read a line of input from stdin (SDK mode)
959
+ * Uses readline 'line' event for reliable stdin reading
960
+ * SDK control messages (approval_response, question_response) bypass the main loop
961
+ */
962
+ readSdkInput() {
963
+ return new Promise((resolve) => {
964
+ // Create readline interface if not exists
965
+ if (!this.sdkRl) {
966
+ this.sdkRl = readline.createInterface({
967
+ input: process.stdin,
968
+ crlfDelay: Infinity,
969
+ });
970
+ // Handle line events - SDK control messages bypass normal flow
971
+ this.sdkRl.on('line', async (line) => {
972
+ const cleanLine = line
973
+ .replace(/^\uFEFF/, '')
974
+ // eslint-disable-next-line no-control-regex
975
+ .replace(/[\x00-\x1F\x7F-\x9F]/g, '');
976
+ // Check if this is an SDK control message
977
+ if (this.isSdkControlMessage(cleanLine)) {
978
+ // Process immediately, don't wait for main loop
979
+ // We need to set a flag to prevent re-entrancy issues
980
+ if (this.sdkInputProcessing) {
981
+ // Already processing, queue it
982
+ this.sdkInputBuffer.push(cleanLine);
983
+ }
984
+ else {
985
+ this.sdkInputProcessing = true;
986
+ try {
987
+ await this.processSdkInputLine(cleanLine);
988
+ }
989
+ finally {
990
+ this.sdkInputProcessing = false;
991
+ }
992
+ }
993
+ return;
994
+ }
995
+ // Regular input
996
+ if (this.resolveInput) {
997
+ // Immediate handler available, resolve immediately
998
+ this.resolveInput(cleanLine);
999
+ this.resolveInput = null;
1000
+ }
1001
+ else {
1002
+ // No handler available, queue the message
1003
+ this.sdkInputBuffer.push(cleanLine);
1004
+ }
1005
+ });
1006
+ // Handle close events
1007
+ this.sdkRl.on('close', () => {
1008
+ if (this.resolveInput) {
1009
+ this.resolveInput(null);
1010
+ this.resolveInput = null;
1011
+ }
1012
+ });
1013
+ // Handle errors
1014
+ this.sdkRl.on('error', () => {
1015
+ if (this.resolveInput) {
1016
+ this.resolveInput(null);
1017
+ this.resolveInput = null;
1018
+ }
1019
+ });
1020
+ }
1021
+ // Check for SDK control messages in buffer first
1022
+ for (let i = 0; i < this.sdkInputBuffer.length; i++) {
1023
+ const line = this.sdkInputBuffer[i];
1024
+ if (this.isSdkControlMessage(line)) {
1025
+ this.sdkInputBuffer.splice(i, 1);
1026
+ resolve(line);
1027
+ return;
1028
+ }
1029
+ }
1030
+ if (this.sdkInputBuffer.length > 0) {
1031
+ const line = this.sdkInputBuffer.shift();
1032
+ resolve(line);
1033
+ return;
1034
+ }
1035
+ // Set up the resolve callback
1036
+ this.resolveInput = (value) => {
1037
+ resolve(value);
1038
+ };
1039
+ });
1040
+ }
1041
+ /**
1042
+ * Handle SDK ping messages (heartbeat)
1043
+ */
1044
+ async handlePing(pingMessage) {
1045
+ const requestId = pingMessage.request_id || `ping_${Date.now()}`;
1046
+ // Reset activity timestamp on ping (heartbeat activity)
1047
+ this.lastActivityTime = Date.now();
1048
+ // Send pong response through SDK adapter for consistency
1049
+ this.sdkOutputAdapter?.output({
1050
+ type: 'system',
1051
+ subtype: 'pong',
1052
+ timestamp: Date.now(),
1053
+ data: {
1054
+ type: 'pong',
1055
+ request_id: requestId,
1056
+ timestamp: Date.now()
1057
+ }
1058
+ });
1059
+ }
1060
+ /**
1061
+ * Handle SDK control requests
1062
+ */
1063
+ async handleControlRequest(request) {
1064
+ // Update activity to prevent heartbeat timeout during control requests
1065
+ this.lastActivityTime = Date.now();
1066
+ const { request_id, request: req } = request;
1067
+ switch (req.subtype) {
1068
+ case 'interrupt':
1069
+ this.sdkOutputAdapter?.outputSystem('interrupt', { request_id });
1070
+ this._isShuttingDown = true;
1071
+ process.exit(0);
1072
+ break;
1073
+ case 'set_permission_mode':
1074
+ {
1075
+ const { ExecutionMode } = await import('./types.js');
1076
+ const modeMap = {
1077
+ 'default': ExecutionMode.DEFAULT,
1078
+ 'acceptEdits': ExecutionMode.ACCEPT_EDITS,
1079
+ 'plan': ExecutionMode.PLAN,
1080
+ 'bypassPermissions': ExecutionMode.YOLO,
1081
+ };
1082
+ const mode = modeMap[req.mode] || ExecutionMode.SMART;
1083
+ this.executionMode = mode;
1084
+ this.sdkOutputAdapter?.outputSystem('permission_mode_changed', {
1085
+ request_id,
1086
+ mode: req.mode
1087
+ });
1088
+ }
1089
+ break;
1090
+ case 'set_model':
1091
+ this.sdkOutputAdapter?.outputSystem('model_changed', {
1092
+ request_id,
1093
+ model: req.model
1094
+ });
1095
+ break;
1096
+ default:
1097
+ this.sdkOutputAdapter?.outputWarning(`Unknown control request: ${req.subtype}`);
1098
+ }
1099
+ }
1100
+ /**
1101
+ * Handle SDK approval responses
1102
+ */
1103
+ async handleApprovalResponse(response) {
1104
+ const { request_id, approved } = response;
1105
+ const pending = this.approvalPromises.get(request_id);
1106
+ if (pending) {
1107
+ pending.resolve(approved);
1108
+ this.approvalPromises.delete(request_id);
1109
+ }
1110
+ else {
1111
+ this.sdkOutputAdapter?.outputWarning(`Unknown approval request ID: ${request_id}`);
1112
+ }
1113
+ }
1114
+ /**
1115
+ * Handle SDK question responses
1116
+ */
1117
+ async handleQuestionResponse(response) {
1118
+ const { request_id, answers } = response;
1119
+ const pending = this.questionPromises.get(request_id);
1120
+ if (pending) {
1121
+ pending.resolve(answers);
1122
+ this.questionPromises.delete(request_id);
1123
+ }
1124
+ else {
1125
+ this.sdkOutputAdapter?.outputWarning(`Unknown question request ID: ${request_id}`);
1126
+ }
1127
+ }
1128
+ /**
1129
+ * Wait for SDK approval response
1130
+ */
1131
+ async waitForApprovalResponse(requestId, timeoutMs = 300000) {
1132
+ return new Promise((resolve, reject) => {
1133
+ const timeout = setTimeout(() => {
1134
+ const pending = this.approvalPromises.get(requestId);
1135
+ if (pending) {
1136
+ pending.reject(new Error('Approval request timeout'));
1137
+ this.approvalPromises.delete(requestId);
1138
+ }
1139
+ resolve(false);
1140
+ }, timeoutMs);
1141
+ this.approvalPromises.set(requestId, {
1142
+ resolve: (approved) => {
1143
+ clearTimeout(timeout);
1144
+ resolve(approved);
1145
+ },
1146
+ reject: (err) => {
1147
+ clearTimeout(timeout);
1148
+ reject(err);
1149
+ }
1150
+ });
1151
+ });
1152
+ }
1153
+ /**
1154
+ * Wait for SDK question response
1155
+ */
1156
+ async waitForQuestionResponse(requestId, timeoutMs = 300000) {
1157
+ return new Promise((resolve, reject) => {
1158
+ const timeout = setTimeout(() => {
1159
+ const pending = this.questionPromises.get(requestId);
1160
+ if (pending) {
1161
+ pending.reject(new Error('Question request timeout'));
1162
+ this.questionPromises.delete(requestId);
1163
+ }
1164
+ resolve([]); // Return empty array on timeout instead of rejecting
1165
+ }, timeoutMs);
1166
+ this.questionPromises.set(requestId, {
1167
+ resolve: (answers) => {
1168
+ clearTimeout(timeout);
1169
+ resolve(answers);
1170
+ },
1171
+ reject: (err) => {
1172
+ clearTimeout(timeout);
1173
+ reject(err);
1174
+ }
1175
+ });
1176
+ });
1177
+ }
496
1178
  async handleSubAgentCommand(input) {
497
1179
  const [agentType, ...taskParts] = input.slice(1).split(' ');
498
1180
  const task = taskParts.join(' ');
@@ -511,11 +1193,11 @@ export class InteractiveSession {
511
1193
  this.currentAgent = agent;
512
1194
  await this.processUserMessage(task, agent);
513
1195
  }
514
- async processUserMessage(message, agent) {
515
- const inputs = parseInput(message);
516
- const textInput = inputs.find(i => i.type === 'text');
517
- const fileInputs = inputs.filter(i => i.type === 'file');
518
- const commandInput = inputs.find(i => i.type === 'command');
1196
+ async processUserMessage(message, _agent) {
1197
+ const inputs = await parseInput(message);
1198
+ const textInput = inputs.find((i) => i.type === 'text');
1199
+ const fileInputs = inputs.filter((i) => i.type === 'file');
1200
+ const commandInput = inputs.find((i) => i.type === 'command');
519
1201
  if (commandInput) {
520
1202
  await this.executeShellCommand(commandInput.content);
521
1203
  return;
@@ -538,7 +1220,7 @@ export class InteractiveSession {
538
1220
  type: 'text',
539
1221
  content: userContent,
540
1222
  rawInput: message,
541
- timestamp: Date.now()
1223
+ timestamp: Date.now(),
542
1224
  };
543
1225
  await this.sessionManager.addInput(sessionInput);
544
1226
  // Calculate thinking tokens based on config and user input
@@ -552,25 +1234,19 @@ export class InteractiveSession {
552
1234
  const userMessage = {
553
1235
  role: 'user',
554
1236
  content: userContent,
555
- timestamp: Date.now()
1237
+ timestamp: Date.now(),
556
1238
  };
557
- // Save last user message for recovery after compression
558
- const lastUserMessage = userMessage;
559
1239
  this.conversation.push(userMessage);
560
1240
  await this.conversationManager.addMessage(userMessage);
561
- // Check if context compression is needed
562
- await this.checkAndCompressContext(lastUserMessage);
563
1241
  // Use remote AI client if available (OAuth XAGENT mode)
564
1242
  const currentSelectedAuthType = this.configManager.get('selectedAuthType');
565
- logger.debug('[DEBUG processUserMessage] remoteAIClient exists:', !!this.remoteAIClient ? 'true' : 'false');
566
- logger.debug('[DEBUG processUserMessage] selectedAuthType:', String(currentSelectedAuthType));
567
- logger.debug('[DEBUG processUserMessage] AuthType.OAUTH_XAGENT:', String(AuthType.OAUTH_XAGENT));
1243
+ logger.debug(`[DEBUG] processUserMessage: remoteAIClient exists=${!!this.remoteAIClient}, selectedAuthType=${currentSelectedAuthType}`);
568
1244
  if (this.remoteAIClient) {
569
- logger.debug('[DEBUG processUserMessage] Using generateRemoteResponse');
1245
+ logger.debug('[DEBUG] Using generateRemoteResponse (remote mode)');
570
1246
  await this.generateRemoteResponse(thinkingTokens);
571
1247
  }
572
1248
  else {
573
- logger.debug('[DEBUG processUserMessage] Using generateResponse (local mode)');
1249
+ logger.debug('[DEBUG] Using generateResponse (local mode)');
574
1250
  await this.generateResponse(thinkingTokens);
575
1251
  }
576
1252
  }
@@ -578,6 +1254,11 @@ export class InteractiveSession {
578
1254
  const indent = this.getIndent();
579
1255
  const thinkingConfig = this.configManager.getThinkingConfig();
580
1256
  const displayMode = thinkingConfig.displayMode || 'compact';
1257
+ // SDK 模式:使用 adapter 输出
1258
+ if (this.isSdkMode && this.sdkOutputAdapter) {
1259
+ this.sdkOutputAdapter.outputThinking(reasoningContent, displayMode);
1260
+ return;
1261
+ }
581
1262
  const separator = icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length);
582
1263
  console.log('');
583
1264
  console.log(`${indent}${colors.border(separator)}`);
@@ -588,7 +1269,7 @@ export class InteractiveSession {
588
1269
  console.log('');
589
1270
  console.log(`${indent}${colors.textDim(reasoningContent.replace(/^/gm, indent))}`);
590
1271
  break;
591
- case 'compact':
1272
+ case 'compact': {
592
1273
  // Compact display, truncate partial content
593
1274
  const maxLength = 500;
594
1275
  const truncatedContent = reasoningContent.length > maxLength
@@ -599,6 +1280,7 @@ export class InteractiveSession {
599
1280
  console.log(`${indent}${colors.textDim(truncatedContent.replace(/^/gm, indent))}`);
600
1281
  console.log(`${indent}${colors.textDim(`[${reasoningContent.length} chars total]`)}`);
601
1282
  break;
1283
+ }
602
1284
  case 'indicator':
603
1285
  // Show indicator only
604
1286
  console.log(`${indent}${colors.textDim(`${icons.brain} Thinking process completed`)}`);
@@ -615,34 +1297,69 @@ export class InteractiveSession {
615
1297
  /**
616
1298
  * Check and compress conversation context
617
1299
  */
618
- async checkAndCompressContext(lastUserMessage) {
1300
+ async checkAndCompressContext() {
619
1301
  const compressionConfig = this.configManager.getContextCompressionConfig();
620
1302
  if (!compressionConfig.enabled) {
621
1303
  return;
622
1304
  }
623
- const { needsCompression, reason } = this.contextCompressor.needsCompression(this.conversation, compressionConfig);
624
- if (needsCompression) {
625
- const indent = this.getIndent();
1305
+ const indent = this.getIndent();
1306
+ const _currentTokens = this.contextCompressor.estimateContextTokens(this.conversation);
1307
+ const currentMessages = this.conversation.length;
1308
+ const { needsCompression, reason, tokenCount } = this.contextCompressor.needsCompression(this.conversation, compressionConfig);
1309
+ if (!needsCompression) {
1310
+ return;
1311
+ }
1312
+ // Extract threshold and contextWindow from reason
1313
+ const thresholdMatch = reason.match(/budget\s*\((\d+)/);
1314
+ const contextWindowMatch = reason.match(/contextWindow:\s*(\d+)/);
1315
+ const threshold = thresholdMatch ? parseInt(thresholdMatch[1], 10) : 0;
1316
+ const contextWindow = contextWindowMatch ? parseInt(contextWindowMatch[1], 10) : 0;
1317
+ if (this.isSdkMode && this.sdkOutputAdapter) {
1318
+ this.sdkOutputAdapter.outputContextCompressionTriggered(reason);
1319
+ }
1320
+ else {
626
1321
  console.log('');
627
- console.log(`${indent}${colors.warning(`${icons.brain} Context compression triggered: ${reason}`)}`);
628
- const toolRegistry = getToolRegistry();
629
- const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are a helpful AI assistant.';
630
- const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode);
631
- const enhancedSystemPrompt = await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
632
- const result = await this.contextCompressor.compressContext(this.conversation, enhancedSystemPrompt, compressionConfig);
633
- if (result.wasCompressed) {
634
- this.conversation = result.compressedMessages;
635
- // console.log(`${indent}${colors.success(`✓ Compressed ${result.originalMessageCount} messages to ${result.compressedMessageCount} messages`)}`);
636
- console.log(`${indent}${colors.textMuted(`✓ Size: ${result.originalSize} → ${result.compressedSize} chars (${Math.round((1 - result.compressedSize / result.originalSize) * 100)}% reduction)`)}`);
637
- // Display compressed summary content
638
- const summaryMessage = result.compressedMessages.find(m => m.role === 'assistant');
639
- if (summaryMessage && summaryMessage.content) {
640
- const maxPreviewLength = 800;
641
- let summaryContent = summaryMessage.content;
642
- const isTruncated = summaryContent.length > maxPreviewLength;
643
- if (isTruncated) {
644
- summaryContent = summaryContent.substring(0, maxPreviewLength) + '\n...';
645
- }
1322
+ console.log(`${indent}${colors.success(`${icons.sparkles} Compressing context (${currentMessages} msgs, ${tokenCount.toLocaleString()} > ${threshold.toLocaleString()}/${contextWindow.toLocaleString()} tokens, ${Math.round((tokenCount / contextWindow) * 100)}% of context window)...`)}`);
1323
+ }
1324
+ const toolRegistry = getToolRegistry();
1325
+ const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are a helpful AI assistant.';
1326
+ const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode);
1327
+ const enhancedSystemPrompt = await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
1328
+ const result = await this.contextCompressor.compressContext(this.conversation, enhancedSystemPrompt, compressionConfig);
1329
+ if (result.wasCompressed) {
1330
+ this.conversation = result.compressedMessages;
1331
+ const reductionPercent = Math.round((1 - result.compressedSize / result.originalSize) * 100);
1332
+ if (this.isSdkMode && this.sdkOutputAdapter) {
1333
+ this.sdkOutputAdapter.outputContextCompressionResult(result.originalSize, result.compressedSize, reductionPercent, result.originalMessageCount, result.compressedMessageCount);
1334
+ }
1335
+ else {
1336
+ console.log(`${indent}${colors.success(`${icons.success} Compressed ${result.originalMessageCount} → ${result.compressedMessageCount} messages (${reductionPercent}% smaller)`)}`);
1337
+ }
1338
+ // Summary is embedded in first user message, look for it
1339
+ // The format is: "[Conversation Summary - X messages compressed]\n\n${summary}"
1340
+ let summaryMessage = result.compressedMessages.find((m) => m.role === 'user' && m.content.includes('[Conversation Summary'));
1341
+ if (summaryMessage) {
1342
+ // Extract summary content after the header
1343
+ const match = summaryMessage.content.match(/\[Conversation Summary.*?\]:\n\n(.+)/s);
1344
+ if (match) {
1345
+ summaryMessage = {
1346
+ role: 'assistant',
1347
+ content: match[1],
1348
+ timestamp: summaryMessage.timestamp,
1349
+ };
1350
+ }
1351
+ }
1352
+ if (summaryMessage && summaryMessage.content) {
1353
+ const maxPreviewLength = 800;
1354
+ let summaryContent = summaryMessage.content;
1355
+ const isTruncated = summaryContent.length > maxPreviewLength;
1356
+ if (isTruncated) {
1357
+ summaryContent = summaryContent.substring(0, maxPreviewLength) + '\n...';
1358
+ }
1359
+ if (this.isSdkMode && this.sdkOutputAdapter) {
1360
+ this.sdkOutputAdapter.outputContextCompressionSummary('Conversation compressed successfully', summaryContent, isTruncated, summaryMessage.content.length);
1361
+ }
1362
+ else {
646
1363
  console.log('');
647
1364
  console.log(`${indent}${theme.predefinedStyles.title(`${icons.sparkles} Conversation Summary`)}`);
648
1365
  const separator = icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length * 2);
@@ -654,13 +1371,9 @@ export class InteractiveSession {
654
1371
  }
655
1372
  console.log(`${indent}${colors.border(separator)}`);
656
1373
  }
657
- // Restore user messages after compression, ensuring user message exists for API calls
658
- if (lastUserMessage) {
659
- this.conversation.push(lastUserMessage);
660
- }
661
- // Sync compressed conversation history to slashCommandHandler
662
- this.slashCommandHandler.setConversationHistory(this.conversation);
663
1374
  }
1375
+ // Sync compressed conversation history to slashCommandHandler
1376
+ this.slashCommandHandler.setConversationHistory(this.conversation);
664
1377
  }
665
1378
  }
666
1379
  async executeShellCommand(command) {
@@ -683,15 +1396,15 @@ export class InteractiveSession {
683
1396
  tool: 'Bash',
684
1397
  params: { command },
685
1398
  result,
686
- timestamp: Date.now()
1399
+ timestamp: Date.now(),
687
1400
  };
688
- this.toolCalls.push(toolCall);
1401
+ this.tool_calls.push(toolCall);
689
1402
  // Record command execution to session manager
690
1403
  await this.sessionManager.addInput({
691
1404
  type: 'command',
692
1405
  content: command,
693
1406
  rawInput: command,
694
- timestamp: Date.now()
1407
+ timestamp: Date.now(),
695
1408
  });
696
1409
  await this.sessionManager.addOutput({
697
1410
  role: 'tool',
@@ -699,7 +1412,7 @@ export class InteractiveSession {
699
1412
  toolName: 'Bash',
700
1413
  toolParams: { command },
701
1414
  toolResult: result,
702
- timestamp: Date.now()
1415
+ timestamp: Date.now(),
703
1416
  });
704
1417
  }
705
1418
  catch (error) {
@@ -724,11 +1437,22 @@ export class InteractiveSession {
724
1437
  /**
725
1438
  * Create remote mode LLM caller
726
1439
  */
727
- createRemoteCaller(taskId, status) {
1440
+ createRemoteCaller(taskId, _status) {
728
1441
  const client = this.remoteAIClient;
729
1442
  return {
730
- chatCompletion: (messages, options) => client.chatCompletion(messages, { ...options, taskId, status }),
731
- isRemote: true
1443
+ chatCompletion: (messages, options) => {
1444
+ // Must fetch authConfig inside the closure, otherwise it captures stale config
1445
+ const authConfig = this.configManager.getAuthConfig();
1446
+ logger.debug(`[DEBUG] createRemoteCaller: llmModelName=${authConfig.remote_llmModelName}, vlmModelName=${authConfig.remote_vlmModelName}`);
1447
+ return client.chatCompletion(messages, {
1448
+ ...options,
1449
+ taskId,
1450
+ status: options.isFirstApiCall ? 'begin' : 'continue',
1451
+ llmModelName: authConfig.remote_llmModelName,
1452
+ vlmModelName: authConfig.remote_vlmModelName
1453
+ });
1454
+ },
1455
+ isRemote: true,
732
1456
  };
733
1457
  }
734
1458
  /**
@@ -738,47 +1462,43 @@ export class InteractiveSession {
738
1462
  const client = this.aiClient;
739
1463
  return {
740
1464
  chatCompletion: (messages, options) => client.chatCompletion(messages, options),
741
- isRemote: false
1465
+ isRemote: false,
742
1466
  };
743
1467
  }
744
- async generateResponse(thinkingTokens = 0, customAIClient, existingTaskId) {
1468
+ async generateResponse(thinkingTokens = 0, _customAIClient, existingTaskId) {
745
1469
  // Use existing taskId or create new one for this user interaction
746
1470
  // If taskId already exists (e.g., from tool calls), reuse it
747
1471
  const taskId = existingTaskId || this.currentTaskId || crypto.randomUUID();
748
1472
  this.currentTaskId = taskId;
749
- this.isFirstApiCall = true;
750
- // Determine status based on whether this is the first API call
1473
+ // isFirstApiCall is reset in generateRemoteResponse for new tasks
1474
+ // For continuation calls (existingTaskId provided), keep previous value
1475
+ // Use unified LLM Caller with taskId (automatically selects local or remote mode)
751
1476
  const status = this.isFirstApiCall ? 'begin' : 'continue';
752
- // Use custom AI client if provided, otherwise use default logic
753
- let chatCompletion;
754
- let isRemote = false;
755
- if (customAIClient) {
756
- // Custom client (used by remote mode) - pass taskId and status
757
- chatCompletion = (messages, options) => customAIClient.chatCompletion(messages, { ...options, taskId, status });
758
- isRemote = true;
759
- }
760
- else {
761
- // Use unified LLM Caller with taskId (automatically selects local or remote mode)
762
- const caller = this.createLLMCaller(taskId, status);
763
- chatCompletion = caller.chatCompletion;
764
- isRemote = caller.isRemote;
765
- }
766
- if (!isRemote && !this.aiClient && !customAIClient) {
1477
+ const caller = this.createLLMCaller(taskId, status);
1478
+ const chatCompletion = caller.chatCompletion;
1479
+ const isRemote = caller.isRemote;
1480
+ if (!isRemote && !this.aiClient) {
767
1481
  console.log(colors.error('AI client not initialized'));
768
1482
  return;
769
1483
  }
770
1484
  // Mark that an operation is in progress
771
1485
  this._isOperationInProgress = true;
772
- const indent = this.getIndent();
773
1486
  const thinkingText = colors.textMuted(`Thinking... (Press ESC to cancel)`);
774
1487
  const icon = colors.primary(icons.brain);
775
1488
  const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
776
1489
  let frameIndex = 0;
1490
+ // SDK 模式下不显示 spinner
1491
+ const showThinkingSpinner = !this.isSdkMode;
1492
+ let spinnerInterval = null;
777
1493
  // Custom spinner: only icon rotates, text stays static
778
- const spinnerInterval = setInterval(() => {
779
- process.stdout.write(`\r${colors.primary(frames[frameIndex])} ${icon} ${thinkingText}`);
780
- frameIndex = (frameIndex + 1) % frames.length;
781
- }, 120);
1494
+ if (showThinkingSpinner) {
1495
+ spinnerInterval = setInterval(() => {
1496
+ process.stdout.write(`\r${colors.primary(frames[frameIndex])} ${icon} ${thinkingText}`);
1497
+ frameIndex = (frameIndex + 1) % frames.length;
1498
+ }, 120);
1499
+ }
1500
+ let content = '';
1501
+ let reasoningContent = '';
782
1502
  try {
783
1503
  const memory = await this.memoryManager.loadMemory();
784
1504
  const toolRegistry = getToolRegistry();
@@ -790,81 +1510,94 @@ export class InteractiveSession {
790
1510
  const toolDefinitions = toolRegistry.getToolDefinitions();
791
1511
  // Available tools for this session
792
1512
  const availableTools = this.executionMode !== ExecutionMode.DEFAULT && allowedToolNames.length > 0
793
- ? toolDefinitions.filter((tool) => allowedToolNames.includes(tool.function.name))
1513
+ ? toolDefinitions.filter((tool) => typeof tool.function?.name === 'string' &&
1514
+ allowedToolNames.includes(tool.function.name))
794
1515
  : toolDefinitions;
795
- const baseSystemPrompt = this.currentAgent?.systemPrompt;
1516
+ const baseSystemPrompt = this.currentAgent?.systemPrompt ?? '';
796
1517
  const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode, undefined, this.mcpManager);
797
1518
  const enhancedSystemPrompt = await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
798
1519
  const messages = [
799
1520
  { role: 'system', content: `${enhancedSystemPrompt}\n\n${memory}`, timestamp: Date.now() },
800
- ...this.conversation
1521
+ ...this.conversation,
801
1522
  ];
802
1523
  const operationId = `ai-response-${Date.now()}`;
803
1524
  const response = await this.cancellationManager.withCancellation(chatCompletion(messages, {
804
1525
  tools: availableTools,
805
1526
  toolChoice: availableTools.length > 0 ? 'auto' : 'none',
806
- thinkingTokens
1527
+ thinkingTokens,
1528
+ isFirstApiCall: this.isFirstApiCall,
807
1529
  }), operationId);
808
1530
  // Mark that first API call is complete
809
1531
  this.isFirstApiCall = false;
810
- clearInterval(spinnerInterval);
1532
+ if (spinnerInterval)
1533
+ clearInterval(spinnerInterval);
811
1534
  process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r'); // Clear spinner line
812
1535
  const assistantMessage = response.choices[0].message;
813
- const content = typeof assistantMessage.content === 'string'
814
- ? assistantMessage.content
815
- : '';
816
- const reasoningContent = assistantMessage.reasoning_content || '';
1536
+ content = typeof assistantMessage.content === 'string' ? assistantMessage.content : '';
1537
+ reasoningContent = assistantMessage.reasoning_content || '';
817
1538
  // Display reasoning content if available and thinking mode is enabled
818
1539
  if (reasoningContent && this.configManager.getThinkingConfig().enabled) {
819
1540
  this.displayThinkingContent(reasoningContent);
820
1541
  }
821
- console.log('');
822
- console.log(`${indent}${colors.primaryBright(`${icons.robot} Assistant:`)}`);
823
- console.log(`${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`);
824
- console.log('');
825
- const renderedContent = renderMarkdown(content, (process.stdout.columns || 80) - indent.length * 2);
826
- console.log(`${indent}${renderedContent.replace(/^/gm, indent)}`);
827
- console.log('');
1542
+ // Output assistant response
1543
+ this.outputAssistant(content, reasoningContent);
828
1544
  this.conversation.push({
829
1545
  role: 'assistant',
830
1546
  content,
831
1547
  timestamp: Date.now(),
832
- reasoningContent,
833
- toolCalls: assistantMessage.tool_calls
1548
+ reasoning_content: reasoningContent,
1549
+ tool_calls: assistantMessage.tool_calls,
834
1550
  });
835
1551
  // Record output to session manager
836
1552
  await this.sessionManager.addOutput({
837
1553
  role: 'assistant',
838
1554
  content,
839
1555
  timestamp: Date.now(),
840
- reasoningContent,
841
- toolCalls: assistantMessage.tool_calls
1556
+ reasoning_content: reasoningContent,
1557
+ tool_calls: assistantMessage.tool_calls,
842
1558
  });
843
1559
  if (assistantMessage.tool_calls) {
844
1560
  await this.handleToolCalls(assistantMessage.tool_calls);
845
1561
  }
1562
+ else {
1563
+ await this.checkAndCompressContext();
1564
+ }
846
1565
  if (this.checkpointManager.isEnabled()) {
847
- await this.checkpointManager.createCheckpoint(`Response generated at ${new Date().toLocaleString()}`, [...this.conversation], [...this.toolCalls]);
1566
+ await this.checkpointManager.createCheckpoint(`Response generated at ${new Date().toLocaleString()}`, [...this.conversation], [...this.tool_calls]);
1567
+ }
1568
+ // Signal request completion to SDK (no tools to execute)
1569
+ if (this.isSdkMode && this.sdkOutputAdapter && this._currentRequestId) {
1570
+ this.sdkOutputAdapter.outputRequestDone(this._currentRequestId, 'success');
1571
+ this._currentRequestId = null;
848
1572
  }
849
1573
  // Operation completed successfully, clear the flag
850
1574
  this._isOperationInProgress = false;
851
1575
  }
852
1576
  catch (error) {
853
- clearInterval(spinnerInterval);
1577
+ if (spinnerInterval)
1578
+ clearInterval(spinnerInterval);
854
1579
  process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
855
1580
  // Clear the operation flag
856
1581
  this._isOperationInProgress = false;
1582
+ // Signal request completion to SDK
1583
+ if (this.isSdkMode && this.sdkOutputAdapter && this._currentRequestId) {
1584
+ const status = error.message === 'Operation cancelled by user' ? 'cancelled' : 'error';
1585
+ this.sdkOutputAdapter.outputRequestDone(this._currentRequestId, status);
1586
+ this._currentRequestId = null;
1587
+ }
857
1588
  if (error.message === 'Operation cancelled by user') {
858
- // Mark task as cancelled
1589
+ // Notify backend to cancel the task
859
1590
  if (this.remoteAIClient && this.currentTaskId) {
860
- await this.remoteAIClient.cancelTask(this.currentTaskId).catch(() => { });
1591
+ await this.remoteAIClient.cancelTask?.(this.currentTaskId).catch(() => { });
861
1592
  }
862
1593
  return;
863
1594
  }
864
- // Mark task as cancelled when error occurs (发送 status: 'cancel')
865
- logger.debug(`[Session] Task failed: taskId=${this.currentTaskId}, error: ${error.message}`);
1595
+ // Distinguish error types: timeout vs other failures
1596
+ const isTimeout = error.message.includes('timeout') || error.message.includes('Timeout');
1597
+ const failureReason = isTimeout ? 'timeout' : 'failure';
1598
+ logger.debug(`[Session] Task failed: taskId=${this.currentTaskId}, error: ${error.message}, reason: ${failureReason}`);
866
1599
  if (this.remoteAIClient && this.currentTaskId) {
867
- await this.remoteAIClient.cancelTask(this.currentTaskId).catch(() => { });
1600
+ await this.remoteAIClient.failTask?.(this.currentTaskId, failureReason).catch(() => { });
868
1601
  }
869
1602
  console.log(colors.error(`Error: ${error.message}`));
870
1603
  }
@@ -881,12 +1614,10 @@ export class InteractiveSession {
881
1614
  const taskId = existingTaskId || crypto.randomUUID();
882
1615
  this.currentTaskId = taskId;
883
1616
  logger.debug(`[Session] generateRemoteResponse: taskId=${taskId}, existingTaskId=${!!existingTaskId}`);
884
- // Reset isFirstApiCall for new task, keep true for continuation
885
- if (!existingTaskId) {
886
- this.isFirstApiCall = true;
887
- }
888
- // Determine status based on whether this is the first API call
889
- const status = this.isFirstApiCall ? 'begin' : 'continue';
1617
+ // Each new user message is a fresh task - always set isFirstApiCall = true
1618
+ // This ensures status is 'begin' for every user message
1619
+ this.isFirstApiCall = true;
1620
+ const status = 'begin';
890
1621
  logger.debug(`[Session] Status for this call: ${status}, isFirstApiCall=${this.isFirstApiCall}`);
891
1622
  // Check if remote client is available
892
1623
  if (!this.remoteAIClient) {
@@ -894,18 +1625,23 @@ export class InteractiveSession {
894
1625
  return;
895
1626
  }
896
1627
  try {
897
- // Reuse generateResponse with remote client, pass taskId to avoid generating new one
898
- await this.generateResponse(thinkingTokens, this.remoteAIClient, taskId);
1628
+ // Use unified generateResponse without passing customAIClient,
1629
+ // let createLLMCaller handle remote/local selection
1630
+ await this.generateResponse(thinkingTokens, undefined, taskId);
899
1631
  // Mark task as completed (发送 status: 'end')
900
1632
  logger.debug(`[Session] Task completed: taskId=${this.currentTaskId}`);
901
- if (this.currentTaskId) {
902
- await this.remoteAIClient.completeTask(this.currentTaskId);
1633
+ if (this.remoteAIClient && this.currentTaskId) {
1634
+ await this.remoteAIClient.completeTask?.(this.currentTaskId);
903
1635
  }
904
1636
  }
905
1637
  catch (error) {
906
1638
  // Clear the operation flag
907
1639
  this._isOperationInProgress = false;
908
1640
  if (error.message === 'Operation cancelled by user') {
1641
+ // Notify backend to cancel the task
1642
+ if (this.remoteAIClient && this.currentTaskId) {
1643
+ await this.remoteAIClient.cancelTask?.(this.currentTaskId).catch(() => { });
1644
+ }
909
1645
  return;
910
1646
  }
911
1647
  // Handle token invalid error - trigger re-authentication
@@ -915,46 +1651,48 @@ export class InteractiveSession {
915
1651
  console.log(colors.info('Your browser session has been logged out. Please log in again.'));
916
1652
  console.log('');
917
1653
  // Clear invalid credentials and persist
918
- await this.configManager.set('apiKey', '');
919
- await this.configManager.set('refreshToken', '');
920
- await this.configManager.save('global');
1654
+ this.configManager.set('apiKey', '');
1655
+ this.configManager.set('refreshToken', '');
1656
+ this.configManager.save('global');
921
1657
  logger.debug('[DEBUG generateRemoteResponse] Cleared invalid credentials, starting re-authentication...');
922
1658
  // Re-authenticate
923
1659
  await this.setupAuthentication();
924
1660
  // Reload config to ensure we have the latest authConfig
925
- logger.debug('[DEBUG generateRemoteResponse] Re-authentication completed, reloading config...');
926
- await this.configManager.load();
1661
+ this.configManager.load();
927
1662
  const authConfig = this.configManager.getAuthConfig();
928
1663
  logger.debug('[DEBUG generateRemoteResponse] After re-auth:');
929
- logger.debug(' - authConfig.apiKey exists:', !!authConfig.apiKey ? 'true' : 'false');
930
- // Recreate readline interface after inquirer
1664
+ logger.debug(' - authConfig.apiKey exists:', authConfig.apiKey ? 'true' : 'false');
1665
+ // Recreate readline interface after interactive prompt
931
1666
  this.rl.close();
932
1667
  this.rl = readline.createInterface({
933
1668
  input: process.stdin,
934
- output: process.stdout
1669
+ output: process.stdout,
935
1670
  });
936
1671
  this.rl.on('close', () => {
937
1672
  logger.debug('DEBUG: readline interface closed');
938
1673
  });
939
1674
  // Reinitialize RemoteAIClient with new token
940
1675
  if (authConfig.apiKey) {
941
- const webBaseUrl = authConfig.xagentApiBaseUrl || 'https://www.xagent-colife.net';
942
1676
  logger.debug('[DEBUG generateRemoteResponse] Reinitializing RemoteAIClient with new token');
943
- this.remoteAIClient = new RemoteAIClient(authConfig.apiKey, webBaseUrl, authConfig.showAIDebugInfo);
1677
+ this.remoteAIClient = createAIClient(authConfig);
944
1678
  }
945
1679
  else {
946
1680
  logger.debug('[DEBUG generateRemoteResponse] WARNING: No apiKey after re-authentication!');
947
1681
  }
1682
+ // Sync remoteAIClient reference to slashCommandHandler for /provider command
1683
+ this.slashCommandHandler.setRemoteAIClient(this.remoteAIClient);
948
1684
  // Retry the current operation
949
1685
  console.log('');
950
1686
  console.log(colors.info('Retrying with new authentication...'));
951
1687
  console.log('');
952
1688
  return this.generateRemoteResponse(thinkingTokens);
953
1689
  }
954
- // Mark task as cancelled when error occurs (发送 status: 'cancel')
955
- logger.debug(`[Session] Task failed: taskId=${this.currentTaskId}, error: ${error.message}`);
1690
+ // Distinguish error types: timeout vs other failures
1691
+ const isTimeout = error.message.includes('timeout') || error.message.includes('Timeout');
1692
+ const failureReason = isTimeout ? 'timeout' : 'failure';
1693
+ logger.debug(`[Session] Task failed: taskId=${this.currentTaskId}, error: ${error.message}, reason: ${failureReason}`);
956
1694
  if (this.remoteAIClient && this.currentTaskId) {
957
- await this.remoteAIClient.cancelTask(this.currentTaskId).catch(() => { });
1695
+ await this.remoteAIClient.failTask?.(this.currentTaskId, failureReason).catch(() => { });
958
1696
  }
959
1697
  console.log(colors.error(`Error: ${error.message}`));
960
1698
  return;
@@ -973,238 +1711,284 @@ export class InteractiveSession {
973
1711
  try {
974
1712
  parsedParams = typeof params === 'string' ? JSON.parse(params) : params;
975
1713
  }
976
- catch (e) {
1714
+ catch {
977
1715
  parsedParams = params;
978
1716
  }
979
1717
  return { name, params: parsedParams, index, id: toolCall.id };
980
1718
  });
981
1719
  // Display all tool calls info
982
1720
  for (const { name, params } of preparedToolCalls) {
983
- if (showToolDetails) {
984
- console.log('');
985
- console.log(`${indent}${colors.warning(`${icons.tool} Tool Call: ${name}`)}`);
986
- console.log(`${indent}${colors.textDim(JSON.stringify(params, null, 2))}`);
1721
+ // SDK mode: use adapter output
1722
+ if (this.isSdkMode && this.sdkOutputAdapter) {
1723
+ this.sdkOutputAdapter.outputToolStart(name, params);
987
1724
  }
988
1725
  else {
989
- const toolDescription = this.getToolDescription(name, params);
990
- console.log(`${indent}${colors.textMuted(`${icons.loading} ${toolDescription}`)}`);
1726
+ // Normal mode: console output
1727
+ if (showToolDetails) {
1728
+ console.log('');
1729
+ console.log(`${indent}${colors.warning(`${icons.tool} Tool Call: ${name}`)}`);
1730
+ console.log(`${indent}${colors.textDim(JSON.stringify(params, null, 2))}`);
1731
+ }
1732
+ else {
1733
+ const toolDescription = this.getToolDescription(name, params);
1734
+ console.log(`${indent}${colors.textMuted(`${icons.loading} ${toolDescription}`)}`);
1735
+ }
991
1736
  }
992
1737
  }
993
1738
  // Execute all tools in parallel
994
- const results = await toolRegistry.executeAll(preparedToolCalls.map(tc => ({ name: tc.name, params: tc.params })), this.executionMode);
995
- // Process results and maintain order
996
- let hasError = false;
997
- for (const { tool, result, error } of results) {
998
- const toolCall = preparedToolCalls.find(tc => tc.name === tool);
999
- if (!toolCall)
1000
- continue;
1001
- const { params } = toolCall;
1739
+ const results = await toolRegistry.executeAll(preparedToolCalls.map((tc) => ({ name: tc.name, params: tc.params })), this.executionMode);
1740
+ // Create a map to store results by tool call index to maintain original order
1741
+ const resultsByIndex = new Map();
1742
+ const usedIndices = new Set();
1743
+ for (const result of results) {
1744
+ // Find the first unused original index in preparedToolCalls that matches the tool name
1745
+ const originalIndex = preparedToolCalls.findIndex((tc, idx) => tc.name === result.tool && !usedIndices.has(idx));
1746
+ if (originalIndex !== -1) {
1747
+ usedIndices.add(originalIndex);
1748
+ resultsByIndex.set(originalIndex, result);
1749
+ }
1750
+ }
1751
+ // Process results in the original tool_calls order (critical for Anthropic format APIs)
1752
+ let _hasError = false;
1753
+ for (let i = 0; i < preparedToolCalls.length; i++) {
1754
+ const toolCall = preparedToolCalls[i];
1755
+ const { name: tool, params } = toolCall;
1756
+ const resultData = resultsByIndex.get(i);
1757
+ const result = resultData?.result;
1758
+ const error = resultData?.error;
1002
1759
  if (error) {
1003
1760
  if (error === 'Operation cancelled by user') {
1761
+ // Notify backend to cancel the task
1762
+ if (this.remoteAIClient && this.currentTaskId) {
1763
+ await this.remoteAIClient.cancelTask?.(this.currentTaskId).catch(() => { });
1764
+ }
1765
+ // 清理 conversation 中未完成的 tool_call
1766
+ this.cleanupIncompleteToolCalls();
1004
1767
  this._isOperationInProgress = false;
1005
1768
  return;
1006
1769
  }
1007
- hasError = true;
1008
- console.log('');
1009
- console.log(`${indent}${colors.error(`${icons.cross} Tool Error: ${tool} - ${error}`)}`);
1010
- // 添加详细的错误信息,包含工具名称和参数,便于 AI 理解和修正
1770
+ _hasError = true;
1771
+ // SDK mode: use adapter output
1772
+ if (this.isSdkMode && this.sdkOutputAdapter) {
1773
+ this.sdkOutputAdapter.outputToolError(tool, error);
1774
+ }
1775
+ else {
1776
+ // Normal mode: console output
1777
+ console.log('');
1778
+ console.log(`${indent}${colors.error(`${icons.cross} Tool Error: ${tool} - ${error}`)}`);
1779
+ }
1780
+ // Add detailed error info including tool name and params for AI understanding and correction
1011
1781
  this.conversation.push({
1012
1782
  role: 'tool',
1013
1783
  content: JSON.stringify({
1014
1784
  name: tool,
1015
1785
  parameters: params,
1016
- error: error
1786
+ error: error,
1017
1787
  }),
1018
1788
  tool_call_id: toolCall.id,
1019
- timestamp: Date.now()
1789
+ timestamp: Date.now(),
1020
1790
  });
1021
1791
  }
1022
1792
  else {
1793
+ // SDK mode: output tool result via adapter
1794
+ if (this.isSdkMode && this.sdkOutputAdapter) {
1795
+ this.sdkOutputAdapter.outputToolResult(tool, result);
1796
+ }
1023
1797
  // Use correct indent for gui-subagent tasks
1024
1798
  const isGuiSubagent = tool === 'task' && params?.subagent_type === 'gui-subagent';
1025
1799
  const displayIndent = isGuiSubagent ? indent + ' ' : indent;
1026
- // Always show details for todo tools so users can see their task lists
1027
- const isTodoTool = tool === 'todo_write' || tool === 'todo_read';
1028
- // Special handling for edit tool with diff
1029
- const isEditTool = tool === 'Edit';
1030
- const hasDiff = isEditTool && result?.diff;
1031
- // Special handling for Write tool with file preview
1032
- const isWriteTool = tool === 'Write';
1033
- const hasFilePreview = isWriteTool && result?.preview;
1034
- // Special handling for DeleteFile tool
1035
- const isDeleteTool = tool === 'DeleteFile';
1036
- const hasDeleteInfo = isDeleteTool && result?.filePath;
1037
- // Special handling for task tool (subagent)
1038
- const isTaskTool = tool === 'task' && params?.subagent_type;
1039
- // Check if tool is an MCP wrapper tool by looking up in tool registry
1040
- const { getToolRegistry } = await import('./tools.js');
1041
- const toolRegistry = getToolRegistry();
1042
- const toolDef = toolRegistry.get(tool);
1043
- const isMcpTool = toolDef && toolDef._isMcpTool === true;
1044
- if (isTodoTool) {
1045
- console.log('');
1046
- console.log(`${displayIndent}${colors.success(`${icons.check} Todo List:`)}`);
1047
- console.log(this.renderTodoList(result?.todos || [], displayIndent));
1048
- // Show summary if available
1049
- if (result?.message) {
1050
- console.log(`${displayIndent}${colors.textDim(result.message)}`);
1051
- }
1052
- }
1053
- else if (hasDiff) {
1054
- // Show edit result with diff
1055
- console.log('');
1056
- const diffOutput = renderDiff(result.diff);
1057
- const indentedDiff = diffOutput.split('\n').map(line => `${displayIndent} ${line}`).join('\n');
1058
- console.log(`${indentedDiff}`);
1059
- }
1060
- else if (hasFilePreview) {
1061
- // Show new file content in diff-like style
1062
- console.log('');
1063
- console.log(`${displayIndent}${colors.success(`${icons.file} ${result.filePath}`)}`);
1064
- console.log(`${displayIndent}${colors.textDim(` ${result.lineCount} lines`)}`);
1065
- console.log('');
1066
- console.log(renderLines(result.preview, { maxLines: 10, indent: displayIndent + ' ' }));
1067
- }
1068
- else if (hasDeleteInfo) {
1069
- // Show DeleteFile result
1070
- console.log('');
1071
- console.log(`${displayIndent}${colors.success(`${icons.check} Deleted: ${result.filePath}`)}`);
1072
- }
1073
- else if (isTaskTool) {
1074
- // Special handling for task tool (subagent) - show friendly summary
1075
- console.log('');
1076
- const subagentType = params.subagent_type;
1077
- const subagentName = params.description || (params.prompt ? params.prompt.substring(0, 50).replace(/\n/g, ' ') : 'Unknown task');
1078
- if (result?.success) {
1079
- console.log(`${displayIndent}${colors.success(`${icons.check} ${subagentType}: Completed`)}`);
1080
- console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
1081
- if (result.message) {
1082
- console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
1800
+ // Normal mode: console output (SDK mode already output via adapter above)
1801
+ if (!this.isSdkMode || !this.sdkOutputAdapter) {
1802
+ // Always show details for todo tools so users can see their task lists
1803
+ const isTodoTool = tool === 'todo_write' || tool === 'todo_read';
1804
+ // Special handling for edit tool with diff
1805
+ const isEditTool = tool === 'Edit';
1806
+ const hasDiff = isEditTool && result?.diff;
1807
+ // Special handling for Write tool with file preview
1808
+ const isWriteTool = tool === 'Write';
1809
+ const hasFilePreview = isWriteTool && result?.preview;
1810
+ // Special handling for DeleteFile tool
1811
+ const isDeleteTool = tool === 'DeleteFile';
1812
+ const hasDeleteInfo = isDeleteTool && result?.filePath;
1813
+ // Special handling for task tool (subagent)
1814
+ const isTaskTool = tool === 'task' && params?.subagent_type;
1815
+ // Check if tool is an MCP wrapper tool by looking up in tool registry
1816
+ const { getToolRegistry } = await import('./tools.js');
1817
+ const toolRegistry = getToolRegistry();
1818
+ const toolDef = toolRegistry.get(tool);
1819
+ const isMcpTool = toolDef && toolDef._isMcpTool === true;
1820
+ if (isTodoTool) {
1821
+ console.log('');
1822
+ console.log(`${displayIndent}${colors.success(`${icons.check} Todo List:`)}`);
1823
+ console.log(this.renderTodoList(result?.todos || [], displayIndent));
1824
+ // Show summary if available
1825
+ if (result?.message) {
1826
+ console.log(`${displayIndent}${colors.textDim(result.message)}`);
1083
1827
  }
1084
1828
  }
1085
- else if (result?.cancelled) {
1086
- console.log(`${displayIndent}${colors.warning(`${icons.cross} ${subagentType}: Cancelled`)}`);
1087
- console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
1829
+ else if (hasDiff) {
1830
+ // Show edit result with diff
1831
+ console.log('');
1832
+ const diffOutput = renderDiff(result.diff);
1833
+ const indentedDiff = diffOutput
1834
+ .split('\n')
1835
+ .map((line) => `${displayIndent} ${line}`)
1836
+ .join('\n');
1837
+ console.log(`${indentedDiff}`);
1088
1838
  }
1089
- else {
1090
- console.log(`${displayIndent}${colors.error(`${icons.cross} ${subagentType}: Failed`)}`);
1091
- console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
1092
- if (result?.message) {
1093
- console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
1094
- }
1839
+ else if (hasFilePreview) {
1840
+ // Show new file content in diff-like style
1841
+ console.log('');
1842
+ console.log(`${displayIndent}${colors.success(`${icons.file} ${result.filePath}`)}`);
1843
+ console.log(`${displayIndent}${colors.textDim(` ${result.lineCount} lines`)}`);
1844
+ console.log('');
1845
+ console.log(renderLines(result.preview, { maxLines: 10, indent: displayIndent + ' ' }));
1095
1846
  }
1096
- }
1097
- else if (isMcpTool) {
1098
- // Special handling for MCP tools - show friendly summary
1099
- console.log('');
1100
- // Extract server name and tool name from tool name (format: serverName__toolName)
1101
- let serverName = 'MCP';
1102
- let toolDisplayName = tool;
1103
- if (tool.includes('__')) {
1104
- const parts = tool.split('__');
1105
- serverName = parts[0];
1106
- toolDisplayName = parts.slice(1).join('__');
1847
+ else if (hasDeleteInfo) {
1848
+ // Show DeleteFile result
1849
+ console.log('');
1850
+ console.log(`${displayIndent}${colors.success(`${icons.check} Deleted: ${result.filePath}`)}`);
1107
1851
  }
1108
- // Try to extract meaningful content from MCP result
1109
- let summary = '';
1110
- if (result?.content && Array.isArray(result.content) && result.content.length > 0) {
1111
- const firstBlock = result.content[0];
1112
- if (firstBlock?.type === 'text' && firstBlock?.text) {
1113
- const text = firstBlock.text;
1114
- if (typeof text === 'string') {
1115
- // Detect HTML content
1116
- if (text.trim().startsWith('<!DOCTYPE') || text.trim().startsWith('<html')) {
1117
- summary = '[HTML content fetched]';
1118
- }
1119
- else {
1120
- // Try to parse if it's JSON
1121
- try {
1122
- const parsed = JSON.parse(text);
1123
- if (Array.isArray(parsed) && parsed.length > 0 && parsed[0]?.title) {
1124
- // Search results format
1125
- summary = `Found ${parsed.length} result(s)`;
1126
- }
1127
- else if (parsed?.message) {
1128
- summary = parsed.message;
1852
+ else if (isTaskTool) {
1853
+ // Special handling for task tool (subagent) - show friendly summary
1854
+ console.log('');
1855
+ const subagentType = params.subagent_type;
1856
+ const subagentName = params.description ||
1857
+ (params.prompt ? params.prompt.substring(0, 50).replace(/\n/g, ' ') : 'Unknown task');
1858
+ if (result?.success) {
1859
+ console.log(`${displayIndent}${colors.success(`${icons.check} ${subagentType}: Completed`)}`);
1860
+ console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
1861
+ if (result.message) {
1862
+ console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
1863
+ }
1864
+ }
1865
+ else if (result?.cancelled) {
1866
+ console.log(`${displayIndent}${colors.warning(`${icons.cross} ${subagentType}: Cancelled`)}`);
1867
+ console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
1868
+ }
1869
+ else {
1870
+ console.log(`${displayIndent}${colors.error(`${icons.cross} ${subagentType}: Failed`)}`);
1871
+ console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
1872
+ if (result?.message) {
1873
+ console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
1874
+ }
1875
+ }
1876
+ }
1877
+ else if (isMcpTool) {
1878
+ // Special handling for MCP tools - show friendly summary
1879
+ console.log('');
1880
+ // Extract server name and tool name from tool name (format: serverName__toolName)
1881
+ let serverName = 'MCP';
1882
+ let toolDisplayName = tool;
1883
+ if (tool.includes('__')) {
1884
+ const parts = tool.split('__');
1885
+ serverName = parts[0];
1886
+ toolDisplayName = parts.slice(1).join('__');
1887
+ }
1888
+ // Try to extract meaningful content from MCP result
1889
+ let summary = '';
1890
+ if (result?.content && Array.isArray(result.content) && result.content.length > 0) {
1891
+ const firstBlock = result.content[0];
1892
+ if (firstBlock?.type === 'text' && firstBlock?.text) {
1893
+ const text = firstBlock.text;
1894
+ if (typeof text === 'string') {
1895
+ // Detect HTML content
1896
+ if (text.trim().startsWith('<!DOCTYPE') || text.trim().startsWith('<html')) {
1897
+ summary = '[HTML content fetched]';
1898
+ }
1899
+ else {
1900
+ // Try to parse if it's JSON
1901
+ try {
1902
+ const parsed = JSON.parse(text);
1903
+ if (Array.isArray(parsed) && parsed.length > 0 && parsed[0]?.title) {
1904
+ // Search results format
1905
+ summary = `Found ${parsed.length} result(s)`;
1906
+ }
1907
+ else if (parsed?.message) {
1908
+ summary = parsed.message;
1909
+ }
1910
+ else if (typeof parsed === 'string') {
1911
+ summary = parsed.substring(0, 100);
1912
+ }
1129
1913
  }
1130
- else if (typeof parsed === 'string') {
1131
- summary = parsed.substring(0, 100);
1914
+ catch {
1915
+ // Not JSON, use as-is with truncation
1916
+ summary = text.substring(0, 100);
1132
1917
  }
1133
1918
  }
1134
- catch {
1135
- // Not JSON, use as-is with truncation
1136
- summary = text.substring(0, 100);
1137
- }
1138
1919
  }
1139
1920
  }
1140
1921
  }
1141
- }
1142
- else if (result?.message) {
1143
- summary = result.message;
1144
- }
1145
- if (result?.success !== false) {
1146
- console.log(`${displayIndent}${colors.success(`${icons.check} ${serverName}: Success`)}`);
1147
- console.log(`${displayIndent}${colors.textDim(` Tool: ${toolDisplayName}`)}`);
1148
- if (summary) {
1149
- console.log(`${displayIndent}${colors.textDim(` ${summary}`)}`);
1922
+ else if (result?.message) {
1923
+ summary = result.message;
1150
1924
  }
1151
- }
1152
- else {
1153
- console.log(`${displayIndent}${colors.error(`${icons.cross} ${serverName}: Failed`)}`);
1154
- console.log(`${displayIndent}${colors.textDim(` Tool: ${toolDisplayName}`)}`);
1155
- if (result?.message || result?.error) {
1156
- console.log(`${displayIndent}${colors.textDim(` ${result?.message || result?.error}`)}`);
1925
+ if (result?.success !== false) {
1926
+ console.log(`${displayIndent}${colors.success(`${icons.check} ${serverName}: Success`)}`);
1927
+ console.log(`${displayIndent}${colors.textDim(` Tool: ${toolDisplayName}`)}`);
1928
+ if (summary) {
1929
+ console.log(`${displayIndent}${colors.textDim(` ${summary}`)}`);
1930
+ }
1931
+ }
1932
+ else {
1933
+ console.log(`${displayIndent}${colors.error(`${icons.cross} ${serverName}: Failed`)}`);
1934
+ console.log(`${displayIndent}${colors.textDim(` Tool: ${toolDisplayName}`)}`);
1935
+ if (result?.message || result?.error) {
1936
+ console.log(`${displayIndent}${colors.textDim(` ${result?.message || result?.error}`)}`);
1937
+ }
1157
1938
  }
1158
1939
  }
1159
- }
1160
- else if (tool === 'InvokeSkill') {
1161
- // Special handling for InvokeSkill - show friendly summary
1162
- console.log('');
1163
- const skillName = params?.skillId || 'Unknown skill';
1164
- const taskDesc = params?.taskDescription || '';
1165
- if (result?.success) {
1166
- console.log(`${displayIndent}${colors.success(`${icons.check} Skill: Completed`)}`);
1167
- console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
1168
- if (taskDesc) {
1169
- const truncatedTask = taskDesc.length > 60 ? taskDesc.substring(0, 60) + '...' : taskDesc;
1170
- console.log(`${displayIndent}${colors.textDim(` Task: ${truncatedTask}`)}`);
1940
+ else if (tool === 'InvokeSkill') {
1941
+ // Special handling for InvokeSkill - show friendly summary
1942
+ console.log('');
1943
+ const skillName = params?.skillId || 'Unknown skill';
1944
+ const taskDesc = params?.taskDescription || '';
1945
+ if (result?.success) {
1946
+ console.log(`${displayIndent}${colors.success(`${icons.check} Skill: Completed`)}`);
1947
+ console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
1948
+ if (taskDesc) {
1949
+ const truncatedTask = taskDesc.length > 60 ? taskDesc.substring(0, 60) + '...' : taskDesc;
1950
+ console.log(`${displayIndent}${colors.textDim(` Task: ${truncatedTask}`)}`);
1951
+ }
1171
1952
  }
1953
+ else {
1954
+ console.log(`${displayIndent}${colors.error(`${icons.cross} Skill: Failed`)}`);
1955
+ console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
1956
+ if (result?.message) {
1957
+ console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
1958
+ }
1959
+ }
1960
+ }
1961
+ else if (showToolDetails) {
1962
+ console.log('');
1963
+ console.log(`${displayIndent}${colors.success(`${icons.check} Tool Result:`)}`);
1964
+ console.log(`${displayIndent}${colors.textDim(JSON.stringify(result, null, 2))}`);
1965
+ }
1966
+ else if (result && result.success === false) {
1967
+ // GUI task or other tool failed
1968
+ console.log(`${displayIndent}${colors.error(`${icons.cross} ${result.message || 'Failed'}`)}`);
1969
+ }
1970
+ else if (result) {
1971
+ // Show brief preview by default (consistent with subagent behavior)
1972
+ const resultPreview = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
1973
+ const truncatedPreview = resultPreview.length > 200 ? resultPreview.substring(0, 200) + '...' : resultPreview;
1974
+ // Indent the preview
1975
+ const indentedPreview = truncatedPreview
1976
+ .split('\n')
1977
+ .map((line) => `${displayIndent} ${line}`)
1978
+ .join('\n');
1979
+ console.log(`${indentedPreview}`);
1172
1980
  }
1173
1981
  else {
1174
- console.log(`${displayIndent}${colors.error(`${icons.cross} Skill: Failed`)}`);
1175
- console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
1176
- if (result?.message) {
1177
- console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
1178
- }
1982
+ console.log(`${displayIndent}${colors.textDim('(no result)')}`);
1179
1983
  }
1180
1984
  }
1181
- else if (showToolDetails) {
1182
- console.log('');
1183
- console.log(`${displayIndent}${colors.success(`${icons.check} Tool Result:`)}`);
1184
- console.log(`${displayIndent}${colors.textDim(JSON.stringify(result, null, 2))}`);
1185
- }
1186
- else if (result && result.success === false) {
1187
- // GUI task or other tool failed
1188
- console.log(`${displayIndent}${colors.error(`${icons.cross} ${result.message || 'Failed'}`)}`);
1189
- }
1190
- else if (result) {
1191
- // Show brief preview by default (consistent with subagent behavior)
1192
- const resultPreview = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
1193
- const truncatedPreview = resultPreview.length > 200 ? resultPreview.substring(0, 200) + '...' : resultPreview;
1194
- // Indent the preview
1195
- const indentedPreview = truncatedPreview.split('\n').map(line => `${displayIndent} ${line}`).join('\n');
1196
- console.log(`${indentedPreview}`);
1197
- }
1198
- else {
1199
- console.log(`${displayIndent}${colors.textDim('(no result)')}`);
1200
- }
1201
1985
  const toolCallRecord = {
1202
1986
  tool,
1203
1987
  params,
1204
1988
  result,
1205
- timestamp: Date.now()
1989
+ timestamp: Date.now(),
1206
1990
  };
1207
- this.toolCalls.push(toolCallRecord);
1991
+ this.tool_calls.push(toolCallRecord);
1208
1992
  // Record tool output to session manager
1209
1993
  await this.sessionManager.addOutput({
1210
1994
  role: 'tool',
@@ -1212,77 +1996,104 @@ export class InteractiveSession {
1212
1996
  toolName: tool,
1213
1997
  toolParams: params,
1214
1998
  toolResult: result,
1215
- timestamp: Date.now()
1999
+ timestamp: Date.now(),
1216
2000
  });
1217
- // 统一消息格式,包含工具名称和参数
2001
+ // Unified message format with tool name and params
2002
+ // Format: OpenAI-compatible tool result with plain text content
1218
2003
  this.conversation.push({
1219
2004
  role: 'tool',
1220
- content: JSON.stringify({
1221
- name: tool,
1222
- parameters: params,
1223
- result: result
1224
- }),
2005
+ content: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
1225
2006
  tool_call_id: toolCall.id,
1226
- timestamp: Date.now()
2007
+ timestamp: Date.now(),
1227
2008
  });
1228
2009
  }
1229
2010
  }
1230
2011
  // Logic: Only skip returning results to main agent when user explicitly cancelled (ESC)
1231
2012
  // For all other cases (success, failure, errors), always return results for further processing
1232
- const guiSubagentCancelled = preparedToolCalls.some(tc => tc.name === 'task' && tc.params?.subagent_type === 'gui-subagent' && results.some(r => r.tool === 'task' && r.result?.cancelled === true));
2013
+ const guiSubagentCancelled = preparedToolCalls.some((tc) => tc.name === 'task' &&
2014
+ tc.params?.subagent_type === 'gui-subagent' &&
2015
+ results.some((r) => r.tool === 'task' && r.result?.cancelled === true));
1233
2016
  // If GUI agent was cancelled by user, don't continue generating response
1234
2017
  // This avoids wasting API calls and tokens on cancelled tasks
1235
2018
  if (guiSubagentCancelled) {
1236
- console.log('');
1237
- console.log(`${indent}${colors.textMuted('GUI task cancelled by user')}`);
1238
2019
  this._isOperationInProgress = false;
1239
2020
  return;
1240
2021
  }
1241
- // Handle errors and completion based on whether onComplete callback is provided
1242
- if (hasError) {
1243
- this._isOperationInProgress = false;
1244
- // 不再抛出异常,而是将错误结果返回给 AI,让 AI 决定如何处理
1245
- // 这样可以避免工具错误导致程序退出
1246
- }
1247
- // Continue based on mode - 统一处理,无论是否有错误
2022
+ // Continue based on mode - unified handling for both success and error cases
1248
2023
  if (onComplete) {
2024
+ await this.checkAndCompressContext();
1249
2025
  // Remote mode: use provided callback
1250
2026
  await onComplete();
1251
2027
  }
1252
2028
  else {
2029
+ await this.checkAndCompressContext();
1253
2030
  // Local mode: default behavior - continue with generateResponse
1254
2031
  await this.generateResponse();
1255
2032
  }
1256
2033
  }
2034
+ /**
2035
+ * Clean up incomplete tool calls from conversation after cancellation
2036
+ * This removes assistant messages with tool_calls that don't have corresponding tool_results
2037
+ */
2038
+ async cleanupIncompleteToolCalls() {
2039
+ // 从后往前找到包含 tool_calls 的 assistant 消息
2040
+ for (let i = this.conversation.length - 1; i >= 0; i--) {
2041
+ const msg = this.conversation[i];
2042
+ if (msg.role === 'assistant' && msg.tool_calls?.length) {
2043
+ // 收集所有 tool_call IDs
2044
+ const allToolCallIds = new Set(msg.tool_calls.map((tc) => tc.id));
2045
+ // 找出哪些 tool_call IDs 已经有对应的 tool_result
2046
+ const completedToolCallIds = new Set();
2047
+ for (let k = i + 1; k < this.conversation.length; k++) {
2048
+ const resultMsg = this.conversation[k];
2049
+ if (resultMsg.role === 'tool' && resultMsg.tool_call_id) {
2050
+ completedToolCallIds.add(resultMsg.tool_call_id);
2051
+ }
2052
+ else if (resultMsg.role === 'user' || resultMsg.role === 'assistant') {
2053
+ break; // 遇到下一个角色消息就停止
2054
+ }
2055
+ }
2056
+ // 找出未完成的 tool_call IDs
2057
+ const incompleteToolCallIds = [...allToolCallIds].filter(id => !completedToolCallIds.has(id));
2058
+ // 如果所有 tool_call 都已完成,不需要清理
2059
+ if (incompleteToolCallIds.length === 0) {
2060
+ break;
2061
+ }
2062
+ // 只移除未完成的 tool_call,不移除已完成的 tool_result
2063
+ msg.tool_calls = msg.tool_calls.filter((tc) => !incompleteToolCallIds.includes(tc.id));
2064
+ break;
2065
+ }
2066
+ }
2067
+ }
1257
2068
  /**
1258
2069
  * Get user-friendly description for tool
1259
2070
  */
1260
2071
  getToolDescription(toolName, params) {
1261
2072
  const descriptions = {
1262
- 'Read': (p) => `Read file: ${this.truncatePath(p.filePath)}`,
1263
- 'Write': (p) => `Write file: ${this.truncatePath(p.filePath)}`,
1264
- 'Grep': (p) => `Search text: "${p.pattern}"`,
1265
- 'Bash': (p) => `Execute command: ${this.truncateCommand(p.command)}`,
1266
- 'ListDirectory': (p) => `List directory: ${this.truncatePath(p.path || '.')}`,
1267
- 'SearchFiles': (p) => `Search files: ${p.pattern}`,
1268
- 'DeleteFile': (p) => `Delete file: ${this.truncatePath(p.filePath)}`,
1269
- 'CreateDirectory': (p) => `Create directory: ${this.truncatePath(p.dirPath)}`,
1270
- 'Edit': (p) => `Edit text: ${this.truncatePath(p.file_path)}`,
1271
- 'web_search': (p) => `Web search: "${p.query}"`,
1272
- 'todo_write': () => `Update todo list`,
1273
- 'todo_read': () => `Read todo list`,
1274
- 'task': (p) => `Launch subtask: ${p.description}`,
1275
- 'ReadBashOutput': (p) => `Read task output: ${p.task_id}`,
1276
- 'web_fetch': () => `Fetch web content`,
1277
- 'ask_user_question': () => `Ask user`,
1278
- 'save_memory': () => `Save memory`,
1279
- 'exit_plan_mode': () => `Complete plan`,
1280
- 'xml_escape': (p) => `XML escape: ${this.truncatePath(p.file_path)}`,
1281
- 'image_read': (p) => `Read image: ${this.truncatePath(p.image_input)}`,
2073
+ Read: (p) => `Read file: ${this.truncatePath(p.filePath)}`,
2074
+ Write: (p) => `Write file: ${this.truncatePath(p.filePath)}`,
2075
+ Grep: (p) => `Search text: "${p.pattern}"`,
2076
+ Bash: (p) => `Execute command: ${this.truncateCommand(p.command)}`,
2077
+ ListDirectory: (p) => `List directory: ${this.truncatePath(p.path || '.')}`,
2078
+ SearchFiles: (p) => `Search files: ${p.pattern}`,
2079
+ DeleteFile: (p) => `Delete file: ${this.truncatePath(p.filePath)}`,
2080
+ CreateDirectory: (p) => `Create directory: ${this.truncatePath(p.dirPath)}`,
2081
+ Edit: (p) => `Edit text: ${this.truncatePath(p.file_path)}`,
2082
+ web_search: (p) => `Web search: "${p.query}"`,
2083
+ todo_write: () => `Update todo list`,
2084
+ todo_read: () => `Read todo list`,
2085
+ task: (p) => `Launch subtask: ${p.description}`,
2086
+ ReadBashOutput: (p) => `Read task output: ${p.task_id}`,
2087
+ web_fetch: () => `Fetch web content`,
2088
+ ask_user_question: () => `Ask user`,
2089
+ save_memory: () => `Save memory`,
2090
+ exit_plan_mode: () => `Complete plan`,
2091
+ xml_escape: (p) => `XML escape: ${this.truncatePath(p.file_path)}`,
2092
+ image_read: (p) => `Read image: ${this.truncatePath(p.image_input)}`,
1282
2093
  // 'Skill': (p) => `Execute skill: ${p.skill}`,
1283
2094
  // 'ListSkills': () => `List available skills`,
1284
2095
  // 'GetSkillDetails': (p) => `Get skill details: ${p.skill}`,
1285
- 'InvokeSkill': (p) => `Invoke skill: ${p.skillId} - ${this.truncatePath(p.taskDescription || '', 40)}`
2096
+ InvokeSkill: (p) => `Invoke skill: ${p.skillId} - ${this.truncatePath(p.taskDescription || '', 40)}`,
1286
2097
  };
1287
2098
  const getDescription = descriptions[toolName];
1288
2099
  return getDescription ? getDescription(params) : `Execute tool: ${toolName}`;
@@ -1315,10 +2126,10 @@ export class InteractiveSession {
1315
2126
  return `${indent}${colors.textMuted('No tasks')}`;
1316
2127
  }
1317
2128
  const statusConfig = {
1318
- 'pending': { icon: icons.circle, color: colors.textMuted, label: 'Pending' },
1319
- 'in_progress': { icon: icons.loading, color: colors.warning, label: 'In Progress' },
1320
- 'completed': { icon: icons.success, color: colors.success, label: 'Completed' },
1321
- 'failed': { icon: icons.error, color: colors.error, label: 'Failed' }
2129
+ pending: { icon: icons.circle, color: colors.textMuted, label: 'Pending' },
2130
+ in_progress: { icon: icons.loading, color: colors.warning, label: 'In Progress' },
2131
+ completed: { icon: icons.success, color: colors.success, label: 'Completed' },
2132
+ failed: { icon: icons.error, color: colors.error, label: 'Failed' },
1322
2133
  };
1323
2134
  const lines = [];
1324
2135
  for (const todo of todos) {
@@ -1431,7 +2242,155 @@ export class InteractiveSession {
1431
2242
  return this.currentTaskId;
1432
2243
  }
1433
2244
  }
2245
+ /**
2246
+ * Clean up stale temporary workspaces from previous sessions.
2247
+ * Called at startup to ensure no leftover temp files accumulate.
2248
+ */
2249
+ async function cleanupStaleWorkspaces() {
2250
+ try {
2251
+ const configManager = getConfigManager();
2252
+ const workspacePath = configManager.getWorkspacePath();
2253
+ // Use default workspace path if workspacePath is empty or undefined
2254
+ const effectiveWorkspacePath = (workspacePath && workspacePath.trim())
2255
+ ? workspacePath
2256
+ : os.homedir();
2257
+ const baseWorkspaceDir = path.join(effectiveWorkspacePath, '.xagent', 'workspace');
2258
+ // First, verify the workspace directory exists before attempting cleanup
2259
+ try {
2260
+ const stats = await fsPromises.stat(baseWorkspaceDir);
2261
+ if (!stats.isDirectory()) {
2262
+ return; // Not a directory, skip cleanup
2263
+ }
2264
+ }
2265
+ catch {
2266
+ // Directory doesn't exist - nothing to clean
2267
+ return;
2268
+ }
2269
+ try {
2270
+ const entries = await fsPromises.readdir(baseWorkspaceDir, { withFileTypes: true });
2271
+ let cleanedCount = 0;
2272
+ for (const entry of entries) {
2273
+ if (entry.isDirectory()) {
2274
+ const fullPath = path.join(baseWorkspaceDir, entry.name);
2275
+ try {
2276
+ await fsPromises.rm(fullPath, { recursive: true, force: true });
2277
+ cleanedCount++;
2278
+ }
2279
+ catch {
2280
+ // Skip directories that can't be removed (in use or permission issues)
2281
+ }
2282
+ }
2283
+ }
2284
+ if (cleanedCount > 0) {
2285
+ console.log(colors.textDim(`🧹 Cleaned up ${cleanedCount} stale workspace(s)`));
2286
+ }
2287
+ }
2288
+ catch {
2289
+ // Can't read directory - skip cleanup
2290
+ }
2291
+ }
2292
+ catch {
2293
+ // Config not available - skip cleanup
2294
+ }
2295
+ }
2296
+ /**
2297
+ * Initialize built-in skills on first run.
2298
+ * Checks if user skills directory is empty or doesn't exist,
2299
+ * then copies all built-in skills including the protected find-skills.
2300
+ * @returns Number of skills initialized, or 0 if no initialization was needed.
2301
+ */
2302
+ async function initializeSkillsOnDemand() {
2303
+ const __filename = fileURLToPath(import.meta.url);
2304
+ const __dirname = path.dirname(__filename);
2305
+ // Get user skills directory (respects OS-specific paths)
2306
+ const configManager = getConfigManager();
2307
+ const userSkillsPath = configManager.getUserSkillsPath() || path.join(os.homedir(), '.xagent', 'skills');
2308
+ // Check if user skills directory exists and has skills
2309
+ let hasSkills = false;
2310
+ try {
2311
+ const entries = await fsPromises.readdir(userSkillsPath, { withFileTypes: true });
2312
+ hasSkills = entries.some(e => e.isDirectory());
2313
+ }
2314
+ catch {
2315
+ hasSkills = false;
2316
+ }
2317
+ // If skills already exist, skip initialization
2318
+ if (hasSkills) {
2319
+ return 0;
2320
+ }
2321
+ // Ensure user skills directory exists
2322
+ await fsPromises.mkdir(userSkillsPath, { recursive: true });
2323
+ // Define skill source directories
2324
+ const builtinSkillsDir = path.join(__dirname, '..', 'skills', 'skills');
2325
+ const findSkillsDir = path.join(__dirname, '..', 'find-skills');
2326
+ const skillsToInstall = [];
2327
+ // Add find-skills from root directory
2328
+ if (fs.existsSync(findSkillsDir) && fs.existsSync(path.join(findSkillsDir, 'SKILL.md'))) {
2329
+ skillsToInstall.push({ source: findSkillsDir, name: 'find-skills' });
2330
+ }
2331
+ // Add skills from skills/skills directory
2332
+ if (fs.existsSync(builtinSkillsDir)) {
2333
+ const entries = fs.readdirSync(builtinSkillsDir, { withFileTypes: true });
2334
+ for (const entry of entries) {
2335
+ if (entry.isDirectory()) {
2336
+ const skillPath = path.join(builtinSkillsDir, entry.name);
2337
+ if (fs.existsSync(path.join(skillPath, 'SKILL.md'))) {
2338
+ skillsToInstall.push({ source: skillPath, name: entry.name });
2339
+ }
2340
+ }
2341
+ }
2342
+ }
2343
+ if (skillsToInstall.length === 0) {
2344
+ return 0;
2345
+ }
2346
+ // Create user skills directory (already done above, but ensure it exists)
2347
+ await fsPromises.mkdir(userSkillsPath, { recursive: true });
2348
+ // Copy all skills
2349
+ for (const { source, name } of skillsToInstall) {
2350
+ const destPath = path.join(userSkillsPath, name);
2351
+ if (!fs.existsSync(destPath)) {
2352
+ await copyDirectoryRecursiveAsync(source, destPath);
2353
+ }
2354
+ }
2355
+ return skillsToInstall.length;
2356
+ }
2357
+ // Synchronous version (kept for backwards compatibility)
2358
+ function _copyDirectoryRecursive(src, dest) {
2359
+ if (!fs.existsSync(dest)) {
2360
+ fs.mkdirSync(dest, { recursive: true });
2361
+ }
2362
+ const entries = fs.readdirSync(src, { withFileTypes: true });
2363
+ for (const entry of entries) {
2364
+ const srcPath = path.join(src, entry.name);
2365
+ const destPath = path.join(dest, entry.name);
2366
+ if (entry.isDirectory()) {
2367
+ _copyDirectoryRecursive(srcPath, destPath);
2368
+ }
2369
+ else if (entry.isFile()) {
2370
+ fs.copyFileSync(srcPath, destPath);
2371
+ }
2372
+ }
2373
+ }
2374
+ // Asynchronous version for concurrent-safe initialization
2375
+ async function copyDirectoryRecursiveAsync(src, dest) {
2376
+ await fsPromises.mkdir(dest, { recursive: true });
2377
+ const entries = await fsPromises.readdir(src, { withFileTypes: true });
2378
+ for (const entry of entries) {
2379
+ const srcPath = path.join(src, entry.name);
2380
+ const destPath = path.join(dest, entry.name);
2381
+ if (entry.isDirectory()) {
2382
+ await copyDirectoryRecursiveAsync(srcPath, destPath);
2383
+ }
2384
+ else if (entry.isFile()) {
2385
+ await fsPromises.copyFile(srcPath, destPath);
2386
+ }
2387
+ }
2388
+ }
1434
2389
  export async function startInteractiveSession() {
2390
+ // Clean up any leftover temp workspaces from previous sessions
2391
+ await cleanupStaleWorkspaces();
2392
+ // Initialize built-in skills on first run (silent, returns count)
2393
+ const initializedCount = await initializeSkillsOnDemand();
1435
2394
  const session = new InteractiveSession();
1436
2395
  // Flag to control shutdown
1437
2396
  session._isShuttingDown = false;
@@ -1467,6 +2426,15 @@ export async function startInteractiveSession() {
1467
2426
  // Force exit
1468
2427
  process.exit(0);
1469
2428
  });
2429
+ await session.start(initializedCount);
2430
+ // Check for updates on startup
2431
+ try {
2432
+ const { checkUpdatesOnStartup } = await import('./update.js');
2433
+ await checkUpdatesOnStartup();
2434
+ }
2435
+ catch {
2436
+ // Silently ignore update check failures
2437
+ }
1470
2438
  await session.start();
1471
2439
  }
1472
2440
  // Singleton session instance for access from other modules