@gajae-code/coding-agent 0.17.1 → 0.17.4

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 (375) hide show
  1. package/CHANGELOG.md +266 -0
  2. package/dist/types/async/job-manager.d.ts +7 -6
  3. package/dist/types/cli/daemon-cli.d.ts +3 -1
  4. package/dist/types/cli/github-release.d.ts +17 -0
  5. package/dist/types/cli/public-command-entry.d.ts +56 -0
  6. package/dist/types/cli/public-command-errors.d.ts +126 -0
  7. package/dist/types/cli/public-command-evidence.d.ts +99 -0
  8. package/dist/types/cli/public-command-help.d.ts +65 -0
  9. package/dist/types/cli/public-command-registry.d.ts +69 -0
  10. package/dist/types/cli/session-picker.d.ts +1 -1
  11. package/dist/types/cli/setup-cli.d.ts +1 -0
  12. package/dist/types/cli/skills-cli.d.ts +5 -0
  13. package/dist/types/cli.d.ts +3 -0
  14. package/dist/types/commands/daemon.d.ts +4 -50
  15. package/dist/types/commands/sdk.d.ts +20 -37
  16. package/dist/types/commands/setup.d.ts +3 -0
  17. package/dist/types/commands/skills.d.ts +9 -0
  18. package/dist/types/config/autorouting-tier-map.d.ts +24 -0
  19. package/dist/types/config/file-lock.d.ts +10 -1
  20. package/dist/types/config/model-profile-activation.d.ts +63 -4
  21. package/dist/types/config/model-registry.d.ts +20 -2
  22. package/dist/types/config/settings-schema.d.ts +11 -0
  23. package/dist/types/coordinator-mcp/question-state.d.ts +6 -1
  24. package/dist/types/coordinator-mcp/server.d.ts +3 -0
  25. package/dist/types/daemon/operator-contract.d.ts +5 -1
  26. package/dist/types/eval/js/worker-protocol.d.ts +2 -0
  27. package/dist/types/extensibility/extensions/types.d.ts +12 -0
  28. package/dist/types/extensibility/gjc-plugins/registry.d.ts +4 -0
  29. package/dist/types/extensibility/runtime-skill-discovery.d.ts +18 -0
  30. package/dist/types/extensibility/shared-events.d.ts +3 -1
  31. package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
  32. package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
  33. package/dist/types/internal-urls/index.d.ts +1 -0
  34. package/dist/types/internal-urls/parse.d.ts +9 -1
  35. package/dist/types/main.d.ts +22 -0
  36. package/dist/types/modes/acp/acp-agent.d.ts +94 -1
  37. package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
  38. package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
  39. package/dist/types/modes/components/session-selector.d.ts +5 -2
  40. package/dist/types/modes/components/thinking-selector.d.ts +2 -1
  41. package/dist/types/modes/components/user-message-selector.d.ts +5 -2
  42. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  43. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  44. package/dist/types/modes/interactive-mode.d.ts +1 -1
  45. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
  46. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
  47. package/dist/types/modes/types.d.ts +2 -2
  48. package/dist/types/runtime/process-lifecycle.d.ts +2 -0
  49. package/dist/types/runtime-mcp/manager.d.ts +56 -0
  50. package/dist/types/runtime-mcp/pool.d.ts +17 -0
  51. package/dist/types/runtime-mcp/redaction.d.ts +20 -0
  52. package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
  53. package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
  54. package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
  55. package/dist/types/sdk/acp/adapter.d.ts +27 -0
  56. package/dist/types/sdk/acp/final-text.d.ts +8 -0
  57. package/dist/types/sdk/broker/broker.d.ts +13 -2
  58. package/dist/types/sdk/broker/ensure.d.ts +46 -1
  59. package/dist/types/sdk/broker/identity.d.ts +3 -1
  60. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
  61. package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
  62. package/dist/types/sdk/broker/runtime.d.ts +1 -1
  63. package/dist/types/sdk/broker/session-index.d.ts +22 -3
  64. package/dist/types/sdk/broker/spawn-authority.d.ts +4 -0
  65. package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
  66. package/dist/types/sdk/bus/index.d.ts +19 -0
  67. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
  68. package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
  69. package/dist/types/sdk/cli/master-cli.d.ts +6 -10
  70. package/dist/types/sdk/cli/rows.d.ts +19 -1
  71. package/dist/types/sdk/cli/session-cli.d.ts +22 -5
  72. package/dist/types/sdk/client/client.d.ts +2 -0
  73. package/dist/types/sdk/guides/cli.d.ts +3 -4
  74. package/dist/types/sdk/guides/verify.d.ts +2 -0
  75. package/dist/types/sdk/host/host.d.ts +9 -0
  76. package/dist/types/sdk/host/session-runtime.d.ts +31 -0
  77. package/dist/types/sdk/host/types.d.ts +10 -0
  78. package/dist/types/sdk/index.d.ts +1 -0
  79. package/dist/types/sdk/prompt-deadline-flush.d.ts +83 -0
  80. package/dist/types/sdk/prompt-deadline-manager.d.ts +38 -1
  81. package/dist/types/sdk/prompt-failure.d.ts +30 -1
  82. package/dist/types/sdk/prompt-status.d.ts +8 -0
  83. package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
  84. package/dist/types/sdk/router/replay-retention-gap-cache.d.ts +13 -0
  85. package/dist/types/sdk/router/session-router.d.ts +2 -0
  86. package/dist/types/sdk/startup-capability.d.ts +2 -0
  87. package/dist/types/sdk/transport/relay.d.ts +1 -0
  88. package/dist/types/sdk/transport/serve-cli.d.ts +73 -2
  89. package/dist/types/sdk/transport/stdio.d.ts +5 -1
  90. package/dist/types/session/agent-session.d.ts +168 -21
  91. package/dist/types/session/fallback-chain-controller.d.ts +6 -6
  92. package/dist/types/session/fold-coordinator.d.ts +17 -3
  93. package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
  94. package/dist/types/session/session-manager.d.ts +64 -1
  95. package/dist/types/session/session-work-lease.d.ts +14 -0
  96. package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
  97. package/dist/types/setup/provider-onboarding.d.ts +81 -1
  98. package/dist/types/thinking.d.ts +3 -1
  99. package/dist/types/tools/bash.d.ts +37 -11
  100. package/dist/types/tools/job.d.ts +1 -1
  101. package/dist/types/tools/steer-fold.d.ts +31 -0
  102. package/dist/types/utils/git.d.ts +28 -2
  103. package/dist/types/utils/mupdf-embedded.d.ts +2 -0
  104. package/dist/types/utils/mupdf-wasm-embedded.d.ts +2 -0
  105. package/dist/types/utils/mupdf.d.ts +4 -0
  106. package/dist/types/utils/session-title-generation.d.ts +6 -0
  107. package/dist/types/utils/title-generator.d.ts +11 -2
  108. package/dist/types/web/scrapers/types.d.ts +3 -0
  109. package/package.json +18 -10
  110. package/scripts/build-binary.ts +9 -3
  111. package/scripts/compile-args.ts +0 -9
  112. package/scripts/embed-mupdf.ts +52 -0
  113. package/scripts/generate-sdk-operation-inventory.ts +31 -1
  114. package/scripts/vendor-markit.ts +196 -0
  115. package/src/async/job-manager.ts +60 -9
  116. package/src/cli/daemon-cli.ts +53 -18
  117. package/src/cli/github-release.ts +87 -5
  118. package/src/cli/mcp-cli.ts +38 -4
  119. package/src/cli/public-command-entry.ts +450 -0
  120. package/src/cli/public-command-errors.ts +581 -0
  121. package/src/cli/public-command-evidence.ts +717 -0
  122. package/src/cli/public-command-help.ts +360 -0
  123. package/src/cli/public-command-registry.ts +535 -0
  124. package/src/cli/session-picker.ts +2 -1
  125. package/src/cli/setup-cli.ts +9 -1
  126. package/src/cli/skills-cli.ts +46 -2
  127. package/src/cli.ts +61 -1
  128. package/src/commands/daemon.ts +65 -69
  129. package/src/commands/sdk.ts +232 -100
  130. package/src/commands/setup.ts +2 -0
  131. package/src/commands/skills.ts +11 -0
  132. package/src/config/autorouting-tier-map.ts +16 -0
  133. package/src/config/file-lock.ts +199 -68
  134. package/src/config/model-profile-activation.ts +406 -53
  135. package/src/config/model-registry.ts +204 -25
  136. package/src/config/settings-schema.ts +26 -2
  137. package/src/coordinator-mcp/policy.ts +10 -5
  138. package/src/coordinator-mcp/question-gate-codec.ts +17 -0
  139. package/src/coordinator-mcp/question-state.ts +175 -8
  140. package/src/coordinator-mcp/server.ts +528 -87
  141. package/src/coordinator-mcp/session-reaper.ts +6 -4
  142. package/src/crash/record-loader.ts +6 -2
  143. package/src/daemon/operator-contract.ts +26 -12
  144. package/src/debug/report-bundle.ts +114 -24
  145. package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
  146. package/src/eval/js/context-manager.ts +8 -8
  147. package/src/eval/js/worker-core.ts +9 -4
  148. package/src/eval/js/worker-protocol.ts +2 -0
  149. package/src/extensibility/extensions/runner.ts +52 -4
  150. package/src/extensibility/extensions/types.ts +10 -0
  151. package/src/extensibility/gjc-plugins/registry.ts +196 -37
  152. package/src/extensibility/runtime-skill-discovery.ts +61 -3
  153. package/src/extensibility/shared-events.ts +3 -1
  154. package/src/gjc-runtime/managed-owner-admission.ts +67 -50
  155. package/src/gjc-runtime/session-state-lock.ts +165 -24
  156. package/src/gjc-runtime/ultragoal-succession.ts +31 -3
  157. package/src/internal-urls/docs-index.generated.ts +21 -19
  158. package/src/internal-urls/embedded-protocol.ts +83 -0
  159. package/src/internal-urls/index.ts +1 -0
  160. package/src/internal-urls/parse.ts +21 -1
  161. package/src/internal-urls/router.ts +4 -2
  162. package/src/main.ts +123 -55
  163. package/src/modes/acp/acp-agent.ts +593 -47
  164. package/src/modes/acp/acp-event-mapper.ts +9 -0
  165. package/src/modes/acp/startup-provenance.ts +59 -0
  166. package/src/modes/components/custom-provider-wizard.ts +308 -7
  167. package/src/modes/components/model-selector.ts +31 -15
  168. package/src/modes/components/oauth-selector.ts +9 -0
  169. package/src/modes/components/session-selector.ts +47 -4
  170. package/src/modes/components/status-line/segments.ts +6 -1
  171. package/src/modes/components/thinking-selector.ts +7 -3
  172. package/src/modes/components/user-message-selector.ts +57 -15
  173. package/src/modes/controllers/command-controller.ts +58 -32
  174. package/src/modes/controllers/extension-ui-controller.ts +33 -7
  175. package/src/modes/controllers/input-controller.ts +4 -0
  176. package/src/modes/controllers/selector-controller.ts +177 -18
  177. package/src/modes/interactive-mode.ts +21 -7
  178. package/src/modes/runtime-init.ts +1 -0
  179. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
  180. package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
  181. package/src/modes/types.ts +2 -2
  182. package/src/prompts/agent-fragments/browser-aside.md +1 -0
  183. package/src/prompts/system/title-system.md +1 -1
  184. package/src/prompts/tools/bash.md +1 -1
  185. package/src/prompts/tools/job.md +1 -1
  186. package/src/prompts/tools/read.md +2 -0
  187. package/src/runtime/network-prewarm-service.test.ts +36 -1
  188. package/src/runtime/network-prewarm-service.ts +28 -3
  189. package/src/runtime/process-lifecycle.ts +22 -11
  190. package/src/runtime-mcp/config.ts +16 -1
  191. package/src/runtime-mcp/manager.ts +689 -55
  192. package/src/runtime-mcp/pool.test.ts +31 -0
  193. package/src/runtime-mcp/pool.ts +304 -28
  194. package/src/runtime-mcp/redaction.ts +42 -0
  195. package/src/runtime-mcp/startup-policy.ts +7 -0
  196. package/src/runtime-mcp/tool-bridge.ts +71 -0
  197. package/src/runtime-mcp/transports/http.ts +108 -25
  198. package/src/sdk/acp/adapter.ts +90 -9
  199. package/src/sdk/acp/final-text.ts +19 -2
  200. package/src/sdk/broker/broker.ts +378 -52
  201. package/src/sdk/broker/ensure.ts +97 -19
  202. package/src/sdk/broker/identity.ts +9 -1
  203. package/src/sdk/broker/lifecycle-ledger.ts +95 -31
  204. package/src/sdk/broker/lifecycle.ts +690 -141
  205. package/src/sdk/broker/runtime.ts +1 -1
  206. package/src/sdk/broker/session-index.ts +97 -4
  207. package/src/sdk/broker/spawn-authority.ts +35 -7
  208. package/src/sdk/broker/stderr-drainer.ts +59 -0
  209. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  210. package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
  211. package/src/sdk/bus/index.ts +721 -84
  212. package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
  213. package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
  214. package/src/sdk/bus/telegram-daemon.ts +10 -3
  215. package/src/sdk/cli/master-cli.ts +47 -36
  216. package/src/sdk/cli/rows.ts +30 -7
  217. package/src/sdk/cli/session-cli.ts +687 -184
  218. package/src/sdk/client/client.ts +18 -0
  219. package/src/sdk/guides/catalog.ts +2 -2
  220. package/src/sdk/guides/cli.ts +20 -62
  221. package/src/sdk/guides/verify.ts +14 -0
  222. package/src/sdk/host/host.ts +49 -5
  223. package/src/sdk/host/session-runtime.test.ts +636 -9
  224. package/src/sdk/host/session-runtime.ts +499 -118
  225. package/src/sdk/host/types.ts +12 -1
  226. package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
  227. package/src/sdk/host/websocket-transport.ts +40 -6
  228. package/src/sdk/index.ts +11 -0
  229. package/src/sdk/prompt-deadline-flush.ts +334 -0
  230. package/src/sdk/prompt-deadline-manager.ts +136 -4
  231. package/src/sdk/prompt-failure.ts +43 -1
  232. package/src/sdk/prompt-status.ts +8 -0
  233. package/src/sdk/prompt-tool-boundary.ts +121 -0
  234. package/src/sdk/protocol/operation-inventory.generated.json +106 -0
  235. package/src/sdk/router/replay-retention-gap-cache.ts +27 -0
  236. package/src/sdk/router/session-router.ts +31 -4
  237. package/src/sdk/session.ts +194 -16
  238. package/src/sdk/startup-capability.ts +1 -1
  239. package/src/sdk/transport/relay.ts +71 -6
  240. package/src/sdk/transport/serve-cli.ts +494 -49
  241. package/src/sdk/transport/socket.ts +12 -11
  242. package/src/sdk/transport/stdio.ts +7 -3
  243. package/src/session/agent-session.ts +2437 -400
  244. package/src/session/fallback-chain-controller.ts +6 -7
  245. package/src/session/fold-coordinator.ts +24 -5
  246. package/src/session/internal/managed-session-scope.ts +58 -18
  247. package/src/session/internal/managed-session-storage.ts +304 -11
  248. package/src/session/session-manager.ts +382 -44
  249. package/src/session/session-work-lease.ts +34 -0
  250. package/src/session-import/codex.ts +12 -3
  251. package/src/setup/model-onboarding-guidance.ts +1 -1
  252. package/src/setup/provider-onboarding.ts +397 -24
  253. package/src/slash-commands/builtin-registry.ts +258 -14
  254. package/src/task/index.ts +14 -2
  255. package/src/thinking.ts +21 -2
  256. package/src/tools/bash.ts +98 -78
  257. package/src/tools/fetch.ts +119 -20
  258. package/src/tools/job.ts +151 -25
  259. package/src/tools/output-meta.ts +15 -2
  260. package/src/tools/path-utils.ts +27 -3
  261. package/src/tools/read.ts +3 -3
  262. package/src/tools/steer-fold.ts +107 -0
  263. package/src/tools/tool-catalog.generated.ts +29 -2
  264. package/src/tools/tool-errors.ts +4 -0
  265. package/src/utils/git.ts +53 -3
  266. package/src/utils/markit.ts +55 -41
  267. package/src/utils/mupdf-assets.d.ts +4 -0
  268. package/src/utils/mupdf-embedded.ts +4 -0
  269. package/src/utils/mupdf-wasm-embedded.ts +3 -0
  270. package/src/utils/mupdf-wasm.ts +24 -2
  271. package/src/utils/mupdf.ts +129 -0
  272. package/src/utils/session-title-generation.ts +29 -0
  273. package/src/utils/title-generator.ts +46 -2
  274. package/src/web/scrapers/types.ts +24 -3
  275. package/vendor/markit-ai/LICENSE +21 -0
  276. package/vendor/markit-ai/MANIFEST.json +412 -0
  277. package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
  278. package/vendor/markit-ai/dist/commands/config.js +139 -0
  279. package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
  280. package/vendor/markit-ai/dist/commands/convert.js +113 -0
  281. package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
  282. package/vendor/markit-ai/dist/commands/formats.js +92 -0
  283. package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
  284. package/vendor/markit-ai/dist/commands/init.js +29 -0
  285. package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
  286. package/vendor/markit-ai/dist/commands/onboard.js +61 -0
  287. package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
  288. package/vendor/markit-ai/dist/commands/plugin.js +58 -0
  289. package/vendor/markit-ai/dist/config.d.ts +26 -0
  290. package/vendor/markit-ai/dist/config.js +42 -0
  291. package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
  292. package/vendor/markit-ai/dist/converters/audio.js +104 -0
  293. package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
  294. package/vendor/markit-ai/dist/converters/csv.js +83 -0
  295. package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
  296. package/vendor/markit-ai/dist/converters/docx.js +57 -0
  297. package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
  298. package/vendor/markit-ai/dist/converters/epub.js +128 -0
  299. package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
  300. package/vendor/markit-ai/dist/converters/github.js +148 -0
  301. package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
  302. package/vendor/markit-ai/dist/converters/html.js +30 -0
  303. package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
  304. package/vendor/markit-ai/dist/converters/image.js +124 -0
  305. package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
  306. package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
  307. package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
  308. package/vendor/markit-ai/dist/converters/iwork.js +391 -0
  309. package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
  310. package/vendor/markit-ai/dist/converters/json.js +21 -0
  311. package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
  312. package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
  313. package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
  314. package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
  315. package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
  316. package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
  317. package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
  318. package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
  319. package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
  320. package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
  321. package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
  322. package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
  323. package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
  324. package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
  325. package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
  326. package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
  327. package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
  328. package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
  329. package/vendor/markit-ai/dist/converters/pptx.js +289 -0
  330. package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
  331. package/vendor/markit-ai/dist/converters/rss.js +138 -0
  332. package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
  333. package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
  334. package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
  335. package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
  336. package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
  337. package/vendor/markit-ai/dist/converters/xml.js +18 -0
  338. package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
  339. package/vendor/markit-ai/dist/converters/yaml.js +17 -0
  340. package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
  341. package/vendor/markit-ai/dist/converters/zip.js +57 -0
  342. package/vendor/markit-ai/dist/index.d.ts +30 -0
  343. package/vendor/markit-ai/dist/index.js +26 -0
  344. package/vendor/markit-ai/dist/main.d.ts +2 -0
  345. package/vendor/markit-ai/dist/main.js +191 -0
  346. package/vendor/markit-ai/dist/markit.d.ts +45 -0
  347. package/vendor/markit-ai/dist/markit.js +267 -0
  348. package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
  349. package/vendor/markit-ai/dist/plugins/api.js +42 -0
  350. package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
  351. package/vendor/markit-ai/dist/plugins/index.js +3 -0
  352. package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
  353. package/vendor/markit-ai/dist/plugins/installer.js +176 -0
  354. package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
  355. package/vendor/markit-ai/dist/plugins/loader.js +61 -0
  356. package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
  357. package/vendor/markit-ai/dist/plugins/types.js +1 -0
  358. package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
  359. package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
  360. package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
  361. package/vendor/markit-ai/dist/providers/index.js +56 -0
  362. package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
  363. package/vendor/markit-ai/dist/providers/openai.js +76 -0
  364. package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
  365. package/vendor/markit-ai/dist/providers/types.js +1 -0
  366. package/vendor/markit-ai/dist/types.d.ts +36 -0
  367. package/vendor/markit-ai/dist/types.js +1 -0
  368. package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
  369. package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
  370. package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
  371. package/vendor/markit-ai/dist/utils/output.js +31 -0
  372. package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
  373. package/vendor/markit-ai/dist/utils/turndown.js +70 -0
  374. package/vendor/markit-ai/package.json +74 -0
  375. package/vendor/markit-ai.patch +49 -0
@@ -4,7 +4,7 @@ import * as fs from "node:fs/promises";
4
4
  import * as path from "node:path";
5
5
  import { getAgentDir, isKnownSinkPeerClosedError, logger } from "@gajae-code/utils";
6
6
  import { normalizePathForComparison, VERSION } from "@gajae-code/utils/dirs";
7
- import { withFileLock } from "../config/file-lock";
7
+ import { isFileLockAcquireTimeout, withFileLock } from "../config/file-lock";
8
8
  import {
9
9
  COORDINATOR_MCP_PROTOCOL_VERSION,
10
10
  COORDINATOR_MCP_SERVER_NAME,
@@ -27,6 +27,7 @@ import type { WorkflowGate, WorkflowGateQueryRecord } from "../modes/shared/agen
27
27
  import type { BrokerDiscovery } from "../sdk/broker/discovery";
28
28
  import { endpointIncarnation } from "../sdk/broker/endpoint-authority";
29
29
  import { type EnsureBrokerSettings, ensureBroker } from "../sdk/broker/ensure";
30
+ import { DEAD_HOST_HEARTBEAT_SILENCE_MS } from "../sdk/broker/session-index";
30
31
  import { lifecycleRequestTimeoutMs } from "../sdk/broker/startup-budget";
31
32
  import { UnsupportedStateVersionError } from "../sdk/broker/state-version";
32
33
  import { SdkClient, SdkClientError } from "../sdk/client/client";
@@ -39,6 +40,7 @@ import {
39
40
  SessionActivationError,
40
41
  } from "../sdk/session-activation";
41
42
  import { SessionListTraversalError, sessionListPageFromResponse, traverseSessionList } from "../sdk/session-list";
43
+ import { sanitizeSdkStartupMessage } from "../sdk/startup-capability";
42
44
  import {
43
45
  ackCodexWakeEvent,
44
46
  bindDelegateCodexHandoff,
@@ -144,6 +146,7 @@ import {
144
146
  recoverExpiredPublicDelivery,
145
147
  releasePublicDeliveryClaim,
146
148
  removeSessionTransaction,
149
+ repairLegacyReportIds,
147
150
  repairProjections,
148
151
  replaceCreationRetirementIntent,
149
152
  rotateClaimedCreationVerifier,
@@ -307,6 +310,9 @@ interface CoordinatorServices {
307
310
  canonicalizePath?: (value: string) => Promise<string>;
308
311
  codexTransportFactory?: CodexTransportFactory;
309
312
  eventWebhookDelivery?: WebhookDelivery;
313
+ /** Observable transitions for consumers coordinating with the filesystem-backed event wait. */
314
+ onCoordinatorEventWatchReady?: () => void;
315
+ onCoordinatorEventWake?: () => void;
310
316
  /** Test barrier invoked after an accepted prompt receipt is durable and before turn finalization. */
311
317
  afterPromptReceiptPersisted?: (sessionId: string) => void | Promise<void>;
312
318
  /** Test barrier invoked after an answer dispatch is claimed and before its final admission check. */
@@ -513,6 +519,11 @@ const OBSERVED_BROKER_FAILURE_CODES = new Set([
513
519
  const MISSING_FINAL_RESPONSE_ADVISORY = "completion_missing_final_response";
514
520
  const PROMPT_ACK_TIMEOUT_REASON = "runtime_prompt_ack_timeout";
515
521
  const DEFAULT_RUNTIME_PROMPT_ACK_TIMEOUT_MS = 10_000;
522
+ function coordinatorKnownSecrets(): string[] {
523
+ return Object.entries(process.env)
524
+ .filter(([name, value]) => value && /(?:token|secret|password|credential|api[_-]?key|auth)/iu.test(name))
525
+ .map(([, value]) => value!);
526
+ }
516
527
  /**
517
528
  * Pagination window for `readCompleteQ12Snapshot`: it stops starting pages once
518
529
  * this much time has passed and gives each page only the remainder as its reply
@@ -524,6 +535,26 @@ const Q12_SNAPSHOT_BUDGET_MS = 5_000;
524
535
  const MAX_RUNTIME_SESSIONS_PER_WATCH_PASS = 4;
525
536
  const MAX_Q12_ATTEMPTS_PER_WATCH_PASS = 2;
526
537
  const MAX_RUNTIME_PROMPT_ACK_TIMEOUT_MS = 5 * 60 * 1000;
538
+ /**
539
+ * A delete intent is superseded once another deletion for the same session and
540
+ * endpoint incarnation has completed: force eviction retires a stale endpoint under
541
+ * its own record and leaves behind the intent an earlier idle reap admitted.
542
+ */
543
+ function isSupersededDeletionIntent(
544
+ entry: NamespaceDeletionEntryV1,
545
+ deletions: readonly NamespaceDeletionEntryV1[],
546
+ ): boolean {
547
+ return (
548
+ entry.phase === "intent" &&
549
+ deletions.some(
550
+ other =>
551
+ other !== entry &&
552
+ other.session_id === entry.session_id &&
553
+ other.endpoint_incarnation === entry.endpoint_incarnation &&
554
+ other.phase === "completed",
555
+ )
556
+ );
557
+ }
527
558
  const ACTIVE_TURN_STATUSES = new Set<TurnStatus>(["delivering", "active", "waiting_for_answer", "completing"]);
528
559
  const TERMINAL_TURN_STATUSES = new Set<TurnStatus>(["completed", "failed", "cancelled", "superseded"]);
529
560
  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;
@@ -738,7 +769,8 @@ function toolSchema(
738
769
  if (name === "gjc_coordinator_await_turn") {
739
770
  return {
740
771
  name,
741
- description: "Poll a durable turn for a bounded time and return the same shape as read_turn.",
772
+ description:
773
+ "Poll a durable turn for a bounded time. Observation expiry returns wait_expired:true (legacy ok:false, reason:timeout) without an MCP error. Inspect turn.status for completion; await again without resending the prompt.",
742
774
  inputSchema: {
743
775
  type: "object",
744
776
  properties: {
@@ -1311,6 +1343,11 @@ interface CoordinatorToolIdempotencyRecord {
1311
1343
  request_digest: string;
1312
1344
  state: "in_progress" | "completed";
1313
1345
  response?: Record<string, unknown>;
1346
+ /** Reused-session delegate prompt-claim provenance, written under the key lock:
1347
+ * `false` when the attempt starts, `true` immediately before the canonical prompt
1348
+ * claim. A same-key retry may claim afresh only on an explicit `false`; `true` or
1349
+ * an absent marker (unknown provenance) keeps missing live state fail-closed. */
1350
+ delegate_prompt_claim_started?: boolean;
1314
1351
  admission?: DelegateAdmissionCheckpoint;
1315
1352
  delegate_response_pin?: DelegateResponsePinV1;
1316
1353
  created_at: string;
@@ -1390,7 +1427,24 @@ function boundedPublicValue(value: unknown, budget: { remaining: number }, depth
1390
1427
  : undefined;
1391
1428
  const code =
1392
1429
  typeof rawCode === "string" && Object.hasOwn(PUBLIC_ERROR_MESSAGES, rawCode) ? rawCode : "unavailable";
1393
- output[key] = { code, message: PUBLIC_ERROR_MESSAGES[code] };
1430
+ const rawDiagnostic =
1431
+ field && typeof field === "object" && !Array.isArray(field)
1432
+ ? (field as Record<string, unknown>).diagnostic
1433
+ : undefined;
1434
+ const boundedDiagnostic =
1435
+ typeof rawDiagnostic === "string" ? boundedPublicValue(rawDiagnostic, budget, depth + 1) : undefined;
1436
+ const rawMessage =
1437
+ field && typeof field === "object" && !Array.isArray(field)
1438
+ ? (field as Record<string, unknown>).message
1439
+ : undefined;
1440
+ output[key] = {
1441
+ code,
1442
+ message:
1443
+ code === "spawn_failed" && typeof rawMessage === "string" && boundedDiagnostic !== undefined
1444
+ ? boundedPublicValue(rawMessage, budget, depth + 1)
1445
+ : PUBLIC_ERROR_MESSAGES[code],
1446
+ ...(boundedDiagnostic === undefined ? {} : { diagnostic: boundedDiagnostic }),
1447
+ };
1394
1448
  } else {
1395
1449
  output[key] = sensitivePublicField(key) ? "[redacted]" : boundedPublicValue(field, budget, depth + 1);
1396
1450
  }
@@ -3188,18 +3242,24 @@ function waitForTurnStateChange(namespaceDir: string, turn: TurnRecord, timeoutM
3188
3242
  return deferred.promise;
3189
3243
  }
3190
3244
 
3191
- async function waitForCoordinatorEvents(namespaceDir: string, timeoutMs: number): Promise<void> {
3245
+ async function waitForCoordinatorEvents(
3246
+ namespaceDir: string,
3247
+ timeoutMs: number,
3248
+ onWatchReady?: () => void,
3249
+ onWake?: () => void,
3250
+ ): Promise<void> {
3192
3251
  const deferred = Promise.withResolvers<void>();
3193
3252
  const watchers: nodeFs.FSWatcher[] = [];
3194
3253
  let settled = false;
3195
- const finish = () => {
3254
+ const finish = (woke: boolean) => {
3196
3255
  if (settled) return;
3197
3256
  settled = true;
3198
3257
  for (const watcher of watchers) watcher.close();
3199
3258
  clearTimeout(timer);
3259
+ if (woke) onWake?.();
3200
3260
  deferred.resolve();
3201
3261
  };
3202
- const timer = setTimeout(finish, Math.max(timeoutMs, 0));
3262
+ const timer = setTimeout(() => finish(false), Math.max(timeoutMs, 0));
3203
3263
  timer.unref?.();
3204
3264
  const eventDir = eventsDir(namespaceDir);
3205
3265
  const watchedDirs = [
@@ -3213,16 +3273,17 @@ async function waitForCoordinatorEvents(namespaceDir: string, timeoutMs: number)
3213
3273
  try {
3214
3274
  const watcher = nodeFs.watch(dir, (_eventType, filename) => {
3215
3275
  if (dir === eventDir) {
3216
- if (filename === "event-journal.jsonl" || filename === "latest-seq.json") finish();
3276
+ if (filename === "event-journal.jsonl" || filename === "latest-seq.json") finish(true);
3217
3277
  return;
3218
3278
  }
3219
- if (typeof filename === "string" && filename.endsWith(".json")) finish();
3279
+ if (typeof filename === "string" && filename.endsWith(".json")) finish(true);
3220
3280
  });
3221
3281
  watchers.push(watcher);
3222
3282
  } catch {
3223
3283
  // Directory may not be watchable on this platform; the timeout remains a bounded fallback.
3224
3284
  }
3225
3285
  }
3286
+ onWatchReady?.();
3226
3287
  return deferred.promise;
3227
3288
  }
3228
3289
 
@@ -3845,7 +3906,25 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
3845
3906
  ): Promise<Map<string, string>> {
3846
3907
  const authorized = new Map<string, string>();
3847
3908
  for (const sessionId of new Set(sessionIds)) {
3848
- const transaction = await readSessionTransaction(questionPaths, sessionId);
3909
+ // A corrupt or unreadable peer session must never abort authorization for
3910
+ // every other session. Only the explicitly scoped session may propagate,
3911
+ // otherwise one bad WAL record makes namespace-wide reads permanently
3912
+ // unavailable instead of degrading to the sessions that are still sound.
3913
+ let transaction: CoordinatorSessionTransactionV1 | null;
3914
+ try {
3915
+ transaction = await readSessionTransaction(questionPaths, sessionId);
3916
+ } catch (error) {
3917
+ logger.warn("Coordinator authorization skipped unreadable session", {
3918
+ sessionId,
3919
+ error: error instanceof Error ? error.message : String(error),
3920
+ });
3921
+ // Only a structurally corrupt WAL is degradable for an unscoped
3922
+ // namespace sweep. Cancellation, I/O, and unexpected invariant errors
3923
+ // must remain visible instead of silently shrinking authorization.
3924
+ if (scopedSessionId === sessionId || !(error instanceof Error && error.message === "state_corrupt"))
3925
+ throw error;
3926
+ continue;
3927
+ }
3849
3928
  if (!transaction) {
3850
3929
  if (scopedSessionId === sessionId && !allowMissingSessionIds.has(sessionId))
3851
3930
  throw new Error("resource_gone");
@@ -4444,6 +4523,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
4444
4523
  questionId: string;
4445
4524
  eventId: string;
4446
4525
  }> = [];
4526
+ let pendingGateCount = 0;
4447
4527
  let q12Admitted = true;
4448
4528
  let liveInFlightAskAdmitted = false;
4449
4529
  let liveInFlightCandidate = false;
@@ -4533,10 +4613,19 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
4533
4613
  byRuntimeTurn.set(runtimeTurnId, owners);
4534
4614
  }
4535
4615
  for (const row of items as WorkflowGateQueryRecord[]) {
4536
- if (!row || typeof row !== "object" || row.tag !== "pending" || typeof row.gate_id !== "string") {
4616
+ if (!row || typeof row !== "object" || typeof row.gate_id !== "string") {
4537
4617
  diagnostic("invalid_gate_row");
4538
4618
  continue;
4539
4619
  }
4620
+ // Accepted and quarantined rows are durable diagnostics, not actionable
4621
+ // questions. They remain in Q12 so SDK clients can explain a completed or
4622
+ // lost gate, but reconciliation must not classify them as malformed.
4623
+ if (row.tag === "accepted" || row.tag === "quarantined") continue;
4624
+ if (row.tag !== "pending") {
4625
+ diagnostic("invalid_gate_row");
4626
+ continue;
4627
+ }
4628
+ pendingGateCount++;
4540
4629
  const gate = row as WorkflowGateQueryRecord & WorkflowGate;
4541
4630
  const authorityId = createHash("sha256")
4542
4631
  .update(
@@ -4782,7 +4871,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
4782
4871
  // even while an authenticated, acknowledged turn is still running.
4783
4872
  // Nonempty snapshots still require an admitted ask; all writer, waiting
4784
4873
  // token and terminal fences above remain authoritative.
4785
- if (!q12Admitted || (liveInFlightCandidate && items.length > 0 && !liveInFlightAskAdmitted))
4874
+ if (!q12Admitted || (liveInFlightCandidate && pendingGateCount > 0 && !liveInFlightAskAdmitted))
4786
4875
  return {
4787
4876
  ok: true,
4788
4877
  schema_version: 1,
@@ -5127,6 +5216,11 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
5127
5216
  return asRecord(response.error)?.code === "ambiguous";
5128
5217
  }
5129
5218
 
5219
+ function isTransientDelegateAuthorityFailure(response: Record<string, unknown>): boolean {
5220
+ if (response.ok !== false) return false;
5221
+ return asRecord(response.error)?.code === "unavailable";
5222
+ }
5223
+
5130
5224
  function publicErrorCode(code: unknown): string {
5131
5225
  return typeof code === "string" && Object.hasOwn(PUBLIC_ERROR_MESSAGES, code) ? code : "unavailable";
5132
5226
  }
@@ -5137,7 +5231,22 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
5137
5231
  const messageCode =
5138
5232
  error instanceof Error && Object.hasOwn(PUBLIC_ERROR_MESSAGES, error.message) ? error.message : undefined;
5139
5233
  const code = publicErrorCode(directCode ?? messageCode);
5140
- return { ok: false, error: { code, message: PUBLIC_ERROR_MESSAGES[code] } };
5234
+ const diagnostic =
5235
+ error instanceof SdkClientError && code === "spawn_failed"
5236
+ ? sanitizeSdkStartupMessage(error.message, coordinatorKnownSecrets())
5237
+ : undefined;
5238
+ const message =
5239
+ diagnostic && diagnostic !== PUBLIC_ERROR_MESSAGES[code]
5240
+ ? `${PUBLIC_ERROR_MESSAGES[code]} Cause: ${diagnostic}`
5241
+ : PUBLIC_ERROR_MESSAGES[code];
5242
+ return {
5243
+ ok: false,
5244
+ error: {
5245
+ code,
5246
+ message,
5247
+ ...(diagnostic && diagnostic !== PUBLIC_ERROR_MESSAGES[code] ? { diagnostic } : {}),
5248
+ },
5249
+ };
5141
5250
  }
5142
5251
  function sdkError(error: unknown): Record<string, unknown> {
5143
5252
  return publicError(error);
@@ -5718,10 +5827,55 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
5718
5827
  );
5719
5828
  }
5720
5829
 
5830
+ async function findPersistedBrokerAuthority(
5831
+ sessions: Array<Record<string, unknown>>,
5832
+ expected: {
5833
+ sessionId: string;
5834
+ workspace: string;
5835
+ endpointGeneration: number | null;
5836
+ endpointIncarnation: string | null;
5837
+ },
5838
+ ): Promise<Record<string, unknown> | undefined> {
5839
+ if (expected.endpointGeneration === null || expected.endpointIncarnation === null) return undefined;
5840
+ let canonicalExpectedWorkspace: string;
5841
+ try {
5842
+ canonicalExpectedWorkspace = await canonicalBrokerWorkspace(expected.workspace);
5843
+ } catch {
5844
+ return undefined;
5845
+ }
5846
+ for (const session of sessions) {
5847
+ if (brokerSessionId(session) !== expected.sessionId) continue;
5848
+ if (
5849
+ brokerEndpointGeneration(session) !== expected.endpointGeneration ||
5850
+ brokerEndpointIncarnation(session, expected.sessionId) !== expected.endpointIncarnation
5851
+ )
5852
+ continue;
5853
+ const declaredWorkspace = brokerSessionScope(session);
5854
+ if (declaredWorkspace === null) continue;
5855
+ try {
5856
+ const canonicalDeclaredWorkspace = await canonicalBrokerWorkspace(declaredWorkspace);
5857
+ if (sameCanonicalPath(canonicalDeclaredWorkspace, canonicalExpectedWorkspace, platform)) return session;
5858
+ } catch {
5859
+ // A missing or unreadable locator is not authority for this session.
5860
+ }
5861
+ }
5862
+ return undefined;
5863
+ }
5864
+
5721
5865
  type BrokerSessionAuthority = {
5722
5866
  workspace: string;
5723
5867
  endpointGeneration: number;
5724
5868
  endpointIncarnation: string;
5869
+ /** Host liveness the broker reports for the indexed row; absent when unreported. */
5870
+ live?: boolean;
5871
+ /** Last host heartbeat the broker recorded for the row (epoch ms); absent when unreported. */
5872
+ lastHeartbeatAt?: number;
5873
+ /** True when more than one unresolved state-root authority claims this session. */
5874
+ ambiguous?: boolean;
5875
+ /** True when teardown ownership is unresolved and must not be force-retired. */
5876
+ terminalUncertain?: boolean;
5877
+ /** True when the exact broker row is already terminal. */
5878
+ terminal?: boolean;
5725
5879
  };
5726
5880
 
5727
5881
  async function exactBrokerSessionAuthority(sessionId: string, workspace: string): Promise<BrokerSessionAuthority> {
@@ -5741,17 +5895,35 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
5741
5895
  if (sameCanonicalPath(canonicalWorkspace, workspace, platform))
5742
5896
  matches.push({ session, workspace: canonicalWorkspace });
5743
5897
  }
5744
- if (matches.length !== 1)
5898
+ if (matches.length === 0)
5745
5899
  throw new SdkClientError(
5746
5900
  "not_found",
5747
5901
  "Session is not uniquely indexed in the requested coordinator workspace.",
5748
5902
  );
5903
+ if (matches.length !== 1)
5904
+ throw new SdkClientError(
5905
+ "ambiguous",
5906
+ "Session has multiple broker authority rows in the requested coordinator workspace.",
5907
+ );
5749
5908
  const match = matches[0]!;
5750
5909
  const endpointGeneration = brokerEndpointGeneration(match.session);
5751
5910
  const endpointIncarnation = brokerEndpointIncarnation(match.session, sessionId);
5752
5911
  if (endpointGeneration === null || endpointIncarnation === null)
5753
5912
  throw new SdkClientError("endpoint_stale", "Broker session has no usable endpoint incarnation.");
5754
- return { workspace: match.workspace, endpointGeneration, endpointIncarnation };
5913
+ return {
5914
+ workspace: match.workspace,
5915
+ endpointGeneration,
5916
+ endpointIncarnation,
5917
+ ...(typeof match.session.live === "boolean" ? { live: match.session.live } : {}),
5918
+ ...(typeof match.session.lastHeartbeatAt === "number"
5919
+ ? { lastHeartbeatAt: match.session.lastHeartbeatAt }
5920
+ : {}),
5921
+ ...(typeof match.session.ambiguous === "boolean" ? { ambiguous: match.session.ambiguous } : {}),
5922
+ ...(typeof match.session.terminalUncertain === "boolean"
5923
+ ? { terminalUncertain: match.session.terminalUncertain }
5924
+ : {}),
5925
+ ...(typeof match.session.terminal === "boolean" ? { terminal: match.session.terminal } : {}),
5926
+ };
5755
5927
  }
5756
5928
 
5757
5929
  async function exactBrokerSessionBinding(sessionId: string, workspace: string): Promise<BrokerSessionAuthority> {
@@ -5840,13 +6012,49 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
5840
6012
  const sessionId = brokerSessionId(session);
5841
6013
  return sessionId !== null && registered.has(sessionId);
5842
6014
  }
6015
+ async function reportProjectionFilesForSession(sessionId: string): Promise<string[]> {
6016
+ const reportsDirectory = path.join(namespaceDir, "reports");
6017
+ const entries = await fs.readdir(reportsDirectory, { withFileTypes: true }).catch(error => {
6018
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
6019
+ throw error;
6020
+ });
6021
+ const files: string[] = [];
6022
+ for (const entry of entries) {
6023
+ if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
6024
+ const file = path.join(reportsDirectory, entry.name);
6025
+ try {
6026
+ const report = asRecord(await readJsonFile(file));
6027
+ if (report?.session_id === sessionId) files.push(file);
6028
+ } catch (error) {
6029
+ logger.warn("Coordinator report projection scan skipped unreadable file", {
6030
+ file,
6031
+ error: error instanceof Error ? error.message : String(error),
6032
+ });
6033
+ }
6034
+ }
6035
+ return files;
6036
+ }
6037
+ async function removeStaleReportProjections(
6038
+ sessionId: string,
6039
+ retainedReportIds: ReadonlySet<string>,
6040
+ ): Promise<void> {
6041
+ for (const file of await reportProjectionFilesForSession(sessionId)) {
6042
+ const reportId = path.basename(file, ".json");
6043
+ if (retainedReportIds.has(reportId)) continue;
6044
+ await fs.rm(file, { force: true });
6045
+ }
6046
+ }
5843
6047
  async function removeReapedProjection(
5844
6048
  sessionId: string,
5845
6049
  turnIds: readonly string[],
5846
6050
  reportIds: readonly string[],
5847
6051
  ): Promise<void> {
5848
6052
  for (const turnId of turnIds) await fs.rm(turnFile(namespaceDir, turnId), { force: true });
5849
- for (const reportId of reportIds) await fs.rm(reportProjectionFile(namespaceDir, reportId), { force: true });
6053
+ for (const reportId of reportIds) {
6054
+ if (COORDINATOR_REPORT_ID_PATTERN.test(reportId))
6055
+ await fs.rm(reportProjectionFile(namespaceDir, reportId), { force: true });
6056
+ }
6057
+ for (const file of await reportProjectionFilesForSession(sessionId)) await fs.rm(file, { force: true });
5850
6058
  await fs.rm(sessionFile(sessionId), { force: true });
5851
6059
  await fs.rm(sessionStateFile(namespaceDir, sessionId), { force: true });
5852
6060
  await fs.rm(activeTurnFile(namespaceDir, sessionId), { force: true });
@@ -6036,19 +6244,21 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
6036
6244
  // manifest first, even while the session projection remains readable, so a
6037
6245
  // verification-uncertain retry never reaches broker close again.
6038
6246
  await ensureQuestionStateReady();
6039
- let pendingDeletion = await withNamespaceRegistry(
6040
- questionPaths,
6041
- async registry =>
6042
- Object.values(registry.deletions)
6247
+ let pendingDeletion = await withNamespaceRegistry(questionPaths, async registry => {
6248
+ const deletions = Object.values(registry.deletions);
6249
+ return (
6250
+ deletions
6043
6251
  .filter(
6044
6252
  entry =>
6045
6253
  entry.session_id === id &&
6046
6254
  (entry.phase === "intent" ||
6047
6255
  entry.phase === "broker_closed" ||
6048
- entry.phase === "cleanup_pending"),
6256
+ entry.phase === "cleanup_pending") &&
6257
+ !isSupersededDeletionIntent(entry, deletions),
6049
6258
  )
6050
- .sort((left, right) => right.updated_at.localeCompare(left.updated_at))[0] ?? null,
6051
- );
6259
+ .sort((left, right) => right.updated_at.localeCompare(left.updated_at))[0] ?? null
6260
+ );
6261
+ });
6052
6262
  if (pendingDeletion?.phase === "intent") {
6053
6263
  try {
6054
6264
  await recoverIntentDeletion(pendingDeletion);
@@ -6076,7 +6286,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
6076
6286
  const deletion = await withNamespaceRegistry(questionPaths, async registry => {
6077
6287
  const matching = Object.values(registry.deletions).filter(entry => entry.session_id === id);
6078
6288
  return (
6079
- matching.find(entry => entry.phase !== "completed") ??
6289
+ matching.find(entry => entry.phase !== "completed" && !isSupersededDeletionIntent(entry, matching)) ??
6080
6290
  matching.find(entry => entry.safe_response !== undefined) ??
6081
6291
  null
6082
6292
  );
@@ -6205,6 +6415,9 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
6205
6415
  }
6206
6416
  const deletionId = `delete:${id}:${persistedIncarnation}`;
6207
6417
  const deletionKey = createHash("sha256").update(deletionId).digest("hex");
6418
+ const projectedReportIds = (await reportProjectionFilesForSession(id)).map(file =>
6419
+ path.basename(file, ".json"),
6420
+ );
6208
6421
  const deletionEntry = {
6209
6422
  deletion_id: deletionId,
6210
6423
  session_id: id,
@@ -6221,7 +6434,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
6221
6434
  session: false,
6222
6435
  events: false,
6223
6436
  turn_ids: canonicalTransaction ? Object.keys(canonicalTransaction.canonical.turns) : [],
6224
- report_ids: canonicalTransaction ? Object.keys(canonicalTransaction.canonical.reports) : [],
6437
+ report_ids: [
6438
+ ...new Set([
6439
+ ...(canonicalTransaction ? Object.keys(canonicalTransaction.canonical.reports) : []),
6440
+ ...projectedReportIds,
6441
+ ]),
6442
+ ],
6225
6443
  },
6226
6444
  authority_digest: deletionKey,
6227
6445
  created_at: new Date().toISOString(),
@@ -6242,7 +6460,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
6242
6460
  }
6243
6461
  const projectionIds = {
6244
6462
  turnIds: Object.keys(admitted.canonical.turns),
6245
- reportIds: Object.keys(admitted.canonical.reports),
6463
+ reportIds: [
6464
+ ...new Set([
6465
+ ...Object.keys(admitted.canonical.reports),
6466
+ ...(await reportProjectionFilesForSession(id)).map(file => path.basename(file, ".json")),
6467
+ ]),
6468
+ ],
6246
6469
  };
6247
6470
  await ensureQuestionStateReady();
6248
6471
  const deletionPhase = await withNamespaceRegistry(
@@ -6399,8 +6622,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
6399
6622
  /**
6400
6623
  * Prove a stored endpoint identity (workspace + generation + incarnation) is no
6401
6624
  * longer current against the live broker index. Returns true only when the
6402
- * broker row is absent (not_found) or its generation/incarnation/workspace has
6403
- * rotated away from the stored triple. A transient authority-read failure
6625
+ * broker row is absent (not_found), its generation/incarnation/workspace has
6626
+ * rotated away from the stored triple, or the broker reports the row not live
6627
+ * and its host has been silent for DEAD_HOST_HEARTBEAT_SILENCE_MS. A transient
6628
+ * authority-read failure
6404
6629
  * returns false: we cannot prove the endpoint recovered, but we also must not
6405
6630
  * force-evict on a flake — the normal reap path will retry.
6406
6631
  */
@@ -6414,6 +6639,17 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
6414
6639
  const workspace = await canonicalBrokerWorkspace(persistedWorkspace);
6415
6640
  const authority = await exactBrokerSessionAuthority(id, workspace);
6416
6641
  return (
6642
+ // The broker can keep indexing a session's exact identity after its host is
6643
+ // gone; it then reports the row as not live and answers close with
6644
+ // endpoint_stale, so an identity match alone does not prove a live endpoint.
6645
+ // `live` alone is heartbeat-derived and flips after a short gap, so also
6646
+ // require the host to have been silent far beyond the broker's freshness
6647
+ // window before treating the row as dead.
6648
+ (authority.ambiguous !== true &&
6649
+ authority.terminalUncertain !== true &&
6650
+ authority.live === false &&
6651
+ authority.lastHeartbeatAt !== undefined &&
6652
+ Date.now() - authority.lastHeartbeatAt >= DEAD_HOST_HEARTBEAT_SILENCE_MS) ||
6417
6653
  !sameCanonicalPath(authority.workspace, persistedWorkspace, platform) ||
6418
6654
  authority.endpointGeneration !== persistedGeneration ||
6419
6655
  authority.endpointIncarnation !== persistedIncarnation
@@ -6525,8 +6761,50 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
6525
6761
  // projection-only delete.
6526
6762
  return;
6527
6763
  }
6764
+ const brokerSessionSnapshot = canonicalTransaction.canonical.session.broker;
6765
+ const brokerWorkspace = optionalString(brokerSessionSnapshot.workspace);
6766
+ const brokerGeneration =
6767
+ typeof brokerSessionSnapshot.endpoint_generation === "number" &&
6768
+ Number.isSafeInteger(brokerSessionSnapshot.endpoint_generation) &&
6769
+ brokerSessionSnapshot.endpoint_generation > 0
6770
+ ? brokerSessionSnapshot.endpoint_generation
6771
+ : null;
6772
+ if (
6773
+ !brokerWorkspace ||
6774
+ brokerGeneration === null ||
6775
+ brokerSessionSnapshot.endpoint_incarnation !== persistedIncarnation
6776
+ )
6777
+ return;
6778
+ // The stale proof above is a read. Before deleting coordinator state, ask the
6779
+ // broker to atomically fence the exact generation/incarnation under its index
6780
+ // lock. A successor, fresh heartbeat, ambiguous row, or terminal-uncertain
6781
+ // authority refuses this request. A missing row is already fenced and is safe
6782
+ // to treat as retired: the preceding reap close observed the same endpoint.
6783
+ try {
6784
+ const fence = strictBrokerSessionClose(
6785
+ await brokerSession(
6786
+ brokerWorkspace,
6787
+ "session.close",
6788
+ {
6789
+ cwd: brokerWorkspace,
6790
+ sessionId,
6791
+ endpointGeneration: brokerGeneration,
6792
+ endpointIncarnation: persistedIncarnation,
6793
+ forceRetireStale: true,
6794
+ },
6795
+ `coordinator-reaper-fence:${sessionId}:${persistedIncarnation}`,
6796
+ ),
6797
+ sessionId,
6798
+ );
6799
+ if (fence.retired !== true) return;
6800
+ } catch (error) {
6801
+ if (!(error instanceof SdkClientError) || error.code !== "not_found") return;
6802
+ }
6528
6803
  const deletionId = `force-evict:${sessionId}:${persistedIncarnation}`;
6529
6804
  const deletionKey = createHash("sha256").update(deletionId).digest("hex");
6805
+ const projectedReportIds = (await reportProjectionFilesForSession(sessionId)).map(file =>
6806
+ path.basename(file, ".json"),
6807
+ );
6530
6808
  const now = new Date().toISOString();
6531
6809
  const entry: NamespaceDeletionEntryV1 = {
6532
6810
  deletion_id: deletionId,
@@ -6544,7 +6822,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
6544
6822
  session: false,
6545
6823
  events: false,
6546
6824
  turn_ids: canonicalTransaction ? Object.keys(canonicalTransaction.canonical.turns) : [],
6547
- report_ids: canonicalTransaction ? Object.keys(canonicalTransaction.canonical.reports) : [],
6825
+ report_ids: [
6826
+ ...new Set([
6827
+ ...(canonicalTransaction ? Object.keys(canonicalTransaction.canonical.reports) : []),
6828
+ ...projectedReportIds,
6829
+ ]),
6830
+ ],
6548
6831
  },
6549
6832
  authority_digest: deletionKey,
6550
6833
  created_at: now,
@@ -6566,6 +6849,24 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
6566
6849
  try {
6567
6850
  await ensureQuestionTransaction(sessionId);
6568
6851
  } catch (error) {
6852
+ // A session whose persisted location the current policy no longer authorizes
6853
+ // must not be reaped (that would mutate an unauthorized location), but it must
6854
+ // not abort enumeration either: rethrowing refuses the whole sweep, so one such
6855
+ // session would stop reaping for every other session in the namespace. An empty
6856
+ // root list is a namespace-wide misconfiguration, not a per-session defect, so it
6857
+ // still refuses the sweep once instead of warning for every session.
6858
+ if (
6859
+ error instanceof Error &&
6860
+ isSessionAuthorityError(error) &&
6861
+ error.message !== "coordinator_workdir_roots_required"
6862
+ ) {
6863
+ logger.warn("Coordinator session reaper skipped an unauthorized session", {
6864
+ sessionId,
6865
+ reason: error.message,
6866
+ detail: error.cause instanceof Error ? error.cause.message : undefined,
6867
+ });
6868
+ continue;
6869
+ }
6569
6870
  if (!(error instanceof Error) || error.message !== "resource_gone") throw error;
6570
6871
  }
6571
6872
  try {
@@ -6602,7 +6903,15 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
6602
6903
  },
6603
6904
  reapSession: async (sessionId: string): Promise<void> => {
6604
6905
  const result = await reapSession(sessionId, { reason: "idle_reaper" });
6605
- if (!result.ok) throw new Error(result.reason ?? "session_reap_failed");
6906
+ if (result.ok) return;
6907
+ // reapSession reports a broker close the endpoint could not serve as
6908
+ // close_failed with the broker code in `detail`. The reaper only counts
6909
+ // endpoint_stale toward force eviction, so pass that code through;
6910
+ // markSessionDead still re-proves staleness under the session lock.
6911
+ const staleClose =
6912
+ result.reason === "close_failed" &&
6913
+ (result.detail === "endpoint_stale" || result.detail === "not_found");
6914
+ throw new Error(staleClose ? "endpoint_stale" : (result.reason ?? "session_reap_failed"));
6606
6915
  },
6607
6916
  markSessionDead: async (sessionId: string): Promise<void> => {
6608
6917
  // Force-evict a session that cannot be reaped normally (endpoint_stale
@@ -6778,6 +7087,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
6778
7087
  return {
6779
7088
  ok: false,
6780
7089
  reason: "timeout",
7090
+ wait_expired: true,
6781
7091
  turn: payload.turn,
6782
7092
  advisory_status: payload.advisory_status,
6783
7093
  session_state: payload.session_state,
@@ -7401,6 +7711,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
7401
7711
  await writeTurnRecord(namespaceDir, turnFromCanonical(turn));
7402
7712
  for (const report of Object.values(canonical.reports))
7403
7713
  await writeJsonFile(reportProjectionFile(namespaceDir, report.report_id), report);
7714
+ await removeStaleReportProjections(sessionId, new Set(Object.keys(canonical.reports)));
7404
7715
  const activeId = canonical.queue.selected_promotion?.to_turn_id ?? canonical.queue.active_turn_id;
7405
7716
  const active = activeId ? canonical.turns[activeId] : null;
7406
7717
  if (active) await writeActiveTurn(namespaceDir, turnFromCanonical(active));
@@ -7479,8 +7790,11 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
7479
7790
  await exportRetainedDeliveries(32, options.signal);
7480
7791
  }
7481
7792
 
7482
- async function recoverCanonicalSessionProjection(sessionId: string): Promise<void> {
7483
- const transaction = await readSessionTransaction(questionPaths, sessionId);
7793
+ async function recoverCanonicalSessionProjection(
7794
+ sessionId: string,
7795
+ options: { signal?: AbortSignal } = {},
7796
+ ): Promise<void> {
7797
+ const transaction = await repairLegacyReportIds(questionPaths, sessionId, options);
7484
7798
  if (!transaction) return;
7485
7799
  const applied = Math.min(
7486
7800
  transaction.projection.applied_turns_revision,
@@ -7489,10 +7803,13 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
7489
7803
  transaction.projection.applied_active_revision,
7490
7804
  transaction.projection.applied_events_revision,
7491
7805
  );
7492
- if (applied < transaction.revision) await repairCanonicalProjections(sessionId);
7806
+ if (applied < transaction.revision) await repairCanonicalProjections(sessionId, options);
7493
7807
  }
7494
7808
 
7495
- async function recoverCanonicalNamespaceProjections(scopedSessionId: string | null = null): Promise<void> {
7809
+ async function recoverCanonicalNamespaceProjections(
7810
+ scopedSessionId: string | null = null,
7811
+ options: { signal?: AbortSignal } = {},
7812
+ ): Promise<void> {
7496
7813
  await ensureQuestionStateReady();
7497
7814
  const roster = await readSchedulerRoster(questionPaths);
7498
7815
  const persisted = await fs.readdir(questionPaths.sessions).catch(() => []);
@@ -7507,13 +7824,21 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
7507
7824
  const transaction = await readSessionTransaction(questionPaths, sessionId);
7508
7825
  if (!transaction) continue;
7509
7826
  await assertPersistedSessionAuthority(transaction.canonical.session);
7510
- await recoverCanonicalSessionProjection(sessionId);
7827
+ await recoverCanonicalSessionProjection(sessionId, options);
7511
7828
  } catch (error) {
7512
- if (
7513
- !(error instanceof Error) ||
7514
- (error.message !== "state_corrupt" && (scopedSessionId !== null || !isSessionAuthorityError(error)))
7515
- )
7516
- throw error;
7829
+ // Degrade per session instead of aborting the namespace sweep: one
7830
+ // unreadable record must not make every coordination read fail.
7831
+ // Log before classifying so the skipped session is always nameable
7832
+ // from the outside — state_corrupt is precisely the shape the two
7833
+ // WAL defects surface as, and silencing it hides the cause.
7834
+ logger.warn("Coordinator projection recovery skipped session", {
7835
+ sessionId,
7836
+ error: error instanceof Error ? error.message : String(error),
7837
+ });
7838
+ const isCorruptWal = error instanceof Error && error.message === "state_corrupt";
7839
+ const isAuthorityFailure = isSessionAuthorityError(error);
7840
+ if (!isCorruptWal && !isAuthorityFailure && !isFileLockAcquireTimeout(error)) throw error;
7841
+ if (scopedSessionId === sessionId && !isCorruptWal) throw error;
7517
7842
  }
7518
7843
  }
7519
7844
  }
@@ -7835,12 +8160,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
7835
8160
  try {
7836
8161
  await reconcileSessionRuntime(entry.session_id, options);
7837
8162
  } catch (error) {
7838
- if (
7839
- !(error instanceof Error) ||
7840
- (error.message !== "resource_gone" &&
7841
- (prioritySessionId !== undefined || !isSessionAuthorityError(error)))
7842
- )
7843
- throw error;
8163
+ const degradable =
8164
+ error instanceof Error &&
8165
+ (error.message === "resource_gone" ||
8166
+ isSessionAuthorityError(error) ||
8167
+ (error.message === "state_corrupt" && prioritySessionId === undefined));
8168
+ if (!degradable) throw error;
7844
8169
  }
7845
8170
  }
7846
8171
  if (processedCursor && !options.signal?.aborted)
@@ -7869,7 +8194,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
7869
8194
  await reconcileSessionRuntime(sessionId, options);
7870
8195
  await reconcileQuestions(sessionId, options);
7871
8196
  } catch (error) {
7872
- if (!(error instanceof Error) || (error.message !== "resource_gone" && !isSessionAuthorityError(error)))
8197
+ if (
8198
+ !(error instanceof Error) ||
8199
+ (error.message !== "resource_gone" &&
8200
+ error.message !== "state_corrupt" &&
8201
+ !isSessionAuthorityError(error))
8202
+ )
7873
8203
  throw error;
7874
8204
  }
7875
8205
  }
@@ -7903,12 +8233,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
7903
8233
  try {
7904
8234
  await reconcileQuestions(entry.session_id, options);
7905
8235
  } catch (error) {
7906
- if (
7907
- !(error instanceof Error) ||
7908
- (error.message !== "resource_gone" &&
7909
- (prioritySessionId !== undefined || !isSessionAuthorityError(error)))
7910
- )
7911
- throw error;
8236
+ const degradable =
8237
+ error instanceof Error &&
8238
+ (error.message === "resource_gone" ||
8239
+ isSessionAuthorityError(error) ||
8240
+ (error.message === "state_corrupt" && prioritySessionId === undefined));
8241
+ if (!degradable) throw error;
7912
8242
  }
7913
8243
  }
7914
8244
  if (processedCursor && !options.signal?.aborted)
@@ -7948,14 +8278,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
7948
8278
  await assertPersistedSessionAuthority(transaction.canonical.session);
7949
8279
  turn = await readActiveTurn(namespaceDir, entry.session_id);
7950
8280
  } catch (error) {
7951
- if (
7952
- !(error instanceof Error) ||
7953
- (error.message !== "resource_gone" &&
7954
- error.message !== "coordinator_workdir_outside_allowed_roots" &&
7955
- error.message !== "coordinator_workdir_roots_required" &&
7956
- error.message !== "coordinator_workspace_required")
7957
- )
7958
- throw error;
8281
+ const degradable =
8282
+ error instanceof Error &&
8283
+ (error.message === "resource_gone" ||
8284
+ isSessionAuthorityError(error) ||
8285
+ (error.message === "state_corrupt" && prioritySessionId === undefined));
8286
+ if (!degradable) throw error;
7959
8287
  }
7960
8288
  processedCursor = entry.session_id;
7961
8289
  if (!turn) continue;
@@ -8044,9 +8372,11 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
8044
8372
  };
8045
8373
  const binding = await exactBrokerSessionBinding(sessionId, cwd);
8046
8374
  const priorSession = asRecord(await readJsonFile(sessionFile(sessionId)));
8375
+ const priorWorkspace = optionalString(priorSession?.broker_workspace);
8047
8376
  const priorAuthority =
8048
8377
  priorSession &&
8049
- priorSession.broker_workspace === binding.workspace &&
8378
+ priorWorkspace !== null &&
8379
+ sameCanonicalPath(priorWorkspace, binding.workspace, platform) &&
8050
8380
  priorSession.endpoint_generation === binding.endpointGeneration &&
8051
8381
  optionalString(priorSession.endpoint_incarnation) === binding.endpointIncarnation
8052
8382
  ? (priorSession.sidecar_verifier as { key_id: string; public_key: string } | undefined)
@@ -8284,18 +8614,49 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
8284
8614
  };
8285
8615
  await reconcileSessionRuntime(canonicalSessionId, { observeQuestions: false });
8286
8616
  try {
8287
- const brokerWorkspace = optionalString(session.broker_workspace) ?? cwd;
8288
- let indexedSession = (await listSessions(brokerWorkspace)).find(
8289
- candidate => brokerSessionId(candidate) === canonicalSessionId,
8617
+ // A projection can be partially written after its canonical WAL commit.
8618
+ // Recover the durable broker identity before proving the live endpoint so
8619
+ // a missing projection field does not turn an indexed session into a
8620
+ // false `not_indexed` result.
8621
+ const canonicalBroker = (await readSessionTransaction(questionPaths, canonicalSessionId))?.canonical
8622
+ .session.broker;
8623
+ const brokerWorkspace =
8624
+ optionalString(session.broker_workspace) ?? optionalString(canonicalBroker?.workspace) ?? cwd;
8625
+ const persistedEndpointGeneration =
8626
+ typeof session.endpoint_generation === "number" &&
8627
+ Number.isSafeInteger(session.endpoint_generation) &&
8628
+ session.endpoint_generation > 0
8629
+ ? session.endpoint_generation
8630
+ : typeof canonicalBroker?.endpoint_generation === "number" &&
8631
+ Number.isSafeInteger(canonicalBroker.endpoint_generation) &&
8632
+ canonicalBroker.endpoint_generation > 0
8633
+ ? canonicalBroker.endpoint_generation
8634
+ : null;
8635
+ const persistedEndpointIncarnation =
8636
+ optionalString(session.endpoint_incarnation) ??
8637
+ optionalString(canonicalBroker?.endpoint_incarnation);
8638
+ const expectedAuthority = {
8639
+ sessionId: canonicalSessionId,
8640
+ workspace: brokerWorkspace,
8641
+ endpointGeneration: persistedEndpointGeneration,
8642
+ endpointIncarnation: persistedEndpointIncarnation,
8643
+ };
8644
+ // A matching session id is not enough: the broker row must still
8645
+ // prove the projection's exact worktree endpoint authority.
8646
+ let indexedSession = await findPersistedBrokerAuthority(
8647
+ await listSessions(brokerWorkspace),
8648
+ expectedAuthority,
8290
8649
  );
8291
- // Windows broker locators may differ in drive-letter casing or separator
8292
- // spelling even after the injected canonical workspace seam has resolved
8293
- // the coordinator path. The scoped listing request is still authoritative;
8294
- // only its local path filter is relaxed for the exact requested session.
8295
- if (!indexedSession && platform === "win32") {
8650
+ // A broker locator can retain a valid worktree under a spelling that
8651
+ // the platform-local scope filter cannot normalize (for example a
8652
+ // symlinked worktree or a Windows alias). Retry the unfiltered page,
8653
+ // but keep canonical workspace and endpoint authority checks so this
8654
+ // cannot turn a foreign or rotated row into an indexed session.
8655
+ if (!indexedSession) {
8296
8656
  const listing = await paginatedBrokerSessionList(brokerWorkspace, { cwd: brokerWorkspace });
8297
- indexedSession = jsonRecords(Array.isArray(listing.sessions) ? listing.sessions : []).find(
8298
- candidate => brokerSessionId(candidate) === canonicalSessionId,
8657
+ indexedSession = await findPersistedBrokerAuthority(
8658
+ jsonRecords(Array.isArray(listing.sessions) ? listing.sessions : []),
8659
+ expectedAuthority,
8299
8660
  );
8300
8661
  }
8301
8662
  const sessionState = publicCoordinatorSessionState(
@@ -8304,7 +8665,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
8304
8665
  return {
8305
8666
  ok: true,
8306
8667
  session: publicCoordinatorStatusSession(session),
8307
- status: { ...brokerLiveness(indexedSession ?? null), ...(sessionState ?? {}) },
8668
+ // Broker indexing is the authority for endpoint liveness. A runtime
8669
+ // sidecar can retain a stale `live:true` projection after its
8670
+ // endpoint incarnation rotates, so it must not mask `not_indexed`.
8671
+ status: { ...(sessionState ?? {}), ...brokerLiveness(indexedSession ?? null) },
8308
8672
  session_state: sessionState,
8309
8673
  };
8310
8674
  } catch (error) {
@@ -8537,14 +8901,14 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
8537
8901
  // repair. The fresh journal snapshot below is the first public read after
8538
8902
  // that repair/export sequence, so events are visible after the caller's
8539
8903
  // cursor without allowing recovery to erase a terminal observation.
8540
- await recoverCanonicalNamespaceProjections(prioritySessionId ?? null);
8904
+ await recoverCanonicalNamespaceProjections(prioritySessionId ?? null, reconcileOptions);
8541
8905
  await exportRetainedDeliveries(32);
8542
8906
  } catch (error) {
8543
8907
  if (!(error instanceof Error && error.message === "resource_gone")) throw error;
8544
8908
  }
8545
8909
  } else if (!watchController.signal.aborted && Date.now() < absoluteDeadline) {
8546
- await recoverCanonicalNamespaceProjections(prioritySessionId ?? null);
8547
8910
  try {
8911
+ await recoverCanonicalNamespaceProjections(prioritySessionId ?? null, reconcileOptions);
8548
8912
  await exportRetainedDeliveries(32, watchController.signal);
8549
8913
  await reconcileWatchAdmissions(prioritySessionId, reconcileOptions);
8550
8914
  await reconcileActiveTurnAcknowledgements(
@@ -8645,7 +9009,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
8645
9009
  if (matched.length === 0 && timeoutMs > 0) {
8646
9010
  const deadline = absoluteDeadline;
8647
9011
  while (matched.length === 0 && Date.now() < deadline && !watchController.signal.aborted) {
8648
- await waitForCoordinatorEvents(namespaceDir, Math.min(50, Math.max(1, deadline - Date.now())));
9012
+ await waitForCoordinatorEvents(
9013
+ namespaceDir,
9014
+ Math.min(50, Math.max(1, deadline - Date.now())),
9015
+ services.onCoordinatorEventWatchReady,
9016
+ services.onCoordinatorEventWake,
9017
+ );
8649
9018
  if (Date.now() >= deadline || watchController.signal.aborted) break;
8650
9019
  try {
8651
9020
  await exportRetainedDeliveries(32, watchController.signal);
@@ -8858,6 +9227,14 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
8858
9227
  let initialDelegateRequest: CoordinatorSessionTransactionV1["recovery"]["initial_delegate_request"];
8859
9228
  if (reusedSessionId) {
8860
9229
  sessionId = reusedSessionId;
9230
+ // Record that this attempt has not claimed a prompt before any step
9231
+ // that can fail transiently. Only this explicit marker lets a same-key
9232
+ // retry claim afresh; a receipt without it has unknown provenance.
9233
+ if (!recovering && outer.value.delegate_prompt_claim_started === undefined)
9234
+ await writeCoordinatorIdempotencyFile(idempotencyFile(idempotencyKey), {
9235
+ ...(outer.value as unknown as CoordinatorToolIdempotencyRecord),
9236
+ delegate_prompt_claim_started: false,
9237
+ });
8861
9238
  const prior = await readSessionTransaction(questionPaths, sessionId);
8862
9239
  const prompt = prior?.requests.prompts[requestKey];
8863
9240
  delegateEffectStarted ||= Boolean(prompt && prompt.phase !== "claimed");
@@ -8889,13 +9266,58 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
8889
9266
  message: "Coordinator session is bound to another workspace.",
8890
9267
  },
8891
9268
  };
8892
- const binding = await exactBrokerSessionBinding(sessionId, canonicalCwd);
8893
- if (
8894
- !sameCanonicalPath(
8895
- optionalString(existing.broker_workspace) ?? "",
8896
- canonicalCwd,
9269
+ // Delegate-created managed-worktree sessions retain the caller's
9270
+ // repository cwd separately from the broker's execution worktree.
9271
+ // Resolve endpoint authority in that persisted broker workspace;
9272
+ // using canonicalCwd here searches the parent repository and makes
9273
+ // every follow-up appear unindexed even while its endpoint is live.
9274
+ const persistedBrokerWorkspace = optionalString(existing.broker_workspace);
9275
+ if (!persistedBrokerWorkspace)
9276
+ return {
9277
+ ok: false,
9278
+ error: {
9279
+ code: "endpoint_stale",
9280
+ message: "Coordinator session endpoint authority is stale.",
9281
+ },
9282
+ };
9283
+ let bindingWorkspace: string;
9284
+ try {
9285
+ bindingWorkspace = await canonicalBrokerWorkspace(persistedBrokerWorkspace);
9286
+ } catch (error) {
9287
+ if (!(error instanceof SdkClientError) || error.code !== "not_found") throw error;
9288
+ return {
9289
+ ok: false,
9290
+ error: {
9291
+ code: "endpoint_stale",
9292
+ message: "Coordinator session endpoint authority is stale.",
9293
+ },
9294
+ };
9295
+ }
9296
+ // The caller cwd guard only proves args.cwd is inside the current roots.
9297
+ // The persisted pair is what the delegate actually dispatches through, so
9298
+ // reauthorize it against the current policy before it becomes binding scope:
9299
+ // a narrowed managed-worktree root or a redirected projection must not let
9300
+ // a reused delegate reach a workspace the coordinator no longer owns.
9301
+ try {
9302
+ await assertCoordinatorSessionLocations(config, existingCwd, bindingWorkspace, {
9303
+ canonicalizePath: services.canonicalizePath,
8897
9304
  platform,
8898
- ) ||
9305
+ });
9306
+ } catch (error) {
9307
+ if (!isSessionAuthorityError(error)) throw error;
9308
+ return {
9309
+ ok: false,
9310
+ error: {
9311
+ code: "workspace_mismatch",
9312
+ message: "Coordinator session workspace is outside the allowed roots.",
9313
+ },
9314
+ };
9315
+ }
9316
+ const binding = await exactBrokerSessionBinding(sessionId, bindingWorkspace);
9317
+ if (
9318
+ // The caller cwd guard above prevents cross-workspace reuse; this
9319
+ // comparison fences the persisted endpoint identity itself.
9320
+ !sameCanonicalPath(binding.workspace, bindingWorkspace, platform) ||
8899
9321
  existing.endpoint_generation !== binding.endpointGeneration ||
8900
9322
  optionalString(existing.endpoint_incarnation) !== binding.endpointIncarnation
8901
9323
  )
@@ -9025,8 +9447,17 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
9025
9447
  (await readCanonicalActiveTurn(sessionId));
9026
9448
  const priorTransaction = await readSessionTransaction(questionPaths, sessionId);
9027
9449
  const priorPrompt = priorTransaction?.requests.prompts[requestKey];
9450
+ // A reused session has no creation authority to recover through, so a
9451
+ // same-key retry after a pre-admission failure (for example a transient
9452
+ // workspace canonicalization error) would otherwise seal as uncertain. The
9453
+ // outer receipt records whether a prompt claim began: an explicit `false`
9454
+ // proves none was claimed and this retry may claim afresh. `true` or an
9455
+ // absent marker means missing live state is still not proof that nothing
9456
+ // was dispatched.
9457
+ const requireClaimedPrompt =
9458
+ recovering && (!reusedSessionId || outer.value.delegate_prompt_claim_started !== false);
9028
9459
  if (
9029
- recovering &&
9460
+ requireClaimedPrompt &&
9030
9461
  !priorPrompt &&
9031
9462
  !hasInitialDelegatePromptAuthority(priorTransaction, initialDelegateRequest)
9032
9463
  )
@@ -9056,13 +9487,18 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
9056
9487
  turn_id: previousActiveTurn.turn_id,
9057
9488
  };
9058
9489
  }
9490
+ if (outer.value.delegate_prompt_claim_started !== true)
9491
+ await writeCoordinatorIdempotencyFile(idempotencyFile(idempotencyKey), {
9492
+ ...(outer.value as unknown as CoordinatorToolIdempotencyRecord),
9493
+ delegate_prompt_claim_started: true,
9494
+ });
9059
9495
  const promptKey = await claimCanonicalPrompt(
9060
9496
  sessionId,
9061
9497
  taggedPrompt,
9062
9498
  operation,
9063
9499
  idempotencyKey,
9064
9500
  true,
9065
- recovering,
9501
+ requireClaimedPrompt,
9066
9502
  initialDelegateRequest,
9067
9503
  );
9068
9504
  if (reserved?.terminal_fence && !(await promptReceipt(sessionId, promptKey))) {
@@ -9209,6 +9645,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
9209
9645
  true,
9210
9646
  response =>
9211
9647
  (!delegateResponseComplete && (creationRemoteStarted || delegateEffectStarted)) ||
9648
+ isTransientDelegateAuthorityFailure(response) ||
9212
9649
  isRouterRequestAmbiguous(response),
9213
9650
  );
9214
9651
  }
@@ -11000,7 +11437,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
11000
11437
  if (request.method === "tools/call") {
11001
11438
  const params = (request.params ?? {}) as { name?: string; arguments?: Record<string, unknown> };
11002
11439
  const payload = await callTool(params.name ?? "", params.arguments ?? {});
11003
- return { jsonrpc: "2.0", id, result: textResult(payload, payload.ok === false) };
11440
+ return { jsonrpc: "2.0", id, result: coordinatorToolResult(payload) };
11004
11441
  }
11005
11442
  return { jsonrpc: "2.0", id, error: { code: -32601, message: `unknown_method:${request.method}` } };
11006
11443
  }
@@ -11022,8 +11459,12 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
11022
11459
  };
11023
11460
  }
11024
11461
 
11025
- function legacyToolResult(payload: unknown): { content: Array<{ type: "text"; text: string }>; isError: boolean } {
11026
- const failed = typeof payload === "object" && payload !== null && (payload as { ok?: unknown }).ok === false;
11462
+ function coordinatorToolResult(payload: unknown): { content: Array<{ type: "text"; text: string }>; isError: boolean } {
11463
+ const record = asRecord(payload);
11464
+ // The legacy ok:false means the observation window ended, not that the RPC
11465
+ // failed. Keep its payload contract while preventing host error/retry breakers.
11466
+ const observationExpired = record?.wait_expired === true && record.reason === "timeout" && !record.error;
11467
+ const failed = record?.ok === false && !observationExpired;
11027
11468
  return textResult(payload, failed);
11028
11469
  }
11029
11470
 
@@ -11068,7 +11509,7 @@ export async function handleCoordinatorMcpRequest(
11068
11509
  return {
11069
11510
  jsonrpc: "2.0",
11070
11511
  id: request.id ?? null,
11071
- result: legacyToolResult(await server.callTool(params.name ?? "", args)),
11512
+ result: coordinatorToolResult(await server.callTool(params.name ?? "", args)),
11072
11513
  };
11073
11514
  } finally {
11074
11515
  await server.close();