@xagent-ai/cli 1.2.2 → 1.3.1

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