@wahack/pi-coding-agent 15.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1152) hide show
  1. package/CHANGELOG.md +10031 -0
  2. package/README.md +36 -0
  3. package/examples/README.md +21 -0
  4. package/examples/custom-tools/README.md +104 -0
  5. package/examples/custom-tools/hello/index.ts +20 -0
  6. package/examples/extensions/README.md +142 -0
  7. package/examples/extensions/api-demo.ts +79 -0
  8. package/examples/extensions/chalk-logger.ts +25 -0
  9. package/examples/extensions/hello.ts +31 -0
  10. package/examples/extensions/pirate.ts +43 -0
  11. package/examples/extensions/plan-mode.ts +549 -0
  12. package/examples/extensions/reload-runtime.ts +38 -0
  13. package/examples/extensions/thinking-note.ts +13 -0
  14. package/examples/extensions/tools.ts +145 -0
  15. package/examples/extensions/with-deps/index.ts +36 -0
  16. package/examples/extensions/with-deps/package-lock.json +31 -0
  17. package/examples/extensions/with-deps/package.json +17 -0
  18. package/examples/hooks/README.md +56 -0
  19. package/examples/hooks/auto-commit-on-exit.ts +48 -0
  20. package/examples/hooks/confirm-destructive.ts +58 -0
  21. package/examples/hooks/custom-compaction.ts +115 -0
  22. package/examples/hooks/dirty-repo-guard.ts +51 -0
  23. package/examples/hooks/file-trigger.ts +40 -0
  24. package/examples/hooks/git-checkpoint.ts +52 -0
  25. package/examples/hooks/handoff.ts +149 -0
  26. package/examples/hooks/permission-gate.ts +33 -0
  27. package/examples/hooks/protected-paths.ts +29 -0
  28. package/examples/hooks/qna.ts +118 -0
  29. package/examples/hooks/status-line.ts +39 -0
  30. package/examples/sdk/01-minimal.ts +21 -0
  31. package/examples/sdk/02-custom-model.ts +49 -0
  32. package/examples/sdk/03-custom-prompt.ts +46 -0
  33. package/examples/sdk/04-skills.ts +43 -0
  34. package/examples/sdk/06-extensions.ts +82 -0
  35. package/examples/sdk/06-hooks.ts +61 -0
  36. package/examples/sdk/07-context-files.ts +35 -0
  37. package/examples/sdk/08-prompt-templates.ts +41 -0
  38. package/examples/sdk/08-slash-commands.ts +46 -0
  39. package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
  40. package/examples/sdk/11-sessions.ts +47 -0
  41. package/examples/sdk/12-redis-sessions.ts +54 -0
  42. package/examples/sdk/13-sql-sessions.ts +61 -0
  43. package/examples/sdk/README.md +172 -0
  44. package/package.json +554 -0
  45. package/scripts/build-binary.ts +100 -0
  46. package/scripts/bundle-dist.ts +90 -0
  47. package/scripts/format-prompts.ts +68 -0
  48. package/scripts/generate-docs-index.ts +40 -0
  49. package/scripts/generate-template.ts +33 -0
  50. package/scripts/omp +42 -0
  51. package/scripts/omp.ts +19 -0
  52. package/src/async/index.ts +1 -0
  53. package/src/async/job-manager.ts +625 -0
  54. package/src/auto-thinking/classifier.ts +185 -0
  55. package/src/autoresearch/command-resume.md +14 -0
  56. package/src/autoresearch/dashboard.ts +436 -0
  57. package/src/autoresearch/git.ts +319 -0
  58. package/src/autoresearch/helpers.ts +218 -0
  59. package/src/autoresearch/index.ts +536 -0
  60. package/src/autoresearch/prompt-setup.md +43 -0
  61. package/src/autoresearch/prompt.md +103 -0
  62. package/src/autoresearch/resume-message.md +10 -0
  63. package/src/autoresearch/state.ts +273 -0
  64. package/src/autoresearch/storage.ts +699 -0
  65. package/src/autoresearch/tools/init-experiment.ts +272 -0
  66. package/src/autoresearch/tools/log-experiment.ts +524 -0
  67. package/src/autoresearch/tools/run-experiment.ts +407 -0
  68. package/src/autoresearch/tools/update-notes.ts +109 -0
  69. package/src/autoresearch/types.ts +168 -0
  70. package/src/bun-imports.d.ts +28 -0
  71. package/src/capability/context-file.ts +44 -0
  72. package/src/capability/extension-module.ts +34 -0
  73. package/src/capability/extension.ts +47 -0
  74. package/src/capability/fs.ts +117 -0
  75. package/src/capability/hook.ts +40 -0
  76. package/src/capability/index.ts +436 -0
  77. package/src/capability/instruction.ts +37 -0
  78. package/src/capability/mcp.ts +74 -0
  79. package/src/capability/prompt.ts +35 -0
  80. package/src/capability/rule-buckets.ts +66 -0
  81. package/src/capability/rule.ts +261 -0
  82. package/src/capability/settings.ts +34 -0
  83. package/src/capability/skill.ts +63 -0
  84. package/src/capability/slash-command.ts +40 -0
  85. package/src/capability/ssh.ts +41 -0
  86. package/src/capability/system-prompt.ts +34 -0
  87. package/src/capability/tool.ts +38 -0
  88. package/src/capability/types.ts +168 -0
  89. package/src/cli/agents-cli.ts +138 -0
  90. package/src/cli/args.ts +340 -0
  91. package/src/cli/auth-broker-cli.ts +895 -0
  92. package/src/cli/auth-gateway-cli.ts +611 -0
  93. package/src/cli/classify-install-target.ts +76 -0
  94. package/src/cli/claude-trace-cli.ts +795 -0
  95. package/src/cli/commands/init-xdg.ts +27 -0
  96. package/src/cli/completion-gen.ts +550 -0
  97. package/src/cli/config-cli.ts +418 -0
  98. package/src/cli/dry-balance-cli.ts +856 -0
  99. package/src/cli/extension-flags.ts +48 -0
  100. package/src/cli/file-processor.ts +133 -0
  101. package/src/cli/gallery-cli.ts +230 -0
  102. package/src/cli/gallery-fixtures/agentic.ts +407 -0
  103. package/src/cli/gallery-fixtures/codeintel.ts +187 -0
  104. package/src/cli/gallery-fixtures/edit.ts +194 -0
  105. package/src/cli/gallery-fixtures/fs.ts +220 -0
  106. package/src/cli/gallery-fixtures/index.ts +40 -0
  107. package/src/cli/gallery-fixtures/interaction.ts +49 -0
  108. package/src/cli/gallery-fixtures/memory.ts +81 -0
  109. package/src/cli/gallery-fixtures/misc.ts +250 -0
  110. package/src/cli/gallery-fixtures/search.ts +213 -0
  111. package/src/cli/gallery-fixtures/shell.ts +167 -0
  112. package/src/cli/gallery-fixtures/types.ts +57 -0
  113. package/src/cli/gallery-fixtures/web.ts +158 -0
  114. package/src/cli/gallery-screenshot.ts +279 -0
  115. package/src/cli/grep-cli.ts +160 -0
  116. package/src/cli/grievances-cli.ts +256 -0
  117. package/src/cli/initial-message.ts +58 -0
  118. package/src/cli/list-models.ts +194 -0
  119. package/src/cli/plugin-cli.ts +996 -0
  120. package/src/cli/read-cli.ts +57 -0
  121. package/src/cli/session-picker.ts +79 -0
  122. package/src/cli/setup-cli.ts +231 -0
  123. package/src/cli/shell-cli.ts +176 -0
  124. package/src/cli/ssh-cli.ts +179 -0
  125. package/src/cli/startup-cwd.ts +68 -0
  126. package/src/cli/stats-cli.ts +238 -0
  127. package/src/cli/tiny-models-cli.ts +127 -0
  128. package/src/cli/update-cli.ts +611 -0
  129. package/src/cli/usage-cli.ts +603 -0
  130. package/src/cli/web-search-cli.ts +132 -0
  131. package/src/cli/worktree-cli.ts +291 -0
  132. package/src/cli-commands.ts +79 -0
  133. package/src/cli.ts +200 -0
  134. package/src/commands/acp.ts +24 -0
  135. package/src/commands/agents.ts +57 -0
  136. package/src/commands/auth-broker.ts +99 -0
  137. package/src/commands/auth-gateway.ts +69 -0
  138. package/src/commands/commit.ts +46 -0
  139. package/src/commands/complete.ts +66 -0
  140. package/src/commands/completions.ts +60 -0
  141. package/src/commands/config.ts +51 -0
  142. package/src/commands/dry-balance.ts +43 -0
  143. package/src/commands/gallery.ts +52 -0
  144. package/src/commands/grep.ts +48 -0
  145. package/src/commands/grievances.ts +51 -0
  146. package/src/commands/install.ts +107 -0
  147. package/src/commands/launch.ts +169 -0
  148. package/src/commands/plugin.ts +78 -0
  149. package/src/commands/read.ts +38 -0
  150. package/src/commands/setup.ts +67 -0
  151. package/src/commands/shell.ts +29 -0
  152. package/src/commands/ssh.ts +60 -0
  153. package/src/commands/stats.ts +29 -0
  154. package/src/commands/tiny-models.ts +36 -0
  155. package/src/commands/update.ts +21 -0
  156. package/src/commands/usage.ts +35 -0
  157. package/src/commands/web-search.ts +42 -0
  158. package/src/commands/worktree.ts +56 -0
  159. package/src/commit/agentic/agent.ts +317 -0
  160. package/src/commit/agentic/fallback.ts +96 -0
  161. package/src/commit/agentic/index.ts +355 -0
  162. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  163. package/src/commit/agentic/prompts/session-user.md +25 -0
  164. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  165. package/src/commit/agentic/prompts/system.md +38 -0
  166. package/src/commit/agentic/state.ts +60 -0
  167. package/src/commit/agentic/tools/analyze-file.ts +146 -0
  168. package/src/commit/agentic/tools/git-file-diff.ts +191 -0
  169. package/src/commit/agentic/tools/git-hunk.ts +50 -0
  170. package/src/commit/agentic/tools/git-overview.ts +81 -0
  171. package/src/commit/agentic/tools/index.ts +54 -0
  172. package/src/commit/agentic/tools/propose-changelog.ts +144 -0
  173. package/src/commit/agentic/tools/propose-commit.ts +109 -0
  174. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  175. package/src/commit/agentic/tools/schemas.ts +23 -0
  176. package/src/commit/agentic/tools/split-commit.ts +245 -0
  177. package/src/commit/agentic/topo-sort.ts +44 -0
  178. package/src/commit/agentic/trivial.ts +51 -0
  179. package/src/commit/agentic/validation.ts +183 -0
  180. package/src/commit/analysis/conventional.ts +64 -0
  181. package/src/commit/analysis/index.ts +4 -0
  182. package/src/commit/analysis/scope.ts +242 -0
  183. package/src/commit/analysis/summary.ts +105 -0
  184. package/src/commit/analysis/validation.ts +66 -0
  185. package/src/commit/changelog/detect.ts +40 -0
  186. package/src/commit/changelog/generate.ts +97 -0
  187. package/src/commit/changelog/index.ts +234 -0
  188. package/src/commit/changelog/parse.ts +44 -0
  189. package/src/commit/cli.ts +85 -0
  190. package/src/commit/git/diff.ts +148 -0
  191. package/src/commit/index.ts +5 -0
  192. package/src/commit/map-reduce/index.ts +69 -0
  193. package/src/commit/map-reduce/map-phase.ts +193 -0
  194. package/src/commit/map-reduce/reduce-phase.ts +49 -0
  195. package/src/commit/map-reduce/utils.ts +9 -0
  196. package/src/commit/message.ts +11 -0
  197. package/src/commit/model-selection.ts +92 -0
  198. package/src/commit/pipeline.ts +243 -0
  199. package/src/commit/prompts/analysis-system.md +148 -0
  200. package/src/commit/prompts/analysis-user.md +38 -0
  201. package/src/commit/prompts/changelog-system.md +50 -0
  202. package/src/commit/prompts/changelog-user.md +18 -0
  203. package/src/commit/prompts/file-observer-system.md +24 -0
  204. package/src/commit/prompts/file-observer-user.md +8 -0
  205. package/src/commit/prompts/reduce-system.md +50 -0
  206. package/src/commit/prompts/reduce-user.md +17 -0
  207. package/src/commit/prompts/summary-retry.md +3 -0
  208. package/src/commit/prompts/summary-system.md +38 -0
  209. package/src/commit/prompts/summary-user.md +13 -0
  210. package/src/commit/prompts/types-description.md +2 -0
  211. package/src/commit/shared-llm.ts +77 -0
  212. package/src/commit/types.ts +118 -0
  213. package/src/commit/utils/exclusions.ts +42 -0
  214. package/src/commit/utils.ts +58 -0
  215. package/src/config/api-key-resolver.ts +60 -0
  216. package/src/config/append-only-context-mode.ts +31 -0
  217. package/src/config/config-file.ts +317 -0
  218. package/src/config/file-lock.ts +164 -0
  219. package/src/config/keybindings.ts +628 -0
  220. package/src/config/mcp-schema.json +230 -0
  221. package/src/config/model-discovery.ts +554 -0
  222. package/src/config/model-registry.ts +2090 -0
  223. package/src/config/model-resolver.ts +1502 -0
  224. package/src/config/model-roles.ts +74 -0
  225. package/src/config/models-config-schema.ts +226 -0
  226. package/src/config/models-config.ts +129 -0
  227. package/src/config/prompt-templates.ts +185 -0
  228. package/src/config/resolve-config-value.ts +94 -0
  229. package/src/config/settings-schema.ts +3530 -0
  230. package/src/config/settings.ts +1178 -0
  231. package/src/config.ts +242 -0
  232. package/src/cursor.ts +340 -0
  233. package/src/dap/client.ts +760 -0
  234. package/src/dap/config.ts +189 -0
  235. package/src/dap/defaults.json +212 -0
  236. package/src/dap/index.ts +4 -0
  237. package/src/dap/session.ts +1441 -0
  238. package/src/dap/types.ts +610 -0
  239. package/src/debug/index.ts +515 -0
  240. package/src/debug/log-formatting.ts +58 -0
  241. package/src/debug/log-viewer.ts +908 -0
  242. package/src/debug/profiler.ts +162 -0
  243. package/src/debug/protocol-probe.ts +267 -0
  244. package/src/debug/raw-sse-buffer.ts +273 -0
  245. package/src/debug/raw-sse.ts +292 -0
  246. package/src/debug/report-bundle.ts +374 -0
  247. package/src/debug/system-info.ts +111 -0
  248. package/src/debug/terminal-info.ts +124 -0
  249. package/src/discovery/agents-md.ts +67 -0
  250. package/src/discovery/agents.ts +230 -0
  251. package/src/discovery/at-imports.ts +273 -0
  252. package/src/discovery/builtin-defaults.ts +39 -0
  253. package/src/discovery/builtin-rules/index.ts +54 -0
  254. package/src/discovery/builtin-rules/rs-box-leak.md +48 -0
  255. package/src/discovery/builtin-rules/rs-future-prelude.md +23 -0
  256. package/src/discovery/builtin-rules/rs-lazylock.md +51 -0
  257. package/src/discovery/builtin-rules/rs-match-ergonomics.md +67 -0
  258. package/src/discovery/builtin-rules/rs-parking-lot.md +44 -0
  259. package/src/discovery/builtin-rules/rs-result-type.md +19 -0
  260. package/src/discovery/builtin-rules/ts-bare-catch.md +38 -0
  261. package/src/discovery/builtin-rules/ts-import-type.md +42 -0
  262. package/src/discovery/builtin-rules/ts-no-any.md +56 -0
  263. package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +44 -0
  264. package/src/discovery/builtin-rules/ts-no-dynamic-import.md +39 -0
  265. package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
  266. package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
  267. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +51 -0
  268. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +65 -0
  269. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
  270. package/src/discovery/builtin-rules/ts-set-map.md +28 -0
  271. package/src/discovery/builtin.ts +906 -0
  272. package/src/discovery/claude-plugins.ts +386 -0
  273. package/src/discovery/claude.ts +584 -0
  274. package/src/discovery/cline.ts +83 -0
  275. package/src/discovery/codex.ts +522 -0
  276. package/src/discovery/cursor.ts +220 -0
  277. package/src/discovery/gemini.ts +383 -0
  278. package/src/discovery/github.ts +154 -0
  279. package/src/discovery/helpers.ts +1016 -0
  280. package/src/discovery/index.ts +81 -0
  281. package/src/discovery/mcp-json.ts +171 -0
  282. package/src/discovery/omp-extension-roots.ts +190 -0
  283. package/src/discovery/omp-plugins.ts +383 -0
  284. package/src/discovery/opencode.ts +398 -0
  285. package/src/discovery/plugin-dir-roots.ts +28 -0
  286. package/src/discovery/ssh.ts +153 -0
  287. package/src/discovery/substitute-plugin-root.ts +29 -0
  288. package/src/discovery/vscode.ts +105 -0
  289. package/src/discovery/windsurf.ts +147 -0
  290. package/src/edit/apply-patch/index.ts +87 -0
  291. package/src/edit/apply-patch/parser.ts +174 -0
  292. package/src/edit/diff.ts +999 -0
  293. package/src/edit/file-snapshot-store.ts +91 -0
  294. package/src/edit/hashline/block-resolver.ts +33 -0
  295. package/src/edit/hashline/diff.ts +290 -0
  296. package/src/edit/hashline/execute.ts +242 -0
  297. package/src/edit/hashline/filesystem.ts +130 -0
  298. package/src/edit/hashline/index.ts +5 -0
  299. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  300. package/src/edit/hashline/params.ts +18 -0
  301. package/src/edit/index.ts +571 -0
  302. package/src/edit/modes/apply-patch.lark +19 -0
  303. package/src/edit/modes/apply-patch.ts +53 -0
  304. package/src/edit/modes/patch.ts +1891 -0
  305. package/src/edit/modes/replace.ts +1137 -0
  306. package/src/edit/normalize.ts +345 -0
  307. package/src/edit/notebook.ts +242 -0
  308. package/src/edit/read-file.ts +25 -0
  309. package/src/edit/renderer.ts +769 -0
  310. package/src/edit/streaming.ts +517 -0
  311. package/src/eval/__tests__/agent-bridge.test.ts +708 -0
  312. package/src/eval/__tests__/bridge-timeout.test.ts +64 -0
  313. package/src/eval/__tests__/budget-bridge.test.ts +69 -0
  314. package/src/eval/__tests__/completion-bridge.test.ts +412 -0
  315. package/src/eval/__tests__/helpers-local-roots.test.ts +58 -0
  316. package/src/eval/__tests__/idle-timeout.test.ts +80 -0
  317. package/src/eval/__tests__/js-context-manager.test.ts +241 -0
  318. package/src/eval/__tests__/kernel-spawn.test.ts +103 -0
  319. package/src/eval/agent-bridge.ts +319 -0
  320. package/src/eval/backend.ts +71 -0
  321. package/src/eval/bridge-timeout.ts +44 -0
  322. package/src/eval/budget-bridge.ts +48 -0
  323. package/src/eval/completion-bridge.ts +207 -0
  324. package/src/eval/concurrency-bridge.ts +34 -0
  325. package/src/eval/idle-timeout.ts +91 -0
  326. package/src/eval/index.ts +4 -0
  327. package/src/eval/js/context-manager.ts +502 -0
  328. package/src/eval/js/executor.ts +173 -0
  329. package/src/eval/js/index.ts +51 -0
  330. package/src/eval/js/shared/helpers.ts +283 -0
  331. package/src/eval/js/shared/indirect-eval.ts +30 -0
  332. package/src/eval/js/shared/local-module-loader.ts +342 -0
  333. package/src/eval/js/shared/prelude.ts +2 -0
  334. package/src/eval/js/shared/prelude.txt +246 -0
  335. package/src/eval/js/shared/rewrite-imports.ts +532 -0
  336. package/src/eval/js/shared/runtime.ts +352 -0
  337. package/src/eval/js/shared/types.ts +18 -0
  338. package/src/eval/js/tool-bridge.ts +162 -0
  339. package/src/eval/js/worker-core.ts +132 -0
  340. package/src/eval/js/worker-entry.ts +30 -0
  341. package/src/eval/js/worker-protocol.ts +47 -0
  342. package/src/eval/py/__tests__/prelude.test.ts +19 -0
  343. package/src/eval/py/display.ts +71 -0
  344. package/src/eval/py/executor.ts +742 -0
  345. package/src/eval/py/index.ts +68 -0
  346. package/src/eval/py/kernel.ts +748 -0
  347. package/src/eval/py/prelude.py +658 -0
  348. package/src/eval/py/prelude.ts +3 -0
  349. package/src/eval/py/runner.py +1133 -0
  350. package/src/eval/py/runtime.ts +276 -0
  351. package/src/eval/py/spawn-options.ts +126 -0
  352. package/src/eval/py/tool-bridge.ts +182 -0
  353. package/src/eval/session-id.ts +8 -0
  354. package/src/eval/types.ts +48 -0
  355. package/src/exa/index.ts +2 -0
  356. package/src/exa/mcp-client.ts +370 -0
  357. package/src/exa/types.ts +69 -0
  358. package/src/exec/bash-executor.ts +419 -0
  359. package/src/exec/exec.ts +53 -0
  360. package/src/exec/non-interactive-env.ts +48 -0
  361. package/src/export/custom-share.ts +65 -0
  362. package/src/export/html/index.ts +164 -0
  363. package/src/export/html/template.css +1051 -0
  364. package/src/export/html/template.generated.ts +2 -0
  365. package/src/export/html/template.html +46 -0
  366. package/src/export/html/template.js +2271 -0
  367. package/src/export/html/template.macro.ts +25 -0
  368. package/src/export/html/vendor/highlight.min.js +1213 -0
  369. package/src/export/html/vendor/marked.min.js +6 -0
  370. package/src/export/ttsr.ts +583 -0
  371. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +54 -0
  372. package/src/extensibility/custom-commands/bundled/review/index.ts +489 -0
  373. package/src/extensibility/custom-commands/index.ts +2 -0
  374. package/src/extensibility/custom-commands/loader.ts +238 -0
  375. package/src/extensibility/custom-commands/types.ts +113 -0
  376. package/src/extensibility/custom-tools/index.ts +7 -0
  377. package/src/extensibility/custom-tools/loader.ts +269 -0
  378. package/src/extensibility/custom-tools/types.ts +270 -0
  379. package/src/extensibility/custom-tools/wrapper.ts +47 -0
  380. package/src/extensibility/extensions/compact-handler.ts +40 -0
  381. package/src/extensibility/extensions/get-commands-handler.ts +78 -0
  382. package/src/extensibility/extensions/index.ts +16 -0
  383. package/src/extensibility/extensions/loader.ts +572 -0
  384. package/src/extensibility/extensions/runner.ts +922 -0
  385. package/src/extensibility/extensions/types.ts +1322 -0
  386. package/src/extensibility/extensions/wrapper.ts +223 -0
  387. package/src/extensibility/hooks/index.ts +5 -0
  388. package/src/extensibility/hooks/loader.ts +257 -0
  389. package/src/extensibility/hooks/runner.ts +425 -0
  390. package/src/extensibility/hooks/tool-wrapper.ts +107 -0
  391. package/src/extensibility/hooks/types.ts +606 -0
  392. package/src/extensibility/legacy-pi-ai-shim.ts +24 -0
  393. package/src/extensibility/legacy-pi-coding-agent-shim.ts +15 -0
  394. package/src/extensibility/plugins/doctor.ts +65 -0
  395. package/src/extensibility/plugins/git-url.ts +367 -0
  396. package/src/extensibility/plugins/index.ts +9 -0
  397. package/src/extensibility/plugins/installer.ts +192 -0
  398. package/src/extensibility/plugins/legacy-pi-compat.ts +682 -0
  399. package/src/extensibility/plugins/loader.ts +313 -0
  400. package/src/extensibility/plugins/manager.ts +827 -0
  401. package/src/extensibility/plugins/marketplace/cache.ts +136 -0
  402. package/src/extensibility/plugins/marketplace/fetcher.ts +317 -0
  403. package/src/extensibility/plugins/marketplace/index.ts +6 -0
  404. package/src/extensibility/plugins/marketplace/manager.ts +770 -0
  405. package/src/extensibility/plugins/marketplace/registry.ts +196 -0
  406. package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
  407. package/src/extensibility/plugins/marketplace/types.ts +191 -0
  408. package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
  409. package/src/extensibility/plugins/parser.ts +105 -0
  410. package/src/extensibility/plugins/types.ts +194 -0
  411. package/src/extensibility/shared-events.ts +343 -0
  412. package/src/extensibility/skills.ts +312 -0
  413. package/src/extensibility/slash-commands.ts +227 -0
  414. package/src/extensibility/tool-proxy.ts +25 -0
  415. package/src/extensibility/typebox.ts +418 -0
  416. package/src/extensibility/utils.ts +44 -0
  417. package/src/goals/index.ts +3 -0
  418. package/src/goals/runtime.ts +528 -0
  419. package/src/goals/state.ts +37 -0
  420. package/src/goals/tools/goal-tool.ts +251 -0
  421. package/src/hindsight/backend.ts +354 -0
  422. package/src/hindsight/bank.ts +156 -0
  423. package/src/hindsight/client.ts +598 -0
  424. package/src/hindsight/config.ts +175 -0
  425. package/src/hindsight/content.ts +210 -0
  426. package/src/hindsight/index.ts +8 -0
  427. package/src/hindsight/mental-models.ts +429 -0
  428. package/src/hindsight/seeds.json +32 -0
  429. package/src/hindsight/state.ts +488 -0
  430. package/src/hindsight/transcript.ts +71 -0
  431. package/src/index.ts +59 -0
  432. package/src/internal-urls/agent-protocol.ts +146 -0
  433. package/src/internal-urls/artifact-protocol.ts +107 -0
  434. package/src/internal-urls/docs-index.generated.ts +106 -0
  435. package/src/internal-urls/history-protocol.ts +113 -0
  436. package/src/internal-urls/index.ts +25 -0
  437. package/src/internal-urls/issue-pr-protocol.ts +584 -0
  438. package/src/internal-urls/json-query.ts +126 -0
  439. package/src/internal-urls/local-protocol.ts +287 -0
  440. package/src/internal-urls/mcp-protocol.ts +151 -0
  441. package/src/internal-urls/memory-protocol.ts +169 -0
  442. package/src/internal-urls/omp-protocol.ts +93 -0
  443. package/src/internal-urls/parse.ts +72 -0
  444. package/src/internal-urls/registry-helpers.ts +25 -0
  445. package/src/internal-urls/router.ts +105 -0
  446. package/src/internal-urls/rule-protocol.ts +45 -0
  447. package/src/internal-urls/skill-protocol.ts +96 -0
  448. package/src/internal-urls/types.ts +152 -0
  449. package/src/internal-urls/vault-protocol.ts +936 -0
  450. package/src/irc/bus.ts +292 -0
  451. package/src/lib/xai-http.ts +124 -0
  452. package/src/lsp/client.ts +1193 -0
  453. package/src/lsp/clients/biome-client.ts +264 -0
  454. package/src/lsp/clients/index.ts +50 -0
  455. package/src/lsp/clients/lsp-linter-client.ts +93 -0
  456. package/src/lsp/clients/swiftlint-client.ts +120 -0
  457. package/src/lsp/config.ts +502 -0
  458. package/src/lsp/defaults.json +493 -0
  459. package/src/lsp/diagnostics-ledger.ts +51 -0
  460. package/src/lsp/edits.ts +267 -0
  461. package/src/lsp/index.ts +2477 -0
  462. package/src/lsp/lspmux.ts +233 -0
  463. package/src/lsp/render.ts +694 -0
  464. package/src/lsp/startup-events.ts +13 -0
  465. package/src/lsp/types.ts +455 -0
  466. package/src/lsp/utils.ts +718 -0
  467. package/src/main.ts +1325 -0
  468. package/src/mcp/client.ts +484 -0
  469. package/src/mcp/config-writer.ts +225 -0
  470. package/src/mcp/config.ts +365 -0
  471. package/src/mcp/index.ts +29 -0
  472. package/src/mcp/json-rpc.ts +122 -0
  473. package/src/mcp/loader.ts +124 -0
  474. package/src/mcp/manager.ts +1275 -0
  475. package/src/mcp/oauth-discovery.ts +442 -0
  476. package/src/mcp/oauth-flow.ts +442 -0
  477. package/src/mcp/render.ts +132 -0
  478. package/src/mcp/smithery-auth.ts +104 -0
  479. package/src/mcp/smithery-connect.ts +145 -0
  480. package/src/mcp/smithery-registry.ts +477 -0
  481. package/src/mcp/timeout.ts +59 -0
  482. package/src/mcp/tool-bridge.ts +426 -0
  483. package/src/mcp/tool-cache.ts +117 -0
  484. package/src/mcp/transports/http.ts +519 -0
  485. package/src/mcp/transports/index.ts +6 -0
  486. package/src/mcp/transports/stdio.ts +528 -0
  487. package/src/mcp/types.ts +423 -0
  488. package/src/memories/index.ts +1150 -0
  489. package/src/memories/storage.ts +577 -0
  490. package/src/memory-backend/index.ts +18 -0
  491. package/src/memory-backend/local-backend.ts +39 -0
  492. package/src/memory-backend/off-backend.ts +25 -0
  493. package/src/memory-backend/resolve.ts +25 -0
  494. package/src/memory-backend/runtime.ts +66 -0
  495. package/src/memory-backend/types.ts +166 -0
  496. package/src/mnemopi/backend.ts +547 -0
  497. package/src/mnemopi/config.ts +160 -0
  498. package/src/mnemopi/index.ts +3 -0
  499. package/src/mnemopi/state.ts +584 -0
  500. package/src/modes/acp/acp-agent.ts +2407 -0
  501. package/src/modes/acp/acp-client-bridge.ts +154 -0
  502. package/src/modes/acp/acp-event-mapper.ts +929 -0
  503. package/src/modes/acp/acp-mode.ts +23 -0
  504. package/src/modes/acp/index.ts +2 -0
  505. package/src/modes/acp/terminal-auth.ts +37 -0
  506. package/src/modes/components/agent-dashboard.ts +1206 -0
  507. package/src/modes/components/agent-hub.ts +1071 -0
  508. package/src/modes/components/assistant-message.ts +307 -0
  509. package/src/modes/components/bash-execution.ts +220 -0
  510. package/src/modes/components/bordered-loader.ts +41 -0
  511. package/src/modes/components/branch-summary-message.ts +45 -0
  512. package/src/modes/components/btw-panel.ts +104 -0
  513. package/src/modes/components/chat-block.ts +111 -0
  514. package/src/modes/components/compaction-summary-message.ts +87 -0
  515. package/src/modes/components/copy-selector.ts +206 -0
  516. package/src/modes/components/countdown-timer.ts +75 -0
  517. package/src/modes/components/custom-editor.ts +398 -0
  518. package/src/modes/components/custom-message.ts +63 -0
  519. package/src/modes/components/diff.ts +277 -0
  520. package/src/modes/components/dynamic-border.ts +34 -0
  521. package/src/modes/components/error-banner.ts +33 -0
  522. package/src/modes/components/eval-execution.ts +158 -0
  523. package/src/modes/components/execution-shared.ts +101 -0
  524. package/src/modes/components/extensions/extension-dashboard.ts +399 -0
  525. package/src/modes/components/extensions/extension-list.ts +502 -0
  526. package/src/modes/components/extensions/index.ts +9 -0
  527. package/src/modes/components/extensions/inspector-panel.ts +317 -0
  528. package/src/modes/components/extensions/state-manager.ts +627 -0
  529. package/src/modes/components/extensions/types.ts +186 -0
  530. package/src/modes/components/footer.ts +274 -0
  531. package/src/modes/components/history-search.ts +280 -0
  532. package/src/modes/components/hook-editor.ts +167 -0
  533. package/src/modes/components/hook-input.ts +87 -0
  534. package/src/modes/components/hook-message.ts +66 -0
  535. package/src/modes/components/hook-selector.ts +660 -0
  536. package/src/modes/components/index.ts +38 -0
  537. package/src/modes/components/keybinding-hints.ts +65 -0
  538. package/src/modes/components/late-diagnostics-message.ts +60 -0
  539. package/src/modes/components/login-dialog.ts +164 -0
  540. package/src/modes/components/mcp-add-wizard.ts +1340 -0
  541. package/src/modes/components/message-frame.ts +88 -0
  542. package/src/modes/components/model-selector.ts +1271 -0
  543. package/src/modes/components/oauth-selector.ts +368 -0
  544. package/src/modes/components/omfg-panel.ts +141 -0
  545. package/src/modes/components/overlay-box.ts +108 -0
  546. package/src/modes/components/plan-review-overlay.ts +820 -0
  547. package/src/modes/components/plan-toc.ts +138 -0
  548. package/src/modes/components/plugin-selector.ts +95 -0
  549. package/src/modes/components/plugin-settings.ts +722 -0
  550. package/src/modes/components/queue-mode-selector.ts +56 -0
  551. package/src/modes/components/read-tool-group.ts +670 -0
  552. package/src/modes/components/segment-track.ts +52 -0
  553. package/src/modes/components/session-selector.ts +625 -0
  554. package/src/modes/components/settings-defs.ts +189 -0
  555. package/src/modes/components/settings-selector.ts +651 -0
  556. package/src/modes/components/show-images-selector.ts +45 -0
  557. package/src/modes/components/skill-message.ts +89 -0
  558. package/src/modes/components/status-line/component.ts +869 -0
  559. package/src/modes/components/status-line/context-thresholds.ts +79 -0
  560. package/src/modes/components/status-line/git-utils.ts +42 -0
  561. package/src/modes/components/status-line/index.ts +5 -0
  562. package/src/modes/components/status-line/presets.ts +106 -0
  563. package/src/modes/components/status-line/segments.ts +584 -0
  564. package/src/modes/components/status-line/separators.ts +55 -0
  565. package/src/modes/components/status-line/token-rate.ts +66 -0
  566. package/src/modes/components/status-line/types.ts +108 -0
  567. package/src/modes/components/theme-selector.ts +63 -0
  568. package/src/modes/components/thinking-selector.ts +52 -0
  569. package/src/modes/components/tiny-title-download-progress.ts +90 -0
  570. package/src/modes/components/tips.txt +19 -0
  571. package/src/modes/components/todo-reminder.ts +38 -0
  572. package/src/modes/components/tool-execution.ts +1024 -0
  573. package/src/modes/components/transcript-container.ts +608 -0
  574. package/src/modes/components/tree-selector.ts +978 -0
  575. package/src/modes/components/ttsr-notification.ts +122 -0
  576. package/src/modes/components/user-message-selector.ts +227 -0
  577. package/src/modes/components/user-message.ts +66 -0
  578. package/src/modes/components/visual-truncate.ts +63 -0
  579. package/src/modes/components/welcome.ts +493 -0
  580. package/src/modes/controllers/btw-controller.ts +105 -0
  581. package/src/modes/controllers/command-controller-shared.ts +109 -0
  582. package/src/modes/controllers/command-controller.ts +1566 -0
  583. package/src/modes/controllers/event-controller.ts +1054 -0
  584. package/src/modes/controllers/extension-ui-controller.ts +886 -0
  585. package/src/modes/controllers/input-controller.ts +1073 -0
  586. package/src/modes/controllers/mcp-command-controller.ts +2017 -0
  587. package/src/modes/controllers/omfg-controller.ts +283 -0
  588. package/src/modes/controllers/omfg-rule.ts +647 -0
  589. package/src/modes/controllers/selector-controller.ts +1108 -0
  590. package/src/modes/controllers/ssh-command-controller.ts +384 -0
  591. package/src/modes/controllers/streaming-reveal.ts +279 -0
  592. package/src/modes/controllers/tan-command-controller.ts +173 -0
  593. package/src/modes/controllers/todo-command-controller.ts +485 -0
  594. package/src/modes/data/emojis.json +1 -0
  595. package/src/modes/emoji-autocomplete.ts +285 -0
  596. package/src/modes/gradient-highlight.ts +87 -0
  597. package/src/modes/image-references.ts +117 -0
  598. package/src/modes/index.ts +17 -0
  599. package/src/modes/interactive-mode.ts +3370 -0
  600. package/src/modes/internal-url-autocomplete.ts +143 -0
  601. package/src/modes/loop-limit.ts +140 -0
  602. package/src/modes/magic-keywords.ts +20 -0
  603. package/src/modes/markdown-prose.ts +247 -0
  604. package/src/modes/oauth-manual-input.ts +69 -0
  605. package/src/modes/orchestrate.ts +42 -0
  606. package/src/modes/print-mode.ts +126 -0
  607. package/src/modes/prompt-action-autocomplete.ts +260 -0
  608. package/src/modes/rpc/host-tools.ts +186 -0
  609. package/src/modes/rpc/host-uris.ts +235 -0
  610. package/src/modes/rpc/rpc-client.ts +963 -0
  611. package/src/modes/rpc/rpc-mode.ts +947 -0
  612. package/src/modes/rpc/rpc-subagents.ts +265 -0
  613. package/src/modes/rpc/rpc-types.ts +458 -0
  614. package/src/modes/runtime-init.ts +116 -0
  615. package/src/modes/session-observer-registry.ts +146 -0
  616. package/src/modes/setup-version.ts +11 -0
  617. package/src/modes/setup-wizard/index.ts +99 -0
  618. package/src/modes/setup-wizard/lazy.ts +16 -0
  619. package/src/modes/setup-wizard/scenes/glyph.ts +96 -0
  620. package/src/modes/setup-wizard/scenes/outro.ts +35 -0
  621. package/src/modes/setup-wizard/scenes/providers.ts +69 -0
  622. package/src/modes/setup-wizard/scenes/sign-in.ts +205 -0
  623. package/src/modes/setup-wizard/scenes/splash.ts +201 -0
  624. package/src/modes/setup-wizard/scenes/theme.ts +299 -0
  625. package/src/modes/setup-wizard/scenes/types.ts +48 -0
  626. package/src/modes/setup-wizard/scenes/web-search.ts +129 -0
  627. package/src/modes/setup-wizard/wizard-overlay.ts +275 -0
  628. package/src/modes/shared.ts +47 -0
  629. package/src/modes/theme/dark.json +95 -0
  630. package/src/modes/theme/defaults/alabaster.json +93 -0
  631. package/src/modes/theme/defaults/amethyst.json +96 -0
  632. package/src/modes/theme/defaults/anthracite.json +93 -0
  633. package/src/modes/theme/defaults/basalt.json +91 -0
  634. package/src/modes/theme/defaults/birch.json +95 -0
  635. package/src/modes/theme/defaults/dark-abyss.json +91 -0
  636. package/src/modes/theme/defaults/dark-arctic.json +104 -0
  637. package/src/modes/theme/defaults/dark-aurora.json +95 -0
  638. package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
  639. package/src/modes/theme/defaults/dark-cavern.json +91 -0
  640. package/src/modes/theme/defaults/dark-copper.json +95 -0
  641. package/src/modes/theme/defaults/dark-cosmos.json +90 -0
  642. package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
  643. package/src/modes/theme/defaults/dark-dracula.json +98 -0
  644. package/src/modes/theme/defaults/dark-eclipse.json +91 -0
  645. package/src/modes/theme/defaults/dark-ember.json +95 -0
  646. package/src/modes/theme/defaults/dark-equinox.json +90 -0
  647. package/src/modes/theme/defaults/dark-forest.json +96 -0
  648. package/src/modes/theme/defaults/dark-github.json +105 -0
  649. package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
  650. package/src/modes/theme/defaults/dark-lavender.json +95 -0
  651. package/src/modes/theme/defaults/dark-lunar.json +89 -0
  652. package/src/modes/theme/defaults/dark-midnight.json +95 -0
  653. package/src/modes/theme/defaults/dark-monochrome.json +94 -0
  654. package/src/modes/theme/defaults/dark-monokai.json +98 -0
  655. package/src/modes/theme/defaults/dark-nebula.json +90 -0
  656. package/src/modes/theme/defaults/dark-nord.json +97 -0
  657. package/src/modes/theme/defaults/dark-ocean.json +101 -0
  658. package/src/modes/theme/defaults/dark-one.json +100 -0
  659. package/src/modes/theme/defaults/dark-poimandres.json +142 -0
  660. package/src/modes/theme/defaults/dark-rainforest.json +91 -0
  661. package/src/modes/theme/defaults/dark-reef.json +91 -0
  662. package/src/modes/theme/defaults/dark-retro.json +92 -0
  663. package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
  664. package/src/modes/theme/defaults/dark-sakura.json +95 -0
  665. package/src/modes/theme/defaults/dark-slate.json +95 -0
  666. package/src/modes/theme/defaults/dark-solarized.json +97 -0
  667. package/src/modes/theme/defaults/dark-solstice.json +90 -0
  668. package/src/modes/theme/defaults/dark-starfall.json +91 -0
  669. package/src/modes/theme/defaults/dark-sunset.json +99 -0
  670. package/src/modes/theme/defaults/dark-swamp.json +90 -0
  671. package/src/modes/theme/defaults/dark-synthwave.json +103 -0
  672. package/src/modes/theme/defaults/dark-taiga.json +91 -0
  673. package/src/modes/theme/defaults/dark-terminal.json +95 -0
  674. package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
  675. package/src/modes/theme/defaults/dark-tundra.json +91 -0
  676. package/src/modes/theme/defaults/dark-twilight.json +91 -0
  677. package/src/modes/theme/defaults/dark-volcanic.json +91 -0
  678. package/src/modes/theme/defaults/graphite.json +92 -0
  679. package/src/modes/theme/defaults/index.ts +199 -0
  680. package/src/modes/theme/defaults/light-arctic.json +107 -0
  681. package/src/modes/theme/defaults/light-aurora-day.json +91 -0
  682. package/src/modes/theme/defaults/light-canyon.json +91 -0
  683. package/src/modes/theme/defaults/light-catppuccin.json +106 -0
  684. package/src/modes/theme/defaults/light-cirrus.json +90 -0
  685. package/src/modes/theme/defaults/light-coral.json +95 -0
  686. package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
  687. package/src/modes/theme/defaults/light-dawn.json +90 -0
  688. package/src/modes/theme/defaults/light-dunes.json +91 -0
  689. package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
  690. package/src/modes/theme/defaults/light-forest.json +100 -0
  691. package/src/modes/theme/defaults/light-frost.json +95 -0
  692. package/src/modes/theme/defaults/light-github.json +115 -0
  693. package/src/modes/theme/defaults/light-glacier.json +91 -0
  694. package/src/modes/theme/defaults/light-gruvbox.json +108 -0
  695. package/src/modes/theme/defaults/light-haze.json +90 -0
  696. package/src/modes/theme/defaults/light-honeycomb.json +95 -0
  697. package/src/modes/theme/defaults/light-lagoon.json +91 -0
  698. package/src/modes/theme/defaults/light-lavender.json +95 -0
  699. package/src/modes/theme/defaults/light-meadow.json +91 -0
  700. package/src/modes/theme/defaults/light-mint.json +95 -0
  701. package/src/modes/theme/defaults/light-monochrome.json +101 -0
  702. package/src/modes/theme/defaults/light-ocean.json +99 -0
  703. package/src/modes/theme/defaults/light-one.json +99 -0
  704. package/src/modes/theme/defaults/light-opal.json +91 -0
  705. package/src/modes/theme/defaults/light-orchard.json +91 -0
  706. package/src/modes/theme/defaults/light-paper.json +95 -0
  707. package/src/modes/theme/defaults/light-poimandres.json +142 -0
  708. package/src/modes/theme/defaults/light-prism.json +90 -0
  709. package/src/modes/theme/defaults/light-retro.json +98 -0
  710. package/src/modes/theme/defaults/light-sand.json +95 -0
  711. package/src/modes/theme/defaults/light-savanna.json +91 -0
  712. package/src/modes/theme/defaults/light-solarized.json +102 -0
  713. package/src/modes/theme/defaults/light-soleil.json +90 -0
  714. package/src/modes/theme/defaults/light-sunset.json +99 -0
  715. package/src/modes/theme/defaults/light-synthwave.json +98 -0
  716. package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
  717. package/src/modes/theme/defaults/light-wetland.json +91 -0
  718. package/src/modes/theme/defaults/light-zenith.json +89 -0
  719. package/src/modes/theme/defaults/limestone.json +94 -0
  720. package/src/modes/theme/defaults/mahogany.json +97 -0
  721. package/src/modes/theme/defaults/marble.json +93 -0
  722. package/src/modes/theme/defaults/obsidian.json +91 -0
  723. package/src/modes/theme/defaults/onyx.json +91 -0
  724. package/src/modes/theme/defaults/pearl.json +93 -0
  725. package/src/modes/theme/defaults/porcelain.json +91 -0
  726. package/src/modes/theme/defaults/quartz.json +96 -0
  727. package/src/modes/theme/defaults/sandstone.json +95 -0
  728. package/src/modes/theme/defaults/titanium.json +90 -0
  729. package/src/modes/theme/light.json +93 -0
  730. package/src/modes/theme/mermaid-cache.ts +29 -0
  731. package/src/modes/theme/shimmer.ts +235 -0
  732. package/src/modes/theme/theme-schema.json +459 -0
  733. package/src/modes/theme/theme.ts +2676 -0
  734. package/src/modes/turn-budget.ts +31 -0
  735. package/src/modes/types.ts +359 -0
  736. package/src/modes/ultrathink.ts +41 -0
  737. package/src/modes/utils/context-usage.ts +339 -0
  738. package/src/modes/utils/copy-targets.ts +360 -0
  739. package/src/modes/utils/hotkeys-markdown.ts +61 -0
  740. package/src/modes/utils/keybinding-matchers.ts +51 -0
  741. package/src/modes/utils/tools-markdown.ts +27 -0
  742. package/src/modes/utils/ui-helpers.ts +801 -0
  743. package/src/modes/workflow.ts +42 -0
  744. package/src/plan-mode/approved-plan.ts +186 -0
  745. package/src/plan-mode/plan-handoff.ts +37 -0
  746. package/src/plan-mode/plan-protection.ts +31 -0
  747. package/src/plan-mode/state.ts +6 -0
  748. package/src/priority.json +41 -0
  749. package/src/prompts/agents/designer.md +66 -0
  750. package/src/prompts/agents/explore.md +58 -0
  751. package/src/prompts/agents/frontmatter.md +11 -0
  752. package/src/prompts/agents/init.md +33 -0
  753. package/src/prompts/agents/librarian.md +119 -0
  754. package/src/prompts/agents/oracle.md +55 -0
  755. package/src/prompts/agents/plan.md +48 -0
  756. package/src/prompts/agents/reviewer.md +140 -0
  757. package/src/prompts/agents/task.md +16 -0
  758. package/src/prompts/ci-green-request.md +36 -0
  759. package/src/prompts/dry-balance-bench.md +8 -0
  760. package/src/prompts/goals/goal-budget-limit.md +16 -0
  761. package/src/prompts/goals/goal-continuation.md +28 -0
  762. package/src/prompts/goals/goal-mode-active.md +23 -0
  763. package/src/prompts/memories/consolidation.md +30 -0
  764. package/src/prompts/memories/read-path.md +11 -0
  765. package/src/prompts/memories/stage_one_input.md +6 -0
  766. package/src/prompts/memories/stage_one_system.md +21 -0
  767. package/src/prompts/review-custom-request.md +22 -0
  768. package/src/prompts/review-headless-request.md +16 -0
  769. package/src/prompts/review-request.md +69 -0
  770. package/src/prompts/steering/user-interjection.md +10 -0
  771. package/src/prompts/system/agent-creation-architect.md +50 -0
  772. package/src/prompts/system/agent-creation-user.md +6 -0
  773. package/src/prompts/system/auto-continue.md +1 -0
  774. package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
  775. package/src/prompts/system/auto-thinking-difficulty.md +12 -0
  776. package/src/prompts/system/background-tan-dispatch.md +8 -0
  777. package/src/prompts/system/btw-user.md +8 -0
  778. package/src/prompts/system/commit-message-system.md +14 -0
  779. package/src/prompts/system/custom-system-prompt.md +64 -0
  780. package/src/prompts/system/eager-todo.md +13 -0
  781. package/src/prompts/system/empty-stop-retry.md +6 -0
  782. package/src/prompts/system/irc-incoming.md +7 -0
  783. package/src/prompts/system/manual-continue.md +7 -0
  784. package/src/prompts/system/memory-consolidation-system.md +8 -0
  785. package/src/prompts/system/memory-extraction-system.md +26 -0
  786. package/src/prompts/system/omfg-user.md +50 -0
  787. package/src/prompts/system/orchestrate-notice.md +40 -0
  788. package/src/prompts/system/plan-mode-active.md +109 -0
  789. package/src/prompts/system/plan-mode-approved.md +25 -0
  790. package/src/prompts/system/plan-mode-compact-instructions.md +16 -0
  791. package/src/prompts/system/plan-mode-reference.md +11 -0
  792. package/src/prompts/system/plan-mode-subagent.md +33 -0
  793. package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
  794. package/src/prompts/system/project-prompt.md +52 -0
  795. package/src/prompts/system/subagent-system-prompt.md +64 -0
  796. package/src/prompts/system/subagent-user-prompt.md +3 -0
  797. package/src/prompts/system/subagent-yield-reminder.md +12 -0
  798. package/src/prompts/system/system-prompt.md +258 -0
  799. package/src/prompts/system/tiny-title-system.md +8 -0
  800. package/src/prompts/system/title-system.md +16 -0
  801. package/src/prompts/system/ttsr-interrupt.md +7 -0
  802. package/src/prompts/system/ttsr-tool-reminder.md +5 -0
  803. package/src/prompts/system/ultrathink-notice.md +3 -0
  804. package/src/prompts/system/web-search.md +25 -0
  805. package/src/prompts/system/workflow-notice.md +70 -0
  806. package/src/prompts/tools/apply-patch.md +65 -0
  807. package/src/prompts/tools/ask.md +30 -0
  808. package/src/prompts/tools/ast-edit.md +39 -0
  809. package/src/prompts/tools/ast-grep.md +42 -0
  810. package/src/prompts/tools/async-result.md +8 -0
  811. package/src/prompts/tools/bash.md +46 -0
  812. package/src/prompts/tools/browser.md +73 -0
  813. package/src/prompts/tools/checkpoint.md +16 -0
  814. package/src/prompts/tools/debug.md +34 -0
  815. package/src/prompts/tools/eval.md +92 -0
  816. package/src/prompts/tools/find.md +36 -0
  817. package/src/prompts/tools/github.md +21 -0
  818. package/src/prompts/tools/goal.md +18 -0
  819. package/src/prompts/tools/image-gen.md +7 -0
  820. package/src/prompts/tools/inspect-image-system.md +20 -0
  821. package/src/prompts/tools/inspect-image.md +32 -0
  822. package/src/prompts/tools/irc.md +59 -0
  823. package/src/prompts/tools/job.md +19 -0
  824. package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
  825. package/src/prompts/tools/lsp.md +42 -0
  826. package/src/prompts/tools/memory-edit.md +8 -0
  827. package/src/prompts/tools/patch.md +70 -0
  828. package/src/prompts/tools/read.md +84 -0
  829. package/src/prompts/tools/recall.md +5 -0
  830. package/src/prompts/tools/reflect.md +5 -0
  831. package/src/prompts/tools/render-mermaid.md +9 -0
  832. package/src/prompts/tools/replace.md +30 -0
  833. package/src/prompts/tools/resolve.md +9 -0
  834. package/src/prompts/tools/retain.md +6 -0
  835. package/src/prompts/tools/rewind.md +13 -0
  836. package/src/prompts/tools/search-tool-bm25.md +32 -0
  837. package/src/prompts/tools/search.md +24 -0
  838. package/src/prompts/tools/ssh.md +31 -0
  839. package/src/prompts/tools/task-summary.md +17 -0
  840. package/src/prompts/tools/task.md +88 -0
  841. package/src/prompts/tools/todo.md +62 -0
  842. package/src/prompts/tools/web-search.md +10 -0
  843. package/src/prompts/tools/write.md +14 -0
  844. package/src/registry/agent-lifecycle.ts +218 -0
  845. package/src/registry/agent-registry.ts +151 -0
  846. package/src/sdk.ts +2558 -0
  847. package/src/secrets/index.ts +123 -0
  848. package/src/secrets/obfuscator.ts +298 -0
  849. package/src/secrets/regex.ts +21 -0
  850. package/src/session/agent-session.ts +10121 -0
  851. package/src/session/agent-storage.ts +455 -0
  852. package/src/session/artifacts.ts +135 -0
  853. package/src/session/auth-broker-config.ts +131 -0
  854. package/src/session/auth-storage.ts +29 -0
  855. package/src/session/blob-store.ts +255 -0
  856. package/src/session/client-bridge.ts +85 -0
  857. package/src/session/history-storage.ts +348 -0
  858. package/src/session/indexed-session-storage.ts +430 -0
  859. package/src/session/messages.ts +541 -0
  860. package/src/session/redis-session-storage.ts +170 -0
  861. package/src/session/session-dump-format.ts +209 -0
  862. package/src/session/session-history-format.ts +246 -0
  863. package/src/session/session-manager.ts +3676 -0
  864. package/src/session/session-storage.ts +529 -0
  865. package/src/session/shake-types.ts +43 -0
  866. package/src/session/sql-session-storage.ts +314 -0
  867. package/src/session/streaming-output.ts +1330 -0
  868. package/src/session/tool-choice-queue.ts +213 -0
  869. package/src/session/yield-queue.ts +173 -0
  870. package/src/slash-commands/acp-builtins.ts +70 -0
  871. package/src/slash-commands/builtin-registry.ts +1798 -0
  872. package/src/slash-commands/helpers/context-report.ts +39 -0
  873. package/src/slash-commands/helpers/format.ts +46 -0
  874. package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
  875. package/src/slash-commands/helpers/mcp.ts +532 -0
  876. package/src/slash-commands/helpers/parse.ts +85 -0
  877. package/src/slash-commands/helpers/ssh.ts +195 -0
  878. package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
  879. package/src/slash-commands/helpers/todo.ts +279 -0
  880. package/src/slash-commands/helpers/usage-report.ts +95 -0
  881. package/src/slash-commands/marketplace-install-parser.ts +99 -0
  882. package/src/slash-commands/types.ts +135 -0
  883. package/src/ssh/config-writer.ts +183 -0
  884. package/src/ssh/connection-manager.ts +509 -0
  885. package/src/ssh/ssh-executor.ts +189 -0
  886. package/src/ssh/sshfs-mount.ts +140 -0
  887. package/src/ssh/utils.ts +8 -0
  888. package/src/stt/downloader.ts +71 -0
  889. package/src/stt/index.ts +3 -0
  890. package/src/stt/recorder.ts +351 -0
  891. package/src/stt/setup.ts +52 -0
  892. package/src/stt/stt-controller.ts +160 -0
  893. package/src/stt/transcribe.py +70 -0
  894. package/src/stt/transcriber.ts +91 -0
  895. package/src/stubs/natives/index.ts +814 -0
  896. package/src/stubs/natives/package.json +7 -0
  897. package/src/stubs/tui/index.ts +282 -0
  898. package/src/stubs/tui/package.json +7 -0
  899. package/src/system-prompt.ts +611 -0
  900. package/src/task/agents.ts +167 -0
  901. package/src/task/commands.ts +132 -0
  902. package/src/task/discovery.ts +122 -0
  903. package/src/task/executor.ts +2133 -0
  904. package/src/task/index.ts +1419 -0
  905. package/src/task/name-generator.ts +1577 -0
  906. package/src/task/omp-command.ts +26 -0
  907. package/src/task/output-manager.ts +88 -0
  908. package/src/task/parallel.ts +116 -0
  909. package/src/task/render.ts +1381 -0
  910. package/src/task/repair-args.ts +129 -0
  911. package/src/task/subprocess-tool-registry.ts +88 -0
  912. package/src/task/types.ts +336 -0
  913. package/src/task/worktree.ts +514 -0
  914. package/src/telemetry-export.ts +144 -0
  915. package/src/thinking.ts +167 -0
  916. package/src/tiny/compiled-runtime.ts +179 -0
  917. package/src/tiny/device.ts +111 -0
  918. package/src/tiny/dtype.ts +101 -0
  919. package/src/tiny/models.ts +242 -0
  920. package/src/tiny/text.ts +165 -0
  921. package/src/tiny/title-client.ts +543 -0
  922. package/src/tiny/title-protocol.ts +56 -0
  923. package/src/tiny/worker.ts +568 -0
  924. package/src/tool-discovery/mode.ts +24 -0
  925. package/src/tool-discovery/tool-index.ts +256 -0
  926. package/src/tools/approval.ts +189 -0
  927. package/src/tools/archive-reader.ts +721 -0
  928. package/src/tools/ask.ts +928 -0
  929. package/src/tools/ast-edit.ts +642 -0
  930. package/src/tools/ast-grep.ts +452 -0
  931. package/src/tools/auto-generated-guard.ts +322 -0
  932. package/src/tools/bash-command-fixup.ts +37 -0
  933. package/src/tools/bash-interactive.ts +408 -0
  934. package/src/tools/bash-interceptor.ts +67 -0
  935. package/src/tools/bash-pty-selection.ts +14 -0
  936. package/src/tools/bash-skill-urls.ts +248 -0
  937. package/src/tools/bash.ts +1386 -0
  938. package/src/tools/browser/attach.ts +175 -0
  939. package/src/tools/browser/launch.ts +660 -0
  940. package/src/tools/browser/readable.ts +112 -0
  941. package/src/tools/browser/registry.ts +197 -0
  942. package/src/tools/browser/render.ts +216 -0
  943. package/src/tools/browser/tab-protocol.ts +105 -0
  944. package/src/tools/browser/tab-supervisor.ts +628 -0
  945. package/src/tools/browser/tab-worker-entry.ts +21 -0
  946. package/src/tools/browser/tab-worker.ts +1226 -0
  947. package/src/tools/browser.ts +343 -0
  948. package/src/tools/checkpoint.ts +136 -0
  949. package/src/tools/conflict-detect.ts +718 -0
  950. package/src/tools/context.ts +39 -0
  951. package/src/tools/debug.ts +1067 -0
  952. package/src/tools/eval-backends.ts +27 -0
  953. package/src/tools/eval-render.ts +752 -0
  954. package/src/tools/eval.ts +577 -0
  955. package/src/tools/fetch.ts +1926 -0
  956. package/src/tools/file-recorder.ts +35 -0
  957. package/src/tools/find.ts +609 -0
  958. package/src/tools/fs-cache-invalidation.ts +28 -0
  959. package/src/tools/gh-cache-invalidation.ts +255 -0
  960. package/src/tools/gh-format.ts +12 -0
  961. package/src/tools/gh-renderer.ts +481 -0
  962. package/src/tools/gh.ts +3720 -0
  963. package/src/tools/github-cache.ts +637 -0
  964. package/src/tools/grouped-file-output.ts +210 -0
  965. package/src/tools/image-gen.ts +1517 -0
  966. package/src/tools/index.ts +599 -0
  967. package/src/tools/inspect-image-renderer.ts +132 -0
  968. package/src/tools/inspect-image.ts +174 -0
  969. package/src/tools/irc.ts +723 -0
  970. package/src/tools/job.ts +557 -0
  971. package/src/tools/json-tree.ts +243 -0
  972. package/src/tools/jtd-to-json-schema.ts +219 -0
  973. package/src/tools/jtd-to-typescript.ts +136 -0
  974. package/src/tools/jtd-utils.ts +102 -0
  975. package/src/tools/list-limit.ts +40 -0
  976. package/src/tools/match-line-format.ts +20 -0
  977. package/src/tools/memory-edit.ts +59 -0
  978. package/src/tools/memory-recall.ts +100 -0
  979. package/src/tools/memory-reflect.ts +88 -0
  980. package/src/tools/memory-render.ts +202 -0
  981. package/src/tools/memory-retain.ts +91 -0
  982. package/src/tools/output-meta.ts +754 -0
  983. package/src/tools/output-schema-validator.ts +132 -0
  984. package/src/tools/path-utils.ts +1054 -0
  985. package/src/tools/plan-mode-guard.ts +108 -0
  986. package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
  987. package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
  988. package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
  989. package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
  990. package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
  991. package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
  992. package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
  993. package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
  994. package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
  995. package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
  996. package/src/tools/puppeteer/10_stealth_plugins.txt +206 -0
  997. package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
  998. package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
  999. package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
  1000. package/src/tools/read.ts +2929 -0
  1001. package/src/tools/render-mermaid.ts +69 -0
  1002. package/src/tools/render-utils.ts +838 -0
  1003. package/src/tools/renderers.ts +77 -0
  1004. package/src/tools/report-tool-issue.ts +534 -0
  1005. package/src/tools/resolve.ts +276 -0
  1006. package/src/tools/review.ts +253 -0
  1007. package/src/tools/search-tool-bm25.ts +351 -0
  1008. package/src/tools/search.ts +1580 -0
  1009. package/src/tools/sqlite-reader.ts +828 -0
  1010. package/src/tools/ssh.ts +349 -0
  1011. package/src/tools/todo.ts +982 -0
  1012. package/src/tools/tool-errors.ts +62 -0
  1013. package/src/tools/tool-result.ts +94 -0
  1014. package/src/tools/tool-timeouts.ts +30 -0
  1015. package/src/tools/tts.ts +133 -0
  1016. package/src/tools/write.ts +1217 -0
  1017. package/src/tools/yield.ts +269 -0
  1018. package/src/tui/code-cell.ts +216 -0
  1019. package/src/tui/file-list.ts +55 -0
  1020. package/src/tui/hyperlink.ts +175 -0
  1021. package/src/tui/index.ts +12 -0
  1022. package/src/tui/output-block.ts +240 -0
  1023. package/src/tui/status-line.ts +54 -0
  1024. package/src/tui/tree-list.ts +84 -0
  1025. package/src/tui/types.ts +15 -0
  1026. package/src/tui/utils.ts +103 -0
  1027. package/src/utils/block-context.ts +312 -0
  1028. package/src/utils/changelog.ts +132 -0
  1029. package/src/utils/clipboard.ts +193 -0
  1030. package/src/utils/command-args.ts +76 -0
  1031. package/src/utils/commit-message-generator.ts +151 -0
  1032. package/src/utils/edit-mode.ts +41 -0
  1033. package/src/utils/enhanced-paste.ts +230 -0
  1034. package/src/utils/event-bus.ts +33 -0
  1035. package/src/utils/external-editor.ts +65 -0
  1036. package/src/utils/file-display-mode.ts +45 -0
  1037. package/src/utils/file-mentions.ts +281 -0
  1038. package/src/utils/git.ts +1833 -0
  1039. package/src/utils/image-loading.ts +132 -0
  1040. package/src/utils/image-resize.ts +309 -0
  1041. package/src/utils/jj.ts +248 -0
  1042. package/src/utils/lang-from-path.ts +239 -0
  1043. package/src/utils/markit.ts +89 -0
  1044. package/src/utils/open.ts +55 -0
  1045. package/src/utils/session-color.ts +68 -0
  1046. package/src/utils/shell-snapshot.ts +187 -0
  1047. package/src/utils/sixel.ts +69 -0
  1048. package/src/utils/title-generator.ts +373 -0
  1049. package/src/utils/tool-choice.ts +33 -0
  1050. package/src/utils/tools-manager.ts +363 -0
  1051. package/src/web/kagi.ts +305 -0
  1052. package/src/web/parallel.ts +353 -0
  1053. package/src/web/scrapers/artifacthub.ts +207 -0
  1054. package/src/web/scrapers/arxiv.ts +83 -0
  1055. package/src/web/scrapers/aur.ts +162 -0
  1056. package/src/web/scrapers/biorxiv.ts +133 -0
  1057. package/src/web/scrapers/bluesky.ts +262 -0
  1058. package/src/web/scrapers/brew.ts +172 -0
  1059. package/src/web/scrapers/cheatsh.ts +68 -0
  1060. package/src/web/scrapers/chocolatey.ts +196 -0
  1061. package/src/web/scrapers/choosealicense.ts +95 -0
  1062. package/src/web/scrapers/cisa-kev.ts +87 -0
  1063. package/src/web/scrapers/clojars.ts +154 -0
  1064. package/src/web/scrapers/coingecko.ts +177 -0
  1065. package/src/web/scrapers/crates-io.ts +97 -0
  1066. package/src/web/scrapers/crossref.ts +136 -0
  1067. package/src/web/scrapers/devto.ts +147 -0
  1068. package/src/web/scrapers/discogs.ts +306 -0
  1069. package/src/web/scrapers/discourse.ts +197 -0
  1070. package/src/web/scrapers/dockerhub.ts +138 -0
  1071. package/src/web/scrapers/docs-rs.ts +653 -0
  1072. package/src/web/scrapers/fdroid.ts +134 -0
  1073. package/src/web/scrapers/firefox-addons.ts +191 -0
  1074. package/src/web/scrapers/flathub.ts +223 -0
  1075. package/src/web/scrapers/github-gist.ts +58 -0
  1076. package/src/web/scrapers/github.ts +704 -0
  1077. package/src/web/scrapers/gitlab.ts +401 -0
  1078. package/src/web/scrapers/go-pkg.ts +266 -0
  1079. package/src/web/scrapers/hackage.ts +140 -0
  1080. package/src/web/scrapers/hackernews.ts +189 -0
  1081. package/src/web/scrapers/hex.ts +105 -0
  1082. package/src/web/scrapers/huggingface.ts +321 -0
  1083. package/src/web/scrapers/iacr.ts +89 -0
  1084. package/src/web/scrapers/index.ts +252 -0
  1085. package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
  1086. package/src/web/scrapers/lemmy.ts +203 -0
  1087. package/src/web/scrapers/lobsters.ts +175 -0
  1088. package/src/web/scrapers/mastodon.ts +292 -0
  1089. package/src/web/scrapers/maven.ts +138 -0
  1090. package/src/web/scrapers/mdn.ts +173 -0
  1091. package/src/web/scrapers/metacpan.ts +222 -0
  1092. package/src/web/scrapers/musicbrainz.ts +250 -0
  1093. package/src/web/scrapers/npm.ts +98 -0
  1094. package/src/web/scrapers/nuget.ts +183 -0
  1095. package/src/web/scrapers/nvd.ts +222 -0
  1096. package/src/web/scrapers/ollama.ts +239 -0
  1097. package/src/web/scrapers/open-vsx.ts +106 -0
  1098. package/src/web/scrapers/opencorporates.ts +292 -0
  1099. package/src/web/scrapers/openlibrary.ts +336 -0
  1100. package/src/web/scrapers/orcid.ts +286 -0
  1101. package/src/web/scrapers/osv.ts +176 -0
  1102. package/src/web/scrapers/packagist.ts +160 -0
  1103. package/src/web/scrapers/pub-dev.ts +143 -0
  1104. package/src/web/scrapers/pubmed.ts +211 -0
  1105. package/src/web/scrapers/pypi.ts +112 -0
  1106. package/src/web/scrapers/rawg.ts +110 -0
  1107. package/src/web/scrapers/readthedocs.ts +120 -0
  1108. package/src/web/scrapers/reddit.ts +95 -0
  1109. package/src/web/scrapers/repology.ts +251 -0
  1110. package/src/web/scrapers/rfc.ts +201 -0
  1111. package/src/web/scrapers/rubygems.ts +103 -0
  1112. package/src/web/scrapers/searchcode.ts +189 -0
  1113. package/src/web/scrapers/sec-edgar.ts +261 -0
  1114. package/src/web/scrapers/semantic-scholar.ts +171 -0
  1115. package/src/web/scrapers/snapcraft.ts +187 -0
  1116. package/src/web/scrapers/sourcegraph.ts +336 -0
  1117. package/src/web/scrapers/spdx.ts +108 -0
  1118. package/src/web/scrapers/spotify.ts +198 -0
  1119. package/src/web/scrapers/stackoverflow.ts +120 -0
  1120. package/src/web/scrapers/terraform.ts +277 -0
  1121. package/src/web/scrapers/tldr.ts +47 -0
  1122. package/src/web/scrapers/twitter.ts +94 -0
  1123. package/src/web/scrapers/types.ts +397 -0
  1124. package/src/web/scrapers/utils.ts +109 -0
  1125. package/src/web/scrapers/vimeo.ts +133 -0
  1126. package/src/web/scrapers/vscode-marketplace.ts +187 -0
  1127. package/src/web/scrapers/w3c.ts +156 -0
  1128. package/src/web/scrapers/wikidata.ts +344 -0
  1129. package/src/web/scrapers/wikipedia.ts +84 -0
  1130. package/src/web/scrapers/youtube.ts +325 -0
  1131. package/src/web/search/index.ts +292 -0
  1132. package/src/web/search/provider.ts +157 -0
  1133. package/src/web/search/providers/anthropic.ts +318 -0
  1134. package/src/web/search/providers/base.ts +89 -0
  1135. package/src/web/search/providers/brave.ts +152 -0
  1136. package/src/web/search/providers/codex.ts +591 -0
  1137. package/src/web/search/providers/exa.ts +400 -0
  1138. package/src/web/search/providers/gemini.ts +460 -0
  1139. package/src/web/search/providers/jina.ts +111 -0
  1140. package/src/web/search/providers/kagi.ts +86 -0
  1141. package/src/web/search/providers/kimi.ts +196 -0
  1142. package/src/web/search/providers/parallel.ts +225 -0
  1143. package/src/web/search/providers/perplexity.ts +730 -0
  1144. package/src/web/search/providers/searxng.ts +313 -0
  1145. package/src/web/search/providers/synthetic.ts +114 -0
  1146. package/src/web/search/providers/tavily.ts +176 -0
  1147. package/src/web/search/providers/utils.ts +128 -0
  1148. package/src/web/search/providers/zai.ts +333 -0
  1149. package/src/web/search/render.ts +262 -0
  1150. package/src/web/search/types.ts +482 -0
  1151. package/src/web/search/utils.ts +17 -0
  1152. package/src/workspace-tree.ts +286 -0
@@ -0,0 +1,2407 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import {
4
+ type Agent,
5
+ type AgentSideConnection,
6
+ type AuthenticateRequest,
7
+ type AuthenticateResponse,
8
+ type AuthMethod,
9
+ type AvailableCommand,
10
+ type ClientCapabilities,
11
+ type CloseSessionRequest,
12
+ type CloseSessionResponse,
13
+ type CreateElicitationResponse,
14
+ type ElicitationContentValue,
15
+ type ElicitationPropertySchema,
16
+ type ForkSessionRequest,
17
+ type ForkSessionResponse,
18
+ type InitializeRequest,
19
+ type InitializeResponse,
20
+ type ListSessionsRequest,
21
+ type ListSessionsResponse,
22
+ type LoadSessionRequest,
23
+ type LoadSessionResponse,
24
+ type McpServer,
25
+ type NewSessionRequest,
26
+ type NewSessionResponse,
27
+ PROTOCOL_VERSION,
28
+ type PromptRequest,
29
+ type PromptResponse,
30
+ type ResumeSessionRequest,
31
+ type ResumeSessionResponse,
32
+ type SessionConfigOption,
33
+ type SessionInfo,
34
+ type SessionModelState,
35
+ type SessionModeState,
36
+ type SessionNotification,
37
+ type SessionUpdate,
38
+ type SetSessionConfigOptionRequest,
39
+ type SetSessionConfigOptionResponse,
40
+ type SetSessionModelRequest,
41
+ type SetSessionModelResponse,
42
+ type SetSessionModeRequest,
43
+ type SetSessionModeResponse,
44
+ type Usage,
45
+ } from "@agentclientprotocol/sdk";
46
+ import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
47
+ import type { AssistantMessage, Model } from "@oh-my-pi/pi-ai";
48
+ import { isEnoent, logger, VERSION } from "@oh-my-pi/pi-utils";
49
+ import { disableProvider, enableProvider, reset as resetCapabilities } from "../../capability";
50
+ import { Settings } from "../../config/settings";
51
+ import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
52
+ import {
53
+ type ExtensionUIContext,
54
+ type ExtensionUIDialogOptions,
55
+ getExtensionUISelectOptionLabel,
56
+ } from "../../extensibility/extensions";
57
+ import { runExtensionCompact } from "../../extensibility/extensions/compact-handler";
58
+ import { getSessionSlashCommands } from "../../extensibility/extensions/get-commands-handler";
59
+ import { buildSkillPromptMessage, getSkillSlashCommandName } from "../../extensibility/skills";
60
+ import { loadSlashCommands } from "../../extensibility/slash-commands";
61
+ import { resolveLocalUrlToPath } from "../../internal-urls";
62
+ import { MCPManager } from "../../mcp/manager";
63
+ import type { MCPServerConfig } from "../../mcp/types";
64
+ import { loadAllExtensions } from "../../modes/components/extensions/state-manager";
65
+ import { theme } from "../../modes/theme/theme";
66
+ import { type PlanApprovalDetails, resolveApprovedPlan } from "../../plan-mode/approved-plan";
67
+ import type { AgentSession, AgentSessionEvent } from "../../session/agent-session";
68
+ import { isSilentAbort, SKILL_PROMPT_MESSAGE_TYPE } from "../../session/messages";
69
+ import {
70
+ SessionManager,
71
+ type SessionInfo as StoredSessionInfo,
72
+ type UsageStatistics,
73
+ } from "../../session/session-manager";
74
+ import {
75
+ ACP_BUILTIN_RESERVED_NAMES,
76
+ ACP_BUILTIN_SLASH_COMMANDS,
77
+ executeAcpBuiltinSlashCommand,
78
+ isAcpBuiltinShadowedName,
79
+ } from "../../slash-commands/acp-builtins";
80
+ import { AUTO_THINKING, parseConfiguredThinkingLevel } from "../../thinking";
81
+ import { normalizeLocalScheme } from "../../tools/path-utils";
82
+ import { runResolveInvocation } from "../../tools/resolve";
83
+ import { ToolError } from "../../tools/tool-errors";
84
+ import { createAcpClientBridge } from "./acp-client-bridge";
85
+ import {
86
+ buildToolCallStartUpdate,
87
+ mapAgentSessionEventToAcpSessionUpdates,
88
+ normalizeReplayToolArguments,
89
+ } from "./acp-event-mapper";
90
+ import { ACP_TERMINAL_AUTH_FLAG } from "./terminal-auth";
91
+
92
+ const ACP_DEFAULT_MODE_ID = "default";
93
+ const ACP_PLAN_MODE_ID = "plan";
94
+ const DEFAULT_PLAN_FILE_URL = "local://PLAN.md";
95
+ const APPROVE_OPTION = "Approve and execute";
96
+ const REFINE_OPTION = "Refine plan";
97
+ const MODE_CONFIG_ID = "mode";
98
+ const MODEL_CONFIG_ID = "model";
99
+ const THINKING_CONFIG_ID = "thinking";
100
+ const THINKING_OFF = "off";
101
+ const SESSION_PAGE_SIZE = 50;
102
+ /**
103
+ * Delay between `session/new` (or `session/load` / `session/resume` /
104
+ * `unstable_session/fork`) returning and the agent firing the first
105
+ * notifications against the new session id. Mitigates Zed's
106
+ * `Received session notification for unknown session` race — see
107
+ * `#scheduleBootstrapUpdates`. Exported so the ACP test harness can
108
+ * wait past this guard without hard-coding the literal.
109
+ */
110
+ export const ACP_BOOTSTRAP_RACE_GUARD_MS = 50;
111
+ const ACP_CANCEL_CLEANUP_TIMEOUT_MS = 5_000;
112
+ const ACP_ASYNC_DELIVERY_DRAIN_TIMEOUT_MS = 250;
113
+ const ACP_ASYNC_DELIVERY_DRAIN_MAX_PASSES = 3;
114
+
115
+ type AgentImageContent = {
116
+ type: "image";
117
+ data: string;
118
+ mimeType: string;
119
+ };
120
+
121
+ type PromptQueueState = {
122
+ promise: Promise<void>;
123
+ release: (() => void) | undefined;
124
+ };
125
+ type PromptLifecycleError = Error & { readonly code: "ACP_SESSION_CLOSED" };
126
+
127
+ type PromptTurnState = {
128
+ userMessageId: string;
129
+ cancelRequested: boolean;
130
+ settled: boolean;
131
+ /**
132
+ * `abort()` is in-flight (or its bounded-timeout race). `undefined` while the turn is
133
+ * running normally and after cleanup completes. The turn occupies `record.promptTurn`
134
+ * for as long as either `!settled` or `cleanup` is set — that combined window is the
135
+ * "turn in flight" predicate (`isPromptTurnInFlight`) every consumer gates on.
136
+ */
137
+ cleanup: Promise<void> | undefined;
138
+ usageBaseline: UsageStatistics;
139
+ unsubscribe: (() => void) | undefined;
140
+ resolve: (value: PromptResponse) => void;
141
+ reject: (reason?: unknown) => void;
142
+ promise: Promise<PromptResponse>;
143
+ };
144
+
145
+ /**
146
+ * A turn is "in flight" from the moment `prompt()` reserves the slot until `settled` is
147
+ * true AND any cancel cleanup has completed. Fork/queue/event gating all depend on this
148
+ * combined window — a settled-but-still-aborting turn is not safe to fork from, queue
149
+ * onto, or forward late events for.
150
+ */
151
+ function isPromptTurnInFlight(turn: PromptTurnState | undefined): turn is PromptTurnState {
152
+ return turn !== undefined && (!turn.settled || turn.cleanup !== undefined);
153
+ }
154
+
155
+ type ManagedSessionRecord = {
156
+ session: AgentSession;
157
+ mcpManager: MCPManager | undefined;
158
+ promptTurn: PromptTurnState | undefined;
159
+ promptQueue: PromptQueueState;
160
+ liveMessageId: string | undefined;
161
+ liveMessageProgress: { textEmitted: boolean; thoughtEmitted: boolean } | undefined;
162
+ toolArgsById: Map<string, unknown>;
163
+ extensionsConfigured: boolean;
164
+ // Installed inside `#scheduleBootstrapUpdates` (post-race-guard); released
165
+ // in `#disposeSessionRecord`. Lives independent of any prompt turn.
166
+ lifetimeUnsubscribe: (() => void) | undefined;
167
+ closedError: PromptLifecycleError | undefined;
168
+ promptEventHandlers: Set<Promise<void>>;
169
+ extensionUserMessageTasks: Set<Promise<void>>;
170
+ };
171
+
172
+ type ReplayableMessage = {
173
+ role: string;
174
+ content?: unknown;
175
+ errorMessage?: string;
176
+ toolCallId?: string;
177
+ toolName?: string;
178
+ details?: unknown;
179
+ isError?: boolean;
180
+ };
181
+
182
+ type ReplayableToolItem = {
183
+ type?: unknown;
184
+ id?: unknown;
185
+ name?: unknown;
186
+ arguments?: unknown;
187
+ input?: unknown;
188
+ };
189
+
190
+ type MCPConfigMap = {
191
+ [name: string]: MCPServerConfig;
192
+ };
193
+
194
+ type MCPSource = {
195
+ provider: string;
196
+ providerName: string;
197
+ path: string;
198
+ level: "project";
199
+ };
200
+
201
+ type MCPSourceMap = {
202
+ [name: string]: MCPSource;
203
+ };
204
+
205
+ type CreateAcpSession = (cwd: string) => Promise<AgentSession>;
206
+
207
+ /**
208
+ * Bridge a single ExtensionUIContext call to the ACP `unstable_createElicitation`
209
+ * surface. Skills/extensions ask for one value at a time (a chosen option, a
210
+ * confirmation, a piece of text), so every elicitation here uses a one-property
211
+ * `value` schema; the caller narrows the resulting `ElicitationContentValue`
212
+ * back to its concrete primitive type.
213
+ *
214
+ * `dialogOptions.signal` short-circuits the elicitation if it is already
215
+ * aborted and races the in-flight request against the abort event. The SDK
216
+ * exposes no `cancel_elicitation` surface for form-mode elicitations
217
+ * (`unstable_completeElicitation` is URL-mode only), so the ACP request itself
218
+ * keeps running on the client side until the user dismisses it — but
219
+ * resolving the local promise unblocks the caller (matches the RPC mode
220
+ * pattern in `requestRpcEditor`). The abort listener is removed once the
221
+ * elicitation settles so that callers which reuse the same signal across many
222
+ * elicitations (e.g. `ask` multi-select loops) don't accumulate listeners and
223
+ * trip Node's `MaxListeners` warning.
224
+ *
225
+ * `dialogOptions.timeout` mirrors `RpcExtensionUIContext.#createDialogPromise`:
226
+ * when the timer fires before the client responds, `onTimeout` is invoked and
227
+ * the caller's promise resolves to the stub fallback. Late SDK responses that
228
+ * arrive after abort/timeout — both rejections and successful `accept`s —
229
+ * are dropped silently (no `logger.warn`) to keep operator logs clean.
230
+ */
231
+ async function elicitFromAcpClient(
232
+ connection: AgentSideConnection,
233
+ sessionId: string,
234
+ method: "select" | "confirm" | "input",
235
+ message: string,
236
+ property: ElicitationPropertySchema,
237
+ dialogOptions: ExtensionUIDialogOptions | undefined,
238
+ ): Promise<ElicitationContentValue | undefined> {
239
+ const signal = dialogOptions?.signal;
240
+ if (signal?.aborted) {
241
+ return undefined;
242
+ }
243
+ const { promise, resolve } = Promise.withResolvers<CreateElicitationResponse | undefined>();
244
+ let settled = false;
245
+ let timeoutId: NodeJS.Timeout | undefined;
246
+ const finish = (value: CreateElicitationResponse | undefined) => {
247
+ if (settled) return;
248
+ settled = true;
249
+ if (timeoutId !== undefined) clearTimeout(timeoutId);
250
+ signal?.removeEventListener("abort", onAbort);
251
+ resolve(value);
252
+ };
253
+ const onAbort = () => finish(undefined);
254
+ signal?.addEventListener("abort", onAbort, { once: true });
255
+ if (dialogOptions?.timeout !== undefined) {
256
+ timeoutId = setTimeout(() => {
257
+ if (settled) return;
258
+ try {
259
+ dialogOptions.onTimeout?.();
260
+ } catch (error) {
261
+ // A throwing `onTimeout` must not leave the elicitation promise
262
+ // pending — settle it via `finish` below regardless.
263
+ logger.warn("ACP elicitation onTimeout threw", { sessionId, method, error });
264
+ }
265
+ finish(undefined);
266
+ }, dialogOptions.timeout);
267
+ // A long pending timeout alone shouldn't keep the event loop alive when
268
+ // the rest of the agent has shut down — matches `job-manager.ts` /
269
+ // `executor.ts` timer hygiene. Connection + session lifetimes keep the
270
+ // loop alive on the happy path.
271
+ timeoutId.unref();
272
+ }
273
+ connection
274
+ .unstable_createElicitation({
275
+ mode: "form",
276
+ sessionId,
277
+ message,
278
+ requestedSchema: {
279
+ type: "object",
280
+ properties: { value: property },
281
+ required: ["value"],
282
+ },
283
+ })
284
+ .then(finish, error => {
285
+ // Caller may already have moved on via abort/timeout; suppress noise.
286
+ if (settled) return;
287
+ logger.warn("ACP elicitation failed", { sessionId, method, error });
288
+ finish(undefined);
289
+ });
290
+ const response = await promise;
291
+ if (response?.action !== "accept" || !response.content) {
292
+ return undefined;
293
+ }
294
+ return response.content.value;
295
+ }
296
+
297
+ /**
298
+ * Build an {@link ExtensionUIContext} that translates skill/extension UI
299
+ * requests into ACP elicitations against `connection` for the session
300
+ * returned by `getSessionId()`. The id is read lazily at each elicitation
301
+ * because `AgentSession.sessionId` is a getter over `sessionManager` state
302
+ * that mutates when an extension command calls `ctx.newSession` /
303
+ * `ctx.switchSession` — snapshotting it once at factory time would route
304
+ * later elicitations to the pre-switch id. Live reads keep the bridge
305
+ * symmetric with every other `sessionUpdate` call in this file
306
+ * (`record.session.sessionId` is always evaluated at emit time).
307
+ *
308
+ * The non-elicitation surface (custom components, editor, theming,
309
+ * terminal input) remains stubbed — ACP clients render those themselves
310
+ * or not at all. Capability gating respects the client's `initialize`
311
+ * advertisement.
312
+ */
313
+ export function createAcpExtensionUiContext(
314
+ connection: AgentSideConnection,
315
+ getSessionId: () => string,
316
+ clientCapabilities: ClientCapabilities | undefined,
317
+ ): ExtensionUIContext {
318
+ const supportsForm = clientCapabilities?.elicitation?.form != null;
319
+ return {
320
+ select: async (title, options, dialogOptions) => {
321
+ if (!supportsForm) return undefined;
322
+ const value = await elicitFromAcpClient(
323
+ connection,
324
+ getSessionId(),
325
+ "select",
326
+ title,
327
+ { type: "string", enum: options.map(getExtensionUISelectOptionLabel) },
328
+ dialogOptions,
329
+ );
330
+ return typeof value === "string" ? value : undefined;
331
+ },
332
+ confirm: async (title, message, dialogOptions) => {
333
+ if (!supportsForm) return false;
334
+ const value = await elicitFromAcpClient(
335
+ connection,
336
+ getSessionId(),
337
+ "confirm",
338
+ message.trim().length > 0 ? `${title}\n\n${message}` : title,
339
+ { type: "boolean" },
340
+ dialogOptions,
341
+ );
342
+ return typeof value === "boolean" ? value : false;
343
+ },
344
+ input: async (title, placeholder, dialogOptions) => {
345
+ if (!supportsForm) return undefined;
346
+ const value = await elicitFromAcpClient(
347
+ connection,
348
+ getSessionId(),
349
+ "input",
350
+ title,
351
+ // ACP's `StringPropertySchema` has no `placeholder` field, so we
352
+ // surface the placeholder text as `description` — the closest
353
+ // semantic field a client can render alongside the input.
354
+ // Empty / whitespace-only placeholders are treated as absent.
355
+ { type: "string", ...(placeholder?.trim() ? { description: placeholder } : {}) },
356
+ dialogOptions,
357
+ );
358
+ return typeof value === "string" ? value : undefined;
359
+ },
360
+ notify: (message, type) => {
361
+ logger.debug("ACP extension notification", { message, type });
362
+ },
363
+ onTerminalInput: () => () => {},
364
+ setStatus: () => {},
365
+ setWorkingMessage: () => {},
366
+ setWidget: () => {},
367
+ setFooter: () => {},
368
+ setHeader: () => {},
369
+ setTitle: () => {},
370
+ custom: async () => undefined as never,
371
+ pasteToEditor: () => {},
372
+ setEditorText: () => {},
373
+ getEditorText: () => "",
374
+ editor: async () => undefined,
375
+ setEditorComponent: () => {},
376
+ get theme() {
377
+ return theme;
378
+ },
379
+ getAllThemes: async () => [],
380
+ getTheme: async () => undefined,
381
+ setTheme: async () => ({ success: false, error: "Theme changes are unavailable in ACP mode" }),
382
+ getToolsExpanded: () => false,
383
+ setToolsExpanded: () => {},
384
+ };
385
+ }
386
+
387
+ export class AcpAgent implements Agent {
388
+ #connection: AgentSideConnection;
389
+ #initialSession: AgentSession | undefined;
390
+ #createSession: CreateAcpSession;
391
+ #sessions = new Map<string, ManagedSessionRecord>();
392
+ #disposePromise: Promise<void> | undefined;
393
+ #cleanupRegistered = false;
394
+ #clientCapabilities: ClientCapabilities | undefined;
395
+ #cancelCleanupTimeoutMs = ACP_CANCEL_CLEANUP_TIMEOUT_MS;
396
+
397
+ constructor(connection: AgentSideConnection, createSession: CreateAcpSession, initialSession?: AgentSession) {
398
+ this.#connection = connection;
399
+ this.#initialSession = initialSession;
400
+ this.#createSession = createSession;
401
+ }
402
+
403
+ setCancelCleanupTimeoutForTesting(timeoutMs: number): void {
404
+ this.#cancelCleanupTimeoutMs = Math.max(1, timeoutMs);
405
+ }
406
+
407
+ async initialize(params: InitializeRequest): Promise<InitializeResponse> {
408
+ this.#registerConnectionCleanup();
409
+ this.#clientCapabilities = params.clientCapabilities;
410
+ const authMethods: AuthMethod[] = [
411
+ {
412
+ id: "agent",
413
+ name: "Use existing local credentials",
414
+ description: "Authenticate via the provider keys/OAuth state already configured under ~/.omp.",
415
+ },
416
+ ];
417
+ if (params.clientCapabilities?.auth?.terminal === true) {
418
+ authMethods.push({
419
+ type: "terminal",
420
+ id: "terminal",
421
+ name: "Set up Oh My Pi in terminal",
422
+ description: "Launch the omp TUI to add provider keys and select models.",
423
+ args: [ACP_TERMINAL_AUTH_FLAG],
424
+ });
425
+ }
426
+ return {
427
+ protocolVersion: PROTOCOL_VERSION,
428
+ agentInfo: {
429
+ name: "oh-my-pi",
430
+ title: "Oh My Pi",
431
+ version: VERSION,
432
+ },
433
+ authMethods,
434
+ agentCapabilities: {
435
+ loadSession: true,
436
+ mcpCapabilities: {
437
+ http: true,
438
+ sse: true,
439
+ },
440
+ promptCapabilities: {
441
+ embeddedContext: true,
442
+ image: true,
443
+ },
444
+ sessionCapabilities: {
445
+ list: {},
446
+ fork: {},
447
+ resume: {},
448
+ close: {},
449
+ },
450
+ },
451
+ };
452
+ }
453
+
454
+ async authenticate(params: AuthenticateRequest): Promise<AuthenticateResponse> {
455
+ // ACP spec: `methodId` must be one of the methods advertised by `initialize`.
456
+ // Reject anything else so malformed clients fail fast rather than appearing
457
+ // authenticated and surfacing a downstream model failure later.
458
+ const supportsTerminalAuth = this.#clientCapabilities?.auth?.terminal === true;
459
+ const validMethods = supportsTerminalAuth ? ["agent", "terminal"] : ["agent"];
460
+ if (!validMethods.includes(params.methodId)) {
461
+ throw new Error(`Unknown ACP auth method: ${params.methodId}`);
462
+ }
463
+ return {};
464
+ }
465
+
466
+ async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
467
+ this.#assertAbsoluteCwd(params.cwd);
468
+ const record = await this.#createNewSessionRecord(params.cwd, params.mcpServers);
469
+ const response: NewSessionResponse = {
470
+ sessionId: record.session.sessionId,
471
+ configOptions: this.#buildConfigOptions(record.session),
472
+ models: this.#buildModelState(record.session),
473
+ modes: this.#buildModeState(record.session),
474
+ };
475
+ this.#scheduleBootstrapUpdates(record.session.sessionId);
476
+ return response;
477
+ }
478
+
479
+ async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
480
+ this.#assertAbsoluteCwd(params.cwd);
481
+ const record = await this.#loadManagedSession(params.sessionId, params.cwd, params.mcpServers);
482
+ await this.#replaySessionHistory(record);
483
+ const response: LoadSessionResponse = {
484
+ configOptions: this.#buildConfigOptions(record.session),
485
+ models: this.#buildModelState(record.session),
486
+ modes: this.#buildModeState(record.session),
487
+ };
488
+ this.#scheduleBootstrapUpdates(record.session.sessionId);
489
+ return response;
490
+ }
491
+
492
+ async listSessions(params: ListSessionsRequest): Promise<ListSessionsResponse> {
493
+ if (params.cwd) {
494
+ this.#assertAbsoluteCwd(params.cwd);
495
+ }
496
+ for (const record of this.#sessions.values()) {
497
+ await record.session.sessionManager.flush();
498
+ }
499
+ const sessions = await this.#listStoredSessions(params.cwd ?? undefined);
500
+ const offset = this.#parseCursor(params.cursor ?? undefined);
501
+ const paged = sessions.slice(offset, offset + SESSION_PAGE_SIZE);
502
+ const nextOffset = offset + paged.length;
503
+ return {
504
+ sessions: paged.map(session => this.#toSessionInfo(session)),
505
+ nextCursor: nextOffset < sessions.length ? String(nextOffset) : undefined,
506
+ };
507
+ }
508
+
509
+ async resumeSession(params: ResumeSessionRequest): Promise<ResumeSessionResponse> {
510
+ this.#assertAbsoluteCwd(params.cwd);
511
+ const record = await this.#resumeManagedSession(params.sessionId, params.cwd, params.mcpServers ?? []);
512
+ const response: ResumeSessionResponse = {
513
+ configOptions: this.#buildConfigOptions(record.session),
514
+ models: this.#buildModelState(record.session),
515
+ modes: this.#buildModeState(record.session),
516
+ };
517
+ this.#scheduleBootstrapUpdates(record.session.sessionId);
518
+ return response;
519
+ }
520
+
521
+ async unstable_forkSession(params: ForkSessionRequest): Promise<ForkSessionResponse> {
522
+ this.#assertAbsoluteCwd(params.cwd);
523
+ const record = await this.#forkManagedSession(params);
524
+ const response: ForkSessionResponse = {
525
+ sessionId: record.session.sessionId,
526
+ configOptions: this.#buildConfigOptions(record.session),
527
+ models: this.#buildModelState(record.session),
528
+ modes: this.#buildModeState(record.session),
529
+ };
530
+ this.#scheduleBootstrapUpdates(record.session.sessionId);
531
+ return response;
532
+ }
533
+
534
+ async closeSession(params: CloseSessionRequest): Promise<CloseSessionResponse> {
535
+ const record = this.#sessions.get(params.sessionId);
536
+ if (!record) {
537
+ return {};
538
+ }
539
+ await this.#closeManagedSession(params.sessionId, record);
540
+ return {};
541
+ }
542
+
543
+ async setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse> {
544
+ const record = this.#getSessionRecord(params.sessionId);
545
+ this.#applyModeChange(record.session, params.modeId);
546
+ await this.#connection.sessionUpdate({
547
+ sessionId: record.session.sessionId,
548
+ update: this.#buildCurrentModeUpdate(record.session),
549
+ });
550
+ await this.#pushConfigOptionUpdate(record);
551
+ return {};
552
+ }
553
+
554
+ async setSessionConfigOption(params: SetSessionConfigOptionRequest): Promise<SetSessionConfigOptionResponse> {
555
+ const record = this.#getSessionRecord(params.sessionId);
556
+ if (typeof params.value === "boolean") {
557
+ throw new Error(`Unsupported boolean ACP config option: ${params.configId}`);
558
+ }
559
+
560
+ switch (params.configId) {
561
+ case MODE_CONFIG_ID:
562
+ this.#applyModeChange(record.session, params.value);
563
+ break;
564
+ case MODEL_CONFIG_ID:
565
+ await this.#setModelById(record.session, params.value);
566
+ break;
567
+ case THINKING_CONFIG_ID:
568
+ this.#setThinkingLevelById(record.session, params.value);
569
+ break;
570
+ default:
571
+ throw new Error(`Unknown ACP config option: ${params.configId}`);
572
+ }
573
+
574
+ // When mode is changed via the generic config-option API, mirror the
575
+ // `current_mode_update` notification that `setSessionMode` emits so
576
+ // ACP clients tracking session-mode state see a consistent transition.
577
+ if (params.configId === MODE_CONFIG_ID) {
578
+ await this.#connection.sessionUpdate({
579
+ sessionId: record.session.sessionId,
580
+ update: this.#buildCurrentModeUpdate(record.session),
581
+ });
582
+ }
583
+
584
+ // For `thinking` the lifetime subscription pushes post-bootstrap; only
585
+ // push here when it's not yet installed so pre-bootstrap callers still
586
+ // see the change without a post-bootstrap duplicate.
587
+ const thinkingHandledBySubscription =
588
+ params.configId === THINKING_CONFIG_ID && record.lifetimeUnsubscribe !== undefined;
589
+ if (!thinkingHandledBySubscription) {
590
+ await this.#pushConfigOptionUpdate(record);
591
+ }
592
+ return { configOptions: this.#buildConfigOptions(record.session) };
593
+ }
594
+
595
+ async unstable_setSessionModel(params: SetSessionModelRequest): Promise<SetSessionModelResponse> {
596
+ const record = this.#getSessionRecord(params.sessionId);
597
+ await this.#setModelById(record.session, params.modelId);
598
+ await this.#pushConfigOptionUpdate(record);
599
+ return {};
600
+ }
601
+
602
+ async prompt(params: PromptRequest): Promise<PromptResponse> {
603
+ const record = this.#getSessionRecord(params.sessionId);
604
+ const activeTurn = record.promptTurn;
605
+ if (activeTurn && !activeTurn.settled && record.session.isStreaming) {
606
+ // New prompt arrived while the previous turn is still in-flight (e.g. the
607
+ // client sent a message immediately after pressing stop, before or without
608
+ // a preceding session/cancel notification). Implicitly cancel the running
609
+ // turn so the new prompt can queue behind the abort cleanup — identical to
610
+ // what cancel() does when called explicitly. #beginCancelCleanup is
611
+ // idempotent, so a concurrent session/cancel notification is harmless.
612
+ // Mirror cancel()'s timeout handling: if abort() hangs past the cleanup
613
+ // timeout, close the managed session instead of leaving it registered
614
+ // with a still-streaming AgentSession. The queued prompt below observes
615
+ // the same cleanup rejection and fails accordingly.
616
+ this.#beginCancelCleanup(record, activeTurn).catch(async (error: unknown) => {
617
+ logger.warn("ACP cancel cleanup timed out; closing session", {
618
+ sessionId: record.session.sessionId,
619
+ error,
620
+ });
621
+ await this.#closeManagedSession(params.sessionId, record);
622
+ });
623
+ }
624
+ return await this.#queuePrompt(record, async () => {
625
+ const previousTurn = record.promptTurn;
626
+ if (previousTurn) {
627
+ // Wait for any prompt that's still settling or whose cancel cleanup is
628
+ // still in flight. We deliberately swallow the prompt rejection (the
629
+ // owning caller already received it) but let cleanup rejections
630
+ // propagate — a timed-out cancel must fail this queued prompt instead
631
+ // of letting it run on a session that is about to be closed.
632
+ await previousTurn.promise.catch(() => undefined);
633
+ await previousTurn.cleanup;
634
+ }
635
+ this.#throwIfRecordClosed(record);
636
+
637
+ const converted = this.#convertPromptBlocks(params.prompt);
638
+ const pendingPrompt = Promise.withResolvers<PromptResponse>();
639
+ record.promptTurn = {
640
+ userMessageId: params.messageId ?? crypto.randomUUID(),
641
+ cancelRequested: false,
642
+ settled: false,
643
+ cleanup: undefined,
644
+ usageBaseline: this.#cloneUsageStatistics(record.session.sessionManager.getUsageStatistics()),
645
+ unsubscribe: undefined,
646
+ resolve: pendingPrompt.resolve,
647
+ reject: pendingPrompt.reject,
648
+ promise: pendingPrompt.promise,
649
+ };
650
+
651
+ record.promptTurn.unsubscribe = record.session.subscribe(event => {
652
+ this.#trackPromptEvent(record, event);
653
+ });
654
+
655
+ this.#runPromptOrCommand(record, converted.text, converted.images).catch((error: unknown) => {
656
+ this.#finishPrompt(record, undefined, error);
657
+ });
658
+
659
+ return await pendingPrompt.promise;
660
+ });
661
+ }
662
+
663
+ async #queuePrompt(record: ManagedSessionRecord, run: () => Promise<PromptResponse>): Promise<PromptResponse> {
664
+ const nextQueue = Promise.withResolvers<void>();
665
+ const releaseQueue = nextQueue.resolve;
666
+ const previousQueue = record.promptQueue;
667
+ record.promptQueue = {
668
+ promise: nextQueue.promise,
669
+ release: releaseQueue,
670
+ };
671
+ await previousQueue.promise;
672
+ this.#throwIfRecordClosed(record);
673
+ try {
674
+ return await run();
675
+ } finally {
676
+ releaseQueue();
677
+ if (record.promptQueue.release === releaseQueue) {
678
+ record.promptQueue.release = undefined;
679
+ }
680
+ }
681
+ }
682
+
683
+ #throwIfRecordClosed(record: ManagedSessionRecord): void {
684
+ if (record.closedError) {
685
+ throw record.closedError;
686
+ }
687
+ }
688
+
689
+ #createPromptLifecycleError(message: string): PromptLifecycleError {
690
+ return Object.assign(new Error(message), { code: "ACP_SESSION_CLOSED" as const });
691
+ }
692
+
693
+ #trackPromptEvent(record: ManagedSessionRecord, event: AgentSessionEvent): void {
694
+ const handling = this.#handlePromptEvent(record, event).catch((error: unknown) => {
695
+ logger.warn("ACP prompt event handler failed", { error });
696
+ });
697
+ record.promptEventHandlers.add(handling);
698
+ void handling.finally(() => {
699
+ record.promptEventHandlers.delete(handling);
700
+ });
701
+ }
702
+
703
+ async #waitForPromptEventHandlers(record: ManagedSessionRecord): Promise<void> {
704
+ while (record.promptEventHandlers.size > 0) {
705
+ await Promise.allSettled(Array.from(record.promptEventHandlers));
706
+ }
707
+ }
708
+
709
+ #trackExtensionUserMessage(record: ManagedSessionRecord, task: Promise<void>): void {
710
+ const tracked = task.catch((error: unknown) => {
711
+ logger.warn("ACP extension sendUserMessage failed", { error });
712
+ });
713
+ record.extensionUserMessageTasks.add(tracked);
714
+ void tracked.finally(() => {
715
+ record.extensionUserMessageTasks.delete(tracked);
716
+ });
717
+ }
718
+
719
+ async #waitForExtensionUserMessages(
720
+ record: ManagedSessionRecord,
721
+ baseline: ReadonlySet<Promise<void>>,
722
+ ): Promise<void> {
723
+ while (true) {
724
+ const pending = Array.from(record.extensionUserMessageTasks).filter(task => !baseline.has(task));
725
+ if (pending.length === 0) {
726
+ return;
727
+ }
728
+ await Promise.allSettled(pending);
729
+ }
730
+ }
731
+
732
+ async #runPromptOrCommand(record: ManagedSessionRecord, text: string, images: AgentImageContent[]): Promise<void> {
733
+ const skillResult = await this.#tryRunSkillCommand(record, text);
734
+ if (skillResult) {
735
+ return;
736
+ }
737
+
738
+ const builtinResult = await executeAcpBuiltinSlashCommand(text, {
739
+ session: record.session,
740
+ sessionManager: record.session.sessionManager,
741
+ settings: record.session.settings,
742
+ cwd: record.session.sessionManager.getCwd(),
743
+ output: output => this.#emitCommandOutput(record, output),
744
+ refreshCommands: () => this.#emitAvailableCommandsUpdate(record),
745
+ reloadPlugins: () => this.#reloadPluginState(record),
746
+ notifyTitleChanged: async () => {
747
+ await this.#connection.sessionUpdate({
748
+ sessionId: record.session.sessionId,
749
+ update: {
750
+ sessionUpdate: "session_info_update",
751
+ title: record.session.sessionName,
752
+ updatedAt: new Date().toISOString(),
753
+ },
754
+ });
755
+ },
756
+ notifyConfigChanged: async () => {
757
+ await this.#pushConfigOptionUpdate(record);
758
+ },
759
+ });
760
+ if (builtinResult !== false) {
761
+ if ("prompt" in builtinResult) {
762
+ await record.session.prompt(builtinResult.prompt, { images });
763
+ return;
764
+ }
765
+ const promptTurn = record.promptTurn;
766
+ this.#finishPrompt(record, {
767
+ stopReason: "end_turn",
768
+ usage: this.#buildTurnUsage(
769
+ promptTurn?.usageBaseline ??
770
+ this.#cloneUsageStatistics(record.session.sessionManager.getUsageStatistics()),
771
+ record.session.sessionManager.getUsageStatistics(),
772
+ ),
773
+ userMessageId: promptTurn?.userMessageId,
774
+ });
775
+ return;
776
+ }
777
+
778
+ const extensionPromptBaseline = new Set(record.extensionUserMessageTasks);
779
+ const agentInvoked = await record.session.prompt(text, { images });
780
+ // Extension and custom-TS commands are handled locally inside session.prompt().
781
+ // An ACP extension command can still call pi.sendUserMessage(), which starts
782
+ // an async nested prompt through the extension runtime. Keep the ACP turn
783
+ // subscribed until those scheduled prompts and their event handlers drain;
784
+ // only then is `false` proof that the slash command was purely local.
785
+ if (!agentInvoked) {
786
+ await this.#waitForExtensionUserMessages(record, extensionPromptBaseline);
787
+ await this.#waitForPromptEventHandlers(record);
788
+ this.#finishPrompt(record, { stopReason: "end_turn" });
789
+ }
790
+ }
791
+
792
+ async #tryRunSkillCommand(record: ManagedSessionRecord, text: string): Promise<boolean> {
793
+ if (!text.startsWith("/skill:")) {
794
+ return false;
795
+ }
796
+ if (!record.session.skillsSettings?.enableSkillCommands) {
797
+ return false;
798
+ }
799
+ const spaceIndex = text.indexOf(" ");
800
+ const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
801
+ const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
802
+ const skillName = commandName.slice("skill:".length);
803
+ const skill = record.session.skills.find(candidate => candidate.name === skillName);
804
+ if (!skill) {
805
+ return false;
806
+ }
807
+ const built = await buildSkillPromptMessage(skill, args);
808
+ await record.session.promptCustomMessage({
809
+ customType: SKILL_PROMPT_MESSAGE_TYPE,
810
+ content: built.message,
811
+ display: true,
812
+ details: built.details,
813
+ attribution: "user",
814
+ });
815
+ return true;
816
+ }
817
+
818
+ async cancel(params: { sessionId: string }): Promise<void> {
819
+ const record = this.#getSessionRecord(params.sessionId);
820
+ const promptTurn = record.promptTurn;
821
+ if (!promptTurn || promptTurn.settled) {
822
+ return;
823
+ }
824
+ const cleanup = this.#beginCancelCleanup(record, promptTurn);
825
+ try {
826
+ await cleanup;
827
+ } catch (error: unknown) {
828
+ logger.warn("ACP cancel cleanup timed out; closing session", { sessionId: record.session.sessionId, error });
829
+ await this.#closeManagedSession(record.session.sessionId, record);
830
+ }
831
+ }
832
+
833
+ /**
834
+ * Transition a still-running turn into cancellation: mark intent, drop the live-event
835
+ * subscription, start the bounded `abort()` race, and resolve the ACP prompt response
836
+ * with `stopReason: "cancelled"` so the client sees acceptance immediately. The
837
+ * returned promise is the cleanup barrier — it resolves when `abort()` completes and
838
+ * rejects when the timeout fires. Idempotent: a second call returns the same barrier.
839
+ */
840
+ #beginCancelCleanup(record: ManagedSessionRecord, promptTurn: PromptTurnState): Promise<void> {
841
+ if (promptTurn.cleanup) {
842
+ return promptTurn.cleanup;
843
+ }
844
+ promptTurn.cancelRequested = true;
845
+ promptTurn.unsubscribe?.();
846
+ const cleanup = this.#runCancelCleanup(record, promptTurn);
847
+ promptTurn.cleanup = cleanup;
848
+ this.#finishPrompt(record, {
849
+ stopReason: "cancelled",
850
+ usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
851
+ userMessageId: promptTurn.userMessageId,
852
+ });
853
+ return cleanup;
854
+ }
855
+
856
+ async #runCancelCleanup(record: ManagedSessionRecord, promptTurn: PromptTurnState): Promise<void> {
857
+ let timer: NodeJS.Timeout | undefined;
858
+ const timeout = new Promise<never>((_, reject) => {
859
+ timer = setTimeout(() => reject(new Error("ACP cancel cleanup timed out")), this.#cancelCleanupTimeoutMs);
860
+ });
861
+ try {
862
+ await Promise.race([record.session.abort(), timeout]);
863
+ } finally {
864
+ if (timer) clearTimeout(timer);
865
+ // Order matters: clear `cleanup` before evicting the slot so the slot-eviction
866
+ // branch matches what `#finishPrompt` saw if it ran first.
867
+ promptTurn.cleanup = undefined;
868
+ if (promptTurn.settled && record.promptTurn === promptTurn) {
869
+ record.promptTurn = undefined;
870
+ }
871
+ }
872
+ }
873
+
874
+ async extMethod(method: string, params: { [key: string]: unknown }): Promise<{ [key: string]: unknown }> {
875
+ switch (method) {
876
+ case "_omp/sessions/listAll": {
877
+ const limit = typeof params.limit === "number" ? Math.max(1, Math.min(5000, params.limit as number)) : 1000;
878
+ const sessions = await SessionManager.listAll();
879
+ const sorted = sessions.sort((l, r) => r.modified.getTime() - l.modified.getTime()).slice(0, limit);
880
+ return {
881
+ sessions: sorted.map(s => this.#toSessionInfo(s)),
882
+ total: sessions.length,
883
+ };
884
+ }
885
+ case "_omp/projects/list": {
886
+ const sessions = await SessionManager.listAll();
887
+ const buckets = new Map<
888
+ string,
889
+ { cwd: string; sessionCount: number; lastActivityAt: number; lastTitle: string }
890
+ >();
891
+ for (const s of sessions) {
892
+ if (!s.cwd) continue;
893
+ const ts = s.modified.getTime();
894
+ const existing = buckets.get(s.cwd);
895
+ if (existing) {
896
+ existing.sessionCount += 1;
897
+ if (ts > existing.lastActivityAt) {
898
+ existing.lastActivityAt = ts;
899
+ existing.lastTitle = s.title ?? "";
900
+ }
901
+ } else {
902
+ buckets.set(s.cwd, {
903
+ cwd: s.cwd,
904
+ sessionCount: 1,
905
+ lastActivityAt: ts,
906
+ lastTitle: s.title ?? "",
907
+ });
908
+ }
909
+ }
910
+ const projects = Array.from(buckets.values()).sort((a, b) => b.lastActivityAt - a.lastActivityAt);
911
+ return { projects, totalSessions: sessions.length };
912
+ }
913
+ case "_omp/chats/byCwd": {
914
+ const cwd = typeof params.cwd === "string" ? (params.cwd as string) : undefined;
915
+ if (!cwd) throw new Error("cwd required");
916
+ const limit = typeof params.limit === "number" ? Math.max(1, Math.min(500, params.limit as number)) : 100;
917
+ const sessions = await SessionManager.list(cwd);
918
+ const sorted = sessions.sort((l, r) => r.modified.getTime() - l.modified.getTime()).slice(0, limit);
919
+ return { sessions: sorted.map(s => this.#toSessionInfo(s)) };
920
+ }
921
+ case "_omp/usage": {
922
+ const [firstRecord] = this.#sessions.values();
923
+ const target = firstRecord?.session ?? this.#initialSession;
924
+ if (!target) {
925
+ return { reports: [] };
926
+ }
927
+ const reports = await target.fetchUsageReports();
928
+ return { reports: reports ?? [] };
929
+ }
930
+ case "_omp/extensions": {
931
+ const cwd = typeof params.cwd === "string" ? (params.cwd as string) : undefined;
932
+ const sm = await Settings.init();
933
+ const disabledIds = (sm.get("disabledExtensions") as string[] | undefined) ?? [];
934
+ const extensions = await loadAllExtensions(cwd, disabledIds);
935
+ return { extensions: extensions as unknown as Array<{ [key: string]: unknown }> };
936
+ }
937
+ case "_omp/extensions/toggle": {
938
+ const providerId = params.providerId;
939
+ if (typeof providerId !== "string") throw new Error("providerId required");
940
+ if (params.enabled === false) {
941
+ disableProvider(providerId);
942
+ return { enabled: false };
943
+ }
944
+ enableProvider(providerId);
945
+ return { enabled: true };
946
+ }
947
+ default:
948
+ throw new Error(`Unknown ACP ext method: ${method}`);
949
+ }
950
+ }
951
+
952
+ async extNotification(_method: string, _params: { [key: string]: unknown }): Promise<void> {}
953
+
954
+ get signal(): AbortSignal {
955
+ return this.#connection.signal;
956
+ }
957
+
958
+ get closed(): Promise<void> {
959
+ return this.#connection.closed;
960
+ }
961
+
962
+ #registerConnectionCleanup(): void {
963
+ if (this.#cleanupRegistered) {
964
+ return;
965
+ }
966
+ this.#cleanupRegistered = true;
967
+ this.#connection.signal.addEventListener(
968
+ "abort",
969
+ () => {
970
+ void this.#disposeAllSessions();
971
+ },
972
+ { once: true },
973
+ );
974
+ }
975
+
976
+ async #createNewSessionRecord(cwd: string, mcpServers: McpServer[]): Promise<ManagedSessionRecord> {
977
+ const session = await this.#createSession(path.resolve(cwd));
978
+ try {
979
+ await session.sessionManager.ensureOnDisk();
980
+ } catch (error) {
981
+ await this.#disposeStandaloneSession(session);
982
+ throw error;
983
+ }
984
+ return await this.#registerPreparedSession(session, mcpServers);
985
+ }
986
+
987
+ async #loadManagedSession(sessionId: string, cwd: string, mcpServers: McpServer[]): Promise<ManagedSessionRecord> {
988
+ const existing = this.#sessions.get(sessionId);
989
+ if (existing) {
990
+ this.#assertMatchingCwd(existing.session, cwd);
991
+ await this.#configureMcpServers(existing, mcpServers);
992
+ return existing;
993
+ }
994
+
995
+ const storedSession = await this.#findStoredSession(sessionId, cwd);
996
+ if (!storedSession) {
997
+ throw new Error(`ACP session not found: ${sessionId}`);
998
+ }
999
+ return await this.#openStoredSession(storedSession.path, cwd, mcpServers, sessionId);
1000
+ }
1001
+
1002
+ async #resumeManagedSession(sessionId: string, cwd: string, mcpServers: McpServer[]): Promise<ManagedSessionRecord> {
1003
+ const existing = this.#sessions.get(sessionId);
1004
+ if (existing) {
1005
+ this.#assertMatchingCwd(existing.session, cwd);
1006
+ await this.#configureMcpServers(existing, mcpServers);
1007
+ return existing;
1008
+ }
1009
+
1010
+ const storedSession = await this.#findStoredSession(sessionId, cwd);
1011
+ if (!storedSession) {
1012
+ throw new Error(`ACP session not found: ${sessionId}`);
1013
+ }
1014
+ return await this.#openStoredSession(storedSession.path, cwd, mcpServers, sessionId);
1015
+ }
1016
+
1017
+ async #forkManagedSession(params: ForkSessionRequest): Promise<ManagedSessionRecord> {
1018
+ const sourcePath = await this.#resolveForkSourceSessionPath(params.sessionId);
1019
+ const session = await this.#createSession(path.resolve(params.cwd));
1020
+ try {
1021
+ const success = await session.switchSession(sourcePath);
1022
+ if (!success) {
1023
+ throw new Error(`ACP session fork was cancelled: ${params.sessionId}`);
1024
+ }
1025
+ const forked = await session.fork();
1026
+ if (!forked) {
1027
+ throw new Error(`ACP session fork failed: ${params.sessionId}`);
1028
+ }
1029
+ } catch (error) {
1030
+ await this.#disposeStandaloneSession(session);
1031
+ throw error;
1032
+ }
1033
+ return await this.#registerPreparedSession(session, params.mcpServers ?? []);
1034
+ }
1035
+
1036
+ async #openStoredSession(
1037
+ sessionPath: string,
1038
+ cwd: string,
1039
+ mcpServers: McpServer[],
1040
+ sessionId: string,
1041
+ ): Promise<ManagedSessionRecord> {
1042
+ const session = await this.#createSession(path.resolve(cwd));
1043
+ try {
1044
+ const success = await session.switchSession(sessionPath);
1045
+ if (!success) {
1046
+ throw new Error(`ACP session load was cancelled: ${sessionId}`);
1047
+ }
1048
+ } catch (error) {
1049
+ await this.#disposeStandaloneSession(session);
1050
+ throw error;
1051
+ }
1052
+ return await this.#registerPreparedSession(session, mcpServers);
1053
+ }
1054
+
1055
+ async #registerPreparedSession(session: AgentSession, mcpServers: McpServer[]): Promise<ManagedSessionRecord> {
1056
+ const record = this.#createManagedSessionRecord(session);
1057
+ session.setClientBridge(createAcpClientBridge(this.#connection, session.sessionId, this.#clientCapabilities));
1058
+ // `record.lifetimeUnsubscribe` is installed in `#scheduleBootstrapUpdates`
1059
+ // so it shares the bootstrap race guard — see that comment for why.
1060
+ try {
1061
+ await this.#configureExtensions(record);
1062
+ await this.#configureMcpServers(record, mcpServers);
1063
+ this.#sessions.set(session.sessionId, record);
1064
+ return record;
1065
+ } catch (error) {
1066
+ await this.#disposeSessionRecord(record);
1067
+ throw error;
1068
+ }
1069
+ }
1070
+
1071
+ #createManagedSessionRecord(session: AgentSession): ManagedSessionRecord {
1072
+ return {
1073
+ session,
1074
+ mcpManager: undefined,
1075
+ promptTurn: undefined,
1076
+ promptQueue: { promise: Promise.resolve(), release: undefined },
1077
+ liveMessageId: undefined,
1078
+ liveMessageProgress: undefined,
1079
+ toolArgsById: new Map(),
1080
+ extensionsConfigured: false,
1081
+ closedError: undefined,
1082
+ promptEventHandlers: new Set(),
1083
+ extensionUserMessageTasks: new Set(),
1084
+ lifetimeUnsubscribe: undefined,
1085
+ };
1086
+ }
1087
+
1088
+ async #handleLifetimeEvent(record: ManagedSessionRecord, event: AgentSessionEvent): Promise<void> {
1089
+ if (event.type !== "thinking_level_changed") {
1090
+ return;
1091
+ }
1092
+ try {
1093
+ await this.#pushConfigOptionUpdate(record);
1094
+ } catch (error) {
1095
+ logger.warn("Failed to push thinking-level config_option_update", {
1096
+ sessionId: record.session.sessionId,
1097
+ error,
1098
+ });
1099
+ }
1100
+ }
1101
+
1102
+ #getSessionRecord(sessionId: string): ManagedSessionRecord {
1103
+ const record = this.#sessions.get(sessionId);
1104
+ if (!record) {
1105
+ throw new Error(`Unsupported ACP session: ${sessionId}`);
1106
+ }
1107
+ return record;
1108
+ }
1109
+
1110
+ #assertMatchingCwd(session: AgentSession, cwd: string): void {
1111
+ const expected = path.resolve(cwd);
1112
+ const actual = path.resolve(session.sessionManager.getCwd());
1113
+ if (actual !== expected) {
1114
+ throw new Error(`ACP session ${session.sessionId} is already loaded for ${actual}, not ${expected}`);
1115
+ }
1116
+ }
1117
+
1118
+ async #resolveForkSourceSessionPath(sessionId: string): Promise<string> {
1119
+ const loaded = this.#sessions.get(sessionId);
1120
+ if (loaded) {
1121
+ if (isPromptTurnInFlight(loaded.promptTurn)) {
1122
+ throw new Error(`ACP session fork is unavailable while a prompt is in progress: ${sessionId}`);
1123
+ }
1124
+ await loaded.session.sessionManager.flush();
1125
+ const sessionPath = loaded.session.sessionManager.getSessionFile();
1126
+ if (!sessionPath) {
1127
+ throw new Error(`ACP session cannot be forked before it is persisted: ${sessionId}`);
1128
+ }
1129
+ return sessionPath;
1130
+ }
1131
+
1132
+ const storedSession = await this.#findStoredSessionById(sessionId);
1133
+ if (!storedSession) {
1134
+ throw new Error(`ACP session not found: ${sessionId}`);
1135
+ }
1136
+ return storedSession.path;
1137
+ }
1138
+
1139
+ async #handlePromptEvent(record: ManagedSessionRecord, event: AgentSessionEvent): Promise<void> {
1140
+ const promptTurn = record.promptTurn;
1141
+ if (!promptTurn || promptTurn.settled || promptTurn.cancelRequested) {
1142
+ return;
1143
+ }
1144
+
1145
+ if (event.type === "tool_execution_start" || event.type === "tool_execution_update") {
1146
+ record.toolArgsById.set(event.toolCallId, event.args);
1147
+ }
1148
+
1149
+ this.#prepareLiveAssistantMessage(record, event);
1150
+ for (const notification of mapAgentSessionEventToAcpSessionUpdates(event, record.session.sessionId, {
1151
+ getMessageId: message => this.#getLiveMessageId(record, message),
1152
+ getMessageProgress: message => this.#getLiveMessageProgress(record, message),
1153
+ getToolArgs: toolCallId => record.toolArgsById.get(toolCallId),
1154
+ cwd: record.session.sessionManager.getCwd(),
1155
+ })) {
1156
+ await this.#connection.sessionUpdate(notification);
1157
+ }
1158
+ if (event.type === "tool_execution_end") {
1159
+ record.toolArgsById.delete(event.toolCallId);
1160
+ }
1161
+ this.#clearLiveAssistantMessageAfterEvent(record, event);
1162
+
1163
+ if (event.type === "agent_end") {
1164
+ await this.#emitEndOfTurnUpdates(record);
1165
+ await this.#waitForAcpPromptIdle(record);
1166
+ this.#finishPrompt(record, {
1167
+ stopReason: this.#resolveStopReason(event, promptTurn.cancelRequested),
1168
+ usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
1169
+ userMessageId: promptTurn.userMessageId,
1170
+ });
1171
+ }
1172
+ }
1173
+
1174
+ async #waitForAcpPromptIdle(record: ManagedSessionRecord): Promise<void> {
1175
+ for (let pass = 0; pass < ACP_ASYNC_DELIVERY_DRAIN_MAX_PASSES; pass++) {
1176
+ await record.session.waitForIdle();
1177
+ const delivered = await record.session.drainAsyncJobDeliveriesForAcp({
1178
+ timeoutMs: ACP_ASYNC_DELIVERY_DRAIN_TIMEOUT_MS,
1179
+ });
1180
+ if (!delivered) {
1181
+ return;
1182
+ }
1183
+ }
1184
+
1185
+ await record.session.waitForIdle();
1186
+ }
1187
+
1188
+ #prepareLiveAssistantMessage(record: ManagedSessionRecord, event: AgentSessionEvent): void {
1189
+ if (
1190
+ (event.type === "message_start" || event.type === "message_update" || event.type === "message_end") &&
1191
+ event.message.role === "assistant" &&
1192
+ (event.type === "message_start" || !record.liveMessageId || !record.liveMessageProgress)
1193
+ ) {
1194
+ record.liveMessageId = crypto.randomUUID();
1195
+ record.liveMessageProgress = { textEmitted: false, thoughtEmitted: false };
1196
+ }
1197
+ }
1198
+
1199
+ #clearLiveAssistantMessageAfterEvent(record: ManagedSessionRecord, event: AgentSessionEvent): void {
1200
+ if ((event.type === "message_end" && event.message.role === "assistant") || event.type === "agent_end") {
1201
+ record.liveMessageId = undefined;
1202
+ record.liveMessageProgress = undefined;
1203
+ }
1204
+ }
1205
+
1206
+ #getLiveMessageId(record: ManagedSessionRecord, message: unknown): string | undefined {
1207
+ if (typeof message !== "object" || message === null) {
1208
+ return undefined;
1209
+ }
1210
+ record.liveMessageId ??= crypto.randomUUID();
1211
+ return record.liveMessageId;
1212
+ }
1213
+
1214
+ #getLiveMessageProgress(
1215
+ record: ManagedSessionRecord,
1216
+ message: unknown,
1217
+ ): { textEmitted: boolean; thoughtEmitted: boolean } | undefined {
1218
+ if (typeof message !== "object" || message === null) {
1219
+ return undefined;
1220
+ }
1221
+ record.liveMessageProgress ??= { textEmitted: false, thoughtEmitted: false };
1222
+ return record.liveMessageProgress;
1223
+ }
1224
+
1225
+ #finishPrompt(record: ManagedSessionRecord, response?: PromptResponse, error?: unknown): void {
1226
+ const promptTurn = record.promptTurn;
1227
+ if (!promptTurn || promptTurn.settled) {
1228
+ return;
1229
+ }
1230
+ promptTurn.settled = true;
1231
+ promptTurn.unsubscribe?.();
1232
+ // Keep the slot occupied until cancel cleanup finishes — `#runCancelCleanup`
1233
+ // evicts the slot in its finally block once both flags say it's safe.
1234
+ if (!promptTurn.cleanup && record.promptTurn === promptTurn) {
1235
+ record.promptTurn = undefined;
1236
+ }
1237
+ if (error !== undefined) {
1238
+ promptTurn.reject(error);
1239
+ return;
1240
+ }
1241
+ promptTurn.resolve(response ?? { stopReason: "end_turn" });
1242
+ }
1243
+
1244
+ #resolveStopReason(
1245
+ event: Extract<AgentSessionEvent, { type: "agent_end" }>,
1246
+ cancelRequested: boolean,
1247
+ ): PromptResponse["stopReason"] {
1248
+ if (cancelRequested) {
1249
+ return "cancelled";
1250
+ }
1251
+ const lastAssistant = [...event.messages]
1252
+ .reverse()
1253
+ .find((message): message is AssistantMessage => message.role === "assistant");
1254
+ const reason = lastAssistant?.stopReason;
1255
+ switch (reason) {
1256
+ case "aborted":
1257
+ return "cancelled";
1258
+ case "length":
1259
+ return "max_tokens";
1260
+ case "error": {
1261
+ const errorMessage = lastAssistant?.errorMessage ?? "";
1262
+ if (/content[_ ]?filter|refus(al|ed)/i.test(errorMessage)) {
1263
+ return "refusal";
1264
+ }
1265
+ return "end_turn";
1266
+ }
1267
+ default:
1268
+ return "end_turn";
1269
+ }
1270
+ }
1271
+
1272
+ async #emitCommandOutput(record: ManagedSessionRecord, text: string): Promise<void> {
1273
+ if (!text) {
1274
+ return;
1275
+ }
1276
+ await this.#connection.sessionUpdate({
1277
+ sessionId: record.session.sessionId,
1278
+ update: {
1279
+ sessionUpdate: "agent_message_chunk",
1280
+ content: { type: "text", text },
1281
+ messageId: crypto.randomUUID(),
1282
+ },
1283
+ });
1284
+ }
1285
+
1286
+ #assertAbsoluteCwd(cwd: string): void {
1287
+ if (!path.isAbsolute(cwd)) {
1288
+ throw new Error(`ACP cwd must be absolute: ${cwd}`);
1289
+ }
1290
+ }
1291
+
1292
+ #convertPromptBlocks(blocks: PromptRequest["prompt"]): { text: string; images: AgentImageContent[] } {
1293
+ const textParts: string[] = [];
1294
+ const images: AgentImageContent[] = [];
1295
+ for (const block of blocks) {
1296
+ switch (block.type) {
1297
+ case "text":
1298
+ textParts.push(block.text);
1299
+ break;
1300
+ case "image":
1301
+ images.push({ type: "image", data: block.data, mimeType: block.mimeType });
1302
+ break;
1303
+ case "resource":
1304
+ if ("text" in block.resource) {
1305
+ textParts.push(block.resource.text);
1306
+ } else if (typeof block.resource.mimeType === "string" && block.resource.mimeType.startsWith("image/")) {
1307
+ // `embeddedContext: true` covers both text and blob resources, but
1308
+ // blobs aren't directly consumable by the LLM. Route image blobs
1309
+ // to the images array so the user's intent survives; everything
1310
+ // else falls back to the URI placeholder below.
1311
+ images.push({ type: "image", data: block.resource.blob, mimeType: block.resource.mimeType });
1312
+ } else {
1313
+ textParts.push(`[embedded resource: ${block.resource.uri}]`);
1314
+ }
1315
+ break;
1316
+ case "resource_link":
1317
+ textParts.push(block.title ?? block.name ?? block.uri);
1318
+ break;
1319
+ case "audio":
1320
+ textParts.push("[audio omitted]");
1321
+ break;
1322
+ }
1323
+ }
1324
+ return {
1325
+ text: textParts.join("\n\n").trim(),
1326
+ images,
1327
+ };
1328
+ }
1329
+
1330
+ async #pushConfigOptionUpdate(record: ManagedSessionRecord): Promise<void> {
1331
+ await this.#pushConfigOptionUpdateForSession(record.session);
1332
+ }
1333
+
1334
+ async #pushConfigOptionUpdateForSession(session: AgentSession): Promise<void> {
1335
+ await this.#connection.sessionUpdate({
1336
+ sessionId: session.sessionId,
1337
+ update: {
1338
+ sessionUpdate: "config_option_update",
1339
+ configOptions: this.#buildConfigOptions(session),
1340
+ },
1341
+ });
1342
+ }
1343
+
1344
+ #buildConfigOptions(session: AgentSession): SessionConfigOption[] {
1345
+ const currentModeId = this.#getCurrentModeId(session);
1346
+ const modeOptions = this.#getAvailableModes(session).map(mode => ({
1347
+ value: mode.id,
1348
+ name: mode.name,
1349
+ description: mode.description,
1350
+ }));
1351
+ const configOptions: SessionConfigOption[] = [
1352
+ {
1353
+ id: MODE_CONFIG_ID,
1354
+ name: "Mode",
1355
+ category: "mode",
1356
+ type: "select",
1357
+ currentValue: currentModeId,
1358
+ options: modeOptions,
1359
+ },
1360
+ ];
1361
+
1362
+ const models = session.getAvailableModels();
1363
+ const currentModel = session.model;
1364
+ if (models.length > 0) {
1365
+ configOptions.push({
1366
+ id: MODEL_CONFIG_ID,
1367
+ name: "Model",
1368
+ category: "model",
1369
+ type: "select",
1370
+ currentValue: currentModel ? this.#toModelId(currentModel) : this.#toModelId(models[0]),
1371
+ options: models.map(model => ({
1372
+ value: this.#toModelId(model),
1373
+ name: model.name,
1374
+ description: `${model.provider}/${model.id}`,
1375
+ })),
1376
+ });
1377
+ }
1378
+
1379
+ configOptions.push({
1380
+ id: THINKING_CONFIG_ID,
1381
+ name: "Thinking",
1382
+ category: "thought_level",
1383
+ type: "select",
1384
+ currentValue: this.#toThinkingConfigValue(
1385
+ session.model?.reasoning ? this.#getConfiguredThinkingLevel(session) : undefined,
1386
+ ),
1387
+ options: this.#buildThinkingOptions(session),
1388
+ });
1389
+ return configOptions;
1390
+ }
1391
+
1392
+ #buildModelState(session: AgentSession): SessionModelState | undefined {
1393
+ const models = session.getAvailableModels();
1394
+ if (models.length === 0) {
1395
+ return undefined;
1396
+ }
1397
+
1398
+ const availableModels = models.map(model => ({
1399
+ modelId: this.#toModelId(model),
1400
+ name: model.name,
1401
+ description: `${model.provider}/${model.id}`,
1402
+ }));
1403
+ const currentModelId = session.model ? this.#toModelId(session.model) : availableModels[0]?.modelId;
1404
+ if (!currentModelId) {
1405
+ return undefined;
1406
+ }
1407
+
1408
+ return {
1409
+ availableModels,
1410
+ currentModelId,
1411
+ };
1412
+ }
1413
+
1414
+ #buildThinkingOptions(session: AgentSession): Array<{ value: string; name: string; description?: string }> {
1415
+ return [
1416
+ { value: THINKING_OFF, name: "Off" },
1417
+ { value: AUTO_THINKING, name: "Auto", description: "Auto-detect per prompt (low–xhigh)" },
1418
+ ...session.getAvailableThinkingLevels().map(level => ({
1419
+ value: level,
1420
+ name: level,
1421
+ })),
1422
+ ];
1423
+ }
1424
+ #getConfiguredThinkingLevel(session: AgentSession): string | undefined {
1425
+ const configuredThinkingLevel = (session as { configuredThinkingLevel?: () => string | undefined })
1426
+ .configuredThinkingLevel;
1427
+ return typeof configuredThinkingLevel === "function"
1428
+ ? configuredThinkingLevel.call(session)
1429
+ : session.thinkingLevel;
1430
+ }
1431
+
1432
+ #toThinkingConfigValue(value: string | undefined): string {
1433
+ return value && value !== "inherit" ? value : THINKING_OFF;
1434
+ }
1435
+
1436
+ async #setModelById(session: AgentSession, modelId: string): Promise<void> {
1437
+ const model = session.getAvailableModels().find(candidate => this.#toModelId(candidate) === modelId);
1438
+ if (!model) {
1439
+ throw new Error(`Unknown ACP model: ${modelId}`);
1440
+ }
1441
+ await session.setModel(model);
1442
+ }
1443
+
1444
+ #setThinkingLevelById(session: AgentSession, value: string): void {
1445
+ const thinkingLevel = parseConfiguredThinkingLevel(value);
1446
+ if (!thinkingLevel) {
1447
+ throw new Error(`Unknown ACP thinking level: ${value}`);
1448
+ }
1449
+ session.setThinkingLevel(thinkingLevel);
1450
+ }
1451
+
1452
+ #toModelId(model: Model): string {
1453
+ return `${model.provider}/${model.id}`;
1454
+ }
1455
+
1456
+ #getAvailableModes(session: AgentSession): Array<{ id: string; name: string; description: string }> {
1457
+ const modes = [{ id: ACP_DEFAULT_MODE_ID, name: "Default", description: "Standard ACP headless mode" }];
1458
+ if (session.settings.get("plan.enabled")) {
1459
+ modes.push({
1460
+ id: ACP_PLAN_MODE_ID,
1461
+ name: "Plan",
1462
+ description: "Read-only planning mode that drafts a plan to a markdown file before any code changes",
1463
+ });
1464
+ }
1465
+ void session;
1466
+ return modes;
1467
+ }
1468
+
1469
+ #getCurrentModeId(session: AgentSession): string {
1470
+ return session.getPlanModeState()?.enabled ? ACP_PLAN_MODE_ID : ACP_DEFAULT_MODE_ID;
1471
+ }
1472
+
1473
+ #applyModeChange(session: AgentSession, modeId: string): void {
1474
+ const availableModes = this.#getAvailableModes(session);
1475
+ if (!availableModes.some(mode => mode.id === modeId)) {
1476
+ throw new Error(`Unsupported ACP mode: ${modeId}`);
1477
+ }
1478
+ if (modeId === ACP_PLAN_MODE_ID) {
1479
+ const previous = session.getPlanModeState();
1480
+ session.setPlanModeState({
1481
+ enabled: true,
1482
+ planFilePath: previous?.planFilePath ?? DEFAULT_PLAN_FILE_URL,
1483
+ workflow: previous?.workflow ?? "parallel",
1484
+ reentry: previous !== undefined,
1485
+ });
1486
+ // Mirror `InteractiveMode.#enterPlanMode`: register the standing resolve
1487
+ // handler that consumes `resolve { action: "apply" }` from plan-mode.
1488
+ // Without this, the agent's resolve call falls through to the "No
1489
+ // pending action to resolve" error (issue #1869).
1490
+ session.setStandingResolveHandler?.(input => this.#runAcpPlanApprovalResolve(session, input));
1491
+ } else {
1492
+ session.setStandingResolveHandler?.(null);
1493
+ session.setPlanModeState(undefined);
1494
+ }
1495
+ }
1496
+
1497
+ /**
1498
+ * Standing resolve handler installed while ACP plan mode is active. The agent
1499
+ * submits the finalized plan via `resolve { action: "apply", extra: { title } }`;
1500
+ * this handler validates the plan file, normalizes the title, asks the ACP
1501
+ * client to confirm (via `unstable_createElicitation` when supported), and on
1502
+ * approval renames the plan to `local://<title>.md`, exits plan mode, and
1503
+ * notifies the client of both mode surfaces so the agent regains full tools.
1504
+ *
1505
+ * Mirrors `InteractiveMode.#runPlanApprovalResolve` for the parts the agent
1506
+ * sees (same `PlanApprovalDetails` shape, same source tool name `plan_approval`).
1507
+ * Clients without form-mode elicitation get an auto-approve so plan mode is
1508
+ * never stranded — the agent always has a way out.
1509
+ */
1510
+ #runAcpPlanApprovalResolve(session: AgentSession, input: unknown): Promise<AgentToolResult<unknown>> {
1511
+ return runResolveInvocation(input as Parameters<typeof runResolveInvocation>[0], {
1512
+ sourceToolName: "plan_approval",
1513
+ label: "Plan ready for approval",
1514
+ apply: async (_reason, extra) => {
1515
+ const state = session.getPlanModeState();
1516
+ if (!state?.enabled) {
1517
+ throw new ToolError("Plan mode is not active.");
1518
+ }
1519
+ const { planFilePath, planContent, title } = await resolveApprovedPlan({
1520
+ suppliedTitle: extra?.title,
1521
+ statePlanFilePath: state.planFilePath,
1522
+ readPlan: url => this.#readAcpPlanFile(session, url),
1523
+ listPlanFiles: () => this.#listAcpLocalPlanFiles(session),
1524
+ });
1525
+ const approved = await this.#requestAcpPlanApprovalChoice(session.sessionId, title, planContent);
1526
+ const details: PlanApprovalDetails = {
1527
+ planFilePath,
1528
+ title,
1529
+ planExists: true,
1530
+ };
1531
+ if (!approved) {
1532
+ // User chose to refine: leave plan mode active so the agent
1533
+ // keeps the read-only toolset and can iterate on the plan file.
1534
+ return {
1535
+ content: [
1536
+ {
1537
+ type: "text" as const,
1538
+ text: 'Plan refinement requested. Update the plan file, then call `resolve { action: "apply" }` again when ready.',
1539
+ },
1540
+ ],
1541
+ details,
1542
+ };
1543
+ }
1544
+ // Approved. Set the plan reference so the next turn injects the plan
1545
+ // content as context (the file keeps its agent-chosen name — no
1546
+ // rename), then exit plan mode so the agent regains full tools.
1547
+ session.setPlanReferencePath(planFilePath);
1548
+ session.setStandingResolveHandler?.(null);
1549
+ session.setPlanModeState(undefined);
1550
+ try {
1551
+ await this.#connection.sessionUpdate({
1552
+ sessionId: session.sessionId,
1553
+ update: this.#buildCurrentModeUpdate(session),
1554
+ });
1555
+ await this.#pushConfigOptionUpdateForSession(session);
1556
+ } catch (error) {
1557
+ logger.warn("Failed to emit mode updates after plan approval", {
1558
+ sessionId: session.sessionId,
1559
+ error,
1560
+ });
1561
+ }
1562
+ return {
1563
+ content: [
1564
+ {
1565
+ type: "text" as const,
1566
+ text: `Plan approved at ${planFilePath}. Plan mode exited; proceed with the implementation.`,
1567
+ },
1568
+ ],
1569
+ details,
1570
+ };
1571
+ },
1572
+ });
1573
+ }
1574
+
1575
+ #resolveAcpPlanFilePath(session: AgentSession, planFilePath: string): string {
1576
+ if (planFilePath.startsWith("local:")) {
1577
+ const normalized = normalizeLocalScheme(planFilePath);
1578
+ return resolveLocalUrlToPath(normalized, {
1579
+ getArtifactsDir: () => session.sessionManager.getArtifactsDir(),
1580
+ getSessionId: () => session.sessionManager.getSessionId(),
1581
+ });
1582
+ }
1583
+ return path.resolve(session.sessionManager.getCwd(), planFilePath);
1584
+ }
1585
+
1586
+ async #readAcpPlanFile(session: AgentSession, planFilePath: string): Promise<string | null> {
1587
+ const resolvedPath = this.#resolveAcpPlanFilePath(session, planFilePath);
1588
+ try {
1589
+ return await Bun.file(resolvedPath).text();
1590
+ } catch (error) {
1591
+ if (isEnoent(error)) {
1592
+ return null;
1593
+ }
1594
+ throw error;
1595
+ }
1596
+ }
1597
+
1598
+ /** `local://` URLs of plan files in the session-local root, newest first —
1599
+ * the `resolveApprovedPlan` fallback for a dropped `extra.title`. */
1600
+ async #listAcpLocalPlanFiles(session: AgentSession): Promise<string[]> {
1601
+ const localRoot = this.#resolveAcpPlanFilePath(session, "local://");
1602
+ try {
1603
+ const entries = await fs.readdir(localRoot, { withFileTypes: true });
1604
+ const plans = await Promise.all(
1605
+ entries
1606
+ .filter(entry => entry.isFile() && /plan\.md$/i.test(entry.name))
1607
+ .map(async entry => {
1608
+ const stat = await fs.stat(path.join(localRoot, entry.name)).catch(() => null);
1609
+ return { url: `local://${entry.name}`, mtime: stat?.mtimeMs ?? 0 };
1610
+ }),
1611
+ );
1612
+ return plans.sort((a, b) => b.mtime - a.mtime).map(plan => plan.url);
1613
+ } catch {
1614
+ return [];
1615
+ }
1616
+ }
1617
+
1618
+ /**
1619
+ * Ask the ACP client to confirm plan approval. Returns `true` only on an
1620
+ * explicit `APPROVE_OPTION` selection. Refine, dismissal (`undefined`), or
1621
+ * any unrecognized value falls through to refine semantics — the caller
1622
+ * keeps plan mode active and surfaces guidance text to the agent. Clients
1623
+ * without `elicitation.form` support auto-approve because there is no
1624
+ * confirmation surface available; without that, plan mode would strand
1625
+ * the agent (the bug this method exists to fix).
1626
+ */
1627
+ async #requestAcpPlanApprovalChoice(sessionId: string, title: string, planContent: string): Promise<boolean> {
1628
+ const supportsForm = this.#clientCapabilities?.elicitation?.form != null;
1629
+ if (!supportsForm) return true;
1630
+ // Include a short preview of the plan so the user has context in the
1631
+ // dialog. Keep the body bounded — Zed renders elicitation messages
1632
+ // inline and a multi-thousand-line plan blows out the dialog.
1633
+ const previewLines = planContent.split("\n").slice(0, 12).join("\n");
1634
+ const ellipsis = planContent.split("\n").length > 12 ? "\n…" : "";
1635
+ const message = `Approve plan "${title}" and start implementation?\n\n${previewLines}${ellipsis}`;
1636
+ const value = await elicitFromAcpClient(
1637
+ this.#connection,
1638
+ sessionId,
1639
+ "select",
1640
+ message,
1641
+ { type: "string", enum: [APPROVE_OPTION, REFINE_OPTION] },
1642
+ undefined,
1643
+ );
1644
+ // Approve ONLY on the explicit approve selection. Dismissal, cancel,
1645
+ // timeout, or any other non-approve response falls through to refine
1646
+ // semantics so closing the dialog can never grant write access.
1647
+ return value === APPROVE_OPTION;
1648
+ }
1649
+
1650
+ #buildModeState(session: AgentSession): SessionModeState {
1651
+ return {
1652
+ availableModes: this.#getAvailableModes(session),
1653
+ currentModeId: this.#getCurrentModeId(session),
1654
+ };
1655
+ }
1656
+
1657
+ #buildCurrentModeUpdate(session: AgentSession): SessionUpdate {
1658
+ return {
1659
+ sessionUpdate: "current_mode_update",
1660
+ currentModeId: this.#getCurrentModeId(session),
1661
+ };
1662
+ }
1663
+
1664
+ async #buildAvailableCommands(session: AgentSession): Promise<AvailableCommand[]> {
1665
+ const commands: AvailableCommand[] = [];
1666
+ const seenNames = new Set<string>();
1667
+ const appendCommand = (command: AvailableCommand): void => {
1668
+ if (seenNames.has(command.name)) {
1669
+ return;
1670
+ }
1671
+ seenNames.add(command.name);
1672
+ commands.push(command);
1673
+ };
1674
+
1675
+ // Advertise in the order dispatch resolves them (mirrors AgentSession
1676
+ // dispatch: builtins → skills → extensions → custom TS → file-based).
1677
+ // `appendCommand` dedupes by name so earlier entries win; extension
1678
+ // commands therefore correctly shadow custom TS commands of the same
1679
+ // name, matching the runtime behaviour of #tryExecuteExtensionCommand
1680
+ // running before #tryExecuteCustomCommand.
1681
+ for (const command of ACP_BUILTIN_SLASH_COMMANDS) {
1682
+ appendCommand(command);
1683
+ }
1684
+
1685
+ if (session.skillsSettings?.enableSkillCommands) {
1686
+ for (const skill of session.skills) {
1687
+ appendCommand({
1688
+ name: getSkillSlashCommandName(skill),
1689
+ description: skill.description || `Run ${skill.name} skill`,
1690
+ input: { hint: "arguments" },
1691
+ });
1692
+ }
1693
+ }
1694
+
1695
+ for (const command of session.extensionRunner?.getRegisteredCommands(ACP_BUILTIN_RESERVED_NAMES) ?? []) {
1696
+ // Reserved-set filtering in getRegisteredCommands only covers exact
1697
+ // names; colon-namespaced names whose prefix is a builtin (e.g.
1698
+ // `model:foo`) would still dispatch to the builtin in ACP.
1699
+ if (isAcpBuiltinShadowedName(command.name)) {
1700
+ continue;
1701
+ }
1702
+ appendCommand({
1703
+ name: command.name,
1704
+ description: command.description ?? "(extension command)",
1705
+ input: { hint: "arguments" },
1706
+ });
1707
+ }
1708
+
1709
+ for (const command of session.customCommands) {
1710
+ appendCommand({
1711
+ name: command.command.name,
1712
+ description: command.command.description,
1713
+ input: { hint: "arguments" },
1714
+ });
1715
+ }
1716
+
1717
+ for (const command of await loadSlashCommands({ cwd: session.sessionManager.getCwd() })) {
1718
+ appendCommand({
1719
+ name: command.name,
1720
+ description: command.description,
1721
+ });
1722
+ }
1723
+
1724
+ return commands;
1725
+ }
1726
+
1727
+ #toSessionInfo(session: StoredSessionInfo): SessionInfo {
1728
+ return {
1729
+ sessionId: session.id,
1730
+ cwd: session.cwd,
1731
+ title: session.title,
1732
+ updatedAt: session.modified.toISOString(),
1733
+ _meta: {
1734
+ messageCount: session.messageCount,
1735
+ size: session.size,
1736
+ },
1737
+ };
1738
+ }
1739
+
1740
+ #scheduleBootstrapUpdates(sessionId: string): void {
1741
+ // Defer first notifications until the response has reached the client.
1742
+ // Zed's agent-client-protocol reader dispatches responses and
1743
+ // notifications to different async tasks; sending the first
1744
+ // `available_commands_update` from `setTimeout(0)` reliably loses the
1745
+ // race against the response handler and Zed logs `Received session
1746
+ // notification for unknown session` then drops the update — leaving
1747
+ // the slash-command palette empty (#1015 follow-up; see
1748
+ // zed-industries/zed#55965 for the same race biting other ACP agents).
1749
+ // `ACP_BOOTSTRAP_RACE_GUARD_MS` is invisible to the operator and large
1750
+ // enough that the response future has scheduled before our timer fires
1751
+ // on stdio-only transports.
1752
+ //
1753
+ // The session-lifetime subscription is installed inside the same timer
1754
+ // so it shares this guard — without it, an extension's `session_start`
1755
+ // handler (or any async work it schedules) calling `setThinkingLevel`
1756
+ // would push a `config_option_update` for a session id the client
1757
+ // hasn't been told about yet. The pre-bootstrap thinking level is
1758
+ // reported in the response's `configOptions`, so deferring the
1759
+ // notification loses no state.
1760
+ setTimeout(() => {
1761
+ if (this.#connection.signal.aborted) {
1762
+ return;
1763
+ }
1764
+ const record = this.#sessions.get(sessionId);
1765
+ if (!record) {
1766
+ return;
1767
+ }
1768
+ if (!record.lifetimeUnsubscribe) {
1769
+ record.lifetimeUnsubscribe = record.session.subscribe(event => {
1770
+ void this.#handleLifetimeEvent(record, event);
1771
+ });
1772
+ }
1773
+ void this.#emitBootstrapUpdates(sessionId, record);
1774
+ }, ACP_BOOTSTRAP_RACE_GUARD_MS);
1775
+ }
1776
+
1777
+ async #emitBootstrapUpdates(sessionId: string, record: ManagedSessionRecord): Promise<void> {
1778
+ if (this.#sessions.get(sessionId) !== record) {
1779
+ return;
1780
+ }
1781
+ await this.#connection.sessionUpdate({
1782
+ sessionId,
1783
+ update: {
1784
+ sessionUpdate: "available_commands_update",
1785
+ availableCommands: await this.#buildAvailableCommands(record.session),
1786
+ },
1787
+ });
1788
+ await this.#connection.sessionUpdate({
1789
+ sessionId,
1790
+ update: {
1791
+ sessionUpdate: "session_info_update",
1792
+ title: record.session.sessionName,
1793
+ updatedAt: record.session.sessionManager.getHeader()?.timestamp,
1794
+ },
1795
+ });
1796
+ }
1797
+
1798
+ async #emitAvailableCommandsUpdate(record: ManagedSessionRecord): Promise<void> {
1799
+ await this.#connection.sessionUpdate({
1800
+ sessionId: record.session.sessionId,
1801
+ update: {
1802
+ sessionUpdate: "available_commands_update",
1803
+ availableCommands: await this.#buildAvailableCommands(record.session),
1804
+ },
1805
+ });
1806
+ }
1807
+
1808
+ /**
1809
+ * Reload plugin/registry state for an ACP session. Mirrors the interactive
1810
+ * `/reload-plugins` and `/move` flows: invalidates the plugin-roots cache,
1811
+ * resets the capability cache, refreshes the session's slash-command state,
1812
+ * then re-advertises commands so the client sees newly installed/disabled
1813
+ * plugins.
1814
+ */
1815
+ async #reloadPluginState(record: ManagedSessionRecord): Promise<void> {
1816
+ const cwd = record.session.sessionManager.getCwd();
1817
+ const projectPath = await resolveActiveProjectRegistryPath(cwd);
1818
+ clearPluginRootsAndCaches(projectPath ? [projectPath] : undefined);
1819
+ resetCapabilities();
1820
+ const fileCommands = await loadSlashCommands({ cwd });
1821
+ record.session.setSlashCommands(fileCommands);
1822
+ await record.session.refreshSshTool({ activateIfAvailable: true });
1823
+ await this.#emitAvailableCommandsUpdate(record);
1824
+ }
1825
+
1826
+ async #emitEndOfTurnUpdates(record: ManagedSessionRecord): Promise<void> {
1827
+ const sessionId = record.session.sessionId;
1828
+
1829
+ const contextUsage = record.session.getContextUsage();
1830
+ if (contextUsage) {
1831
+ const usageStats = record.session.sessionManager.getUsageStatistics();
1832
+ await this.#connection.sessionUpdate({
1833
+ sessionId,
1834
+ update: {
1835
+ sessionUpdate: "usage_update",
1836
+ size: contextUsage.contextWindow,
1837
+ used: contextUsage.tokens ?? 0,
1838
+ cost: usageStats.cost > 0 ? { amount: usageStats.cost, currency: "USD" } : undefined,
1839
+ },
1840
+ });
1841
+ }
1842
+
1843
+ await this.#connection.sessionUpdate({
1844
+ sessionId,
1845
+ update: {
1846
+ sessionUpdate: "session_info_update",
1847
+ title: record.session.sessionName,
1848
+ updatedAt: new Date().toISOString(),
1849
+ },
1850
+ });
1851
+ }
1852
+
1853
+ #cloneUsageStatistics(usage: UsageStatistics): UsageStatistics {
1854
+ return {
1855
+ input: usage.input,
1856
+ output: usage.output,
1857
+ cacheRead: usage.cacheRead,
1858
+ cacheWrite: usage.cacheWrite,
1859
+ premiumRequests: usage.premiumRequests,
1860
+ cost: usage.cost,
1861
+ };
1862
+ }
1863
+
1864
+ #buildTurnUsage(previous: UsageStatistics, current: UsageStatistics): Usage | undefined {
1865
+ const inputTokens = Math.max(0, current.input - previous.input);
1866
+ const outputTokens = Math.max(0, current.output - previous.output);
1867
+ const cachedReadTokens = Math.max(0, current.cacheRead - previous.cacheRead);
1868
+ const cachedWriteTokens = Math.max(0, current.cacheWrite - previous.cacheWrite);
1869
+ const totalTokens = inputTokens + outputTokens + cachedReadTokens + cachedWriteTokens;
1870
+
1871
+ if (totalTokens === 0) {
1872
+ return undefined;
1873
+ }
1874
+
1875
+ const usage: Usage = {
1876
+ inputTokens,
1877
+ outputTokens,
1878
+ totalTokens,
1879
+ };
1880
+ if (cachedReadTokens > 0) {
1881
+ usage.cachedReadTokens = cachedReadTokens;
1882
+ }
1883
+ if (cachedWriteTokens > 0) {
1884
+ usage.cachedWriteTokens = cachedWriteTokens;
1885
+ }
1886
+ return usage;
1887
+ }
1888
+
1889
+ async #listStoredSessions(cwd?: string): Promise<StoredSessionInfo[]> {
1890
+ const sessions = cwd ? await SessionManager.list(cwd) : await SessionManager.listAll();
1891
+ return sessions.sort((left, right) => right.modified.getTime() - left.modified.getTime());
1892
+ }
1893
+
1894
+ async #findStoredSession(sessionId: string, cwd: string): Promise<StoredSessionInfo | undefined> {
1895
+ const sessions = await this.#listStoredSessions(cwd);
1896
+ return sessions.find(session => session.id === sessionId);
1897
+ }
1898
+
1899
+ async #findStoredSessionById(sessionId: string): Promise<StoredSessionInfo | undefined> {
1900
+ const sessions = await this.#listStoredSessions();
1901
+ return sessions.find(session => session.id === sessionId);
1902
+ }
1903
+
1904
+ #parseCursor(cursor: string | undefined): number {
1905
+ if (!cursor) {
1906
+ return 0;
1907
+ }
1908
+ const parsed = Number.parseInt(cursor, 10);
1909
+ if (!Number.isFinite(parsed) || parsed < 0) {
1910
+ throw new Error(`Invalid ACP session cursor: ${cursor}`);
1911
+ }
1912
+ return parsed;
1913
+ }
1914
+
1915
+ async #replaySessionHistory(record: ManagedSessionRecord): Promise<void> {
1916
+ const cwd = record.session.sessionManager.getCwd();
1917
+ const replayedToolCallIds = new Set<string>();
1918
+ const replayedToolCallArgs = new Map<string, unknown>();
1919
+ for (const message of record.session.sessionManager.buildSessionContext().messages as ReplayableMessage[]) {
1920
+ for (const notification of this.#messageToReplayNotifications(
1921
+ record.session.sessionId,
1922
+ message,
1923
+ cwd,
1924
+ replayedToolCallIds,
1925
+ replayedToolCallArgs,
1926
+ )) {
1927
+ await this.#connection.sessionUpdate(notification);
1928
+ }
1929
+ }
1930
+ }
1931
+
1932
+ #messageToReplayNotifications(
1933
+ sessionId: string,
1934
+ message: ReplayableMessage,
1935
+ cwd: string,
1936
+ replayedToolCallIds: Set<string>,
1937
+ replayedToolCallArgs: Map<string, unknown>,
1938
+ ): SessionNotification[] {
1939
+ if (message.role === "assistant") {
1940
+ return this.#replayAssistantMessage(sessionId, message, cwd, replayedToolCallIds, replayedToolCallArgs);
1941
+ }
1942
+ if (
1943
+ message.role === "user" ||
1944
+ message.role === "developer" ||
1945
+ message.role === "custom" ||
1946
+ message.role === "hookMessage"
1947
+ ) {
1948
+ return this.#wrapReplayContent(
1949
+ sessionId,
1950
+ this.#extractReplayContent(message.content, undefined),
1951
+ "user_message_chunk",
1952
+ crypto.randomUUID(),
1953
+ );
1954
+ }
1955
+ if (
1956
+ message.role === "toolResult" &&
1957
+ typeof message.toolCallId === "string" &&
1958
+ typeof message.toolName === "string"
1959
+ ) {
1960
+ return this.#replayToolResult(
1961
+ sessionId,
1962
+ cwd,
1963
+ {
1964
+ ...message,
1965
+ toolCallId: message.toolCallId,
1966
+ toolName: message.toolName,
1967
+ },
1968
+ {
1969
+ includeStart: !replayedToolCallIds.has(message.toolCallId),
1970
+ toolArgs: replayedToolCallArgs.get(message.toolCallId),
1971
+ },
1972
+ );
1973
+ }
1974
+ if (
1975
+ message.role === "bashExecution" ||
1976
+ message.role === "pythonExecution" ||
1977
+ message.role === "compactionSummary"
1978
+ ) {
1979
+ return this.#wrapReplayContent(
1980
+ sessionId,
1981
+ this.#extractReplayContent(message.content, undefined),
1982
+ "user_message_chunk",
1983
+ crypto.randomUUID(),
1984
+ );
1985
+ }
1986
+ return [];
1987
+ }
1988
+
1989
+ #replayAssistantMessage(
1990
+ sessionId: string,
1991
+ message: ReplayableMessage,
1992
+ cwd: string,
1993
+ replayedToolCallIds: Set<string>,
1994
+ replayedToolCallArgs: Map<string, unknown>,
1995
+ ): SessionNotification[] {
1996
+ const notifications: SessionNotification[] = [];
1997
+ const messageId = crypto.randomUUID();
1998
+ if (Array.isArray(message.content)) {
1999
+ for (const item of message.content) {
2000
+ if (typeof item !== "object" || item === null || !("type" in item)) {
2001
+ continue;
2002
+ }
2003
+ if (item.type === "text" && "text" in item && typeof item.text === "string" && item.text.length > 0) {
2004
+ notifications.push({
2005
+ sessionId,
2006
+ update: {
2007
+ sessionUpdate: "agent_message_chunk",
2008
+ content: { type: "text", text: item.text },
2009
+ messageId,
2010
+ },
2011
+ });
2012
+ continue;
2013
+ }
2014
+ if (
2015
+ item.type === "thinking" &&
2016
+ "thinking" in item &&
2017
+ typeof item.thinking === "string" &&
2018
+ item.thinking.length > 0
2019
+ ) {
2020
+ notifications.push({
2021
+ sessionId,
2022
+ update: {
2023
+ sessionUpdate: "agent_thought_chunk",
2024
+ content: { type: "text", text: item.thinking },
2025
+ messageId,
2026
+ },
2027
+ });
2028
+ continue;
2029
+ }
2030
+ const toolItem = item as ReplayableToolItem;
2031
+ if (
2032
+ (toolItem.type === "toolCall" || toolItem.type === "tool_use") &&
2033
+ typeof toolItem.id === "string" &&
2034
+ typeof toolItem.name === "string"
2035
+ ) {
2036
+ const args = this.#buildReplayAssistantToolArgs(toolItem);
2037
+ const update = buildToolCallStartUpdate({
2038
+ toolCallId: toolItem.id,
2039
+ toolName: toolItem.name,
2040
+ args,
2041
+ status: "completed",
2042
+ cwd,
2043
+ });
2044
+ notifications.push({ sessionId, update });
2045
+ replayedToolCallIds.add(toolItem.id);
2046
+ replayedToolCallArgs.set(toolItem.id, args);
2047
+ }
2048
+ }
2049
+ }
2050
+ if (notifications.length === 0 && message.errorMessage && !isSilentAbort(message.errorMessage)) {
2051
+ notifications.push({
2052
+ sessionId,
2053
+ update: {
2054
+ sessionUpdate: "agent_message_chunk",
2055
+ content: { type: "text", text: message.errorMessage },
2056
+ messageId,
2057
+ },
2058
+ });
2059
+ }
2060
+ return notifications;
2061
+ }
2062
+
2063
+ #buildReplayAssistantToolArgs(item: ReplayableToolItem): unknown {
2064
+ if ("arguments" in item) {
2065
+ return normalizeReplayToolArguments(item.arguments).args;
2066
+ }
2067
+ if (item.type === "tool_use" && "input" in item) {
2068
+ return item.input;
2069
+ }
2070
+ return {};
2071
+ }
2072
+
2073
+ #replayToolResult(
2074
+ sessionId: string,
2075
+ cwd: string,
2076
+ message: Required<Pick<ReplayableMessage, "toolCallId" | "toolName">> & ReplayableMessage,
2077
+ options: { includeStart?: boolean; toolArgs?: unknown } = {},
2078
+ ): SessionNotification[] {
2079
+ const args = this.#buildReplayToolArgs(message.details);
2080
+ const startEvent: AgentSessionEvent = {
2081
+ type: "tool_execution_start",
2082
+ toolCallId: message.toolCallId,
2083
+ toolName: message.toolName,
2084
+ args,
2085
+ };
2086
+ const endEvent: AgentSessionEvent = {
2087
+ type: "tool_execution_end",
2088
+ toolCallId: message.toolCallId,
2089
+ toolName: message.toolName,
2090
+ isError: message.isError === true,
2091
+ result: {
2092
+ content: message.content,
2093
+ details: message.details,
2094
+ errorMessage: message.errorMessage,
2095
+ },
2096
+ };
2097
+ const notifications = mapAgentSessionEventToAcpSessionUpdates(endEvent, sessionId, {
2098
+ cwd,
2099
+ getToolArgs: toolCallId => (toolCallId === message.toolCallId ? options.toolArgs : undefined),
2100
+ });
2101
+ if (options.includeStart === false) {
2102
+ return notifications;
2103
+ }
2104
+ return [...mapAgentSessionEventToAcpSessionUpdates(startEvent, sessionId, { cwd }), ...notifications];
2105
+ }
2106
+
2107
+ #buildReplayToolArgs(details: unknown): { path?: string } {
2108
+ if (typeof details !== "object" || details === null || !("path" in details)) {
2109
+ return {};
2110
+ }
2111
+ const value = (details as { path?: unknown }).path;
2112
+ return typeof value === "string" && value.length > 0 ? { path: value } : {};
2113
+ }
2114
+
2115
+ #wrapReplayContent(
2116
+ sessionId: string,
2117
+ content: PromptRequest["prompt"],
2118
+ kind: "agent_message_chunk" | "user_message_chunk",
2119
+ messageId: string,
2120
+ ): SessionNotification[] {
2121
+ return content.map(block => ({
2122
+ sessionId,
2123
+ update: {
2124
+ sessionUpdate: kind,
2125
+ content: block,
2126
+ messageId,
2127
+ },
2128
+ }));
2129
+ }
2130
+
2131
+ #extractReplayContent(content: unknown, errorMessage: string | undefined): PromptRequest["prompt"] {
2132
+ const replay: PromptRequest["prompt"] = [];
2133
+ if (Array.isArray(content)) {
2134
+ for (const item of content) {
2135
+ if (typeof item !== "object" || item === null || !("type" in item)) {
2136
+ continue;
2137
+ }
2138
+ if (item.type === "text" && "text" in item && typeof item.text === "string" && item.text.length > 0) {
2139
+ replay.push({ type: "text", text: item.text });
2140
+ continue;
2141
+ }
2142
+ if (
2143
+ item.type === "image" &&
2144
+ "data" in item &&
2145
+ "mimeType" in item &&
2146
+ typeof item.data === "string" &&
2147
+ typeof item.mimeType === "string"
2148
+ ) {
2149
+ replay.push({ type: "image", data: item.data, mimeType: item.mimeType });
2150
+ }
2151
+ }
2152
+ }
2153
+ if (replay.length === 0 && errorMessage) {
2154
+ replay.push({ type: "text", text: errorMessage });
2155
+ }
2156
+ return replay;
2157
+ }
2158
+
2159
+ async #configureExtensions(record: ManagedSessionRecord): Promise<void> {
2160
+ if (record.extensionsConfigured) {
2161
+ return;
2162
+ }
2163
+
2164
+ const extensionRunner = record.session.extensionRunner;
2165
+ if (!extensionRunner) {
2166
+ record.extensionsConfigured = true;
2167
+ return;
2168
+ }
2169
+
2170
+ extensionRunner.initialize(
2171
+ {
2172
+ sendMessage: (message, options) => {
2173
+ record.session.sendCustomMessage(message, options).catch((error: unknown) => {
2174
+ logger.warn("ACP extension sendMessage failed", { error });
2175
+ });
2176
+ },
2177
+ sendUserMessage: (content, options) => {
2178
+ this.#trackExtensionUserMessage(record, record.session.sendUserMessage(content, options));
2179
+ },
2180
+ appendEntry: (customType, data) => {
2181
+ record.session.sessionManager.appendCustomEntry(customType, data);
2182
+ },
2183
+ setLabel: (targetId, label) => {
2184
+ record.session.sessionManager.appendLabelChange(targetId, label);
2185
+ },
2186
+ getActiveTools: () => record.session.getActiveToolNames(),
2187
+ getAllTools: () => record.session.getAllToolNames(),
2188
+ setActiveTools: toolNames => record.session.setActiveToolsByName(toolNames),
2189
+ getCommands: () => getSessionSlashCommands(record.session),
2190
+ setModel: async model => {
2191
+ const apiKey = await record.session.modelRegistry.getApiKey(model);
2192
+ if (!apiKey) {
2193
+ return false;
2194
+ }
2195
+ await record.session.setModel(model);
2196
+ return true;
2197
+ },
2198
+ getThinkingLevel: () => record.session.thinkingLevel,
2199
+ setThinkingLevel: level => record.session.setThinkingLevel(level),
2200
+ getSessionName: () => record.session.sessionManager.getSessionName(),
2201
+ setSessionName: async name => {
2202
+ await record.session.sessionManager.setSessionName(name, "user");
2203
+ },
2204
+ },
2205
+ {
2206
+ getModel: () => record.session.model,
2207
+ isIdle: () => !record.session.isStreaming,
2208
+ abort: () => {
2209
+ void record.session.abort();
2210
+ },
2211
+ hasPendingMessages: () => record.session.queuedMessageCount > 0,
2212
+ shutdown: () => {},
2213
+ getContextUsage: () => record.session.getContextUsage(),
2214
+ getSystemPrompt: () => record.session.systemPrompt,
2215
+ compact: instructionsOrOptions => runExtensionCompact(record.session, instructionsOrOptions),
2216
+ },
2217
+ {
2218
+ getContextUsage: () => record.session.getContextUsage(),
2219
+ waitForIdle: () => record.session.agent.waitForIdle(),
2220
+ newSession: async options => {
2221
+ const success = await record.session.newSession({ parentSession: options?.parentSession });
2222
+ if (success && options?.setup) {
2223
+ await options.setup(record.session.sessionManager);
2224
+ }
2225
+ return { cancelled: !success };
2226
+ },
2227
+ branch: async entryId => {
2228
+ const result = await record.session.branch(entryId);
2229
+ return { cancelled: result.cancelled };
2230
+ },
2231
+ navigateTree: async (targetId, options) => {
2232
+ const result = await record.session.navigateTree(targetId, { summarize: options?.summarize });
2233
+ return { cancelled: result.cancelled };
2234
+ },
2235
+ switchSession: async sessionPath => {
2236
+ const success = await record.session.switchSession(sessionPath);
2237
+ return { cancelled: !success };
2238
+ },
2239
+ reload: async () => {
2240
+ await record.session.reload();
2241
+ },
2242
+ compact: instructionsOrOptions => runExtensionCompact(record.session, instructionsOrOptions),
2243
+ },
2244
+ // Per-session getter: `record.session.sessionId` reads through to
2245
+ // `sessionManager.getSessionId()` (it's a getter, not a field), so an
2246
+ // extension command that calls `ctx.newSession` / `ctx.switchSession`
2247
+ // — both exposed in the block just above — mutates the underlying id
2248
+ // mid-flight. Reading lazily on each elicitation matches every other
2249
+ // `sessionUpdate` call in this file. Hoisting the factory to an
2250
+ // `AcpAgent` field would still be wrong because it would also lose
2251
+ // the per-`record` binding.
2252
+ createAcpExtensionUiContext(this.#connection, () => record.session.sessionId, this.#clientCapabilities),
2253
+ );
2254
+ await extensionRunner.emit({ type: "session_start" });
2255
+ record.extensionsConfigured = true;
2256
+ }
2257
+
2258
+ async #configureMcpServers(record: ManagedSessionRecord, servers: McpServer[]): Promise<void> {
2259
+ if (record.mcpManager) {
2260
+ await record.mcpManager.disconnectAll();
2261
+ }
2262
+ if (servers.length === 0) {
2263
+ record.mcpManager = undefined;
2264
+ await record.session.refreshMCPTools([], { activateAll: true });
2265
+ return;
2266
+ }
2267
+
2268
+ const manager = new MCPManager(record.session.sessionManager.getCwd());
2269
+ const configs: MCPConfigMap = {};
2270
+ const sources: MCPSourceMap = {};
2271
+ for (const server of servers) {
2272
+ configs[server.name] = this.#toMcpConfig(server);
2273
+ sources[server.name] = {
2274
+ provider: "acp",
2275
+ providerName: "ACP Client",
2276
+ path: `acp://${server.name}`,
2277
+ level: "project",
2278
+ };
2279
+ }
2280
+
2281
+ const result = await manager.connectServers(configs, sources);
2282
+ if (result.errors.size > 0) {
2283
+ throw new Error(
2284
+ Array.from(result.errors.entries())
2285
+ .map(([name, message]) => `${name}: ${message}`)
2286
+ .join("; "),
2287
+ );
2288
+ }
2289
+
2290
+ record.mcpManager = manager;
2291
+ await record.session.refreshMCPTools(result.tools, { activateAll: true });
2292
+ }
2293
+
2294
+ #toMcpConfig(server: McpServer): MCPServerConfig {
2295
+ if ("command" in server) {
2296
+ return {
2297
+ type: "stdio",
2298
+ command: server.command,
2299
+ args: server.args,
2300
+ env: this.#toNameValueMap(server.env),
2301
+ };
2302
+ }
2303
+ if (server.type === "http") {
2304
+ return {
2305
+ type: "http",
2306
+ url: server.url,
2307
+ headers: this.#toNameValueMap(server.headers),
2308
+ };
2309
+ }
2310
+ if (server.type === "sse") {
2311
+ return {
2312
+ type: "sse",
2313
+ url: server.url,
2314
+ headers: this.#toNameValueMap(server.headers),
2315
+ };
2316
+ }
2317
+ // The experimental ACP-channel transport (`type: "acp"`) is not advertised in
2318
+ // `mcpCapabilities`, so a spec-compliant client never sends it; reject defensively.
2319
+ throw new Error(`Unsupported MCP server transport: ${server.type}`);
2320
+ }
2321
+
2322
+ #toNameValueMap(values: Array<{ name: string; value: string }>): { [name: string]: string } {
2323
+ const mapped: { [name: string]: string } = {};
2324
+ for (const value of values) {
2325
+ mapped[value.name] = value.value;
2326
+ }
2327
+ return mapped;
2328
+ }
2329
+
2330
+ async #closeManagedSession(sessionId: string, record: ManagedSessionRecord): Promise<void> {
2331
+ record.closedError ??= this.#createPromptLifecycleError("ACP session closed before queued prompt could run");
2332
+ this.#sessions.delete(sessionId);
2333
+ await this.#cancelPromptForClose(record);
2334
+ await this.#disposeSessionRecord(record);
2335
+ }
2336
+
2337
+ async #cancelPromptForClose(record: ManagedSessionRecord): Promise<void> {
2338
+ const promptTurn = record.promptTurn;
2339
+ if (!isPromptTurnInFlight(promptTurn)) {
2340
+ return;
2341
+ }
2342
+ const cleanup = promptTurn.cleanup ?? this.#beginCancelCleanup(record, promptTurn);
2343
+ try {
2344
+ await cleanup;
2345
+ } catch (error) {
2346
+ logger.warn("Failed to abort ACP prompt during session close", { error });
2347
+ }
2348
+ }
2349
+
2350
+ async #disposeSessionRecord(record: ManagedSessionRecord): Promise<void> {
2351
+ record.lifetimeUnsubscribe?.();
2352
+ if (record.mcpManager) {
2353
+ try {
2354
+ await record.mcpManager.disconnectAll();
2355
+ } catch (error) {
2356
+ logger.warn("Failed to disconnect ACP MCP servers", { error });
2357
+ }
2358
+ record.mcpManager = undefined;
2359
+ }
2360
+ try {
2361
+ await record.session.dispose();
2362
+ } catch (error) {
2363
+ logger.warn("Failed to dispose ACP session", { error });
2364
+ }
2365
+ }
2366
+
2367
+ async #disposeStandaloneSession(session: AgentSession): Promise<void> {
2368
+ try {
2369
+ await session.dispose();
2370
+ } catch (error) {
2371
+ logger.warn("Failed to dispose ACP session", { error });
2372
+ }
2373
+ }
2374
+
2375
+ async #disposeAllSessions(): Promise<void> {
2376
+ if (this.#disposePromise) {
2377
+ await this.#disposePromise;
2378
+ return;
2379
+ }
2380
+
2381
+ this.#disposePromise = (async () => {
2382
+ const records = Array.from(this.#sessions.entries());
2383
+ this.#sessions.clear();
2384
+ await Promise.all(
2385
+ records.map(async ([sessionId, record]) => {
2386
+ try {
2387
+ record.closedError ??= this.#createPromptLifecycleError(
2388
+ "ACP agent disposed before queued prompt could run",
2389
+ );
2390
+ await this.#cancelPromptForClose(record);
2391
+ await this.#disposeSessionRecord(record);
2392
+ } catch (error) {
2393
+ logger.warn("Failed to clean up ACP session", { sessionId, error });
2394
+ }
2395
+ }),
2396
+ );
2397
+
2398
+ const initialSession = this.#initialSession;
2399
+ this.#initialSession = undefined;
2400
+ if (initialSession) {
2401
+ await this.#disposeStandaloneSession(initialSession);
2402
+ }
2403
+ })();
2404
+
2405
+ await this.#disposePromise;
2406
+ }
2407
+ }