@gajae-code/coding-agent 0.13.3 → 0.14.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 (599) hide show
  1. package/CHANGELOG.md +184 -2
  2. package/README.md +2 -0
  3. package/bin/gajaessi.js +4 -0
  4. package/dist/types/async/job-manager.d.ts +55 -2
  5. package/dist/types/async/support.d.ts +14 -0
  6. package/dist/types/capability/mcp.d.ts +6 -0
  7. package/dist/types/capability/types.d.ts +6 -0
  8. package/dist/types/cli/accounts-cli.d.ts +19 -0
  9. package/dist/types/cli/args.d.ts +3 -0
  10. package/dist/types/cli/config-cli.d.ts +6 -0
  11. package/dist/types/cli/crash-cli.d.ts +10 -0
  12. package/dist/types/cli/customize-doctor.d.ts +145 -0
  13. package/dist/types/cli/daemon-cli.d.ts +3 -1
  14. package/dist/types/cli/mcp-cli.d.ts +11 -0
  15. package/dist/types/cli/notify-cli.d.ts +1 -1
  16. package/dist/types/cli/root-flags.d.ts +12 -0
  17. package/dist/types/cli/setup-cli.d.ts +3 -1
  18. package/dist/types/cli/skills-cli.d.ts +2 -1
  19. package/dist/types/cli/update-cli.d.ts +19 -1
  20. package/dist/types/cli.d.ts +6 -0
  21. package/dist/types/commands/accounts.d.ts +40 -0
  22. package/dist/types/commands/crash.d.ts +24 -0
  23. package/dist/types/commands/customize.d.ts +22 -0
  24. package/dist/types/commands/daemon.d.ts +1 -1
  25. package/dist/types/commands/launch.d.ts +6 -0
  26. package/dist/types/commands/setup.d.ts +6 -0
  27. package/dist/types/commands/skills.d.ts +6 -1
  28. package/dist/types/commands/update.d.ts +7 -0
  29. package/dist/types/config/atomic-yaml-patch-worker.d.ts +25 -0
  30. package/dist/types/config/atomic-yaml-patch.d.ts +62 -2
  31. package/dist/types/config/config-schema-version.d.ts +10 -0
  32. package/dist/types/config/file-lock.d.ts +18 -0
  33. package/dist/types/config/model-profile-activation.d.ts +3 -1
  34. package/dist/types/config/model-registry.d.ts +9 -1
  35. package/dist/types/config/models-config-schema.d.ts +2 -0
  36. package/dist/types/config/settings-schema.d.ts +70 -6
  37. package/dist/types/config/settings.d.ts +63 -0
  38. package/dist/types/config/skill-settings-defaults.d.ts +26 -0
  39. package/dist/types/config/task-delegation.d.ts +39 -0
  40. package/dist/types/config/update-channel.d.ts +10 -0
  41. package/dist/types/coordinator-mcp/server.d.ts +5 -1
  42. package/dist/types/crash/index-store.d.ts +87 -0
  43. package/dist/types/crash/nudge.d.ts +50 -0
  44. package/dist/types/crash/record-loader.d.ts +17 -0
  45. package/dist/types/crash/report.d.ts +134 -0
  46. package/dist/types/crash/sanitize.d.ts +25 -0
  47. package/dist/types/cursor.d.ts +13 -2
  48. package/dist/types/customization/import.d.ts +37 -0
  49. package/dist/types/customization/index.d.ts +4 -0
  50. package/dist/types/customization/inventory.d.ts +25 -0
  51. package/dist/types/customization/mutations.d.ts +32 -0
  52. package/dist/types/customization/types.d.ts +115 -0
  53. package/dist/types/daemon/control-types.d.ts +8 -1
  54. package/dist/types/daemon/operator-contract.d.ts +4 -4
  55. package/dist/types/discovery/claude.d.ts +39 -1
  56. package/dist/types/discovery/codex.d.ts +39 -1
  57. package/dist/types/discovery/index.d.ts +2 -0
  58. package/dist/types/discovery/mcp-compat.d.ts +30 -0
  59. package/dist/types/edit/renderer.d.ts +23 -0
  60. package/dist/types/extensibility/extensions/runner.d.ts +3 -0
  61. package/dist/types/extensibility/extensions/types.d.ts +21 -4
  62. package/dist/types/extensibility/gjc-plugins/observability.d.ts +6 -1
  63. package/dist/types/extensibility/gjc-plugins/registry.d.ts +8 -1
  64. package/dist/types/extensibility/hooks/loader.d.ts +19 -5
  65. package/dist/types/extensibility/runtime-skill-discovery.d.ts +22 -1
  66. package/dist/types/extensibility/skill-management.d.ts +107 -0
  67. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +3 -1
  68. package/dist/types/gjc-runtime/launch-worktree.d.ts +8 -0
  69. package/dist/types/gjc-runtime/linux-proc.d.ts +3 -0
  70. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +10 -6
  71. package/dist/types/gjc-runtime/session-state-lock.d.ts +104 -0
  72. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +120 -2
  73. package/dist/types/gjc-runtime/state-writer.d.ts +10 -1
  74. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +4 -2
  75. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -6
  76. package/dist/types/gjc-runtime/workflow-settings.d.ts +109 -0
  77. package/dist/types/hooks/events.d.ts +60 -0
  78. package/dist/types/hooks/normalize.d.ts +83 -0
  79. package/dist/types/hooks/skill-state.d.ts +9 -2
  80. package/dist/types/main.d.ts +1 -1
  81. package/dist/types/modes/acp/abort-scope.d.ts +13 -0
  82. package/dist/types/modes/acp/acp-agent.d.ts +7 -4
  83. package/dist/types/modes/components/customization/customization-dashboard.d.ts +44 -0
  84. package/dist/types/modes/components/customization/import-wizard.d.ts +21 -0
  85. package/dist/types/modes/components/customization/index.d.ts +1 -0
  86. package/dist/types/modes/components/gajae-pet-widget.d.ts +8 -30
  87. package/dist/types/modes/components/irc-sidebar.d.ts +1 -0
  88. package/dist/types/modes/components/iterm-pet-transport.d.ts +122 -0
  89. package/dist/types/modes/components/oauth-selector.d.ts +10 -4
  90. package/dist/types/modes/components/pet-capability.d.ts +11 -25
  91. package/dist/types/modes/components/status-line/gh.d.ts +6 -2
  92. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
  93. package/dist/types/modes/controllers/selector-controller.d.ts +7 -8
  94. package/dist/types/modes/controllers/stt-controller.d.ts +1 -1
  95. package/dist/types/modes/interactive-mode.d.ts +1 -0
  96. package/dist/types/modes/types.d.ts +1 -0
  97. package/dist/types/modes/utils/injected-user-submission.d.ts +10 -0
  98. package/dist/types/runtime-mcp/config.d.ts +9 -0
  99. package/dist/types/runtime-mcp/manager.d.ts +26 -1
  100. package/dist/types/runtime-mcp/oauth-discovery.d.ts +4 -0
  101. package/dist/types/runtime-mcp/oauth-flow.d.ts +24 -1
  102. package/dist/types/runtime-mcp/pool-key.d.ts +2 -0
  103. package/dist/types/runtime-mcp/protocol.d.ts +278 -0
  104. package/dist/types/runtime-mcp/scope-config.d.ts +29 -0
  105. package/dist/types/runtime-mcp/tool-bridge.d.ts +15 -13
  106. package/dist/types/runtime-mcp/transports/http.d.ts +9 -0
  107. package/dist/types/runtime-mcp/types.d.ts +102 -10
  108. package/dist/types/sdk/acp/adapter.d.ts +26 -5
  109. package/dist/types/sdk/broker/broker.d.ts +3 -55
  110. package/dist/types/sdk/broker/lifecycle.d.ts +18 -12
  111. package/dist/types/sdk/broker/session-index.d.ts +14 -5
  112. package/dist/types/sdk/broker/startup-failure.d.ts +16 -1
  113. package/dist/types/sdk/bus/chat-daemon-control.d.ts +51 -1
  114. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -41
  115. package/dist/types/sdk/bus/config.d.ts +11 -0
  116. package/dist/types/sdk/bus/conversation-store.d.ts +0 -1
  117. package/dist/types/sdk/bus/daemon-paths.d.ts +6 -0
  118. package/dist/types/sdk/bus/discord-conversation.d.ts +2 -0
  119. package/dist/types/sdk/bus/discord-daemon.d.ts +17 -12
  120. package/dist/types/sdk/bus/existing-thread-readiness.d.ts +4 -34
  121. package/dist/types/sdk/bus/inbound-reaction-ordering.d.ts +61 -0
  122. package/dist/types/sdk/bus/index.d.ts +85 -99
  123. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +29 -7
  124. package/dist/types/sdk/bus/managed-daemon.d.ts +2 -32
  125. package/dist/types/sdk/bus/notification-service.d.ts +52 -0
  126. package/dist/types/sdk/bus/operator-runtime.d.ts +1 -1
  127. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +16 -5
  128. package/dist/types/sdk/bus/reconciliation-store.d.ts +113 -5
  129. package/dist/types/sdk/bus/session-control.d.ts +25 -1
  130. package/dist/types/sdk/bus/slack-conversation.d.ts +4 -0
  131. package/dist/types/sdk/bus/slack-daemon.d.ts +9 -17
  132. package/dist/types/sdk/bus/slack-live-provider.d.ts +3 -1
  133. package/dist/types/sdk/bus/slack-provider.d.ts +4 -0
  134. package/dist/types/sdk/bus/slack-thread-binding.d.ts +6 -40
  135. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +30 -104
  136. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +10 -0
  137. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +262 -19
  138. package/dist/types/sdk/bus/telegram-daemon-orphan-reap.d.ts +125 -0
  139. package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +22 -0
  140. package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -310
  141. package/dist/types/sdk/bus/telegram-reference.d.ts +1 -47
  142. package/dist/types/sdk/bus/threaded-render.d.ts +1 -0
  143. package/dist/types/sdk/bus/topic-registry.d.ts +33 -17
  144. package/dist/types/sdk/cli/rows.d.ts +2 -11
  145. package/dist/types/sdk/cli/session-cli.d.ts +15 -25
  146. package/dist/types/sdk/client/client.d.ts +97 -1
  147. package/dist/types/sdk/host/control/dispatch.d.ts +0 -7
  148. package/dist/types/sdk/host/control/index.d.ts +1 -1
  149. package/dist/types/sdk/host/control/operations.d.ts +18 -5
  150. package/dist/types/sdk/host/control/runtime-gate.d.ts +19 -0
  151. package/dist/types/sdk/host/host.d.ts +15 -1
  152. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  153. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  154. package/dist/types/sdk/host/session-runtime.d.ts +127 -7
  155. package/dist/types/sdk/host/types.d.ts +1 -1
  156. package/dist/types/sdk/host/websocket-transport.d.ts +1 -1
  157. package/dist/types/sdk/index.d.ts +2 -1
  158. package/dist/types/sdk/lifecycle/broker-client.d.ts +8 -0
  159. package/dist/types/sdk/lifecycle/client.d.ts +59 -0
  160. package/dist/types/sdk/lifecycle/index.d.ts +3 -0
  161. package/dist/types/sdk/lifecycle/service.d.ts +232 -0
  162. package/dist/types/sdk/mcp/server.d.ts +9 -6
  163. package/dist/types/sdk/prompt-deadline-lease.d.ts +43 -0
  164. package/dist/types/sdk/prompt-deadline-manager.d.ts +28 -0
  165. package/dist/types/sdk/prompt-status.d.ts +10 -3
  166. package/dist/types/sdk/protocol/operation-registry.d.ts +5 -0
  167. package/dist/types/sdk/receipt-state.d.ts +17 -0
  168. package/dist/types/sdk/reconciliation-extensions.d.ts +2 -0
  169. package/dist/types/sdk/router/index.d.ts +1 -0
  170. package/dist/types/sdk/router/session-router.d.ts +152 -0
  171. package/dist/types/sdk/session-list.d.ts +22 -0
  172. package/dist/types/sdk/session-reconnect.d.ts +43 -1
  173. package/dist/types/sdk/session.d.ts +34 -1
  174. package/dist/types/sdk/transport/serve-cli.d.ts +1 -4
  175. package/dist/types/session/account-inventory.d.ts +86 -0
  176. package/dist/types/session/agent-session.d.ts +145 -6
  177. package/dist/types/session/agent-storage.d.ts +10 -0
  178. package/dist/types/session/coordinator-tool-label.d.ts +28 -0
  179. package/dist/types/session/internal/managed-session-storage.d.ts +23 -4
  180. package/dist/types/session/messages.d.ts +1 -1
  181. package/dist/types/session/secure-token-file.d.ts +3 -0
  182. package/dist/types/session/session-manager.d.ts +40 -1
  183. package/dist/types/session/startup-auth-config.d.ts +44 -0
  184. package/dist/types/session/terminal-abort.d.ts +378 -0
  185. package/dist/types/session/yield-queue.d.ts +7 -0
  186. package/dist/types/session-import/claude.d.ts +26 -0
  187. package/dist/types/session-import/codex.d.ts +88 -0
  188. package/dist/types/session-import/command.d.ts +35 -0
  189. package/dist/types/session-import/detect.d.ts +22 -0
  190. package/dist/types/session-import/index.d.ts +7 -0
  191. package/dist/types/session-import/provider-codex.d.ts +22 -0
  192. package/dist/types/session-import/provider-service.d.ts +38 -0
  193. package/dist/types/session-import/redact.d.ts +23 -0
  194. package/dist/types/session-import/service.d.ts +47 -0
  195. package/dist/types/session-import/types.d.ts +120 -0
  196. package/dist/types/setup/paseo/check.d.ts +16 -0
  197. package/dist/types/setup/paseo/install-saga.d.ts +115 -0
  198. package/dist/types/setup/paseo/json-publisher.d.ts +76 -0
  199. package/dist/types/setup/paseo/orchestration-preferences.d.ts +24 -0
  200. package/dist/types/setup/paseo/paseo-ownership.d.ts +87 -0
  201. package/dist/types/setup/paseo/paseo-setup.d.ts +29 -0
  202. package/dist/types/setup/paseo/provider-config.d.ts +35 -0
  203. package/dist/types/setup/paseo/remove.d.ts +14 -0
  204. package/dist/types/setup/paseo/result-types.d.ts +59 -0
  205. package/dist/types/setup/paseo/setup-deps.d.ts +73 -0
  206. package/dist/types/setup/paseo/skills-bridge.d.ts +44 -0
  207. package/dist/types/skill-state/active-state.d.ts +5 -1
  208. package/dist/types/slash-commands/acp-builtins.d.ts +5 -1
  209. package/dist/types/slash-commands/builtin-registry.d.ts +2 -0
  210. package/dist/types/slash-commands/helpers/credential-switch.d.ts +12 -0
  211. package/dist/types/slash-commands/helpers/usage-report.d.ts +5 -6
  212. package/dist/types/slash-commands/types.d.ts +7 -1
  213. package/dist/types/task/executor.d.ts +10 -0
  214. package/dist/types/task/index.d.ts +3 -1
  215. package/dist/types/tools/ask-answer-registry.d.ts +6 -3
  216. package/dist/types/tools/bash-sleep-advisory.d.ts +14 -0
  217. package/dist/types/tools/bash.d.ts +2 -6
  218. package/dist/types/tools/browser/attach.d.ts +11 -0
  219. package/dist/types/tools/browser/launch.d.ts +20 -0
  220. package/dist/types/tools/browser/profile-discovery.d.ts +6 -0
  221. package/dist/types/tools/browser/render.d.ts +1 -0
  222. package/dist/types/tools/browser.d.ts +3 -1
  223. package/dist/types/tools/image-gen.d.ts +9 -2
  224. package/dist/types/tools/index.d.ts +8 -0
  225. package/dist/types/tools/monitor.d.ts +1 -1
  226. package/dist/types/tools/skill-discovery.d.ts +8 -1
  227. package/dist/types/tools/subagent.d.ts +2 -0
  228. package/dist/types/tools/ultragoal-ask-guard.d.ts +2 -2
  229. package/dist/types/utils/herdr-pane.d.ts +76 -0
  230. package/package.json +19 -9
  231. package/scripts/build-binary.ts +2 -1
  232. package/scripts/build-sdk-package-smoke.ts +2 -13
  233. package/scripts/capture-sticky-viewport-showcase.ts +0 -1
  234. package/scripts/compile-args.ts +2 -0
  235. package/scripts/generate-sdk-adapter-parity-manifest.ts +31 -16
  236. package/scripts/generate-sdk-operation-inventory.ts +28 -0
  237. package/scripts/generate-telegram-baseline-manifest.ts +1 -0
  238. package/scripts/generate-tool-catalog.ts +6 -4
  239. package/scripts/verify-gjc-sdk-canonicalization.ts +5 -39
  240. package/src/async/job-manager.ts +352 -17
  241. package/src/async/support.ts +25 -0
  242. package/src/autoresearch/dashboard.ts +28 -13
  243. package/src/capability/index.ts +3 -1
  244. package/src/capability/mcp.ts +6 -0
  245. package/src/capability/types.ts +6 -0
  246. package/src/cli/accounts-cli.ts +395 -0
  247. package/src/cli/args.ts +23 -0
  248. package/src/cli/auth-broker-cli.ts +136 -66
  249. package/src/cli/auth-gateway-cli.ts +94 -70
  250. package/src/cli/config-cli.ts +27 -2
  251. package/src/cli/crash-cli.ts +78 -0
  252. package/src/cli/customize-doctor.ts +1605 -0
  253. package/src/cli/daemon-cli.ts +12 -3
  254. package/src/cli/fast-help.ts +1 -0
  255. package/src/cli/mcp-cli.ts +128 -60
  256. package/src/cli/notify-cli.ts +8 -14
  257. package/src/cli/root-flags.ts +8 -0
  258. package/src/cli/setup-cli.ts +145 -0
  259. package/src/cli/skills-cli.ts +38 -2
  260. package/src/cli/update-cli.ts +167 -12
  261. package/src/cli.ts +4 -0
  262. package/src/commands/accounts.ts +71 -0
  263. package/src/commands/crash.ts +35 -0
  264. package/src/commands/customize.ts +84 -0
  265. package/src/commands/daemon.ts +2 -2
  266. package/src/commands/deep-interview.ts +2 -0
  267. package/src/commands/launch.ts +1 -0
  268. package/src/commands/mcp.ts +12 -7
  269. package/src/commands/ralplan.ts +2 -0
  270. package/src/commands/sdk.ts +65 -32
  271. package/src/commands/setup.ts +5 -0
  272. package/src/commands/skills.ts +14 -4
  273. package/src/commands/ultragoal.ts +10 -1
  274. package/src/commands/update.ts +26 -15
  275. package/src/config/atomic-yaml-patch-worker.ts +57 -0
  276. package/src/config/atomic-yaml-patch.ts +769 -29
  277. package/src/config/config-schema-version.ts +10 -0
  278. package/src/config/file-lock.ts +70 -1
  279. package/src/config/mcp-schema.json +52 -14
  280. package/src/config/model-profile-activation.ts +20 -1
  281. package/src/config/model-profiles.ts +29 -19
  282. package/src/config/model-registry.ts +132 -21
  283. package/src/config/models-config-schema.ts +1 -1
  284. package/src/config/settings-schema.ts +76 -6
  285. package/src/config/settings.ts +4038 -199
  286. package/src/config/skill-settings-defaults.ts +41 -3
  287. package/src/config/task-delegation.ts +87 -0
  288. package/src/config/update-channel.ts +23 -0
  289. package/src/coordinator-mcp/server.ts +377 -308
  290. package/src/crash/index-store.ts +723 -0
  291. package/src/crash/nudge.ts +89 -0
  292. package/src/crash/record-loader.ts +149 -0
  293. package/src/crash/report.ts +551 -0
  294. package/src/crash/sanitize.ts +129 -0
  295. package/src/cursor.ts +36 -10
  296. package/src/customization/import.ts +946 -0
  297. package/src/customization/index.ts +4 -0
  298. package/src/customization/inventory.ts +335 -0
  299. package/src/customization/mutations.ts +141 -0
  300. package/src/customization/types.ts +223 -0
  301. package/src/daemon/control-types.ts +8 -1
  302. package/src/daemon/operator-contract.ts +6 -6
  303. package/src/defaults/gjc/skills/deep-interview/SKILL.md +11 -22
  304. package/src/defaults/gjc/skills/ralplan/SKILL.md +36 -23
  305. package/src/defaults/gjc/skills/ultragoal/SKILL.md +16 -0
  306. package/src/discovery/claude-hooks.ts +38 -0
  307. package/src/discovery/claude.ts +142 -209
  308. package/src/discovery/codex-hooks.ts +38 -0
  309. package/src/discovery/codex.ts +132 -198
  310. package/src/discovery/helpers.ts +8 -1
  311. package/src/discovery/index.ts +2 -0
  312. package/src/discovery/mcp-compat.ts +234 -0
  313. package/src/discovery/mcp-json.ts +6 -0
  314. package/src/edit/renderer.ts +40 -0
  315. package/src/edit/streaming.ts +4 -2
  316. package/src/exec/bash-executor.ts +24 -20
  317. package/src/export/html/index.ts +51 -2
  318. package/src/extensibility/extensions/loader.ts +9 -1
  319. package/src/extensibility/extensions/runner.ts +17 -4
  320. package/src/extensibility/extensions/types.ts +31 -6
  321. package/src/extensibility/gjc-plugins/constrained-hooks.ts +33 -0
  322. package/src/extensibility/gjc-plugins/installer.ts +9 -5
  323. package/src/extensibility/gjc-plugins/observability.ts +8 -2
  324. package/src/extensibility/gjc-plugins/registry.ts +13 -2
  325. package/src/extensibility/gjc-plugins/runtime-adapters.ts +3 -1
  326. package/src/extensibility/gjc-plugins/schema.ts +222 -39
  327. package/src/extensibility/hooks/loader.ts +154 -10
  328. package/src/extensibility/runtime-skill-discovery.ts +257 -60
  329. package/src/extensibility/skill-management.ts +368 -0
  330. package/src/extensibility/skills.ts +52 -16
  331. package/src/gjc-runtime/deep-interview-runtime.ts +45 -61
  332. package/src/gjc-runtime/launch-worktree.ts +56 -2
  333. package/src/gjc-runtime/linux-proc.ts +7 -1
  334. package/src/gjc-runtime/ralplan-runtime.ts +100 -164
  335. package/src/gjc-runtime/session-state-lock.ts +992 -0
  336. package/src/gjc-runtime/session-state-sidecar.ts +644 -142
  337. package/src/gjc-runtime/state-writer.ts +39 -14
  338. package/src/gjc-runtime/tmux-owner-isolation.ts +5 -4
  339. package/src/gjc-runtime/ultragoal-guard.ts +11 -4
  340. package/src/gjc-runtime/ultragoal-runtime.ts +44 -41
  341. package/src/gjc-runtime/workflow-settings.ts +596 -0
  342. package/src/goals/tools/goal-tool.ts +6 -1
  343. package/src/hooks/events.ts +291 -0
  344. package/src/hooks/native-skill-hook.ts +4 -0
  345. package/src/hooks/normalize.ts +369 -0
  346. package/src/hooks/skill-state.ts +165 -46
  347. package/src/internal-urls/docs-index.generated.ts +40 -32
  348. package/src/main.ts +32 -5
  349. package/src/migrate/mcp-mapper.ts +25 -2
  350. package/src/modes/acp/abort-scope.ts +29 -0
  351. package/src/modes/acp/acp-agent.ts +423 -98
  352. package/src/modes/components/assistant-message.ts +6 -1
  353. package/src/modes/components/customization/customization-dashboard.ts +443 -0
  354. package/src/modes/components/customization/import-wizard.ts +372 -0
  355. package/src/modes/components/customization/index.ts +1 -0
  356. package/src/modes/components/gajae-pet-widget.ts +539 -213
  357. package/src/modes/components/irc-sidebar.ts +11 -0
  358. package/src/modes/components/iterm-pet-transport.ts +585 -0
  359. package/src/modes/components/oauth-selector.ts +277 -86
  360. package/src/modes/components/pet-capability.ts +39 -40
  361. package/src/modes/components/settings-selector.ts +9 -0
  362. package/src/modes/components/status-line/gh.ts +45 -1
  363. package/src/modes/components/status-line/segments.ts +16 -1
  364. package/src/modes/components/tool-status-header.ts +13 -3
  365. package/src/modes/controllers/command-controller.ts +20 -8
  366. package/src/modes/controllers/extension-ui-controller.ts +56 -257
  367. package/src/modes/controllers/input-controller.ts +10 -0
  368. package/src/modes/controllers/runtime-mcp-command-controller.ts +16 -3
  369. package/src/modes/controllers/selector-controller.ts +177 -72
  370. package/src/modes/controllers/stt-controller.ts +3 -3
  371. package/src/modes/interactive-mode.ts +88 -16
  372. package/src/modes/print-mode.ts +35 -6
  373. package/src/modes/runtime-init.ts +21 -68
  374. package/src/modes/types.ts +1 -0
  375. package/src/modes/utils/injected-user-submission.ts +25 -0
  376. package/src/modes/utils/terminal-bell.ts +15 -1
  377. package/src/prompts/agent-fragments/ralplan-persistence.md +1 -1
  378. package/src/prompts/agent-fragments/restricted-bash.md +1 -1
  379. package/src/prompts/agents/critic.md +1 -1
  380. package/src/prompts/system/rlm-research.md +1 -1
  381. package/src/prompts/system/system-prompt.md +0 -2
  382. package/src/prompts/tools/bash.md +7 -1
  383. package/src/prompts/tools/browser.md +1 -1
  384. package/src/prompts/tools/cron.md +6 -6
  385. package/src/prompts/tools/read.md +0 -2
  386. package/src/prompts/tools/skill-discovery.md +4 -2
  387. package/src/runtime-mcp/client.ts +427 -23
  388. package/src/runtime-mcp/config.ts +37 -5
  389. package/src/runtime-mcp/manager-pool.test.ts +56 -0
  390. package/src/runtime-mcp/manager.ts +52 -2
  391. package/src/runtime-mcp/oauth-discovery.ts +13 -0
  392. package/src/runtime-mcp/oauth-flow.ts +46 -1
  393. package/src/runtime-mcp/pool-key.ts +5 -1
  394. package/src/runtime-mcp/pool.test.ts +9 -0
  395. package/src/runtime-mcp/protocol.test.ts +353 -0
  396. package/src/runtime-mcp/protocol.ts +624 -0
  397. package/src/runtime-mcp/scope-config.ts +78 -0
  398. package/src/runtime-mcp/tool-bridge.ts +44 -10
  399. package/src/runtime-mcp/transports/http.ts +84 -13
  400. package/src/runtime-mcp/types.ts +123 -12
  401. package/src/sdk/acp/adapter.ts +328 -97
  402. package/src/sdk/broker/broker.ts +160 -741
  403. package/src/sdk/broker/ensure.ts +9 -1
  404. package/src/sdk/broker/lifecycle.ts +510 -116
  405. package/src/sdk/broker/operation-receipt-ledger.ts +11 -2
  406. package/src/sdk/broker/process-incarnation.ts +19 -0
  407. package/src/sdk/broker/session-index.ts +516 -117
  408. package/src/sdk/broker/startup-failure.ts +28 -5
  409. package/src/sdk/broker/transport.ts +1 -13
  410. package/src/sdk/bus/chat-daemon-cli.ts +4 -2
  411. package/src/sdk/bus/chat-daemon-control.ts +67 -6
  412. package/src/sdk/bus/chat-daemon-runtime.ts +183 -859
  413. package/src/sdk/bus/config.ts +13 -0
  414. package/src/sdk/bus/conversation-store.ts +59 -171
  415. package/src/sdk/bus/daemon-paths.ts +20 -0
  416. package/src/sdk/bus/discord-conversation.ts +2 -0
  417. package/src/sdk/bus/discord-daemon.ts +524 -126
  418. package/src/sdk/bus/discord-live-provider.ts +206 -79
  419. package/src/sdk/bus/existing-thread-readiness.ts +4 -97
  420. package/src/sdk/bus/inbound-reaction-ordering.ts +122 -0
  421. package/src/sdk/bus/index.ts +2480 -456
  422. package/src/sdk/bus/kind-aware-reconciliation.ts +204 -34
  423. package/src/sdk/bus/lifecycle-commands.ts +2 -2
  424. package/src/sdk/bus/managed-daemon.ts +2 -145
  425. package/src/sdk/bus/notification-service.ts +196 -0
  426. package/src/sdk/bus/operator-runtime.ts +1 -1
  427. package/src/sdk/bus/prompt-reconciliation.ts +24 -15
  428. package/src/sdk/bus/reconciliation-store.ts +553 -24
  429. package/src/sdk/bus/session-control.ts +49 -3
  430. package/src/sdk/bus/slack-conversation.ts +4 -0
  431. package/src/sdk/bus/slack-daemon.ts +733 -134
  432. package/src/sdk/bus/slack-live-provider.ts +65 -15
  433. package/src/sdk/bus/slack-provider.ts +4 -0
  434. package/src/sdk/bus/slack-thread-binding.ts +9 -104
  435. package/src/sdk/bus/telegram-adoption-intent.ts +299 -209
  436. package/src/sdk/bus/telegram-daemon-cli.ts +194 -7
  437. package/src/sdk/bus/telegram-daemon-contract.ts +265 -19
  438. package/src/sdk/bus/telegram-daemon-control.ts +3 -13
  439. package/src/sdk/bus/telegram-daemon-orphan-reap.ts +498 -0
  440. package/src/sdk/bus/telegram-daemon-owner-registry.ts +176 -0
  441. package/src/sdk/bus/telegram-daemon.ts +2552 -2280
  442. package/src/sdk/bus/telegram-reference.ts +1 -226
  443. package/src/sdk/bus/telegram-setup.ts +2 -2
  444. package/src/sdk/bus/threaded-render.ts +1 -0
  445. package/src/sdk/bus/topic-registry.ts +279 -146
  446. package/src/sdk/cli/rows.ts +2 -35
  447. package/src/sdk/cli/session-cli.ts +776 -700
  448. package/src/sdk/client/client.ts +892 -1
  449. package/src/sdk/host/control/dispatch.ts +93 -58
  450. package/src/sdk/host/control/index.ts +1 -1
  451. package/src/sdk/host/control/operations.ts +18 -10
  452. package/src/sdk/host/control/runtime-gate.ts +40 -0
  453. package/src/sdk/host/host.ts +63 -12
  454. package/src/sdk/host/query/handlers.ts +66 -7
  455. package/src/sdk/host/reverse-leases.ts +2 -2
  456. package/src/sdk/host/sdk-surface-parity.test.ts +1 -1
  457. package/src/sdk/host/session-runtime.test.ts +2787 -34
  458. package/src/sdk/host/session-runtime.ts +1489 -101
  459. package/src/sdk/host/surface-policy.ts +12 -8
  460. package/src/sdk/host/types.ts +1 -1
  461. package/src/sdk/host/websocket-transport.lifecycle.test.ts +31 -59
  462. package/src/sdk/host/websocket-transport.ts +85 -50
  463. package/src/sdk/index.ts +2 -1
  464. package/src/sdk/lifecycle/broker-client.ts +53 -0
  465. package/src/sdk/lifecycle/client.ts +234 -0
  466. package/src/sdk/lifecycle/index.ts +3 -0
  467. package/src/sdk/{bus/recent-activity.ts → lifecycle/recent-sessions.ts} +5 -2
  468. package/src/sdk/lifecycle/service.ts +720 -0
  469. package/src/sdk/mcp/server.ts +226 -188
  470. package/src/sdk/prompt-deadline-lease.ts +55 -0
  471. package/src/sdk/prompt-deadline-manager.ts +148 -0
  472. package/src/sdk/prompt-status.ts +11 -3
  473. package/src/sdk/protocol/operation-inventory.generated.json +272 -129
  474. package/src/sdk/protocol/operation-registry.ts +71 -8
  475. package/src/sdk/receipt-state.ts +43 -0
  476. package/src/sdk/reconciliation-extensions.ts +14 -0
  477. package/src/sdk/router/index.ts +1 -0
  478. package/src/sdk/router/session-router.ts +1658 -0
  479. package/src/sdk/session-list.ts +93 -0
  480. package/src/sdk/session-reconnect.ts +45 -1
  481. package/src/sdk/session.ts +716 -120
  482. package/src/sdk/transport/serve-cli.ts +18 -34
  483. package/src/session/account-inventory.ts +603 -0
  484. package/src/session/agent-session.ts +2857 -582
  485. package/src/session/agent-storage.ts +35 -0
  486. package/src/session/blob-store.ts +32 -1
  487. package/src/session/coordinator-tool-label.ts +40 -0
  488. package/src/session/internal/managed-session-scope.ts +4 -0
  489. package/src/session/internal/managed-session-storage.ts +173 -12
  490. package/src/session/messages.ts +1 -1
  491. package/src/session/secure-token-file.ts +80 -0
  492. package/src/session/session-manager.ts +730 -58
  493. package/src/session/session-storage.ts +25 -16
  494. package/src/session/startup-auth-config.ts +297 -0
  495. package/src/session/terminal-abort.ts +1253 -0
  496. package/src/session/yield-queue.ts +57 -15
  497. package/src/session-import/claude.ts +334 -0
  498. package/src/session-import/codex.ts +1033 -0
  499. package/src/session-import/command.ts +77 -0
  500. package/src/session-import/detect.ts +133 -0
  501. package/src/session-import/index.ts +7 -0
  502. package/src/session-import/provider-codex.ts +327 -0
  503. package/src/session-import/provider-service.ts +457 -0
  504. package/src/session-import/redact.ts +128 -0
  505. package/src/session-import/service.ts +1264 -0
  506. package/src/session-import/types.ts +148 -0
  507. package/src/setup/paseo/check.ts +213 -0
  508. package/src/setup/paseo/install-saga.ts +293 -0
  509. package/src/setup/paseo/json-publisher.ts +263 -0
  510. package/src/setup/paseo/orchestration-preferences.ts +86 -0
  511. package/src/setup/paseo/paseo-ownership.ts +224 -0
  512. package/src/setup/paseo/paseo-setup.ts +253 -0
  513. package/src/setup/paseo/provider-config.ts +139 -0
  514. package/src/setup/paseo/remove.ts +183 -0
  515. package/src/setup/paseo/result-types.ts +79 -0
  516. package/src/setup/paseo/setup-deps.ts +175 -0
  517. package/src/setup/paseo/skills-bridge.ts +260 -0
  518. package/src/skill-state/active-state.ts +21 -11
  519. package/src/slash-commands/acp-builtins.ts +9 -1
  520. package/src/slash-commands/builtin-registry.ts +98 -16
  521. package/src/slash-commands/helpers/credential-switch.ts +143 -0
  522. package/src/slash-commands/helpers/mcp.ts +9 -3
  523. package/src/slash-commands/helpers/usage-report.ts +61 -56
  524. package/src/slash-commands/types.ts +7 -2
  525. package/src/task/executor.ts +30 -11
  526. package/src/task/index.ts +66 -7
  527. package/src/task/render.ts +23 -9
  528. package/src/tools/ask-answer-registry.ts +23 -7
  529. package/src/tools/ask.ts +8 -4
  530. package/src/tools/bash-sleep-advisory.ts +115 -0
  531. package/src/tools/bash.ts +98 -18
  532. package/src/tools/browser/attach.ts +71 -3
  533. package/src/tools/browser/launch.ts +86 -13
  534. package/src/tools/browser/profile-discovery.ts +57 -7
  535. package/src/tools/browser/render.ts +8 -1
  536. package/src/tools/browser.ts +130 -21
  537. package/src/tools/computer.enforcement.test.ts +1 -1
  538. package/src/tools/computer.ts +3 -2
  539. package/src/tools/cron.ts +12 -1
  540. package/src/tools/image-gen.ts +27 -16
  541. package/src/tools/index.ts +8 -0
  542. package/src/tools/job.ts +33 -6
  543. package/src/tools/monitor.ts +41 -2
  544. package/src/tools/skill-discovery.ts +21 -29
  545. package/src/tools/subagent.ts +142 -24
  546. package/src/tools/tool-catalog.generated.ts +6 -6
  547. package/src/tools/ultragoal-ask-guard.ts +10 -3
  548. package/src/utils/herdr-pane.ts +345 -0
  549. package/src/utils/title-generator.ts +2 -0
  550. package/src/web/scrapers/index.ts +6 -4
  551. package/dist/types/cli/malloc-env-guard.test.d.ts +0 -1
  552. package/dist/types/defaults/gjc-defaults.test.d.ts +0 -1
  553. package/dist/types/memory-backend/lazy-loading.test.d.ts +0 -1
  554. package/dist/types/runtime/boot-ordering.test.d.ts +0 -1
  555. package/dist/types/runtime/lazy-service.test.d.ts +0 -1
  556. package/dist/types/runtime/network-prewarm-service.test.d.ts +0 -1
  557. package/dist/types/runtime/workspace-tree-service.test.d.ts +0 -1
  558. package/dist/types/runtime-mcp/config.test.d.ts +0 -1
  559. package/dist/types/runtime-mcp/http.test.d.ts +0 -1
  560. package/dist/types/runtime-mcp/manager-pool.test.d.ts +0 -1
  561. package/dist/types/runtime-mcp/pool-key.test.d.ts +0 -1
  562. package/dist/types/runtime-mcp/pool.test.d.ts +0 -1
  563. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +0 -117
  564. package/dist/types/sdk/bus/lifecycle-orchestrator.d.ts +0 -141
  565. package/dist/types/sdk/bus/telegram-cli.d.ts +0 -31
  566. package/dist/types/sdk/elevation/allowlist.d.ts +0 -20
  567. package/dist/types/sdk/elevation/audit.d.ts +0 -93
  568. package/dist/types/sdk/elevation/capability.d.ts +0 -3
  569. package/dist/types/sdk/elevation/digest.d.ts +0 -72
  570. package/dist/types/sdk/elevation/dispatch-receipt.d.ts +0 -85
  571. package/dist/types/sdk/elevation/grant-ledger.d.ts +0 -163
  572. package/dist/types/sdk/elevation/owner.d.ts +0 -48
  573. package/dist/types/sdk/host/import-graph.test.d.ts +0 -1
  574. package/dist/types/sdk/host/sdk-surface-parity.test.d.ts +0 -1
  575. package/dist/types/sdk/host/session-runtime.test.d.ts +0 -1
  576. package/dist/types/sdk/host/websocket-transport.lifecycle.test.d.ts +0 -1
  577. package/dist/types/session/auth-broker-config.d.ts +0 -13
  578. package/dist/types/session/heap-eviction-artifacts.test.d.ts +0 -1
  579. package/dist/types/skills/skills.test.d.ts +0 -1
  580. package/dist/types/slash-commands/notify-command.test.d.ts +0 -1
  581. package/dist/types/task/token-log.test.d.ts +0 -1
  582. package/dist/types/tools/computer.enforcement.test.d.ts +0 -1
  583. package/dist/types/tools/descriptors.test.d.ts +0 -1
  584. package/scripts/g005-daemon-smoke.ts +0 -180
  585. package/scripts/g011-daemon-path-smoke.ts +0 -153
  586. package/src/sdk/bus/lifecycle-control-runtime.ts +0 -1733
  587. package/src/sdk/bus/lifecycle-orchestrator.ts +0 -489
  588. package/src/sdk/bus/telegram-cli.ts +0 -212
  589. package/src/sdk/elevation/allowlist.ts +0 -54
  590. package/src/sdk/elevation/audit.ts +0 -208
  591. package/src/sdk/elevation/capability.ts +0 -41
  592. package/src/sdk/elevation/digest.ts +0 -129
  593. package/src/sdk/elevation/dispatch-receipt.ts +0 -184
  594. package/src/sdk/elevation/grant-ledger.ts +0 -1142
  595. package/src/sdk/elevation/owner.ts +0 -109
  596. package/src/session/auth-broker-config.ts +0 -109
  597. /package/dist/types/{ai-core-import-gate.test.d.ts → discovery/claude-hooks.d.ts} +0 -0
  598. /package/dist/types/{cli/fixture-report.test.d.ts → discovery/codex-hooks.d.ts} +0 -0
  599. /package/dist/types/sdk/{bus/recent-activity.d.ts → lifecycle/recent-sessions.d.ts} +0 -0
@@ -7,6 +7,7 @@ import {
7
7
  type AuthenticateResponse,
8
8
  type AuthMethod,
9
9
  type AvailableCommand,
10
+ type CancelNotification,
10
11
  type ClientCapabilities,
11
12
  type CloseSessionRequest,
12
13
  type CloseSessionResponse,
@@ -49,9 +50,13 @@ import { resolveAcpFinalText } from "../../sdk/acp/final-text";
49
50
  import { ACP_MCP_LIFECYCLE_TIMEOUT_MS, type SessionLifecycleMcpServer } from "../../sdk/acp/mcp";
50
51
  import { ensureBroker } from "../../sdk/broker/ensure";
51
52
  import { readSdkBrokerDiscovery, SdkClient, SdkClientError } from "../../sdk/client";
53
+ import type { AbortScope } from "../../sdk/host/control/operations";
52
54
  import { SYNTHETIC_PROVIDER_ID } from "../../sdk/model-profile-namespace";
53
55
  import type { SdkPromptTerminalOutcome } from "../../sdk/prompt-status";
54
56
  import { PromptActivity, type PromptWatchdogClock, systemPromptWatchdogClock } from "../../sdk/prompt-watchdog";
57
+ import { type SessionAttachment, SessionRouter, type SessionRouterFrame } from "../../sdk/router";
58
+ import { SessionListTraversalError, sessionListPageFromResponse, traverseSessionList } from "../../sdk/session-list";
59
+ import { resolveAcpAbortScope } from "./abort-scope";
55
60
  import {
56
61
  buildToolCallStartUpdate,
57
62
  mapAgentSessionEventToAcpSessionUpdates,
@@ -70,7 +75,6 @@ const ACP_CUSTOM_MODEL_PRESET = "__custom__";
70
75
  const THINKING_CONFIG_ID = "thinking";
71
76
  const SESSION_PAGE_SIZE = 50;
72
77
  const MAX_ACP_REPLAY_PAGES = 10_000;
73
- const MAX_ACP_SESSION_LIST_PAGES = 10_000;
74
78
  /** Bounded retention of settled prompt correlations so late duplicates stay closed. */
75
79
  const SETTLED_PROMPT_CORRELATION_RETENTION = 16;
76
80
  /**
@@ -117,6 +121,19 @@ interface PromptWaiter {
117
121
  cancelWatchdog?: () => void;
118
122
  /** What the host is observably doing — a tool running, a model call unanswered — and the bound that follows from it. */
119
123
  activity: PromptActivity;
124
+ /** Coordinates a prompt-control rejection racing an acknowledged ACP cancellation. */
125
+ cancelAttempt?: Promise<boolean>;
126
+ /** Whether ANY overlapping cancel attempt for this prompt was acknowledged:
127
+ * a later failed attempt must not erase an earlier success (review thread P2). */
128
+ cancelAcknowledged?: boolean;
129
+ /** In-flight cancel attempts for this prompt: cancellation intent must stay
130
+ * set while any attempt can still acknowledge, so a failure only clears
131
+ * the shared flag when no attempt remains pending (review thread P2). */
132
+ pendingCancelAttempts?: number;
133
+ /** Shared resolver for cancelAttempt: ANY successful attempt resolves it
134
+ * immediately (no request-order serialization), and the LAST failing
135
+ * attempt resolves false (review thread P2). */
136
+ cancelAttemptResolve?: (acknowledged: boolean) => void;
120
137
  resolve: (response: PromptResponse) => void;
121
138
  reject: (error: Error) => void;
122
139
  }
@@ -129,6 +146,7 @@ type PendingAttachment = { epoch: number; task: Promise<void> };
129
146
  type SessionRecord = {
130
147
  cwd: string;
131
148
  adapter: AcpSdkAdapter;
149
+ attachment: SessionAttachment;
132
150
  closeIdempotencyKey: string;
133
151
  unsubscribe: () => void;
134
152
  reconnectUnsubscribe: () => void;
@@ -156,13 +174,10 @@ function promptWaiterRetired(record: SessionRecord, waiter: PromptWaiter): boole
156
174
  return waiter.settled || record.activePrompt !== waiter;
157
175
  }
158
176
 
159
- type Endpoint = { url: string; token: string };
160
-
161
177
  type BrokerSession = {
162
178
  sessionId: string;
163
179
  locator?: { repo?: string };
164
180
  live?: boolean;
165
- terminal?: boolean;
166
181
  endpointGeneration?: number;
167
182
  endpointMtimeMs?: number;
168
183
  title?: string;
@@ -196,40 +211,44 @@ function object(value: unknown): JsonObject | undefined {
196
211
  return value !== null && typeof value === "object" && !Array.isArray(value) ? (value as JsonObject) : undefined;
197
212
  }
198
213
 
214
+ function isAcpUnavailableSlashCommand(text: string): boolean {
215
+ return /^\/import-session(?:\s|:|$)/u.test(text);
216
+ }
217
+
199
218
  async function collectAcpSessionList(
200
219
  request: (input: JsonObject) => Promise<unknown>,
201
220
  input: JsonObject = {},
202
221
  ): Promise<JsonObject> {
203
- const aggregate: JsonObject = {};
204
- const sessions: unknown[] = [];
205
- let cursor: string | undefined;
206
- for (let pageCount = 0; pageCount < MAX_ACP_SESSION_LIST_PAGES; pageCount++) {
207
- const response = object(await request({ ...input, ...(cursor === undefined ? {} : { cursor }) }));
208
- const listing = object(response?.result) ?? response;
209
- if (response?.ok === false) {
210
- const failure = object(response.error);
211
- throw new AcpSdkAdapterError(
212
- typeof failure?.code === "string" ? failure.code : "broker_error",
213
- typeof failure?.message === "string" ? failure.message : "session.list failed",
214
- );
215
- }
216
- if (listing) {
217
- for (const [key, value] of Object.entries(listing)) {
222
+ try {
223
+ const pages = await traverseSessionList(
224
+ input,
225
+ async pageInput => {
226
+ const response = await request(pageInput);
227
+ const envelope = object(response);
228
+ if (envelope?.ok === false) {
229
+ const failure = object(envelope.error);
230
+ throw new AcpSdkAdapterError(
231
+ typeof failure?.code === "string" ? failure.code : "broker_error",
232
+ typeof failure?.message === "string" ? failure.message : "session.list failed",
233
+ );
234
+ }
235
+ return response;
236
+ },
237
+ response => sessionListPageFromResponse(response),
238
+ );
239
+ const aggregate: JsonObject = {};
240
+ const sessions: unknown[] = [];
241
+ for (const { page } of pages) {
242
+ for (const [key, value] of Object.entries(page)) {
218
243
  if (key !== "sessions" && key !== "continuationCursor") aggregate[key] = value;
219
244
  }
220
- if (Array.isArray(listing.sessions)) sessions.push(...listing.sessions);
221
- const nextCursor =
222
- typeof listing.continuationCursor === "string" && listing.continuationCursor.length > 0
223
- ? listing.continuationCursor
224
- : undefined;
225
- if (nextCursor) {
226
- cursor = nextCursor;
227
- continue;
228
- }
245
+ sessions.push(...page.sessions);
229
246
  }
230
247
  return { ...aggregate, sessions };
248
+ } catch (error) {
249
+ if (error instanceof SessionListTraversalError) throw new AcpSdkAdapterError("protocol_error", error.message);
250
+ throw error;
231
251
  }
232
- throw new AcpSdkAdapterError("protocol_error", "session.list exceeded the page budget.");
233
252
  }
234
253
 
235
254
  function aggregateAcpFailure(code: string, message: string, failures: unknown[]): AcpSdkAdapterError {
@@ -253,11 +272,7 @@ export function paginateAcpSessions(
253
272
  (value): value is BrokerSession & { locator: { repo: string } } =>
254
273
  typeof value?.sessionId === "string" && typeof value.locator?.repo === "string",
255
274
  )
256
- .filter(value => !cwd || value.locator.repo === cwd)
257
- // DR-1 retains closed/unregistered rows in the broker index for inspect
258
- // and offline tail; ACP's session/list advertises only loadable sessions,
259
- // so terminal rows are excluded here (matching the pre-DR-1 wire shape).
260
- .filter(value => value.terminal !== true);
275
+ .filter(value => !cwd || value.locator.repo === cwd);
261
276
  const sessions = filtered.slice(offset, offset + SESSION_PAGE_SIZE).map(value => {
262
277
  const metadata = sessionMetadata.get(value.sessionId);
263
278
  const updatedAt =
@@ -286,15 +301,6 @@ export function paginateAcpSessions(
286
301
  };
287
302
  }
288
303
 
289
- function endpoint(value: unknown): Endpoint {
290
- const candidate = object(value);
291
- const result = object(candidate?.result) ?? candidate;
292
- const nested = object(result?.endpoint) ?? result;
293
- if (typeof nested?.url !== "string" || typeof nested.token !== "string")
294
- throw new AcpSdkAdapterError("unavailable", "SDK lifecycle response omitted a session endpoint.");
295
- return { url: nested.url, token: nested.token };
296
- }
297
-
298
304
  function sessionId(value: unknown): string {
299
305
  const candidate = object(value);
300
306
  const result = object(candidate?.result) ?? candidate;
@@ -383,6 +389,26 @@ function promptAcknowledgement(value: unknown): PromptCorrelation | undefined {
383
389
  if (typeof payload.turnId !== "string" || payload.turnId.trim().length === 0) return undefined;
384
390
  return { commandId: payload.commandId, turnId: payload.turnId };
385
391
  }
392
+
393
+ /**
394
+ * A `session/cancel` is acknowledged ONLY by a matching-scope C04 `stopped`
395
+ * disposition (`{ok:true, turn:"stopped", selection: <requested scope>, ...}`)
396
+ * or by the legacy `{aborted:true}` plain-abort ack from a broker that predates
397
+ * terminal mode. The no-effect dispositions (`no_active_turn`, `no_effect`,
398
+ * `no_store`) and `uncertain` explicitly provide NO proof the worker was
399
+ * stopped — the agent turn may keep running and executing tools — so accepting
400
+ * them would settle the ACP prompt as cancelled against a live worker (review
401
+ * thread P1). `no_active_turn` can also be a requester-ownership no-op after an
402
+ * SDK reconnect. Anything else means the SDK did not confirm the stop and the
403
+ * client must see an error rather than a settled turn.
404
+ */
405
+ function isAbortAcknowledged(value: unknown, scope: AbortScope): boolean {
406
+ const candidate = object(value);
407
+ const result = object(candidate?.result) ?? candidate;
408
+ if (result === undefined) return false;
409
+ if (result.aborted === true) return true;
410
+ return result.ok === true && result.turn === "stopped" && result.selection === scope;
411
+ }
386
412
  function strictCorrelationFrom(...values: unknown[]): PromptCorrelation | undefined {
387
413
  const correlation: PromptCorrelation = {};
388
414
  let malformed = false;
@@ -602,6 +628,36 @@ function receivedSdkEvent(frame: JsonObject): ReceivedSdkEvent | undefined {
602
628
  };
603
629
  }
604
630
 
631
+ const ROUTER_PASSTHROUGH_FRAME_TYPES = new Set([
632
+ "hello",
633
+ "server_hello",
634
+ "reverse_request",
635
+ "reverse_cancel",
636
+ "reverse_request_cancel",
637
+ "reverse_request_cancelled",
638
+ ]);
639
+
640
+ function acpFrameFromRouted(frame: SessionRouterFrame): JsonObject {
641
+ if (
642
+ frame.body.type === "activity" ||
643
+ frame.body.type === "agent_start" ||
644
+ frame.body.type === "agent_end" ||
645
+ frame.body.type === "agent_failed"
646
+ )
647
+ return frame.body;
648
+ if (typeof frame.body.type === "string" && ROUTER_PASSTHROUGH_FRAME_TYPES.has(frame.body.type)) return frame.body;
649
+ const connectionId = typeof frame.body.connectionId === "string" ? frame.body.connectionId : undefined;
650
+ return {
651
+ type: "event",
652
+ ...(frame.name === undefined ? {} : { kind: frame.name }),
653
+ ...(frame.sessionId === undefined ? {} : { sessionId: frame.sessionId }),
654
+ ...(frame.commandId === undefined ? {} : { commandId: frame.commandId }),
655
+ ...(frame.turnId === undefined ? {} : { turnId: frame.turnId }),
656
+ ...(connectionId === undefined ? {} : { connectionId }),
657
+ payload: frame.body,
658
+ };
659
+ }
660
+
605
661
  /**
606
662
  * Author of the message a `message_start`/`message_end` frame carries. The host echoes the
607
663
  * user prompt and every tool result back through the same events, so only `"assistant"`
@@ -864,6 +920,14 @@ export function acpSessionStateFromConfig(
864
920
  };
865
921
  }
866
922
 
923
+ /** Recognize a canonical ACP skill command only when it is the complete, single text prompt. */
924
+ export function acpSkillInvocation(blocks: PromptRequest["prompt"]): { name: string; args: string } | undefined {
925
+ if (blocks.length !== 1 || blocks[0]?.type !== "text") return undefined;
926
+ const match = /^\/skill:([^\s]+)(?:\s+([\s\S]*))?$/.exec(blocks[0].text.trim());
927
+ if (!match?.[1]) return undefined;
928
+ return { name: match[1], args: match[2]?.trim() ?? "" };
929
+ }
930
+
867
931
  /** Convert every ACP prompt block the agent advertises without silently discarding context. */
868
932
  export function acpPromptPayload(blocks: PromptRequest["prompt"]): {
869
933
  text: string;
@@ -1061,10 +1125,15 @@ export async function applyAcpStartupOptions(
1061
1125
  export class AcpAgent implements Agent {
1062
1126
  readonly #connection: AgentSideConnection;
1063
1127
  readonly #agentDir: string;
1128
+ readonly #router: SessionRouter;
1129
+ readonly #pendingRouterAdapters = new Map<string, AcpSdkAdapter>();
1130
+ readonly #pendingRouterFrames = new Map<string, Record<string, unknown>[]>();
1131
+ #routerStartPromise: Promise<void> | undefined;
1064
1132
  readonly #sessions = new Map<string, SessionRecord>();
1065
1133
  readonly #attaching = new Map<string, PendingAttachment>();
1066
1134
  readonly #resolvingExisting = new Map<string, PendingAttachment>();
1067
1135
  readonly #knownSessionCwds = new Map<string, string>();
1136
+ readonly #knownSessionMcpServers = new Map<string, SessionLifecycleMcpServer[]>();
1068
1137
  readonly #knownSessionMetadata = new Map<string, { title?: string; updatedAt?: string }>();
1069
1138
  readonly #pendingDeleteLocators = new Map<string, { cwd: string; path: string }>();
1070
1139
  readonly #pendingCloseIdempotencyKeys = new Map<string, string>();
@@ -1093,6 +1162,39 @@ export class AcpAgent implements Agent {
1093
1162
  this.#connection = connection;
1094
1163
  const candidate = object(options);
1095
1164
  this.#agentDir = typeof candidate?.agentDir === "string" ? candidate.agentDir : getAgentDir();
1165
+ this.#router = new SessionRouter({
1166
+ agentDir: this.#agentDir,
1167
+ deps: {
1168
+ onAttachment: attachment => {
1169
+ const record = this.#sessions.get(attachment.sessionId);
1170
+ const adapter = record?.adapter ?? this.#pendingRouterAdapters.get(attachment.sessionId);
1171
+ if (!adapter) return;
1172
+ if (record) record.attachment = attachment;
1173
+ adapter.acceptAttachment(attachment);
1174
+ },
1175
+ onAttachmentReady: async attachment => {
1176
+ const record = this.#sessions.get(attachment.sessionId);
1177
+ const adapter = record?.adapter ?? this.#pendingRouterAdapters.get(attachment.sessionId);
1178
+ if (!adapter) return;
1179
+ if (record) record.attachment = attachment;
1180
+ await adapter.attachmentReady(attachment);
1181
+ },
1182
+ onFrame: (attachment, frame) => {
1183
+ const acpFrame = acpFrameFromRouted(frame);
1184
+ const adapter =
1185
+ this.#sessions.get(attachment.sessionId)?.adapter ??
1186
+ this.#pendingRouterAdapters.get(attachment.sessionId);
1187
+ if (adapter) adapter.acceptFrame(acpFrame);
1188
+ else this.#pendingRouterFrames.get(attachment.sessionId)?.push(acpFrame);
1189
+ },
1190
+ onSessionRemoved: attachment => {
1191
+ const adapter =
1192
+ this.#sessions.get(attachment.sessionId)?.adapter ??
1193
+ this.#pendingRouterAdapters.get(attachment.sessionId);
1194
+ adapter?.revokeAttachment(attachment);
1195
+ },
1196
+ },
1197
+ });
1096
1198
  this.#startupOptions = parseAcpStartupOptions(candidate?.startupOptions);
1097
1199
  this.#cancelSettlementGraceMs =
1098
1200
  typeof candidate?.cancelSettlementGraceMs === "number" &&
@@ -1174,8 +1276,9 @@ export class AcpAgent implements Agent {
1174
1276
  );
1175
1277
  const id = sessionId(result);
1176
1278
  this.#knownSessionCwds.set(id, params.cwd);
1279
+ this.#knownSessionMcpServers.set(id, mcpServers);
1177
1280
  try {
1178
- await this.#attach(id, params.cwd, endpoint(result));
1281
+ await this.#attach(id, params.cwd, undefined, result);
1179
1282
  await applyAcpStartupOptions(this.#adapter(id), this.#startupOptions);
1180
1283
  const response = { sessionId: id, ...(await this.#sessionState(id, true)) };
1181
1284
  this.#scheduleBootstrap(id);
@@ -1190,6 +1293,7 @@ export class AcpAgent implements Agent {
1190
1293
  const mcpServers = this.#mcpServers(params);
1191
1294
  this.#assertAbsoluteCwd(params.cwd);
1192
1295
  this.#assertNoAdditionalDirectories(params.additionalDirectories);
1296
+ if (mcpServers.length > 0) this.#knownSessionMcpServers.set(params.sessionId, mcpServers);
1193
1297
  await this.#attachExisting(params.sessionId, params.cwd, mcpServers);
1194
1298
  await this.#replaySession(params.sessionId);
1195
1299
  const response = await this.#sessionState(params.sessionId);
@@ -1201,6 +1305,7 @@ export class AcpAgent implements Agent {
1201
1305
  const mcpServers = this.#mcpServers(params);
1202
1306
  this.#assertAbsoluteCwd(params.cwd);
1203
1307
  this.#assertNoAdditionalDirectories(params.additionalDirectories);
1308
+ if (mcpServers.length > 0) this.#knownSessionMcpServers.set(params.sessionId, mcpServers);
1204
1309
  await this.#attachExisting(params.sessionId, params.cwd, mcpServers);
1205
1310
  const response = await this.#sessionState(params.sessionId);
1206
1311
  this.#scheduleBootstrap(params.sessionId);
@@ -1226,8 +1331,9 @@ export class AcpAgent implements Agent {
1226
1331
  );
1227
1332
  const id = sessionId(result);
1228
1333
  this.#knownSessionCwds.set(id, params.cwd);
1334
+ this.#knownSessionMcpServers.set(id, mcpServers);
1229
1335
  try {
1230
- await this.#attach(id, params.cwd, endpoint(result));
1336
+ await this.#attach(id, params.cwd, undefined, result);
1231
1337
  const response = { sessionId: id, ...(await this.#sessionState(id)) };
1232
1338
  this.#scheduleBootstrap(id);
1233
1339
  return response;
@@ -1250,10 +1356,7 @@ export class AcpAgent implements Agent {
1250
1356
  if (
1251
1357
  typeof candidate?.sessionId !== "string" ||
1252
1358
  typeof candidate.locator?.repo !== "string" ||
1253
- path.resolve(candidate.locator.repo) !== path.resolve(params.cwd) ||
1254
- // A DR-1 terminal row is not a loadable session: adopting its cwd
1255
- // here would re-arm teardown authority for a host that is gone.
1256
- candidate.terminal === true
1359
+ path.resolve(candidate.locator.repo) !== path.resolve(params.cwd)
1257
1360
  )
1258
1361
  continue;
1259
1362
  if (discovered.has(candidate.sessionId))
@@ -1309,7 +1412,11 @@ export class AcpAgent implements Agent {
1309
1412
  }
1310
1413
  this.#beginTeardown(params.sessionId);
1311
1414
  try {
1312
- await this.#teardownSession(params.sessionId, "deleted", true);
1415
+ // A retained delete locator proves the prior attempt already completed
1416
+ // connection/process teardown and reached durable artifact cleanup. Re-closing
1417
+ // that terminal session can only replace the authoritative cleanup_pending
1418
+ // result with unrelated close uncertainty, so retries resume deletion directly.
1419
+ await this.#teardownSession(params.sessionId, "deleted", pendingLocator === undefined || record !== undefined);
1313
1420
  let saved = pendingLocator?.cwd === cwd ? pendingLocator.path : undefined;
1314
1421
  if (!saved) {
1315
1422
  try {
@@ -1317,6 +1424,7 @@ export class AcpAgent implements Agent {
1317
1424
  } catch (error) {
1318
1425
  if (error instanceof AcpSdkAdapterError && error.code === "not_found") {
1319
1426
  this.#knownSessionCwds.delete(params.sessionId);
1427
+ this.#knownSessionMcpServers.delete(params.sessionId);
1320
1428
  this.#knownSessionMetadata.delete(params.sessionId);
1321
1429
  return {};
1322
1430
  }
@@ -1330,6 +1438,7 @@ export class AcpAgent implements Agent {
1330
1438
  this.#lifecycleIdempotencyKey(params.sessionId, "session.delete"),
1331
1439
  );
1332
1440
  this.#knownSessionCwds.delete(params.sessionId);
1441
+ this.#knownSessionMcpServers.delete(params.sessionId);
1333
1442
  this.#knownSessionMetadata.delete(params.sessionId);
1334
1443
  this.#pendingDeleteLocators.delete(params.sessionId);
1335
1444
  return {};
@@ -1392,6 +1501,21 @@ export class AcpAgent implements Agent {
1392
1501
  // A new turn starts uncancelled; a stale flag must never settle it as `cancelled`.
1393
1502
  record.cancelRequested = false;
1394
1503
  const payload = acpPromptPayload(params.prompt);
1504
+ const skillInvocation = acpSkillInvocation(params.prompt);
1505
+ if (isAcpUnavailableSlashCommand(payload.text)) {
1506
+ await this.#publishSessionUpdate(
1507
+ params.sessionId,
1508
+ {
1509
+ sessionId: params.sessionId,
1510
+ update: {
1511
+ sessionUpdate: "agent_message_chunk",
1512
+ content: { type: "text", text: "Slash command /import-session is unavailable over ACP." },
1513
+ },
1514
+ },
1515
+ record.adapter,
1516
+ );
1517
+ return { stopReason: "end_turn" };
1518
+ }
1395
1519
  // The SDK transport hard-caps a single request frame at 256 KiB and answers an
1396
1520
  // oversize frame by closing the socket (CloseCode::Size, crates/gjc-sdk/src/server.rs),
1397
1521
  // which surfaces to the client as an opaque `connection_closed` mid-turn. Reject
@@ -1402,8 +1526,11 @@ export class AcpAgent implements Agent {
1402
1526
  const promptFrameBytes = Buffer.byteLength(
1403
1527
  JSON.stringify({
1404
1528
  type: "control_request",
1405
- operation: "turn.prompt",
1406
- input: { text: payload.text, images: payload.images },
1529
+ operation: skillInvocation ? "skill.invoke" : "turn.prompt",
1530
+ input: skillInvocation ?? { text: payload.text, images: payload.images },
1531
+ // AcpSdkAdapter.control passes `confirm: false` to SdkClient, which serializes
1532
+ // the field even though it is not part of the skill input payload.
1533
+ ...(skillInvocation ? { confirm: false } : {}),
1407
1534
  id: PROMPT_FRAME_ID_PLACEHOLDER,
1408
1535
  }),
1409
1536
  );
@@ -1457,10 +1584,12 @@ export class AcpAgent implements Agent {
1457
1584
  );
1458
1585
  }
1459
1586
  const acknowledgementTask = (async (): Promise<PromptResponse> => {
1460
- const acknowledgement = await record.adapter.prompt({
1461
- text: payload.text,
1462
- ...(payload.images.length ? { images: payload.images } : {}),
1463
- });
1587
+ const acknowledgement = skillInvocation
1588
+ ? await record.adapter.control("skill.invoke", skillInvocation)
1589
+ : await record.adapter.prompt({
1590
+ text: payload.text,
1591
+ ...(payload.images.length ? { images: payload.images } : {}),
1592
+ });
1464
1593
  const acknowledgementCorrelation = promptAcknowledgement(acknowledgement);
1465
1594
  if (!acknowledgementCorrelation)
1466
1595
  throw new AcpSdkAdapterError(
@@ -1546,12 +1675,21 @@ export class AcpAgent implements Agent {
1546
1675
  try {
1547
1676
  return await Promise.race([response, acknowledgementTask]);
1548
1677
  } catch (error) {
1678
+ if (waiter.cancelAttempt && (await waiter.cancelAttempt) && waiter.settled) return await response;
1549
1679
  waiter.deferredFrames.length = 0;
1550
1680
  waiter.deferredActivityFrames.length = 0;
1551
1681
  clearPromptWatchdog(waiter);
1552
1682
  waiter.terminal = undefined;
1553
1683
  waiter.settled = true;
1554
1684
  if (record.activePrompt === waiter) record.activePrompt = undefined;
1685
+ // Keep a late terminal for this (cancelled) turn closed, exactly like the
1686
+ // other settlement paths, so an aborted run's trailing frame can never
1687
+ // publish over a later prompt that reuses the identity.
1688
+ if (hasCompleteCorrelation(waiter.correlation)) {
1689
+ record.settledPromptCorrelations.push(waiter.correlation);
1690
+ while (record.settledPromptCorrelations.length > SETTLED_PROMPT_CORRELATION_RETENTION)
1691
+ record.settledPromptCorrelations.shift();
1692
+ }
1555
1693
  // A prompt cancelled before the SDK acknowledged it still ends this turn by
1556
1694
  // client request, and ACP is explicit: "Agents MUST catch these errors and
1557
1695
  // return the semantically meaningful `cancelled` stop reason, so that Clients
@@ -1559,28 +1697,110 @@ export class AcpAgent implements Agent {
1559
1697
  // rejection instead would show the user a spurious error for their own cancel.
1560
1698
  if (record.cancelRequested) {
1561
1699
  record.cancelRequested = false;
1700
+ // The client's turn is settled by the return; the advisory idle publication
1701
+ // must not gate it and must still be attempted so the running phase is
1702
+ // released (gjcRunning:false) instead of spinning behind a settled cancel.
1703
+ void this.#publishPromptPhaseIdle(params.sessionId, record.adapter);
1562
1704
  return { stopReason: "cancelled" };
1563
1705
  }
1564
1706
  throw error;
1565
1707
  }
1566
1708
  }
1567
1709
 
1568
- async cancel(params: { sessionId: string }): Promise<void> {
1710
+ async cancel(params: CancelNotification): Promise<void> {
1569
1711
  const record = this.#sessions.get(params.sessionId);
1570
1712
  if (!record) throw new AcpSdkAdapterError("not_found", `Unknown session, not found: ${params.sessionId}`);
1571
1713
  // Record the client's intent before awaiting the SDK so a prompt that rejects
1572
1714
  // mid-cancel (e.g. preflight `busy`) can still settle as `cancelled`.
1573
1715
  record.cancelRequested = true;
1574
- const acknowledgement = await record.adapter.cancel();
1575
- const result = object(object(acknowledgement)?.result) ?? object(acknowledgement);
1576
- if (result?.aborted !== true)
1577
- throw new AcpSdkAdapterError(
1578
- "abort_unacknowledged",
1579
- "SDK did not acknowledge cancellation of the active prompt.",
1580
- );
1581
- // The acknowledgement proves the run was aborted, not that its terminal was
1582
- // published. Arm the bounded settlement so the turn cannot outlive the cancel.
1583
- this.#scheduleCancelSettlement(params.sessionId, record);
1716
+ // C04 terminal abort: an external client cancel stops the current turn
1717
+ // (`scope:"turn"`, the default, matching the SDK `turn.abort` default and
1718
+ // other ACP clients' cancel behavior). A client that also wants exact owned
1719
+ // subagents and background tasks stopped opts in with
1720
+ // `_meta.gjc.abortScope: "owned"` (or `GJC_ACP_ABORT_SCOPE=owned`).
1721
+ const scope = resolveAcpAbortScope(params._meta, process.env);
1722
+ const waiter = record.activePrompt;
1723
+ if (waiter) {
1724
+ // Overlapping cancels must not lose an earlier successful
1725
+ // acknowledgement: ANY successful attempt resolves the shared
1726
+ // waiter promise IMMEDIATELY (aggregation, not request-order
1727
+ // serialization — an unanswered first attempt must not delay the
1728
+ // second attempt's acknowledged success, or a prompt rejection
1729
+ // awaiting cancelAttempt could hang past the grace bound; review
1730
+ // thread P2). The LAST failing attempt resolves false. In-flight
1731
+ // attempts are counted so a failure only clears the shared intent
1732
+ // when no earlier attempt can still acknowledge.
1733
+ waiter.pendingCancelAttempts = (waiter.pendingCancelAttempts ?? 0) + 1;
1734
+ if (!waiter.cancelAttemptResolve) {
1735
+ const deferred = Promise.withResolvers<boolean>();
1736
+ waiter.cancelAttempt = deferred.promise;
1737
+ waiter.cancelAttemptResolve = deferred.resolve;
1738
+ }
1739
+ }
1740
+ try {
1741
+ const acknowledgement = await record.adapter.cancel(scope);
1742
+ const result = object(object(acknowledgement)?.result) ?? object(acknowledgement);
1743
+ if (!isAbortAcknowledged(acknowledgement, scope))
1744
+ throw new AcpSdkAdapterError(
1745
+ "abort_unacknowledged",
1746
+ "SDK did not acknowledge cancellation of the active prompt.",
1747
+ );
1748
+ if (waiter) waiter.cancelAcknowledged = true;
1749
+ if (waiter && record.activePrompt !== waiter) {
1750
+ waiter.cancelAttemptResolve?.(true);
1751
+ return;
1752
+ }
1753
+ if (
1754
+ result?.disposition === "preflight_cancelled" &&
1755
+ waiter &&
1756
+ record.activePrompt === waiter &&
1757
+ !waiter.acknowledged &&
1758
+ !waiter.settled
1759
+ ) {
1760
+ record.activePrompt = undefined;
1761
+ waiter.settled = true;
1762
+ waiter.deferredFrames.length = 0;
1763
+ waiter.terminal = undefined;
1764
+ waiter.resolve({ stopReason: "cancelled" });
1765
+ // Release the running phase for consistency with the resolved cancel; the
1766
+ // publication is advisory and must never gate the settlement above.
1767
+ void this.#publishPromptPhaseIdle(params.sessionId, record.adapter);
1768
+ } else {
1769
+ // The acknowledgement proves the run was aborted, not that its terminal was
1770
+ // published. Arm the bounded settlement so the turn cannot outlive the cancel.
1771
+ this.#scheduleCancelSettlement(params.sessionId, record);
1772
+ }
1773
+ waiter?.cancelAttemptResolve?.(true);
1774
+ } catch (error) {
1775
+ if (!waiter) record.cancelRequested = false;
1776
+ // Only the LAST in-flight attempt resolves the shared promise false;
1777
+ // an earlier attempt may still acknowledge (review thread P2). After
1778
+ // every attempt of this wave failed, RE-ARM the aggregate: a later
1779
+ // cancellation wave must get a fresh resolver — a stale resolved-false
1780
+ // promise would let the prompt path observe the old failure
1781
+ // immediately and report cancelled while the retry is still pending
1782
+ // (review thread P2).
1783
+ // Only clear and re-arm the aggregate when the LAST in-flight
1784
+ // attempt fails AND the entire wave was unacknowledged: a
1785
+ // failing attempt that follows an acknowledged one must not
1786
+ // erase the already-resolved successful aggregate, or a later
1787
+ // cancel installs a fresh unresolved cancelAttempt that a
1788
+ // concurrent prompt-preflight rejection awaits past the grace
1789
+ // bound (review thread P2).
1790
+ if (waiter && (waiter.pendingCancelAttempts ?? 0) <= 1 && !waiter.cancelAcknowledged) {
1791
+ waiter.cancelAttemptResolve?.(false);
1792
+ waiter.cancelAttempt = undefined;
1793
+ waiter.cancelAttemptResolve = undefined;
1794
+ }
1795
+ throw error;
1796
+ } finally {
1797
+ if (waiter) {
1798
+ waiter.pendingCancelAttempts = Math.max(0, (waiter.pendingCancelAttempts ?? 1) - 1);
1799
+ if (waiter.pendingCancelAttempts === 0 && !waiter.cancelAcknowledged && record.activePrompt === waiter) {
1800
+ record.cancelRequested = false;
1801
+ }
1802
+ }
1803
+ }
1584
1804
  }
1585
1805
 
1586
1806
  /**
@@ -1615,8 +1835,14 @@ export class AcpAgent implements Agent {
1615
1835
  while (record.settledPromptCorrelations.length > SETTLED_PROMPT_CORRELATION_RETENTION)
1616
1836
  record.settledPromptCorrelations.shift();
1617
1837
  }
1618
- await this.#publishPromptPhaseIdle(id, record.adapter);
1838
+ // The client's turn ends with the resolution, not with the advisory idle update.
1839
+ // Publishing the phase transition first made a backpressured ACP transport hold
1840
+ // the settlement hostage: `sessionUpdate` awaits the stream write, a client that
1841
+ // stops reading (force-cancel in progress) blocks it, and `waiter.resolve` never
1842
+ // ran — the acknowledged cancel left the prompt pending forever, the client
1843
+ // force-cancelled, and the next prompt collided with the stale foreground turn.
1619
1844
  waiter.resolve({ stopReason: "cancelled" });
1845
+ void this.#publishPromptPhaseIdle(id, record.adapter);
1620
1846
  }
1621
1847
 
1622
1848
  async extMethod(method: string, params: JsonObject): Promise<JsonObject> {
@@ -1707,6 +1933,18 @@ export class AcpAgent implements Agent {
1707
1933
  return !["terminal_uncertain", "cleanup_pending", "broker_restarting", "unavailable"].includes(error.code);
1708
1934
  }
1709
1935
 
1936
+ async #ensureRouterReady(): Promise<void> {
1937
+ if (!this.#routerStartPromise) {
1938
+ let pending!: Promise<void>;
1939
+ pending = this.#router.start().catch(error => {
1940
+ if (this.#routerStartPromise === pending) this.#routerStartPromise = undefined;
1941
+ throw error;
1942
+ });
1943
+ this.#routerStartPromise = pending;
1944
+ }
1945
+ await this.#routerStartPromise;
1946
+ }
1947
+
1710
1948
  async #attachExisting(id: string, cwd: string, mcpServers: SessionLifecycleMcpServer[] = []): Promise<void> {
1711
1949
  const epoch = this.#sessionEpoch(id);
1712
1950
  const attached = this.#sessions.get(id);
@@ -1716,6 +1954,7 @@ export class AcpAgent implements Agent {
1716
1954
  // ACP clients replay their declared MCP servers when reconnecting. The live
1717
1955
  // session host remains authoritative for its immutable configuration, so
1718
1956
  // attachment must not reinterpret the replay as a mutation request.
1957
+ this.#pendingDeleteLocators.delete(id);
1719
1958
  return;
1720
1959
  }
1721
1960
  const knownCwd = this.#knownSessionCwds.get(id);
@@ -1755,9 +1994,7 @@ export class AcpAgent implements Agent {
1755
1994
  if (indexed?.live) {
1756
1995
  // A reconnect may repeat the client's MCP declaration. Attaching to the
1757
1996
  // existing endpoint preserves the live host's immutable configuration.
1758
- const result = await this.#brokerEndpoint(id, indexed.endpointGeneration);
1759
- this.#assertSessionEpoch(id, epoch);
1760
- await this.#attach(id, cwd, endpoint(result), epoch);
1997
+ await this.#attach(id, cwd, epoch);
1761
1998
  return;
1762
1999
  }
1763
2000
 
@@ -1776,7 +2013,7 @@ export class AcpAgent implements Agent {
1776
2013
  mcpServers,
1777
2014
  );
1778
2015
  this.#assertSessionEpoch(id, epoch);
1779
- await this.#attach(id, cwd, endpoint(result), epoch);
2016
+ await this.#attach(id, cwd, epoch, result);
1780
2017
  }
1781
2018
 
1782
2019
  async #scopedBrokerSession(id: string, cwd: string): Promise<BrokerSession | undefined> {
@@ -1795,7 +2032,7 @@ export class AcpAgent implements Agent {
1795
2032
  return matches[0];
1796
2033
  }
1797
2034
 
1798
- async #attach(id: string, cwd: string, discovered: Endpoint, epoch = this.#sessionEpoch(id)): Promise<void> {
2035
+ async #attach(id: string, cwd: string, epoch = this.#sessionEpoch(id), lifecycleResult?: unknown): Promise<void> {
1799
2036
  this.#assertSessionEpoch(id, epoch);
1800
2037
  const existing = this.#sessions.get(id);
1801
2038
  if (existing) {
@@ -1814,7 +2051,7 @@ export class AcpAgent implements Agent {
1814
2051
  return;
1815
2052
  }
1816
2053
 
1817
- const task = this.#attachEndpoint(id, cwd, discovered, epoch);
2054
+ const task = this.#attachEndpoint(id, cwd, epoch, lifecycleResult);
1818
2055
  const pending = { epoch, task };
1819
2056
  this.#attaching.set(id, pending);
1820
2057
  try {
@@ -1825,15 +2062,37 @@ export class AcpAgent implements Agent {
1825
2062
  }
1826
2063
  }
1827
2064
 
1828
- async #attachEndpoint(id: string, cwd: string, discovered: Endpoint, epoch: number): Promise<void> {
2065
+ async #attachEndpoint(id: string, cwd: string, epoch: number, lifecycleResult?: unknown): Promise<void> {
1829
2066
  let adapter: AcpSdkAdapter | undefined;
2067
+ const bufferedFrames: Record<string, unknown>[] = [];
2068
+ const pendingAdapterFrames: Record<string, unknown>[] = [];
2069
+ let unsubscribePendingFrames = () => {};
2070
+ this.#pendingRouterFrames.set(id, bufferedFrames);
1830
2071
  try {
1831
- adapter = await AcpSdkAdapter.connect({
1832
- url: discovered.url,
1833
- token: discovered.token,
2072
+ await this.#ensureRouterReady();
2073
+ const attachment = lifecycleResult
2074
+ ? await this.#router.adoptLifecycleResult(lifecycleResult, { sessionId: id, cwd })
2075
+ : this.#router.attachment(id);
2076
+ if (!attachment)
2077
+ throw new AcpSdkAdapterError("unavailable", `ACP session ${id} has no current Router attachment.`);
2078
+ let currentAttachment = this.#router.attachment(id);
2079
+ for (let attempt = 0; !currentAttachment && attempt < 40; attempt++) {
2080
+ await Bun.sleep(50);
2081
+ await this.#router.reconcile();
2082
+ currentAttachment = this.#router.attachment(id);
2083
+ }
2084
+ if (!currentAttachment)
2085
+ throw new AcpSdkAdapterError("unavailable", `ACP session ${id} lost exact Router authority.`);
2086
+ adapter = new AcpSdkAdapter({
2087
+ router: this.#router,
2088
+ attachment: currentAttachment,
2089
+ sessionId: id,
1834
2090
  connection: this.#reverseConnection(id),
1835
2091
  providers: this.#providers(),
1836
2092
  });
2093
+ unsubscribePendingFrames = adapter.onFrame(frame => pendingAdapterFrames.push(frame));
2094
+ this.#pendingRouterAdapters.set(id, adapter);
2095
+ await adapter.start();
1837
2096
  let capabilities: JsonObject | undefined;
1838
2097
  try {
1839
2098
  const response = object(await adapter.query("runtime.capabilities"));
@@ -1848,9 +2107,13 @@ export class AcpAgent implements Agent {
1848
2107
  "This ACP client requires a newer GJC SDK session; restart the session.",
1849
2108
  );
1850
2109
  this.#assertSessionEpoch(id, epoch);
2110
+ const exactAttachment = this.#router.attachment(id) ?? currentAttachment;
2111
+ if (!exactAttachment.isCurrent())
2112
+ throw new AcpSdkAdapterError("unavailable", `ACP session ${id} lost exact Router authority.`);
1851
2113
  const record: SessionRecord = {
1852
2114
  cwd,
1853
2115
  adapter,
2116
+ attachment: exactAttachment,
1854
2117
  closeIdempotencyKey: randomUUID(),
1855
2118
  unsubscribe: () => {},
1856
2119
  reconnectUnsubscribe: () => {},
@@ -1866,21 +2129,41 @@ export class AcpAgent implements Agent {
1866
2129
  this.#recoverSessionAfterTransportFailure(id, adapter!, error),
1867
2130
  );
1868
2131
  this.#sessions.set(id, record);
2132
+ unsubscribePendingFrames();
2133
+ for (const frame of bufferedFrames) adapter.acceptFrame(frame);
2134
+ for (const frame of pendingAdapterFrames) this.#enqueueSdkFrame(id, adapter, frame);
2135
+ this.#pendingRouterAdapters.delete(id);
2136
+ this.#pendingRouterFrames.delete(id);
1869
2137
  this.#knownSessionCwds.set(id, cwd);
1870
2138
  await applyAcpPermissionMode(adapter, this.#clientCapabilities);
1871
2139
  this.#assertSessionEpoch(id, epoch);
2140
+ // A successful attachment establishes a new live-owner epoch. Any locator
2141
+ // retained from an earlier cleanup_pending delete belongs to the terminal
2142
+ // owner and must not suppress remote close when this live session is deleted.
2143
+ this.#pendingDeleteLocators.delete(id);
1872
2144
  this.#pendingCloseIdempotencyKeys.delete(id);
1873
2145
  } catch (error) {
2146
+ unsubscribePendingFrames();
2147
+ this.#pendingRouterAdapters.delete(id);
2148
+ this.#pendingRouterFrames.delete(id);
1874
2149
  if (adapter && this.#sessions.get(id)?.adapter === adapter) {
1875
2150
  try {
1876
2151
  await this.#teardownSession(id, "attachment failed", false);
1877
2152
  } finally {
1878
2153
  this.#knownSessionCwds.delete(id);
2154
+ this.#knownSessionMcpServers.delete(id);
1879
2155
  }
1880
2156
  } else if (adapter) {
1881
2157
  try {
1882
2158
  await adapter.close();
1883
2159
  } catch {}
2160
+ try {
2161
+ await this.#router.attachment(id)?.retire?.();
2162
+ } catch {}
2163
+ } else {
2164
+ try {
2165
+ await this.#router.attachment(id)?.retire?.();
2166
+ } catch {}
1884
2167
  }
1885
2168
  throw error;
1886
2169
  }
@@ -1889,6 +2172,16 @@ export class AcpAgent implements Agent {
1889
2172
  #recoverSessionAfterTransportFailure(id: string, adapter: AcpSdkAdapter, error: Error): void {
1890
2173
  const record = this.#sessions.get(id);
1891
2174
  if (!record || record.adapter !== adapter) return;
2175
+ if (error instanceof SdkClientError && error.code !== "reconnect_exhausted") {
2176
+ logger.warn(
2177
+ `ACP session ${id} transport failure is not reconnect_exhausted (${error.code}); ignoring terminal recovery.`,
2178
+ );
2179
+ return;
2180
+ }
2181
+ if (!(error instanceof SdkClientError)) {
2182
+ logger.warn(`ACP session ${id} non-transport error reached reconnect handler; ignoring terminal recovery.`);
2183
+ return;
2184
+ }
1892
2185
  const detail = error.message || "SDK transport reconnect failed.";
1893
2186
  const terminal = new AcpSdkAdapterError("connection_closed", `ACP session transport was lost: ${detail}`);
1894
2187
  void this.#recoverSessionAfterTransportFailureAsync(id, adapter, record.cwd, terminal);
@@ -1902,16 +2195,28 @@ export class AcpAgent implements Agent {
1902
2195
  ): Promise<void> {
1903
2196
  await this.#failSession(id, adapter, error);
1904
2197
  if (this.#disposed || this.#knownSessionCwds.get(id) !== cwd) return;
2198
+ const mcpServers = this.#knownSessionMcpServers.get(id) ?? [];
1905
2199
  try {
1906
- await this.#attachExisting(id, cwd);
1907
- } catch {
1908
- // The affected prompt was rejected and the stale adapter was removed. A later load/resume retries discovery.
2200
+ await this.#attachExisting(id, cwd, mcpServers);
2201
+ } catch (attachError) {
2202
+ const detail = attachError instanceof Error ? attachError.message : String(attachError);
2203
+ logger.warn(`ACP session ${id} auto-reattach after transport loss failed: ${detail}`);
2204
+ try {
2205
+ await this.#connection.sessionUpdate({
2206
+ sessionId: id,
2207
+ update: {
2208
+ sessionUpdate: "session_info_update",
2209
+ _meta: { gjcRecoverFailed: true, gjcRecoverError: detail },
2210
+ },
2211
+ });
2212
+ } catch {}
1909
2213
  }
1910
2214
  }
1911
2215
 
1912
2216
  async #discardNewSession(id: string): Promise<void> {
1913
2217
  await this.#teardownSession(id, "discarded", true);
1914
2218
  this.#knownSessionCwds.delete(id);
2219
+ this.#knownSessionMcpServers.delete(id);
1915
2220
  this.#knownSessionMetadata.delete(id);
1916
2221
  }
1917
2222
 
@@ -1924,6 +2229,7 @@ export class AcpAgent implements Agent {
1924
2229
  if (attaching) await Promise.allSettled([attaching.task]);
1925
2230
  await this.#teardownSession(id, "closed", true);
1926
2231
  this.#knownSessionCwds.delete(id);
2232
+ this.#knownSessionMcpServers.delete(id);
1927
2233
  this.#knownSessionMetadata.delete(id);
1928
2234
  return {};
1929
2235
  } finally {
@@ -1964,11 +2270,6 @@ export class AcpAgent implements Agent {
1964
2270
  }
1965
2271
 
1966
2272
  const failures: unknown[] = [];
1967
- try {
1968
- await record?.adapter.close();
1969
- } catch (error) {
1970
- failures.push(error);
1971
- }
1972
2273
  if (closeRemote) {
1973
2274
  const closeIdempotencyKey =
1974
2275
  record?.closeIdempotencyKey ?? this.#pendingCloseIdempotencyKeys.get(id) ?? randomUUID();
@@ -1980,6 +2281,16 @@ export class AcpAgent implements Agent {
1980
2281
  if (!(ownershipBound && this.#isAlreadyGone(error))) failures.push(error);
1981
2282
  }
1982
2283
  }
2284
+ try {
2285
+ await record?.adapter.close();
2286
+ } catch (error) {
2287
+ failures.push(error);
2288
+ }
2289
+ try {
2290
+ await record?.attachment.retire?.();
2291
+ } catch (error) {
2292
+ failures.push(error);
2293
+ }
1983
2294
  if (failures.length > 0) {
1984
2295
  const detail = failures
1985
2296
  .map(failure => (failure instanceof Error ? failure.message : String(failure)))
@@ -2006,18 +2317,15 @@ export class AcpAgent implements Agent {
2006
2317
  try {
2007
2318
  await adapter.close();
2008
2319
  } catch {}
2320
+ try {
2321
+ await record.attachment.retire?.();
2322
+ } catch {}
2009
2323
  }
2010
2324
 
2011
2325
  async #brokerAdapter(): Promise<AcpSdkAdapter> {
2012
2326
  return (await this.#brokerConnection()).adapter;
2013
2327
  }
2014
2328
 
2015
- /** Machine-local endpoint lookup; never routed through ACP extension methods. */
2016
- async #brokerEndpoint(sessionId: string, endpointGeneration: number | undefined): Promise<unknown> {
2017
- const input = { sessionId, ...(endpointGeneration === undefined ? {} : { endpointGeneration }) };
2018
- return await (await this.#brokerConnection()).client.global("session.get_endpoint", input);
2019
- }
2020
-
2021
2329
  async #brokerConnection(): Promise<BrokerConnection> {
2022
2330
  if (!this.#broker) {
2023
2331
  let pending!: Promise<BrokerConnection>;
@@ -2026,7 +2334,7 @@ export class AcpAgent implements Agent {
2026
2334
  const discovery = await readSdkBrokerDiscovery(this.#agentDir);
2027
2335
  if (!discovery) throw new AcpSdkAdapterError("unavailable", "SDK broker discovery is unavailable.");
2028
2336
  const client = await SdkClient.connect(discovery.url, discovery.token, { ...ACP_SESSION_RECONNECT });
2029
- const adapter = new AcpSdkAdapter({ url: discovery.url, token: discovery.token, client });
2337
+ const adapter = new AcpSdkAdapter({ client });
2030
2338
  adapter.onReconnectFailed(() => {
2031
2339
  if (this.#broker === pending) this.#broker = undefined;
2032
2340
  void adapter.close().catch(() => undefined);
@@ -2182,6 +2490,7 @@ export class AcpAgent implements Agent {
2182
2490
  #enqueueSdkFrame(id: string, adapter: AcpSdkAdapter, frame: JsonObject): void {
2183
2491
  const record = this.#sessions.get(id);
2184
2492
  if (!record || record.adapter !== adapter) return;
2493
+ if (typeof frame.connectionId === "string") record.connectionId = frame.connectionId;
2185
2494
  // Ingress ordering is recorded before queued work begins.
2186
2495
  this.#observeSessionActivity(record, frame);
2187
2496
  // Correlation is checked at ingress before a prompt-owned frame may refresh the
@@ -2217,6 +2526,9 @@ export class AcpAgent implements Agent {
2217
2526
  "The prompt owner connection was lost before completion.",
2218
2527
  ),
2219
2528
  );
2529
+ // The turn is settled by the rejection; release the running phase
2530
+ // best-effort so a cancelled prompt is not left reporting working.
2531
+ void this.#publishPromptPhaseIdle(id, record.adapter);
2220
2532
  }
2221
2533
  }
2222
2534
  return;
@@ -2400,17 +2712,22 @@ export class AcpAgent implements Agent {
2400
2712
  record.settledPromptCorrelations.shift();
2401
2713
  }
2402
2714
  // The turn is over even though it ended badly, so the client's running phase has
2403
- // to be released. Skipping it here is what leaves a client composer spinning on a
2404
- // turn that will never produce another frame.
2405
- await this.#publishPromptPhaseIdle(id, record.adapter);
2715
+ // to be released. Rejection is the settlement; the advisory idle publication must
2716
+ // never gate it (a backpressured transport would otherwise hold the rejection
2717
+ // forever). Skipping the idle publish entirely is what left a client composer
2718
+ // spinning on a turn that already produced its terminal.
2406
2719
  waiter.reject(error);
2720
+ void this.#publishPromptPhaseIdle(id, record.adapter);
2407
2721
  }
2408
2722
 
2409
2723
  /**
2410
2724
  * Publishes only the phase transition — no `context.get`/`session.metadata` queries —
2411
2725
  * because an abnormal settlement has no trustworthy usage or title to report. Publish
2412
2726
  * failures are swallowed: the turn is already settled, and escalating to session
2413
- * failure here would tear down a session the client can still use.
2727
+ * failure here would tear down a session the client can still use. Callers must
2728
+ * settle the prompt BEFORE invoking this (or detach it with `void`): `sessionUpdate`
2729
+ * awaits the transport write, so awaiting it ahead of resolution lets a backpressured
2730
+ * client hold the settlement hostage.
2414
2731
  */
2415
2732
  async #publishPromptPhaseIdle(id: string, adapter: AcpSdkAdapter): Promise<void> {
2416
2733
  const record = this.#sessions.get(id);
@@ -3161,7 +3478,7 @@ export class AcpAgent implements Agent {
3161
3478
  mcpServers: SessionLifecycleMcpServer[],
3162
3479
  ): Promise<unknown> {
3163
3480
  try {
3164
- return await (await this.#brokerAdapter()).global(operation, input, idempotencyKey);
3481
+ return await (await this.#brokerAdapter()).lifecycle(operation, input, idempotencyKey);
3165
3482
  } catch (error) {
3166
3483
  throw acpMcpLaunchFailure(error, mcpServers);
3167
3484
  }
@@ -3190,6 +3507,7 @@ export class AcpAgent implements Agent {
3190
3507
  this.#attaching.clear();
3191
3508
  this.#resolvingExisting.clear();
3192
3509
  this.#knownSessionCwds.clear();
3510
+ this.#knownSessionMcpServers.clear();
3193
3511
  this.#knownSessionMetadata.clear();
3194
3512
  this.#pendingDeleteLocators.clear();
3195
3513
  this.#pendingCloseIdempotencyKeys.clear();
@@ -3204,6 +3522,13 @@ export class AcpAgent implements Agent {
3204
3522
  failures.push(error);
3205
3523
  }
3206
3524
  }
3525
+ this.#pendingRouterAdapters.clear();
3526
+ this.#pendingRouterFrames.clear();
3527
+ try {
3528
+ await this.#router.stop();
3529
+ } catch (error) {
3530
+ failures.push(error);
3531
+ }
3207
3532
  if (failures.length > 0) {
3208
3533
  const detail = failures
3209
3534
  .map(failure => (failure instanceof Error ? failure.message : String(failure)))