@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
@@ -4,27 +4,34 @@ import * as fs from "node:fs/promises";
4
4
  import * as path from "node:path";
5
5
  import { getAgentDir, isKnownSinkPeerClosedError } from "@gajae-code/utils";
6
6
  import { normalizePathForComparison, VERSION } from "@gajae-code/utils/dirs";
7
-
8
- import { withFileLock } from "../config/file-lock";
9
7
  import {
10
8
  COORDINATOR_MCP_PROTOCOL_VERSION,
11
9
  COORDINATOR_MCP_SERVER_NAME,
12
10
  COORDINATOR_MCP_TOOL_NAMES,
13
11
  type CoordinatorToolName,
14
12
  } from "../coordinator/contract";
13
+ import { SessionStateLockUnavailableError, withSessionStateFileLock } from "../gjc-runtime/session-state-lock";
14
+ import {
15
+ classifyRuntimeToolActivity,
16
+ publicRuntimeToolActivity,
17
+ terminallySettledRuntimeToolActivity,
18
+ } from "../gjc-runtime/session-state-sidecar";
15
19
  import { listMcpDelegateHostContexts } from "../hooks/mcp-delegate-host-context";
16
20
  import type { WorkflowGate, WorkflowGateQueryRecord } from "../modes/shared/agent-wire/workflow-gate-types";
17
21
  import type { BrokerDiscovery } from "../sdk/broker/discovery";
18
22
  import { type EnsureBrokerSettings, ensureBroker } from "../sdk/broker/ensure";
19
23
  import { lifecycleRequestTimeoutMs } from "../sdk/broker/startup-budget";
20
24
  import { UnsupportedStateVersionError } from "../sdk/broker/state-version";
21
- import { SdkClient, SdkClientError, type SdkSentRecord } from "../sdk/client/client";
25
+ import { SdkClient, SdkClientError } from "../sdk/client/client";
22
26
  import { readSdkBrokerDiscovery } from "../sdk/client/discovery";
27
+ import { reduceTerminalReceiptState } from "../sdk/receipt-state";
28
+ import { type SessionAttachment, SessionRouter, type SessionRouterDeps, SessionRouterError } from "../sdk/router";
23
29
  import {
24
30
  type ActivatedPreparedSession,
25
31
  requestPreparedSessionActivation,
26
32
  SessionActivationError,
27
33
  } from "../sdk/session-activation";
34
+ import { SessionListTraversalError, sessionListPageFromResponse, traverseSessionList } from "../sdk/session-list";
28
35
  import {
29
36
  ackCodexWakeEvent,
30
37
  bindDelegateCodexHandoff,
@@ -111,16 +118,6 @@ interface JsonRpcResponse {
111
118
  error?: { code: number; message: string; data?: unknown };
112
119
  }
113
120
 
114
- function isLifecycleOperation(operation: string): boolean {
115
- return (
116
- operation === "session.create" ||
117
- operation === "session.fork" ||
118
- operation === "session.resume" ||
119
- operation === "session.close" ||
120
- operation === "session.delete"
121
- );
122
- }
123
-
124
121
  function sinkErrorCode(error: unknown): string | undefined {
125
122
  if (error === null || (typeof error !== "object" && typeof error !== "function")) return undefined;
126
123
  try {
@@ -173,10 +170,11 @@ interface CoordinatorFinalResponse {
173
170
  truncated: boolean;
174
171
  }
175
172
 
176
- function reportableFinalResponse(response: CoordinatorFinalResponse): boolean {
177
- return (
178
- (typeof response.text === "string" && response.text.trim().length > 0) ||
179
- (typeof response.artifact_path === "string" && response.artifact_path.trim().length > 0)
173
+ function reportableFinalResponse(response: CoordinatorFinalResponse | undefined): boolean {
174
+ return Boolean(
175
+ response &&
176
+ ((typeof response.text === "string" && response.text.trim().length > 0) ||
177
+ (typeof response.artifact_path === "string" && response.artifact_path.trim().length > 0)),
180
178
  );
181
179
  }
182
180
 
@@ -186,7 +184,8 @@ interface RuntimeSessionStatePayload extends CoordinatorSessionState {
186
184
  }
187
185
 
188
186
  interface CoordinatorServices {
189
- connectSdk?: (url: string, token: string) => Promise<SdkClient>;
187
+ connectBroker?: (url: string, token: string) => Promise<SdkClient>;
188
+ routerDeps?: SessionRouterDeps;
190
189
  ensureBroker?: (settings: EnsureBrokerSettings) => Promise<BrokerDiscovery>;
191
190
  readSdkBrokerDiscovery?: (agentDir: string) => Promise<BrokerDiscovery | null>;
192
191
  getAgentDir?: () => string;
@@ -277,13 +276,15 @@ interface CoordinatorSessionState {
277
276
  current_turn_id: string | null;
278
277
  last_turn_id: string | null;
279
278
  updated_at: string;
279
+ ended_at?: string;
280
280
  source: "coordinator" | "agent_session_event";
281
281
  live: boolean | null;
282
282
  reason: string | null;
283
- last_activity_at?: string;
283
+ /**
284
+ * Tool-activity snapshot owned by the runtime sidecar; never lifecycle state. It is
285
+ * carried opaquely because only the sidecar's validator decides what is publishable.
286
+ */
284
287
  activity?: unknown;
285
- active_tools?: unknown;
286
- active_tool_calls?: unknown;
287
288
  }
288
289
 
289
290
  type CoordinatorEventKind =
@@ -338,8 +339,15 @@ const UNOBSERVED_COMPENSATION_CODE = "broker_compensation_unobserved";
338
339
  const MISSING_FINAL_RESPONSE_ADVISORY = "completion_missing_final_response";
339
340
  const PROMPT_ACK_TIMEOUT_REASON = "runtime_prompt_ack_timeout";
340
341
  const DEFAULT_RUNTIME_PROMPT_ACK_TIMEOUT_MS = 10_000;
342
+ /**
343
+ * Pagination window for `readCompleteQ12Snapshot`: it stops starting pages once
344
+ * this much time has passed and gives each page only the remainder as its reply
345
+ * budget. It bounds pagination, not the whole call — attachment resolution runs
346
+ * before the window opens, and a page's budget is a post-connect reply deadline,
347
+ * so transport reconnect time sits outside it.
348
+ */
349
+ const Q12_SNAPSHOT_BUDGET_MS = 5_000;
341
350
  const MAX_RUNTIME_PROMPT_ACK_TIMEOUT_MS = 5 * 60 * 1000;
342
- const MAX_COORDINATOR_SESSION_LIST_PAGES = 10_000;
343
351
  const ACTIVE_TURN_STATUSES = new Set<TurnStatus>(["delivering", "active", "waiting_for_answer", "completing"]);
344
352
  const TERMINAL_TURN_STATUSES = new Set<TurnStatus>(["completed", "failed", "cancelled", "superseded"]);
345
353
  const TURN_ID_PATTERN = /^turn-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -1241,7 +1249,10 @@ function publicLifecycleReceipt(result: Record<string, unknown>, sessionId: stri
1241
1249
 
1242
1250
  function publicCoordinatorSessionState(state: CoordinatorSessionState | null): Record<string, unknown> | null {
1243
1251
  if (!state) return null;
1244
- const result: Record<string, unknown> = {
1252
+ // The sidecar owns the one projection that strips its private correlation state and
1253
+ // withholds any snapshot that contradicts the lifecycle state published beside it.
1254
+ const activity = publicRuntimeToolActivity(state.activity, state.state);
1255
+ return {
1245
1256
  session_id: state.session_id,
1246
1257
  state: state.state,
1247
1258
  ready_for_input: state.ready_for_input,
@@ -1249,56 +1260,11 @@ function publicCoordinatorSessionState(state: CoordinatorSessionState | null): R
1249
1260
  last_turn_id: state.last_turn_id,
1250
1261
  updated_at: state.updated_at,
1251
1262
  ...(typeof state.live === "boolean" ? { live: state.live } : {}),
1263
+ ...(activity ? { activity } : {}),
1264
+ ...(typeof state.ended_at === "string" && Number.isFinite(Date.parse(state.ended_at))
1265
+ ? { ended_at: state.ended_at }
1266
+ : {}),
1252
1267
  };
1253
- const activity = asRecord(state.activity);
1254
- if (
1255
- typeof state.last_activity_at === "string" &&
1256
- Number.isFinite(Date.parse(state.last_activity_at)) &&
1257
- activity &&
1258
- typeof activity.sequence === "number" &&
1259
- Number.isSafeInteger(activity.sequence) &&
1260
- activity.sequence > 0 &&
1261
- activity.kind === "tool_execution" &&
1262
- (activity.phase === "started" || activity.phase === "finished") &&
1263
- typeof activity.tool_name === "string" &&
1264
- /^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(activity.tool_name) &&
1265
- typeof activity.observed_at === "string" &&
1266
- Number.isFinite(Date.parse(activity.observed_at)) &&
1267
- typeof activity.active_tool_count === "number" &&
1268
- Number.isSafeInteger(activity.active_tool_count) &&
1269
- activity.active_tool_count >= 0
1270
- ) {
1271
- result.last_activity_at = state.last_activity_at;
1272
- result.activity = {
1273
- sequence: activity.sequence,
1274
- kind: activity.kind,
1275
- phase: activity.phase,
1276
- tool_name: activity.tool_name,
1277
- observed_at: activity.observed_at,
1278
- ...(typeof activity.started_at === "string" && Number.isFinite(Date.parse(activity.started_at))
1279
- ? { started_at: activity.started_at }
1280
- : {}),
1281
- ...(typeof activity.elapsed_ms === "number" &&
1282
- Number.isSafeInteger(activity.elapsed_ms) &&
1283
- activity.elapsed_ms >= 0
1284
- ? { elapsed_ms: activity.elapsed_ms }
1285
- : {}),
1286
- ...(activity.outcome === "succeeded" || activity.outcome === "failed" ? { outcome: activity.outcome } : {}),
1287
- active_tool_count: activity.active_tool_count,
1288
- };
1289
- if (Array.isArray(state.active_tools))
1290
- result.active_tools = state.active_tools.slice(0, 8).flatMap(value => {
1291
- const tool = asRecord(value);
1292
- return tool &&
1293
- typeof tool.tool_name === "string" &&
1294
- /^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(tool.tool_name) &&
1295
- typeof tool.started_at === "string" &&
1296
- Number.isFinite(Date.parse(tool.started_at))
1297
- ? [{ tool_name: tool.tool_name, started_at: tool.started_at }]
1298
- : [];
1299
- });
1300
- }
1301
- return result;
1302
1268
  }
1303
1269
 
1304
1270
  function eventTimestamp(record: Record<string, unknown>): string | null {
@@ -1888,35 +1854,52 @@ async function writeSessionStateUnlocked(
1888
1854
  live?: boolean | null;
1889
1855
  reason?: string | null;
1890
1856
  source?: CoordinatorSessionState["source"];
1857
+ /** Rebuild the lifecycle fields from canonical state instead of the previous projection. */
1891
1858
  overwrite?: boolean;
1892
1859
  } = {},
1893
1860
  ): Promise<CoordinatorSessionState> {
1894
- const existing = await readSessionState(namespaceDir, sessionId);
1895
- const previous = options.overwrite ? null : existing;
1861
+ const persisted = await readSessionState(namespaceDir, sessionId);
1862
+ // An overwrite is a canonical lifecycle repair, not proof that tool activity ended.
1863
+ const previous = options.overwrite ? null : persisted;
1896
1864
  const hasCurrentTurn = Object.hasOwn(options, "currentTurnId");
1897
1865
  const hasLastTurn = Object.hasOwn(options, "lastTurnId");
1898
1866
  const hasLive = Object.hasOwn(options, "live");
1867
+ const updatedAt = new Date().toISOString();
1868
+ // A coordinator write says nothing about tool activity, so every lifecycle path —
1869
+ // including canonical repair — carries the sidecar's snapshot and its sequence
1870
+ // forward. A TERMINAL lifecycle state is the one exception: it is authority that
1871
+ // nothing can still be running, so it settles orphaned in-flight calls through the
1872
+ // exact helper the runtime uses. A malformed snapshot is preserved verbatim rather
1873
+ // than reset to a lower sequence; the public projection refuses to publish it either way.
1874
+ const terminal = state === "completed" || state === "errored";
1875
+ const activity = terminal
1876
+ ? terminallySettledRuntimeToolActivity(persisted?.activity, updatedAt)
1877
+ : classifyRuntimeToolActivity(persisted?.activity);
1878
+ const isTerminalState = state === "completed" || state === "errored" || state === "stale";
1879
+ const endedAt = isTerminalState
1880
+ ? updatedAt
1881
+ : typeof persisted?.ended_at === "string" && Number.isFinite(Date.parse(persisted.ended_at))
1882
+ ? persisted.ended_at
1883
+ : undefined;
1899
1884
  const payload: CoordinatorSessionState = {
1900
1885
  schema_version: 1,
1901
1886
  session_id: sessionId,
1902
1887
  state,
1903
- ready_for_input: state === "ready_for_input" || state === "completed",
1888
+ ready_for_input: state === "ready_for_input",
1904
1889
  current_turn_id: hasCurrentTurn
1905
1890
  ? (options.currentTurnId ?? null)
1906
1891
  : state === "running"
1907
1892
  ? (previous?.current_turn_id ?? null)
1908
1893
  : null,
1909
1894
  last_turn_id: hasLastTurn ? (options.lastTurnId ?? null) : (previous?.last_turn_id ?? null),
1910
- updated_at: new Date().toISOString(),
1895
+ updated_at: updatedAt,
1911
1896
  source: options.source ?? "coordinator",
1912
1897
  live: hasLive ? (options.live ?? null) : (previous?.live ?? null),
1913
1898
  reason: options.reason ?? null,
1914
- ...(typeof existing?.last_activity_at === "string" ? { last_activity_at: existing.last_activity_at } : {}),
1915
- ...(existing?.activity && typeof existing.activity === "object" ? { activity: existing.activity } : {}),
1916
- ...(Array.isArray(existing?.active_tools) ? { active_tools: existing.active_tools } : {}),
1917
- ...(existing?.active_tool_calls && typeof existing.active_tool_calls === "object"
1918
- ? { active_tool_calls: existing.active_tool_calls }
1919
- : {}),
1899
+ ...(endedAt !== undefined ? { ended_at: endedAt } : {}),
1900
+ ...(activity.kind === "absent"
1901
+ ? {}
1902
+ : { activity: activity.kind === "valid" ? activity.activity : persisted?.activity }),
1920
1903
  };
1921
1904
  await writeJsonFile(sessionStateFile(namespaceDir, sessionId), payload);
1922
1905
  if (
@@ -1944,8 +1927,24 @@ async function writeSessionStateUnlocked(
1944
1927
  return payload;
1945
1928
  }
1946
1929
 
1930
+ /**
1931
+ * Serialize a coordinator state mutation on the same lock the runtime sidecar takes.
1932
+ *
1933
+ * Both writers contend for `<file>.lock`, so they must use one implementation and one
1934
+ * on-disk owner format. That format is the base Coordinator's regular-file owner JSON,
1935
+ * now shared: a directory-style lock here would make a base-version `<file>.lock` regular
1936
+ * file permanently unusable (`ENOTDIR` on `<file>.lock/info`), stranding the session. The
1937
+ * base runtime did use a directory at this path, so the shared implementation reclaims
1938
+ * that shape too. Live owners are waited for; dead and malformed ones are reclaimed by
1939
+ * that implementation's own liveness and staleness checks.
1940
+ */
1947
1941
  async function withSessionStateLock<T>(stateFile: string, operation: () => Promise<T>): Promise<T> {
1948
- return await withFileLock(stateFile, operation, { staleMs: 30_000, retries: 12_000, retryDelayMs: 5 });
1942
+ try {
1943
+ return await withSessionStateFileLock(stateFile, operation);
1944
+ } catch (error) {
1945
+ if (error instanceof SessionStateLockUnavailableError) throw new Error("coordinator_state_unreadable");
1946
+ throw error;
1947
+ }
1949
1948
  }
1950
1949
 
1951
1950
  async function writeSessionState(
@@ -1993,7 +1992,12 @@ async function markTurnTerminalFromSessionState(
1993
1992
  turn: TurnRecord,
1994
1993
  sessionState: CoordinatorSessionState,
1995
1994
  ): Promise<TurnRecord> {
1996
- const terminalStatus: TurnStatus = sessionState.state === "errored" ? "failed" : "completed";
1995
+ const receiptState = reduceTerminalReceiptState({
1996
+ execution: sessionState.state === "errored" ? "failed" : "completed",
1997
+ reportable: reportableFinalResponse((sessionState as RuntimeSessionStatePayload).final_response),
1998
+ });
1999
+ const terminalStatus: TurnStatus =
2000
+ receiptState.receipt === "missing" ? "failed" : sessionState.state === "errored" ? "failed" : "completed";
1997
2001
  const runtimeState = sessionState as RuntimeSessionStatePayload;
1998
2002
  const finalResponse = runtimeState.final_response ?? {
1999
2003
  text: null,
@@ -2012,24 +2016,21 @@ async function markTurnTerminalFromSessionState(
2012
2016
  state: "acknowledged",
2013
2017
  },
2014
2018
  final_response: finalResponse,
2015
- evidence: reportableFinalResponse(finalResponse)
2016
- ? turn.evidence
2017
- : [
2018
- ...turn.evidence,
2019
- {
2020
- type: MISSING_FINAL_RESPONSE_ADVISORY,
2021
- message: "Runtime completed without reportable final_response text or artifact_path.",
2022
- created_at: timestamp,
2023
- },
2024
- ],
2019
+ evidence: turn.evidence,
2025
2020
  error:
2026
- terminalStatus === "failed"
2027
- ? (runtimeState.error ?? {
2028
- code: "runtime_errored",
2029
- message: sessionState.reason ?? "runtime_errored",
2021
+ receiptState.receipt === "missing"
2022
+ ? {
2023
+ code: "receipt_missing",
2024
+ message: "Runtime completed without reportable final_response text or artifact_path.",
2030
2025
  recoverable: true,
2031
- })
2032
- : null,
2026
+ }
2027
+ : terminalStatus === "failed"
2028
+ ? (runtimeState.error ?? {
2029
+ code: "runtime_errored",
2030
+ message: sessionState.reason ?? "runtime_errored",
2031
+ recoverable: true,
2032
+ })
2033
+ : null,
2033
2034
  updated_at: timestamp,
2034
2035
  completed_at: timestamp,
2035
2036
  };
@@ -2363,6 +2364,16 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
2363
2364
  const config = buildCoordinatorMcpConfig(env);
2364
2365
  const promptAckTimeoutMs = boundedRuntimePromptAckTimeoutMs(env.GJC_COORDINATOR_MCP_PROMPT_ACK_TIMEOUT_MS);
2365
2366
  const services = options.services ?? {};
2367
+ const routerAgentDir = services.getAgentDir?.() ?? getAgentDir();
2368
+ const router = new SessionRouter({ agentDir: routerAgentDir, deps: services.routerDeps });
2369
+ let routerReady: Promise<void> | null = null;
2370
+ async function ensureRouterReady(): Promise<void> {
2371
+ routerReady ??= router.start().catch(error => {
2372
+ routerReady = null;
2373
+ throw error;
2374
+ });
2375
+ await routerReady;
2376
+ }
2366
2377
  const platform = options.platform ?? process.platform;
2367
2378
  const loadModelProfiles = services.resolveModelProfiles ?? loadCoordinatorModelProfiles;
2368
2379
  const namespaceDir = path.join(
@@ -2832,27 +2843,111 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
2832
2843
  }
2833
2844
  }
2834
2845
 
2846
+ type CoordinatorSessionAttachment = {
2847
+ sessionId: string;
2848
+ generation: number;
2849
+ attachment: SessionAttachment;
2850
+ };
2851
+
2852
+ async function resolveSessionAttachment(session: Record<string, unknown>): Promise<CoordinatorSessionAttachment> {
2853
+ const sessionId = optionalString(session.session_id) ?? optionalString(session.sessionId);
2854
+ const generation =
2855
+ typeof session.endpoint_generation === "number" &&
2856
+ Number.isSafeInteger(session.endpoint_generation) &&
2857
+ session.endpoint_generation > 0
2858
+ ? session.endpoint_generation
2859
+ : null;
2860
+ const workspace = optionalString(session.broker_workspace);
2861
+ const incarnation = optionalString(session.endpoint_incarnation);
2862
+ if (!sessionId || generation === null)
2863
+ throw new SdkClientError("not_found", "Coordinator session has no usable endpoint generation.");
2864
+ if (!workspace || !incarnation)
2865
+ throw new SdkClientError("endpoint_stale", "Coordinator session has no exact endpoint authority.");
2866
+ let persistedWorkspace: string;
2867
+ try {
2868
+ persistedWorkspace = await canonicalBrokerWorkspace(workspace);
2869
+ } catch {
2870
+ throw new SdkClientError("endpoint_stale", "Coordinator session workspace authority is unavailable.");
2871
+ }
2872
+ await ensureRouterReady();
2873
+ await router.reconcile();
2874
+ const attachment = router.attachment(sessionId, generation);
2875
+ if (!attachment?.isCurrent())
2876
+ throw new SdkClientError("endpoint_stale", "Coordinator session attachment is unavailable or stale.");
2877
+ let authority: BrokerSessionAuthority;
2878
+ try {
2879
+ authority = await exactBrokerSessionAuthority(sessionId, persistedWorkspace);
2880
+ } catch (error) {
2881
+ if (error instanceof SdkClientError && (error.code === "not_found" || error.code === "endpoint_stale"))
2882
+ throw new SdkClientError(
2883
+ "endpoint_stale",
2884
+ "Coordinator session endpoint authority is unavailable or stale.",
2885
+ );
2886
+ throw error;
2887
+ }
2888
+ if (
2889
+ !sameCanonicalPath(authority.workspace, persistedWorkspace, platform) ||
2890
+ authority.endpointGeneration !== generation ||
2891
+ authority.endpointIncarnation !== incarnation
2892
+ )
2893
+ throw new SdkClientError("endpoint_stale", "Coordinator session endpoint authority changed.");
2894
+ return { sessionId, generation, attachment };
2895
+ }
2896
+
2897
+ function sdkResponse(response: Record<string, unknown>, operation: string): Record<string, unknown> {
2898
+ if (response.ok !== true) {
2899
+ const error = asRecord(response.error);
2900
+ throw new SdkClientError(
2901
+ typeof error?.code === "string" ? error.code : "unavailable",
2902
+ typeof error?.message === "string" ? error.message : `SDK ${operation} request failed.`,
2903
+ );
2904
+ }
2905
+ return response;
2906
+ }
2907
+
2908
+ async function requestSessionFrame(
2909
+ target: CoordinatorSessionAttachment,
2910
+ frame: Record<string, unknown>,
2911
+ options?: { timeoutMs?: number },
2912
+ ): Promise<Record<string, unknown>> {
2913
+ try {
2914
+ return await router.request(target.sessionId, frame, target.generation, target.attachment, options);
2915
+ } catch (error) {
2916
+ if (error instanceof SessionRouterError)
2917
+ throw new SdkClientError(
2918
+ error.phase === "ambiguous" ? "ambiguous" : "endpoint_stale",
2919
+ error.phase === "ambiguous"
2920
+ ? "Coordinator session request may have been accepted before attachment changed."
2921
+ : "Coordinator session attachment changed before request dispatch.",
2922
+ error,
2923
+ );
2924
+ throw error;
2925
+ }
2926
+ }
2927
+
2835
2928
  async function controlSession(
2836
2929
  session: Record<string, unknown>,
2837
2930
  operation: string,
2838
2931
  input: Record<string, unknown>,
2839
2932
  idempotencyKey: string,
2840
2933
  ): Promise<unknown> {
2841
- const endpoint = await resolveSessionEndpoint(session, idempotencyKey);
2842
- const client = await (services.connectSdk ?? ((url, token) => SdkClient.connect(url, token)))(
2843
- endpoint.url,
2844
- endpoint.token,
2845
- );
2846
- const isPromptOperation =
2934
+ const target = await resolveSessionAttachment(session);
2935
+ const promptOperation =
2847
2936
  operation === "turn.prompt" || operation === "turn.follow_up" || operation === "turn.abort_and_prompt";
2848
- try {
2849
- return await client.control(operation, input, {
2850
- idempotencyKey,
2851
- ...(isPromptOperation ? { timeoutMs: promptAckTimeoutMs } : {}),
2852
- });
2853
- } finally {
2854
- await client.close();
2855
- }
2937
+ const response = asRecord(
2938
+ await requestSessionFrame(
2939
+ target,
2940
+ {
2941
+ type: "control_request",
2942
+ operation,
2943
+ input,
2944
+ idempotencyKey,
2945
+ },
2946
+ promptOperation ? { timeoutMs: promptAckTimeoutMs } : undefined,
2947
+ ),
2948
+ );
2949
+ if (response?.ok === false) sdkResponse(response, operation);
2950
+ return response;
2856
2951
  }
2857
2952
 
2858
2953
  async function querySession(
@@ -2861,24 +2956,17 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
2861
2956
  input: Record<string, unknown> = {},
2862
2957
  cursor?: string,
2863
2958
  ): Promise<Record<string, unknown>> {
2864
- const endpoint = await resolveSessionEndpoint(session);
2865
- const client = await (services.connectSdk ?? ((url, token) => SdkClient.connect(url, token)))(
2866
- endpoint.url,
2867
- endpoint.token,
2959
+ const target = await resolveSessionAttachment(session);
2960
+ const response = asRecord(
2961
+ await requestSessionFrame(target, {
2962
+ type: "query_request",
2963
+ query,
2964
+ input,
2965
+ ...(cursor === undefined ? {} : { cursor }),
2966
+ }),
2868
2967
  );
2869
- try {
2870
- const response = asRecord(await client.query(query, input, cursor));
2871
- if (response?.ok !== true) {
2872
- const error = asRecord(response?.error);
2873
- throw new SdkClientError(
2874
- typeof error?.code === "string" ? error.code : "unavailable",
2875
- typeof error?.message === "string" ? error.message : `SDK ${query} query failed.`,
2876
- );
2877
- }
2878
- return response;
2879
- } finally {
2880
- await client.close();
2881
- }
2968
+ if (!response) throw new SdkClientError("unavailable", `SDK ${query} query returned an invalid response.`);
2969
+ return sdkResponse(response, query);
2882
2970
  }
2883
2971
 
2884
2972
  async function readCompleteQ12Snapshot(session: Record<string, unknown>): Promise<{
@@ -2887,25 +2975,36 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
2887
2975
  complete: boolean;
2888
2976
  reason: "pagination_malformed" | "query_unavailable" | null;
2889
2977
  }> {
2890
- const deadline = Date.now() + 5_000;
2978
+ const deadline = Date.now() + Q12_SNAPSHOT_BUDGET_MS;
2891
2979
  const items: unknown[] = [];
2892
2980
  const cursors = new Set<string>();
2893
2981
  let cursor: string | undefined;
2894
2982
  let revision: string | null = null;
2895
2983
  let bytes = 0;
2896
- let client: SdkClient;
2984
+ let target: CoordinatorSessionAttachment;
2897
2985
  try {
2898
- const endpoint = await resolveSessionEndpoint(session);
2899
- client = await (services.connectSdk ?? ((url, token) => SdkClient.connect(url, token)))(
2900
- endpoint.url,
2901
- endpoint.token,
2902
- );
2986
+ target = await resolveSessionAttachment(session);
2903
2987
  } catch {
2904
2988
  return { items: [], revision, complete: false, reason: "query_unavailable" };
2905
2989
  }
2906
2990
  try {
2907
2991
  for (let pageCount = 0; pageCount < 8 && Date.now() <= deadline; pageCount++) {
2908
- const response = asRecord(await client.query("Q12", {}, cursor));
2992
+ // The snapshot deadline is only checked between pages, so each page has to
2993
+ // carry what is left of it. Without that budget the page inherits the
2994
+ // Router's session reply budget and one wedged page outlives the whole
2995
+ // snapshot bound this function promises its callers.
2996
+ const response = asRecord(
2997
+ await requestSessionFrame(
2998
+ target,
2999
+ {
3000
+ type: "query_request",
3001
+ query: "Q12",
3002
+ input: {},
3003
+ ...(cursor === undefined ? {} : { cursor }),
3004
+ },
3005
+ { timeoutMs: Math.max(1, deadline - Date.now()) },
3006
+ ),
3007
+ );
2909
3008
  if (response?.ok !== true) return { items: [], revision, complete: false, reason: "query_unavailable" };
2910
3009
  const page = asRecord(response.page);
2911
3010
  const pageItems = page?.items;
@@ -2946,8 +3045,6 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
2946
3045
  return { items: [], revision, complete: false, reason: "pagination_malformed" };
2947
3046
  } catch {
2948
3047
  return { items: [], revision, complete: false, reason: "query_unavailable" };
2949
- } finally {
2950
- await client.close().catch(() => undefined);
2951
3048
  }
2952
3049
  }
2953
3050
 
@@ -2992,6 +3089,11 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
2992
3089
  return error?.code === UNOBSERVED_COMPENSATION_CODE;
2993
3090
  }
2994
3091
 
3092
+ function isRouterRequestAmbiguous(response: Record<string, unknown>): boolean {
3093
+ if (response.ok !== false) return false;
3094
+ return asRecord(response.error)?.code === "ambiguous";
3095
+ }
3096
+
2995
3097
  function sdkError(error: unknown): Record<string, unknown> {
2996
3098
  if (error instanceof SdkClientError) return { ok: false, error: { code: error.code, message: error.message } };
2997
3099
  return {
@@ -3006,6 +3108,20 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
3006
3108
  return key;
3007
3109
  }
3008
3110
 
3111
+ /**
3112
+ * Path for the lock owner file that serializes idempotency mutations.
3113
+ *
3114
+ * Lock artifacts (owner records, transition markers, quarantine placeholders) are
3115
+ * created as siblings of the locked path by {@link withSessionStateFileLock}.
3116
+ * The record directory must enumerate as parseable records only, so the lock
3117
+ * path lives in a sibling `idempotency-locks` directory: its artifacts can never
3118
+ * appear inside `idempotency/`, which would surface as
3119
+ * `JSON Parse error: Unexpected EOF` during replay or enumeration.
3120
+ */
3121
+ function idempotencyLockFile(idempotencyKey: string): string {
3122
+ const keyDigest = createHash("sha256").update(idempotencyKey).digest("hex");
3123
+ return path.join(namespaceDir, "idempotency-locks", `${keyDigest}.json`);
3124
+ }
3009
3125
  function idempotencyFile(idempotencyKey: string): string {
3010
3126
  const keyDigest = createHash("sha256").update(idempotencyKey).digest("hex");
3011
3127
  return path.join(namespaceDir, "idempotency", `${keyDigest}.json`);
@@ -3021,8 +3137,8 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
3021
3137
  * the remote effect settled. A tool may declare such a response nonterminal,
3022
3138
  * which returns it to this caller while the receipt stays `in_progress`, so
3023
3139
  * an exact same-key retry re-runs the observation under the same request
3024
- * digest. The default declares nothing nonterminal, so every other tool keeps
3025
- * its existing caching, conflict, and replay behaviour unchanged.
3140
+ * digest. Router post-send ambiguity is nonterminal by default; every other
3141
+ * tool-specific exception is supplied by its handler.
3026
3142
  */
3027
3143
  async function withToolIdempotency(
3028
3144
  tool: string,
@@ -3030,14 +3146,15 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
3030
3146
  canonicalArgs: Record<string, unknown>,
3031
3147
  operation: () => Promise<Record<string, unknown>>,
3032
3148
  recoverInProgress = false,
3033
- isNonterminal: (response: Record<string, unknown>) => boolean = () => false,
3149
+ isNonterminal: (response: Record<string, unknown>) => boolean = isRouterRequestAmbiguous,
3034
3150
  ): Promise<Record<string, unknown>> {
3035
3151
  const keyDigest = createHash("sha256").update(idempotencyKey).digest("hex");
3036
3152
  const requestDigest = createHash("sha256")
3037
3153
  .update(canonicalJson({ tool, args: canonicalArgs }))
3038
3154
  .digest("hex");
3039
3155
  const file = idempotencyFile(idempotencyKey);
3040
- return await withSessionStateLock(file, async () => {
3156
+ const lockFile = idempotencyLockFile(idempotencyKey);
3157
+ return await withSessionStateLock(lockFile, async () => {
3041
3158
  const existingFile = await readCoordinatorIdempotencyFile(file);
3042
3159
  if (existingFile.kind === "corrupt")
3043
3160
  return {
@@ -3144,7 +3261,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
3144
3261
 
3145
3262
  let client: SdkClient;
3146
3263
  try {
3147
- client = await (services.connectSdk ?? ((url, token) => SdkClient.connect(url, token)))(
3264
+ client = await (services.connectBroker ?? ((url, token) => SdkClient.connect(url, token)))(
3148
3265
  discovery.url,
3149
3266
  discovery.token,
3150
3267
  );
@@ -3161,15 +3278,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
3161
3278
  ...(timeoutMs === undefined ? {} : { timeoutMs }),
3162
3279
  });
3163
3280
  } catch (error) {
3164
- const mapped = toCoordinatorBrokerError("request", error);
3165
- if (
3166
- isLifecycleOperation(operation) &&
3167
- mapped.code === "uncertain_after_send" &&
3168
- mapped.details &&
3169
- typeof mapped.details === "object"
3170
- )
3171
- result = await client.lookupLifecycle(mapped.details as SdkSentRecord);
3172
- else requestError = mapped;
3281
+ requestError = toCoordinatorBrokerError("request", error);
3173
3282
  }
3174
3283
  try {
3175
3284
  await client.close();
@@ -3196,28 +3305,29 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
3196
3305
  cwd: string,
3197
3306
  input: Record<string, unknown>,
3198
3307
  ): Promise<Record<string, unknown>> {
3199
- const aggregate: Record<string, unknown> = {};
3200
- const sessions: unknown[] = [];
3201
- let cursor: string | undefined;
3202
- for (let pageCount = 0; pageCount < MAX_COORDINATOR_SESSION_LIST_PAGES; pageCount++) {
3203
- const page = brokerResult(
3204
- await brokerSession(cwd, "session.list", {
3205
- ...input,
3206
- ...(cursor === undefined ? {} : { cursor }),
3207
- }),
3308
+ try {
3309
+ const pages = await traverseSessionList(
3310
+ input,
3311
+ async pageInput => {
3312
+ const response = await brokerSession(cwd, "session.list", pageInput);
3313
+ if (asRecord(response)?.ok === false) brokerResult(response);
3314
+ return response;
3315
+ },
3316
+ response => sessionListPageFromResponse(response),
3208
3317
  );
3209
- for (const [key, value] of Object.entries(page)) {
3210
- if (key !== "sessions" && key !== "continuationCursor") aggregate[key] = value;
3318
+ const aggregate: Record<string, unknown> = {};
3319
+ const sessions: unknown[] = [];
3320
+ for (const { page } of pages) {
3321
+ for (const [key, value] of Object.entries(page)) {
3322
+ if (key !== "sessions" && key !== "continuationCursor") aggregate[key] = value;
3323
+ }
3324
+ sessions.push(...page.sessions);
3211
3325
  }
3212
- if (Array.isArray(page.sessions)) sessions.push(...page.sessions);
3213
- const nextCursor =
3214
- typeof page.continuationCursor === "string" && page.continuationCursor.length > 0
3215
- ? page.continuationCursor
3216
- : undefined;
3217
- if (!nextCursor) return { ...aggregate, sessions };
3218
- cursor = nextCursor;
3326
+ return { ...aggregate, sessions };
3327
+ } catch (error) {
3328
+ if (error instanceof SessionListTraversalError) throw new SdkClientError("protocol_error", error.message);
3329
+ throw error;
3219
3330
  }
3220
- throw new SdkClientError("protocol_error", "session.list exceeded the page budget.");
3221
3331
  }
3222
3332
 
3223
3333
  async function canonicalBrokerWorkspace(cwd: string): Promise<string> {
@@ -3295,123 +3405,29 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
3295
3405
  return { workspace: match.workspace, endpointGeneration, endpointIncarnation };
3296
3406
  }
3297
3407
 
3298
- async function exactBrokerSessionBinding(
3299
- sessionId: string,
3300
- workspace: string,
3301
- idempotencyKey?: string,
3302
- ): Promise<BrokerSessionAuthority & { endpoint: { url: string; token: string } }> {
3303
- const authority = await exactBrokerSessionAuthority(sessionId, workspace);
3304
- const endpointRecord = brokerResult(
3305
- await brokerSession(
3306
- workspace,
3307
- "session.get_endpoint",
3308
- {
3309
- sessionId,
3310
- endpointGeneration: authority.endpointGeneration,
3311
- endpointIncarnation: authority.endpointIncarnation,
3312
- },
3313
- idempotencyKey,
3314
- ),
3315
- );
3316
- const url = optionalString(endpointRecord.url);
3317
- const token = optionalString(endpointRecord.token);
3318
- if (!url || !token)
3319
- throw new SdkClientError("endpoint_stale", "Broker returned an invalid incarnation-bound endpoint.");
3320
- return { ...authority, endpoint: { url, token } };
3321
- }
3322
-
3323
- async function resolveSessionEndpoint(
3324
- session: Record<string, unknown>,
3325
- idempotencyKey?: string,
3326
- ): Promise<{ url: string; token: string }> {
3327
- const sessionId = optionalString(session.session_id) ?? optionalString(session.sessionId);
3328
- const cwd = optionalString(session.cwd);
3329
- const persistedWorkspace = optionalString(session.broker_workspace);
3330
- const persistedGeneration =
3331
- typeof session.endpoint_generation === "number" &&
3332
- Number.isSafeInteger(session.endpoint_generation) &&
3333
- session.endpoint_generation > 0
3334
- ? session.endpoint_generation
3335
- : null;
3336
- const persistedIncarnation = optionalString(session.endpoint_incarnation);
3337
- if (!sessionId || !cwd || !persistedWorkspace || persistedGeneration === null || !persistedIncarnation)
3338
- throw new SdkClientError("not_found", "Coordinator session has no incarnation-bound broker identity.");
3339
- const workspace = await canonicalBrokerWorkspace(cwd);
3340
- if (!sameCanonicalPath(workspace, persistedWorkspace, platform))
3341
- throw new SdkClientError("endpoint_stale", "Coordinator session workspace binding is stale.");
3342
- const binding = await exactBrokerSessionBinding(sessionId, workspace, idempotencyKey);
3343
- if (binding.endpointGeneration !== persistedGeneration || binding.endpointIncarnation !== persistedIncarnation)
3344
- throw new SdkClientError("endpoint_stale", "Coordinator session endpoint incarnation is stale.");
3345
- return binding.endpoint;
3346
- }
3347
-
3348
- /**
3349
- * The same incarnation-bound authority `resolveSessionEndpoint` requires,
3350
- * plus the exact endpoint generation the activation frame has to name. A
3351
- * session whose endpoint rolled or whose workspace binding drifted is refused
3352
- * here, before any activation is attempted.
3353
- */
3354
- async function resolveSessionActivationTarget(
3355
- session: Record<string, unknown>,
3356
- idempotencyKey?: string,
3357
- ): Promise<{ endpoint: { url: string; token: string }; endpointGeneration: number }> {
3358
- const sessionId = optionalString(session.session_id) ?? optionalString(session.sessionId);
3359
- const cwd = optionalString(session.cwd);
3360
- const persistedWorkspace = optionalString(session.broker_workspace);
3361
- const persistedGeneration =
3362
- typeof session.endpoint_generation === "number" &&
3363
- Number.isSafeInteger(session.endpoint_generation) &&
3364
- session.endpoint_generation > 0
3365
- ? session.endpoint_generation
3366
- : null;
3367
- const persistedIncarnation = optionalString(session.endpoint_incarnation);
3368
- if (!sessionId || !cwd || !persistedWorkspace || persistedGeneration === null || !persistedIncarnation)
3369
- throw new SdkClientError("not_found", "Coordinator session has no incarnation-bound broker identity.");
3370
- const workspace = await canonicalBrokerWorkspace(cwd);
3371
- if (!sameCanonicalPath(workspace, persistedWorkspace, platform))
3372
- throw new SdkClientError("endpoint_stale", "Coordinator session workspace binding is stale.");
3373
- const binding = await exactBrokerSessionBinding(sessionId, workspace, idempotencyKey);
3374
- if (binding.endpointGeneration !== persistedGeneration || binding.endpointIncarnation !== persistedIncarnation)
3375
- throw new SdkClientError("endpoint_stale", "Coordinator session endpoint incarnation is stale.");
3376
- return { endpoint: binding.endpoint, endpointGeneration: binding.endpointGeneration };
3408
+ async function exactBrokerSessionBinding(sessionId: string, workspace: string): Promise<BrokerSessionAuthority> {
3409
+ return await exactBrokerSessionAuthority(sessionId, workspace);
3377
3410
  }
3378
3411
 
3379
3412
  /**
3380
- * Ask a prepared session to publish its withheld readiness.
3381
- *
3382
- * The Coordinator never writes a chat mapping and never fakes a readiness
3383
- * signal: it proves exact endpoint authority, then delegates to the same
3384
- * activation exchange the `gjc notify activate-thread` CLI uses. The session's
3385
- * own activation gate remains the authority on whether a binding exists.
3413
+ * Ask a prepared session to publish its withheld readiness through the
3414
+ * Router-owned attachment. Endpoint credentials and SDK clients never leave
3415
+ * SessionRouter.
3386
3416
  */
3387
3417
  async function activatePreparedCoordinatorSession(
3388
3418
  session: Record<string, unknown>,
3389
3419
  sessionId: string,
3390
- idempotencyKey: string,
3420
+ _idempotencyKey: string,
3391
3421
  ): Promise<ActivatedPreparedSession> {
3392
- const target = await resolveSessionActivationTarget(session, idempotencyKey);
3393
- let client: SdkClient;
3394
- try {
3395
- client = await (services.connectSdk ?? ((url, token) => SdkClient.connect(url, token)))(
3396
- target.endpoint.url,
3397
- target.endpoint.token,
3398
- );
3399
- } catch {
3400
- // Nothing was sent, so no activation can have been applied.
3401
- throw new SessionActivationError("activation_unavailable", "The session endpoint could not be reached.");
3402
- }
3403
- try {
3404
- return await requestPreparedSessionActivation(
3405
- {
3406
- request: async frame => (await client.request(frame)) as Record<string, unknown>,
3407
- close: async () => await client.close(),
3408
- },
3409
- sessionId,
3410
- target.endpointGeneration,
3411
- );
3412
- } finally {
3413
- await client.close().catch(() => undefined);
3414
- }
3422
+ const target = await resolveSessionAttachment(session);
3423
+ return await requestPreparedSessionActivation(
3424
+ {
3425
+ request: async frame => await requestSessionFrame(target, frame),
3426
+ close: async () => {},
3427
+ },
3428
+ sessionId,
3429
+ target.generation,
3430
+ );
3415
3431
  }
3416
3432
 
3417
3433
  async function listSessions(cwd?: string): Promise<Array<Record<string, unknown>>> {
@@ -3528,16 +3544,49 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
3528
3544
  await advanceDeletion(questionPaths, deletionId, "broker_closed");
3529
3545
 
3530
3546
  try {
3531
- await exactBrokerSessionAuthority(id, workspace);
3532
- return { ok: false, reason: "endpoint_stale", closed: false };
3547
+ const listing = await paginatedBrokerSessionList(workspace, { cwd: workspace });
3548
+ const rows = jsonRecords(
3549
+ Array.isArray((listing as Record<string, unknown>).sessions)
3550
+ ? ((listing as Record<string, unknown>).sessions as unknown[])
3551
+ : [],
3552
+ );
3553
+ const candidates: Array<Record<string, unknown>> = [];
3554
+ for (const row of rows) {
3555
+ if (brokerSessionId(row) !== id) continue;
3556
+ const declaredWorkspace = brokerSessionScope(row);
3557
+ if (!declaredWorkspace) continue;
3558
+ let canonicalDeclared: string;
3559
+ try {
3560
+ canonicalDeclared = await canonicalBrokerWorkspace(declaredWorkspace);
3561
+ } catch {
3562
+ continue;
3563
+ }
3564
+ if (sameCanonicalPath(canonicalDeclared, workspace, platform)) candidates.push(row);
3565
+ }
3566
+ if (candidates.length === 0) {
3567
+ // No retained row: proven deletion (session_deleted) after successful close.
3568
+ } else if (candidates.length !== 1) {
3569
+ return { ok: false, reason: "close_failed", detail: "endpoint_stale", closed: false };
3570
+ } else {
3571
+ const row = candidates[0]!;
3572
+ const gen = brokerEndpointGeneration(row);
3573
+ const inc = brokerEndpointIncarnation(row, id);
3574
+ const genMatches = gen === persistedGeneration;
3575
+ const incMatches = inc === persistedIncarnation;
3576
+ if (!genMatches || !incMatches) return { ok: false, reason: "endpoint_stale", closed: false };
3577
+ const isAmbiguous = row.ambiguous === true;
3578
+ const isTerminalUncertain =
3579
+ row.terminalUncertain === true || (row as Record<string, unknown>).terminal_uncertain === true;
3580
+ const isTerminal = row.terminal === true;
3581
+ const isLive = row.live === true;
3582
+ if (isAmbiguous || isTerminalUncertain)
3583
+ return { ok: false, reason: "close_failed", detail: "endpoint_stale", closed: false };
3584
+ if (!isTerminal || isLive) return { ok: false, reason: "endpoint_stale", closed: false };
3585
+ }
3533
3586
  } catch (error) {
3534
- if (!(error instanceof SdkClientError) || error.code !== "not_found")
3535
- return {
3536
- ok: false,
3537
- reason: "close_failed",
3538
- detail: error instanceof SdkClientError ? error.code : "unavailable",
3539
- closed: false,
3540
- };
3587
+ if (error instanceof SdkClientError)
3588
+ return { ok: false, reason: "close_failed", detail: error.code, closed: false };
3589
+ return { ok: false, reason: "close_failed", detail: "unavailable", closed: false };
3541
3590
  }
3542
3591
  await fs.rm(sessionFile(id), { force: true });
3543
3592
  await fs.rm(sessionStateFile(namespaceDir, id), { force: true });
@@ -3771,9 +3820,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
3771
3820
  const existing = transaction.requests.prompts[keyDigest];
3772
3821
  if (existing) {
3773
3822
  if (existing.request_digest !== requestDigest) throw new Error("idempotency_conflict");
3774
- if (existing.phase === "remote_started" || existing.phase === "uncertain")
3775
- throw new Error("terminal_uncertain");
3776
- return;
3823
+ if (existing.phase === "uncertain") throw new Error("terminal_uncertain");
3824
+ // The host deduplicates this key, so a post-send Router ambiguity may re-dispatch it to recover the receipt.
3825
+ if (existing.phase === "remote_started") {
3826
+ existing.updated_at = new Date().toISOString();
3827
+ return;
3828
+ }
3777
3829
  }
3778
3830
  const now = new Date().toISOString();
3779
3831
  transaction.requests.prompts[keyDigest] = {
@@ -4170,7 +4222,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
4170
4222
  const creation = await claimProductionCreation(name, idempotencyKey, canonicalArgs);
4171
4223
  if (creation.request.phase === "completed" && creation.request.safe_response)
4172
4224
  return creation.request.safe_response;
4173
- const binding = await exactBrokerSessionBinding(sessionId, cwd, idempotencyKey);
4225
+ const binding = await exactBrokerSessionBinding(sessionId, cwd);
4174
4226
  const session = normalizeSession({
4175
4227
  session_id: sessionId,
4176
4228
  cwd,
@@ -4555,7 +4607,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
4555
4607
  message: "Coordinator session is bound to another workspace.",
4556
4608
  },
4557
4609
  };
4558
- const binding = await exactBrokerSessionBinding(sessionId, canonicalCwd, idempotencyKey);
4610
+ const binding = await exactBrokerSessionBinding(sessionId, canonicalCwd);
4559
4611
  if (
4560
4612
  !sameCanonicalPath(
4561
4613
  optionalString(existing.broker_workspace) ?? "",
@@ -4619,7 +4671,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
4619
4671
  const createdCwd = await canonicalBrokerWorkspace(
4620
4672
  optionalString(created.cwd) ?? canonicalCwd,
4621
4673
  );
4622
- const binding = await exactBrokerSessionBinding(sessionId, createdCwd, idempotencyKey);
4674
+ const binding = await exactBrokerSessionBinding(sessionId, createdCwd);
4623
4675
  session = normalizeSession({
4624
4676
  session_id: sessionId,
4625
4677
  cwd: createdCwd,
@@ -4875,7 +4927,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
4875
4927
  }
4876
4928
  sessionId = safeExternalId("session", created.sessionId ?? created.session_id);
4877
4929
  const sessionCwd = await canonicalBrokerWorkspace(optionalString(created.cwd) ?? cwd);
4878
- const binding = await exactBrokerSessionBinding(sessionId, sessionCwd, idempotencyKey);
4930
+ const binding = await exactBrokerSessionBinding(sessionId, sessionCwd);
4879
4931
  session = normalizeSession({
4880
4932
  session_id: sessionId,
4881
4933
  cwd: sessionCwd,
@@ -4956,7 +5008,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
4956
5008
  return response;
4957
5009
  },
4958
5010
  true,
4959
- isUnobservedCompensation,
5011
+ response => isUnobservedCompensation(response) || isRouterRequestAmbiguous(response),
4960
5012
  );
4961
5013
  }
4962
5014
  if (name === "gjc_coordinator_activate_session") {
@@ -5055,7 +5107,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
5055
5107
  * `already` rather than publishing readiness twice.
5056
5108
  */
5057
5109
  true,
5058
- isUnknownActivationOutcome,
5110
+ response => isUnknownActivationOutcome(response) || isRouterRequestAmbiguous(response),
5059
5111
  );
5060
5112
  }
5061
5113
  if (name === "gjc_coordinator_send_prompt") {
@@ -5145,6 +5197,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
5145
5197
  session_state: publicCoordinatorSessionState(await readSessionState(namespaceDir, sessionId)),
5146
5198
  };
5147
5199
  }),
5200
+ true,
5148
5201
  );
5149
5202
  }
5150
5203
  if (name === "gjc_coordinator_read_turn") {
@@ -5713,7 +5766,19 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
5713
5766
  return { jsonrpc: "2.0", id, error: { code: -32601, message: `unknown_method:${request.method}` } };
5714
5767
  }
5715
5768
 
5716
- return { config, callTool, handleJsonRpc, handle: handleJsonRpc, reapSession, sessionReaper };
5769
+ return {
5770
+ config,
5771
+ callTool,
5772
+ handleJsonRpc,
5773
+ handle: handleJsonRpc,
5774
+ reapSession,
5775
+ sessionReaper,
5776
+ router,
5777
+ close: async () => {
5778
+ sessionReaper.stop();
5779
+ await router.stop();
5780
+ },
5781
+ };
5717
5782
  }
5718
5783
 
5719
5784
  function legacyToolResult(payload: unknown): { content: Array<{ type: "text"; text: string }>; isError: boolean } {
@@ -5754,11 +5819,15 @@ export async function handleCoordinatorMcpRequest(
5754
5819
  const params = (request.params ?? {}) as { name?: string; arguments?: Record<string, unknown> };
5755
5820
  const args = params.arguments ?? {};
5756
5821
  const server = createCoordinatorMcpServer({ env: options.env ?? process.env });
5757
- return {
5758
- jsonrpc: "2.0",
5759
- id: request.id ?? null,
5760
- result: legacyToolResult(await server.callTool(params.name ?? "", args)),
5761
- };
5822
+ try {
5823
+ return {
5824
+ jsonrpc: "2.0",
5825
+ id: request.id ?? null,
5826
+ result: legacyToolResult(await server.callTool(params.name ?? "", args)),
5827
+ };
5828
+ } finally {
5829
+ await server.close();
5830
+ }
5762
5831
  }
5763
5832
 
5764
5833
  export interface PumpCoordinatorOptions {
@@ -5982,6 +6051,6 @@ export async function runCoordinatorMcpStdio(options: CoordinatorMcpServerOption
5982
6051
  },
5983
6052
  );
5984
6053
  } finally {
5985
- server.sessionReaper.stop();
6054
+ await server.close();
5986
6055
  }
5987
6056
  }