@sayknow-cli/coding-agent 0.3.16 → 0.4.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 (1474) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/package.json +127 -110
  3. package/scripts/build-sdk-package-smoke.ts +132 -0
  4. package/scripts/capture-notifications-settings-showcase.ts +435 -0
  5. package/scripts/compile-args.ts +5 -2
  6. package/scripts/g005-daemon-smoke.ts +4 -5
  7. package/scripts/g011-daemon-path-smoke.ts +5 -5
  8. package/scripts/generate-hotkeys-docs.ts +74 -0
  9. package/scripts/generate-sdk-adapter-parity-manifest.ts +163 -0
  10. package/scripts/generate-sdk-operation-inventory.ts +914 -0
  11. package/scripts/generate-telegram-baseline-manifest.ts +128 -0
  12. package/scripts/manifest-schema.ts +124 -0
  13. package/scripts/run-test-manifest.ts +153 -0
  14. package/scripts/run-threat-model-check.ts +217 -0
  15. package/scripts/verify-skc-sdk-canonicalization.ts +3647 -0
  16. package/scripts/verify-skc-sdk-rename.ts +90 -0
  17. package/src/append-only-mode.ts +1 -1
  18. package/src/async/job-manager.ts +455 -69
  19. package/src/cli/args.ts +43 -12
  20. package/src/cli/auth-gateway-cli.ts +2 -2
  21. package/src/cli/config-cli.ts +77 -3
  22. package/src/cli/daemon-cli.ts +63 -14
  23. package/src/cli/launch-disposition.ts +42 -0
  24. package/src/cli/list-models.ts +1 -1
  25. package/src/cli/notify-cli.ts +373 -283
  26. package/src/cli/session-picker.ts +20 -3
  27. package/src/cli/setup-cli.ts +1 -1
  28. package/src/cli/worktree-cli.ts +9 -287
  29. package/src/cli.ts +31 -11
  30. package/src/commands/config.ts +1 -1
  31. package/src/commands/daemon.ts +50 -6
  32. package/src/commands/gc.ts +11 -1
  33. package/src/commands/harness.ts +127 -142
  34. package/src/commands/launch.ts +11 -6
  35. package/src/commands/mcp-serve.ts +97 -13
  36. package/src/commands/mcp.ts +2 -2
  37. package/src/commands/notify.ts +44 -7
  38. package/src/commands/sdk.ts +453 -0
  39. package/src/commands/ultragoal.ts +4 -1
  40. package/src/commands/worktree.ts +10 -53
  41. package/src/commit/agentic/agent.ts +1 -0
  42. package/src/config/atomic-yaml-patch.ts +490 -0
  43. package/src/config/file-lock.ts +105 -43
  44. package/src/config/keybindings.ts +64 -20
  45. package/src/config/model-auth.ts +9 -0
  46. package/src/config/model-bindings-applier.ts +139 -0
  47. package/src/config/model-discovery-manager.ts +224 -0
  48. package/src/config/model-equivalence.ts +66 -0
  49. package/src/config/model-profile-activation.ts +233 -86
  50. package/src/config/model-profiles.ts +52 -49
  51. package/src/config/model-registry.ts +5 -4
  52. package/src/config/model-resolver.ts +487 -410
  53. package/src/config/model-selector-value.ts +50 -0
  54. package/src/config/models-config-schema.ts +17 -23
  55. package/src/config/settings-schema.ts +372 -21
  56. package/src/config/settings.ts +1120 -182
  57. package/src/coordinator-mcp/model-preset.ts +74 -20
  58. package/src/coordinator-mcp/policy.ts +76 -11
  59. package/src/coordinator-mcp/question-gate-codec.ts +274 -0
  60. package/src/coordinator-mcp/question-state.ts +778 -0
  61. package/src/coordinator-mcp/server.ts +3120 -3111
  62. package/src/daemon/builtin.ts +8 -5
  63. package/src/daemon/control-types.ts +1 -1
  64. package/src/daemon/runtime.ts +2 -2
  65. package/src/debug/index.ts +1 -1
  66. package/src/defaults/skc/skills/team/SKILL.md +27 -13
  67. package/src/defaults/skc/skills/ultragoal/SKILL.md +0 -2
  68. package/src/discovery/helpers.ts +1 -50
  69. package/src/discovery/mcp-json.ts +327 -20
  70. package/src/edit/index.ts +64 -0
  71. package/src/eval/js/shared/runtime.ts +1 -1
  72. package/src/eval/py/env.ts +26 -0
  73. package/src/eval/py/index.ts +2 -2
  74. package/src/eval/py/kernel.ts +5 -3
  75. package/src/export/html/template.generated.ts +1 -1
  76. package/src/export/html/template.js +52 -18
  77. package/src/extensibility/custom-tools/types.ts +1 -1
  78. package/src/extensibility/extensions/compact-handler.ts +3 -3
  79. package/src/extensibility/extensions/get-commands-handler.ts +2 -2
  80. package/src/extensibility/extensions/loader.ts +77 -5
  81. package/src/extensibility/extensions/runner.ts +124 -7
  82. package/src/extensibility/extensions/types.ts +215 -18
  83. package/src/extensibility/hooks/runner.ts +2 -2
  84. package/src/extensibility/hooks/types.ts +1 -1
  85. package/src/extensibility/plugins/marketplace/manager.ts +94 -10
  86. package/src/extensibility/runtime-skill-discovery.ts +33 -13
  87. package/src/extensibility/shared-events.ts +2 -0
  88. package/src/extensibility/skc-plugins/installer.ts +16 -16
  89. package/src/extensibility/slash-commands.ts +3 -3
  90. package/src/goals/continuation-timeout-guard.ts +79 -0
  91. package/src/goals/runtime.ts +7 -5
  92. package/src/goals/state.ts +18 -0
  93. package/src/harness-control-plane/lifecycle-reconciliation.ts +64 -0
  94. package/src/harness-control-plane/operate.ts +9 -9
  95. package/src/harness-control-plane/owner.ts +341 -41
  96. package/src/harness-control-plane/phase-rollup.ts +4 -7
  97. package/src/harness-control-plane/receipt-ingest.ts +5 -0
  98. package/src/harness-control-plane/receipt-spool.ts +51 -7
  99. package/src/harness-control-plane/receipts.ts +0 -1
  100. package/src/harness-control-plane/sdk-transport.ts +395 -0
  101. package/src/harness-control-plane/session-lease.ts +12 -130
  102. package/src/harness-control-plane/session-transport.ts +94 -0
  103. package/src/harness-control-plane/types.ts +1 -1
  104. package/src/hindsight/backend.ts +26 -27
  105. package/src/hindsight/client.ts +65 -2
  106. package/src/hindsight/state.ts +101 -32
  107. package/src/hooks/skill-state.ts +17 -11
  108. package/src/i18n/messages/en.ts +1 -0
  109. package/src/internal-urls/agent-protocol.ts +76 -5
  110. package/src/internal-urls/docs-index.generated.ts +44 -37
  111. package/src/internal-urls/local-protocol.ts +420 -18
  112. package/src/lsp/client.ts +43 -7
  113. package/src/lsp/config.ts +145 -26
  114. package/src/lsp/index.ts +7 -91
  115. package/src/lsp/lspmux.ts +39 -10
  116. package/src/lsp/path-trust.ts +81 -0
  117. package/src/main.ts +451 -236
  118. package/src/memories/index.ts +1 -0
  119. package/src/memories/storage.ts +6 -2
  120. package/src/modes/DESIGN.md +258 -0
  121. package/src/modes/acp/acp-agent.ts +1236 -2612
  122. package/src/modes/acp/acp-client-bridge.ts +2 -20
  123. package/src/modes/acp/acp-event-mapper.ts +18 -2
  124. package/src/modes/acp/acp-mode.ts +8 -36
  125. package/src/modes/acp/permission-mode.ts +25 -0
  126. package/src/modes/acp/startup-options.ts +6 -0
  127. package/src/modes/action-registry.ts +165 -0
  128. package/src/modes/bridge/bridge-mode.ts +6 -4
  129. package/src/modes/components/agent-dashboard.ts +37 -17
  130. package/src/modes/components/ansi-display-validator.ts +54 -0
  131. package/src/modes/components/chrome.ts +40 -0
  132. package/src/modes/components/command-palette.ts +168 -0
  133. package/src/modes/components/custom-editor.ts +105 -33
  134. package/src/modes/components/custom-provider-wizard.ts +77 -15
  135. package/src/modes/components/index.ts +2 -1
  136. package/src/modes/components/model-selector.ts +93 -35
  137. package/src/modes/components/notifications-settings-editor.ts +1403 -0
  138. package/src/modes/components/pet-capability.ts +115 -0
  139. package/src/modes/components/pet-selector.ts +15 -23
  140. package/src/modes/components/plan-preview-overlay.ts +295 -0
  141. package/src/modes/components/queue-mode-selector.ts +10 -27
  142. package/src/modes/components/queue-pane.ts +79 -0
  143. package/src/modes/components/read-tool-group.ts +13 -0
  144. package/src/modes/components/sayknow-pet-widget.ts +169 -20
  145. package/src/modes/components/session-observer-overlay.ts +213 -775
  146. package/src/modes/components/sessions-dashboard.ts +181 -0
  147. package/src/modes/components/settings-selector.ts +89 -12
  148. package/src/modes/components/show-images-selector.ts +10 -24
  149. package/src/modes/components/status-line/types.ts +1 -1
  150. package/src/modes/components/status-line.ts +1 -969
  151. package/src/modes/components/tasks-pane.ts +73 -0
  152. package/src/modes/components/theme-selector.ts +11 -31
  153. package/src/modes/components/tool-execution.ts +22 -0
  154. package/src/modes/components/tool-status-header.ts +1056 -0
  155. package/src/modes/components/tool-transcript-format.ts +393 -0
  156. package/src/modes/components/transcript-viewer-overlay.ts +446 -0
  157. package/src/modes/components/welcome.ts +4 -4
  158. package/src/modes/controllers/btw-controller.ts +1 -0
  159. package/src/modes/controllers/command-controller.ts +25 -17
  160. package/src/modes/controllers/event-controller.ts +14 -18
  161. package/src/modes/controllers/extension-ui-controller.ts +441 -37
  162. package/src/modes/controllers/goal-mode-controller.ts +493 -0
  163. package/src/modes/controllers/input-controller.ts +889 -263
  164. package/src/modes/controllers/mode-gate.ts +24 -0
  165. package/src/modes/controllers/plan-mode-controller.ts +519 -0
  166. package/src/modes/controllers/runtime-mcp-command-controller.ts +3 -0
  167. package/src/modes/controllers/selector-controller.ts +957 -54
  168. package/src/modes/controllers/stt-controller.ts +125 -0
  169. package/src/modes/index.ts +0 -22
  170. package/src/modes/interactive-mode.ts +376 -1130
  171. package/src/modes/jobs-observer.ts +3 -0
  172. package/src/modes/prompt-action-autocomplete.ts +7 -0
  173. package/src/modes/rpc/rpc-mode.ts +3 -1
  174. package/src/modes/runtime-init.ts +320 -6
  175. package/src/modes/shared/agent-wire/approval-gate.ts +9 -11
  176. package/src/modes/shared/agent-wire/command-dispatch.ts +3 -1
  177. package/src/modes/shared/agent-wire/deep-interview-gate.ts +65 -111
  178. package/src/modes/shared/agent-wire/event-contract.ts +7 -8
  179. package/src/modes/shared/agent-wire/event-envelope.ts +9 -17
  180. package/src/modes/shared/agent-wire/event-observation.ts +16 -7
  181. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +1331 -89
  182. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +19 -17
  183. package/src/modes/shared/agent-wire/workflow-gate-types.ts +407 -0
  184. package/src/modes/tasks-aggregator.ts +199 -0
  185. package/src/modes/transcript-item-registry.ts +229 -0
  186. package/src/modes/types.ts +63 -21
  187. package/src/modes/utils/ui-helpers.ts +17 -9
  188. package/src/notifications/index.ts +8 -2
  189. package/src/plan-mode/approved-plan.ts +3 -1
  190. package/src/prompts/agent-fragments/ralplan-persistence.md +6 -0
  191. package/src/prompts/agent-fragments/restricted-bash.md +1 -0
  192. package/src/prompts/agents/architect.md +3 -8
  193. package/src/prompts/agents/critic.md +3 -8
  194. package/src/prompts/agents/executor.md +3 -0
  195. package/src/prompts/agents/planner.md +4 -12
  196. package/src/prompts/system/custom-system-prompt.md +2 -24
  197. package/src/prompts/system/plan-mode-approved.md +8 -0
  198. package/src/prompts/system/system-prompt.md +18 -94
  199. package/src/prompts/tools/bash.md +4 -4
  200. package/src/prompts/tools/browser.md +15 -52
  201. package/src/prompts/tools/find.md +2 -2
  202. package/src/prompts/tools/hashline.md +13 -55
  203. package/src/prompts/tools/lsp.md +3 -3
  204. package/src/prompts/tools/read.md +3 -6
  205. package/src/prompts/tools/search-tool-bm25.md +0 -2
  206. package/src/prompts/tools/search.md +2 -4
  207. package/src/prompts/tools/skill-discovery.md +1 -2
  208. package/src/prompts/tools/task.md +1 -1
  209. package/src/runtime-mcp/client.ts +94 -19
  210. package/src/runtime-mcp/config.ts +27 -15
  211. package/src/runtime-mcp/discoverable-tool-metadata.ts +16 -13
  212. package/src/runtime-mcp/manager.ts +359 -106
  213. package/src/runtime-mcp/oauth-discovery.ts +116 -26
  214. package/src/runtime-mcp/oauth-flow.ts +42 -12
  215. package/src/runtime-mcp/transports/http.ts +49 -19
  216. package/src/runtime-mcp/transports/stdio.ts +47 -28
  217. package/src/runtime-mcp/types.ts +10 -0
  218. package/src/sdk/acp/adapter.ts +418 -0
  219. package/src/sdk/acp/index.ts +2 -0
  220. package/src/sdk/broker/broker.ts +877 -0
  221. package/src/sdk/broker/discovery.ts +238 -0
  222. package/src/sdk/broker/ensure.ts +393 -0
  223. package/src/sdk/broker/identity.ts +42 -0
  224. package/src/sdk/broker/index.ts +8 -0
  225. package/src/sdk/broker/internal-source-marker-2178.txt +1 -0
  226. package/src/sdk/broker/internal-source.bunfig.toml +1 -0
  227. package/src/sdk/broker/internal.ts +10 -0
  228. package/src/sdk/broker/lifecycle-codec.ts +11 -0
  229. package/src/sdk/broker/lifecycle-ledger.ts +583 -0
  230. package/src/sdk/broker/lifecycle.ts +3413 -0
  231. package/src/sdk/broker/process-incarnation.ts +151 -0
  232. package/src/sdk/broker/runtime.ts +164 -0
  233. package/src/sdk/broker/session-index.ts +678 -0
  234. package/src/sdk/broker/state-version.ts +47 -0
  235. package/src/sdk/broker/transport.ts +125 -0
  236. package/src/sdk/bus/attachment-registry.ts +23 -0
  237. package/src/sdk/bus/chat-adapters.ts +147 -0
  238. package/src/sdk/bus/chat-command-policy.ts +107 -0
  239. package/src/sdk/bus/chat-daemon-cli.ts +278 -0
  240. package/src/sdk/bus/chat-daemon-control.ts +1101 -0
  241. package/src/sdk/bus/chat-daemon-runtime.ts +527 -0
  242. package/src/sdk/bus/chat-effect-journal.ts +363 -0
  243. package/src/sdk/bus/config-commands.ts +192 -0
  244. package/src/sdk/bus/config.ts +568 -0
  245. package/src/sdk/bus/conversation-store.ts +367 -0
  246. package/src/sdk/bus/daemon-paths.ts +34 -0
  247. package/src/sdk/bus/discord-conversation.ts +85 -0
  248. package/src/sdk/bus/discord-daemon.ts +2292 -0
  249. package/src/sdk/bus/discord-live-provider.ts +521 -0
  250. package/src/sdk/bus/discord-provider.ts +56 -0
  251. package/src/sdk/bus/engine.ts +100 -0
  252. package/src/sdk/bus/helpers.ts +135 -0
  253. package/src/sdk/bus/html-format.ts +518 -0
  254. package/src/sdk/bus/index.ts +4961 -0
  255. package/src/sdk/bus/lifecycle-commands.ts +291 -0
  256. package/src/sdk/bus/lifecycle-control-runtime.ts +1409 -0
  257. package/src/sdk/bus/lifecycle-orchestrator.ts +458 -0
  258. package/src/sdk/bus/managed-daemon.ts +163 -0
  259. package/src/sdk/bus/native-runtime-compatibility.ts +42 -0
  260. package/src/sdk/bus/natives-augment.d.ts +63 -0
  261. package/src/sdk/bus/notification-orchestration.ts +535 -0
  262. package/src/sdk/bus/notification-service.ts +1174 -0
  263. package/src/sdk/bus/operator-runtime.ts +171 -0
  264. package/src/sdk/bus/rate-limit-pool.ts +276 -0
  265. package/src/sdk/bus/recent-activity.ts +279 -0
  266. package/src/sdk/bus/reply-sent-store.ts +134 -0
  267. package/src/sdk/bus/rich-document.ts +258 -0
  268. package/src/sdk/bus/rich-draft.ts +107 -0
  269. package/src/sdk/bus/rich-render.ts +135 -0
  270. package/src/sdk/bus/session-control.ts +439 -0
  271. package/src/sdk/bus/slack-conversation.ts +74 -0
  272. package/src/sdk/bus/slack-daemon.ts +2144 -0
  273. package/src/sdk/bus/slack-live-provider.ts +380 -0
  274. package/src/sdk/bus/slack-provider.ts +76 -0
  275. package/src/sdk/bus/telegram-cli.ts +194 -0
  276. package/src/sdk/bus/telegram-daemon-cli.ts +292 -0
  277. package/src/sdk/bus/telegram-daemon-contract.ts +27 -0
  278. package/src/sdk/bus/telegram-daemon-control.ts +642 -0
  279. package/src/sdk/bus/telegram-daemon.ts +6675 -0
  280. package/src/sdk/bus/telegram-reference.ts +436 -0
  281. package/src/sdk/bus/telegram-setup.ts +663 -0
  282. package/src/sdk/bus/threaded-inbound.ts +141 -0
  283. package/src/sdk/bus/threaded-render.ts +294 -0
  284. package/src/sdk/bus/topic-registry.ts +364 -0
  285. package/src/sdk/cli/index.ts +1 -0
  286. package/src/sdk/cli/session-cli.ts +275 -0
  287. package/src/sdk/client/client.ts +1 -0
  288. package/src/sdk/client/discovery.ts +108 -0
  289. package/src/sdk/client/index.ts +2 -0
  290. package/src/sdk/host/control/dispatch.ts +388 -0
  291. package/src/sdk/host/control/index.ts +10 -0
  292. package/src/sdk/host/control/operations.ts +68 -0
  293. package/src/sdk/host/events.ts +91 -0
  294. package/src/sdk/host/host.ts +338 -0
  295. package/src/sdk/host/index.ts +4 -0
  296. package/src/sdk/host/query/cursor.ts +189 -0
  297. package/src/sdk/host/query/handlers.ts +531 -0
  298. package/src/sdk/host/query/index.ts +3 -0
  299. package/src/sdk/host/query/revision-store.ts +1149 -0
  300. package/src/sdk/host/reverse-leases.ts +325 -0
  301. package/src/sdk/host/types.ts +29 -0
  302. package/src/sdk/index.ts +14 -0
  303. package/src/sdk/lifecycle-session.ts +39 -0
  304. package/src/sdk/mcp/index.ts +1 -0
  305. package/src/sdk/mcp/server.ts +332 -0
  306. package/src/sdk/models.ts +168 -0
  307. package/src/sdk/protocol/adapter-validation.ts +48 -0
  308. package/src/sdk/protocol/operation-inventory.generated.json +4719 -0
  309. package/src/sdk/protocol/operation-registry.ts +312 -0
  310. package/src/sdk/session-directory.ts +141 -0
  311. package/src/sdk/session.ts +2869 -0
  312. package/src/sdk/startup-capability.ts +189 -0
  313. package/src/secrets/index.ts +53 -10
  314. package/src/secrets/obfuscator.ts +21 -18
  315. package/src/secrets/regex.ts +2 -0
  316. package/src/session/agent-session.ts +5602 -2138
  317. package/src/session/agent-storage.ts +18 -0
  318. package/src/session/artifacts.ts +188 -23
  319. package/src/session/cache-economics.ts +25 -8
  320. package/src/session/context-estimation.ts +7 -2
  321. package/src/session/default-model-selection.ts +93 -0
  322. package/src/session/fallback-chain-controller.ts +186 -0
  323. package/src/session/internal/managed-session-scope.ts +2402 -0
  324. package/src/session/internal/managed-session-storage.ts +1559 -0
  325. package/src/session/messages.ts +91 -2
  326. package/src/session/session-manager-internal.ts +33 -0
  327. package/src/session/session-manager.ts +2578 -392
  328. package/src/session/session-storage.ts +550 -40
  329. package/src/session/streaming-output.ts +6 -0
  330. package/src/session/volatile-context-pruning.ts +78 -0
  331. package/src/session/yield-queue.ts +20 -0
  332. package/src/setup/credential-auto-import.ts +291 -35
  333. package/src/setup/hermes/templates/operator-instructions.v1.md +4 -6
  334. package/src/setup/provider-onboarding.ts +36 -61
  335. package/src/setup/provider-presets.json +5 -0
  336. package/src/skc-runtime/deep-interview-recorder.ts +70 -3
  337. package/src/skc-runtime/deep-interview-state.ts +441 -5
  338. package/src/skc-runtime/gc-render.ts +19 -1
  339. package/src/skc-runtime/gc-runtime.ts +68 -3
  340. package/src/skc-runtime/goal-mode-request.ts +55 -8
  341. package/src/skc-runtime/launch-worktree.ts +5 -5
  342. package/src/skc-runtime/linux-proc.ts +71 -0
  343. package/src/skc-runtime/managed-owner-admission.ts +352 -0
  344. package/src/skc-runtime/managed-owner-supervisor.ts +162 -0
  345. package/src/skc-runtime/psmux-detect.ts +82 -4
  346. package/src/skc-runtime/ralplan-runtime.ts +74 -90
  347. package/src/skc-runtime/session-resolution.ts +18 -3
  348. package/src/skc-runtime/session-state-sidecar.ts +47 -138
  349. package/src/skc-runtime/state-argv.ts +292 -0
  350. package/src/skc-runtime/state-migrations.ts +1 -1
  351. package/src/skc-runtime/state-runtime.ts +387 -451
  352. package/src/skc-runtime/state-writer.ts +36 -40
  353. package/src/skc-runtime/team-launch.ts +319 -0
  354. package/src/skc-runtime/team-notify.ts +326 -0
  355. package/src/skc-runtime/team-runtime.ts +8 -2
  356. package/src/skc-runtime/team-store.ts +828 -0
  357. package/src/skc-runtime/team-workers.ts +962 -0
  358. package/src/skc-runtime/tmux-owner-isolation-cli.ts +55 -28
  359. package/src/skc-runtime/tmux-owner-isolation.ts +136 -11
  360. package/src/skc-runtime/tmux-sessions.ts +23 -34
  361. package/src/skc-runtime/ultragoal-change-set.ts +172 -0
  362. package/src/skc-runtime/ultragoal-evidence.ts +874 -0
  363. package/src/skc-runtime/ultragoal-guard.ts +136 -18
  364. package/src/skc-runtime/ultragoal-owner-loss-recovery.ts +328 -0
  365. package/src/skc-runtime/ultragoal-pipeline.ts +306 -0
  366. package/src/skc-runtime/ultragoal-receipt-freshness.ts +102 -1
  367. package/src/skc-runtime/ultragoal-runtime.ts +325 -1322
  368. package/src/skc-runtime/windows-powershell-command.ts +58 -0
  369. package/src/skc-runtime/workflow-cli-common.ts +30 -0
  370. package/src/skc-runtime/workflow-manifest.generated.json +58 -0
  371. package/src/skc-runtime/workflow-manifest.ts +18 -0
  372. package/src/skill-state/active-state.ts +3 -13
  373. package/src/skill-state/workflow-mutation-guard.ts +661 -0
  374. package/src/skill-state/workflow-state-contract.ts +6 -41
  375. package/src/slash-commands/builtin-registry.ts +115 -41
  376. package/src/system-prompt.ts +37 -44
  377. package/src/task/agents.ts +19 -1
  378. package/src/task/executor.ts +223 -45
  379. package/src/task/index.ts +124 -56
  380. package/src/thinking.ts +21 -6
  381. package/src/tool-discovery/tool-index.ts +20 -134
  382. package/src/tools/ask-answer-registry.ts +16 -2
  383. package/src/tools/ast-edit.ts +1 -1
  384. package/src/tools/bash-allowed-prefixes.ts +79 -29
  385. package/src/tools/bash-skill-urls.ts +14 -3
  386. package/src/tools/bash.ts +40 -2
  387. package/src/tools/browser/STEALTH-THIRD-PARTY-NOTICES.md +26 -0
  388. package/src/tools/browser/benchmark/detector-report.ts +157 -0
  389. package/src/tools/browser/benchmark/run-suite.ts +39 -0
  390. package/src/tools/browser/dead-tab-recovery.ts +78 -0
  391. package/src/tools/browser/launch.ts +127 -20
  392. package/src/tools/browser/profile-discovery.ts +63 -0
  393. package/src/tools/browser/profile-posture.ts +59 -0
  394. package/src/tools/browser/profile-reuse.ts +102 -0
  395. package/src/tools/browser/profile-warmup.ts +109 -0
  396. package/src/tools/browser/readable.ts +1 -1
  397. package/src/tools/browser/registry.ts +87 -11
  398. package/src/tools/browser/screenshot-format.ts +34 -0
  399. package/src/tools/browser/tab-protocol.ts +4 -0
  400. package/src/tools/browser/tab-supervisor.ts +392 -142
  401. package/src/tools/browser/tab-worker-entry.ts +33 -14
  402. package/src/tools/browser/tab-worker-path-resolver.ts +67 -0
  403. package/src/tools/browser/tab-worker-smoke.ts +87 -0
  404. package/src/tools/browser/tab-worker.ts +8 -6
  405. package/src/tools/browser.ts +6 -0
  406. package/src/tools/computer.ts +10 -3
  407. package/src/tools/cron.ts +30 -16
  408. package/src/tools/fetch.ts +11 -4
  409. package/src/tools/index.ts +88 -32
  410. package/src/tools/output-meta.ts +19 -7
  411. package/src/tools/path-utils.ts +42 -35
  412. package/src/tools/plan-mode-guard.ts +1 -0
  413. package/src/tools/puppeteer/14_stealth_permissions.txt +57 -0
  414. package/src/tools/puppeteer/15_stealth_webrtc.txt +60 -0
  415. package/src/tools/read.ts +226 -65
  416. package/src/tools/resource-gc.ts +125 -36
  417. package/src/tools/search-tool-bm25.ts +11 -74
  418. package/src/tools/sqlite-reader.ts +3 -2
  419. package/src/tools/subagent-render.ts +19 -8
  420. package/src/tools/subagent.ts +129 -34
  421. package/src/tools/telegram-send.ts +2 -2
  422. package/src/utils/event-bus.ts +9 -4
  423. package/src/utils/file-mentions.ts +4 -4
  424. package/src/utils/image-loading.ts +85 -34
  425. package/src/utils/image-resize.ts +16 -9
  426. package/src/utils/pasted-image-loading.ts +514 -0
  427. package/src/utils/pasted-image-path.ts +223 -98
  428. package/src/web/scrapers/html-to-markdown.ts +68 -0
  429. package/src/web/scrapers/readthedocs.ts +1 -1
  430. package/src/web/scrapers/types.ts +1 -68
  431. package/src/web/search/index.ts +3 -3
  432. package/src/web/search/providers/openai-compatible.ts +76 -23
  433. package/dist/types/append-only-mode.d.ts +0 -31
  434. package/dist/types/async/index.d.ts +0 -2
  435. package/dist/types/async/job-manager.d.ts +0 -328
  436. package/dist/types/async/support.d.ts +0 -2
  437. package/dist/types/autoresearch/dashboard.d.ts +0 -4
  438. package/dist/types/autoresearch/git.d.ts +0 -36
  439. package/dist/types/autoresearch/helpers.d.ts +0 -24
  440. package/dist/types/autoresearch/index.d.ts +0 -2
  441. package/dist/types/autoresearch/state.d.ts +0 -17
  442. package/dist/types/autoresearch/storage.d.ts +0 -142
  443. package/dist/types/autoresearch/tools/init-experiment.d.ts +0 -31
  444. package/dist/types/autoresearch/tools/log-experiment.d.ts +0 -23
  445. package/dist/types/autoresearch/tools/run-experiment.d.ts +0 -8
  446. package/dist/types/autoresearch/tools/update-notes.d.ts +0 -12
  447. package/dist/types/autoresearch/types.d.ts +0 -154
  448. package/dist/types/build-metadata.d.ts +0 -7
  449. package/dist/types/capability/context-file.d.ts +0 -17
  450. package/dist/types/capability/extension-module.d.ts +0 -15
  451. package/dist/types/capability/extension.d.ts +0 -28
  452. package/dist/types/capability/fs.d.ts +0 -20
  453. package/dist/types/capability/hook.d.ts +0 -19
  454. package/dist/types/capability/index.d.ts +0 -80
  455. package/dist/types/capability/instruction.d.ts +0 -17
  456. package/dist/types/capability/mcp.d.ts +0 -49
  457. package/dist/types/capability/prompt.d.ts +0 -15
  458. package/dist/types/capability/rule.d.ts +0 -69
  459. package/dist/types/capability/settings.d.ts +0 -15
  460. package/dist/types/capability/skill.d.ts +0 -36
  461. package/dist/types/capability/slash-command.d.ts +0 -17
  462. package/dist/types/capability/ssh.d.ts +0 -23
  463. package/dist/types/capability/system-prompt.d.ts +0 -15
  464. package/dist/types/capability/tool.d.ts +0 -19
  465. package/dist/types/capability/types.d.ts +0 -154
  466. package/dist/types/cli/agents-cli.d.ts +0 -12
  467. package/dist/types/cli/args.d.ts +0 -57
  468. package/dist/types/cli/auth-broker-cli.d.ts +0 -25
  469. package/dist/types/cli/auth-gateway-cli.d.ts +0 -18
  470. package/dist/types/cli/classify-install-target.d.ts +0 -8
  471. package/dist/types/cli/commands/init-xdg.d.ts +0 -1
  472. package/dist/types/cli/completion-cli.d.ts +0 -41
  473. package/dist/types/cli/config-cli.d.ts +0 -23
  474. package/dist/types/cli/daemon-cli.d.ts +0 -27
  475. package/dist/types/cli/fast-help.d.ts +0 -1
  476. package/dist/types/cli/file-processor.d.ts +0 -11
  477. package/dist/types/cli/fixture-report.d.ts +0 -20
  478. package/dist/types/cli/fixture-report.test.d.ts +0 -1
  479. package/dist/types/cli/grep-cli.d.ts +0 -17
  480. package/dist/types/cli/initial-message.d.ts +0 -17
  481. package/dist/types/cli/list-models.d.ts +0 -36
  482. package/dist/types/cli/local-provider-smoke.d.ts +0 -59
  483. package/dist/types/cli/malloc-env-guard.d.ts +0 -65
  484. package/dist/types/cli/malloc-env-guard.test.d.ts +0 -1
  485. package/dist/types/cli/mcp-cli.d.ts +0 -25
  486. package/dist/types/cli/migrate-cli.d.ts +0 -20
  487. package/dist/types/cli/nofile-limit.d.ts +0 -11
  488. package/dist/types/cli/notify-cli.d.ts +0 -39
  489. package/dist/types/cli/plugin-cli.d.ts +0 -34
  490. package/dist/types/cli/read-cli.d.ts +0 -4
  491. package/dist/types/cli/session-picker.d.ts +0 -4
  492. package/dist/types/cli/setup-cli.d.ts +0 -73
  493. package/dist/types/cli/shell-cli.d.ts +0 -8
  494. package/dist/types/cli/skills-cli.d.ts +0 -9
  495. package/dist/types/cli/ssh-cli.d.ts +0 -21
  496. package/dist/types/cli/stats-cli.d.ts +0 -17
  497. package/dist/types/cli/telegram-cli.d.ts +0 -13
  498. package/dist/types/cli/update-cli.d.ts +0 -108
  499. package/dist/types/cli/web-search-cli.d.ts +0 -32
  500. package/dist/types/cli/worktree-cli.d.ts +0 -26
  501. package/dist/types/cli.d.ts +0 -140
  502. package/dist/types/commands/acp.d.ts +0 -12
  503. package/dist/types/commands/agents.d.ts +0 -34
  504. package/dist/types/commands/auth-broker.d.ts +0 -54
  505. package/dist/types/commands/auth-gateway.d.ts +0 -32
  506. package/dist/types/commands/codex-native-hook.d.ts +0 -6
  507. package/dist/types/commands/commit.d.ts +0 -27
  508. package/dist/types/commands/completion.d.ts +0 -29
  509. package/dist/types/commands/config.d.ts +0 -33
  510. package/dist/types/commands/contribution-prep.d.ts +0 -18
  511. package/dist/types/commands/coordinator.d.ts +0 -19
  512. package/dist/types/commands/daemon.d.ts +0 -45
  513. package/dist/types/commands/deep-interview.d.ts +0 -54
  514. package/dist/types/commands/gc.d.ts +0 -26
  515. package/dist/types/commands/grep.d.ts +0 -42
  516. package/dist/types/commands/harness.d.ts +0 -43
  517. package/dist/types/commands/launch.d.ts +0 -131
  518. package/dist/types/commands/local-provider.d.ts +0 -34
  519. package/dist/types/commands/mcp-serve.d.ts +0 -24
  520. package/dist/types/commands/mcp.d.ts +0 -70
  521. package/dist/types/commands/migrate.d.ts +0 -33
  522. package/dist/types/commands/notify.d.ts +0 -47
  523. package/dist/types/commands/plugin.d.ts +0 -58
  524. package/dist/types/commands/ralplan.d.ts +0 -7
  525. package/dist/types/commands/read.d.ts +0 -15
  526. package/dist/types/commands/rlm.d.ts +0 -10
  527. package/dist/types/commands/session.d.ts +0 -30
  528. package/dist/types/commands/setup.d.ts +0 -105
  529. package/dist/types/commands/shell.d.ts +0 -21
  530. package/dist/types/commands/skills.d.ts +0 -26
  531. package/dist/types/commands/ssh.d.ts +0 -48
  532. package/dist/types/commands/state.d.ts +0 -7
  533. package/dist/types/commands/stats.d.ts +0 -25
  534. package/dist/types/commands/team.d.ts +0 -24
  535. package/dist/types/commands/telegram.d.ts +0 -22
  536. package/dist/types/commands/ultragoal.d.ts +0 -8
  537. package/dist/types/commands/update.d.ts +0 -20
  538. package/dist/types/commands/web-search.d.ts +0 -87
  539. package/dist/types/commands/worktree.d.ts +0 -34
  540. package/dist/types/commit/agentic/agent.d.ts +0 -31
  541. package/dist/types/commit/agentic/fallback.d.ts +0 -5
  542. package/dist/types/commit/agentic/index.d.ts +0 -2
  543. package/dist/types/commit/agentic/state.d.ts +0 -58
  544. package/dist/types/commit/agentic/tools/analyze-file.d.ts +0 -19
  545. package/dist/types/commit/agentic/tools/git-file-diff.d.ts +0 -10
  546. package/dist/types/commit/agentic/tools/git-hunk.d.ts +0 -9
  547. package/dist/types/commit/agentic/tools/git-overview.d.ts +0 -9
  548. package/dist/types/commit/agentic/tools/index.d.ts +0 -16
  549. package/dist/types/commit/agentic/tools/propose-changelog.d.ts +0 -28
  550. package/dist/types/commit/agentic/tools/propose-commit.d.ts +0 -36
  551. package/dist/types/commit/agentic/tools/recent-commits.d.ts +0 -7
  552. package/dist/types/commit/agentic/tools/schemas.d.ts +0 -27
  553. package/dist/types/commit/agentic/tools/split-commit.d.ts +0 -53
  554. package/dist/types/commit/agentic/topo-sort.d.ts +0 -4
  555. package/dist/types/commit/agentic/trivial.d.ts +0 -7
  556. package/dist/types/commit/agentic/validation.d.ts +0 -20
  557. package/dist/types/commit/analysis/conventional.d.ts +0 -22
  558. package/dist/types/commit/analysis/index.d.ts +0 -4
  559. package/dist/types/commit/analysis/scope.d.ts +0 -6
  560. package/dist/types/commit/analysis/summary.d.ts +0 -19
  561. package/dist/types/commit/analysis/validation.d.ts +0 -8
  562. package/dist/types/commit/changelog/detect.d.ts +0 -2
  563. package/dist/types/commit/changelog/generate.d.ts +0 -30
  564. package/dist/types/commit/changelog/index.d.ts +0 -30
  565. package/dist/types/commit/changelog/parse.d.ts +0 -2
  566. package/dist/types/commit/cli.d.ts +0 -3
  567. package/dist/types/commit/git/diff.d.ts +0 -5
  568. package/dist/types/commit/index.d.ts +0 -4
  569. package/dist/types/commit/map-reduce/index.d.ts +0 -28
  570. package/dist/types/commit/map-reduce/map-phase.d.ts +0 -17
  571. package/dist/types/commit/map-reduce/reduce-phase.d.ts +0 -13
  572. package/dist/types/commit/map-reduce/utils.d.ts +0 -2
  573. package/dist/types/commit/message.d.ts +0 -2
  574. package/dist/types/commit/model-selection.d.ts +0 -15
  575. package/dist/types/commit/pipeline.d.ts +0 -5
  576. package/dist/types/commit/shared-llm.d.ts +0 -54
  577. package/dist/types/commit/types.d.ts +0 -78
  578. package/dist/types/commit/utils/exclusions.d.ts +0 -4
  579. package/dist/types/commit/utils.d.ts +0 -20
  580. package/dist/types/config/config-file.d.ts +0 -58
  581. package/dist/types/config/file-lock-gc.d.ts +0 -5
  582. package/dist/types/config/file-lock.d.ts +0 -36
  583. package/dist/types/config/keybindings.d.ts +0 -364
  584. package/dist/types/config/model-equivalence.d.ts +0 -24
  585. package/dist/types/config/model-profile-activation.d.ts +0 -65
  586. package/dist/types/config/model-profiles.d.ts +0 -39
  587. package/dist/types/config/model-registry.d.ts +0 -473
  588. package/dist/types/config/model-resolver.d.ts +0 -217
  589. package/dist/types/config/models-config-schema.d.ts +0 -700
  590. package/dist/types/config/prompt-templates.d.ts +0 -32
  591. package/dist/types/config/resolve-config-value.d.ts +0 -17
  592. package/dist/types/config/settings-schema.d.ts +0 -4223
  593. package/dist/types/config/settings.d.ts +0 -158
  594. package/dist/types/config/skill-settings-defaults.d.ts +0 -14
  595. package/dist/types/config/telegram-autostart.d.ts +0 -14
  596. package/dist/types/config/telegram-env-bridge.d.ts +0 -18
  597. package/dist/types/config.d.ts +0 -66
  598. package/dist/types/coordinator/contract.d.ts +0 -4
  599. package/dist/types/coordinator-mcp/model-preset.d.ts +0 -45
  600. package/dist/types/coordinator-mcp/policy.d.ts +0 -27
  601. package/dist/types/coordinator-mcp/safety.d.ts +0 -26
  602. package/dist/types/coordinator-mcp/server.d.ts +0 -139
  603. package/dist/types/coordinator-mcp/session-reaper.d.ts +0 -53
  604. package/dist/types/cursor.d.ts +0 -24
  605. package/dist/types/daemon/builtin.d.ts +0 -20
  606. package/dist/types/daemon/control-types.d.ts +0 -67
  607. package/dist/types/daemon/operator-contract.d.ts +0 -47
  608. package/dist/types/daemon/runtime.d.ts +0 -25
  609. package/dist/types/dap/client.d.ts +0 -39
  610. package/dist/types/dap/config.d.ts +0 -6
  611. package/dist/types/dap/index.d.ts +0 -4
  612. package/dist/types/dap/session.d.ts +0 -108
  613. package/dist/types/dap/types.d.ts +0 -524
  614. package/dist/types/debug/crash-diagnostics.d.ts +0 -45
  615. package/dist/types/debug/index.d.ts +0 -15
  616. package/dist/types/debug/log-formatting.d.ts +0 -4
  617. package/dist/types/debug/log-viewer.d.ts +0 -68
  618. package/dist/types/debug/profiler.d.ts +0 -24
  619. package/dist/types/debug/raw-sse-buffer.d.ts +0 -44
  620. package/dist/types/debug/raw-sse.d.ts +0 -16
  621. package/dist/types/debug/report-bundle.d.ts +0 -55
  622. package/dist/types/debug/runtime-gauges.d.ts +0 -6
  623. package/dist/types/debug/system-info.d.ts +0 -26
  624. package/dist/types/deep-interview/plaintext-gate-guard.d.ts +0 -11
  625. package/dist/types/deep-interview/render-middleware.d.ts +0 -6
  626. package/dist/types/defaults/skc/extensions/grok-build/index.d.ts +0 -1
  627. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/index.d.ts +0 -1
  628. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.d.ts +0 -29
  629. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/payload/sanitize.d.ts +0 -27
  630. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/provider/billing.d.ts +0 -8
  631. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/provider/register.d.ts +0 -5
  632. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/provider/stream.d.ts +0 -10
  633. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/provider/usage.d.ts +0 -2
  634. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/shared/base-url.d.ts +0 -2
  635. package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/shared/errors.d.ts +0 -38
  636. package/dist/types/defaults/skc-defaults.d.ts +0 -64
  637. package/dist/types/defaults/skc-grok-cli.d.ts +0 -5
  638. package/dist/types/discovery/agents-md.d.ts +0 -1
  639. package/dist/types/discovery/agents.d.ts +0 -12
  640. package/dist/types/discovery/builtin.d.ts +0 -1
  641. package/dist/types/discovery/claude-plugins.d.ts +0 -1
  642. package/dist/types/discovery/claude.d.ts +0 -1
  643. package/dist/types/discovery/cline.d.ts +0 -1
  644. package/dist/types/discovery/codex.d.ts +0 -1
  645. package/dist/types/discovery/cursor.d.ts +0 -19
  646. package/dist/types/discovery/gemini.d.ts +0 -1
  647. package/dist/types/discovery/github.d.ts +0 -1
  648. package/dist/types/discovery/helpers.d.ts +0 -271
  649. package/dist/types/discovery/index.d.ts +0 -48
  650. package/dist/types/discovery/mcp-json.d.ts +0 -1
  651. package/dist/types/discovery/opencode.d.ts +0 -1
  652. package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
  653. package/dist/types/discovery/ssh.d.ts +0 -1
  654. package/dist/types/discovery/substitute-plugin-root.d.ts +0 -1
  655. package/dist/types/discovery/vscode.d.ts +0 -1
  656. package/dist/types/discovery/windsurf.d.ts +0 -15
  657. package/dist/types/edit/apply-patch/index.d.ts +0 -35
  658. package/dist/types/edit/apply-patch/parser.d.ts +0 -34
  659. package/dist/types/edit/diff.d.ts +0 -75
  660. package/dist/types/edit/file-read-cache.d.ts +0 -25
  661. package/dist/types/edit/index.d.ts +0 -58
  662. package/dist/types/edit/modes/apply-patch.d.ts +0 -24
  663. package/dist/types/edit/modes/patch.d.ts +0 -99
  664. package/dist/types/edit/modes/replace.d.ts +0 -149
  665. package/dist/types/edit/normalize.d.ts +0 -43
  666. package/dist/types/edit/notebook.d.ts +0 -26
  667. package/dist/types/edit/read-file.d.ts +0 -8
  668. package/dist/types/edit/renderer.d.ts +0 -110
  669. package/dist/types/edit/streaming.d.ts +0 -66
  670. package/dist/types/eval/backend.d.ts +0 -40
  671. package/dist/types/eval/index.d.ts +0 -4
  672. package/dist/types/eval/js/context-manager.d.ts +0 -27
  673. package/dist/types/eval/js/executor.d.ts +0 -29
  674. package/dist/types/eval/js/index.d.ts +0 -10
  675. package/dist/types/eval/js/shared/helpers.d.ts +0 -38
  676. package/dist/types/eval/js/shared/indirect-eval.d.ts +0 -14
  677. package/dist/types/eval/js/shared/prelude.d.ts +0 -1
  678. package/dist/types/eval/js/shared/rewrite-imports.d.ts +0 -6
  679. package/dist/types/eval/js/shared/runtime.d.ts +0 -47
  680. package/dist/types/eval/js/shared/types.d.ts +0 -24
  681. package/dist/types/eval/js/tool-bridge.d.ts +0 -18
  682. package/dist/types/eval/js/worker-core.d.ts +0 -5
  683. package/dist/types/eval/js/worker-entry.d.ts +0 -1
  684. package/dist/types/eval/js/worker-protocol.d.ts +0 -77
  685. package/dist/types/eval/py/display.d.ts +0 -25
  686. package/dist/types/eval/py/executor.d.ts +0 -88
  687. package/dist/types/eval/py/index.d.ts +0 -10
  688. package/dist/types/eval/py/kernel.d.ts +0 -65
  689. package/dist/types/eval/py/prelude.d.ts +0 -1
  690. package/dist/types/eval/py/runtime.d.ts +0 -29
  691. package/dist/types/eval/py/tool-bridge.d.ts +0 -20
  692. package/dist/types/eval/types.d.ts +0 -52
  693. package/dist/types/exa/factory.d.ts +0 -13
  694. package/dist/types/exa/index.d.ts +0 -20
  695. package/dist/types/exa/mcp-client.d.ts +0 -45
  696. package/dist/types/exa/render.d.ts +0 -19
  697. package/dist/types/exa/researcher.d.ts +0 -9
  698. package/dist/types/exa/search.d.ts +0 -9
  699. package/dist/types/exa/types.d.ts +0 -155
  700. package/dist/types/exa/websets.d.ts +0 -9
  701. package/dist/types/exec/bash-executor.d.ts +0 -64
  702. package/dist/types/exec/exec.d.ts +0 -25
  703. package/dist/types/exec/idle-timeout-watchdog.d.ts +0 -18
  704. package/dist/types/exec/non-interactive-env.d.ts +0 -1
  705. package/dist/types/export/custom-share.d.ts +0 -20
  706. package/dist/types/export/html/index.d.ts +0 -10
  707. package/dist/types/export/html/template.generated.d.ts +0 -1
  708. package/dist/types/export/html/template.macro.d.ts +0 -5
  709. package/dist/types/export/ttsr.d.ts +0 -55
  710. package/dist/types/extensibility/custom-commands/bundled/ci-green/index.d.ts +0 -9
  711. package/dist/types/extensibility/custom-commands/index.d.ts +0 -2
  712. package/dist/types/extensibility/custom-commands/loader.d.ts +0 -29
  713. package/dist/types/extensibility/custom-commands/types.d.ts +0 -106
  714. package/dist/types/extensibility/custom-tools/index.d.ts +0 -6
  715. package/dist/types/extensibility/custom-tools/loader.d.ts +0 -69
  716. package/dist/types/extensibility/custom-tools/types.d.ts +0 -229
  717. package/dist/types/extensibility/custom-tools/wrapper.d.ts +0 -24
  718. package/dist/types/extensibility/extensions/compact-handler.d.ts +0 -26
  719. package/dist/types/extensibility/extensions/get-commands-handler.d.ts +0 -29
  720. package/dist/types/extensibility/extensions/index.d.ts +0 -9
  721. package/dist/types/extensibility/extensions/loader.d.ts +0 -43
  722. package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +0 -35
  723. package/dist/types/extensibility/extensions/runner.d.ts +0 -135
  724. package/dist/types/extensibility/extensions/types.d.ts +0 -920
  725. package/dist/types/extensibility/extensions/wrapper.d.ts +0 -55
  726. package/dist/types/extensibility/hooks/index.d.ts +0 -5
  727. package/dist/types/extensibility/hooks/loader.d.ts +0 -89
  728. package/dist/types/extensibility/hooks/runner.d.ts +0 -126
  729. package/dist/types/extensibility/hooks/tool-wrapper.d.ts +0 -25
  730. package/dist/types/extensibility/hooks/types.d.ts +0 -431
  731. package/dist/types/extensibility/plugins/doctor.d.ts +0 -3
  732. package/dist/types/extensibility/plugins/git-url.d.ts +0 -34
  733. package/dist/types/extensibility/plugins/index.d.ts +0 -8
  734. package/dist/types/extensibility/plugins/installer.d.ts +0 -5
  735. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +0 -2
  736. package/dist/types/extensibility/plugins/loader.d.ts +0 -31
  737. package/dist/types/extensibility/plugins/manager.d.ts +0 -65
  738. package/dist/types/extensibility/plugins/marketplace/cache.d.ts +0 -41
  739. package/dist/types/extensibility/plugins/marketplace/fetcher.d.ts +0 -48
  740. package/dist/types/extensibility/plugins/marketplace/index.d.ts +0 -6
  741. package/dist/types/extensibility/plugins/marketplace/manager.d.ts +0 -57
  742. package/dist/types/extensibility/plugins/marketplace/registry.d.ts +0 -34
  743. package/dist/types/extensibility/plugins/marketplace/source-resolver.d.ts +0 -28
  744. package/dist/types/extensibility/plugins/marketplace/types.d.ts +0 -130
  745. package/dist/types/extensibility/plugins/parser.d.ts +0 -52
  746. package/dist/types/extensibility/plugins/security-scanner.d.ts +0 -31
  747. package/dist/types/extensibility/plugins/types.d.ts +0 -149
  748. package/dist/types/extensibility/runtime-skill-discovery.d.ts +0 -20
  749. package/dist/types/extensibility/shared-events.d.ts +0 -281
  750. package/dist/types/extensibility/skc-plugins/activation.d.ts +0 -14
  751. package/dist/types/extensibility/skc-plugins/compiler.d.ts +0 -19
  752. package/dist/types/extensibility/skc-plugins/constrained-hooks.d.ts +0 -29
  753. package/dist/types/extensibility/skc-plugins/index.d.ts +0 -18
  754. package/dist/types/extensibility/skc-plugins/injection.d.ts +0 -40
  755. package/dist/types/extensibility/skc-plugins/installer.d.ts +0 -13
  756. package/dist/types/extensibility/skc-plugins/loader.d.ts +0 -3
  757. package/dist/types/extensibility/skc-plugins/mcp-policy.d.ts +0 -26
  758. package/dist/types/extensibility/skc-plugins/observability.d.ts +0 -27
  759. package/dist/types/extensibility/skc-plugins/paths.d.ts +0 -8
  760. package/dist/types/extensibility/skc-plugins/prompt-appendix.d.ts +0 -16
  761. package/dist/types/extensibility/skc-plugins/registry.d.ts +0 -32
  762. package/dist/types/extensibility/skc-plugins/runtime-adapters.d.ts +0 -64
  763. package/dist/types/extensibility/skc-plugins/schema.d.ts +0 -3
  764. package/dist/types/extensibility/skc-plugins/session-validation.d.ts +0 -42
  765. package/dist/types/extensibility/skc-plugins/state.d.ts +0 -9
  766. package/dist/types/extensibility/skc-plugins/tools.d.ts +0 -8
  767. package/dist/types/extensibility/skc-plugins/types.d.ts +0 -220
  768. package/dist/types/extensibility/skc-plugins/validation.d.ts +0 -11
  769. package/dist/types/extensibility/skills.d.ts +0 -82
  770. package/dist/types/extensibility/slash-commands.d.ts +0 -48
  771. package/dist/types/extensibility/tool-proxy.d.ts +0 -4
  772. package/dist/types/extensibility/typebox.d.ts +0 -155
  773. package/dist/types/extensibility/utils.d.ts +0 -12
  774. package/dist/types/goals/index.d.ts +0 -3
  775. package/dist/types/goals/runtime.d.ts +0 -63
  776. package/dist/types/goals/state.d.ts +0 -32
  777. package/dist/types/goals/tools/goal-tool.d.ts +0 -62
  778. package/dist/types/harness-control-plane/classifier.d.ts +0 -13
  779. package/dist/types/harness-control-plane/control-endpoint.d.ts +0 -31
  780. package/dist/types/harness-control-plane/finalize.d.ts +0 -60
  781. package/dist/types/harness-control-plane/gc-adapter.d.ts +0 -3
  782. package/dist/types/harness-control-plane/operate.d.ts +0 -35
  783. package/dist/types/harness-control-plane/owner.d.ts +0 -53
  784. package/dist/types/harness-control-plane/phase-rollup.d.ts +0 -23
  785. package/dist/types/harness-control-plane/preserve.d.ts +0 -19
  786. package/dist/types/harness-control-plane/receipt-ingest.d.ts +0 -19
  787. package/dist/types/harness-control-plane/receipt-spool.d.ts +0 -19
  788. package/dist/types/harness-control-plane/receipts.d.ts +0 -149
  789. package/dist/types/harness-control-plane/rpc-adapter.d.ts +0 -69
  790. package/dist/types/harness-control-plane/seams.d.ts +0 -21
  791. package/dist/types/harness-control-plane/session-lease.d.ts +0 -65
  792. package/dist/types/harness-control-plane/state-machine.d.ts +0 -24
  793. package/dist/types/harness-control-plane/storage.d.ts +0 -81
  794. package/dist/types/harness-control-plane/types.d.ts +0 -187
  795. package/dist/types/hashline/anchors.d.ts +0 -20
  796. package/dist/types/hashline/apply.d.ts +0 -14
  797. package/dist/types/hashline/constants.d.ts +0 -19
  798. package/dist/types/hashline/diff-preview.d.ts +0 -2
  799. package/dist/types/hashline/diff.d.ts +0 -17
  800. package/dist/types/hashline/execute.d.ts +0 -4
  801. package/dist/types/hashline/hash.d.ts +0 -123
  802. package/dist/types/hashline/index.d.ts +0 -13
  803. package/dist/types/hashline/input.d.ts +0 -11
  804. package/dist/types/hashline/parser.d.ts +0 -7
  805. package/dist/types/hashline/prefixes.d.ts +0 -7
  806. package/dist/types/hashline/recovery.d.ts +0 -27
  807. package/dist/types/hashline/stream.d.ts +0 -2
  808. package/dist/types/hashline/types.d.ts +0 -77
  809. package/dist/types/hindsight/backend.d.ts +0 -13
  810. package/dist/types/hindsight/bank.d.ts +0 -54
  811. package/dist/types/hindsight/client.d.ts +0 -224
  812. package/dist/types/hindsight/config.d.ts +0 -51
  813. package/dist/types/hindsight/content.d.ts +0 -70
  814. package/dist/types/hindsight/index.d.ts +0 -8
  815. package/dist/types/hindsight/mental-models.d.ts +0 -125
  816. package/dist/types/hindsight/state.d.ts +0 -99
  817. package/dist/types/hindsight/transcript.d.ts +0 -28
  818. package/dist/types/hooks/codex-native-hooks-config.d.ts +0 -29
  819. package/dist/types/hooks/native-skill-hook.d.ts +0 -28
  820. package/dist/types/hooks/skill-keywords.d.ts +0 -18
  821. package/dist/types/hooks/skill-state.d.ts +0 -93
  822. package/dist/types/i18n/index.d.ts +0 -38
  823. package/dist/types/i18n/messages/de.d.ts +0 -2
  824. package/dist/types/i18n/messages/de.messages.d.ts +0 -1
  825. package/dist/types/i18n/messages/de.settings.d.ts +0 -1
  826. package/dist/types/i18n/messages/en.d.ts +0 -81
  827. package/dist/types/i18n/messages/es.d.ts +0 -2
  828. package/dist/types/i18n/messages/es.messages.d.ts +0 -1
  829. package/dist/types/i18n/messages/es.settings.d.ts +0 -1
  830. package/dist/types/i18n/messages/fr.d.ts +0 -2
  831. package/dist/types/i18n/messages/fr.messages.d.ts +0 -1
  832. package/dist/types/i18n/messages/fr.settings.d.ts +0 -1
  833. package/dist/types/i18n/messages/ja.d.ts +0 -2
  834. package/dist/types/i18n/messages/ja.messages.d.ts +0 -1
  835. package/dist/types/i18n/messages/ja.settings.d.ts +0 -1
  836. package/dist/types/i18n/messages/ko.d.ts +0 -2
  837. package/dist/types/i18n/messages/ko.messages.d.ts +0 -1
  838. package/dist/types/i18n/messages/ko.settings.d.ts +0 -1
  839. package/dist/types/i18n/messages/zh.d.ts +0 -2
  840. package/dist/types/i18n/messages/zh.messages.d.ts +0 -1
  841. package/dist/types/i18n/messages/zh.settings.d.ts +0 -1
  842. package/dist/types/index.d.ts +0 -33
  843. package/dist/types/internal-urls/agent-protocol.d.ts +0 -12
  844. package/dist/types/internal-urls/artifact-protocol.d.ts +0 -6
  845. package/dist/types/internal-urls/docs-index.generated.d.ts +0 -2
  846. package/dist/types/internal-urls/index.d.ts +0 -20
  847. package/dist/types/internal-urls/issue-pr-protocol.d.ts +0 -17
  848. package/dist/types/internal-urls/json-query.d.ts +0 -30
  849. package/dist/types/internal-urls/local-protocol.d.ts +0 -43
  850. package/dist/types/internal-urls/mcp-protocol.d.ts +0 -12
  851. package/dist/types/internal-urls/memory-protocol.d.ts +0 -23
  852. package/dist/types/internal-urls/parse.d.ts +0 -19
  853. package/dist/types/internal-urls/registry-helpers.d.ts +0 -11
  854. package/dist/types/internal-urls/router.d.ts +0 -14
  855. package/dist/types/internal-urls/rule-protocol.d.ts +0 -6
  856. package/dist/types/internal-urls/skc-protocol.d.ts +0 -12
  857. package/dist/types/internal-urls/skill-protocol.d.ts +0 -13
  858. package/dist/types/internal-urls/types.d.ts +0 -109
  859. package/dist/types/lsp/client.d.ts +0 -66
  860. package/dist/types/lsp/clients/biome-client.d.ts +0 -16
  861. package/dist/types/lsp/clients/index.d.ts +0 -19
  862. package/dist/types/lsp/clients/lsp-linter-client.d.ts +0 -16
  863. package/dist/types/lsp/clients/swiftlint-client.d.ts +0 -20
  864. package/dist/types/lsp/config.d.ts +0 -65
  865. package/dist/types/lsp/edits.d.ts +0 -23
  866. package/dist/types/lsp/index.d.ts +0 -130
  867. package/dist/types/lsp/lspmux.d.ts +0 -58
  868. package/dist/types/lsp/render.d.ts +0 -32
  869. package/dist/types/lsp/startup-events.d.ts +0 -12
  870. package/dist/types/lsp/types.d.ts +0 -306
  871. package/dist/types/lsp/utils.d.ts +0 -108
  872. package/dist/types/main.d.ts +0 -139
  873. package/dist/types/memories/index.d.ts +0 -28
  874. package/dist/types/memories/storage.d.ts +0 -110
  875. package/dist/types/memory-backend/index.d.ts +0 -4
  876. package/dist/types/memory-backend/local-backend.d.ts +0 -9
  877. package/dist/types/memory-backend/off-backend.d.ts +0 -7
  878. package/dist/types/memory-backend/resolve.d.ts +0 -15
  879. package/dist/types/memory-backend/types.d.ts +0 -61
  880. package/dist/types/migrate/action-planner.d.ts +0 -11
  881. package/dist/types/migrate/adapters/claude-code.d.ts +0 -2
  882. package/dist/types/migrate/adapters/codex.d.ts +0 -5
  883. package/dist/types/migrate/adapters/index.d.ts +0 -45
  884. package/dist/types/migrate/adapters/opencode.d.ts +0 -2
  885. package/dist/types/migrate/executor.d.ts +0 -2
  886. package/dist/types/migrate/mcp-mapper.d.ts +0 -20
  887. package/dist/types/migrate/report.d.ts +0 -18
  888. package/dist/types/migrate/skill-normalizer.d.ts +0 -27
  889. package/dist/types/migrate/types.d.ts +0 -126
  890. package/dist/types/modes/acp/acp-agent.d.ts +0 -78
  891. package/dist/types/modes/acp/acp-client-bridge.d.ts +0 -9
  892. package/dist/types/modes/acp/acp-event-mapper.d.ts +0 -36
  893. package/dist/types/modes/acp/acp-mode.d.ts +0 -17
  894. package/dist/types/modes/acp/index.d.ts +0 -2
  895. package/dist/types/modes/acp/terminal-auth.d.ts +0 -6
  896. package/dist/types/modes/bridge/auth.d.ts +0 -12
  897. package/dist/types/modes/bridge/bridge-client-bridge.d.ts +0 -9
  898. package/dist/types/modes/bridge/bridge-mode.d.ts +0 -47
  899. package/dist/types/modes/bridge/bridge-ui-context.d.ts +0 -88
  900. package/dist/types/modes/bridge/event-stream.d.ts +0 -8
  901. package/dist/types/modes/components/agent-dashboard.d.ts +0 -21
  902. package/dist/types/modes/components/assistant-message.d.ts +0 -20
  903. package/dist/types/modes/components/bash-execution.d.ts +0 -29
  904. package/dist/types/modes/components/bordered-loader.d.ts +0 -11
  905. package/dist/types/modes/components/branch-summary-message.d.ts +0 -13
  906. package/dist/types/modes/components/btw-panel.d.ts +0 -16
  907. package/dist/types/modes/components/compaction-summary-message.d.ts +0 -13
  908. package/dist/types/modes/components/countdown-timer.d.ts +0 -14
  909. package/dist/types/modes/components/custom-editor.d.ts +0 -69
  910. package/dist/types/modes/components/custom-message.d.ts +0 -15
  911. package/dist/types/modes/components/custom-model-preset-wizard.d.ts +0 -12
  912. package/dist/types/modes/components/custom-provider-wizard.d.ts +0 -10
  913. package/dist/types/modes/components/diff.d.ts +0 -11
  914. package/dist/types/modes/components/dynamic-border.d.ts +0 -14
  915. package/dist/types/modes/components/eval-execution.d.ts +0 -23
  916. package/dist/types/modes/components/execution-shared.d.ts +0 -46
  917. package/dist/types/modes/components/extensions/extension-dashboard.d.ts +0 -26
  918. package/dist/types/modes/components/extensions/extension-list.d.ts +0 -39
  919. package/dist/types/modes/components/extensions/index.d.ts +0 -8
  920. package/dist/types/modes/components/extensions/inspector-panel.d.ts +0 -8
  921. package/dist/types/modes/components/extensions/state-manager.d.ts +0 -50
  922. package/dist/types/modes/components/extensions/types.d.ts +0 -151
  923. package/dist/types/modes/components/footer.d.ts +0 -30
  924. package/dist/types/modes/components/history-search.d.ts +0 -7
  925. package/dist/types/modes/components/hook-editor.d.ts +0 -18
  926. package/dist/types/modes/components/hook-input.d.ts +0 -15
  927. package/dist/types/modes/components/hook-message.d.ts +0 -15
  928. package/dist/types/modes/components/hook-selector.d.ts +0 -59
  929. package/dist/types/modes/components/index.d.ts +0 -35
  930. package/dist/types/modes/components/irc-sidebar.d.ts +0 -32
  931. package/dist/types/modes/components/jobs-overlay-model.d.ts +0 -31
  932. package/dist/types/modes/components/jobs-overlay.d.ts +0 -30
  933. package/dist/types/modes/components/keybinding-hints.d.ts +0 -40
  934. package/dist/types/modes/components/login-dialog.d.ts +0 -32
  935. package/dist/types/modes/components/message-frame.d.ts +0 -42
  936. package/dist/types/modes/components/model-selector.d.ts +0 -60
  937. package/dist/types/modes/components/oauth-selector.d.ts +0 -17
  938. package/dist/types/modes/components/pet-selector.d.ts +0 -11
  939. package/dist/types/modes/components/plugin-selector.d.ts +0 -26
  940. package/dist/types/modes/components/plugin-settings.d.ts +0 -66
  941. package/dist/types/modes/components/provider-onboarding-selector.d.ts +0 -7
  942. package/dist/types/modes/components/queue-mode-selector.d.ts +0 -9
  943. package/dist/types/modes/components/queued-message-selector.d.ts +0 -12
  944. package/dist/types/modes/components/read-tool-group.d.ts +0 -30
  945. package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +0 -26
  946. package/dist/types/modes/components/sayknow-pet-widget.d.ts +0 -59
  947. package/dist/types/modes/components/session-observer-overlay.d.ts +0 -11
  948. package/dist/types/modes/components/session-selector.d.ts +0 -43
  949. package/dist/types/modes/components/settings-defs.d.ts +0 -50
  950. package/dist/types/modes/components/settings-selector.d.ts +0 -64
  951. package/dist/types/modes/components/show-images-selector.d.ts +0 -9
  952. package/dist/types/modes/components/skill-hud/render.d.ts +0 -2
  953. package/dist/types/modes/components/skill-message.d.ts +0 -10
  954. package/dist/types/modes/components/status-line/context-thresholds.d.ts +0 -4
  955. package/dist/types/modes/components/status-line/git-utils.d.ts +0 -28
  956. package/dist/types/modes/components/status-line/index.d.ts +0 -4
  957. package/dist/types/modes/components/status-line/presets.d.ts +0 -3
  958. package/dist/types/modes/components/status-line/segments.d.ts +0 -5
  959. package/dist/types/modes/components/status-line/separators.d.ts +0 -3
  960. package/dist/types/modes/components/status-line/token-rate.d.ts +0 -10
  961. package/dist/types/modes/components/status-line/types.d.ts +0 -83
  962. package/dist/types/modes/components/status-line.d.ts +0 -94
  963. package/dist/types/modes/components/theme-selector.d.ts +0 -10
  964. package/dist/types/modes/components/thinking-selector.d.ts +0 -15
  965. package/dist/types/modes/components/todo-reminder.d.ts +0 -13
  966. package/dist/types/modes/components/tool-execution.d.ts +0 -55
  967. package/dist/types/modes/components/tree-selector.d.ts +0 -31
  968. package/dist/types/modes/components/ttsr-notification.d.ts +0 -13
  969. package/dist/types/modes/components/two-column-body.d.ts +0 -9
  970. package/dist/types/modes/components/user-message-selector.d.ts +0 -28
  971. package/dist/types/modes/components/user-message.d.ts +0 -8
  972. package/dist/types/modes/components/visual-truncate.d.ts +0 -19
  973. package/dist/types/modes/components/welcome.d.ts +0 -46
  974. package/dist/types/modes/controllers/btw-controller.d.ts +0 -10
  975. package/dist/types/modes/controllers/command-controller-shared.d.ts +0 -47
  976. package/dist/types/modes/controllers/command-controller.d.ts +0 -40
  977. package/dist/types/modes/controllers/event-controller.d.ts +0 -21
  978. package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -80
  979. package/dist/types/modes/controllers/input-controller.d.ts +0 -42
  980. package/dist/types/modes/controllers/runtime-mcp-command-controller.d.ts +0 -10
  981. package/dist/types/modes/controllers/selector-controller.d.ts +0 -68
  982. package/dist/types/modes/controllers/ssh-command-controller.d.ts +0 -10
  983. package/dist/types/modes/controllers/todo-command-controller.d.ts +0 -7
  984. package/dist/types/modes/emoji-autocomplete.d.ts +0 -16
  985. package/dist/types/modes/index.d.ts +0 -10
  986. package/dist/types/modes/interactive-mode.d.ts +0 -297
  987. package/dist/types/modes/irc-observation-ledger.d.ts +0 -22
  988. package/dist/types/modes/jobs-observer.d.ts +0 -57
  989. package/dist/types/modes/oauth-manual-input.d.ts +0 -8
  990. package/dist/types/modes/print-mode.d.ts +0 -44
  991. package/dist/types/modes/prompt-action-autocomplete.d.ts +0 -50
  992. package/dist/types/modes/rpc/host-tools.d.ts +0 -1
  993. package/dist/types/modes/rpc/host-uris.d.ts +0 -1
  994. package/dist/types/modes/rpc/rpc-client.d.ts +0 -299
  995. package/dist/types/modes/rpc/rpc-mode.d.ts +0 -86
  996. package/dist/types/modes/rpc/rpc-socket-security.d.ts +0 -10
  997. package/dist/types/modes/rpc/rpc-types.d.ts +0 -810
  998. package/dist/types/modes/runtime-init.d.ts +0 -21
  999. package/dist/types/modes/session-observer-registry.d.ts +0 -26
  1000. package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +0 -57
  1001. package/dist/types/modes/shared/agent-wire/command-contract.d.ts +0 -18
  1002. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +0 -37
  1003. package/dist/types/modes/shared/agent-wire/command-validation.d.ts +0 -2
  1004. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +0 -102
  1005. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +0 -103
  1006. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +0 -60
  1007. package/dist/types/modes/shared/agent-wire/event-observation.d.ts +0 -37
  1008. package/dist/types/modes/shared/agent-wire/handshake.d.ts +0 -56
  1009. package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +0 -16
  1010. package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +0 -17
  1011. package/dist/types/modes/shared/agent-wire/protocol.d.ts +0 -25
  1012. package/dist/types/modes/shared/agent-wire/responses.d.ts +0 -4
  1013. package/dist/types/modes/shared/agent-wire/scopes.d.ts +0 -18
  1014. package/dist/types/modes/shared/agent-wire/session-registry.d.ts +0 -25
  1015. package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +0 -42
  1016. package/dist/types/modes/shared/agent-wire/ui-result.d.ts +0 -27
  1017. package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +0 -29
  1018. package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +0 -68
  1019. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +0 -166
  1020. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +0 -127
  1021. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +0 -154
  1022. package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +0 -39
  1023. package/dist/types/modes/shared.d.ts +0 -15
  1024. package/dist/types/modes/slash-command-visibility.d.ts +0 -1
  1025. package/dist/types/modes/theme/defaults/index.d.ts +0 -652
  1026. package/dist/types/modes/theme/mermaid-cache.d.ts +0 -9
  1027. package/dist/types/modes/theme/shimmer.d.ts +0 -38
  1028. package/dist/types/modes/theme/theme.d.ts +0 -275
  1029. package/dist/types/modes/tmux-scroll.d.ts +0 -18
  1030. package/dist/types/modes/types.d.ts +0 -318
  1031. package/dist/types/modes/utils/abort-message.d.ts +0 -4
  1032. package/dist/types/modes/utils/context-usage.d.ts +0 -36
  1033. package/dist/types/modes/utils/hotkeys-markdown.d.ts +0 -5
  1034. package/dist/types/modes/utils/injected-user-submission.d.ts +0 -52
  1035. package/dist/types/modes/utils/irc-message.d.ts +0 -38
  1036. package/dist/types/modes/utils/keybinding-matchers.d.ts +0 -10
  1037. package/dist/types/modes/utils/terminal-bell.d.ts +0 -3
  1038. package/dist/types/modes/utils/tools-markdown.d.ts +0 -5
  1039. package/dist/types/modes/utils/ui-helpers.d.ts +0 -75
  1040. package/dist/types/notifications/attachment-registry.d.ts +0 -17
  1041. package/dist/types/notifications/chat-adapters.d.ts +0 -9
  1042. package/dist/types/notifications/config-commands.d.ts +0 -64
  1043. package/dist/types/notifications/config.d.ts +0 -132
  1044. package/dist/types/notifications/daemon-paths.d.ts +0 -17
  1045. package/dist/types/notifications/engine.d.ts +0 -59
  1046. package/dist/types/notifications/helpers.d.ts +0 -55
  1047. package/dist/types/notifications/html-format.d.ts +0 -75
  1048. package/dist/types/notifications/index.d.ts +0 -219
  1049. package/dist/types/notifications/lifecycle-commands.d.ts +0 -69
  1050. package/dist/types/notifications/lifecycle-control-runtime.d.ts +0 -107
  1051. package/dist/types/notifications/lifecycle-orchestrator.d.ts +0 -144
  1052. package/dist/types/notifications/managed-daemon.d.ts +0 -48
  1053. package/dist/types/notifications/notification-service.d.ts +0 -144
  1054. package/dist/types/notifications/operator-runtime.d.ts +0 -52
  1055. package/dist/types/notifications/rate-limit-pool.d.ts +0 -113
  1056. package/dist/types/notifications/recent-activity.d.ts +0 -39
  1057. package/dist/types/notifications/reply-sent-store.d.ts +0 -53
  1058. package/dist/types/notifications/rich-draft.d.ts +0 -68
  1059. package/dist/types/notifications/rich-render.d.ts +0 -90
  1060. package/dist/types/notifications/telegram-cli.d.ts +0 -19
  1061. package/dist/types/notifications/telegram-daemon-cli.d.ts +0 -28
  1062. package/dist/types/notifications/telegram-daemon-control.d.ts +0 -70
  1063. package/dist/types/notifications/telegram-daemon.d.ts +0 -558
  1064. package/dist/types/notifications/telegram-reference.d.ts +0 -136
  1065. package/dist/types/notifications/threaded-inbound.d.ts +0 -77
  1066. package/dist/types/notifications/threaded-render.d.ts +0 -88
  1067. package/dist/types/notifications/topic-registry.d.ts +0 -89
  1068. package/dist/types/plan-mode/approved-plan.d.ts +0 -49
  1069. package/dist/types/plan-mode/state.d.ts +0 -6
  1070. package/dist/types/registry/agent-registry.d.ts +0 -64
  1071. package/dist/types/reminders/star-reminder.d.ts +0 -115
  1072. package/dist/types/research-plan/index.d.ts +0 -1
  1073. package/dist/types/research-plan/ledger.d.ts +0 -33
  1074. package/dist/types/rlm/artifacts.d.ts +0 -9
  1075. package/dist/types/rlm/complete-research-tool.d.ts +0 -35
  1076. package/dist/types/rlm/data-context.d.ts +0 -6
  1077. package/dist/types/rlm/index.d.ts +0 -47
  1078. package/dist/types/rlm/notebook.d.ts +0 -12
  1079. package/dist/types/rlm/preset.d.ts +0 -23
  1080. package/dist/types/rlm/python-tool.d.ts +0 -16
  1081. package/dist/types/rlm/report.d.ts +0 -14
  1082. package/dist/types/rlm/types.d.ts +0 -37
  1083. package/dist/types/runtime/process-lifecycle.d.ts +0 -108
  1084. package/dist/types/runtime-api-key.d.ts +0 -7
  1085. package/dist/types/runtime-credential-selector.d.ts +0 -7
  1086. package/dist/types/runtime-mcp/client.d.ts +0 -74
  1087. package/dist/types/runtime-mcp/config-writer.d.ts +0 -89
  1088. package/dist/types/runtime-mcp/config.d.ts +0 -77
  1089. package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
  1090. package/dist/types/runtime-mcp/index.d.ts +0 -18
  1091. package/dist/types/runtime-mcp/json-rpc.d.ts +0 -22
  1092. package/dist/types/runtime-mcp/loader.d.ts +0 -45
  1093. package/dist/types/runtime-mcp/manager.d.ts +0 -195
  1094. package/dist/types/runtime-mcp/oauth-discovery.d.ts +0 -40
  1095. package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -59
  1096. package/dist/types/runtime-mcp/render.d.ts +0 -25
  1097. package/dist/types/runtime-mcp/smithery-auth.d.ts +0 -16
  1098. package/dist/types/runtime-mcp/smithery-connect.d.ts +0 -38
  1099. package/dist/types/runtime-mcp/smithery-registry.d.ts +0 -51
  1100. package/dist/types/runtime-mcp/tool-bridge.d.ts +0 -86
  1101. package/dist/types/runtime-mcp/tool-cache.d.ts +0 -8
  1102. package/dist/types/runtime-mcp/transports/http.d.ts +0 -36
  1103. package/dist/types/runtime-mcp/transports/index.d.ts +0 -5
  1104. package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -29
  1105. package/dist/types/runtime-mcp/types.d.ts +0 -349
  1106. package/dist/types/sdk.d.ts +0 -256
  1107. package/dist/types/secrets/index.d.ts +0 -9
  1108. package/dist/types/secrets/obfuscator.d.ts +0 -23
  1109. package/dist/types/secrets/regex.d.ts +0 -2
  1110. package/dist/types/session/agent-session.d.ts +0 -1238
  1111. package/dist/types/session/agent-storage.d.ts +0 -101
  1112. package/dist/types/session/artifacts.d.ts +0 -64
  1113. package/dist/types/session/auth-broker-config.d.ts +0 -13
  1114. package/dist/types/session/auth-storage.d.ts +0 -6
  1115. package/dist/types/session/blob-store.d.ts +0 -160
  1116. package/dist/types/session/cache-economics.d.ts +0 -79
  1117. package/dist/types/session/client-bridge.d.ts +0 -88
  1118. package/dist/types/session/context-estimation.d.ts +0 -31
  1119. package/dist/types/session/contribution-prep.d.ts +0 -47
  1120. package/dist/types/session/file-mention-pruning.d.ts +0 -8
  1121. package/dist/types/session/history-storage.d.ts +0 -16
  1122. package/dist/types/session/messages.d.ts +0 -169
  1123. package/dist/types/session/provider-safety-stop.d.ts +0 -5
  1124. package/dist/types/session/session-dump-format.d.ts +0 -22
  1125. package/dist/types/session/session-manager.d.ts +0 -781
  1126. package/dist/types/session/session-storage.d.ts +0 -230
  1127. package/dist/types/session/streaming-output.d.ts +0 -209
  1128. package/dist/types/session/tool-choice-queue.d.ts +0 -84
  1129. package/dist/types/session/yield-queue.d.ts +0 -24
  1130. package/dist/types/setup/credential-auto-import.d.ts +0 -63
  1131. package/dist/types/setup/credential-import.d.ts +0 -82
  1132. package/dist/types/setup/hermes-setup.d.ts +0 -78
  1133. package/dist/types/setup/host-plugin-setup.d.ts +0 -39
  1134. package/dist/types/setup/model-onboarding-guidance.d.ts +0 -9
  1135. package/dist/types/setup/provider-onboarding.d.ts +0 -52
  1136. package/dist/types/skc-runtime/cli-write-receipt.d.ts +0 -24
  1137. package/dist/types/skc-runtime/deep-interview-ambiguity.d.ts +0 -39
  1138. package/dist/types/skc-runtime/deep-interview-recorder.d.ts +0 -114
  1139. package/dist/types/skc-runtime/deep-interview-runtime.d.ts +0 -36
  1140. package/dist/types/skc-runtime/deep-interview-state.d.ts +0 -127
  1141. package/dist/types/skc-runtime/gc-render.d.ts +0 -6
  1142. package/dist/types/skc-runtime/gc-runtime.d.ts +0 -134
  1143. package/dist/types/skc-runtime/goal-mode-request.d.ts +0 -53
  1144. package/dist/types/skc-runtime/launch-tmux.d.ts +0 -78
  1145. package/dist/types/skc-runtime/launch-worktree.d.ts +0 -46
  1146. package/dist/types/skc-runtime/ledger-event-renderer.d.ts +0 -68
  1147. package/dist/types/skc-runtime/psmux-detect.d.ts +0 -78
  1148. package/dist/types/skc-runtime/ralplan-runtime.d.ts +0 -26
  1149. package/dist/types/skc-runtime/restricted-role-agent-bash.d.ts +0 -3
  1150. package/dist/types/skc-runtime/session-layout.d.ts +0 -59
  1151. package/dist/types/skc-runtime/session-resolution.d.ts +0 -47
  1152. package/dist/types/skc-runtime/session-state-sidecar.d.ts +0 -70
  1153. package/dist/types/skc-runtime/state-graph.d.ts +0 -4
  1154. package/dist/types/skc-runtime/state-migrations.d.ts +0 -33
  1155. package/dist/types/skc-runtime/state-renderer.d.ts +0 -70
  1156. package/dist/types/skc-runtime/state-runtime.d.ts +0 -38
  1157. package/dist/types/skc-runtime/state-schema.d.ts +0 -319
  1158. package/dist/types/skc-runtime/state-validation.d.ts +0 -6
  1159. package/dist/types/skc-runtime/state-writer.d.ts +0 -244
  1160. package/dist/types/skc-runtime/team-gc.d.ts +0 -7
  1161. package/dist/types/skc-runtime/team-runtime.d.ts +0 -355
  1162. package/dist/types/skc-runtime/tmux-common.d.ts +0 -111
  1163. package/dist/types/skc-runtime/tmux-gc.d.ts +0 -7
  1164. package/dist/types/skc-runtime/tmux-owner-isolation-cli.d.ts +0 -5
  1165. package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +0 -309
  1166. package/dist/types/skc-runtime/tmux-sessions.d.ts +0 -94
  1167. package/dist/types/skc-runtime/ultragoal-guard.d.ts +0 -93
  1168. package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +0 -42
  1169. package/dist/types/skc-runtime/ultragoal-runtime.d.ts +0 -410
  1170. package/dist/types/skc-runtime/workflow-command-ref.d.ts +0 -43
  1171. package/dist/types/skc-runtime/workflow-manifest.d.ts +0 -54
  1172. package/dist/types/skill-state/active-state.d.ts +0 -126
  1173. package/dist/types/skill-state/canonical-skills.d.ts +0 -3
  1174. package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +0 -38
  1175. package/dist/types/skill-state/initial-phase.d.ts +0 -12
  1176. package/dist/types/skill-state/workflow-hud.d.ts +0 -83
  1177. package/dist/types/skill-state/workflow-state-contract.d.ts +0 -57
  1178. package/dist/types/skill-state/workflow-state-version.d.ts +0 -3
  1179. package/dist/types/slash-commands/acp-builtins.d.ts +0 -18
  1180. package/dist/types/slash-commands/builtin-registry.d.ts +0 -24
  1181. package/dist/types/slash-commands/helpers/context-report.d.ts +0 -7
  1182. package/dist/types/slash-commands/helpers/fast-status-report.d.ts +0 -82
  1183. package/dist/types/slash-commands/helpers/format.d.ts +0 -10
  1184. package/dist/types/slash-commands/helpers/mcp.d.ts +0 -3
  1185. package/dist/types/slash-commands/helpers/parse.d.ts +0 -33
  1186. package/dist/types/slash-commands/helpers/ssh.d.ts +0 -3
  1187. package/dist/types/slash-commands/helpers/todo.d.ts +0 -3
  1188. package/dist/types/slash-commands/helpers/usage-report.d.ts +0 -7
  1189. package/dist/types/slash-commands/types.d.ts +0 -120
  1190. package/dist/types/ssh/config-writer.d.ts +0 -49
  1191. package/dist/types/ssh/connection-manager.d.ts +0 -33
  1192. package/dist/types/ssh/ssh-executor.d.ts +0 -37
  1193. package/dist/types/ssh/sshfs-mount.d.ts +0 -6
  1194. package/dist/types/ssh/utils.d.ts +0 -2
  1195. package/dist/types/stt/downloader.d.ts +0 -9
  1196. package/dist/types/stt/index.d.ts +0 -3
  1197. package/dist/types/stt/recorder.d.ts +0 -13
  1198. package/dist/types/stt/setup.d.ts +0 -18
  1199. package/dist/types/stt/stt-controller.d.ts +0 -16
  1200. package/dist/types/stt/transcriber.d.ts +0 -16
  1201. package/dist/types/system-prompt.d.ts +0 -118
  1202. package/dist/types/task/agents.d.ts +0 -28
  1203. package/dist/types/task/commands.d.ts +0 -35
  1204. package/dist/types/task/discovery.d.ts +0 -19
  1205. package/dist/types/task/executor.d.ts +0 -127
  1206. package/dist/types/task/fork-context-advisory.d.ts +0 -13
  1207. package/dist/types/task/fork-context-budget.d.ts +0 -7
  1208. package/dist/types/task/id.d.ts +0 -7
  1209. package/dist/types/task/index.d.ts +0 -43
  1210. package/dist/types/task/name-generator.d.ts +0 -16
  1211. package/dist/types/task/output-manager.d.ts +0 -36
  1212. package/dist/types/task/parallel.d.ts +0 -34
  1213. package/dist/types/task/receipt.d.ts +0 -89
  1214. package/dist/types/task/render.d.ts +0 -28
  1215. package/dist/types/task/roi-reconciliation.d.ts +0 -27
  1216. package/dist/types/task/simple-mode.d.ts +0 -8
  1217. package/dist/types/task/skc-command.d.ts +0 -7
  1218. package/dist/types/task/spawn-gate.d.ts +0 -29
  1219. package/dist/types/task/subprocess-tool-registry.d.ts +0 -72
  1220. package/dist/types/task/token-log.d.ts +0 -30
  1221. package/dist/types/task/token-log.test.d.ts +0 -1
  1222. package/dist/types/task/types.d.ts +0 -516
  1223. package/dist/types/task/worktree.d.ts +0 -94
  1224. package/dist/types/thinking-metadata.d.ts +0 -16
  1225. package/dist/types/thinking.d.ts +0 -22
  1226. package/dist/types/tool-discovery/tool-index.d.ts +0 -112
  1227. package/dist/types/tools/archive-reader.d.ts +0 -40
  1228. package/dist/types/tools/ask-answer-registry.d.ts +0 -18
  1229. package/dist/types/tools/ask.d.ts +0 -123
  1230. package/dist/types/tools/ast-edit.d.ts +0 -77
  1231. package/dist/types/tools/ast-grep.d.ts +0 -69
  1232. package/dist/types/tools/auto-generated-guard.d.ts +0 -17
  1233. package/dist/types/tools/bash-allowed-prefixes.d.ts +0 -10
  1234. package/dist/types/tools/bash-command-fixup.d.ts +0 -11
  1235. package/dist/types/tools/bash-interactive.d.ts +0 -16
  1236. package/dist/types/tools/bash-interceptor.d.ts +0 -24
  1237. package/dist/types/tools/bash-pty-selection.d.ts +0 -7
  1238. package/dist/types/tools/bash-skill-urls.d.ts +0 -31
  1239. package/dist/types/tools/bash.d.ts +0 -157
  1240. package/dist/types/tools/bisect.d.ts +0 -81
  1241. package/dist/types/tools/browser/actions.d.ts +0 -54
  1242. package/dist/types/tools/browser/attach.d.ts +0 -50
  1243. package/dist/types/tools/browser/launch.d.ts +0 -62
  1244. package/dist/types/tools/browser/readable.d.ts +0 -17
  1245. package/dist/types/tools/browser/registry.d.ts +0 -57
  1246. package/dist/types/tools/browser/render.d.ts +0 -53
  1247. package/dist/types/tools/browser/tab-protocol.d.ts +0 -144
  1248. package/dist/types/tools/browser/tab-supervisor.d.ts +0 -109
  1249. package/dist/types/tools/browser/tab-worker-entry.d.ts +0 -1
  1250. package/dist/types/tools/browser/tab-worker.d.ts +0 -19
  1251. package/dist/types/tools/browser.d.ts +0 -211
  1252. package/dist/types/tools/calculator.d.ts +0 -76
  1253. package/dist/types/tools/checkpoint.d.ts +0 -63
  1254. package/dist/types/tools/composer-bash-policy.d.ts +0 -14
  1255. package/dist/types/tools/computer/render.d.ts +0 -17
  1256. package/dist/types/tools/computer-gc.d.ts +0 -23
  1257. package/dist/types/tools/computer.d.ts +0 -465
  1258. package/dist/types/tools/conflict-detect.d.ts +0 -205
  1259. package/dist/types/tools/context.d.ts +0 -19
  1260. package/dist/types/tools/cron.d.ts +0 -91
  1261. package/dist/types/tools/debug.d.ts +0 -209
  1262. package/dist/types/tools/eval.d.ts +0 -108
  1263. package/dist/types/tools/fetch.d.ts +0 -84
  1264. package/dist/types/tools/file-recorder.d.ts +0 -13
  1265. package/dist/types/tools/find.d.ts +0 -95
  1266. package/dist/types/tools/fs-cache-invalidation.d.ts +0 -15
  1267. package/dist/types/tools/gh-format.d.ts +0 -6
  1268. package/dist/types/tools/gh-renderer.d.ts +0 -25
  1269. package/dist/types/tools/gh.d.ts +0 -356
  1270. package/dist/types/tools/github-cache.d.ts +0 -103
  1271. package/dist/types/tools/grouped-file-output.d.ts +0 -36
  1272. package/dist/types/tools/hindsight-recall.d.ts +0 -21
  1273. package/dist/types/tools/hindsight-reflect.d.ts +0 -23
  1274. package/dist/types/tools/hindsight-retain.d.ts +0 -27
  1275. package/dist/types/tools/image-gen.d.ts +0 -79
  1276. package/dist/types/tools/index.d.ts +0 -371
  1277. package/dist/types/tools/irc.d.ts +0 -79
  1278. package/dist/types/tools/job.d.ts +0 -78
  1279. package/dist/types/tools/json-tree.d.ts +0 -23
  1280. package/dist/types/tools/jtd-to-json-schema.d.ts +0 -29
  1281. package/dist/types/tools/jtd-to-typescript.d.ts +0 -26
  1282. package/dist/types/tools/jtd-utils.d.ts +0 -42
  1283. package/dist/types/tools/list-limit.d.ts +0 -12
  1284. package/dist/types/tools/match-line-format.d.ts +0 -11
  1285. package/dist/types/tools/monitor.d.ts +0 -54
  1286. package/dist/types/tools/output-meta.d.ts +0 -204
  1287. package/dist/types/tools/path-utils.d.ts +0 -147
  1288. package/dist/types/tools/plan-mode-guard.d.ts +0 -18
  1289. package/dist/types/tools/read.d.ts +0 -85
  1290. package/dist/types/tools/recipe/index.d.ts +0 -45
  1291. package/dist/types/tools/recipe/render.d.ts +0 -36
  1292. package/dist/types/tools/recipe/runner.d.ts +0 -60
  1293. package/dist/types/tools/recipe/runners/cargo.d.ts +0 -16
  1294. package/dist/types/tools/recipe/runners/index.d.ts +0 -2
  1295. package/dist/types/tools/recipe/runners/just.d.ts +0 -2
  1296. package/dist/types/tools/recipe/runners/make.d.ts +0 -2
  1297. package/dist/types/tools/recipe/runners/pkg.d.ts +0 -2
  1298. package/dist/types/tools/recipe/runners/task.d.ts +0 -2
  1299. package/dist/types/tools/render-mermaid.d.ts +0 -37
  1300. package/dist/types/tools/render-utils.d.ts +0 -158
  1301. package/dist/types/tools/renderers.d.ts +0 -26
  1302. package/dist/types/tools/resolve.d.ts +0 -80
  1303. package/dist/types/tools/resource-gc.d.ts +0 -54
  1304. package/dist/types/tools/review.d.ts +0 -63
  1305. package/dist/types/tools/search-tool-bm25.d.ts +0 -65
  1306. package/dist/types/tools/search.d.ts +0 -93
  1307. package/dist/types/tools/skill-discovery.d.ts +0 -40
  1308. package/dist/types/tools/skill.d.ts +0 -48
  1309. package/dist/types/tools/sqlite-reader.d.ts +0 -94
  1310. package/dist/types/tools/ssh.d.ts +0 -67
  1311. package/dist/types/tools/subagent-render.d.ts +0 -31
  1312. package/dist/types/tools/subagent.d.ts +0 -117
  1313. package/dist/types/tools/telegram-send.d.ts +0 -39
  1314. package/dist/types/tools/todo-write.d.ts +0 -120
  1315. package/dist/types/tools/tool-errors.d.ts +0 -33
  1316. package/dist/types/tools/tool-result.d.ts +0 -30
  1317. package/dist/types/tools/tool-timeouts.d.ts +0 -56
  1318. package/dist/types/tools/ultragoal-ask-guard.d.ts +0 -12
  1319. package/dist/types/tools/vim.d.ts +0 -58
  1320. package/dist/types/tools/write.d.ts +0 -59
  1321. package/dist/types/tools/yield.d.ts +0 -25
  1322. package/dist/types/tui/code-cell.d.ts +0 -32
  1323. package/dist/types/tui/file-list.d.ts +0 -22
  1324. package/dist/types/tui/hyperlink.d.ts +0 -42
  1325. package/dist/types/tui/index.d.ts +0 -11
  1326. package/dist/types/tui/output-block.d.ts +0 -28
  1327. package/dist/types/tui/status-line.d.ts +0 -18
  1328. package/dist/types/tui/tree-list.d.ts +0 -19
  1329. package/dist/types/tui/types.d.ts +0 -13
  1330. package/dist/types/tui/utils.d.ts +0 -36
  1331. package/dist/types/utils/changelog.d.ts +0 -37
  1332. package/dist/types/utils/clipboard.d.ts +0 -22
  1333. package/dist/types/utils/cmux-workspace.d.ts +0 -52
  1334. package/dist/types/utils/command-args.d.ts +0 -9
  1335. package/dist/types/utils/commit-message-generator.d.ts +0 -7
  1336. package/dist/types/utils/edit-mode.d.ts +0 -13
  1337. package/dist/types/utils/event-bus.d.ts +0 -6
  1338. package/dist/types/utils/external-editor.d.ts +0 -19
  1339. package/dist/types/utils/file-display-mode.d.ts +0 -27
  1340. package/dist/types/utils/file-mentions.d.ts +0 -25
  1341. package/dist/types/utils/git.d.ts +0 -393
  1342. package/dist/types/utils/image-loading.d.ts +0 -26
  1343. package/dist/types/utils/image-resize.d.ts +0 -39
  1344. package/dist/types/utils/lang-from-path.d.ts +0 -8
  1345. package/dist/types/utils/linkedom.d.ts +0 -4
  1346. package/dist/types/utils/markit.d.ts +0 -7
  1347. package/dist/types/utils/open.d.ts +0 -2
  1348. package/dist/types/utils/pasted-image-path.d.ts +0 -32
  1349. package/dist/types/utils/session-color.d.ts +0 -10
  1350. package/dist/types/utils/shell-snapshot.d.ts +0 -5
  1351. package/dist/types/utils/sixel.d.ts +0 -21
  1352. package/dist/types/utils/title-generator.d.ts +0 -31
  1353. package/dist/types/utils/tool-choice.d.ts +0 -20
  1354. package/dist/types/utils/tools-manager.d.ts +0 -9
  1355. package/dist/types/vim/buffer.d.ts +0 -41
  1356. package/dist/types/vim/commands.d.ts +0 -6
  1357. package/dist/types/vim/engine.d.ts +0 -47
  1358. package/dist/types/vim/parser.d.ts +0 -3
  1359. package/dist/types/vim/render.d.ts +0 -25
  1360. package/dist/types/vim/types.d.ts +0 -182
  1361. package/dist/types/web/insane/bridge.d.ts +0 -103
  1362. package/dist/types/web/insane/url-guard.d.ts +0 -25
  1363. package/dist/types/web/kagi.d.ts +0 -23
  1364. package/dist/types/web/parallel.d.ts +0 -58
  1365. package/dist/types/web/scrapers/artifacthub.d.ts +0 -6
  1366. package/dist/types/web/scrapers/arxiv.d.ts +0 -5
  1367. package/dist/types/web/scrapers/aur.d.ts +0 -5
  1368. package/dist/types/web/scrapers/biorxiv.d.ts +0 -5
  1369. package/dist/types/web/scrapers/bluesky.d.ts +0 -5
  1370. package/dist/types/web/scrapers/brew.d.ts +0 -5
  1371. package/dist/types/web/scrapers/cheatsh.d.ts +0 -8
  1372. package/dist/types/web/scrapers/chocolatey.d.ts +0 -5
  1373. package/dist/types/web/scrapers/choosealicense.d.ts +0 -2
  1374. package/dist/types/web/scrapers/cisa-kev.d.ts +0 -5
  1375. package/dist/types/web/scrapers/clojars.d.ts +0 -5
  1376. package/dist/types/web/scrapers/coingecko.d.ts +0 -5
  1377. package/dist/types/web/scrapers/crates-io.d.ts +0 -5
  1378. package/dist/types/web/scrapers/crossref.d.ts +0 -2
  1379. package/dist/types/web/scrapers/devto.d.ts +0 -5
  1380. package/dist/types/web/scrapers/discogs.d.ts +0 -8
  1381. package/dist/types/web/scrapers/discourse.d.ts +0 -5
  1382. package/dist/types/web/scrapers/dockerhub.d.ts +0 -5
  1383. package/dist/types/web/scrapers/docs-rs.d.ts +0 -2
  1384. package/dist/types/web/scrapers/fdroid.d.ts +0 -5
  1385. package/dist/types/web/scrapers/firefox-addons.d.ts +0 -2
  1386. package/dist/types/web/scrapers/flathub.d.ts +0 -2
  1387. package/dist/types/web/scrapers/github-gist.d.ts +0 -5
  1388. package/dist/types/web/scrapers/github.d.ts +0 -12
  1389. package/dist/types/web/scrapers/gitlab.d.ts +0 -5
  1390. package/dist/types/web/scrapers/go-pkg.d.ts +0 -5
  1391. package/dist/types/web/scrapers/hackage.d.ts +0 -5
  1392. package/dist/types/web/scrapers/hackernews.d.ts +0 -2
  1393. package/dist/types/web/scrapers/hex.d.ts +0 -5
  1394. package/dist/types/web/scrapers/huggingface.d.ts +0 -2
  1395. package/dist/types/web/scrapers/iacr.d.ts +0 -5
  1396. package/dist/types/web/scrapers/index.d.ts +0 -84
  1397. package/dist/types/web/scrapers/jetbrains-marketplace.d.ts +0 -2
  1398. package/dist/types/web/scrapers/lemmy.d.ts +0 -2
  1399. package/dist/types/web/scrapers/lobsters.d.ts +0 -5
  1400. package/dist/types/web/scrapers/mastodon.d.ts +0 -5
  1401. package/dist/types/web/scrapers/maven.d.ts +0 -6
  1402. package/dist/types/web/scrapers/mdn.d.ts +0 -2
  1403. package/dist/types/web/scrapers/metacpan.d.ts +0 -5
  1404. package/dist/types/web/scrapers/musicbrainz.d.ts +0 -5
  1405. package/dist/types/web/scrapers/npm.d.ts +0 -5
  1406. package/dist/types/web/scrapers/nuget.d.ts +0 -5
  1407. package/dist/types/web/scrapers/nvd.d.ts +0 -5
  1408. package/dist/types/web/scrapers/ollama.d.ts +0 -2
  1409. package/dist/types/web/scrapers/open-vsx.d.ts +0 -5
  1410. package/dist/types/web/scrapers/opencorporates.d.ts +0 -5
  1411. package/dist/types/web/scrapers/openlibrary.d.ts +0 -5
  1412. package/dist/types/web/scrapers/orcid.d.ts +0 -5
  1413. package/dist/types/web/scrapers/osv.d.ts +0 -5
  1414. package/dist/types/web/scrapers/packagist.d.ts +0 -5
  1415. package/dist/types/web/scrapers/pub-dev.d.ts +0 -5
  1416. package/dist/types/web/scrapers/pubmed.d.ts +0 -5
  1417. package/dist/types/web/scrapers/pypi.d.ts +0 -5
  1418. package/dist/types/web/scrapers/rawg.d.ts +0 -2
  1419. package/dist/types/web/scrapers/readthedocs.d.ts +0 -2
  1420. package/dist/types/web/scrapers/reddit.d.ts +0 -5
  1421. package/dist/types/web/scrapers/repology.d.ts +0 -5
  1422. package/dist/types/web/scrapers/rfc.d.ts +0 -5
  1423. package/dist/types/web/scrapers/rubygems.d.ts +0 -5
  1424. package/dist/types/web/scrapers/searchcode.d.ts +0 -2
  1425. package/dist/types/web/scrapers/sec-edgar.d.ts +0 -5
  1426. package/dist/types/web/scrapers/semantic-scholar.d.ts +0 -2
  1427. package/dist/types/web/scrapers/snapcraft.d.ts +0 -2
  1428. package/dist/types/web/scrapers/sourcegraph.d.ts +0 -2
  1429. package/dist/types/web/scrapers/spdx.d.ts +0 -5
  1430. package/dist/types/web/scrapers/spotify.d.ts +0 -8
  1431. package/dist/types/web/scrapers/stackoverflow.d.ts +0 -6
  1432. package/dist/types/web/scrapers/terraform.d.ts +0 -5
  1433. package/dist/types/web/scrapers/tldr.d.ts +0 -7
  1434. package/dist/types/web/scrapers/twitter.d.ts +0 -5
  1435. package/dist/types/web/scrapers/types.d.ts +0 -83
  1436. package/dist/types/web/scrapers/utils.d.ts +0 -32
  1437. package/dist/types/web/scrapers/vimeo.d.ts +0 -5
  1438. package/dist/types/web/scrapers/vscode-marketplace.d.ts +0 -5
  1439. package/dist/types/web/scrapers/w3c.d.ts +0 -2
  1440. package/dist/types/web/scrapers/wikidata.d.ts +0 -5
  1441. package/dist/types/web/scrapers/wikipedia.d.ts +0 -5
  1442. package/dist/types/web/scrapers/youtube.d.ts +0 -13
  1443. package/dist/types/web/search/index.d.ts +0 -118
  1444. package/dist/types/web/search/provider.d.ts +0 -55
  1445. package/dist/types/web/search/providers/anthropic.d.ts +0 -32
  1446. package/dist/types/web/search/providers/base.d.ts +0 -90
  1447. package/dist/types/web/search/providers/brave.d.ts +0 -27
  1448. package/dist/types/web/search/providers/codex.d.ts +0 -35
  1449. package/dist/types/web/search/providers/duckduckgo.d.ts +0 -57
  1450. package/dist/types/web/search/providers/exa.d.ts +0 -52
  1451. package/dist/types/web/search/providers/gemini.d.ts +0 -57
  1452. package/dist/types/web/search/providers/insane.d.ts +0 -53
  1453. package/dist/types/web/search/providers/jina.d.ts +0 -26
  1454. package/dist/types/web/search/providers/kagi.d.ts +0 -24
  1455. package/dist/types/web/search/providers/kimi.d.ts +0 -27
  1456. package/dist/types/web/search/providers/openai-compatible.d.ts +0 -9
  1457. package/dist/types/web/search/providers/parallel.d.ts +0 -15
  1458. package/dist/types/web/search/providers/perplexity.d.ts +0 -38
  1459. package/dist/types/web/search/providers/searxng.d.ts +0 -44
  1460. package/dist/types/web/search/providers/synthetic.d.ts +0 -21
  1461. package/dist/types/web/search/providers/tavily.d.ts +0 -29
  1462. package/dist/types/web/search/providers/text-citations.d.ts +0 -23
  1463. package/dist/types/web/search/providers/utils.d.ts +0 -88
  1464. package/dist/types/web/search/providers/xai.d.ts +0 -64
  1465. package/dist/types/web/search/providers/zai.d.ts +0 -28
  1466. package/dist/types/web/search/render.d.ts +0 -36
  1467. package/dist/types/web/search/types.d.ts +0 -372
  1468. package/dist/types/web/search/utils.d.ts +0 -4
  1469. package/dist/types/workflow/workflow-intent-diff.d.ts +0 -25
  1470. package/dist/types/workflow/workflow-intent-report-consumer.d.ts +0 -50
  1471. package/dist/types/workflow/workflow-intent-report-schema.d.ts +0 -2
  1472. package/dist/types/workflow/workflow-intent-report.d.ts +0 -48
  1473. package/dist/types/workspace-tree.d.ts +0 -42
  1474. package/src/sdk.ts +0 -2623
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import * as path from "node:path";
2
3
  import {
3
4
  type Agent,
@@ -5,15 +6,11 @@ import {
5
6
  type AuthenticateRequest,
6
7
  type AuthenticateResponse,
7
8
  type AuthMethod,
8
- type AvailableCommand,
9
9
  type ClientCapabilities,
10
10
  type CloseSessionRequest,
11
11
  type CloseSessionResponse,
12
- type CreateElicitationResponse,
13
12
  type DeleteSessionRequest,
14
13
  type DeleteSessionResponse,
15
- type ElicitationContentValue,
16
- type ElicitationPropertySchema,
17
14
  type ForkSessionRequest,
18
15
  type ForkSessionResponse,
19
16
  type InitializeRequest,
@@ -22,7 +19,6 @@ import {
22
19
  type ListSessionsResponse,
23
20
  type LoadSessionRequest,
24
21
  type LoadSessionResponse,
25
- type McpServer,
26
22
  type NewSessionRequest,
27
23
  type NewSessionResponse,
28
24
  PROTOCOL_VERSION,
@@ -30,471 +26,577 @@ import {
30
26
  type PromptResponse,
31
27
  type ResumeSessionRequest,
32
28
  type ResumeSessionResponse,
33
- type SessionConfigOption,
34
29
  type SessionInfo,
35
- type SessionModeState,
36
30
  type SessionNotification,
37
- type SessionUpdate,
38
31
  type SetSessionConfigOptionRequest,
39
32
  type SetSessionConfigOptionResponse,
40
33
  type SetSessionModeRequest,
41
34
  type SetSessionModeResponse,
42
- type Usage,
43
35
  } from "@agentclientprotocol/sdk";
44
- import type { AssistantMessage, Model } from "@sayknow-cli/ai";
45
- import { logger } from "@sayknow-cli/utils";
36
+ import { getAgentDir } from "@sayknow-cli/utils";
46
37
  import packageJson from "../../../package.json" with { type: "json" };
47
- import { disableProvider, enableProvider, reset as resetCapabilities } from "../../capability";
48
- import { Settings } from "../../config/settings";
49
- import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
50
- import type { ExtensionUIContext, ExtensionUIDialogOptions } from "../../extensibility/extensions";
51
- import { runExtensionCompact } from "../../extensibility/extensions/compact-handler";
52
- import { getSessionSlashCommands } from "../../extensibility/extensions/get-commands-handler";
53
- import { resolveSubskillActivationForSkillInvocation } from "../../extensibility/skc-plugins";
54
38
  import {
55
- buildSkillPromptMessage,
56
- getSkillSlashCommandNames,
57
- isNamespacedSkillSlashCommandName,
58
- isSkillSlashCommandName,
59
- parseSkillInvocations,
60
- } from "../../extensibility/skills";
61
- import { loadSlashCommands } from "../../extensibility/slash-commands";
62
- import { loadAllExtensions } from "../../modes/components/extensions/state-manager";
63
- import { theme } from "../../modes/theme/theme";
64
- import { MCPManager } from "../../runtime-mcp/manager";
65
- import type { MCPServerConfig } from "../../runtime-mcp/types";
66
- import type { AgentSession, AgentSessionEvent } from "../../session/agent-session";
67
- import { isSilentAbort, SKILL_PROMPT_MESSAGE_TYPE } from "../../session/messages";
68
- import { isLegacyProviderSafetyStopMessage } from "../../session/provider-safety-stop";
69
- import {
70
- SessionManager,
71
- type SessionInfo as StoredSessionInfo,
72
- type StrictInventoryCandidate,
73
- type UsageStatistics,
74
- } from "../../session/session-manager";
75
- import {
76
- FileSessionStorage,
77
- type SessionStorageFileIdentity,
78
- type VerifiedSessionDeleteResult,
79
- type VerifiedSessionDeleteTarget,
80
- } from "../../session/session-storage";
81
- import { ACP_BUILTIN_SLASH_COMMANDS, executeAcpBuiltinSlashCommand } from "../../slash-commands/acp-builtins";
82
- import { parseThinkingLevel } from "../../thinking";
83
- import { toAgentWireEventPayload } from "../shared/agent-wire/event-envelope";
84
- import { createAcpClientBridge } from "./acp-client-bridge";
85
- import {
86
- buildToolCallStartUpdate,
87
- mapAgentWireEventPayloadToAcpSessionUpdates,
88
- normalizeReplayToolArguments,
89
- } from "./acp-event-mapper";
39
+ type AcpProviderRegistration,
40
+ type AcpReverseConnection,
41
+ AcpSdkAdapter,
42
+ AcpSdkAdapterError,
43
+ } from "../../sdk/acp";
44
+ import { ensureBroker } from "../../sdk/broker/ensure";
45
+ import { readSdkBrokerDiscovery, SdkClient } from "../../sdk/client";
46
+ import { mapAgentWireEventPayloadToAcpSessionUpdates } from "./acp-event-mapper";
47
+ import { resolveAcpPermissionMode } from "./permission-mode";
48
+ import type { AcpStartupOptions } from "./startup-options";
90
49
  import { ACP_TERMINAL_AUTH_FLAG } from "./terminal-auth";
91
50
 
92
51
  const ACP_DEFAULT_MODE_ID = "default";
93
52
  const ACP_PLAN_MODE_ID = "plan";
94
- const DEFAULT_PLAN_FILE_URL = "local://PLAN.md";
95
53
  const MODE_CONFIG_ID = "mode";
96
54
  const MODEL_CONFIG_ID = "model";
97
55
  const THINKING_CONFIG_ID = "thinking";
98
- const THINKING_OFF = "off";
99
56
  const SESSION_PAGE_SIZE = 50;
57
+ export const ACP_BOOTSTRAP_RACE_GUARD_MS = 50;
58
+ const MAX_ACP_REPLAY_PAGES = 10_000;
100
59
 
60
+ type JsonObject = Record<string, unknown>;
101
61
  /**
102
- * One immutable authorization snapshot built from a complete strict scoped inventory.
103
- * `entries` maps every discovered session id to either the single exact-identity
104
- * candidate (authoritative) or a conflict tombstone (duplicate/unsafe). The snapshot
105
- * is built once, before pagination, so a duplicate beyond page 1 is known before the
106
- * first page response. Lifecycle changes bump {@link #authorityGeneration} and
107
- * invalidate the snapshot.
62
+ * ACP prompt completion is tied to a post-acknowledgement lifecycle boundary.
63
+ * AgentSession events do not carry the command identity themselves, so the
64
+ * host stamps command/turn identities into its replay ring and ACP also keeps
65
+ * a per-endpoint ingress sequence. A frame observed before an acknowledgement
66
+ * can never settle the waiter that acknowledgement creates.
108
67
  */
109
- type AcpAuthorityEntry =
110
- | { kind: "candidate"; candidate: StrictInventoryCandidate }
111
- | { kind: "conflict"; reason: string };
112
- /**
113
- * Phase-aware same-connection retry evidence for an incomplete verified delete.
114
- * The transcript identity is always recorded and re-verified on retry; the
115
- * artifacts identity is recorded only when the failure occurred at the
116
- * artifacts phase (the artifact directory still existed at failure time).
117
- */
118
- type PendingDeleteEvidence = {
119
- transcriptIdentity: SessionStorageFileIdentity;
120
- artifactsIdentity?: SessionStorageFileIdentity;
121
- };
68
+ interface PromptWaiter {
69
+ cancelRequested: boolean;
70
+ acknowledged: boolean;
71
+ activityObserved: boolean;
72
+ /** The prompt was accepted while the host was already busy, so its next valid idle ends the steer. */
73
+ steeringAtAcknowledgement: boolean;
74
+ /** Highest inbound frame sequence already observed when the prompt was acknowledged. */
75
+ boundary: number;
76
+ correlation: PromptCorrelation;
77
+ messageProgress?: { textEmitted: boolean; thoughtEmitted: boolean };
78
+ pendingTerminal?: PromptCorrelation;
79
+ resolve: (response: PromptResponse) => void;
80
+ reject: (error: Error) => void;
81
+ }
122
82
 
123
- type AcpAuthoritySnapshot = {
124
- scope: string;
125
- entries: Map<string, AcpAuthorityEntry>;
126
- /** Ordered unique session ids (conflicts appear once at first sight) for paging. */
127
- orderedIds: string[];
128
- generation: number;
129
- };
130
- /**
131
- * Delay between `session/new` (or `session/load` / `session/resume` /
132
- * `unstable_session/fork`) returning and the agent firing the first
133
- * notifications against the new session id. Mitigates Zed's
134
- * `Received session notification for unknown session` race — see
135
- * `#scheduleBootstrapUpdates`. Exported so the ACP test harness can
136
- * wait past this guard without hard-coding the literal.
137
- */
138
- export const ACP_BOOTSTRAP_RACE_GUARD_MS = 50;
139
- const CODING_AGENT_VERSION: string = packageJson.version;
140
- const ACP_CANCEL_CLEANUP_TIMEOUT_MS = 5_000;
141
- const ACP_ASYNC_DELIVERY_DRAIN_TIMEOUT_MS = 250;
142
- const ACP_ASYNC_DELIVERY_DRAIN_MAX_PASSES = 3;
143
-
144
- type AgentImageContent = {
145
- type: "image";
146
- data: string;
147
- mimeType: string;
83
+ type PromptCorrelation = { commandId?: string; turnId?: string };
84
+
85
+ type BrokerConnection = { adapter: AcpSdkAdapter; client: SdkClient };
86
+ type PendingAttachment = { epoch: number; task: Promise<void> };
87
+
88
+ type SessionRecord = {
89
+ cwd: string;
90
+ adapter: AcpSdkAdapter;
91
+ unsubscribe: () => void;
92
+ reconnectUnsubscribe: () => void;
93
+ /** Per-session frame work queue; callbacks never race prompt ownership. */
94
+ frameTail: Promise<void>;
95
+ /** Monotonic at WebSocket ingress, before queued work begins. */
96
+ inboundSequence: number;
97
+ /** Updated at ingress so a prompt acknowledgement can distinguish a steer from a fresh turn. */
98
+ busy: boolean;
99
+ activePrompt?: PromptWaiter;
148
100
  };
101
+ type Endpoint = { url: string; token: string };
149
102
 
150
- type PromptQueueState = {
151
- promise: Promise<void>;
152
- release: (() => void) | undefined;
103
+ type BrokerSession = {
104
+ sessionId: string;
105
+ locator?: { repo?: string };
106
+ live?: boolean;
107
+ endpointGeneration?: number;
153
108
  };
154
109
 
155
- type PromptTurnState = {
156
- userMessageId: string;
157
- cancelRequested: boolean;
158
- settled: boolean;
159
- /**
160
- * `abort()` is in-flight (or its bounded-timeout race). `undefined` while the turn is
161
- * running normally and after cleanup completes. The turn occupies `record.promptTurn`
162
- * for as long as either `!settled` or `cleanup` is set — that combined window is the
163
- * "turn in flight" predicate (`isPromptTurnInFlight`) every consumer gates on.
164
- */
165
- cleanup: Promise<void> | undefined;
166
- usageBaseline: UsageStatistics;
167
- unsubscribe: (() => void) | undefined;
168
- resolve: (value: PromptResponse) => void;
169
- reject: (reason?: unknown) => void;
170
- promise: Promise<PromptResponse>;
171
- };
110
+ function parseAcpStartupOptions(value: unknown): AcpStartupOptions | undefined {
111
+ const candidate = object(value);
112
+ if (!candidate) return undefined;
113
+ const modelId = typeof candidate.modelId === "string" ? candidate.modelId : undefined;
114
+ const modelPreset = typeof candidate.modelPreset === "string" ? candidate.modelPreset : undefined;
115
+ const thinkingLevel = typeof candidate.thinkingLevel === "string" ? candidate.thinkingLevel : undefined;
116
+ return modelId || modelPreset || thinkingLevel
117
+ ? {
118
+ ...(modelId ? { modelId } : {}),
119
+ ...(modelPreset ? { modelPreset } : {}),
120
+ ...(thinkingLevel ? { thinkingLevel } : {}),
121
+ }
122
+ : undefined;
123
+ }
172
124
 
173
- /**
174
- * A turn is "in flight" from the moment `prompt()` reserves the slot until `settled` is
175
- * true AND any cancel cleanup has completed. Fork/queue/event gating all depend on this
176
- * combined window — a settled-but-still-aborting turn is not safe to fork from, queue
177
- * onto, or forward late events for.
178
- */
179
- function isPromptTurnInFlight(turn: PromptTurnState | undefined): turn is PromptTurnState {
180
- return turn !== undefined && (!turn.settled || turn.cleanup !== undefined);
125
+ function object(value: unknown): JsonObject | undefined {
126
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? (value as JsonObject) : undefined;
127
+ }
128
+
129
+ function aggregateAcpFailure(code: string, message: string, failures: unknown[]): AcpSdkAdapterError {
130
+ const aggregate = new AggregateError(failures, message);
131
+ return Object.assign(new AcpSdkAdapterError(code, aggregate.message), {
132
+ cause: aggregate,
133
+ errors: aggregate.errors,
134
+ });
135
+ }
136
+
137
+ /** Applies ACP's offset cursor after narrowing the broker listing to the requested cwd. */
138
+ export function paginateAcpSessions(listed: unknown[], cwd: string | undefined, offset: number): ListSessionsResponse {
139
+ const filtered = listed
140
+ .map(value => object(value) as BrokerSession | undefined)
141
+ .filter(
142
+ (value): value is BrokerSession & { locator: { repo: string } } =>
143
+ typeof value?.sessionId === "string" && typeof value.locator?.repo === "string",
144
+ )
145
+ .filter(value => !cwd || value.locator.repo === cwd);
146
+ const sessions = filtered
147
+ .slice(offset, offset + SESSION_PAGE_SIZE)
148
+ .map(
149
+ value =>
150
+ ({ sessionId: value.sessionId, cwd: value.locator.repo, title: value.sessionId }) satisfies SessionInfo,
151
+ );
152
+ return {
153
+ sessions,
154
+ nextCursor: offset + sessions.length < filtered.length ? String(offset + sessions.length) : undefined,
155
+ };
156
+ }
157
+
158
+ function endpoint(value: unknown): Endpoint {
159
+ const candidate = object(value);
160
+ const result = object(candidate?.result) ?? candidate;
161
+ const nested = object(result?.endpoint) ?? result;
162
+ if (typeof nested?.url !== "string" || typeof nested.token !== "string")
163
+ throw new AcpSdkAdapterError("unavailable", "SDK lifecycle response omitted a session endpoint.");
164
+ return { url: nested.url, token: nested.token };
165
+ }
166
+
167
+ function sessionId(value: unknown): string {
168
+ const candidate = object(value);
169
+ const result = object(candidate?.result) ?? candidate;
170
+ if (typeof result?.sessionId !== "string" || !result.sessionId)
171
+ throw new AcpSdkAdapterError("unavailable", "SDK lifecycle response omitted a session id.");
172
+ return result.sessionId;
181
173
  }
182
174
 
175
+ function pageItems(value: unknown): unknown[] {
176
+ const response = object(value);
177
+ const result = object(response?.result) ?? response;
178
+ const page = object(result?.page);
179
+ return Array.isArray(page?.items) ? page.items : [];
180
+ }
181
+
182
+ function correlationFrom(...values: unknown[]): PromptCorrelation {
183
+ const correlation: PromptCorrelation = {};
184
+ for (const value of values) {
185
+ const candidate = object(value);
186
+ for (const record of [candidate, object(candidate?.result)]) {
187
+ if (!record) continue;
188
+ if (!correlation.commandId) {
189
+ const commandId = record.commandId ?? record.command_id;
190
+ if (typeof commandId === "string" && commandId) correlation.commandId = commandId;
191
+ }
192
+ if (!correlation.turnId) {
193
+ const turnId = record.turnId ?? record.turn_id;
194
+ if (typeof turnId === "string" && turnId) correlation.turnId = turnId;
195
+ }
196
+ }
197
+ }
198
+ return correlation;
199
+ }
200
+
201
+ function correlationsConflict(expected: PromptCorrelation, actual: PromptCorrelation): boolean {
202
+ return (
203
+ (expected.commandId !== undefined && actual.commandId !== undefined && expected.commandId !== actual.commandId) ||
204
+ (expected.turnId !== undefined && actual.turnId !== undefined && expected.turnId !== actual.turnId)
205
+ );
206
+ }
207
+
208
+ function correlationsMatch(expected: PromptCorrelation, actual: PromptCorrelation): boolean {
209
+ return (
210
+ (expected.commandId !== undefined && expected.commandId === actual.commandId) ||
211
+ (expected.turnId !== undefined && expected.turnId === actual.turnId)
212
+ );
213
+ }
214
+
215
+ function isPromptActivity(eventType: string): boolean {
216
+ return [
217
+ "agent_start",
218
+ "turn_start",
219
+ "message_start",
220
+ "message_update",
221
+ "message_end",
222
+ "tool_execution_start",
223
+ "tool_execution_update",
224
+ "tool_execution_end",
225
+ ].includes(eventType);
226
+ }
227
+
228
+ export type TranscriptReplayBlock = { type: "text"; text: string } | { type: "image"; data: string; mimeType: string };
229
+
183
230
  /**
184
- * Per-record terminal state machine for session deletion.
185
- * - `active`: normal operation; all lifecycle methods allowed.
186
- * - `deleting`: a terminal delete has reserved the record; prompt/config/close/
187
- * load/resume/fork reject, concurrent deletes join the terminal promise.
188
- * - `pre_dispatch_retryable`: close_failed_retryable before verified-delete
189
- * dispatch; normal ops rejected but a subsequent delete may retry.
190
- * - `cleanup_pending`: verified delete returned cleanup_pending with identity
191
- * evidence; normal ops rejected but a subsequent delete may retry.
192
- * - `terminal_failure`: close_unknown or quiescence failure (or any unexpected
193
- * failure); normal ops rejected and delete NEVER reopens — subsequent deletes
194
- * share the settled terminal failure error.
195
- * - `deleted`: verified delete succeeded; record removed from the active map.
231
+ * The production transcript query exposes durable `{ body, textSummary }`
232
+ * entries, not an ACP-shaped `content` array. Historical session JSONL has no
233
+ * recoverable image bytes, so replay exposes that boundary rather than
234
+ * pretending images were restored.
196
235
  */
197
- type AcpSessionTerminalState =
198
- | "active"
199
- | "deleting"
200
- | "pre_dispatch_retryable"
201
- | "cleanup_pending"
202
- | "terminal_failure"
203
- | "deleted";
204
-
205
- type ManagedSessionRecord = {
206
- session: AgentSession;
207
- mcpManager: MCPManager | undefined;
208
- promptTurn: PromptTurnState | undefined;
209
- promptQueue: PromptQueueState;
210
- liveMessageId: string | undefined;
211
- liveMessageProgress: { textEmitted: boolean; thoughtEmitted: boolean } | undefined;
212
- toolArgsById: Map<string, unknown>;
213
- extensionsConfigured: boolean;
214
- // Installed after the bootstrap race guard or eagerly when the client starts a prompt;
215
- // released in `#disposeSessionRecord`. Lives independent of any prompt turn.
216
- lifetimeUnsubscribe: (() => void) | undefined;
217
- // Per-record terminal state machine (see AcpSessionTerminalState). After ANY
218
- // delete failure the record stays non-`active` so prompt/config/close/load/
219
- // resume/fork remain rejected. Only `pre_dispatch_retryable` and
220
- // `cleanup_pending` permit a retry delete; `terminal_failure` never reopens.
221
- terminalState: AcpSessionTerminalState;
222
- // The settled terminal failure error, shared with subsequent delete callers
223
- // so a terminal_failure is observable without re-running the terminal op.
224
- terminalFailure: Error | undefined;
225
- // Resolves when an in-progress terminal delete settles. Concurrent delete
226
- // callers and abort/shutdown join this promise.
227
- terminalPromise: Promise<DeleteSessionResponse> | undefined;
228
- };
229
-
230
- type ReplayableMessage = {
231
- role: string;
232
- content?: unknown;
233
- errorMessage?: string;
234
- toolCallId?: string;
235
- toolName?: string;
236
- details?: unknown;
237
- isError?: boolean;
238
- };
236
+ export interface TranscriptReplayContent {
237
+ blocks: TranscriptReplayBlock[];
238
+ images: { available: false; reason: "historical_transcript_images_unavailable" };
239
+ }
239
240
 
240
- type ReplayableToolItem = {
241
- type?: unknown;
242
- id?: unknown;
243
- name?: unknown;
244
- arguments?: unknown;
245
- input?: unknown;
246
- };
241
+ export function transcriptReplayContent(entry: unknown): TranscriptReplayContent {
242
+ const record = object(entry);
243
+ if (typeof record?.body !== "string")
244
+ throw new AcpSdkAdapterError(
245
+ "transcript_body_unavailable",
246
+ "ACP cannot replay a transcript entry without its production body.",
247
+ );
248
+ return {
249
+ blocks: record.body.length > 0 ? [{ type: "text", text: record.body }] : [],
250
+ images: { available: false, reason: "historical_transcript_images_unavailable" },
251
+ };
252
+ }
247
253
 
248
- type MCPConfigMap = {
249
- [name: string]: MCPServerConfig;
254
+ type ReceivedSdkEvent = {
255
+ event: JsonObject;
256
+ /** Event payload accepted by the ACP event mapper, when this is an agent-wire frame. */
257
+ wirePayload?: JsonObject;
250
258
  };
251
259
 
252
- type MCPSource = {
253
- provider: string;
254
- providerName: string;
255
- path: string;
256
- level: "project";
257
- };
260
+ /**
261
+ * Native session hosts emit `activity` directly; test-only/legacy adapters may
262
+ * wrap agent-wire events in `{ type: "event", payload }`. Normalize both
263
+ * without treating notification-specific frames as agent lifecycle truth.
264
+ */
265
+ function receivedSdkEvent(frame: JsonObject): ReceivedSdkEvent | undefined {
266
+ if (frame.type === "activity") {
267
+ const type = frame.state === "busy" ? "agent_start" : frame.state === "idle" ? "agent_end" : undefined;
268
+ return type ? { event: { type, ...correlationFrom(frame) } } : undefined;
269
+ }
270
+ if (frame.type !== "event") return undefined;
271
+ const payload = object(frame.payload);
272
+ if (!payload) return undefined;
273
+ const replayPayload = object(payload.payload);
274
+ const event = object(payload.event) ?? replayPayload ?? payload;
275
+ if (typeof event.type !== "string") return undefined;
276
+ return {
277
+ event,
278
+ ...(object(payload.event) ? { wirePayload: payload } : {}),
279
+ };
280
+ }
258
281
 
259
- type MCPSourceMap = {
260
- [name: string]: MCPSource;
282
+ const ACP_CONFIG_OPTIONS = [
283
+ { id: MODEL_CONFIG_ID, name: "Model", options: [] },
284
+ { id: THINKING_CONFIG_ID, name: "Thinking", options: [] },
285
+ {
286
+ id: "steeringMode",
287
+ name: "Steering queue",
288
+ options: [
289
+ { value: "all", name: "All" },
290
+ { value: "one-at-a-time", name: "One at a time" },
291
+ ],
292
+ },
293
+ {
294
+ id: "followUpMode",
295
+ name: "Follow-up queue",
296
+ options: [
297
+ { value: "all", name: "All" },
298
+ { value: "one-at-a-time", name: "One at a time" },
299
+ ],
300
+ },
301
+ {
302
+ id: "interruptMode",
303
+ name: "Interrupt mode",
304
+ options: [
305
+ { value: "immediate", name: "Immediate" },
306
+ { value: "wait", name: "Wait" },
307
+ ],
308
+ },
309
+ ] as const;
310
+
311
+ const ACP_CONFIG_CONTROL_OPERATIONS: Record<string, string> = {
312
+ steeringMode: "queue.steering_mode.set",
313
+ followUpMode: "queue.follow_up_mode.set",
314
+ interruptMode: "queue.interrupt_mode.set",
261
315
  };
262
316
 
263
- type CreateAcpSession = (cwd: string) => Promise<AgentSession>;
317
+ function configValues(query: unknown): Map<string, string> {
318
+ const values = new Map<string, string>();
319
+ for (const item of pageItems(query)) {
320
+ const record = object(item);
321
+ if (!record) continue;
322
+ if (typeof record.id === "string" && typeof record.value === "string") {
323
+ values.set(record.id, record.value);
324
+ continue;
325
+ }
326
+ for (const [id, value] of Object.entries(record)) {
327
+ if (typeof value === "string") values.set(id, value);
328
+ }
329
+ }
330
+ return values;
331
+ }
264
332
 
265
- /**
266
- * Bridge a single ExtensionUIContext call to the ACP `unstable_createElicitation`
267
- * surface. Skills/extensions ask for one value at a time (a chosen option, a
268
- * confirmation, a piece of text), so every elicitation here uses a one-property
269
- * `value` schema; the caller narrows the resulting `ElicitationContentValue`
270
- * back to its concrete primitive type.
271
- *
272
- * `dialogOptions.signal` short-circuits the elicitation if it is already
273
- * aborted and races the in-flight request against the abort event. The SDK
274
- * exposes no `cancel_elicitation` surface for form-mode elicitations
275
- * (`unstable_completeElicitation` is URL-mode only), so the ACP request itself
276
- * keeps running on the client side until the user dismisses it — but
277
- * resolving the local promise unblocks the caller (matches the RPC mode
278
- * pattern in `requestRpcEditor`). The abort listener is removed once the
279
- * elicitation settles so that callers which reuse the same signal across many
280
- * elicitations (e.g. `ask` multi-select loops) don't accumulate listeners and
281
- * trip Node's `MaxListeners` warning.
282
- *
283
- * `dialogOptions.timeout` mirrors `RpcExtensionUIContext.#createDialogPromise`:
284
- * when the timer fires before the client responds, `onTimeout` is invoked and
285
- * the caller's promise resolves to the stub fallback. Late SDK responses that
286
- * arrive after abort/timeout — both rejections and successful `accept`s —
287
- * are dropped silently (no `logger.warn`) to keep operator logs clean.
288
- */
289
- async function elicitFromAcpClient(
290
- connection: AgentSideConnection,
291
- sessionId: string,
292
- method: "select" | "confirm" | "input",
293
- message: string,
294
- property: ElicitationPropertySchema,
295
- dialogOptions: ExtensionUIDialogOptions | undefined,
296
- ): Promise<ElicitationContentValue | undefined> {
297
- const signal = dialogOptions?.signal;
298
- if (signal?.aborted) {
299
- return undefined;
333
+ function modelConfigOptions(query: unknown, current: string | undefined): { value: string; name: string }[] {
334
+ const options = new Map<string, string>();
335
+ for (const item of pageItems(query)) {
336
+ const model = object(item);
337
+ if (!model || typeof model.provider !== "string" || typeof model.id !== "string") continue;
338
+ const value = `${model.provider}/${model.id}`;
339
+ options.set(value, typeof model.name === "string" ? model.name : value);
300
340
  }
301
- const { promise, resolve } = Promise.withResolvers<CreateElicitationResponse | undefined>();
302
- let settled = false;
303
- let timeoutId: NodeJS.Timeout | undefined;
304
- const finish = (value: CreateElicitationResponse | undefined) => {
305
- if (settled) return;
306
- settled = true;
307
- if (timeoutId !== undefined) clearTimeout(timeoutId);
308
- signal?.removeEventListener("abort", onAbort);
309
- resolve(value);
341
+ if (current && !options.has(current)) options.set(current, current);
342
+ return [...options].map(([value, name]) => ({ value, name }));
343
+ }
344
+
345
+ const THINKING_CONFIG_OPTIONS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"].map(value => ({
346
+ value,
347
+ name: value,
348
+ }));
349
+
350
+ /** Maps live canonical SDK config and model queries into the ACP 1.2.1 session state surface. */
351
+ export function acpSessionStateFromConfig(query: unknown, modelsQuery?: unknown) {
352
+ const values = configValues(query);
353
+ const currentModeId = values.get(MODE_CONFIG_ID) === ACP_PLAN_MODE_ID ? ACP_PLAN_MODE_ID : ACP_DEFAULT_MODE_ID;
354
+ return {
355
+ configOptions: [
356
+ {
357
+ id: MODE_CONFIG_ID,
358
+ name: "Mode",
359
+ type: "select" as const,
360
+ currentValue: currentModeId,
361
+ options: [
362
+ { value: ACP_DEFAULT_MODE_ID, name: "Default" },
363
+ { value: ACP_PLAN_MODE_ID, name: "Plan" },
364
+ ],
365
+ },
366
+ ...ACP_CONFIG_OPTIONS.flatMap(option => {
367
+ const value = values.get(option.id);
368
+ if (value === undefined) return [];
369
+ const options =
370
+ option.id === MODEL_CONFIG_ID
371
+ ? modelConfigOptions(modelsQuery, value)
372
+ : option.id === THINKING_CONFIG_ID
373
+ ? THINKING_CONFIG_OPTIONS
374
+ : [...option.options];
375
+ return [{ ...option, type: "select" as const, currentValue: value, options }];
376
+ }),
377
+ ],
378
+ modes: {
379
+ availableModes: [
380
+ { id: ACP_DEFAULT_MODE_ID, name: "Default" },
381
+ { id: ACP_PLAN_MODE_ID, name: "Plan" },
382
+ ],
383
+ currentModeId,
384
+ },
310
385
  };
311
- const onAbort = () => finish(undefined);
312
- signal?.addEventListener("abort", onAbort, { once: true });
313
- if (dialogOptions?.timeout !== undefined) {
314
- timeoutId = setTimeout(() => {
315
- if (settled) return;
316
- try {
317
- dialogOptions.onTimeout?.();
318
- } catch (error) {
319
- // A throwing `onTimeout` must not leave the elicitation promise
320
- // pending — settle it via `finish` below regardless.
321
- logger.warn("ACP elicitation onTimeout threw", { sessionId, method, error });
386
+ }
387
+
388
+ /** Convert every ACP prompt block the agent advertises without silently discarding context. */
389
+ export function acpPromptPayload(blocks: PromptRequest["prompt"]): {
390
+ text: string;
391
+ images: Array<{ data: string; mimeType: string }>;
392
+ } {
393
+ const text: string[] = [];
394
+ const images: Array<{ data: string; mimeType: string }> = [];
395
+ for (const block of blocks) {
396
+ switch (block.type) {
397
+ case "text":
398
+ text.push(block.text);
399
+ break;
400
+ case "image":
401
+ if (block.uri) text.push(`[Image URI: ${block.uri}]`);
402
+ images.push({ data: block.data, mimeType: block.mimeType });
403
+ break;
404
+ case "resource_link":
405
+ text.push(
406
+ [
407
+ `[Resource: ${block.name}]`,
408
+ `URI: ${block.uri}`,
409
+ ...(block.title ? [`Title: ${block.title}`] : []),
410
+ ...(block.description ? [block.description] : []),
411
+ ...(block.mimeType ? [`MIME: ${block.mimeType}`] : []),
412
+ ...(typeof block.size === "number" ? [`Size: ${block.size}`] : []),
413
+ ].join("\n"),
414
+ );
415
+ break;
416
+ case "resource": {
417
+ const resource = block.resource;
418
+ if ("text" in resource) {
419
+ text.push(
420
+ [
421
+ `[Resource: ${resource.uri}]`,
422
+ ...(resource.mimeType ? [`MIME: ${resource.mimeType}`] : []),
423
+ resource.text,
424
+ ].join("\n"),
425
+ );
426
+ break;
427
+ }
428
+ const mimeType = resource.mimeType ?? "application/octet-stream";
429
+ if (!mimeType.startsWith("image/"))
430
+ throw new AcpSdkAdapterError(
431
+ "unsupported_content",
432
+ `Unsupported embedded resource MIME type: ${mimeType}`,
433
+ );
434
+ text.push(`[Resource: ${resource.uri}]\nMIME: ${mimeType}`);
435
+ images.push({ data: resource.blob, mimeType });
436
+ break;
322
437
  }
323
- finish(undefined);
324
- }, dialogOptions.timeout);
325
- // A long pending timeout alone shouldn't keep the event loop alive when
326
- // the rest of the agent has shut down — matches `job-manager.ts` /
327
- // `executor.ts` timer hygiene. Connection + session lifetimes keep the
328
- // loop alive on the happy path.
329
- timeoutId.unref();
438
+ case "audio":
439
+ throw new AcpSdkAdapterError("unsupported_content", "ACP audio prompts are not supported.");
440
+ default:
441
+ throw new AcpSdkAdapterError("unsupported_content", "Unsupported ACP prompt content.");
442
+ }
330
443
  }
331
- connection
332
- .unstable_createElicitation({
333
- mode: "form",
334
- sessionId,
444
+ if (text.length === 0 && images.length === 0)
445
+ throw new AcpSdkAdapterError("invalid_input", "ACP prompt must contain at least one supported content block.");
446
+ return { text: text.join("\n"), images };
447
+ }
448
+
449
+ /** Registers a permission provider only when the ACP client requires prompts. */
450
+ export function acpProviderRegistrations(
451
+ capabilities: ClientCapabilities | undefined,
452
+ env: NodeJS.ProcessEnv = process.env,
453
+ ): AcpProviderRegistration[] {
454
+ return [
455
+ ...(capabilities?.fs?.readTextFile || capabilities?.fs?.writeTextFile
456
+ ? [{ capability: "fs", definitions: [] }]
457
+ : []),
458
+ ...(capabilities?.terminal ? [{ capability: "terminal", definitions: [] }] : []),
459
+ ...(resolveAcpPermissionMode(capabilities, env) === "prompt"
460
+ ? [{ capability: "permission", definitions: [] }]
461
+ : []),
462
+ { capability: "ui", definitions: [] },
463
+ ];
464
+ }
465
+
466
+ /** Maps ACP permission handling to the session's canonical SDK policy. */
467
+ export async function applyAcpPermissionMode(
468
+ adapter: Pick<AcpSdkAdapter, "control">,
469
+ capabilities: ClientCapabilities | undefined,
470
+ env: NodeJS.ProcessEnv = process.env,
471
+ ): Promise<void> {
472
+ const mode = resolveAcpPermissionMode(capabilities, env);
473
+ await adapter.control("permission_mode.set", { mode: mode === "prompt" ? "prompt" : "allow" });
474
+ }
475
+
476
+ /** Applies CLI-provided ACP startup settings through SDK controls before session exposure. */
477
+ export async function applyAcpStartupOptions(
478
+ adapter: Pick<AcpSdkAdapter, "setModel" | "control">,
479
+ options: AcpStartupOptions | undefined,
480
+ ): Promise<void> {
481
+ if (options?.modelId) await adapter.setModel(options.modelId);
482
+ if (options?.thinkingLevel) await adapter.control("thinking.set", { level: options.thinkingLevel });
483
+ }
484
+
485
+ /** ACP form elicitation uses the client-facing reverse surface without owning a session runtime. */
486
+ export function createAcpExtensionUiContext(
487
+ connection: AgentSideConnection,
488
+ getSessionId: () => string,
489
+ capabilities: ClientCapabilities | undefined,
490
+ ): {
491
+ select: (
492
+ message: string,
493
+ options: string[],
494
+ dialog?: { signal?: AbortSignal; timeout?: number; onTimeout?: () => void },
495
+ ) => Promise<string | undefined>;
496
+ confirm: (
497
+ message: string,
498
+ detail?: string,
499
+ dialog?: { signal?: AbortSignal; timeout?: number; onTimeout?: () => void },
500
+ ) => Promise<boolean>;
501
+ input: (
502
+ message: string,
503
+ placeholder?: string,
504
+ dialog?: { signal?: AbortSignal; timeout?: number; onTimeout?: () => void },
505
+ ) => Promise<string | undefined>;
506
+ } {
507
+ const elicit = async (
508
+ kind: "select" | "confirm" | "input",
509
+ message: string,
510
+ options: string[] | undefined,
511
+ dialog: { signal?: AbortSignal; timeout?: number; onTimeout?: () => void } | undefined,
512
+ ): Promise<unknown> => {
513
+ if (!capabilities?.elicitation?.form || dialog?.signal?.aborted) return undefined;
514
+ const request = (
515
+ connection as unknown as { unstable_createElicitation(input: JsonObject): Promise<JsonObject> }
516
+ ).unstable_createElicitation({
517
+ sessionId: getSessionId(),
335
518
  message,
336
519
  requestedSchema: {
337
520
  type: "object",
338
- properties: { value: property },
521
+ properties: {
522
+ value:
523
+ kind === "confirm" ? { type: "boolean" } : { type: "string", ...(options ? { enum: options } : {}) },
524
+ },
339
525
  required: ["value"],
340
526
  },
341
- })
342
- .then(finish, error => {
343
- // Caller may already have moved on via abort/timeout; suppress noise.
344
- if (settled) return;
345
- logger.warn("ACP elicitation failed", { sessionId, method, error });
346
- finish(undefined);
347
527
  });
348
- const response = await promise;
349
- if (
350
- response?.action !== "accept" ||
351
- typeof response.content !== "object" ||
352
- response.content === null ||
353
- !("value" in response.content)
354
- ) {
355
- return undefined;
356
- }
357
- const value = response.content.value;
358
- if (
359
- typeof value === "string" ||
360
- typeof value === "number" ||
361
- typeof value === "boolean" ||
362
- (Array.isArray(value) && value.every(item => typeof item === "string"))
363
- ) {
364
- return value;
365
- }
366
- return undefined;
367
- }
368
-
369
- /**
370
- * Build an {@link ExtensionUIContext} that translates skill/extension UI
371
- * requests into ACP elicitations against `connection` for the session
372
- * returned by `getSessionId()`. The id is read lazily at each elicitation
373
- * because `AgentSession.sessionId` is a getter over `sessionManager` state
374
- * that mutates when an extension command calls `ctx.newSession` /
375
- * `ctx.switchSession` — snapshotting it once at factory time would route
376
- * later elicitations to the pre-switch id. Live reads keep the bridge
377
- * symmetric with every other `sessionUpdate` call in this file
378
- * (`record.session.sessionId` is always evaluated at emit time).
379
- *
380
- * The non-elicitation surface (custom components, editor, theming,
381
- * terminal input) remains stubbed — ACP clients render those themselves
382
- * or not at all. Capability gating respects the client's `initialize`
383
- * advertisement.
384
- */
385
- export function createAcpExtensionUiContext(
386
- connection: AgentSideConnection,
387
- getSessionId: () => string,
388
- clientCapabilities: ClientCapabilities | undefined,
389
- ): ExtensionUIContext {
390
- const supportsForm = clientCapabilities?.elicitation?.form != null;
528
+ let timer: NodeJS.Timeout | undefined;
529
+ const timeout =
530
+ dialog?.timeout === undefined
531
+ ? undefined
532
+ : new Promise<undefined>(resolve => {
533
+ timer = setTimeout(() => {
534
+ dialog.onTimeout?.();
535
+ resolve(undefined);
536
+ }, dialog.timeout);
537
+ });
538
+ try {
539
+ const response = timeout ? await Promise.race([request, timeout]) : await request;
540
+ return object(object(response)?.content)?.value;
541
+ } catch {
542
+ return undefined;
543
+ } finally {
544
+ if (timer) clearTimeout(timer);
545
+ }
546
+ };
391
547
  return {
392
- select: async (title, options, dialogOptions) => {
393
- if (!supportsForm) return undefined;
394
- const value = await elicitFromAcpClient(
395
- connection,
396
- getSessionId(),
397
- "select",
398
- title,
399
- { type: "string", enum: options },
400
- dialogOptions,
401
- );
402
- return typeof value === "string" ? value : undefined;
403
- },
404
- confirm: async (title, message, dialogOptions) => {
405
- if (!supportsForm) return false;
406
- const value = await elicitFromAcpClient(
407
- connection,
408
- getSessionId(),
409
- "confirm",
410
- message.trim().length > 0 ? `${title}\n\n${message}` : title,
411
- { type: "boolean" },
412
- dialogOptions,
413
- );
414
- return typeof value === "boolean" ? value : false;
548
+ select: async (message, options, dialog) => {
549
+ const value = await elicit("select", message, options, dialog);
550
+ return typeof value === "string" && options.includes(value) ? value : undefined;
415
551
  },
416
- input: async (title, placeholder, dialogOptions) => {
417
- if (!supportsForm) return undefined;
418
- const value = await elicitFromAcpClient(
419
- connection,
420
- getSessionId(),
421
- "input",
422
- title,
423
- // ACP's `StringPropertySchema` has no `placeholder` field, so we
424
- // surface the placeholder text as `description` — the closest
425
- // semantic field a client can render alongside the input.
426
- // Empty / whitespace-only placeholders are treated as absent.
427
- { type: "string", ...(placeholder?.trim() ? { description: placeholder } : {}) },
428
- dialogOptions,
429
- );
552
+ confirm: async (message, detail, dialog) =>
553
+ (await elicit("confirm", detail ? `${message}\n\n${detail}` : message, undefined, dialog)) === true,
554
+ input: async (message, placeholder, dialog) => {
555
+ const value = await elicit("input", placeholder ? `${message}\n\n${placeholder}` : message, undefined, dialog);
430
556
  return typeof value === "string" ? value : undefined;
431
557
  },
432
- notify: (message, type) => {
433
- logger.debug("ACP extension notification", { message, type });
434
- },
435
- onTerminalInput: () => () => {},
436
- setStatus: () => {},
437
- setWorkingMessage: () => {},
438
- setWidget: () => {},
439
- setFooter: () => {},
440
- setHeader: () => {},
441
- setTitle: () => {},
442
- custom: async () => undefined as never,
443
- pasteToEditor: () => {},
444
- setEditorText: () => {},
445
- getEditorText: () => "",
446
- editor: async () => undefined,
447
- setEditorComponent: () => {},
448
- get theme() {
449
- return theme;
450
- },
451
- getAllThemes: async () => [],
452
- getTheme: async () => undefined,
453
- setTheme: async () => ({ success: false, error: "Theme changes are unavailable in ACP mode" }),
454
- getToolsExpanded: () => false,
455
- setToolsExpanded: () => {},
456
558
  };
457
559
  }
458
560
 
561
+ /**
562
+ * ACP is a pure SDK client. Session processes are created and resumed by the
563
+ * broker, while all per-session operations use that session's authenticated SDK
564
+ * endpoint. This class deliberately imports neither AgentSession nor any local
565
+ * runtime host component.
566
+ */
459
567
  export class AcpAgent implements Agent {
460
- #connection: AgentSideConnection;
461
- #initialSession: AgentSession | undefined;
462
- #createSession: CreateAcpSession;
463
- #sessions = new Map<string, ManagedSessionRecord>();
464
- #disposePromise: Promise<void> | undefined;
465
- #cleanupRegistered = false;
568
+ readonly #connection: AgentSideConnection;
569
+ readonly #agentDir: string;
570
+ readonly #sessions = new Map<string, SessionRecord>();
571
+ readonly #attaching = new Map<string, PendingAttachment>();
572
+ readonly #resolvingExisting = new Map<string, PendingAttachment>();
573
+ readonly #knownSessionCwds = new Map<string, string>();
574
+ readonly #sessionEpochs = new Map<string, number>();
575
+ readonly #tearingDown = new Map<string, number>();
466
576
  #clientCapabilities: ClientCapabilities | undefined;
467
- #cancelCleanupTimeoutMs = ACP_CANCEL_CLEANUP_TIMEOUT_MS;
468
- /** Immutable canonical cwd locked by the first successful explicit-cwd lifecycle/list call. */
469
- #canonicalCwdScope: string | undefined;
470
- /** Authority snapshot from the last complete strict scoped inventory. Invalidation bumps this. */
471
- #authorityGeneration = 0;
472
- #authoritySnapshot: AcpAuthoritySnapshot | undefined;
473
- /**
474
- * Same-connection retry evidence for an incomplete verified delete. Keyed by
475
- * session id. Phase-aware: every cleanup_pending carries the transcript
476
- * identity at failure time, which is re-verified against the fresh candidate
477
- * on retry so a replaced transcript cannot authorize a replacement; the
478
- * artifacts-phase result additionally carries the artifact directory
479
- * identity to re-accept. Cleared once a delete reaches `kind: "deleted"`.
480
- */
481
- #pendingDeleteEvidence = new Map<string, PendingDeleteEvidence>();
482
- #shutdownPromise: Promise<void> | undefined;
483
- #lifecycleOperations = new Set<Promise<unknown>>();
484
- #shuttingDown = false;
577
+ #broker: Promise<BrokerConnection> | undefined;
578
+ readonly #startupOptions: AcpStartupOptions | undefined;
579
+ #disposed = false;
580
+ #disposePromise: Promise<void> | undefined;
485
581
 
486
- constructor(connection: AgentSideConnection, createSession: CreateAcpSession, initialSession?: AgentSession) {
582
+ constructor(
583
+ connection: AgentSideConnection,
584
+ options?: { agentDir?: string; startupOptions?: AcpStartupOptions } | unknown,
585
+ ) {
487
586
  this.#connection = connection;
488
- this.#initialSession = initialSession;
489
- this.#createSession = createSession;
490
- }
491
-
492
- setCancelCleanupTimeoutForTesting(timeoutMs: number): void {
493
- this.#cancelCleanupTimeoutMs = Math.max(1, timeoutMs);
587
+ const candidate = object(options);
588
+ this.#agentDir = typeof candidate?.agentDir === "string" ? candidate.agentDir : getAgentDir();
589
+ this.#startupOptions = parseAcpStartupOptions(candidate?.startupOptions);
590
+ queueMicrotask(() => {
591
+ if (connection.signal.aborted) {
592
+ this.#beginDispose();
593
+ } else {
594
+ connection.signal.addEventListener("abort", () => this.#beginDispose(), { once: true });
595
+ }
596
+ });
494
597
  }
495
598
 
496
599
  async initialize(params: InitializeRequest): Promise<InitializeResponse> {
497
- this.#registerConnectionCleanup();
498
600
  this.#clientCapabilities = params.clientCapabilities;
499
601
  const authMethods: AuthMethod[] = [
500
602
  {
@@ -514,2377 +616,899 @@ export class AcpAgent implements Agent {
514
616
  }
515
617
  return {
516
618
  protocolVersion: PROTOCOL_VERSION,
517
- agentInfo: {
518
- name: "sayknow-cli",
519
- title: "Sayknow-CLI",
520
- version: CODING_AGENT_VERSION,
521
- },
619
+ agentInfo: { name: "sayknow-cli", title: "Sayknow-CLI", version: packageJson.version },
522
620
  authMethods,
523
621
  agentCapabilities: {
524
622
  loadSession: true,
525
- mcpCapabilities: {
526
- http: true,
527
- sse: true,
528
- },
529
- promptCapabilities: {
530
- embeddedContext: true,
531
- image: true,
532
- },
533
- sessionCapabilities: {
534
- list: {},
535
- fork: {},
536
- resume: {},
537
- close: {},
538
- delete: {},
539
- },
623
+ promptCapabilities: { embeddedContext: true, image: true },
624
+ sessionCapabilities: { list: {}, fork: {}, resume: {}, close: {}, delete: {} },
540
625
  },
541
626
  };
542
627
  }
543
628
 
544
629
  async authenticate(params: AuthenticateRequest): Promise<AuthenticateResponse> {
545
- // ACP spec: `methodId` must be one of the methods advertised by `initialize`.
546
- // Reject anything else so malformed clients fail fast rather than appearing
547
- // authenticated and surfacing a downstream model failure later.
548
- const supportsTerminalAuth = this.#clientCapabilities?.auth?.terminal === true;
549
- const validMethods = supportsTerminalAuth ? ["agent", "terminal"] : ["agent"];
550
- if (!validMethods.includes(params.methodId)) {
551
- throw new Error(`Unknown ACP auth method: ${params.methodId}`);
552
- }
630
+ const methods = this.#clientCapabilities?.auth?.terminal ? ["agent", "terminal"] : ["agent"];
631
+ if (!methods.includes(params.methodId)) throw new Error(`Unknown ACP auth method: ${params.methodId}`);
553
632
  return {};
554
633
  }
555
634
 
556
635
  async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
557
- this.#rejectIfShuttingDown();
558
- // Stage scope before the lifecycle op; commit only after it succeeds so a
559
- // failed first call does not pin the connection to a cwd it never served.
560
- const canonical = this.#stageCanonicalScope(params.cwd);
561
- const record = await this.#trackLifecycle(() => this.#createNewSessionRecord(params.cwd, params.mcpServers));
562
- this.#rejectIfShuttingDown();
563
- this.#commitCanonicalScope(canonical);
564
- this.#invalidateAuthority();
565
- const response: NewSessionResponse = {
566
- sessionId: record.session.sessionId,
567
- configOptions: this.#buildConfigOptions(record.session),
568
- modes: this.#buildModeState(record.session),
569
- };
570
- this.#scheduleBootstrapUpdates(record.session.sessionId);
571
- return response;
572
- }
573
-
574
- async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
575
- this.#rejectIfShuttingDown();
576
- const canonical = this.#stageCanonicalScope(params.cwd);
577
- const existingRecord = this.#sessions.get(params.sessionId);
578
- const record = await this.#trackLifecycle(() =>
579
- this.#loadManagedSession(params.sessionId, params.cwd, params.mcpServers),
636
+ this.#assertNoMcpServers(params);
637
+ this.#assertAbsoluteCwd(params.cwd);
638
+ const result = await (await this.#brokerAdapter()).global(
639
+ "session.create",
640
+ {
641
+ cwd: params.cwd,
642
+ target: { path: params.cwd },
643
+ ...(this.#startupOptions?.modelPreset ? { modelPreset: this.#startupOptions.modelPreset } : {}),
644
+ },
645
+ randomUUID(),
580
646
  );
647
+ const id = sessionId(result);
648
+ this.#knownSessionCwds.set(id, params.cwd);
581
649
  try {
582
- this.#rejectIfShuttingDown();
583
- await this.#replaySessionHistory(record);
584
- this.#rejectIfShuttingDown();
585
- // Construct every fallible response/bootstrap value inside the
586
- // transaction so a build/schedule failure rolls back the prepared
587
- // record and never pins the connection.
588
- const response: LoadSessionResponse = {
589
- configOptions: this.#buildConfigOptions(record.session),
590
- modes: this.#buildModeState(record.session),
591
- };
592
- this.#scheduleBootstrapUpdates(record.session.sessionId);
593
- // Commit the cwd scope only after replay, response construction, and
594
- // bootstrap scheduling all succeed — a failed first load leaves no
595
- // half-loaded session and does not pin the connection.
596
- this.#commitCanonicalScope(canonical);
597
- this.#invalidateAuthority();
598
- return response;
650
+ await this.#attach(id, params.cwd, endpoint(result));
651
+ await applyAcpStartupOptions(this.#adapter(id), this.#startupOptions);
652
+ this.#scheduleBootstrap(id);
653
+ return { sessionId: id, ...(await this.#sessionState(id)) };
599
654
  } catch (error) {
600
- // Any pre-return failure: roll back the prepared record so a failed
601
- // first load leaves no half-loaded session and does not pin the
602
- // connection, and commit no cwd scope.
603
- if (record !== existingRecord) {
604
- this.#sessions.delete(record.session.sessionId);
605
- await this.#disposeSessionRecord(record);
606
- }
655
+ await this.#discardNewSession(id);
607
656
  throw error;
608
657
  }
609
658
  }
610
659
 
611
- async listSessions(params: ListSessionsRequest): Promise<ListSessionsResponse> {
612
- this.#rejectIfShuttingDown();
613
- const scope = params.cwd ? this.#stageCanonicalScope(params.cwd) : undefined;
614
- for (const record of this.#sessions.values()) {
615
- await record.session.sessionManager.flush();
616
- }
617
- this.#rejectIfShuttingDown();
618
- // Cwd-less / global listing stays display-only and non-authorizing.
619
- if (!scope) {
620
- const sessions = await this.#listStoredSessions(undefined);
621
- const offset = this.#parseCursor(params.cursor ?? undefined);
622
- const paged = sessions.slice(offset, offset + SESSION_PAGE_SIZE);
623
- const nextOffset = offset + paged.length;
624
- return {
625
- sessions: paged.map(session => this.#toSessionInfo(session)),
626
- nextCursor: nextOffset < sessions.length ? String(nextOffset) : undefined,
627
- };
628
- }
629
- // Explicit scoped list: stage scope, strict raw inventory, build the full
630
- // authorization/conflict snapshot BEFORE page slicing so duplicate ids
631
- // beyond page 1 are known before the first page response. Scope commits
632
- // only after cursor validation, stored-session read, and full response
633
- // construction succeed — a failed first list never pins the connection.
634
- if (params.cursor === undefined) {
635
- // A new first-page request is a new issuance event. Refresh the complete
636
- // snapshot and invalidate cursors minted by the previous issuance.
637
- this.#invalidateAuthority();
638
- }
639
- const snapshot = this.#buildAuthoritySnapshot(scope);
640
- // Scoped cursors carry the authority generation at which they were minted
641
- // (form `${generation}:${offset}`). A lifecycle change between page 1 and
642
- // page 2 bumps the generation, so the old cursor is rejected instead of
643
- // silently rebuilding at the new generation. The cwd-less display cursor
644
- // stays a plain offset (see #parseCursor).
645
- const offset = this.#parseScopedCursor(params.cursor ?? undefined, snapshot.generation);
646
- const stored = await this.#listStoredSessions(scope);
647
- const storedById = new Map(stored.map(s => [s.id, s] as const));
648
- const pageIds = snapshot.orderedIds.slice(offset, offset + SESSION_PAGE_SIZE);
649
- const nextOffset = offset + pageIds.length;
650
- const pageSessions = pageIds.map(id => storedById.get(id)).filter((s): s is StoredSessionInfo => s !== undefined);
651
- const response: ListSessionsResponse = {
652
- sessions: pageSessions.map(session => this.#toSessionInfo(session)),
653
- nextCursor: nextOffset < snapshot.orderedIds.length ? `${snapshot.generation}:${nextOffset}` : undefined,
654
- };
655
- // Commit the scope only after cursor validation, stored-session read, and
656
- // full response construction succeed.
657
- this.#commitCanonicalScope(scope);
658
- return response;
660
+ async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
661
+ this.#assertNoMcpServers(params);
662
+ this.#assertAbsoluteCwd(params.cwd);
663
+ await this.#attachExisting(params.sessionId, params.cwd);
664
+ await this.#replaySession(params.sessionId);
665
+ this.#scheduleBootstrap(params.sessionId);
666
+ return await this.#sessionState(params.sessionId);
659
667
  }
660
668
 
661
669
  async resumeSession(params: ResumeSessionRequest): Promise<ResumeSessionResponse> {
662
- this.#rejectIfShuttingDown();
663
- const canonical = this.#stageCanonicalScope(params.cwd);
664
- const record = await this.#trackLifecycle(() =>
665
- this.#resumeManagedSession(params.sessionId, params.cwd, params.mcpServers ?? []),
666
- );
667
- this.#rejectIfShuttingDown();
668
- this.#commitCanonicalScope(canonical);
669
- this.#invalidateAuthority();
670
- const response: ResumeSessionResponse = {
671
- configOptions: this.#buildConfigOptions(record.session),
672
- modes: this.#buildModeState(record.session),
673
- };
674
- this.#scheduleBootstrapUpdates(record.session.sessionId);
675
- return response;
670
+ this.#assertNoMcpServers(params);
671
+ this.#assertAbsoluteCwd(params.cwd);
672
+ await this.#attachExisting(params.sessionId, params.cwd);
673
+ this.#scheduleBootstrap(params.sessionId);
674
+ return await this.#sessionState(params.sessionId);
676
675
  }
677
676
 
678
677
  async unstable_forkSession(params: ForkSessionRequest): Promise<ForkSessionResponse> {
679
- this.#rejectIfShuttingDown();
680
- const canonical = this.#stageCanonicalScope(params.cwd);
681
- const record = await this.#trackLifecycle(() => this.#forkManagedSession(params));
682
- this.#rejectIfShuttingDown();
683
- this.#commitCanonicalScope(canonical);
684
- this.#invalidateAuthority();
685
- const response: ForkSessionResponse = {
686
- sessionId: record.session.sessionId,
687
- configOptions: this.#buildConfigOptions(record.session),
688
- modes: this.#buildModeState(record.session),
689
- };
690
- this.#scheduleBootstrapUpdates(record.session.sessionId);
691
- return response;
678
+ this.#assertNoMcpServers(params);
679
+ this.#assertAbsoluteCwd(params.cwd);
680
+ const source = await this.#resolveSavedSession(params.sessionId, params.cwd);
681
+ const result = await (await this.#brokerAdapter()).global(
682
+ "session.fork",
683
+ {
684
+ cwd: params.cwd,
685
+ sourceSessionId: params.sessionId,
686
+ sourceSessionPath: source,
687
+ target: { path: params.cwd },
688
+ },
689
+ randomUUID(),
690
+ );
691
+ const id = sessionId(result);
692
+ this.#knownSessionCwds.set(id, params.cwd);
693
+ try {
694
+ await this.#attach(id, params.cwd, endpoint(result));
695
+ this.#scheduleBootstrap(id);
696
+ return { sessionId: id, ...(await this.#sessionState(id)) };
697
+ } catch (error) {
698
+ await this.#discardNewSession(id);
699
+ throw error;
700
+ }
701
+ }
702
+
703
+ async listSessions(params: ListSessionsRequest): Promise<ListSessionsResponse> {
704
+ if (params.cwd) this.#assertAbsoluteCwd(params.cwd);
705
+ const result = object(await (await this.#brokerAdapter()).global("session.list"));
706
+ const listing = object(result?.result) ?? result;
707
+ const listed = Array.isArray(listing?.sessions) ? listing.sessions : [];
708
+ if (params.cwd) {
709
+ const discovered = new Set<string>();
710
+ for (const session of listed) {
711
+ const candidate = object(session) as BrokerSession | undefined;
712
+ if (
713
+ typeof candidate?.sessionId !== "string" ||
714
+ typeof candidate.locator?.repo !== "string" ||
715
+ path.resolve(candidate.locator.repo) !== path.resolve(params.cwd)
716
+ )
717
+ continue;
718
+ if (discovered.has(candidate.sessionId))
719
+ throw new AcpSdkAdapterError("conflict", `Broker returned duplicate session id: ${candidate.sessionId}`);
720
+ discovered.add(candidate.sessionId);
721
+ const knownCwd = this.#knownSessionCwds.get(candidate.sessionId);
722
+ if (knownCwd && path.resolve(knownCwd) !== path.resolve(params.cwd))
723
+ throw new AcpSdkAdapterError(
724
+ "conflict",
725
+ `ACP session ${candidate.sessionId} has conflicting cwd authority.`,
726
+ );
727
+ this.#knownSessionCwds.set(candidate.sessionId, params.cwd);
728
+ }
729
+ }
730
+ return paginateAcpSessions(listed, params.cwd ?? undefined, this.#cursor(params.cursor));
692
731
  }
693
732
 
694
733
  async closeSession(params: CloseSessionRequest): Promise<CloseSessionResponse> {
695
734
  const record = this.#sessions.get(params.sessionId);
696
- if (!record) {
735
+ const cwd = record?.cwd ?? this.#knownSessionCwds.get(params.sessionId);
736
+ // ACP close has no cwd. Only connection-owned sessions may reach broker lifecycle control.
737
+ if (!cwd) return {};
738
+ this.#beginTeardown(params.sessionId);
739
+ try {
740
+ await this.#teardownSession(params.sessionId, "closed", true);
741
+ this.#knownSessionCwds.delete(params.sessionId);
697
742
  return {};
743
+ } finally {
744
+ this.#finishTeardown(params.sessionId);
698
745
  }
699
- this.#rejectIfTerminal(record);
700
- await this.#closeManagedSession(params.sessionId, record);
701
- this.#invalidateAuthority();
702
- return {};
703
746
  }
704
747
 
705
- /**
706
- * Hard delete an ACP session. `DeleteSessionRequest` carries only `sessionId`;
707
- * the server owns authorization through the immutable canonical cwd scope and
708
- * the complete strict scoped inventory snapshot.
709
- *
710
- * - No scope / unknown / already-deleted id → lookup-free `{}`.
711
- * - Duplicate/conflict id → visible error; neither transcript nor artifacts change.
712
- * - Active session → reserve `deleting`, drain/cancel prompt, flush, strict
713
- * dispose/close, recheck identity/state, then verified artifact-first deletion.
714
- * - Concurrent delete callers join the terminal promise.
715
- */
716
748
  async deleteSession(params: DeleteSessionRequest): Promise<DeleteSessionResponse> {
717
- // Before scope: return {} without scanning/listing storage.
718
- if (this.#canonicalCwdScope === undefined) {
719
- return {};
720
- }
721
- if (this.#shuttingDown) {
722
- throw new Error("ACP session delete is unavailable during shutdown");
723
- }
724
749
  const record = this.#sessions.get(params.sessionId);
725
- if (record) {
726
- return await this.#deleteActiveSession(params.sessionId, record);
750
+ const cwd = record?.cwd ?? this.#knownSessionCwds.get(params.sessionId);
751
+ // ACP's delete request has no cwd. Only delete sessions this connection has
752
+ // already scoped through the broker; unknown ids remain the protocol no-op.
753
+ if (!cwd) return {};
754
+ this.#beginTeardown(params.sessionId);
755
+ try {
756
+ await this.#teardownSession(params.sessionId, "deleted", true);
757
+ let saved: string;
758
+ try {
759
+ saved = await this.#resolveSavedSession(params.sessionId, cwd);
760
+ } catch (error) {
761
+ if (error instanceof AcpSdkAdapterError && error.code === "not_found") {
762
+ this.#knownSessionCwds.delete(params.sessionId);
763
+ return {};
764
+ }
765
+ throw error;
766
+ }
767
+ await (await this.#brokerAdapter()).global(
768
+ "session.delete",
769
+ { sessionId: params.sessionId, sessionPath: saved, cwd, target: { path: cwd } },
770
+ this.#lifecycleIdempotencyKey(params.sessionId, "session.delete"),
771
+ );
772
+ this.#knownSessionCwds.delete(params.sessionId);
773
+ return {};
774
+ } finally {
775
+ this.#finishTeardown(params.sessionId);
727
776
  }
728
- // Inactive: read authority from the strict inventory snapshot.
729
- return await this.#deleteInactiveSession(params.sessionId);
730
777
  }
731
778
 
732
779
  async setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse> {
733
- const record = this.#getSessionRecord(params.sessionId);
734
- this.#rejectIfTerminal(record);
735
- this.#applyModeChange(record.session, params.modeId);
736
- await this.#connection.sessionUpdate({
737
- sessionId: record.session.sessionId,
738
- update: this.#buildCurrentModeUpdate(record.session),
780
+ if (params.modeId !== ACP_DEFAULT_MODE_ID && params.modeId !== ACP_PLAN_MODE_ID)
781
+ throw new Error(`Unsupported ACP mode: ${params.modeId}`);
782
+ await this.#adapter(params.sessionId).control("mode.plan.set", { on: params.modeId === ACP_PLAN_MODE_ID });
783
+ await this.#publishSessionUpdate(params.sessionId, {
784
+ sessionId: params.sessionId,
785
+ update: { sessionUpdate: "current_mode_update", currentModeId: params.modeId },
739
786
  });
740
- await this.#pushConfigOptionUpdate(record);
741
787
  return {};
742
788
  }
743
789
 
744
790
  async setSessionConfigOption(params: SetSessionConfigOptionRequest): Promise<SetSessionConfigOptionResponse> {
745
- const record = this.#getSessionRecord(params.sessionId);
746
- this.#rejectIfTerminal(record);
747
- if (typeof params.value === "boolean") {
791
+ if (typeof params.value !== "string")
748
792
  throw new Error(`Unsupported boolean ACP config option: ${params.configId}`);
749
- }
750
-
751
793
  switch (params.configId) {
752
794
  case MODE_CONFIG_ID:
753
- this.#applyModeChange(record.session, params.value);
795
+ await this.setSessionMode({ sessionId: params.sessionId, modeId: params.value });
754
796
  break;
755
797
  case MODEL_CONFIG_ID:
756
- await this.#setModelById(record.session, params.value);
798
+ await this.#adapter(params.sessionId).setModel(params.value);
757
799
  break;
758
800
  case THINKING_CONFIG_ID:
759
- this.#setThinkingLevelById(record.session, params.value);
801
+ await this.#adapter(params.sessionId).control("thinking.set", { level: params.value });
760
802
  break;
761
- default:
762
- throw new Error(`Unknown ACP config option: ${params.configId}`);
803
+ default: {
804
+ const operation = ACP_CONFIG_CONTROL_OPERATIONS[params.configId];
805
+ if (!operation) throw new Error(`Unknown ACP config option: ${params.configId}`);
806
+ await this.#adapter(params.sessionId).control(operation, { mode: params.value });
807
+ }
763
808
  }
809
+ const state = await this.#sessionState(params.sessionId);
810
+ await this.#publishSessionUpdate(params.sessionId, {
811
+ sessionId: params.sessionId,
812
+ update: { sessionUpdate: "config_option_update", configOptions: state.configOptions ?? [] },
813
+ });
814
+ return { configOptions: state.configOptions ?? [] };
815
+ }
764
816
 
765
- // When mode is changed via the generic config-option API, mirror the
766
- // `current_mode_update` notification that `setSessionMode` emits so
767
- // ACP clients tracking session-mode state see a consistent transition.
768
- if (params.configId === MODE_CONFIG_ID) {
769
- await this.#connection.sessionUpdate({
770
- sessionId: record.session.sessionId,
771
- update: this.#buildCurrentModeUpdate(record.session),
817
+ async prompt(params: PromptRequest): Promise<PromptResponse> {
818
+ const record = this.#sessions.get(params.sessionId);
819
+ if (!record) throw new AcpSdkAdapterError("not_found", `Unsupported ACP session: ${params.sessionId}`);
820
+ if (record.activePrompt) throw new AcpSdkAdapterError("conflict", "ACP session already has an active prompt.");
821
+ const payload = acpPromptPayload(params.prompt);
822
+ let waiter!: PromptWaiter;
823
+ const response = new Promise<PromptResponse>((resolve, reject) => {
824
+ waiter = {
825
+ cancelRequested: false,
826
+ acknowledged: false,
827
+ activityObserved: false,
828
+ steeringAtAcknowledgement: record.busy,
829
+ boundary: record.inboundSequence,
830
+ correlation: {},
831
+ resolve,
832
+ reject,
833
+ };
834
+ record.activePrompt = waiter;
835
+ });
836
+ try {
837
+ const acknowledgement = await record.adapter.prompt({
838
+ text: payload.text,
839
+ ...(payload.images.length ? { images: payload.images } : {}),
772
840
  });
841
+ waiter.steeringAtAcknowledgement = record.busy;
842
+ // Capture the ingress boundary after the command acknowledgement. Frames
843
+ // queued before this point are stale with respect to this ACP prompt.
844
+ waiter.boundary = record.inboundSequence;
845
+ waiter.correlation = correlationFrom(acknowledgement);
846
+ waiter.acknowledged = true;
847
+ this.#settlePrompt(record, waiter);
848
+ } catch (error) {
849
+ if (record.activePrompt === waiter) record.activePrompt = undefined;
850
+ throw error;
773
851
  }
852
+ return await response;
853
+ }
774
854
 
775
- // For `thinking` the lifetime subscription pushes post-bootstrap; only
776
- // push here when it's not yet installed so pre-bootstrap callers still
777
- // see the change without a post-bootstrap duplicate.
778
- const thinkingHandledBySubscription =
779
- params.configId === THINKING_CONFIG_ID && record.lifetimeUnsubscribe !== undefined;
780
- if (!thinkingHandledBySubscription) {
781
- await this.#pushConfigOptionUpdate(record);
782
- }
783
- return { configOptions: this.#buildConfigOptions(record.session) };
855
+ async cancel(params: { sessionId: string }): Promise<void> {
856
+ const record = this.#sessions.get(params.sessionId);
857
+ if (!record) throw new AcpSdkAdapterError("not_found", `Unsupported ACP session: ${params.sessionId}`);
858
+ const waiter = record.activePrompt;
859
+ const acknowledgement = await record.adapter.cancel();
860
+ const result = object(object(acknowledgement)?.result) ?? object(acknowledgement);
861
+ if (result?.aborted !== true)
862
+ throw new AcpSdkAdapterError(
863
+ "abort_unacknowledged",
864
+ "SDK did not acknowledge cancellation of the active prompt.",
865
+ );
866
+ // Do not retroactively mark a waiter that already settled while the abort
867
+ // request was in flight. A cancelled response means the abort itself won.
868
+ if (waiter && record.activePrompt === waiter) waiter.cancelRequested = true;
784
869
  }
785
870
 
786
- async prompt(params: PromptRequest): Promise<PromptResponse> {
787
- const record = this.#getSessionRecord(params.sessionId);
788
- this.#rejectIfTerminal(record);
789
- const activeTurn = record.promptTurn;
790
- if (activeTurn && !activeTurn.settled && record.session.isStreaming) {
791
- throw new Error("ACP prompt already in progress for this session");
792
- }
793
- return await this.#queuePrompt(record, async () => {
794
- const previousTurn = record.promptTurn;
795
- if (previousTurn) {
796
- // Wait for any prompt that's still settling or whose cancel cleanup is
797
- // still in flight. We deliberately swallow the prompt rejection (the
798
- // owning caller already received it) but let cleanup rejections
799
- // propagate — a timed-out cancel must fail this queued prompt instead
800
- // of letting it run on a session that is about to be closed.
801
- await previousTurn.promise.catch(() => undefined);
802
- await previousTurn.cleanup;
871
+ async extMethod(method: string, params: JsonObject): Promise<JsonObject> {
872
+ try {
873
+ if (method === "_skc/sdk/global") {
874
+ const result = await (await this.#brokerAdapter()).handle(method, params);
875
+ return object(result) ?? {};
876
+ }
877
+ if (method === "_skc/sdk/control" || method === "_skc/sdk/query") {
878
+ const id = typeof params.sessionId === "string" ? params.sessionId : undefined;
879
+ if (!id) throw new AcpSdkAdapterError("invalid_input", "sessionId is required.");
880
+ const result = await this.#adapter(id).handle(method, params);
881
+ return object(result) ?? {};
803
882
  }
883
+ throw new AcpSdkAdapterError("method_not_found", `Unknown ACP ext method: ${method}`);
884
+ } catch (error) {
885
+ const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "internal";
886
+ const message = error instanceof Error ? error.message : String(error);
887
+ return { ok: false, error: { code, message } };
888
+ }
889
+ }
804
890
 
805
- const converted = this.#convertPromptBlocks(params.prompt);
806
- const pendingPrompt = Promise.withResolvers<PromptResponse>();
807
- const metaUserMessageId = params._meta?.userMessageId;
808
- record.promptTurn = {
809
- userMessageId: typeof metaUserMessageId === "string" ? metaUserMessageId : crypto.randomUUID(),
810
- cancelRequested: false,
811
- settled: false,
812
- cleanup: undefined,
813
- usageBaseline: this.#cloneUsageStatistics(record.session.sessionManager.getUsageStatistics()),
814
- unsubscribe: undefined,
815
- resolve: pendingPrompt.resolve,
816
- reject: pendingPrompt.reject,
817
- promise: pendingPrompt.promise,
818
- };
891
+ async extNotification(_method: string, _params: JsonObject): Promise<void> {}
892
+ get signal(): AbortSignal {
893
+ return this.#connection.signal;
894
+ }
895
+ get closed(): Promise<void> {
896
+ return this.#connection.closed;
897
+ }
819
898
 
820
- this.#ensureLifetimeSubscription(record);
821
- record.promptTurn.unsubscribe = record.session.subscribe(event => {
822
- void this.#handlePromptEvent(record, event);
823
- });
899
+ #sessionEpoch(id: string): number {
900
+ return this.#sessionEpochs.get(id) ?? 0;
901
+ }
824
902
 
825
- this.#runPromptOrCommand(record, converted.text, converted.images).catch((error: unknown) => {
826
- this.#finishPrompt(record, undefined, error);
827
- });
903
+ #advanceSessionEpoch(id: string): void {
904
+ this.#sessionEpochs.set(id, this.#sessionEpoch(id) + 1);
905
+ }
828
906
 
829
- return await pendingPrompt.promise;
830
- });
907
+ #assertSessionEpoch(id: string, epoch: number): void {
908
+ if (this.#disposed || this.#tearingDown.has(id) || this.#sessionEpoch(id) !== epoch)
909
+ throw new AcpSdkAdapterError("connection_closed", `ACP session ${id} was closed while attaching.`);
831
910
  }
832
911
 
833
- async #queuePrompt(record: ManagedSessionRecord, run: () => Promise<PromptResponse>): Promise<PromptResponse> {
834
- const nextQueue = Promise.withResolvers<void>();
835
- const releaseQueue = nextQueue.resolve;
836
- const previousQueue = record.promptQueue;
837
- record.promptQueue = {
838
- promise: nextQueue.promise,
839
- release: releaseQueue,
840
- };
841
- await previousQueue.promise;
912
+ #beginTeardown(id: string): void {
913
+ this.#tearingDown.set(id, (this.#tearingDown.get(id) ?? 0) + 1);
914
+ }
915
+
916
+ #finishTeardown(id: string): void {
917
+ const remaining = (this.#tearingDown.get(id) ?? 1) - 1;
918
+ if (remaining > 0) this.#tearingDown.set(id, remaining);
919
+ else this.#tearingDown.delete(id);
920
+ }
921
+
922
+ #lifecycleIdempotencyKey(id: string, operation: "session.close" | "session.delete"): string {
923
+ return `acp:${operation}:${id}`;
924
+ }
925
+
926
+ #isAlreadyGone(error: unknown): boolean {
927
+ return (
928
+ typeof error === "object" &&
929
+ error !== null &&
930
+ "code" in error &&
931
+ ((error.code === "not_found" || error.code === "resource_gone") as boolean)
932
+ );
933
+ }
934
+
935
+ async #attachExisting(id: string, cwd: string): Promise<void> {
936
+ const epoch = this.#sessionEpoch(id);
937
+ const attached = this.#sessions.get(id);
938
+ if (attached) {
939
+ if (path.resolve(attached.cwd) !== path.resolve(cwd))
940
+ throw new AcpSdkAdapterError("conflict", `ACP session ${id} has conflicting cwd authority.`);
941
+ return;
942
+ }
943
+ const knownCwd = this.#knownSessionCwds.get(id);
944
+ if (knownCwd && path.resolve(knownCwd) !== path.resolve(cwd))
945
+ throw new AcpSdkAdapterError("conflict", `ACP session ${id} has conflicting cwd authority.`);
946
+ const resolving = this.#resolvingExisting.get(id);
947
+ if (resolving?.epoch === epoch) {
948
+ await resolving.task;
949
+ this.#assertSessionEpoch(id, epoch);
950
+ const resolved = this.#sessions.get(id);
951
+ if (!resolved) throw new AcpSdkAdapterError("unavailable", `ACP session ${id} did not attach.`);
952
+ if (path.resolve(resolved.cwd) !== path.resolve(cwd))
953
+ throw new AcpSdkAdapterError("conflict", `ACP session ${id} has conflicting cwd authority.`);
954
+ return;
955
+ }
956
+
957
+ const task = this.#resolveExistingAttachment(id, cwd, epoch);
958
+ const pending = { epoch, task };
959
+ this.#resolvingExisting.set(id, pending);
842
960
  try {
843
- return await run();
961
+ await task;
962
+ this.#assertSessionEpoch(id, epoch);
844
963
  } finally {
845
- releaseQueue();
846
- if (record.promptQueue.release === releaseQueue) {
847
- record.promptQueue.release = undefined;
848
- }
964
+ if (this.#resolvingExisting.get(id) === pending) this.#resolvingExisting.delete(id);
849
965
  }
850
966
  }
851
967
 
852
- async #runPromptOrCommand(record: ManagedSessionRecord, text: string, images: AgentImageContent[]): Promise<void> {
853
- // Namespaced skill commands cannot collide with ACP builtins; handle
854
- // them before builtin dispatch. Bare `/name` aliases are intentionally
855
- // not skill commands.
856
- if (text.startsWith("/skill:") && (await this.#tryRunSkillCommand(record, text))) {
968
+ async #resolveExistingAttachment(id: string, cwd: string, epoch: number): Promise<void> {
969
+ this.#assertSessionEpoch(id, epoch);
970
+ const indexed = await this.#scopedBrokerSession(id, cwd);
971
+ this.#assertSessionEpoch(id, epoch);
972
+ if (indexed?.live) {
973
+ const result = await this.#brokerEndpoint(id, indexed.endpointGeneration);
974
+ this.#assertSessionEpoch(id, epoch);
975
+ await this.#attach(id, cwd, endpoint(result), epoch);
857
976
  return;
858
977
  }
859
978
 
860
- const builtinResult = await executeAcpBuiltinSlashCommand(text, {
861
- session: record.session,
862
- sessionManager: record.session.sessionManager,
863
- settings: record.session.settings,
864
- cwd: record.session.sessionManager.getCwd(),
865
- output: output => this.#emitCommandOutput(record, output),
866
- refreshCommands: () => this.#emitAvailableCommandsUpdate(record),
867
- reloadPlugins: () => this.#reloadPluginState(record),
868
- notifyTitleChanged: async () => {
869
- await this.#connection.sessionUpdate({
870
- sessionId: record.session.sessionId,
871
- update: {
872
- sessionUpdate: "session_info_update",
873
- title: record.session.sessionName,
874
- updatedAt: new Date().toISOString(),
875
- },
876
- });
877
- },
878
- notifyConfigChanged: async () => {
879
- await this.#pushConfigOptionUpdate(record);
880
- },
881
- });
882
- if (builtinResult !== false) {
883
- if ("prompt" in builtinResult) {
884
- await record.session.prompt(builtinResult.prompt, { images });
885
- return;
886
- }
887
- const promptTurn = record.promptTurn;
888
- this.#finishPrompt(record, {
889
- stopReason: "end_turn",
890
- usage: this.#buildTurnUsage(
891
- promptTurn?.usageBaseline ??
892
- this.#cloneUsageStatistics(record.session.sessionManager.getUsageStatistics()),
893
- record.session.sessionManager.getUsageStatistics(),
894
- ),
895
- _meta: promptTurn ? { userMessageId: promptTurn.userMessageId } : undefined,
896
- });
897
- return;
898
- }
899
-
900
- if (await this.#tryRunSkillCommand(record, text, { directAliasMayCollide: false })) {
901
- return;
902
- }
903
-
904
- await record.session.prompt(text, { images });
905
- }
906
-
907
- async #tryRunSkillCommand(
908
- record: ManagedSessionRecord,
909
- text: string,
910
- options: { directAliasMayCollide?: boolean } = {},
911
- ): Promise<boolean> {
912
- if (!record.session.skillsSettings?.enableSkillCommands) {
913
- return false;
914
- }
915
- const skillsByCommandName = new Map(
916
- record.session.skills
917
- .filter(skill => skill.hide !== true)
918
- .flatMap(skill => getSkillSlashCommandNames(skill).map(commandName => [commandName, skill] as const)),
979
+ const saved = await this.#resolveSavedSession(id, cwd);
980
+ this.#assertSessionEpoch(id, epoch);
981
+ const result = await (await this.#brokerAdapter()).global(
982
+ "session.resume",
983
+ { cwd, sessionId: id, sessionPath: saved, target: { path: cwd } },
984
+ randomUUID(),
919
985
  );
920
- const invocations = parseSkillInvocations(text, skillsByCommandName);
921
- if (invocations.length > 0) {
922
- for (let index = 0; index < invocations.length; index += 1) {
923
- const invocation = invocations[index];
924
- if (!invocation) continue;
925
- const activationResult = await resolveSubskillActivationForSkillInvocation({
926
- cwd: record.session.sessionManager.getCwd(),
927
- sessionId: record.session.sessionId,
928
- skillName: invocation.skill.name,
929
- args: invocation.args,
930
- });
931
- const built = await buildSkillPromptMessage(invocation.skill, activationResult.cleanedArgs, {
932
- subskillActivation: activationResult.activation,
933
- subskillActivationSet: activationResult.activeSubskillsToPersist,
934
- cwd: record.session.sessionManager.getCwd(),
935
- sessionId: record.session.sessionId,
936
- });
937
- if (index === invocations.length - 1) {
938
- await record.session.promptCustomMessage({
939
- customType: SKILL_PROMPT_MESSAGE_TYPE,
940
- content: built.message,
941
- display: true,
942
- details: built.details,
943
- attribution: "user",
944
- });
945
- } else {
946
- await record.session.sendCustomMessage({
947
- customType: SKILL_PROMPT_MESSAGE_TYPE,
948
- content: built.message,
949
- display: true,
950
- details: built.details,
951
- attribution: "user",
952
- });
953
- }
954
- }
955
- return true;
956
- }
957
- const slashText = text.trimStart();
958
- if (!slashText.startsWith("/")) {
959
- return false;
960
- }
961
- const spaceIndex = slashText.indexOf(" ");
962
- const commandName = spaceIndex === -1 ? slashText.slice(1) : slashText.slice(1, spaceIndex);
963
- const args = spaceIndex === -1 ? "" : slashText.slice(spaceIndex + 1).trim();
964
- if (
965
- !isNamespacedSkillSlashCommandName(commandName) &&
966
- options.directAliasMayCollide !== true &&
967
- (await this.#directSkillAliasCollides(record, commandName))
968
- ) {
969
- return false;
970
- }
971
- const skill = record.session.skills.find(candidate => isSkillSlashCommandName(commandName, candidate));
972
- if (!skill || skill.hide === true) {
973
- return false;
974
- }
975
- const activationResult = await resolveSubskillActivationForSkillInvocation({
976
- cwd: record.session.sessionManager.getCwd(),
977
- sessionId: record.session.sessionId,
978
- skillName: skill.name,
979
- args,
980
- });
981
- const built = await buildSkillPromptMessage(skill, activationResult.cleanedArgs, {
982
- subskillActivation: activationResult.activation,
983
- subskillActivationSet: activationResult.activeSubskillsToPersist,
984
- cwd: record.session.sessionManager.getCwd(),
985
- sessionId: record.session.sessionId,
986
- });
987
- await record.session.promptCustomMessage({
988
- customType: SKILL_PROMPT_MESSAGE_TYPE,
989
- content: built.message,
990
- display: true,
991
- details: built.details,
992
- attribution: "user",
993
- });
994
- return true;
986
+ this.#assertSessionEpoch(id, epoch);
987
+ await this.#attach(id, cwd, endpoint(result), epoch);
995
988
  }
996
989
 
997
- async #directSkillAliasCollides(record: ManagedSessionRecord, commandName: string): Promise<boolean> {
998
- if (record.session.customCommands.some(command => command.command.name === commandName)) {
999
- return true;
990
+ async #scopedBrokerSession(id: string, cwd: string): Promise<BrokerSession | undefined> {
991
+ const response = object(await (await this.#brokerAdapter()).global("session.list", { cwd }));
992
+ const result = object(response?.result) ?? response;
993
+ const matches: BrokerSession[] = [];
994
+ for (const item of Array.isArray(result?.sessions) ? result.sessions : []) {
995
+ const session = object(item) as BrokerSession | undefined;
996
+ if (session?.sessionId !== id) continue;
997
+ if (typeof session.locator?.repo !== "string" || path.resolve(session.locator.repo) !== path.resolve(cwd))
998
+ throw new AcpSdkAdapterError("conflict", `Broker returned conflicting session scope for ${id}.`);
999
+ matches.push(session);
1000
1000
  }
1001
- const fileCommands = await loadSlashCommands({ cwd: record.session.sessionManager.getCwd() });
1002
- return fileCommands.some(command => command.name === commandName);
1001
+ if (matches.length > 1) throw new AcpSdkAdapterError("conflict", `Broker returned duplicate session id: ${id}`);
1002
+ return matches[0];
1003
1003
  }
1004
1004
 
1005
- async cancel(params: { sessionId: string }): Promise<void> {
1006
- const record = this.#getSessionRecord(params.sessionId);
1007
- const promptTurn = record.promptTurn;
1008
- if (!promptTurn || promptTurn.settled) {
1005
+ async #attach(id: string, cwd: string, discovered: Endpoint, epoch = this.#sessionEpoch(id)): Promise<void> {
1006
+ this.#assertSessionEpoch(id, epoch);
1007
+ const existing = this.#sessions.get(id);
1008
+ if (existing) {
1009
+ if (path.resolve(existing.cwd) !== path.resolve(cwd))
1010
+ throw new AcpSdkAdapterError("conflict", `ACP session ${id} has conflicting cwd authority.`);
1009
1011
  return;
1010
1012
  }
1011
- const cleanup = this.#beginCancelCleanup(record, promptTurn);
1012
- try {
1013
- await cleanup;
1014
- } catch (error: unknown) {
1015
- // A terminal delete owns the record; let the delete handle the failure
1016
- // rather than racing it with an independent close.
1017
- if (record.terminalState !== "active") {
1018
- return;
1019
- }
1020
- logger.warn("ACP cancel cleanup timed out; closing session", { sessionId: record.session.sessionId, error });
1021
- await this.#closeManagedSession(record.session.sessionId, record);
1022
- }
1023
- }
1024
-
1025
- /**
1026
- * Transition a still-running turn into cancellation: mark intent, drop the live-event
1027
- * subscription, start the bounded `abort()` race, and resolve the ACP prompt response
1028
- * with `stopReason: "cancelled"` so the client sees acceptance immediately. The
1029
- * returned promise is the cleanup barrier — it resolves when `abort()` completes and
1030
- * rejects when the timeout fires. Idempotent: a second call returns the same barrier.
1031
- */
1032
- #beginCancelCleanup(record: ManagedSessionRecord, promptTurn: PromptTurnState): Promise<void> {
1033
- if (promptTurn.cleanup) {
1034
- return promptTurn.cleanup;
1035
- }
1036
- promptTurn.cancelRequested = true;
1037
- promptTurn.unsubscribe?.();
1038
- void this.#emitCancelledPromptIdleUpdate(record);
1039
- const cleanup = this.#runCancelCleanup(record, promptTurn);
1040
- promptTurn.cleanup = cleanup;
1041
- this.#finishPrompt(record, {
1042
- stopReason: "cancelled",
1043
- usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
1044
- _meta: { userMessageId: promptTurn.userMessageId },
1045
- });
1046
- return cleanup;
1047
- }
1048
-
1049
- async #runCancelCleanup(record: ManagedSessionRecord, promptTurn: PromptTurnState): Promise<void> {
1050
- let timer: NodeJS.Timeout | undefined;
1051
- const timeout = new Promise<never>((_, reject) => {
1052
- timer = setTimeout(() => reject(new Error("ACP cancel cleanup timed out")), this.#cancelCleanupTimeoutMs);
1053
- });
1054
- try {
1055
- await Promise.race([record.session.abort(), timeout]);
1056
- } finally {
1057
- if (timer) clearTimeout(timer);
1058
- // Order matters: clear `cleanup` before evicting the slot so the slot-eviction
1059
- // branch matches what `#finishPrompt` saw if it ran first.
1060
- promptTurn.cleanup = undefined;
1061
- if (promptTurn.settled && record.promptTurn === promptTurn) {
1062
- record.promptTurn = undefined;
1063
- }
1064
- }
1065
- }
1066
-
1067
- async extMethod(method: string, params: { [key: string]: unknown }): Promise<{ [key: string]: unknown }> {
1068
- switch (method) {
1069
- case "_skc/sessions/listAll": {
1070
- const limit = typeof params.limit === "number" ? Math.max(1, Math.min(5000, params.limit as number)) : 1000;
1071
- const sessions = await SessionManager.listAll();
1072
- const sorted = sessions.sort((l, r) => r.modified.getTime() - l.modified.getTime()).slice(0, limit);
1073
- return {
1074
- sessions: sorted.map(s => this.#toSessionInfo(s)),
1075
- total: sessions.length,
1076
- };
1077
- }
1078
- case "_skc/projects/list": {
1079
- const sessions = await SessionManager.listAll();
1080
- const buckets = new Map<
1081
- string,
1082
- { cwd: string; sessionCount: number; lastActivityAt: number; lastTitle: string }
1083
- >();
1084
- for (const s of sessions) {
1085
- if (!s.cwd) continue;
1086
- const ts = s.modified.getTime();
1087
- const existing = buckets.get(s.cwd);
1088
- if (existing) {
1089
- existing.sessionCount += 1;
1090
- if (ts > existing.lastActivityAt) {
1091
- existing.lastActivityAt = ts;
1092
- existing.lastTitle = s.title ?? "";
1093
- }
1094
- } else {
1095
- buckets.set(s.cwd, {
1096
- cwd: s.cwd,
1097
- sessionCount: 1,
1098
- lastActivityAt: ts,
1099
- lastTitle: s.title ?? "",
1100
- });
1101
- }
1102
- }
1103
- const projects = Array.from(buckets.values()).sort((a, b) => b.lastActivityAt - a.lastActivityAt);
1104
- return { projects, totalSessions: sessions.length };
1105
- }
1106
- case "_skc/chats/byCwd": {
1107
- const cwd = typeof params.cwd === "string" ? (params.cwd as string) : undefined;
1108
- if (!cwd) throw new Error("cwd required");
1109
- const limit = typeof params.limit === "number" ? Math.max(1, Math.min(500, params.limit as number)) : 100;
1110
- const sessions = await SessionManager.list(cwd);
1111
- const sorted = sessions.sort((l, r) => r.modified.getTime() - l.modified.getTime()).slice(0, limit);
1112
- return { sessions: sorted.map(s => this.#toSessionInfo(s)) };
1113
- }
1114
- case "_skc/usage": {
1115
- const [firstRecord] = this.#sessions.values();
1116
- const target = firstRecord?.session ?? this.#initialSession;
1117
- if (!target) {
1118
- return { reports: [] };
1119
- }
1120
- const reports = await target.fetchUsageReports();
1121
- return { reports: reports ?? [] };
1122
- }
1123
- case "_skc/extensions": {
1124
- const cwd = typeof params.cwd === "string" ? (params.cwd as string) : undefined;
1125
- const sm = await Settings.init();
1126
- const disabledIds = (sm.get("disabledExtensions") as string[] | undefined) ?? [];
1127
- const extensions = await loadAllExtensions(cwd, disabledIds);
1128
- return { extensions: extensions as unknown as Array<{ [key: string]: unknown }> };
1129
- }
1130
- case "_skc/extensions/toggle": {
1131
- const providerId = params.providerId;
1132
- if (typeof providerId !== "string") throw new Error("providerId required");
1133
- if (params.enabled === false) {
1134
- disableProvider(providerId);
1135
- return { enabled: false };
1136
- }
1137
- enableProvider(providerId);
1138
- return { enabled: true };
1139
- }
1140
- default:
1141
- throw new Error(`Unknown ACP ext method: ${method}`);
1142
- }
1143
- }
1144
-
1145
- async extNotification(_method: string, _params: { [key: string]: unknown }): Promise<void> {}
1146
-
1147
- get signal(): AbortSignal {
1148
- return this.#connection.signal;
1149
- }
1150
-
1151
- get closed(): Promise<void> {
1152
- return this.#connection.closed;
1153
- }
1154
-
1155
- #registerConnectionCleanup(): void {
1156
- if (this.#cleanupRegistered) {
1013
+ const attaching = this.#attaching.get(id);
1014
+ if (attaching?.epoch === epoch) {
1015
+ await attaching.task;
1016
+ this.#assertSessionEpoch(id, epoch);
1017
+ const attached = this.#sessions.get(id);
1018
+ if (!attached) throw new AcpSdkAdapterError("unavailable", `ACP session ${id} did not attach.`);
1019
+ if (path.resolve(attached.cwd) !== path.resolve(cwd))
1020
+ throw new AcpSdkAdapterError("conflict", `ACP session ${id} has conflicting cwd authority.`);
1157
1021
  return;
1158
1022
  }
1159
- this.#cleanupRegistered = true;
1160
- this.#connection.signal.addEventListener(
1161
- "abort",
1162
- () => {
1163
- this.#shuttingDown = true;
1164
- // Mark shutdown under mutex (no actual mutex needed — single-threaded),
1165
- // reject new scope/inventory/lifecycle work, then await terminal work.
1166
- this.#shutdownPromise = (async () => {
1167
- // Join lifecycle preparation that has not registered yet. Each tracked
1168
- // operation rechecks shutdown and disposes its prepared session before settling.
1169
- await Promise.allSettled(Array.from(this.#lifecycleOperations));
1170
- const terminalPromises = Array.from(this.#sessions.values())
1171
- .map(r => r.terminalPromise)
1172
- .filter((p): p is Promise<DeleteSessionResponse> => p !== undefined);
1173
- await Promise.allSettled(terminalPromises);
1174
- await this.#disposeAllSessions();
1175
- // Clear connection-local authority.
1176
- this.#authoritySnapshot = undefined;
1177
- this.#canonicalCwdScope = undefined;
1178
- })();
1179
- },
1180
- { once: true },
1181
- );
1182
- }
1183
-
1184
- /**
1185
- * Connection-local shutdown promise. Resolves once all terminal work (dispose,
1186
- * active deletes, abort cleanup) has settled. `acp-mode.ts` awaits this after
1187
- * transport closure so `process.exit` never kills in-flight deletion/disposal.
1188
- */
1189
- get shutdownPromise(): Promise<void> {
1190
- return this.#shutdownPromise ?? Promise.resolve();
1191
- }
1192
1023
 
1193
- async #trackLifecycle<T>(operation: () => Promise<T>): Promise<T> {
1194
- this.#rejectIfShuttingDown();
1195
- const start = Promise.withResolvers<void>();
1196
- const promise = start.promise.then(operation);
1197
- this.#lifecycleOperations.add(promise);
1198
- start.resolve();
1024
+ const task = this.#attachEndpoint(id, cwd, discovered, epoch);
1025
+ const pending = { epoch, task };
1026
+ this.#attaching.set(id, pending);
1199
1027
  try {
1200
- return await promise;
1028
+ await task;
1029
+ this.#assertSessionEpoch(id, epoch);
1201
1030
  } finally {
1202
- this.#lifecycleOperations.delete(promise);
1031
+ if (this.#attaching.get(id) === pending) this.#attaching.delete(id);
1203
1032
  }
1204
1033
  }
1205
1034
 
1206
- async #createNewSessionRecord(cwd: string, mcpServers: McpServer[]): Promise<ManagedSessionRecord> {
1207
- const session = await this.#createSession(path.resolve(cwd));
1035
+ async #attachEndpoint(id: string, cwd: string, discovered: Endpoint, epoch: number): Promise<void> {
1036
+ let adapter: AcpSdkAdapter | undefined;
1208
1037
  try {
1209
- await session.sessionManager.ensureOnDisk();
1038
+ adapter = await AcpSdkAdapter.connect({
1039
+ url: discovered.url,
1040
+ token: discovered.token,
1041
+ connection: this.#reverseConnection(id),
1042
+ providers: this.#providers(),
1043
+ });
1044
+ this.#assertSessionEpoch(id, epoch);
1045
+ const record: SessionRecord = {
1046
+ cwd,
1047
+ adapter,
1048
+ unsubscribe: () => {},
1049
+ reconnectUnsubscribe: () => {},
1050
+ frameTail: Promise.resolve(),
1051
+ inboundSequence: 0,
1052
+ busy: false,
1053
+ };
1054
+ record.unsubscribe = adapter.onFrame(frame => this.#enqueueSdkFrame(id, adapter!, frame));
1055
+ record.reconnectUnsubscribe = adapter.onReconnectFailed(error =>
1056
+ this.#recoverSessionAfterTransportFailure(id, adapter!, error),
1057
+ );
1058
+ this.#sessions.set(id, record);
1059
+ this.#knownSessionCwds.set(id, cwd);
1060
+ await applyAcpPermissionMode(adapter, this.#clientCapabilities);
1061
+ this.#assertSessionEpoch(id, epoch);
1210
1062
  } catch (error) {
1211
- await this.#disposeStandaloneSession(session);
1063
+ if (adapter && this.#sessions.get(id)?.adapter === adapter) {
1064
+ try {
1065
+ await this.#teardownSession(id, "attachment failed", false);
1066
+ } finally {
1067
+ this.#knownSessionCwds.delete(id);
1068
+ }
1069
+ } else if (adapter) {
1070
+ try {
1071
+ await adapter.close();
1072
+ } catch {}
1073
+ }
1212
1074
  throw error;
1213
1075
  }
1214
- return await this.#registerPreparedSession(session, mcpServers);
1215
1076
  }
1216
1077
 
1217
- async #loadManagedSession(sessionId: string, cwd: string, mcpServers: McpServer[]): Promise<ManagedSessionRecord> {
1218
- const existing = this.#sessions.get(sessionId);
1219
- if (existing) {
1220
- this.#rejectIfTerminal(existing);
1221
- this.#assertMatchingCwd(existing.session, cwd);
1222
- await this.#configureMcpServers(existing, mcpServers);
1223
- return existing;
1224
- }
1225
-
1226
- const candidate = this.#resolveStrictLifecycleCandidate(sessionId, cwd);
1227
- return await this.#openStoredSession(candidate, cwd, mcpServers, sessionId);
1078
+ #recoverSessionAfterTransportFailure(id: string, adapter: AcpSdkAdapter, error: Error): void {
1079
+ const record = this.#sessions.get(id);
1080
+ if (!record || record.adapter !== adapter) return;
1081
+ const detail = error.message || "SDK transport reconnect failed.";
1082
+ const terminal = new AcpSdkAdapterError("connection_closed", `ACP session transport was lost: ${detail}`);
1083
+ void this.#recoverSessionAfterTransportFailureAsync(id, adapter, record.cwd, terminal);
1228
1084
  }
1229
1085
 
1230
- async #resumeManagedSession(sessionId: string, cwd: string, mcpServers: McpServer[]): Promise<ManagedSessionRecord> {
1231
- const existing = this.#sessions.get(sessionId);
1232
- if (existing) {
1233
- this.#rejectIfTerminal(existing);
1234
- this.#assertMatchingCwd(existing.session, cwd);
1235
- await this.#configureMcpServers(existing, mcpServers);
1236
- return existing;
1086
+ async #recoverSessionAfterTransportFailureAsync(
1087
+ id: string,
1088
+ adapter: AcpSdkAdapter,
1089
+ cwd: string,
1090
+ error: AcpSdkAdapterError,
1091
+ ): Promise<void> {
1092
+ await this.#failSession(id, adapter, error);
1093
+ if (this.#disposed || this.#knownSessionCwds.get(id) !== cwd) return;
1094
+ try {
1095
+ await this.#attachExisting(id, cwd);
1096
+ } catch {
1097
+ // The affected prompt was rejected and the stale adapter was removed. A later load/resume retries discovery.
1237
1098
  }
1099
+ }
1238
1100
 
1239
- const candidate = this.#resolveStrictLifecycleCandidate(sessionId, cwd);
1240
- return await this.#openStoredSession(candidate, cwd, mcpServers, sessionId);
1101
+ async #discardNewSession(id: string): Promise<void> {
1102
+ await this.#teardownSession(id, "discarded", true);
1103
+ this.#knownSessionCwds.delete(id);
1241
1104
  }
1242
1105
 
1243
1106
  /**
1244
- * Resolve a single exact-one scoped candidate via a fresh strict inventory.
1245
- * Rejects duplicates and inventory failures — never falls back to a forgiving
1246
- * first-match lookup. Used by load/resume so authoritative lifecycle paths bind
1247
- * exact identity before opening a stored session.
1107
+ * All local session disposal follows one path: remove ownership and reject a
1108
+ * waiting prompt before any awaited socket or broker work. A failed close is
1109
+ * terminally uncertain, not a reason to leave a usable-looking ACP record.
1248
1110
  */
1249
- #resolveStrictLifecycleCandidate(sessionId: string, scope: string): StrictInventoryCandidate {
1250
- const snapshot = this.#buildFreshAuthoritySnapshot(scope);
1251
- const entry = snapshot.entries.get(sessionId);
1252
- if (!entry) {
1253
- throw new Error(`ACP session not found: ${sessionId}`);
1254
- }
1255
- if (entry.kind !== "candidate") {
1256
- throw new Error(`ACP session ${sessionId} load failed: ${entry.reason}`);
1257
- }
1258
- return entry.candidate;
1259
- }
1260
-
1261
- async #forkManagedSession(params: ForkSessionRequest): Promise<ManagedSessionRecord> {
1262
- const sourcePath = await this.#resolveForkSourceSessionPath(params.sessionId);
1263
- const session = await this.#createSession(path.resolve(params.cwd));
1111
+ async #teardownSession(id: string, reason: string, closeRemote: boolean): Promise<void> {
1112
+ const record = this.#sessions.get(id);
1113
+ const ownershipBound = record !== undefined || this.#knownSessionCwds.has(id);
1114
+ this.#beginTeardown(id);
1264
1115
  try {
1265
- const success = await session.switchSession(sourcePath);
1266
- if (!success) {
1267
- throw new Error(`ACP session fork was cancelled: ${params.sessionId}`);
1268
- }
1269
- const forked = await session.fork();
1270
- if (!forked) {
1271
- throw new Error(`ACP session fork failed: ${params.sessionId}`);
1116
+ this.#advanceSessionEpoch(id);
1117
+ if (record) {
1118
+ this.#sessions.delete(id);
1119
+ record.unsubscribe();
1120
+ record.reconnectUnsubscribe();
1121
+ const waiter = record.activePrompt;
1122
+ record.activePrompt = undefined;
1123
+ waiter?.reject(new AcpSdkAdapterError("connection_closed", `ACP session was ${reason}.`));
1272
1124
  }
1273
- } catch (error) {
1274
- await this.#disposeStandaloneSession(session);
1275
- throw error;
1276
- }
1277
- return await this.#registerPreparedSession(session, params.mcpServers ?? []);
1278
- }
1279
1125
 
1280
- async #openStoredSession(
1281
- issued: StrictInventoryCandidate,
1282
- cwd: string,
1283
- mcpServers: McpServer[],
1284
- sessionId: string,
1285
- ): Promise<ManagedSessionRecord> {
1286
- const session = await this.#createSession(path.resolve(cwd));
1287
- try {
1288
- const current = this.#resolveStrictLifecycleCandidate(sessionId, cwd);
1289
- if (
1290
- current.path !== issued.path ||
1291
- current.cwd !== issued.cwd ||
1292
- current.identity.dev !== issued.identity.dev ||
1293
- current.identity.ino !== issued.identity.ino
1294
- ) {
1295
- throw new Error(`ACP session ${sessionId} changed while opening`);
1126
+ const failures: unknown[] = [];
1127
+ try {
1128
+ await record?.adapter.close();
1129
+ } catch (error) {
1130
+ failures.push(error);
1131
+ }
1132
+ if (closeRemote) {
1133
+ try {
1134
+ await (await this.#brokerAdapter()).global(
1135
+ "session.close",
1136
+ { sessionId: id },
1137
+ this.#lifecycleIdempotencyKey(id, "session.close"),
1138
+ );
1139
+ } catch (error) {
1140
+ if (!(ownershipBound && this.#isAlreadyGone(error))) failures.push(error);
1141
+ }
1296
1142
  }
1297
- const success = await session.switchSession(current.path);
1298
- if (!success) {
1299
- throw new Error(`ACP session load was cancelled: ${sessionId}`);
1143
+ if (failures.length > 0) {
1144
+ const detail = failures
1145
+ .map(failure => (failure instanceof Error ? failure.message : String(failure)))
1146
+ .join("; ");
1147
+ throw aggregateAcpFailure("terminal_uncertain", `ACP session cleanup is uncertain: ${detail}`, failures);
1300
1148
  }
1301
- } catch (error) {
1302
- await this.#disposeStandaloneSession(session);
1303
- throw error;
1149
+ } finally {
1150
+ this.#finishTeardown(id);
1304
1151
  }
1305
- return await this.#registerPreparedSession(session, mcpServers);
1306
1152
  }
1307
1153
 
1308
- async #registerPreparedSession(session: AgentSession, mcpServers: McpServer[]): Promise<ManagedSessionRecord> {
1309
- const record = this.#createManagedSessionRecord(session);
1310
- session.setClientBridge(createAcpClientBridge(this.#connection, session.sessionId, this.#clientCapabilities));
1311
- // The lifetime subscription normally follows the bootstrap race guard, but
1312
- // prompt() installs it eagerly once the client has demonstrated session ownership.
1154
+ async #failSession(id: string, adapter: AcpSdkAdapter, error: AcpSdkAdapterError): Promise<void> {
1155
+ const record = this.#sessions.get(id);
1156
+ if (!record || record.adapter !== adapter) return;
1157
+ this.#advanceSessionEpoch(id);
1158
+ this.#sessions.delete(id);
1159
+ record.unsubscribe();
1160
+ record.reconnectUnsubscribe();
1161
+ const waiter = record.activePrompt;
1162
+ record.activePrompt = undefined;
1163
+ waiter?.reject(error);
1313
1164
  try {
1314
- await this.#configureExtensions(record);
1315
- await this.#configureMcpServers(record, mcpServers);
1316
- if (this.#shuttingDown) {
1317
- await this.#disposeSessionRecord(record);
1318
- throw new Error("ACP session lifecycle is unavailable during shutdown");
1319
- }
1320
- this.#sessions.set(session.sessionId, record);
1321
- return record;
1322
- } catch (error) {
1323
- await this.#disposeSessionRecord(record);
1324
- throw error;
1325
- }
1165
+ await adapter.close();
1166
+ } catch {}
1326
1167
  }
1327
1168
 
1328
- #createManagedSessionRecord(session: AgentSession): ManagedSessionRecord {
1329
- return {
1330
- session,
1331
- mcpManager: undefined,
1332
- promptTurn: undefined,
1333
- promptQueue: { promise: Promise.resolve(), release: undefined },
1334
- liveMessageId: undefined,
1335
- liveMessageProgress: undefined,
1336
- toolArgsById: new Map(),
1337
- extensionsConfigured: false,
1338
- lifetimeUnsubscribe: undefined,
1339
- terminalState: "active",
1340
- terminalFailure: undefined,
1341
- terminalPromise: undefined,
1342
- };
1169
+ async #brokerAdapter(): Promise<AcpSdkAdapter> {
1170
+ return (await this.#brokerConnection()).adapter;
1343
1171
  }
1344
1172
 
1345
- #ensureLifetimeSubscription(record: ManagedSessionRecord): void {
1346
- if (record.lifetimeUnsubscribe) {
1347
- return;
1348
- }
1349
- record.lifetimeUnsubscribe = record.session.subscribe(event => {
1350
- void this.#handleLifetimeEvent(record, event);
1351
- });
1173
+ /** Machine-local endpoint lookup; never routed through ACP extension methods. */
1174
+ async #brokerEndpoint(sessionId: string, endpointGeneration: number | undefined): Promise<unknown> {
1175
+ const input = { sessionId, ...(endpointGeneration === undefined ? {} : { endpointGeneration }) };
1176
+ return await (await this.#brokerConnection()).client.global("session.get_endpoint", input);
1352
1177
  }
1353
1178
 
1354
- async #handleLifetimeEvent(record: ManagedSessionRecord, event: AgentSessionEvent): Promise<void> {
1355
- if (event.type === "auto_compaction_start" || event.type === "auto_compaction_end") {
1356
- // Prompt-bound compaction is normally forwarded by #handlePromptEvent. The lifetime
1357
- // subscription covers idle maintenance and the end event after prompt cancellation.
1358
- const promptTurn = record.promptTurn;
1359
- if (
1360
- isPromptTurnInFlight(promptTurn) &&
1361
- (!promptTurn.cancelRequested || event.type === "auto_compaction_start")
1362
- ) {
1363
- return;
1364
- }
1365
- for (const notification of mapAgentWireEventPayloadToAcpSessionUpdates(
1366
- toAgentWireEventPayload(event),
1367
- record.session.sessionId,
1368
- {
1369
- cwd: record.session.sessionManager.getCwd(),
1370
- compactionEndPhase: "idle",
1371
- },
1372
- )) {
1373
- await this.#connection.sessionUpdate(notification);
1374
- }
1375
- return;
1376
- }
1377
- if (event.type !== "thinking_level_changed") {
1378
- return;
1179
+ async #brokerConnection(): Promise<BrokerConnection> {
1180
+ if (!this.#broker) {
1181
+ let pending!: Promise<BrokerConnection>;
1182
+ pending = (async () => {
1183
+ await ensureBroker({ agentDir: this.#agentDir });
1184
+ const discovery = await readSdkBrokerDiscovery(this.#agentDir);
1185
+ if (!discovery) throw new AcpSdkAdapterError("unavailable", "SDK broker discovery is unavailable.");
1186
+ const client = await SdkClient.connect(discovery.url, discovery.token);
1187
+ const adapter = new AcpSdkAdapter({ url: discovery.url, token: discovery.token, client });
1188
+ adapter.onReconnectFailed(() => {
1189
+ if (this.#broker === pending) this.#broker = undefined;
1190
+ void adapter.close().catch(() => undefined);
1191
+ });
1192
+ await adapter.start();
1193
+ return { adapter, client };
1194
+ })();
1195
+ this.#broker = pending;
1379
1196
  }
1197
+ const pending = this.#broker;
1380
1198
  try {
1381
- await this.#pushConfigOptionUpdate(record);
1199
+ return await pending;
1382
1200
  } catch (error) {
1383
- logger.warn("Failed to push thinking-level config_option_update", {
1384
- sessionId: record.session.sessionId,
1385
- error,
1386
- });
1201
+ if (this.#broker === pending) this.#broker = undefined;
1202
+ throw error;
1387
1203
  }
1388
1204
  }
1389
1205
 
1390
- #getSessionRecord(sessionId: string): ManagedSessionRecord {
1391
- const record = this.#sessions.get(sessionId);
1392
- if (!record) {
1393
- throw new Error(`Unsupported ACP session: ${sessionId}`);
1394
- }
1395
- return record;
1206
+ #adapter(id: string): AcpSdkAdapter {
1207
+ const record = this.#sessions.get(id);
1208
+ if (!record) throw new AcpSdkAdapterError("not_found", `Unsupported ACP session: ${id}`);
1209
+ return record.adapter;
1396
1210
  }
1397
1211
 
1398
- #assertMatchingCwd(session: AgentSession, cwd: string): void {
1399
- const expected = path.resolve(cwd);
1400
- const actual = path.resolve(session.sessionManager.getCwd());
1401
- if (actual !== expected) {
1402
- throw new Error(`ACP session ${session.sessionId} is already loaded for ${actual}, not ${expected}`);
1403
- }
1212
+ async #resolveSavedSession(id: string, cwd: string): Promise<string> {
1213
+ const response = object(
1214
+ await (await this.#brokerAdapter()).global("session.list", { resolveSessionId: id, cwd }),
1215
+ );
1216
+ const result = object(response?.result) ?? response;
1217
+ const saved = object(result?.savedSession);
1218
+ if (saved?.id !== id || typeof saved.path !== "string")
1219
+ throw new AcpSdkAdapterError("not_found", `Saved ACP session does not exist: ${id}`);
1220
+ return saved.path;
1404
1221
  }
1405
1222
 
1406
- async #resolveForkSourceSessionPath(sessionId: string): Promise<string> {
1407
- const loaded = this.#sessions.get(sessionId);
1408
- if (loaded) {
1409
- if (loaded.terminalState !== "active") {
1410
- throw new Error(`ACP session fork is unavailable while it is in terminal state: ${sessionId}`);
1411
- }
1412
- if (isPromptTurnInFlight(loaded.promptTurn)) {
1413
- throw new Error(`ACP session fork is unavailable while a prompt is in progress: ${sessionId}`);
1414
- }
1415
- await loaded.session.sessionManager.flush();
1416
- const sessionPath = loaded.session.sessionManager.getSessionFile();
1417
- if (!sessionPath) {
1418
- throw new Error(`ACP session cannot be forked before it is persisted: ${sessionId}`);
1419
- }
1420
- return sessionPath;
1421
- }
1422
-
1423
- // Inactive fork source: exact-one scoped lookup only. Never use global
1424
- // listAll/first-match — that was the P1 data-loss path.
1425
- const scope = this.#canonicalCwdScope;
1426
- if (!scope) {
1427
- throw new Error(`ACP session not found (no scope established): ${sessionId}`);
1428
- }
1429
- const snapshot = this.#buildFreshAuthoritySnapshot(scope);
1430
- const entry = snapshot.entries.get(sessionId);
1431
- if (!entry) {
1432
- throw new Error(`ACP session not found: ${sessionId}`);
1433
- }
1434
- if (entry.kind !== "candidate") {
1435
- throw new Error(`ACP session fork failed: ${entry.reason}`);
1436
- }
1437
- return entry.candidate.path;
1223
+ #providers(): AcpProviderRegistration[] {
1224
+ return acpProviderRegistrations(this.#clientCapabilities);
1438
1225
  }
1439
1226
 
1440
- async #handlePromptEvent(record: ManagedSessionRecord, event: AgentSessionEvent): Promise<void> {
1441
- const promptTurn = record.promptTurn;
1442
- if (!promptTurn || promptTurn.settled || promptTurn.cancelRequested) {
1443
- return;
1444
- }
1445
-
1446
- if (event.type === "tool_execution_start" || event.type === "tool_execution_update") {
1447
- record.toolArgsById.set(event.toolCallId, event.args);
1448
- }
1449
-
1450
- this.#prepareLiveAssistantMessage(record, event);
1451
- for (const notification of mapAgentWireEventPayloadToAcpSessionUpdates(
1452
- toAgentWireEventPayload(event),
1453
- record.session.sessionId,
1454
- {
1455
- getMessageId: message => this.#getLiveMessageId(record, message),
1456
- getMessageProgress: message => this.#getLiveMessageProgress(record, message),
1457
- getToolArgs: toolCallId => record.toolArgsById.get(toolCallId),
1458
- cwd: record.session.sessionManager.getCwd(),
1459
- compactionEndPhase: "responding",
1227
+ #reverseConnection(sessionId: string): AcpReverseConnection {
1228
+ const methods: Record<string, string> = {
1229
+ "fs.readTextFile": "readTextFile",
1230
+ "fs.writeTextFile": "writeTextFile",
1231
+ "terminal.create": "createTerminal",
1232
+ "permission.request": "requestPermission",
1233
+ "ui.elicit": "unstable_createElicitation",
1234
+ };
1235
+ return {
1236
+ request: async (method: string, params: JsonObject): Promise<unknown> => {
1237
+ const name = methods[method] ?? method;
1238
+ const target = (this.#connection as unknown as Record<string, unknown>)[name];
1239
+ if (typeof target !== "function")
1240
+ throw new AcpSdkAdapterError("acp_reverse_unavailable", `ACP reverse method is unavailable: ${method}`);
1241
+ const request = method === "permission.request" ? { ...params, sessionId } : params;
1242
+ return await (target as (input: JsonObject) => Promise<unknown>)(request);
1460
1243
  },
1461
- )) {
1462
- await this.#connection.sessionUpdate(notification);
1463
- }
1464
- if (event.type === "tool_execution_end") {
1465
- record.toolArgsById.delete(event.toolCallId);
1466
- }
1467
- this.#clearLiveAssistantMessageAfterEvent(record, event);
1468
-
1469
- if (event.type === "agent_end") {
1470
- await this.#emitEndOfTurnUpdates(record);
1471
- await this.#waitForAcpPromptIdle(record);
1472
- this.#finishPrompt(record, {
1473
- stopReason: this.#resolveStopReason(event, promptTurn.cancelRequested),
1474
- usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
1475
- _meta: { userMessageId: promptTurn.userMessageId },
1476
- });
1477
- }
1244
+ };
1478
1245
  }
1479
1246
 
1480
- async #waitForAcpPromptIdle(record: ManagedSessionRecord): Promise<void> {
1481
- for (let pass = 0; pass < ACP_ASYNC_DELIVERY_DRAIN_MAX_PASSES; pass++) {
1482
- await record.session.waitForIdle();
1483
- const delivered = await record.session.drainAsyncJobDeliveriesForAcp({
1484
- timeoutMs: ACP_ASYNC_DELIVERY_DRAIN_TIMEOUT_MS,
1485
- });
1486
- if (!delivered) {
1487
- return;
1488
- }
1489
- }
1490
-
1491
- await record.session.waitForIdle();
1247
+ #observeSessionActivity(record: SessionRecord, frame: JsonObject): void {
1248
+ const event = receivedSdkEvent(frame)?.event;
1249
+ if (event?.type === "agent_start") record.busy = true;
1250
+ else if (event?.type === "agent_end") record.busy = false;
1492
1251
  }
1493
1252
 
1494
- #prepareLiveAssistantMessage(record: ManagedSessionRecord, event: AgentSessionEvent): void {
1495
- if (
1496
- (event.type === "message_start" || event.type === "message_update" || event.type === "message_end") &&
1497
- event.message.role === "assistant" &&
1498
- (event.type === "message_start" || !record.liveMessageId || !record.liveMessageProgress)
1499
- ) {
1500
- record.liveMessageId = crypto.randomUUID();
1501
- record.liveMessageProgress = { textEmitted: false, thoughtEmitted: false };
1502
- }
1253
+ #frameProcessingFailure(error: unknown): AcpSdkAdapterError {
1254
+ if (error instanceof AcpSdkAdapterError && error.code === "frame_processing_failed") return error;
1255
+ const detail = error instanceof Error ? error.message : String(error);
1256
+ return new AcpSdkAdapterError("frame_processing_failed", `ACP session frame processing failed: ${detail}`);
1503
1257
  }
1504
1258
 
1505
- #clearLiveAssistantMessageAfterEvent(record: ManagedSessionRecord, event: AgentSessionEvent): void {
1506
- if ((event.type === "message_end" && event.message.role === "assistant") || event.type === "agent_end") {
1507
- record.liveMessageId = undefined;
1508
- record.liveMessageProgress = undefined;
1509
- }
1259
+ #enqueueSdkFrame(id: string, adapter: AcpSdkAdapter, frame: JsonObject): void {
1260
+ const record = this.#sessions.get(id);
1261
+ if (!record || record.adapter !== adapter) return;
1262
+ // Sequence and busy state are captured at ingress, before queued work begins.
1263
+ // A frame received before acknowledgement stays before that prompt's boundary.
1264
+ this.#observeSessionActivity(record, frame);
1265
+ const sequence = ++record.inboundSequence;
1266
+ const task = record.frameTail.then(async () => await this.#handleSdkFrame(id, adapter, frame, sequence));
1267
+ record.frameTail = task.catch(
1268
+ async error => await this.#failSession(id, adapter, this.#frameProcessingFailure(error)),
1269
+ );
1510
1270
  }
1511
1271
 
1512
- #getLiveMessageId(record: ManagedSessionRecord, message: unknown): string | undefined {
1513
- if (typeof message !== "object" || message === null) {
1514
- return undefined;
1272
+ async #handleSdkFrame(id: string, adapter: AcpSdkAdapter, frame: JsonObject, sequence: number): Promise<void> {
1273
+ const record = this.#sessions.get(id);
1274
+ if (!record || record.adapter !== adapter) return;
1275
+ const received = receivedSdkEvent(frame);
1276
+ if (!received) return;
1277
+ const { event, wirePayload } = received;
1278
+ const correlation = correlationFrom(frame, event);
1279
+ const activePrompt = record.activePrompt;
1280
+ // Prompt ownership is updated before publishing client notifications, but a
1281
+ // terminal waiter is resolved only after publication succeeds so a failed
1282
+ // sessionUpdate rejects the prompt instead of reporting false completion.
1283
+ if (
1284
+ activePrompt?.acknowledged &&
1285
+ sequence > activePrompt.boundary &&
1286
+ !correlationsConflict(activePrompt.correlation, correlation)
1287
+ ) {
1288
+ if (isPromptActivity(String(event.type))) {
1289
+ activePrompt.activityObserved = true;
1290
+ } else if (event.type === "agent_end") {
1291
+ if (
1292
+ correlationsMatch(activePrompt.correlation, correlation) ||
1293
+ activePrompt.activityObserved ||
1294
+ activePrompt.steeringAtAcknowledgement
1295
+ )
1296
+ activePrompt.pendingTerminal = correlation;
1297
+ }
1515
1298
  }
1516
- record.liveMessageId ??= crypto.randomUUID();
1517
- return record.liveMessageId;
1518
- }
1519
-
1520
- #getLiveMessageProgress(
1521
- record: ManagedSessionRecord,
1522
- message: unknown,
1523
- ): { textEmitted: boolean; thoughtEmitted: boolean } | undefined {
1524
- if (typeof message !== "object" || message === null) {
1525
- return undefined;
1299
+ if (wirePayload) {
1300
+ for (const notification of mapAgentWireEventPayloadToAcpSessionUpdates(wirePayload as never, id, {
1301
+ cwd: record.cwd,
1302
+ getMessageProgress: message => {
1303
+ if (!activePrompt || !object(message)) return undefined;
1304
+ activePrompt.messageProgress ??= { textEmitted: false, thoughtEmitted: false };
1305
+ return activePrompt.messageProgress;
1306
+ },
1307
+ }))
1308
+ await this.#publishSessionUpdate(id, notification, adapter);
1526
1309
  }
1527
- record.liveMessageProgress ??= { textEmitted: false, thoughtEmitted: false };
1528
- return record.liveMessageProgress;
1310
+ if (event.type === "message_end" && object(event.message)?.role === "assistant" && activePrompt)
1311
+ activePrompt.messageProgress = undefined;
1312
+ if (event.type === "agent_end") await this.#emitEndOfTurnUpdates(id, adapter);
1313
+ if (activePrompt) this.#settlePrompt(record, activePrompt);
1529
1314
  }
1530
1315
 
1531
- #finishPrompt(record: ManagedSessionRecord, response?: PromptResponse, error?: unknown): void {
1532
- const promptTurn = record.promptTurn;
1533
- if (!promptTurn || promptTurn.settled) {
1534
- return;
1535
- }
1536
- promptTurn.settled = true;
1537
- promptTurn.unsubscribe?.();
1538
- // Keep the slot occupied until cancel cleanup finishes — `#runCancelCleanup`
1539
- // evicts the slot in its finally block once both flags say it's safe.
1540
- if (!promptTurn.cleanup && record.promptTurn === promptTurn) {
1541
- record.promptTurn = undefined;
1542
- }
1543
- if (error !== undefined) {
1544
- promptTurn.reject(error);
1316
+ #settlePrompt(record: SessionRecord, waiter: PromptWaiter): void {
1317
+ if (record.activePrompt !== waiter || !waiter.acknowledged || !waiter.pendingTerminal) return;
1318
+ if (correlationsConflict(waiter.correlation, waiter.pendingTerminal)) return;
1319
+ if (
1320
+ !correlationsMatch(waiter.correlation, waiter.pendingTerminal) &&
1321
+ !waiter.activityObserved &&
1322
+ !waiter.steeringAtAcknowledgement
1323
+ )
1545
1324
  return;
1546
- }
1547
- promptTurn.resolve(response ?? { stopReason: "end_turn" });
1548
- }
1549
-
1550
- #resolveStopReason(
1551
- event: Extract<AgentSessionEvent, { type: "agent_end" }>,
1552
- cancelRequested: boolean,
1553
- ): PromptResponse["stopReason"] {
1554
- if (cancelRequested) {
1555
- return "cancelled";
1556
- }
1557
- const lastAssistant = [...event.messages]
1558
- .reverse()
1559
- .find((message): message is AssistantMessage => message.role === "assistant");
1560
- const reason = lastAssistant?.stopReason;
1561
- switch (reason) {
1562
- case "aborted":
1563
- return "cancelled";
1564
- case "length":
1565
- return "max_tokens";
1566
- case "error": {
1567
- if (lastAssistant?.errorKind === "provider_safety_stop") {
1568
- return "refusal";
1569
- }
1570
- if (isLegacyProviderSafetyStopMessage(lastAssistant?.errorMessage ?? "")) {
1571
- return "refusal";
1572
- }
1573
- return "end_turn";
1574
- }
1575
- default:
1576
- return "end_turn";
1577
- }
1325
+ record.activePrompt = undefined;
1326
+ waiter.resolve({ stopReason: waiter.cancelRequested ? "cancelled" : "end_turn" });
1578
1327
  }
1579
1328
 
1580
- async #emitCommandOutput(record: ManagedSessionRecord, text: string): Promise<void> {
1581
- if (!text) {
1582
- return;
1329
+ async #emitEndOfTurnUpdates(id: string, adapter: AcpSdkAdapter): Promise<void> {
1330
+ let usage: JsonObject | undefined;
1331
+ try {
1332
+ const response = object(await adapter.query("context.get"));
1333
+ const result = object(response?.result) ?? response;
1334
+ usage = object(result?.usage);
1335
+ } catch {
1336
+ // Context usage is advisory ACP metadata; prompt completion remains authoritative.
1337
+ }
1338
+ if (typeof usage?.tokens === "number" && typeof usage.contextWindow === "number") {
1339
+ await this.#publishSessionUpdate(
1340
+ id,
1341
+ {
1342
+ sessionId: id,
1343
+ update: {
1344
+ sessionUpdate: "usage_update",
1345
+ size: usage.contextWindow,
1346
+ used: usage.tokens,
1347
+ },
1348
+ },
1349
+ adapter,
1350
+ );
1583
1351
  }
1584
- await this.#connection.sessionUpdate({
1585
- sessionId: record.session.sessionId,
1586
- update: {
1587
- sessionUpdate: "agent_message_chunk",
1588
- content: { type: "text", text },
1589
- messageId: crypto.randomUUID(),
1352
+ await this.#publishSessionUpdate(
1353
+ id,
1354
+ {
1355
+ sessionId: id,
1356
+ update: {
1357
+ sessionUpdate: "session_info_update",
1358
+ updatedAt: new Date().toISOString(),
1359
+ _meta: { skcPhase: "idle", running: false, skcRunning: false },
1360
+ },
1590
1361
  },
1591
- });
1592
- }
1593
-
1594
- #assertAbsoluteCwd(cwd: string): void {
1595
- if (!path.isAbsolute(cwd)) {
1596
- throw new Error(`ACP cwd must be absolute: ${cwd}`);
1597
- }
1598
- }
1599
-
1600
- /**
1601
- * Validate the cwd and reject cross-cwd calls against an already-committed scope,
1602
- * WITHOUT committing. Returns the canonical (resolved) cwd. Pair with
1603
- * {@link #commitCanonicalScope} after the lifecycle operation succeeds so a failed
1604
- * first new/load/resume/fork/list never pins the connection to a cwd it never served.
1605
- */
1606
- #stageCanonicalScope(cwd: string): string {
1607
- this.#assertAbsoluteCwd(cwd);
1608
- const canonical = path.resolve(cwd);
1609
- if (this.#canonicalCwdScope !== undefined && this.#canonicalCwdScope !== canonical) {
1610
- throw new Error(`ACP connection is scoped to ${this.#canonicalCwdScope}, not ${canonical}`);
1611
- }
1612
- return canonical;
1362
+ adapter,
1363
+ );
1613
1364
  }
1614
1365
 
1615
- /** Commit the canonical cwd scope on the first successful explicit-cwd call. */
1616
- #commitCanonicalScope(canonical: string): void {
1617
- if (this.#canonicalCwdScope === undefined) {
1618
- this.#canonicalCwdScope = canonical;
1366
+ async #publishSessionUpdate(
1367
+ id: string,
1368
+ notification: SessionNotification,
1369
+ expectedAdapter?: AcpSdkAdapter,
1370
+ ): Promise<void> {
1371
+ const record = this.#sessions.get(id);
1372
+ if (!record || (expectedAdapter && record.adapter !== expectedAdapter)) return;
1373
+ try {
1374
+ await this.#connection.sessionUpdate(notification);
1375
+ } catch (error) {
1376
+ const failure = this.#frameProcessingFailure(error);
1377
+ await this.#failSession(id, record.adapter, failure);
1378
+ throw failure;
1379
+ }
1380
+ }
1381
+
1382
+ async #sessionState(id: string): Promise<Pick<NewSessionResponse, "configOptions" | "modes">> {
1383
+ const record = this.#sessions.get(id);
1384
+ if (!record) throw new AcpSdkAdapterError("not_found", `Unsupported ACP session: ${id}`);
1385
+ const [config, models] = await Promise.all([
1386
+ record.adapter.query("config.list/get"),
1387
+ record.adapter.query("models.list/current"),
1388
+ ]);
1389
+ return acpSessionStateFromConfig(config, models);
1390
+ }
1391
+
1392
+ async #replaySession(id: string): Promise<void> {
1393
+ const adapter = this.#adapter(id);
1394
+ let cursor: string | undefined;
1395
+ let imageLimitationReported = false;
1396
+ for (let pageCount = 0; pageCount < MAX_ACP_REPLAY_PAGES; pageCount++) {
1397
+ const response = object(await adapter.query("transcript.list", {}, cursor));
1398
+ const result = object(response?.result) ?? response;
1399
+ const page = object(result?.page);
1400
+ for (const item of Array.isArray(page?.items) ? page.items : []) {
1401
+ const message = object(item);
1402
+ if (!message || (message.role !== "user" && message.role !== "assistant")) continue;
1403
+ const content = transcriptReplayContent(message);
1404
+ if (!imageLimitationReported) {
1405
+ imageLimitationReported = true;
1406
+ await this.#publishSessionUpdate(
1407
+ id,
1408
+ {
1409
+ sessionId: id,
1410
+ update: {
1411
+ sessionUpdate: "session_info_update",
1412
+ _meta: { skcTranscriptImageReplay: content.images },
1413
+ },
1414
+ },
1415
+ adapter,
1416
+ );
1417
+ }
1418
+ const messageId = typeof message.id === "string" ? message.id : undefined;
1419
+ for (const block of content.blocks) {
1420
+ await this.#publishSessionUpdate(
1421
+ id,
1422
+ {
1423
+ sessionId: id,
1424
+ update: {
1425
+ sessionUpdate: message.role === "user" ? "user_message_chunk" : "agent_message_chunk",
1426
+ content: block,
1427
+ ...(messageId ? { messageId } : {}),
1428
+ },
1429
+ },
1430
+ adapter,
1431
+ );
1432
+ }
1433
+ }
1434
+ cursor = typeof page?.continuationCursor === "string" ? page.continuationCursor : undefined;
1435
+ if (!cursor) return;
1619
1436
  }
1437
+ throw new AcpSdkAdapterError("resource_exhausted", "ACP transcript replay exceeded the page limit.");
1620
1438
  }
1621
1439
 
1622
- /** Bump the generation and discard the authority snapshot on lifecycle changes. */
1623
- #invalidateAuthority(): void {
1624
- this.#authorityGeneration += 1;
1625
- this.#authoritySnapshot = undefined;
1440
+ #scheduleBootstrap(id: string): void {
1441
+ setTimeout(() => {
1442
+ const record = this.#sessions.get(id);
1443
+ if (!record || this.#connection.signal.aborted) return;
1444
+ void this.#publishSessionUpdate(
1445
+ id,
1446
+ {
1447
+ sessionId: id,
1448
+ update: {
1449
+ sessionUpdate: "session_info_update",
1450
+ _meta: { skcPhase: "idle", running: false, skcRunning: false },
1451
+ },
1452
+ },
1453
+ record.adapter,
1454
+ ).catch(() => undefined);
1455
+ }, ACP_BOOTSTRAP_RACE_GUARD_MS);
1626
1456
  }
1627
1457
 
1628
- #rejectIfTerminal(record: ManagedSessionRecord): void {
1629
- if (record.terminalState !== "active") {
1630
- throw new Error(`ACP session ${record.session.sessionId} is in terminal state: ${record.terminalState}`);
1631
- }
1458
+ #cursor(cursor: string | null | undefined): number {
1459
+ if (!cursor) return 0;
1460
+ const value = Number.parseInt(cursor, 10);
1461
+ if (!Number.isSafeInteger(value) || value < 0) throw new Error(`Invalid ACP session cursor: ${cursor}`);
1462
+ return value;
1632
1463
  }
1633
1464
 
1634
- #rejectIfShuttingDown(): void {
1635
- if (this.#shuttingDown) {
1636
- throw new Error("ACP session lifecycle is unavailable during shutdown");
1637
- }
1465
+ #assertAbsoluteCwd(cwd: string): void {
1466
+ if (!path.isAbsolute(cwd)) throw new Error(`ACP cwd must be an absolute path: ${cwd}`);
1638
1467
  }
1639
1468
 
1640
- /**
1641
- * Build (or reuse) the complete authorization snapshot from a strict raw scoped
1642
- * inventory. The cached pagination snapshot is reused for display/list paging.
1643
- * Any inventory failure throws a sanitized error — it grants zero authority.
1644
- * The snapshot binds every discovered id to its exact-identity candidate or a
1645
- * conflict tombstone BEFORE pagination, so duplicates beyond page 1 are known
1646
- * before the first page response.
1647
- */
1648
- #buildAuthoritySnapshot(scope: string): AcpAuthoritySnapshot {
1649
- if (this.#authoritySnapshot?.scope === scope) {
1650
- return this.#authoritySnapshot;
1651
- }
1652
- return this.#rebuildAuthoritySnapshot(scope, true);
1469
+ #assertNoMcpServers(params: { mcpServers?: unknown[] }): void {
1470
+ if (params.mcpServers && params.mcpServers.length > 0)
1471
+ throw new AcpSdkAdapterError("unsupported", "MCP servers are unsupported under SDK-backed ACP.");
1653
1472
  }
1654
1473
 
1655
- /**
1656
- * Build a FRESH authorization snapshot, ignoring the cached pagination snapshot.
1657
- * Destructive authority (delete, inactive fork) must read storage at mutation
1658
- * time so an external create/remove/replace/duplicate between list and delete
1659
- * cannot yield a stale no-op or wrong mutation. The result is NOT cached: it
1660
- * must not poison the generation-aware pagination cursor contract.
1661
- */
1662
- #buildFreshAuthoritySnapshot(scope: string): AcpAuthoritySnapshot {
1663
- return this.#rebuildAuthoritySnapshot(scope, false);
1474
+ #beginDispose(): void {
1475
+ if (this.#disposePromise) return;
1476
+ this.#disposePromise = this.#dispose();
1477
+ // AbortSignal listeners cannot return a promise to their caller. Retain the
1478
+ // aggregate cleanup result while attaching a rejection handler so disposal
1479
+ // never creates a detached unhandled rejection.
1480
+ void this.#disposePromise.catch(() => undefined);
1664
1481
  }
1665
1482
 
1666
- #rebuildAuthoritySnapshot(scope: string, cache: boolean): AcpAuthoritySnapshot {
1667
- const inventory = SessionManager.inventorySessionsStrict(scope);
1668
- if (inventory.kind === "failure") {
1669
- const messages = inventory.failures.map(f => `${f.kind}: ${f.message}`);
1670
- throw new Error(`ACP scoped session inventory is incomplete: ${messages.join("; ")}`);
1671
- }
1672
- const entries = new Map<string, AcpAuthorityEntry>();
1673
- const orderedIds: string[] = [];
1674
- for (const candidate of inventory.candidates) {
1675
- const existing = entries.get(candidate.id);
1676
- if (existing) {
1677
- entries.set(candidate.id, { kind: "conflict", reason: "Duplicate session id in scoped inventory" });
1678
- } else {
1679
- entries.set(candidate.id, { kind: "candidate", candidate });
1680
- orderedIds.push(candidate.id);
1483
+ async #dispose(): Promise<void> {
1484
+ if (this.#disposed) return;
1485
+ this.#disposed = true;
1486
+ const failures: unknown[] = [];
1487
+ for (const id of [...this.#sessions.keys()]) {
1488
+ try {
1489
+ await this.#teardownSession(id, "connection closed", false);
1490
+ } catch (error) {
1491
+ failures.push(error);
1681
1492
  }
1682
1493
  }
1683
- const snapshot: AcpAuthoritySnapshot = {
1684
- scope,
1685
- entries,
1686
- orderedIds,
1687
- generation: this.#authorityGeneration,
1688
- };
1689
- if (cache) {
1690
- this.#authoritySnapshot = snapshot;
1691
- }
1692
- return snapshot;
1693
- }
1694
-
1695
- /**
1696
- * Delete an active (loaded) session via the per-record terminal state machine.
1697
- * Reserves `deleting` before the first await; concurrent deletes join the
1698
- * terminal promise. On failure the state transitions to `pre_dispatch_retryable`
1699
- * (close_failed_retryable), `cleanup_pending` (verified-delete partial), or
1700
- * `terminal_failure` (close_unknown / quiescence / unexpected) — all non-`active`
1701
- * states reject prompt/config/close/load/resume/fork. Only `pre_dispatch_retryable`
1702
- * and `cleanup_pending` permit a retry delete; `terminal_failure` never reopens.
1703
- */
1704
- async #deleteActiveSession(sessionId: string, record: ManagedSessionRecord): Promise<DeleteSessionResponse> {
1705
- // Concurrent delete joins the in-progress terminal promise.
1706
- if (record.terminalPromise) {
1707
- return await record.terminalPromise;
1708
- }
1709
- // terminal_failure: never reopen — share the settled error.
1710
- if (record.terminalState === "terminal_failure") {
1711
- throw record.terminalFailure ?? new Error(`ACP session ${sessionId} has a settled terminal failure`);
1494
+ this.#attaching.clear();
1495
+ this.#resolvingExisting.clear();
1496
+ this.#knownSessionCwds.clear();
1497
+ this.#tearingDown.clear();
1498
+ if (this.#broker) {
1499
+ const broker = this.#broker;
1500
+ this.#broker = undefined;
1501
+ try {
1502
+ await (await broker).adapter.close();
1503
+ } catch (error) {
1504
+ failures.push(error);
1505
+ }
1712
1506
  }
1713
- // Only active / pre_dispatch_retryable / cleanup_pending may start a delete.
1714
- if (record.terminalState === "deleting") {
1715
- throw new Error(`ACP session ${sessionId} is being deleted`);
1507
+ if (failures.length > 0) {
1508
+ const detail = failures
1509
+ .map(failure => (failure instanceof Error ? failure.message : String(failure)))
1510
+ .join("; ");
1511
+ throw aggregateAcpFailure("terminal_uncertain", `ACP connection cleanup is uncertain: ${detail}`, failures);
1716
1512
  }
1717
- record.terminalState = "deleting";
1718
- const scope = this.#canonicalCwdScope!;
1719
- const terminalPromise = (async (): Promise<DeleteSessionResponse> => {
1720
- try {
1721
- // Quiesce — failure is terminal (never swallow).
1722
- try {
1723
- await this.#quiesceForDelete(sessionId, record);
1724
- } catch (quiesceError) {
1725
- record.terminalState = "terminal_failure";
1726
- record.terminalFailure = quiesceError instanceof Error ? quiesceError : new Error(String(quiesceError));
1727
- throw quiesceError;
1728
- }
1729
- const manager = record.session.sessionManager;
1730
- // Strict close — outcome determines retryability.
1731
- const closeOutcome = await record.session.closeWriterStrict();
1732
- if (closeOutcome.kind !== "closed") {
1733
- if (closeOutcome.kind === "close_failed_retryable") {
1734
- record.terminalState = "pre_dispatch_retryable";
1735
- } else {
1736
- record.terminalState = "terminal_failure";
1737
- record.terminalFailure = closeOutcome.error;
1738
- }
1739
- const reason =
1740
- closeOutcome.kind === "close_unknown"
1741
- ? "writer close outcome is unknown"
1742
- : "writer close failed before dispatch";
1743
- throw new Error(`ACP session ${sessionId} cannot be deleted: ${reason}`);
1744
- }
1745
- // Recheck: the record must still own this exact session.
1746
- if (this.#sessions.get(sessionId) !== record || record.terminalState !== "deleting") {
1747
- record.terminalState = "terminal_failure";
1748
- record.terminalFailure = new Error(`ACP session ${sessionId} state changed during deletion`);
1749
- throw new Error(`ACP session ${sessionId} state changed during deletion`);
1750
- }
1751
- const sessionFile = manager.getSessionFile();
1752
- const sessionDir = manager.getSessionDir();
1753
- if (!sessionFile) {
1754
- record.terminalState = "terminal_failure";
1755
- record.terminalFailure = new Error(`ACP session ${sessionId} is not persisted and cannot be deleted`);
1756
- throw new Error(`ACP session ${sessionId} is not persisted and cannot be deleted`);
1757
- }
1758
- const snapshot = this.#buildFreshAuthoritySnapshot(scope);
1759
- const entry = snapshot.entries.get(sessionId);
1760
- if (entry?.kind !== "candidate") {
1761
- record.terminalState = "terminal_failure";
1762
- record.terminalFailure = new Error(`ACP session ${sessionId} is not an exact-one scoped candidate`);
1763
- throw new Error(`ACP session ${sessionId} is not an exact-one scoped candidate`);
1764
- }
1765
- if (entry.candidate.path !== sessionFile) {
1766
- record.terminalState = "terminal_failure";
1767
- record.terminalFailure = new Error(`ACP session ${sessionId} transcript path changed during deletion`);
1768
- throw new Error(`ACP session ${sessionId} transcript path changed during deletion`);
1769
- }
1770
- this.#assertPendingTranscriptIdentity(sessionId, entry.candidate.identity);
1771
- const sessionsRoot = path.dirname(sessionDir);
1772
- const target: VerifiedSessionDeleteTarget = {
1773
- sessionsRoot,
1774
- transcriptPath: sessionFile,
1775
- sessionId,
1776
- cwd: manager.getCwd(),
1777
- transcriptIdentity: {
1778
- dev: entry.candidate.identity.dev,
1779
- ino: entry.candidate.identity.ino,
1780
- },
1781
- ...this.#pendingArtifactsIdentity(sessionId),
1782
- };
1783
- const outcome = await manager.deleteSessionVerified(target);
1784
- if (outcome.kind !== "deleted") {
1785
- record.terminalState = "cleanup_pending";
1786
- this.#recordPendingDeleteEvidence(sessionId, outcome);
1787
- throw new Error(
1788
- `ACP session ${sessionId} delete is incomplete: ${outcome.phase} cleanup pending (${outcome.error.message})`,
1789
- );
1790
- }
1791
- // Fully deleted: clear retry evidence, remove from active map, dispose.
1792
- record.terminalState = "deleted";
1793
- this.#pendingDeleteEvidence.delete(sessionId);
1794
- this.#sessions.delete(sessionId);
1795
- this.#invalidateAuthority();
1796
- await this.#disposeSessionRecord(record);
1797
- return {};
1798
- } catch (error) {
1799
- // terminalState was set at the failure point above. For any
1800
- // unexpected error that left the state as `deleting`, treat it
1801
- // as terminal_failure so the record never silently reopens.
1802
- if (record.terminalState === "deleting") {
1803
- record.terminalState = "terminal_failure";
1804
- record.terminalFailure = error instanceof Error ? error : new Error(String(error));
1805
- }
1806
- record.terminalPromise = undefined;
1807
- this.#invalidateAuthority();
1808
- throw error;
1809
- }
1810
- })();
1811
- record.terminalPromise = terminalPromise;
1812
- return await terminalPromise;
1813
- }
1814
-
1815
- /**
1816
- * Delete an inactive (not currently loaded) session. The ID must have been
1817
- * issued by the last complete scoped authority snapshot; an absent/unissued ID
1818
- * returns lookup-free `{}` with NO inventory scan. Only after issuance may a
1819
- * fresh strict inventory revalidate the same exact candidate before mutation.
1820
- * A repeat-unknown (issued but gone from the fresh inventory) is also lookup-free `{}`.
1821
- */
1822
- async #deleteInactiveSession(sessionId: string): Promise<DeleteSessionResponse> {
1823
- const scope = this.#canonicalCwdScope!;
1824
- // Gate: only IDs issued by the last complete scoped authority snapshot
1825
- // may trigger a fresh inventory scan. Absent/unissued → lookup-free {}.
1826
- const issued = this.#authoritySnapshot?.entries.get(sessionId);
1827
- if (!issued) {
1828
- return {};
1829
- }
1830
- if (issued.kind === "conflict") {
1831
- throw new Error(`ACP session delete failed: ${issued.reason}`);
1832
- }
1833
- // Fresh strict inventory to revalidate the same exact candidate.
1834
- const snapshot = this.#buildFreshAuthoritySnapshot(scope);
1835
- const entry = snapshot.entries.get(sessionId);
1836
- if (!entry) {
1837
- // Repeat unknown: issued but no longer present in the fresh inventory.
1838
- return {};
1839
- }
1840
- if (entry.kind === "conflict") {
1841
- throw new Error(`ACP session delete failed: ${entry.reason}`);
1842
- }
1843
- const candidate = entry.candidate;
1844
- if (
1845
- candidate.path !== issued.candidate.path ||
1846
- candidate.cwd !== issued.candidate.cwd ||
1847
- candidate.identity.dev !== issued.candidate.identity.dev ||
1848
- candidate.identity.ino !== issued.candidate.identity.ino
1849
- ) {
1850
- throw new Error(`ACP session ${sessionId} changed since authority was issued`);
1851
- }
1852
- this.#assertPendingTranscriptIdentity(sessionId, candidate.identity);
1853
- const sessionsRoot = path.dirname(candidate.path);
1854
- const target: VerifiedSessionDeleteTarget = {
1855
- sessionsRoot,
1856
- transcriptPath: candidate.path,
1857
- sessionId,
1858
- cwd: candidate.cwd,
1859
- transcriptIdentity: { dev: candidate.identity.dev, ino: candidate.identity.ino },
1860
- ...this.#pendingArtifactsIdentity(sessionId),
1861
- };
1862
- const storage = new FileSessionStorage();
1863
- const outcome = await storage.deleteSessionVerified(target);
1864
- if (outcome.kind !== "deleted") {
1865
- this.#recordPendingDeleteEvidence(sessionId, outcome);
1866
- throw new Error(
1867
- `ACP session ${sessionId} delete is incomplete: ${outcome.phase} cleanup pending (${outcome.error.message})`,
1868
- );
1869
- }
1870
- this.#pendingDeleteEvidence.delete(sessionId);
1871
- this.#invalidateAuthority();
1872
- return {};
1873
- }
1874
-
1875
- #convertPromptBlocks(blocks: PromptRequest["prompt"]): { text: string; images: AgentImageContent[] } {
1876
- const textParts: string[] = [];
1877
- const images: AgentImageContent[] = [];
1878
- for (const block of blocks) {
1879
- switch (block.type) {
1880
- case "text":
1881
- textParts.push(block.text);
1882
- break;
1883
- case "image":
1884
- images.push({ type: "image", data: block.data, mimeType: block.mimeType });
1885
- break;
1886
- case "resource":
1887
- if ("text" in block.resource) {
1888
- textParts.push(block.resource.text);
1889
- } else if (typeof block.resource.mimeType === "string" && block.resource.mimeType.startsWith("image/")) {
1890
- // `embeddedContext: true` covers both text and blob resources, but
1891
- // blobs aren't directly consumable by the LLM. Route image blobs
1892
- // to the images array so the user's intent survives; everything
1893
- // else falls back to the URI placeholder below.
1894
- images.push({ type: "image", data: block.resource.blob, mimeType: block.resource.mimeType });
1895
- } else {
1896
- textParts.push(`[embedded resource: ${block.resource.uri}]`);
1897
- }
1898
- break;
1899
- case "resource_link":
1900
- textParts.push(block.title ?? block.name ?? block.uri);
1901
- break;
1902
- case "audio":
1903
- textParts.push("[audio omitted]");
1904
- break;
1905
- }
1906
- }
1907
- return {
1908
- text: textParts.join("\n\n").trim(),
1909
- images,
1910
- };
1911
- }
1912
-
1913
- async #pushConfigOptionUpdate(record: ManagedSessionRecord): Promise<void> {
1914
- await this.#connection.sessionUpdate({
1915
- sessionId: record.session.sessionId,
1916
- update: {
1917
- sessionUpdate: "config_option_update",
1918
- configOptions: this.#buildConfigOptions(record.session),
1919
- },
1920
- });
1921
- }
1922
-
1923
- #buildConfigOptions(session: AgentSession): SessionConfigOption[] {
1924
- const currentModeId = this.#getCurrentModeId(session);
1925
- const modeOptions = this.#getAvailableModes(session).map(mode => ({
1926
- value: mode.id,
1927
- name: mode.name,
1928
- description: mode.description,
1929
- }));
1930
- const configOptions: SessionConfigOption[] = [
1931
- {
1932
- id: MODE_CONFIG_ID,
1933
- name: "Mode",
1934
- category: "mode",
1935
- type: "select",
1936
- currentValue: currentModeId,
1937
- options: modeOptions,
1938
- },
1939
- ];
1940
-
1941
- const models = session.getAvailableModels();
1942
- const currentModel = session.model;
1943
- if (models.length > 0) {
1944
- configOptions.push({
1945
- id: MODEL_CONFIG_ID,
1946
- name: "Model",
1947
- category: "model",
1948
- type: "select",
1949
- currentValue: currentModel ? this.#toModelId(currentModel) : this.#toModelId(models[0]),
1950
- options: models.map(model => ({
1951
- value: this.#toModelId(model),
1952
- name: model.name,
1953
- description: `${model.provider}/${model.id}`,
1954
- })),
1955
- });
1956
- }
1957
-
1958
- configOptions.push({
1959
- id: THINKING_CONFIG_ID,
1960
- name: "Thinking",
1961
- category: "thought_level",
1962
- type: "select",
1963
- currentValue: this.#toThinkingConfigValue(session.thinkingLevel),
1964
- options: this.#buildThinkingOptions(session),
1965
- });
1966
- return configOptions;
1967
- }
1968
-
1969
- #buildThinkingOptions(session: AgentSession): Array<{ value: string; name: string; description?: string }> {
1970
- return [
1971
- { value: THINKING_OFF, name: "Off" },
1972
- ...session.getAvailableThinkingLevels().map(level => ({
1973
- value: level,
1974
- name: level,
1975
- })),
1976
- ];
1977
- }
1978
-
1979
- #toThinkingConfigValue(value: string | undefined): string {
1980
- return value && value !== "inherit" ? value : THINKING_OFF;
1981
- }
1982
-
1983
- async #setModelById(session: AgentSession, modelId: string): Promise<void> {
1984
- const model = session.getAvailableModels().find(candidate => this.#toModelId(candidate) === modelId);
1985
- if (!model) {
1986
- throw new Error(`Unknown ACP model: ${modelId}`);
1987
- }
1988
- await session.setModel(model);
1989
- }
1990
-
1991
- #setThinkingLevelById(session: AgentSession, value: string): void {
1992
- const thinkingLevel = parseThinkingLevel(value);
1993
- if (!thinkingLevel) {
1994
- throw new Error(`Unknown ACP thinking level: ${value}`);
1995
- }
1996
- session.setThinkingLevel(thinkingLevel);
1997
- }
1998
-
1999
- #toModelId(model: Model): string {
2000
- return `${model.provider}/${model.id}`;
2001
- }
2002
-
2003
- #getAvailableModes(session: AgentSession): Array<{ id: string; name: string; description: string }> {
2004
- const modes = [{ id: ACP_DEFAULT_MODE_ID, name: "Default", description: "Standard ACP headless mode" }];
2005
- if (session.settings.get("plan.enabled")) {
2006
- modes.push({
2007
- id: ACP_PLAN_MODE_ID,
2008
- name: "Plan",
2009
- description: "Read-only planning mode that drafts a plan to a markdown file before any code changes",
2010
- });
2011
- }
2012
- void session;
2013
- return modes;
2014
- }
2015
-
2016
- #getCurrentModeId(session: AgentSession): string {
2017
- return session.getPlanModeState()?.enabled ? ACP_PLAN_MODE_ID : ACP_DEFAULT_MODE_ID;
2018
- }
2019
-
2020
- #applyModeChange(session: AgentSession, modeId: string): void {
2021
- const availableModes = this.#getAvailableModes(session);
2022
- if (!availableModes.some(mode => mode.id === modeId)) {
2023
- throw new Error(`Unsupported ACP mode: ${modeId}`);
2024
- }
2025
- if (modeId === ACP_PLAN_MODE_ID) {
2026
- const previous = session.getPlanModeState();
2027
- session.setPlanModeState({
2028
- enabled: true,
2029
- planFilePath: previous?.planFilePath ?? DEFAULT_PLAN_FILE_URL,
2030
- workflow: previous?.workflow ?? "parallel",
2031
- reentry: previous !== undefined,
2032
- });
2033
- } else {
2034
- session.setPlanModeState(undefined);
2035
- }
2036
- }
2037
-
2038
- #buildModeState(session: AgentSession): SessionModeState {
2039
- return {
2040
- availableModes: this.#getAvailableModes(session),
2041
- currentModeId: this.#getCurrentModeId(session),
2042
- };
2043
- }
2044
-
2045
- #buildCurrentModeUpdate(session: AgentSession): SessionUpdate {
2046
- return {
2047
- sessionUpdate: "current_mode_update",
2048
- currentModeId: this.#getCurrentModeId(session),
2049
- };
2050
- }
2051
-
2052
- async #buildAvailableCommands(session: AgentSession): Promise<AvailableCommand[]> {
2053
- const commands: AvailableCommand[] = [];
2054
- const seenNames = new Set<string>();
2055
- const appendCommand = (command: AvailableCommand): void => {
2056
- if (seenNames.has(command.name)) {
2057
- return;
2058
- }
2059
- seenNames.add(command.name);
2060
- commands.push(command);
2061
- };
2062
-
2063
- // Advertise builtins first, then custom/user commands, then file-based
2064
- // slash commands, then namespaced `/skill:<name>` skills.
2065
- for (const command of ACP_BUILTIN_SLASH_COMMANDS) {
2066
- appendCommand(command);
2067
- }
2068
-
2069
- for (const command of session.customCommands) {
2070
- appendCommand({
2071
- name: command.command.name,
2072
- description: command.command.description,
2073
- input: { hint: "arguments" },
2074
- });
2075
- }
2076
-
2077
- for (const command of await loadSlashCommands({ cwd: session.sessionManager.getCwd() })) {
2078
- appendCommand({
2079
- name: command.name,
2080
- description: command.description,
2081
- });
2082
- }
2083
-
2084
- if (session.skillsSettings?.enableSkillCommands) {
2085
- for (const skill of session.skills) {
2086
- if (skill.hide === true) continue;
2087
- for (const name of getSkillSlashCommandNames(skill)) {
2088
- appendCommand({
2089
- name,
2090
- description: skill.description || `Run ${skill.name} skill`,
2091
- input: { hint: "arguments" },
2092
- });
2093
- }
2094
- }
2095
- }
2096
-
2097
- return commands;
2098
- }
2099
-
2100
- #toSessionInfo(session: StoredSessionInfo): SessionInfo {
2101
- return {
2102
- sessionId: session.id,
2103
- cwd: session.cwd,
2104
- title: session.title,
2105
- updatedAt: session.modified.toISOString(),
2106
- _meta: {
2107
- messageCount: session.messageCount,
2108
- size: session.size,
2109
- },
2110
- };
2111
- }
2112
-
2113
- #scheduleBootstrapUpdates(sessionId: string): void {
2114
- // Defer first notifications until the response has reached the client.
2115
- // Zed's agent-client-protocol reader dispatches responses and
2116
- // notifications to different async tasks; sending the first
2117
- // `available_commands_update` from `setTimeout(0)` reliably loses the
2118
- // race against the response handler and Zed logs `Received session
2119
- // notification for unknown session` then drops the update — leaving
2120
- // the slash-command palette empty (#1015 follow-up; see
2121
- // zed-industries/zed#55965 for the same race biting other ACP agents).
2122
- // `ACP_BOOTSTRAP_RACE_GUARD_MS` is invisible to the operator and large
2123
- // enough that the response future has scheduled before our timer fires
2124
- // on stdio-only transports.
2125
- //
2126
- // The session-lifetime subscription normally shares this guard so extension
2127
- // work cannot notify an unknown session id. prompt() may install it earlier:
2128
- // receipt of a prompt proves the client already owns the returned session id,
2129
- // and closes the cancellation window before this timer fires.
2130
- setTimeout(() => {
2131
- if (this.#connection.signal.aborted) {
2132
- return;
2133
- }
2134
- const record = this.#sessions.get(sessionId);
2135
- if (!record) {
2136
- return;
2137
- }
2138
- this.#ensureLifetimeSubscription(record);
2139
- void this.#emitBootstrapUpdates(sessionId, record);
2140
- }, ACP_BOOTSTRAP_RACE_GUARD_MS);
2141
- }
2142
-
2143
- async #emitBootstrapUpdates(sessionId: string, record: ManagedSessionRecord): Promise<void> {
2144
- if (this.#sessions.get(sessionId) !== record) {
2145
- return;
2146
- }
2147
- await this.#connection.sessionUpdate({
2148
- sessionId,
2149
- update: {
2150
- sessionUpdate: "available_commands_update",
2151
- availableCommands: await this.#buildAvailableCommands(record.session),
2152
- },
2153
- });
2154
- await this.#connection.sessionUpdate({
2155
- sessionId,
2156
- update: {
2157
- sessionUpdate: "session_info_update",
2158
- title: record.session.sessionName,
2159
- updatedAt: record.session.sessionManager.getHeader()?.timestamp,
2160
- },
2161
- });
2162
- }
2163
-
2164
- async #emitAvailableCommandsUpdate(record: ManagedSessionRecord): Promise<void> {
2165
- await this.#connection.sessionUpdate({
2166
- sessionId: record.session.sessionId,
2167
- update: {
2168
- sessionUpdate: "available_commands_update",
2169
- availableCommands: await this.#buildAvailableCommands(record.session),
2170
- },
2171
- });
2172
- }
2173
-
2174
- /** Ensure cancellation clears a previously emitted busy/compacting phase even if abort never produces an end event. */
2175
- async #emitCancelledPromptIdleUpdate(record: ManagedSessionRecord): Promise<void> {
2176
- if (this.#connection.signal.aborted) {
2177
- return;
2178
- }
2179
- try {
2180
- await this.#connection.sessionUpdate({
2181
- sessionId: record.session.sessionId,
2182
- update: {
2183
- sessionUpdate: "session_info_update",
2184
- title: record.session.sessionName,
2185
- updatedAt: new Date().toISOString(),
2186
- _meta: {
2187
- skcPhase: "idle",
2188
- running: false,
2189
- skcRunning: false,
2190
- },
2191
- },
2192
- });
2193
- } catch (error) {
2194
- logger.warn("Failed to emit cancelled ACP prompt idle update", { error });
2195
- }
2196
- }
2197
-
2198
- /**
2199
- * Reload plugin/registry state for an ACP session. Mirrors the interactive
2200
- * `/reload-plugins` and `/move` flows: invalidates the plugin-roots cache,
2201
- * resets the capability cache, refreshes the session's slash-command state,
2202
- * then re-advertises commands so the client sees newly installed/disabled
2203
- * plugins.
2204
- */
2205
- async #reloadPluginState(record: ManagedSessionRecord): Promise<void> {
2206
- const cwd = record.session.sessionManager.getCwd();
2207
- const projectPath = await resolveActiveProjectRegistryPath(cwd);
2208
- clearPluginRootsAndCaches(projectPath ? [projectPath] : undefined);
2209
- resetCapabilities();
2210
- const fileCommands = await loadSlashCommands({ cwd });
2211
- record.session.setSlashCommands(fileCommands);
2212
- await record.session.refreshSshTool({ activateIfAvailable: true });
2213
- await this.#emitAvailableCommandsUpdate(record);
2214
- }
2215
-
2216
- async #emitEndOfTurnUpdates(record: ManagedSessionRecord): Promise<void> {
2217
- const sessionId = record.session.sessionId;
2218
-
2219
- const contextUsage = record.session.getContextUsage();
2220
- if (contextUsage?.tokens != null) {
2221
- const usageStats = record.session.sessionManager.getUsageStatistics();
2222
- await this.#connection.sessionUpdate({
2223
- sessionId,
2224
- update: {
2225
- sessionUpdate: "usage_update",
2226
- size: contextUsage.contextWindow,
2227
- used: contextUsage.tokens,
2228
- cost: usageStats.cost > 0 ? { amount: usageStats.cost, currency: "USD" } : undefined,
2229
- },
2230
- });
2231
- }
2232
-
2233
- await this.#connection.sessionUpdate({
2234
- sessionId,
2235
- update: {
2236
- sessionUpdate: "session_info_update",
2237
- title: record.session.sessionName,
2238
- updatedAt: new Date().toISOString(),
2239
- _meta: {
2240
- skcPhase: "idle",
2241
- running: false,
2242
- skcRunning: false,
2243
- },
2244
- },
2245
- });
2246
- }
2247
-
2248
- #cloneUsageStatistics(usage: UsageStatistics): UsageStatistics {
2249
- return {
2250
- input: usage.input,
2251
- output: usage.output,
2252
- cacheRead: usage.cacheRead,
2253
- cacheWrite: usage.cacheWrite,
2254
- premiumRequests: usage.premiumRequests,
2255
- cost: usage.cost,
2256
- };
2257
- }
2258
-
2259
- #buildTurnUsage(previous: UsageStatistics, current: UsageStatistics): Usage | undefined {
2260
- const inputTokens = Math.max(0, current.input - previous.input);
2261
- const outputTokens = Math.max(0, current.output - previous.output);
2262
- const cachedReadTokens = Math.max(0, current.cacheRead - previous.cacheRead);
2263
- const cachedWriteTokens = Math.max(0, current.cacheWrite - previous.cacheWrite);
2264
- const totalTokens = inputTokens + outputTokens + cachedReadTokens + cachedWriteTokens;
2265
-
2266
- if (totalTokens === 0) {
2267
- return undefined;
2268
- }
2269
-
2270
- const usage: Usage = {
2271
- inputTokens,
2272
- outputTokens,
2273
- totalTokens,
2274
- };
2275
- if (cachedReadTokens > 0) {
2276
- usage.cachedReadTokens = cachedReadTokens;
2277
- }
2278
- if (cachedWriteTokens > 0) {
2279
- usage.cachedWriteTokens = cachedWriteTokens;
2280
- }
2281
- return usage;
2282
- }
2283
-
2284
- async #listStoredSessions(cwd?: string): Promise<StoredSessionInfo[]> {
2285
- const sessions = cwd ? await SessionManager.list(cwd) : await SessionManager.listAll();
2286
- return sessions.sort((left, right) => right.modified.getTime() - left.modified.getTime());
2287
- }
2288
-
2289
- #parseCursor(cursor: string | undefined): number {
2290
- if (!cursor) {
2291
- return 0;
2292
- }
2293
- const parsed = Number.parseInt(cursor, 10);
2294
- if (!Number.isFinite(parsed) || parsed < 0) {
2295
- throw new Error(`Invalid ACP session cursor: ${cursor}`);
2296
- }
2297
- return parsed;
2298
- }
2299
-
2300
- /**
2301
- * Parse a scoped cursor of the form `${generation}:${offset}` and reject it when
2302
- * its embedded generation no longer matches the current authority generation.
2303
- * Unlike the cwd-less display cursor, the scoped cursor binds the page offset to
2304
- * the exact authority snapshot that produced it, so a lifecycle invalidation
2305
- * between pages is observable instead of silently rebuilt.
2306
- */
2307
- #parseScopedCursor(cursor: string | undefined, currentGeneration: number): number {
2308
- if (!cursor) {
2309
- return 0;
2310
- }
2311
- const separator = cursor.indexOf(":");
2312
- if (separator <= 0) {
2313
- throw new Error(`Invalid ACP session cursor: ${cursor}`);
2314
- }
2315
- const generation = Number.parseInt(cursor.slice(0, separator), 10);
2316
- const offset = Number.parseInt(cursor.slice(separator + 1), 10);
2317
- if (!Number.isFinite(generation) || generation < 0 || !Number.isFinite(offset) || offset < 0) {
2318
- throw new Error(`Invalid ACP session cursor: ${cursor}`);
2319
- }
2320
- if (generation !== currentGeneration) {
2321
- throw new Error("ACP session list cursor is stale; the scoped session inventory changed");
2322
- }
2323
- return offset;
2324
- }
2325
-
2326
- async #replaySessionHistory(record: ManagedSessionRecord): Promise<void> {
2327
- const cwd = record.session.sessionManager.getCwd();
2328
- const replayedToolCallIds = new Set<string>();
2329
- const replayedToolCallArgs = new Map<string, unknown>();
2330
- for (const message of record.session.sessionManager.buildSessionContext().messages as ReplayableMessage[]) {
2331
- for (const notification of this.#messageToReplayNotifications(
2332
- record.session.sessionId,
2333
- message,
2334
- cwd,
2335
- replayedToolCallIds,
2336
- replayedToolCallArgs,
2337
- )) {
2338
- await this.#connection.sessionUpdate(notification);
2339
- }
2340
- }
2341
- }
2342
-
2343
- #messageToReplayNotifications(
2344
- sessionId: string,
2345
- message: ReplayableMessage,
2346
- cwd: string,
2347
- replayedToolCallIds: Set<string>,
2348
- replayedToolCallArgs: Map<string, unknown>,
2349
- ): SessionNotification[] {
2350
- if (message.role === "assistant") {
2351
- return this.#replayAssistantMessage(sessionId, message, cwd, replayedToolCallIds, replayedToolCallArgs);
2352
- }
2353
- if (
2354
- message.role === "user" ||
2355
- message.role === "developer" ||
2356
- message.role === "custom" ||
2357
- message.role === "hookMessage"
2358
- ) {
2359
- return this.#wrapReplayContent(
2360
- sessionId,
2361
- this.#extractReplayContent(message.content, undefined),
2362
- "user_message_chunk",
2363
- crypto.randomUUID(),
2364
- );
2365
- }
2366
- if (
2367
- message.role === "toolResult" &&
2368
- typeof message.toolCallId === "string" &&
2369
- typeof message.toolName === "string"
2370
- ) {
2371
- return this.#replayToolResult(
2372
- sessionId,
2373
- cwd,
2374
- {
2375
- ...message,
2376
- toolCallId: message.toolCallId,
2377
- toolName: message.toolName,
2378
- },
2379
- {
2380
- includeStart: !replayedToolCallIds.has(message.toolCallId),
2381
- toolArgs: replayedToolCallArgs.get(message.toolCallId),
2382
- },
2383
- );
2384
- }
2385
- if (
2386
- message.role === "bashExecution" ||
2387
- message.role === "pythonExecution" ||
2388
- message.role === "compactionSummary"
2389
- ) {
2390
- return this.#wrapReplayContent(
2391
- sessionId,
2392
- this.#extractReplayContent(message.content, undefined),
2393
- "user_message_chunk",
2394
- crypto.randomUUID(),
2395
- );
2396
- }
2397
- return [];
2398
- }
2399
-
2400
- #replayAssistantMessage(
2401
- sessionId: string,
2402
- message: ReplayableMessage,
2403
- cwd: string,
2404
- replayedToolCallIds: Set<string>,
2405
- replayedToolCallArgs: Map<string, unknown>,
2406
- ): SessionNotification[] {
2407
- const notifications: SessionNotification[] = [];
2408
- const messageId = crypto.randomUUID();
2409
- if (Array.isArray(message.content)) {
2410
- for (const item of message.content) {
2411
- if (typeof item !== "object" || item === null || !("type" in item)) {
2412
- continue;
2413
- }
2414
- if (item.type === "text" && "text" in item && typeof item.text === "string" && item.text.length > 0) {
2415
- notifications.push({
2416
- sessionId,
2417
- update: {
2418
- sessionUpdate: "agent_message_chunk",
2419
- content: { type: "text", text: item.text },
2420
- messageId,
2421
- },
2422
- });
2423
- continue;
2424
- }
2425
- if (
2426
- item.type === "thinking" &&
2427
- "thinking" in item &&
2428
- typeof item.thinking === "string" &&
2429
- item.thinking.length > 0
2430
- ) {
2431
- notifications.push({
2432
- sessionId,
2433
- update: {
2434
- sessionUpdate: "agent_thought_chunk",
2435
- content: { type: "text", text: item.thinking },
2436
- messageId,
2437
- },
2438
- });
2439
- continue;
2440
- }
2441
- const toolItem = item as ReplayableToolItem;
2442
- if (
2443
- (toolItem.type === "toolCall" || toolItem.type === "tool_use") &&
2444
- typeof toolItem.id === "string" &&
2445
- typeof toolItem.name === "string"
2446
- ) {
2447
- const args = this.#buildReplayAssistantToolArgs(toolItem);
2448
- const update = buildToolCallStartUpdate({
2449
- toolCallId: toolItem.id,
2450
- toolName: toolItem.name,
2451
- args,
2452
- status: "completed",
2453
- cwd,
2454
- });
2455
- notifications.push({ sessionId, update });
2456
- replayedToolCallIds.add(toolItem.id);
2457
- replayedToolCallArgs.set(toolItem.id, args);
2458
- }
2459
- }
2460
- }
2461
- if (notifications.length === 0 && message.errorMessage && !isSilentAbort(message.errorMessage)) {
2462
- notifications.push({
2463
- sessionId,
2464
- update: {
2465
- sessionUpdate: "agent_message_chunk",
2466
- content: { type: "text", text: message.errorMessage },
2467
- messageId,
2468
- },
2469
- });
2470
- }
2471
- return notifications;
2472
- }
2473
-
2474
- #buildReplayAssistantToolArgs(item: ReplayableToolItem): unknown {
2475
- if ("arguments" in item) {
2476
- return normalizeReplayToolArguments(item.arguments).args;
2477
- }
2478
- if (item.type === "tool_use" && "input" in item) {
2479
- return item.input;
2480
- }
2481
- return {};
2482
- }
2483
-
2484
- #replayToolResult(
2485
- sessionId: string,
2486
- cwd: string,
2487
- message: Required<Pick<ReplayableMessage, "toolCallId" | "toolName">> & ReplayableMessage,
2488
- options: { includeStart?: boolean; toolArgs?: unknown } = {},
2489
- ): SessionNotification[] {
2490
- const args = this.#buildReplayToolArgs(message.details);
2491
- const startEvent: AgentSessionEvent = {
2492
- type: "tool_execution_start",
2493
- toolCallId: message.toolCallId,
2494
- toolName: message.toolName,
2495
- args,
2496
- };
2497
- const endEvent: AgentSessionEvent = {
2498
- type: "tool_execution_end",
2499
- toolCallId: message.toolCallId,
2500
- toolName: message.toolName,
2501
- isError: message.isError === true,
2502
- result: {
2503
- content: message.content,
2504
- details: message.details,
2505
- errorMessage: message.errorMessage,
2506
- },
2507
- };
2508
- const notifications = mapAgentWireEventPayloadToAcpSessionUpdates(toAgentWireEventPayload(endEvent), sessionId, {
2509
- cwd,
2510
- getToolArgs: toolCallId => (toolCallId === message.toolCallId ? options.toolArgs : undefined),
2511
- });
2512
- if (options.includeStart === false) {
2513
- return notifications;
2514
- }
2515
- return [
2516
- ...mapAgentWireEventPayloadToAcpSessionUpdates(toAgentWireEventPayload(startEvent), sessionId, { cwd }),
2517
- ...notifications,
2518
- ];
2519
- }
2520
-
2521
- #buildReplayToolArgs(details: unknown): { path?: string } {
2522
- if (typeof details !== "object" || details === null || !("path" in details)) {
2523
- return {};
2524
- }
2525
- const value = (details as { path?: unknown }).path;
2526
- return typeof value === "string" && value.length > 0 ? { path: value } : {};
2527
- }
2528
-
2529
- #wrapReplayContent(
2530
- sessionId: string,
2531
- content: PromptRequest["prompt"],
2532
- kind: "agent_message_chunk" | "user_message_chunk",
2533
- messageId: string,
2534
- ): SessionNotification[] {
2535
- return content.map(block => ({
2536
- sessionId,
2537
- update: {
2538
- sessionUpdate: kind,
2539
- content: block,
2540
- messageId,
2541
- },
2542
- }));
2543
- }
2544
-
2545
- #extractReplayContent(content: unknown, errorMessage: string | undefined): PromptRequest["prompt"] {
2546
- const replay: PromptRequest["prompt"] = [];
2547
- if (Array.isArray(content)) {
2548
- for (const item of content) {
2549
- if (typeof item !== "object" || item === null || !("type" in item)) {
2550
- continue;
2551
- }
2552
- if (item.type === "text" && "text" in item && typeof item.text === "string" && item.text.length > 0) {
2553
- replay.push({ type: "text", text: item.text });
2554
- continue;
2555
- }
2556
- if (
2557
- item.type === "image" &&
2558
- "data" in item &&
2559
- "mimeType" in item &&
2560
- typeof item.data === "string" &&
2561
- typeof item.mimeType === "string"
2562
- ) {
2563
- replay.push({ type: "image", data: item.data, mimeType: item.mimeType });
2564
- }
2565
- }
2566
- }
2567
- if (replay.length === 0 && errorMessage) {
2568
- replay.push({ type: "text", text: errorMessage });
2569
- }
2570
- return replay;
2571
- }
2572
-
2573
- async #configureExtensions(record: ManagedSessionRecord): Promise<void> {
2574
- if (record.extensionsConfigured) {
2575
- return;
2576
- }
2577
-
2578
- const extensionRunner = record.session.extensionRunner;
2579
- if (!extensionRunner) {
2580
- record.extensionsConfigured = true;
2581
- return;
2582
- }
2583
-
2584
- extensionRunner.initialize(
2585
- {
2586
- sendMessage: (message, options) => {
2587
- record.session.sendCustomMessage(message, options).catch((error: unknown) => {
2588
- logger.warn("ACP extension sendMessage failed", { error });
2589
- });
2590
- },
2591
- sendUserMessage: (content, options) => {
2592
- record.session.sendUserMessage(content, options).catch((error: unknown) => {
2593
- logger.warn("ACP extension sendUserMessage failed", { error });
2594
- });
2595
- },
2596
- appendEntry: (customType, data) => {
2597
- record.session.sessionManager.appendCustomEntry(customType, data);
2598
- },
2599
- setLabel: (targetId, label) => {
2600
- record.session.sessionManager.appendLabelChange(targetId, label);
2601
- },
2602
- getActiveTools: () => record.session.getActiveToolNames(),
2603
- getAllTools: () => record.session.getAllToolNames(),
2604
- setActiveTools: toolNames => record.session.setActiveToolsByName(toolNames),
2605
- getCommands: () => getSessionSlashCommands(record.session),
2606
- setModel: async model => {
2607
- const apiKey = await record.session.modelRegistry.getApiKey(model);
2608
- if (!apiKey) {
2609
- return false;
2610
- }
2611
- await record.session.setModel(model);
2612
- return true;
2613
- },
2614
- getThinkingLevel: () => record.session.thinkingLevel,
2615
- setThinkingLevel: level => record.session.setThinkingLevel(level),
2616
- getSessionName: () => record.session.sessionManager.getSessionName(),
2617
- setSessionName: async name => {
2618
- await record.session.sessionManager.setSessionName(name, "user");
2619
- },
2620
- },
2621
- {
2622
- getModel: () => record.session.model,
2623
- isIdle: () => !record.session.isStreaming,
2624
- abort: () => {
2625
- void record.session.abort();
2626
- },
2627
- hasPendingMessages: () => record.session.queuedMessageCount > 0,
2628
- shutdown: () => {},
2629
- getContextUsage: () => record.session.getContextUsage(),
2630
- getSystemPrompt: () => record.session.systemPrompt,
2631
- compact: instructionsOrOptions => runExtensionCompact(record.session, instructionsOrOptions),
2632
- },
2633
- {
2634
- getContextUsage: () => record.session.getContextUsage(),
2635
- waitForIdle: () => record.session.agent.waitForIdle(),
2636
- newSession: async options => {
2637
- const success = await record.session.newSession({ parentSession: options?.parentSession });
2638
- if (success && options?.setup) {
2639
- await options.setup(record.session.sessionManager);
2640
- }
2641
- return { cancelled: !success };
2642
- },
2643
- branch: async entryId => {
2644
- const result = await record.session.branch(entryId);
2645
- return { cancelled: result.cancelled };
2646
- },
2647
- navigateTree: async (targetId, options) => {
2648
- const result = await record.session.navigateTree(targetId, { summarize: options?.summarize });
2649
- return { cancelled: result.cancelled };
2650
- },
2651
- switchSession: async sessionPath => {
2652
- const success = await record.session.switchSession(sessionPath);
2653
- return { cancelled: !success };
2654
- },
2655
- reload: async () => {
2656
- await record.session.reload();
2657
- },
2658
- compact: instructionsOrOptions => runExtensionCompact(record.session, instructionsOrOptions),
2659
- },
2660
- // Per-session getter: `record.session.sessionId` reads through to
2661
- // `sessionManager.getSessionId()` (it's a getter, not a field), so an
2662
- // extension command that calls `ctx.newSession` / `ctx.switchSession`
2663
- // — both exposed in the block just above — mutates the underlying id
2664
- // mid-flight. Reading lazily on each elicitation matches every other
2665
- // `sessionUpdate` call in this file. Hoisting the factory to an
2666
- // `AcpAgent` field would still be wrong because it would also lose
2667
- // the per-`record` binding.
2668
- createAcpExtensionUiContext(this.#connection, () => record.session.sessionId, this.#clientCapabilities),
2669
- );
2670
- await extensionRunner.emit({ type: "session_start" });
2671
- record.extensionsConfigured = true;
2672
- }
2673
-
2674
- async #configureMcpServers(record: ManagedSessionRecord, servers: McpServer[]): Promise<void> {
2675
- if (record.mcpManager) {
2676
- await record.mcpManager.disconnectAll();
2677
- }
2678
- if (servers.length === 0) {
2679
- record.mcpManager = undefined;
2680
- await record.session.refreshMCPTools([]);
2681
- return;
2682
- }
2683
-
2684
- const manager = new MCPManager(record.session.sessionManager.getCwd());
2685
- const configs: MCPConfigMap = {};
2686
- const sources: MCPSourceMap = {};
2687
- for (const server of servers) {
2688
- configs[server.name] = this.#toMcpConfig(server);
2689
- sources[server.name] = {
2690
- provider: "acp",
2691
- providerName: "ACP Client",
2692
- path: `acp://${server.name}`,
2693
- level: "project",
2694
- };
2695
- }
2696
-
2697
- const result = await manager.connectServers(configs, sources);
2698
- if (result.errors.size > 0) {
2699
- throw new Error(
2700
- Array.from(result.errors.entries())
2701
- .map(([name, message]) => `${name}: ${message}`)
2702
- .join("; "),
2703
- );
2704
- }
2705
-
2706
- record.mcpManager = manager;
2707
- await record.session.refreshMCPTools(result.tools);
2708
- }
2709
-
2710
- #toMcpConfig(server: McpServer): MCPServerConfig {
2711
- if ("command" in server) {
2712
- return {
2713
- type: "stdio",
2714
- command: server.command,
2715
- args: server.args,
2716
- env: this.#toNameValueMap(server.env),
2717
- };
2718
- }
2719
- if (server.type === "http") {
2720
- return {
2721
- type: "http",
2722
- url: server.url,
2723
- headers: this.#toNameValueMap(server.headers),
2724
- };
2725
- }
2726
- if (server.type === "sse") {
2727
- return {
2728
- type: "sse",
2729
- url: server.url,
2730
- headers: this.#toNameValueMap(server.headers),
2731
- };
2732
- }
2733
- throw new Error(`Unsupported ACP MCP transport: ${server.type}`);
2734
- }
2735
-
2736
- #toNameValueMap(values: Array<{ name: string; value: string }>): { [name: string]: string } {
2737
- const mapped: { [name: string]: string } = {};
2738
- for (const value of values) {
2739
- mapped[value.name] = value.value;
2740
- }
2741
- return mapped;
2742
- }
2743
-
2744
- async #closeManagedSession(sessionId: string, record: ManagedSessionRecord): Promise<void> {
2745
- this.#sessions.delete(sessionId);
2746
- await this.#cancelPromptForClose(record);
2747
- await this.#disposeSessionRecord(record);
2748
- }
2749
-
2750
- async #cancelPromptForClose(record: ManagedSessionRecord): Promise<void> {
2751
- const promptTurn = record.promptTurn;
2752
- if (!isPromptTurnInFlight(promptTurn)) {
2753
- return;
2754
- }
2755
- const cleanup = promptTurn.cleanup ?? this.#beginCancelCleanup(record, promptTurn);
2756
- try {
2757
- await cleanup;
2758
- } catch (error) {
2759
- logger.warn("Failed to abort ACP prompt during session close", { error });
2760
- }
2761
- }
2762
- /**
2763
- * Delete-specific strict prompt quiescence barrier. Mirrors the cancel setup of
2764
- * {@link #cancelPromptForClose} but NEVER swallows abort/timeout failures, then
2765
- * waits for full idle + async-delivery drain. A quiescence failure blocks the
2766
- * destructive mutation that follows — it is not catch-and-logged. The best-effort
2767
- * close path is preserved separately for nondestructive shutdown.
2768
- */
2769
- async #quiesceForDelete(sessionId: string, record: ManagedSessionRecord): Promise<void> {
2770
- const promptTurn = record.promptTurn;
2771
- if (isPromptTurnInFlight(promptTurn)) {
2772
- const cleanup = promptTurn.cleanup ?? this.#beginCancelCleanup(record, promptTurn);
2773
- await cleanup;
2774
- }
2775
- await this.#waitForAcpPromptIdle(record);
2776
- // Explicit post-drain status check: the drain loop's boolean return is
2777
- // not trusted as proof. Treat a drain false/timeout/no-progress/max-pass
2778
- // as unproven and verify the owner-scoped delivery state directly. Any
2779
- // remaining queued/delivering work blocks the destructive mutation.
2780
- const delivery = record.session.getAsyncDeliveryStateForAcp();
2781
- if (delivery.queued > 0 || delivery.delivering) {
2782
- throw new Error(`ACP session ${sessionId} cannot be deleted: async delivery is not quiesced`);
2783
- }
2784
- }
2785
-
2786
- /**
2787
- * Record phase-aware same-connection retry evidence for an incomplete verified
2788
- * delete. The transcript identity (carried by every cleanup_pending) is always
2789
- * recorded so a retry can reject a replaced transcript; the artifacts identity
2790
- * is recorded only at the artifacts phase.
2791
- */
2792
- #recordPendingDeleteEvidence(sessionId: string, result: VerifiedSessionDeleteResult): void {
2793
- if (result.kind !== "cleanup_pending") {
2794
- return;
2795
- }
2796
- if (result.phase === "artifacts") {
2797
- this.#pendingDeleteEvidence.set(sessionId, {
2798
- transcriptIdentity: result.transcriptIdentity,
2799
- artifactsIdentity: result.artifactsIdentity,
2800
- });
2801
- } else {
2802
- // Transcript phase: artifacts were already removed successfully, so only
2803
- // the transcript identity remains for retry binding.
2804
- this.#pendingDeleteEvidence.set(sessionId, {
2805
- transcriptIdentity: result.transcriptIdentity,
2806
- });
2807
- }
2808
- }
2809
-
2810
- /** Build the optional `expectedArtifactsIdentity` field for a retry target. */
2811
- #pendingArtifactsIdentity(sessionId: string): { expectedArtifactsIdentity?: SessionStorageFileIdentity } {
2812
- const pending = this.#pendingDeleteEvidence.get(sessionId);
2813
- return pending?.artifactsIdentity ? { expectedArtifactsIdentity: pending.artifactsIdentity } : {};
2814
- }
2815
-
2816
- /**
2817
- * Phase-aware retry gate: a preserved transcript identity must match the fresh
2818
- * candidate's transcript identity before that candidate can authorize a
2819
- * replacement. A mismatch (the transcript was externally replaced between the
2820
- * cleanup_pending failure and the retry) fails closed — it never authorizes a
2821
- * wrong mutation.
2822
- */
2823
- #assertPendingTranscriptIdentity(sessionId: string, candidateIdentity: { dev: bigint; ino: bigint }): void {
2824
- const pending = this.#pendingDeleteEvidence.get(sessionId);
2825
- if (
2826
- pending &&
2827
- (pending.transcriptIdentity.dev !== candidateIdentity.dev ||
2828
- pending.transcriptIdentity.ino !== candidateIdentity.ino)
2829
- ) {
2830
- throw new Error(
2831
- `ACP session ${sessionId} transcript identity changed since pending cleanup; replacement rejected`,
2832
- );
2833
- }
2834
- }
2835
-
2836
- async #disposeSessionRecord(record: ManagedSessionRecord): Promise<void> {
2837
- record.lifetimeUnsubscribe?.();
2838
- if (record.mcpManager) {
2839
- try {
2840
- await record.mcpManager.disconnectAll();
2841
- } catch (error) {
2842
- logger.warn("Failed to disconnect ACP MCP servers", { error });
2843
- }
2844
- record.mcpManager = undefined;
2845
- }
2846
- try {
2847
- await record.session.dispose();
2848
- } catch (error) {
2849
- logger.warn("Failed to dispose ACP session", { error });
2850
- }
2851
- }
2852
-
2853
- async #disposeStandaloneSession(session: AgentSession): Promise<void> {
2854
- try {
2855
- await session.dispose();
2856
- } catch (error) {
2857
- logger.warn("Failed to dispose ACP session", { error });
2858
- }
2859
- }
2860
-
2861
- async #disposeAllSessions(): Promise<void> {
2862
- if (this.#disposePromise) {
2863
- await this.#disposePromise;
2864
- return;
2865
- }
2866
-
2867
- this.#disposePromise = (async () => {
2868
- const records = Array.from(this.#sessions.entries());
2869
- this.#sessions.clear();
2870
- await Promise.all(
2871
- records.map(async ([sessionId, record]) => {
2872
- try {
2873
- await this.#cancelPromptForClose(record);
2874
- await this.#disposeSessionRecord(record);
2875
- } catch (error) {
2876
- logger.warn("Failed to clean up ACP session", { sessionId, error });
2877
- }
2878
- }),
2879
- );
2880
-
2881
- const initialSession = this.#initialSession;
2882
- this.#initialSession = undefined;
2883
- if (initialSession) {
2884
- await this.#disposeStandaloneSession(initialSession);
2885
- }
2886
- })();
2887
-
2888
- await this.#disposePromise;
2889
1513
  }
2890
1514
  }