@gajae-code/coding-agent 0.17.2 → 0.17.5

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 (390) hide show
  1. package/CHANGELOG.md +267 -0
  2. package/dist/types/cli/daemon-cli.d.ts +3 -1
  3. package/dist/types/cli/github-release.d.ts +17 -0
  4. package/dist/types/cli/public-command-entry.d.ts +56 -0
  5. package/dist/types/cli/public-command-errors.d.ts +126 -0
  6. package/dist/types/cli/public-command-evidence.d.ts +99 -0
  7. package/dist/types/cli/public-command-help.d.ts +65 -0
  8. package/dist/types/cli/public-command-registry.d.ts +69 -0
  9. package/dist/types/cli/session-picker.d.ts +1 -1
  10. package/dist/types/cli/setup-cli.d.ts +1 -0
  11. package/dist/types/cli/skills-cli.d.ts +5 -0
  12. package/dist/types/cli.d.ts +3 -0
  13. package/dist/types/commands/daemon.d.ts +4 -50
  14. package/dist/types/commands/sdk.d.ts +20 -37
  15. package/dist/types/commands/setup.d.ts +3 -0
  16. package/dist/types/commands/skills.d.ts +9 -0
  17. package/dist/types/config/autorouting-tier-map.d.ts +1049 -3
  18. package/dist/types/config/file-lock.d.ts +13 -3
  19. package/dist/types/config/model-profile-activation.d.ts +45 -1
  20. package/dist/types/config/model-registry.d.ts +14 -3
  21. package/dist/types/config/model-resolver.d.ts +1 -1
  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/daemon/operator-contract.d.ts +5 -1
  25. package/dist/types/eval/js/worker-protocol.d.ts +2 -0
  26. package/dist/types/exec/bash-executor.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/gjc-plugins/runtime-adapters.d.ts +11 -2
  30. package/dist/types/extensibility/runtime-skill-discovery.d.ts +18 -0
  31. package/dist/types/extensibility/shared-events.d.ts +3 -1
  32. package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
  33. package/dist/types/gjc-runtime/state-writer.d.ts +14 -1
  34. package/dist/types/harness-control-plane/control-endpoint.d.ts +4 -1
  35. package/dist/types/harness-control-plane/finalize.d.ts +14 -3
  36. package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
  37. package/dist/types/internal-urls/index.d.ts +1 -0
  38. package/dist/types/internal-urls/parse.d.ts +9 -1
  39. package/dist/types/main.d.ts +23 -1
  40. package/dist/types/modes/acp/acp-agent.d.ts +55 -1
  41. package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
  42. package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
  43. package/dist/types/modes/components/session-selector.d.ts +5 -2
  44. package/dist/types/modes/components/user-message-selector.d.ts +5 -2
  45. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  46. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  47. package/dist/types/modes/interactive-mode.d.ts +1 -1
  48. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
  49. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
  50. package/dist/types/modes/types.d.ts +2 -2
  51. package/dist/types/runtime/process-lifecycle.d.ts +2 -0
  52. package/dist/types/runtime-mcp/loader.d.ts +2 -0
  53. package/dist/types/runtime-mcp/manager.d.ts +69 -6
  54. package/dist/types/runtime-mcp/pool.d.ts +17 -0
  55. package/dist/types/runtime-mcp/redaction.d.ts +20 -0
  56. package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
  57. package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
  58. package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
  59. package/dist/types/sdk/broker/broker.d.ts +17 -4
  60. package/dist/types/sdk/broker/ensure.d.ts +3 -0
  61. package/dist/types/sdk/broker/identity.d.ts +5 -1
  62. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
  63. package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
  64. package/dist/types/sdk/broker/managed-task-dag.d.ts +1335 -0
  65. package/dist/types/sdk/broker/managed-task-verification.d.ts +37 -0
  66. package/dist/types/sdk/broker/runtime.d.ts +1 -1
  67. package/dist/types/sdk/broker/session-index.d.ts +22 -3
  68. package/dist/types/sdk/broker/spawn-authority.d.ts +12 -0
  69. package/dist/types/sdk/broker/startup-failure.d.ts +4 -0
  70. package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
  71. package/dist/types/sdk/bus/index.d.ts +19 -0
  72. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -1
  73. package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
  74. package/dist/types/sdk/cli/master-cli.d.ts +6 -10
  75. package/dist/types/sdk/cli/rows.d.ts +19 -1
  76. package/dist/types/sdk/cli/session-cli.d.ts +22 -5
  77. package/dist/types/sdk/client/client.d.ts +2 -0
  78. package/dist/types/sdk/guides/cli.d.ts +3 -4
  79. package/dist/types/sdk/guides/verify.d.ts +2 -0
  80. package/dist/types/sdk/host/host.d.ts +9 -0
  81. package/dist/types/sdk/host/session-runtime.d.ts +31 -0
  82. package/dist/types/sdk/host/types.d.ts +10 -0
  83. package/dist/types/sdk/index.d.ts +1 -0
  84. package/dist/types/sdk/prompt-deadline-flush.d.ts +85 -0
  85. package/dist/types/sdk/prompt-deadline-manager.d.ts +37 -0
  86. package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
  87. package/dist/types/sdk/router/session-router.d.ts +2 -0
  88. package/dist/types/sdk/session.d.ts +3 -3
  89. package/dist/types/sdk/startup-capability.d.ts +2 -0
  90. package/dist/types/sdk/transport/relay.d.ts +1 -0
  91. package/dist/types/sdk/transport/serve-cli.d.ts +48 -4
  92. package/dist/types/sdk/transport/stdio.d.ts +5 -1
  93. package/dist/types/session/agent-session.d.ts +175 -23
  94. package/dist/types/session/fallback-chain-controller.d.ts +6 -6
  95. package/dist/types/session/fold-coordinator.d.ts +17 -3
  96. package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
  97. package/dist/types/session/session-manager.d.ts +64 -1
  98. package/dist/types/session/session-work-lease.d.ts +14 -0
  99. package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
  100. package/dist/types/setup/provider-onboarding.d.ts +81 -1
  101. package/dist/types/task/types.d.ts +2 -0
  102. package/dist/types/tools/bash-interactive.d.ts +1 -0
  103. package/dist/types/tools/bash.d.ts +37 -11
  104. package/dist/types/tools/fetch.d.ts +10 -1
  105. package/dist/types/tools/job.d.ts +1 -1
  106. package/dist/types/tools/steer-fold.d.ts +31 -0
  107. package/dist/types/tools/subagent.d.ts +8 -1
  108. package/dist/types/utils/git.d.ts +28 -2
  109. package/dist/types/utils/mupdf-embedded.d.ts +2 -0
  110. package/dist/types/utils/mupdf.d.ts +4 -0
  111. package/dist/types/utils/session-title-generation.d.ts +6 -0
  112. package/dist/types/utils/title-generator.d.ts +11 -2
  113. package/dist/types/web/scrapers/types.d.ts +3 -0
  114. package/package.json +18 -10
  115. package/scripts/build-binary.ts +9 -3
  116. package/scripts/compile-args.ts +0 -9
  117. package/scripts/embed-mupdf.ts +52 -0
  118. package/scripts/generate-sdk-operation-inventory.ts +31 -1
  119. package/scripts/vendor-markit.ts +196 -0
  120. package/src/async/job-manager.ts +22 -1
  121. package/src/cli/daemon-cli.ts +53 -18
  122. package/src/cli/github-release.ts +87 -5
  123. package/src/cli/mcp-cli.ts +38 -4
  124. package/src/cli/public-command-entry.ts +450 -0
  125. package/src/cli/public-command-errors.ts +581 -0
  126. package/src/cli/public-command-evidence.ts +717 -0
  127. package/src/cli/public-command-help.ts +360 -0
  128. package/src/cli/public-command-registry.ts +541 -0
  129. package/src/cli/session-picker.ts +2 -1
  130. package/src/cli/setup-cli.ts +10 -1
  131. package/src/cli/skills-cli.ts +46 -2
  132. package/src/cli/stats-cli.ts +11 -1
  133. package/src/cli.ts +61 -1
  134. package/src/commands/daemon.ts +65 -69
  135. package/src/commands/sdk.ts +175 -117
  136. package/src/commands/setup.ts +2 -0
  137. package/src/commands/skills.ts +11 -0
  138. package/src/config/autorouting-tier-map.ts +543 -3
  139. package/src/config/file-lock.ts +206 -65
  140. package/src/config/model-discovery-manager.ts +4 -4
  141. package/src/config/model-profile-activation.ts +417 -66
  142. package/src/config/model-registry.ts +254 -36
  143. package/src/config/model-resolver.ts +6 -1
  144. package/src/config/settings-schema.ts +26 -2
  145. package/src/coordinator-mcp/policy.ts +10 -5
  146. package/src/coordinator-mcp/question-gate-codec.ts +17 -0
  147. package/src/coordinator-mcp/question-state.ts +179 -9
  148. package/src/coordinator-mcp/server.ts +521 -87
  149. package/src/coordinator-mcp/session-reaper.ts +6 -4
  150. package/src/crash/record-loader.ts +6 -2
  151. package/src/daemon/operator-contract.ts +26 -12
  152. package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
  153. package/src/eval/js/context-manager.ts +8 -8
  154. package/src/eval/js/worker-core.ts +9 -4
  155. package/src/eval/js/worker-protocol.ts +2 -0
  156. package/src/exec/bash-executor.ts +3 -0
  157. package/src/extensibility/extensions/runner.ts +52 -4
  158. package/src/extensibility/extensions/types.ts +10 -0
  159. package/src/extensibility/gjc-plugins/registry.ts +196 -37
  160. package/src/extensibility/gjc-plugins/runtime-adapters.ts +46 -8
  161. package/src/extensibility/runtime-skill-discovery.ts +61 -3
  162. package/src/extensibility/shared-events.ts +3 -1
  163. package/src/gjc-runtime/session-state-lock.ts +165 -24
  164. package/src/gjc-runtime/state-writer.ts +317 -6
  165. package/src/gjc-runtime/ultragoal-succession.ts +31 -3
  166. package/src/harness-control-plane/control-endpoint.ts +24 -6
  167. package/src/harness-control-plane/finalize.ts +312 -25
  168. package/src/harness-control-plane/owner.ts +323 -89
  169. package/src/internal-urls/docs-index.generated.ts +23 -20
  170. package/src/internal-urls/embedded-protocol.ts +83 -0
  171. package/src/internal-urls/index.ts +1 -0
  172. package/src/internal-urls/parse.ts +21 -1
  173. package/src/internal-urls/router.ts +4 -2
  174. package/src/main.ts +192 -74
  175. package/src/modes/acp/acp-agent.ts +973 -56
  176. package/src/modes/acp/startup-provenance.ts +59 -0
  177. package/src/modes/components/custom-provider-wizard.ts +308 -7
  178. package/src/modes/components/gjc-bundle-settings.ts +21 -1
  179. package/src/modes/components/oauth-selector.ts +9 -0
  180. package/src/modes/components/session-selector.ts +47 -4
  181. package/src/modes/components/tool-status-header.ts +72 -6
  182. package/src/modes/components/user-message-selector.ts +57 -15
  183. package/src/modes/controllers/command-controller.ts +58 -32
  184. package/src/modes/controllers/extension-ui-controller.ts +33 -7
  185. package/src/modes/controllers/input-controller.ts +4 -0
  186. package/src/modes/controllers/runtime-mcp-command-controller.ts +1 -1
  187. package/src/modes/controllers/selector-controller.ts +161 -15
  188. package/src/modes/interactive-mode.ts +21 -7
  189. package/src/modes/runtime-init.ts +1 -0
  190. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
  191. package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
  192. package/src/modes/types.ts +2 -2
  193. package/src/prompts/agent-fragments/browser-aside.md +1 -0
  194. package/src/prompts/system/title-system.md +1 -1
  195. package/src/prompts/tools/bash.md +1 -1
  196. package/src/prompts/tools/job.md +1 -1
  197. package/src/prompts/tools/read.md +2 -0
  198. package/src/runtime/network-prewarm-service.test.ts +36 -1
  199. package/src/runtime/network-prewarm-service.ts +28 -3
  200. package/src/runtime/process-lifecycle.ts +22 -11
  201. package/src/runtime-mcp/config.ts +16 -1
  202. package/src/runtime-mcp/loader.ts +6 -3
  203. package/src/runtime-mcp/manager.ts +845 -109
  204. package/src/runtime-mcp/pool.test.ts +31 -0
  205. package/src/runtime-mcp/pool.ts +304 -28
  206. package/src/runtime-mcp/redaction.ts +42 -0
  207. package/src/runtime-mcp/startup-policy.ts +7 -0
  208. package/src/runtime-mcp/tool-bridge.ts +71 -0
  209. package/src/runtime-mcp/transports/http.ts +108 -25
  210. package/src/sdk/acp/adapter.ts +27 -8
  211. package/src/sdk/broker/broker.ts +1431 -198
  212. package/src/sdk/broker/ensure.ts +21 -3
  213. package/src/sdk/broker/identity.ts +22 -1
  214. package/src/sdk/broker/lifecycle-ledger.ts +95 -31
  215. package/src/sdk/broker/lifecycle.ts +690 -141
  216. package/src/sdk/broker/managed-task-dag.ts +1927 -0
  217. package/src/sdk/broker/managed-task-verification.ts +642 -0
  218. package/src/sdk/broker/runtime.ts +1 -1
  219. package/src/sdk/broker/session-index.ts +97 -4
  220. package/src/sdk/broker/spawn-authority.ts +175 -14
  221. package/src/sdk/broker/startup-failure.ts +80 -1
  222. package/src/sdk/broker/stderr-drainer.ts +59 -0
  223. package/src/sdk/broker/transport.ts +1 -0
  224. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  225. package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
  226. package/src/sdk/bus/index.ts +707 -81
  227. package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
  228. package/src/sdk/bus/telegram-daemon-contract.ts +7 -1
  229. package/src/sdk/bus/telegram-daemon.ts +10 -3
  230. package/src/sdk/cli/master-cli.ts +47 -36
  231. package/src/sdk/cli/rows.ts +30 -7
  232. package/src/sdk/cli/session-cli.ts +705 -184
  233. package/src/sdk/client/client.ts +18 -0
  234. package/src/sdk/guides/catalog.ts +2 -2
  235. package/src/sdk/guides/cli.ts +20 -62
  236. package/src/sdk/guides/verify.ts +14 -0
  237. package/src/sdk/host/host.ts +49 -5
  238. package/src/sdk/host/session-runtime.test.ts +184 -2
  239. package/src/sdk/host/session-runtime.ts +322 -101
  240. package/src/sdk/host/types.ts +12 -1
  241. package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
  242. package/src/sdk/host/websocket-transport.ts +40 -6
  243. package/src/sdk/index.ts +11 -0
  244. package/src/sdk/lifecycle/service.ts +2 -0
  245. package/src/sdk/prompt-deadline-flush.ts +356 -0
  246. package/src/sdk/prompt-deadline-manager.ts +133 -1
  247. package/src/sdk/prompt-tool-boundary.ts +121 -0
  248. package/src/sdk/protocol/operation-inventory.generated.json +106 -0
  249. package/src/sdk/router/session-router.ts +9 -1
  250. package/src/sdk/session.ts +763 -116
  251. package/src/sdk/startup-capability.ts +1 -1
  252. package/src/sdk/transport/relay.ts +71 -6
  253. package/src/sdk/transport/serve-cli.ts +397 -42
  254. package/src/sdk/transport/socket.ts +12 -11
  255. package/src/sdk/transport/stdio.ts +7 -3
  256. package/src/session/agent-session.ts +2472 -407
  257. package/src/session/fallback-chain-controller.ts +6 -7
  258. package/src/session/fold-coordinator.ts +24 -5
  259. package/src/session/internal/managed-session-scope.ts +68 -18
  260. package/src/session/internal/managed-session-storage.ts +334 -12
  261. package/src/session/session-manager.ts +382 -44
  262. package/src/session/session-work-lease.ts +34 -0
  263. package/src/setup/model-onboarding-guidance.ts +1 -1
  264. package/src/setup/provider-onboarding.ts +397 -24
  265. package/src/setup/provider-presets.json +2 -1
  266. package/src/slash-commands/builtin-registry.ts +259 -15
  267. package/src/task/executor.ts +46 -17
  268. package/src/task/index.ts +14 -2
  269. package/src/task/types.ts +2 -0
  270. package/src/tools/bash-interactive.ts +2 -0
  271. package/src/tools/bash.ts +118 -78
  272. package/src/tools/fetch.ts +140 -25
  273. package/src/tools/job.ts +151 -25
  274. package/src/tools/output-meta.ts +15 -2
  275. package/src/tools/path-utils.ts +27 -3
  276. package/src/tools/read.ts +3 -3
  277. package/src/tools/steer-fold.ts +107 -0
  278. package/src/tools/subagent-render.ts +24 -1
  279. package/src/tools/subagent.ts +27 -1
  280. package/src/tools/tool-catalog.generated.ts +29 -2
  281. package/src/tools/tool-errors.ts +4 -0
  282. package/src/utils/git.ts +53 -3
  283. package/src/utils/markit.ts +55 -41
  284. package/src/utils/mupdf-assets.d.ts +4 -0
  285. package/src/utils/mupdf-embedded.ts +4 -0
  286. package/src/utils/mupdf.ts +136 -0
  287. package/src/utils/session-title-generation.ts +29 -0
  288. package/src/utils/title-generator.ts +46 -2
  289. package/src/web/scrapers/types.ts +24 -3
  290. package/vendor/markit-ai/LICENSE +21 -0
  291. package/vendor/markit-ai/MANIFEST.json +412 -0
  292. package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
  293. package/vendor/markit-ai/dist/commands/config.js +139 -0
  294. package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
  295. package/vendor/markit-ai/dist/commands/convert.js +113 -0
  296. package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
  297. package/vendor/markit-ai/dist/commands/formats.js +92 -0
  298. package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
  299. package/vendor/markit-ai/dist/commands/init.js +29 -0
  300. package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
  301. package/vendor/markit-ai/dist/commands/onboard.js +61 -0
  302. package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
  303. package/vendor/markit-ai/dist/commands/plugin.js +58 -0
  304. package/vendor/markit-ai/dist/config.d.ts +26 -0
  305. package/vendor/markit-ai/dist/config.js +42 -0
  306. package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
  307. package/vendor/markit-ai/dist/converters/audio.js +104 -0
  308. package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
  309. package/vendor/markit-ai/dist/converters/csv.js +83 -0
  310. package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
  311. package/vendor/markit-ai/dist/converters/docx.js +57 -0
  312. package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
  313. package/vendor/markit-ai/dist/converters/epub.js +128 -0
  314. package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
  315. package/vendor/markit-ai/dist/converters/github.js +148 -0
  316. package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
  317. package/vendor/markit-ai/dist/converters/html.js +30 -0
  318. package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
  319. package/vendor/markit-ai/dist/converters/image.js +124 -0
  320. package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
  321. package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
  322. package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
  323. package/vendor/markit-ai/dist/converters/iwork.js +391 -0
  324. package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
  325. package/vendor/markit-ai/dist/converters/json.js +21 -0
  326. package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
  327. package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
  328. package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
  329. package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
  330. package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
  331. package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
  332. package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
  333. package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
  334. package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
  335. package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
  336. package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
  337. package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
  338. package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
  339. package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
  340. package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
  341. package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
  342. package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
  343. package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
  344. package/vendor/markit-ai/dist/converters/pptx.js +289 -0
  345. package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
  346. package/vendor/markit-ai/dist/converters/rss.js +138 -0
  347. package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
  348. package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
  349. package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
  350. package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
  351. package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
  352. package/vendor/markit-ai/dist/converters/xml.js +18 -0
  353. package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
  354. package/vendor/markit-ai/dist/converters/yaml.js +17 -0
  355. package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
  356. package/vendor/markit-ai/dist/converters/zip.js +57 -0
  357. package/vendor/markit-ai/dist/index.d.ts +30 -0
  358. package/vendor/markit-ai/dist/index.js +26 -0
  359. package/vendor/markit-ai/dist/main.d.ts +2 -0
  360. package/vendor/markit-ai/dist/main.js +191 -0
  361. package/vendor/markit-ai/dist/markit.d.ts +45 -0
  362. package/vendor/markit-ai/dist/markit.js +267 -0
  363. package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
  364. package/vendor/markit-ai/dist/plugins/api.js +42 -0
  365. package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
  366. package/vendor/markit-ai/dist/plugins/index.js +3 -0
  367. package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
  368. package/vendor/markit-ai/dist/plugins/installer.js +176 -0
  369. package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
  370. package/vendor/markit-ai/dist/plugins/loader.js +61 -0
  371. package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
  372. package/vendor/markit-ai/dist/plugins/types.js +1 -0
  373. package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
  374. package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
  375. package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
  376. package/vendor/markit-ai/dist/providers/index.js +56 -0
  377. package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
  378. package/vendor/markit-ai/dist/providers/openai.js +76 -0
  379. package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
  380. package/vendor/markit-ai/dist/providers/types.js +1 -0
  381. package/vendor/markit-ai/dist/types.d.ts +36 -0
  382. package/vendor/markit-ai/dist/types.js +1 -0
  383. package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
  384. package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
  385. package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
  386. package/vendor/markit-ai/dist/utils/output.js +31 -0
  387. package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
  388. package/vendor/markit-ai/dist/utils/turndown.js +70 -0
  389. package/vendor/markit-ai/package.json +74 -0
  390. package/vendor/markit-ai.patch +49 -0
@@ -5,6 +5,7 @@ import { logger, resolveEquivalentPath } from "@gajae-code/utils";
5
5
  import { nativeProcessBindings } from "@gajae-code/utils/native-process";
6
6
  import { withFileLock } from "../../config/file-lock";
7
7
  import { repo } from "../../utils/git";
8
+ import { endpointIncarnation } from "./endpoint-authority";
8
9
  import { processIncarnation } from "./process-incarnation";
9
10
  import {
10
11
  assertSupportedSessionIndexEventVersion,
@@ -26,7 +27,7 @@ export type SessionIndexEventType =
26
27
  | "record_reconciled";
27
28
 
28
29
  export type SessionActivityState = "active" | "idle";
29
- /** Coalesced broker-owned heartbeat checkpoint (C2): state plus the observation time. */
30
+ /** Host-reported or broker-coalesced heartbeat: state plus the observation time. */
30
31
  export interface SessionActivity {
31
32
  state: SessionActivityState;
32
33
  at: number;
@@ -34,6 +35,8 @@ export interface SessionActivity {
34
35
  /** Events persisted without an OS process incarnation (v1/v2 era) are legacy provenance. */
35
36
  export type SessionIdentityProvenance = "composite" | "legacy";
36
37
  export type SessionTombstoneRule = "retain" | "expire";
38
+ /** Durable provenance for a retired host registration. */
39
+ export type HostUnregisteredReason = "process_exited" | "detached_idle";
37
40
  /**
38
41
  * Injected retention policy (C3). The broker schedules compaction independently of
39
42
  * rotation; settings apply at the next scheduled compaction. `clock` drives both
@@ -141,6 +144,7 @@ export interface SessionIndexEvent {
141
144
  /** Immutable endpoint file identity captured by the broker at registration. */
142
145
  endpointFileId?: string;
143
146
  lifecycleRequestId?: string;
147
+ hostUnregisteredReason?: HostUnregisteredReason;
144
148
  terminalUncertain?: boolean;
145
149
  /**
146
150
  * Distinguishes the terminal-uncertain claim written by a forced stop of a
@@ -170,6 +174,7 @@ export interface IndexedSession {
170
174
  live: boolean;
171
175
  indexSeq: number;
172
176
  lifecycleRequestId?: string;
177
+ hostUnregisteredReason?: HostUnregisteredReason;
173
178
  terminalUncertain?: boolean;
174
179
  /** True only for a terminal-uncertain claim written by a forced stale-worktree release. */
175
180
  forcedStaleRelease?: boolean;
@@ -345,6 +350,8 @@ function sameIndexChangeStamp(a: SessionIndexChangeStamp, b: SessionIndexChangeS
345
350
  const ROTATE_BYTES = 4 * 1024 * 1024;
346
351
  /** Coalesced heartbeat checkpoint rate cap (C2): at most one per session per minute. */
347
352
  export const SESSION_HEARTBEAT_INTERVAL_MS = 60_000;
353
+ /** A stale-host retirement requires silence well beyond the broker freshness window. */
354
+ export const DEAD_HOST_HEARTBEAT_SILENCE_MS = 10 * SESSION_HEARTBEAT_INTERVAL_MS;
348
355
  export const DEFAULT_SESSION_RETENTION_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
349
356
  export const DEFAULT_SESSION_RETENTION_MAX_ROWS = 25_000;
350
357
 
@@ -571,6 +578,10 @@ function projectIdentity(
571
578
  const { latest, heartbeat } = state;
572
579
  const terminal = isTerminalEvent(latest);
573
580
  const terminalUncertain = latest.type === "lifecycle_terminal" || latest.terminalUncertain === true;
581
+ const hostUnregisteredReason =
582
+ latest.hostUnregisteredReason === "process_exited" || latest.hostUnregisteredReason === "detached_idle"
583
+ ? latest.hostUnregisteredReason
584
+ : undefined;
574
585
  const pidAlive = alive(latest.pid);
575
586
  // Liveness evidence is host-written: a checkpointed heartbeat, or the
576
587
  // `host_registered` event the host appended itself. Counting registration
@@ -606,6 +617,7 @@ function projectIdentity(
606
617
  endpointMtimeMs: latest.endpointMtimeMs,
607
618
  endpointFileId: latest.endpointFileId,
608
619
  lifecycleRequestId: latest.lifecycleRequestId,
620
+ ...(hostUnregisteredReason === undefined ? {} : { hostUnregisteredReason }),
609
621
  terminalUncertain,
610
622
  ...(latest.forcedStaleRelease === true ? { forcedStaleRelease: true } : {}),
611
623
  indexSeq: latest.indexSeq,
@@ -1687,6 +1699,82 @@ export class SessionIndex {
1687
1699
  });
1688
1700
  });
1689
1701
  }
1702
+
1703
+ /**
1704
+ * Atomically fence an exact, long-silent dead-host authority before a caller
1705
+ * removes state outside the broker index. The comparison and terminal event
1706
+ * append share the machine-global index lock, so a heartbeat, successor
1707
+ * registration, or unresolved-authority update that wins the lock prevents
1708
+ * destructive coordinator cleanup.
1709
+ */
1710
+ async retireStaleIfCurrent(expected: {
1711
+ sessionId: string;
1712
+ workspace: string;
1713
+ endpointGeneration: number;
1714
+ endpointIncarnation: string;
1715
+ }): Promise<"retired" | "already_retired" | "not_found" | "stale"> {
1716
+ const indexPath = path.resolve(logFor(this.#agentDir));
1717
+ // Process-incarnation observations are gathered before the lock. The locked
1718
+ // projection then uses this fixed batch, avoiding an unbounded OS probe while
1719
+ // holding the machine-global index lock.
1720
+ const probed = new Map<string, string | undefined>();
1721
+ for (const row of reduceEvents(this.#events, this.#policy.clock(), this.#agentDir).identities) {
1722
+ const recordedIncarnation = row.hostIncarnation ?? row.processIncarnation;
1723
+ if (recordedIncarnation === undefined || !alive(row.pid)) continue;
1724
+ probed.set(`${row.sessionId}\u0000${row.endpointGeneration}\u0000${row.pid}`, processIncarnation(row.pid));
1725
+ }
1726
+ return await SessionIndex.#enqueue(indexPath, async () => {
1727
+ try {
1728
+ await fs.stat(dirFor(this.#agentDir));
1729
+ } catch (error) {
1730
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return "not_found";
1731
+ throw error;
1732
+ }
1733
+ return await withSessionIndexLock("stale session retirement", this.#agentDir, async () => {
1734
+ await this.#replayUnderLock();
1735
+ if (this.#corruptSuffix) throw new Error("Cannot retire a session from a corrupt session index");
1736
+ const current = this.listSessions(probed).sessions.find(
1737
+ session => session.sessionId === expected.sessionId,
1738
+ );
1739
+ if (!current) return "not_found";
1740
+ if (
1741
+ resolveEquivalentPath(current.locator.cwd) !== resolveEquivalentPath(expected.workspace) ||
1742
+ current.endpointGeneration !== expected.endpointGeneration ||
1743
+ endpointIncarnation(current, expected.sessionId) !== expected.endpointIncarnation
1744
+ )
1745
+ return "stale";
1746
+ if (current.ambiguous || current.terminalUncertain) return "stale";
1747
+ if (current.terminal) return "already_retired";
1748
+ if (
1749
+ current.live !== false ||
1750
+ current.lastHeartbeatAt === undefined ||
1751
+ this.#policy.clock() - current.lastHeartbeatAt < DEAD_HOST_HEARTBEAT_SILENCE_MS
1752
+ )
1753
+ return "stale";
1754
+ const unsigned: Omit<SessionIndexEvent, "checksum"> = {
1755
+ version: SESSION_INDEX_EVENT_VERSION,
1756
+ indexSeq: this.indexSeq + 1,
1757
+ ts: Date.now(),
1758
+ type: "session_closed",
1759
+ sessionId: current.sessionId,
1760
+ locator: current.locator,
1761
+ endpointGeneration: current.endpointGeneration,
1762
+ pid: current.pid,
1763
+ ...(current.processIncarnation === undefined ? {} : { processIncarnation: current.processIncarnation }),
1764
+ ...(current.hostIncarnation === undefined ? {} : { hostIncarnation: current.hostIncarnation }),
1765
+ ...(current.endpointMtimeMs === undefined ? {} : { endpointMtimeMs: current.endpointMtimeMs }),
1766
+ ...(current.endpointFileId === undefined ? {} : { endpointFileId: current.endpointFileId }),
1767
+ ...(current.lifecycleRequestId === undefined ? {} : { lifecycleRequestId: current.lifecycleRequestId }),
1768
+ };
1769
+ const event: SessionIndexEvent = { ...unsigned, checksum: sessionIndexChecksum(unsigned) };
1770
+ await appendSync(logFor(this.#agentDir), JSON.stringify(event));
1771
+ await this.#refreshUnderLock();
1772
+ if ((await fs.stat(logFor(this.#agentDir))).size >= ROTATE_BYTES) await this.#rotate();
1773
+ return "retired";
1774
+ });
1775
+ });
1776
+ }
1777
+
1690
1778
  async unregisterIfCurrent(
1691
1779
  expected: Pick<
1692
1780
  SessionIndexEvent,
@@ -1701,6 +1789,7 @@ export class SessionIndex {
1701
1789
  | "processIncarnation"
1702
1790
  | "hostIncarnation"
1703
1791
  >,
1792
+ reason?: HostUnregisteredReason,
1704
1793
  ): Promise<boolean> {
1705
1794
  const indexPath = path.resolve(logFor(this.#agentDir));
1706
1795
  return await SessionIndex.#enqueue(indexPath, async () => {
@@ -1781,6 +1870,7 @@ export class SessionIndex {
1781
1870
  ...(expected.lifecycleRequestId === undefined
1782
1871
  ? {}
1783
1872
  : { lifecycleRequestId: expected.lifecycleRequestId }),
1873
+ ...(reason === undefined ? {} : { hostUnregisteredReason: reason }),
1784
1874
  };
1785
1875
  const event: SessionIndexEvent = { ...unsigned, checksum: sessionIndexChecksum(unsigned) };
1786
1876
  await appendSync(logFor(this.#agentDir), JSON.stringify(event));
@@ -1866,10 +1956,10 @@ export class SessionIndex {
1866
1956
  this.#changeStamp = await readIndexChangeStamp(this.#agentDir);
1867
1957
  }
1868
1958
 
1869
- listSessions(): SessionList {
1959
+ listSessions(probedIncarnations?: ReadonlyMap<string, string | undefined>): SessionList {
1870
1960
  return {
1871
1961
  indexSeq: this.indexSeq,
1872
- sessions: reduceEvents(this.#events, this.#policy.clock(), this.#agentDir).sessions,
1962
+ sessions: reduceEvents(this.#events, this.#policy.clock(), this.#agentDir, probedIncarnations).sessions,
1873
1963
  warnings: this.#warnings,
1874
1964
  };
1875
1965
  }
@@ -2323,7 +2413,10 @@ export class SessionIndex {
2323
2413
  ...(row.processIncarnation === undefined ? {} : { processIncarnation: row.processIncarnation }),
2324
2414
  ...(row.hostIncarnation === undefined ? {} : { hostIncarnation: row.hostIncarnation }),
2325
2415
  ...(row.masterRole === undefined ? {} : { masterRole: row.masterRole }),
2326
- activity: { state: "active", at: now },
2416
+ // Preserve the host's last observed activity state while this
2417
+ // broker-owned heartbeat only renews liveness. A live idle host
2418
+ // must not be rewritten as active merely because its pid is alive.
2419
+ activity: row.activity ?? { state: "active", at: now },
2327
2420
  ts: now,
2328
2421
  };
2329
2422
  events.push({ ...unsigned, checksum: sessionIndexChecksum(unsigned) });
@@ -23,6 +23,8 @@ export type SpawnClaimV2 = {
23
23
  failure?: SpawnSubstrateFailure;
24
24
  preSendLease?: { epoch: string; status: "owned" | "consumed" };
25
25
  childId?: string;
26
+ /** Launch proof retained while registration is still unbound so a later retry can re-prove or close it. */
27
+ substrateProof?: SpawnSubstrateProof;
26
28
  seed?: SeedDeliveryV2;
27
29
  authorityRef?: string;
28
30
  createdAt: number;
@@ -139,6 +141,8 @@ export type SpawnClaimTransition = {
139
141
  leaseEpoch?: string;
140
142
  /** The fresh child ID is committed before substrate launch. */
141
143
  childId?: string;
144
+ /** Launch proof may be appended to the pre-registration state for later reconciliation. */
145
+ substrateProof?: SpawnSubstrateProof;
142
146
  /** Required for seed and terminal state transitions that carry Q26 facts. */
143
147
  seed?: SeedDeliveryV2;
144
148
  failure?: SpawnSubstrateFailure;
@@ -166,7 +170,8 @@ export type SpawnClaimDecision =
166
170
  | { kind: "replay"; claim: SpawnClaimV2 }
167
171
  | { kind: "terminal"; claim: SpawnClaimV2 }
168
172
  | { kind: "terminal_uncertain"; claim: SpawnClaimV2 }
169
- | { kind: "idempotency_conflict"; claim: SpawnClaimV2 };
173
+ | { kind: "idempotency_conflict"; claim: SpawnClaimV2 }
174
+ | { kind: "managed_key_conflict" };
170
175
 
171
176
  type StoredClaim = {
172
177
  version: 1;
@@ -175,6 +180,15 @@ type StoredClaim = {
175
180
  integrity: string;
176
181
  };
177
182
 
183
+ type ManagedKeyReservation = {
184
+ version: 1;
185
+ kind: "managed_key_reservation";
186
+ aliasIdentities: string[];
187
+ managedIdentity: string;
188
+ controlRoot: string;
189
+ integrity: string;
190
+ };
191
+
178
192
  type SpawnAuthorityDurableWrite = {
179
193
  claim: SpawnClaimV2;
180
194
  authority?: SpawnAuthorityV1;
@@ -225,6 +239,7 @@ const CLAIM_KEYS = new Set([
225
239
  "failure",
226
240
  "preSendLease",
227
241
  "childId",
242
+ "substrateProof",
228
243
  "seed",
229
244
  "authorityRef",
230
245
  "createdAt",
@@ -272,13 +287,13 @@ const FORBIDDEN_FIELD =
272
287
  /(?:task|prompt|capability|idempotency|fingerprint|requesthash|digest|credential|token|secret|password|stderr)/i;
273
288
  const TRANSITION_EDGES: Readonly<Record<SpawnClaimV2["state"], readonly SpawnClaimV2["state"][]>> = {
274
289
  prepared: ["substrate_starting", "pre_send_rejected", "uncertain", "closed"],
275
- substrate_starting: ["authority_active", "pre_send_rejected", "uncertain", "closed"],
290
+ substrate_starting: ["substrate_starting", "authority_active", "pre_send_rejected", "uncertain", "closed"],
276
291
  authority_active: ["seed_prepared", "pre_send_rejected", "uncertain", "closed"],
277
292
  seed_prepared: ["dispatching", "pre_send_rejected", "uncertain", "closed"],
278
293
  dispatching: ["accepted", "pre_send_rejected", "uncertain", "closed"],
279
294
  accepted: ["uncertain", "closed"],
280
295
  pre_send_rejected: ["uncertain", "closed"],
281
- uncertain: ["closed"],
296
+ uncertain: ["pre_send_rejected", "closed"],
282
297
  closed: [],
283
298
  };
284
299
 
@@ -476,6 +491,7 @@ export function isSpawnClaimV2(value: unknown): value is SpawnClaimV2 {
476
491
  ((lease as { status?: unknown }).status === "owned" ||
477
492
  (lease as { status?: unknown }).status === "consumed"))) &&
478
493
  (claim.childId === undefined || isOpaque(claim.childId)) &&
494
+ (claim.substrateProof === undefined || isSpawnSubstrateProof(claim.substrateProof)) &&
479
495
  (claim.seed === undefined || isSeed(claim.seed)) &&
480
496
  (claim.authorityRef === undefined || isOpaque(claim.authorityRef)) &&
481
497
  (claim.failure === undefined || isSpawnSubstrateFailure(claim.failure)) &&
@@ -554,6 +570,8 @@ export class SpawnAuthorityStore {
554
570
  readonly #options: SpawnAuthorityStoreOptions;
555
571
  readonly #latest = new Map<string, SpawnClaimV2>();
556
572
  readonly #authorities = new Map<string, SpawnAuthorityV1>();
573
+ readonly #managedKeyAliases = new Map<string, Map<string, string>>();
574
+ readonly #managedIdentityBindings = new Map<string, { aliasIdentities: string[]; controlRoot: string }>();
557
575
  readonly #active = new Set<string>();
558
576
  #tail: Promise<void> = Promise.resolve();
559
577
 
@@ -586,6 +604,8 @@ export class SpawnAuthorityStore {
586
604
  await fs.mkdir(path.dirname(this.#file), { recursive: true, mode: 0o700 });
587
605
  this.#latest.clear();
588
606
  this.#authorities.clear();
607
+ this.#managedKeyAliases.clear();
608
+ this.#managedIdentityBindings.clear();
589
609
  let source = "";
590
610
  try {
591
611
  source = await Bun.file(this.#file).text();
@@ -600,6 +620,12 @@ export class SpawnAuthorityStore {
600
620
  } catch {
601
621
  throw new Error("Spawn authority journal contains malformed durable evidence.");
602
622
  }
623
+ if (this.#isManagedKeyReservation(row)) {
624
+ const reservation = row as ManagedKeyReservation;
625
+ if (!this.#rememberManagedKeyReservation(reservation))
626
+ throw new Error("Spawn authority journal managed key history is invalid.");
627
+ continue;
628
+ }
603
629
  if (!this.#isStoredClaim(row))
604
630
  throw new Error("Spawn authority journal contains invalid durable evidence.");
605
631
  const stored = row as StoredClaim;
@@ -610,6 +636,52 @@ export class SpawnAuthorityStore {
610
636
  if (stored.authority) this.#authorities.set(stored.claim.lifecycleIdentity, stored.authority);
611
637
  else this.#authorities.delete(stored.claim.lifecycleIdentity);
612
638
  }
639
+ for (const alias of this.#managedKeyAliases.keys())
640
+ if (this.#latest.has(alias)) throw new Error("Spawn authority journal aliases an ordinary claim.");
641
+ });
642
+ }
643
+
644
+ /**
645
+ * Durably reserves an ordinary session.spawn idempotency identity for one
646
+ * managed native identity. Multiple managed roots may share the alias, but an
647
+ * ordinary claim and a managed reservation can never both win.
648
+ */
649
+ async reserveManagedKeyAliases(
650
+ aliasIdentities: readonly string[],
651
+ managedIdentity: string,
652
+ controlRoot: string,
653
+ ): Promise<boolean> {
654
+ const aliases = [...new Set(aliasIdentities)].sort();
655
+ if (
656
+ aliases.length === 0 ||
657
+ aliases.some(identity => !/^[0-9a-f]{64}$/i.test(identity)) ||
658
+ !/^[0-9a-f]{64}$/i.test(managedIdentity) ||
659
+ !path.isAbsolute(controlRoot) ||
660
+ path.normalize(controlRoot) !== controlRoot
661
+ )
662
+ throw new Error("Invalid managed spawn key reservation.");
663
+ return await this.#serial(async () => {
664
+ if (aliases.some(alias => this.#latest.has(alias))) return false;
665
+ const prior = this.#managedIdentityBindings.get(managedIdentity);
666
+ if (prior)
667
+ return prior.controlRoot === controlRoot && canonicalJson(prior.aliasIdentities) === canonicalJson(aliases);
668
+ for (const alias of aliases) {
669
+ const roots = this.#managedKeyAliases.get(alias);
670
+ const existingRoot = roots?.get(managedIdentity);
671
+ if (existingRoot !== undefined && existingRoot !== controlRoot) return false;
672
+ }
673
+ const reservation: ManagedKeyReservation = {
674
+ version: 1,
675
+ kind: "managed_key_reservation",
676
+ aliasIdentities: aliases,
677
+ managedIdentity,
678
+ controlRoot,
679
+ integrity: "",
680
+ };
681
+ reservation.integrity = this.#managedKeyReservationIntegrity(reservation);
682
+ await this.#appendRow(reservation);
683
+ this.#rememberManagedKeyReservation(reservation);
684
+ return true;
613
685
  });
614
686
  }
615
687
 
@@ -626,6 +698,7 @@ export class SpawnAuthorityStore {
626
698
  )
627
699
  throw new Error("Invalid opaque spawn claim key.");
628
700
  return await this.#serial(async () => {
701
+ if (this.#managedKeyAliases.has(lifecycleIdentity)) return { kind: "managed_key_conflict" };
629
702
  const current = this.#latest.get(lifecycleIdentity);
630
703
  if (!current) {
631
704
  if (bindingMac === undefined) throw new Error("A canonical spawn binding is required for a new claim.");
@@ -698,10 +771,19 @@ export class SpawnAuthorityStore {
698
771
  !isOpaque(transition.childId) ||
699
772
  transition.seed !== undefined ||
700
773
  transition.authority !== undefined ||
701
- transition.failure !== undefined
774
+ transition.failure !== undefined ||
775
+ (transition.substrateProof !== undefined && !isSpawnSubstrateProof(transition.substrateProof)) ||
776
+ (current.state === "prepared" && transition.substrateProof !== undefined) ||
777
+ (current.state === "substrate_starting" && transition.substrateProof === undefined) ||
778
+ (current.state === "substrate_starting" && transition.childId !== current.childId)
702
779
  )
703
780
  throw new SpawnAuthorityTransitionError("invalid_transition");
704
- next = { ...current, state: "substrate_starting", childId: transition.childId };
781
+ next = {
782
+ ...current,
783
+ state: "substrate_starting",
784
+ childId: transition.childId,
785
+ ...(transition.substrateProof === undefined ? {} : { substrateProof: transition.substrateProof }),
786
+ };
705
787
  break;
706
788
  }
707
789
  case "authority_active": {
@@ -804,7 +886,11 @@ export class SpawnAuthorityStore {
804
886
  break;
805
887
  }
806
888
  case "uncertain": {
807
- if (transition.childId !== undefined || transition.authority !== undefined)
889
+ if (
890
+ transition.childId !== undefined ||
891
+ transition.authority !== undefined ||
892
+ (transition.failure !== undefined && !isSpawnSubstrateFailure(transition.failure))
893
+ )
808
894
  throw new SpawnAuthorityTransitionError("invalid_transition");
809
895
  if (current.seed) {
810
896
  if (
@@ -813,10 +899,19 @@ export class SpawnAuthorityStore {
813
899
  !sameSeedIdentity(current.seed, transition.seed)
814
900
  )
815
901
  throw new SpawnAuthorityTransitionError("invalid_transition");
816
- next = { ...current, state: "uncertain", seed: transition.seed };
902
+ next = {
903
+ ...current,
904
+ state: "uncertain",
905
+ seed: transition.seed,
906
+ ...(transition.failure === undefined ? {} : { failure: transition.failure }),
907
+ };
817
908
  } else {
818
909
  if (transition.seed !== undefined) throw new SpawnAuthorityTransitionError("invalid_transition");
819
- next = { ...current, state: "uncertain" };
910
+ next = {
911
+ ...current,
912
+ state: "uncertain",
913
+ ...(transition.failure === undefined ? {} : { failure: transition.failure }),
914
+ };
820
915
  }
821
916
  break;
822
917
  }
@@ -936,6 +1031,68 @@ export class SpawnAuthorityStore {
936
1031
  .digest("hex");
937
1032
  }
938
1033
 
1034
+ #managedKeyReservationIntegrity(reservation: ManagedKeyReservation): string {
1035
+ const { integrity: _integrity, ...facts } = reservation;
1036
+ return createHmac("sha256", this.#identityKey).update(canonicalJson(facts)).digest("hex");
1037
+ }
1038
+
1039
+ #isManagedKeyReservation(value: unknown): value is ManagedKeyReservation {
1040
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1041
+ const row = value as Partial<ManagedKeyReservation>;
1042
+ return (
1043
+ Object.keys(row).every(
1044
+ key =>
1045
+ key === "version" ||
1046
+ key === "kind" ||
1047
+ key === "aliasIdentities" ||
1048
+ key === "managedIdentity" ||
1049
+ key === "controlRoot" ||
1050
+ key === "integrity",
1051
+ ) &&
1052
+ row.version === 1 &&
1053
+ row.kind === "managed_key_reservation" &&
1054
+ Array.isArray(row.aliasIdentities) &&
1055
+ row.aliasIdentities.length > 0 &&
1056
+ row.aliasIdentities.every(identity => typeof identity === "string" && /^[0-9a-f]{64}$/i.test(identity)) &&
1057
+ new Set(row.aliasIdentities).size === row.aliasIdentities.length &&
1058
+ canonicalJson(row.aliasIdentities) === canonicalJson([...row.aliasIdentities].sort()) &&
1059
+ typeof row.managedIdentity === "string" &&
1060
+ /^[0-9a-f]{64}$/i.test(row.managedIdentity) &&
1061
+ typeof row.controlRoot === "string" &&
1062
+ path.isAbsolute(row.controlRoot) &&
1063
+ path.normalize(row.controlRoot) === row.controlRoot &&
1064
+ typeof row.integrity === "string" &&
1065
+ /^[0-9a-f]{64}$/i.test(row.integrity) &&
1066
+ timingSafeEqual(
1067
+ Buffer.from(row.integrity, "hex"),
1068
+ Buffer.from(this.#managedKeyReservationIntegrity(row as ManagedKeyReservation), "hex"),
1069
+ )
1070
+ );
1071
+ }
1072
+
1073
+ #rememberManagedKeyReservation(reservation: ManagedKeyReservation): boolean {
1074
+ const prior = this.#managedIdentityBindings.get(reservation.managedIdentity);
1075
+ if (
1076
+ prior &&
1077
+ (prior.controlRoot !== reservation.controlRoot ||
1078
+ canonicalJson(prior.aliasIdentities) !== canonicalJson(reservation.aliasIdentities))
1079
+ )
1080
+ return false;
1081
+ if (prior) return false;
1082
+ this.#managedIdentityBindings.set(reservation.managedIdentity, {
1083
+ aliasIdentities: [...reservation.aliasIdentities],
1084
+ controlRoot: reservation.controlRoot,
1085
+ });
1086
+ for (const alias of reservation.aliasIdentities) {
1087
+ const roots = this.#managedKeyAliases.get(alias) ?? new Map<string, string>();
1088
+ const existingRoot = roots.get(reservation.managedIdentity);
1089
+ if (existingRoot !== undefined && existingRoot !== reservation.controlRoot) return false;
1090
+ roots.set(reservation.managedIdentity, reservation.controlRoot);
1091
+ this.#managedKeyAliases.set(alias, roots);
1092
+ }
1093
+ return true;
1094
+ }
1095
+
939
1096
  #isStoredClaim(value: unknown): value is StoredClaim {
940
1097
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
941
1098
  const row = value as Partial<StoredClaim>;
@@ -961,6 +1118,13 @@ export class SpawnAuthorityStore {
961
1118
  throw new Error("Refusing to persist invalid spawn authority evidence.");
962
1119
  const write: SpawnAuthorityDurableWrite = { claim, ...(authority === undefined ? {} : { authority }) };
963
1120
  const row: StoredClaim = { version: 1, ...write, integrity: this.#integrity(claim, authority) };
1121
+ await this.#appendRow(row, write);
1122
+ this.#latest.set(claim.lifecycleIdentity, claim);
1123
+ if (authority) this.#authorities.set(claim.lifecycleIdentity, authority);
1124
+ else this.#authorities.delete(claim.lifecycleIdentity);
1125
+ }
1126
+
1127
+ async #appendRow(row: unknown, write?: SpawnAuthorityDurableWrite): Promise<void> {
964
1128
  // The row is appended before the durability barrier, so a barrier failure
965
1129
  // can leave a physically written line that in-memory state never accepted.
966
1130
  // A retry would then append a second row for the same claim and reopen
@@ -981,13 +1145,13 @@ export class SpawnAuthorityStore {
981
1145
  try {
982
1146
  try {
983
1147
  await handle.writeFile(`${canonicalJson(row)}\n`);
984
- await this.#options.beforeSyncForTest?.(write);
1148
+ if (write) await this.#options.beforeSyncForTest?.(write);
985
1149
  await handle.sync();
986
1150
  // The parent-directory barrier belongs INSIDE the rolled-back region:
987
1151
  // a throw here would otherwise leave a durable row that in-memory
988
1152
  // state never accepted, and the retry would append a duplicate that
989
1153
  // makes the journal unreopenable.
990
- await this.#options.beforeDirectorySyncForTest?.(write);
1154
+ if (write) await this.#options.beforeDirectorySyncForTest?.(write);
991
1155
  const directory = await fs.open(path.dirname(this.#file), fsSync.constants.O_RDONLY);
992
1156
  try {
993
1157
  await directory.sync();
@@ -1011,10 +1175,7 @@ export class SpawnAuthorityStore {
1011
1175
  } finally {
1012
1176
  await handle.close();
1013
1177
  }
1014
- this.#latest.set(claim.lifecycleIdentity, claim);
1015
- if (authority) this.#authorities.set(claim.lifecycleIdentity, authority);
1016
- else this.#authorities.delete(claim.lifecycleIdentity);
1017
- await this.#options.afterSyncForTest?.(write);
1178
+ if (write) await this.#options.afterSyncForTest?.(write);
1018
1179
  }
1019
1180
 
1020
1181
  async #serial<T>(operation: () => Promise<T>): Promise<T> {
@@ -5,6 +5,8 @@ import { processIncarnation } from "./process-incarnation";
5
5
  export interface BrokerStartupFailureMarker {
6
6
  version: 2;
7
7
  reason: string;
8
+ /** Validated manual lock cleanup command, stored separately when it cannot fit in `reason`. */
9
+ cleanupCommand?: string;
8
10
  exitCode: number | null;
9
11
  signal: string | null;
10
12
  writtenAt: number;
@@ -23,11 +25,82 @@ export interface BrokerStartupFailureMarker {
23
25
 
24
26
  const BROKER_STARTUP_FAILURE_FILE = "broker.startup-failure.json";
25
27
  const MAX_BROKER_STARTUP_FAILURE_REASON = 512;
28
+ // Windows extended paths can be 32,767 UTF-16 code units and PowerShell quoting
29
+ // can double every apostrophe; this also comfortably covers POSIX PATH_MAX paths.
30
+ const MAX_BROKER_STARTUP_FAILURE_CLEANUP_COMMAND = 66_000;
31
+ const FILE_LOCK_CLEANUP_GUIDANCE =
32
+ "; manual cleanup is appropriate only after independently verifying this exact directory still belongs to the dead owner and no successor has taken it: ";
33
+ const FILE_LOCK_OWNER_GUIDANCE =
34
+ "; a live owner is never displaced — if this is an SDK broker (gjc sdk session list), it must finish or be stopped before retrying";
35
+ const COMPACT_FILE_LOCK_CLEANUP_GUIDANCE =
36
+ "Manual cleanup is appropriate only after independently verifying this exact directory still belongs to the dead owner and no successor has taken it: ";
37
+
38
+ function decodeFileLockCleanupCommand(command: string): string | undefined {
39
+ if (command.length === 0 || command.length > MAX_BROKER_STARTUP_FAILURE_CLEANUP_COMMAND) return undefined;
40
+ const prefix = process.platform === "win32" ? "Remove-Item -LiteralPath '" : "rm -rf -- '";
41
+ const suffix = process.platform === "win32" ? "' -Recurse -Force" : "'";
42
+ if (!command.startsWith(prefix) || !command.endsWith(suffix)) return undefined;
43
+
44
+ const encodedPath = command.slice(prefix.length, -suffix.length);
45
+ const quoteEscape = process.platform === "win32" ? "''" : "'\\''";
46
+ const decodedPath = encodedPath.replaceAll(quoteEscape, "'");
47
+ if (!decodedPath || decodedPath.replaceAll("'", quoteEscape) !== encodedPath) return undefined;
48
+ return decodedPath;
49
+ }
50
+
51
+ function validatedFileLockCleanupCommand(reason: string): string | undefined {
52
+ let guidanceIndex = reason.indexOf(FILE_LOCK_CLEANUP_GUIDANCE);
53
+ while (guidanceIndex >= 0) {
54
+ const command = reason.slice(guidanceIndex + FILE_LOCK_CLEANUP_GUIDANCE.length);
55
+ const lockPath = decodeFileLockCleanupCommand(command);
56
+ if (lockPath) {
57
+ const beforeCleanup = reason.slice(0, guidanceIndex);
58
+ const lockAndOwnerGuidance = ` (${lockPath})${FILE_LOCK_OWNER_GUIDANCE}`;
59
+ const acquisition = beforeCleanup.endsWith(lockAndOwnerGuidance)
60
+ ? beforeCleanup.slice(0, -lockAndOwnerGuidance.length)
61
+ : "";
62
+ if (/^Failed to acquire lock for [\s\S]+ after \d+ attempts: [\s\S]+$/.test(acquisition)) {
63
+ return command;
64
+ }
65
+ }
66
+ guidanceIndex = reason.indexOf(FILE_LOCK_CLEANUP_GUIDANCE, guidanceIndex + 1);
67
+ }
68
+ return undefined;
69
+ }
70
+
71
+ function boundedFailureDetails(reason: string): { reason: string; cleanupCommand?: string } {
72
+ if (reason.length <= MAX_BROKER_STARTUP_FAILURE_REASON) return { reason };
73
+
74
+ const cleanupCommand = validatedFileLockCleanupCommand(reason);
75
+ if (cleanupCommand) {
76
+ const compactReason = `${COMPACT_FILE_LOCK_CLEANUP_GUIDANCE}${cleanupCommand}`;
77
+ if (compactReason.length <= MAX_BROKER_STARTUP_FAILURE_REASON) return { reason: compactReason };
78
+ return { reason: COMPACT_FILE_LOCK_CLEANUP_GUIDANCE, cleanupCommand };
79
+ }
80
+
81
+ return { reason: reason.slice(0, MAX_BROKER_STARTUP_FAILURE_REASON) };
82
+ }
26
83
 
27
84
  export function brokerStartupFailurePath(agentDir: string): string {
28
85
  return path.join(agentDir, "sdk", BROKER_STARTUP_FAILURE_FILE);
29
86
  }
30
87
 
88
+ /** Whether validated compact cleanup guidance targets the exact lock path. */
89
+ export function brokerStartupFailureCleanupTargetsLock(
90
+ marker: BrokerStartupFailureMarker | undefined,
91
+ lockPath: string,
92
+ ): boolean {
93
+ if (!marker) return false;
94
+ if (marker.cleanupCommand !== undefined) {
95
+ return (
96
+ marker.reason === COMPACT_FILE_LOCK_CLEANUP_GUIDANCE &&
97
+ decodeFileLockCleanupCommand(marker.cleanupCommand) === lockPath
98
+ );
99
+ }
100
+ if (!marker.reason.startsWith(COMPACT_FILE_LOCK_CLEANUP_GUIDANCE)) return false;
101
+ return decodeFileLockCleanupCommand(marker.reason.slice(COMPACT_FILE_LOCK_CLEANUP_GUIDANCE.length)) === lockPath;
102
+ }
103
+
31
104
  function boundedMarker(
32
105
  reason: string,
33
106
  exitCode: number | null,
@@ -35,9 +108,10 @@ function boundedMarker(
35
108
  pid: number,
36
109
  incarnation: string,
37
110
  ): BrokerStartupFailureMarker {
111
+ const boundedFailure = boundedFailureDetails(reason);
38
112
  return {
39
113
  version: 2,
40
- reason: reason.slice(0, MAX_BROKER_STARTUP_FAILURE_REASON),
114
+ ...boundedFailure,
41
115
  exitCode,
42
116
  signal,
43
117
  writtenAt: Date.now(),
@@ -84,6 +158,11 @@ export async function readBrokerStartupFailureMarker(
84
158
  marker.version !== 2 ||
85
159
  typeof marker.reason !== "string" ||
86
160
  marker.reason.length === 0 ||
161
+ marker.reason.length > MAX_BROKER_STARTUP_FAILURE_REASON ||
162
+ (marker.cleanupCommand !== undefined &&
163
+ (typeof marker.cleanupCommand !== "string" ||
164
+ marker.reason !== COMPACT_FILE_LOCK_CLEANUP_GUIDANCE ||
165
+ decodeFileLockCleanupCommand(marker.cleanupCommand) === undefined)) ||
87
166
  (marker.exitCode !== null && typeof marker.exitCode !== "number") ||
88
167
  (marker.signal !== null && typeof marker.signal !== "string") ||
89
168
  typeof marker.writtenAt !== "number" ||