@gajae-code/coding-agent 0.17.2 → 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 (349) hide show
  1. package/CHANGELOG.md +194 -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 +18 -0
  18. package/dist/types/config/file-lock.d.ts +9 -1
  19. package/dist/types/config/model-profile-activation.d.ts +45 -1
  20. package/dist/types/config/model-registry.d.ts +11 -2
  21. package/dist/types/config/settings-schema.d.ts +11 -0
  22. package/dist/types/coordinator-mcp/question-state.d.ts +6 -1
  23. package/dist/types/daemon/operator-contract.d.ts +5 -1
  24. package/dist/types/eval/js/worker-protocol.d.ts +2 -0
  25. package/dist/types/extensibility/extensions/types.d.ts +12 -0
  26. package/dist/types/extensibility/gjc-plugins/registry.d.ts +4 -0
  27. package/dist/types/extensibility/runtime-skill-discovery.d.ts +18 -0
  28. package/dist/types/extensibility/shared-events.d.ts +3 -1
  29. package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
  30. package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
  31. package/dist/types/internal-urls/index.d.ts +1 -0
  32. package/dist/types/internal-urls/parse.d.ts +9 -1
  33. package/dist/types/main.d.ts +22 -0
  34. package/dist/types/modes/acp/acp-agent.d.ts +55 -1
  35. package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
  36. package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
  37. package/dist/types/modes/components/session-selector.d.ts +5 -2
  38. package/dist/types/modes/components/user-message-selector.d.ts +5 -2
  39. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  40. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  41. package/dist/types/modes/interactive-mode.d.ts +1 -1
  42. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
  43. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
  44. package/dist/types/modes/types.d.ts +2 -2
  45. package/dist/types/runtime/process-lifecycle.d.ts +2 -0
  46. package/dist/types/runtime-mcp/manager.d.ts +56 -0
  47. package/dist/types/runtime-mcp/pool.d.ts +17 -0
  48. package/dist/types/runtime-mcp/redaction.d.ts +20 -0
  49. package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
  50. package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
  51. package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
  52. package/dist/types/sdk/broker/broker.d.ts +13 -2
  53. package/dist/types/sdk/broker/identity.d.ts +3 -1
  54. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
  55. package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
  56. package/dist/types/sdk/broker/runtime.d.ts +1 -1
  57. package/dist/types/sdk/broker/session-index.d.ts +22 -3
  58. package/dist/types/sdk/broker/spawn-authority.d.ts +4 -0
  59. package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
  60. package/dist/types/sdk/bus/index.d.ts +19 -0
  61. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
  62. package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
  63. package/dist/types/sdk/cli/master-cli.d.ts +6 -10
  64. package/dist/types/sdk/cli/rows.d.ts +19 -1
  65. package/dist/types/sdk/cli/session-cli.d.ts +22 -5
  66. package/dist/types/sdk/client/client.d.ts +2 -0
  67. package/dist/types/sdk/guides/cli.d.ts +3 -4
  68. package/dist/types/sdk/guides/verify.d.ts +2 -0
  69. package/dist/types/sdk/host/host.d.ts +9 -0
  70. package/dist/types/sdk/host/session-runtime.d.ts +31 -0
  71. package/dist/types/sdk/host/types.d.ts +10 -0
  72. package/dist/types/sdk/index.d.ts +1 -0
  73. package/dist/types/sdk/prompt-deadline-flush.d.ts +83 -0
  74. package/dist/types/sdk/prompt-deadline-manager.d.ts +37 -0
  75. package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
  76. package/dist/types/sdk/router/session-router.d.ts +2 -0
  77. package/dist/types/sdk/startup-capability.d.ts +2 -0
  78. package/dist/types/sdk/transport/relay.d.ts +1 -0
  79. package/dist/types/sdk/transport/serve-cli.d.ts +48 -4
  80. package/dist/types/sdk/transport/stdio.d.ts +5 -1
  81. package/dist/types/session/agent-session.d.ts +168 -21
  82. package/dist/types/session/fallback-chain-controller.d.ts +6 -6
  83. package/dist/types/session/fold-coordinator.d.ts +17 -3
  84. package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
  85. package/dist/types/session/session-manager.d.ts +64 -1
  86. package/dist/types/session/session-work-lease.d.ts +14 -0
  87. package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
  88. package/dist/types/setup/provider-onboarding.d.ts +81 -1
  89. package/dist/types/tools/bash.d.ts +37 -11
  90. package/dist/types/tools/job.d.ts +1 -1
  91. package/dist/types/tools/steer-fold.d.ts +31 -0
  92. package/dist/types/utils/git.d.ts +28 -2
  93. package/dist/types/utils/mupdf-embedded.d.ts +2 -0
  94. package/dist/types/utils/mupdf.d.ts +4 -0
  95. package/dist/types/utils/session-title-generation.d.ts +6 -0
  96. package/dist/types/utils/title-generator.d.ts +11 -2
  97. package/dist/types/web/scrapers/types.d.ts +3 -0
  98. package/package.json +18 -10
  99. package/scripts/build-binary.ts +9 -3
  100. package/scripts/compile-args.ts +0 -9
  101. package/scripts/embed-mupdf.ts +52 -0
  102. package/scripts/generate-sdk-operation-inventory.ts +31 -1
  103. package/scripts/vendor-markit.ts +196 -0
  104. package/src/async/job-manager.ts +22 -1
  105. package/src/cli/daemon-cli.ts +53 -18
  106. package/src/cli/github-release.ts +87 -5
  107. package/src/cli/mcp-cli.ts +38 -4
  108. package/src/cli/public-command-entry.ts +450 -0
  109. package/src/cli/public-command-errors.ts +581 -0
  110. package/src/cli/public-command-evidence.ts +717 -0
  111. package/src/cli/public-command-help.ts +360 -0
  112. package/src/cli/public-command-registry.ts +535 -0
  113. package/src/cli/session-picker.ts +2 -1
  114. package/src/cli/setup-cli.ts +9 -1
  115. package/src/cli/skills-cli.ts +46 -2
  116. package/src/cli.ts +61 -1
  117. package/src/commands/daemon.ts +65 -69
  118. package/src/commands/sdk.ts +175 -117
  119. package/src/commands/setup.ts +2 -0
  120. package/src/commands/skills.ts +11 -0
  121. package/src/config/autorouting-tier-map.ts +14 -0
  122. package/src/config/file-lock.ts +147 -51
  123. package/src/config/model-profile-activation.ts +334 -43
  124. package/src/config/model-registry.ts +183 -25
  125. package/src/config/settings-schema.ts +26 -2
  126. package/src/coordinator-mcp/policy.ts +10 -5
  127. package/src/coordinator-mcp/question-gate-codec.ts +17 -0
  128. package/src/coordinator-mcp/question-state.ts +175 -8
  129. package/src/coordinator-mcp/server.ts +507 -81
  130. package/src/coordinator-mcp/session-reaper.ts +6 -4
  131. package/src/crash/record-loader.ts +6 -2
  132. package/src/daemon/operator-contract.ts +26 -12
  133. package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
  134. package/src/eval/js/context-manager.ts +8 -8
  135. package/src/eval/js/worker-core.ts +9 -4
  136. package/src/eval/js/worker-protocol.ts +2 -0
  137. package/src/extensibility/extensions/runner.ts +52 -4
  138. package/src/extensibility/extensions/types.ts +10 -0
  139. package/src/extensibility/gjc-plugins/registry.ts +196 -37
  140. package/src/extensibility/runtime-skill-discovery.ts +61 -3
  141. package/src/extensibility/shared-events.ts +3 -1
  142. package/src/gjc-runtime/session-state-lock.ts +165 -24
  143. package/src/gjc-runtime/ultragoal-succession.ts +31 -3
  144. package/src/internal-urls/docs-index.generated.ts +20 -19
  145. package/src/internal-urls/embedded-protocol.ts +83 -0
  146. package/src/internal-urls/index.ts +1 -0
  147. package/src/internal-urls/parse.ts +21 -1
  148. package/src/internal-urls/router.ts +4 -2
  149. package/src/main.ts +123 -55
  150. package/src/modes/acp/acp-agent.ts +408 -35
  151. package/src/modes/acp/startup-provenance.ts +59 -0
  152. package/src/modes/components/custom-provider-wizard.ts +308 -7
  153. package/src/modes/components/oauth-selector.ts +9 -0
  154. package/src/modes/components/session-selector.ts +47 -4
  155. package/src/modes/components/user-message-selector.ts +57 -15
  156. package/src/modes/controllers/command-controller.ts +58 -32
  157. package/src/modes/controllers/extension-ui-controller.ts +33 -7
  158. package/src/modes/controllers/input-controller.ts +4 -0
  159. package/src/modes/controllers/selector-controller.ts +161 -15
  160. package/src/modes/interactive-mode.ts +21 -7
  161. package/src/modes/runtime-init.ts +1 -0
  162. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
  163. package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
  164. package/src/modes/types.ts +2 -2
  165. package/src/prompts/agent-fragments/browser-aside.md +1 -0
  166. package/src/prompts/system/title-system.md +1 -1
  167. package/src/prompts/tools/bash.md +1 -1
  168. package/src/prompts/tools/job.md +1 -1
  169. package/src/prompts/tools/read.md +2 -0
  170. package/src/runtime/network-prewarm-service.test.ts +36 -1
  171. package/src/runtime/network-prewarm-service.ts +28 -3
  172. package/src/runtime/process-lifecycle.ts +22 -11
  173. package/src/runtime-mcp/config.ts +16 -1
  174. package/src/runtime-mcp/manager.ts +689 -55
  175. package/src/runtime-mcp/pool.test.ts +31 -0
  176. package/src/runtime-mcp/pool.ts +304 -28
  177. package/src/runtime-mcp/redaction.ts +42 -0
  178. package/src/runtime-mcp/startup-policy.ts +7 -0
  179. package/src/runtime-mcp/tool-bridge.ts +71 -0
  180. package/src/runtime-mcp/transports/http.ts +108 -25
  181. package/src/sdk/acp/adapter.ts +27 -8
  182. package/src/sdk/broker/broker.ts +378 -52
  183. package/src/sdk/broker/identity.ts +9 -1
  184. package/src/sdk/broker/lifecycle-ledger.ts +95 -31
  185. package/src/sdk/broker/lifecycle.ts +690 -141
  186. package/src/sdk/broker/runtime.ts +1 -1
  187. package/src/sdk/broker/session-index.ts +97 -4
  188. package/src/sdk/broker/spawn-authority.ts +35 -7
  189. package/src/sdk/broker/stderr-drainer.ts +59 -0
  190. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  191. package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
  192. package/src/sdk/bus/index.ts +690 -80
  193. package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
  194. package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
  195. package/src/sdk/bus/telegram-daemon.ts +10 -3
  196. package/src/sdk/cli/master-cli.ts +47 -36
  197. package/src/sdk/cli/rows.ts +30 -7
  198. package/src/sdk/cli/session-cli.ts +687 -184
  199. package/src/sdk/client/client.ts +18 -0
  200. package/src/sdk/guides/catalog.ts +2 -2
  201. package/src/sdk/guides/cli.ts +20 -62
  202. package/src/sdk/guides/verify.ts +14 -0
  203. package/src/sdk/host/host.ts +49 -5
  204. package/src/sdk/host/session-runtime.test.ts +138 -2
  205. package/src/sdk/host/session-runtime.ts +311 -97
  206. package/src/sdk/host/types.ts +12 -1
  207. package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
  208. package/src/sdk/host/websocket-transport.ts +40 -6
  209. package/src/sdk/index.ts +11 -0
  210. package/src/sdk/prompt-deadline-flush.ts +334 -0
  211. package/src/sdk/prompt-deadline-manager.ts +133 -1
  212. package/src/sdk/prompt-tool-boundary.ts +121 -0
  213. package/src/sdk/protocol/operation-inventory.generated.json +106 -0
  214. package/src/sdk/router/session-router.ts +9 -1
  215. package/src/sdk/session.ts +182 -7
  216. package/src/sdk/startup-capability.ts +1 -1
  217. package/src/sdk/transport/relay.ts +71 -6
  218. package/src/sdk/transport/serve-cli.ts +397 -42
  219. package/src/sdk/transport/socket.ts +12 -11
  220. package/src/sdk/transport/stdio.ts +7 -3
  221. package/src/session/agent-session.ts +2437 -400
  222. package/src/session/fallback-chain-controller.ts +6 -7
  223. package/src/session/fold-coordinator.ts +24 -5
  224. package/src/session/internal/managed-session-scope.ts +58 -18
  225. package/src/session/internal/managed-session-storage.ts +304 -11
  226. package/src/session/session-manager.ts +382 -44
  227. package/src/session/session-work-lease.ts +34 -0
  228. package/src/setup/model-onboarding-guidance.ts +1 -1
  229. package/src/setup/provider-onboarding.ts +397 -24
  230. package/src/slash-commands/builtin-registry.ts +258 -14
  231. package/src/task/index.ts +14 -2
  232. package/src/tools/bash.ts +98 -78
  233. package/src/tools/fetch.ts +119 -20
  234. package/src/tools/job.ts +151 -25
  235. package/src/tools/output-meta.ts +15 -2
  236. package/src/tools/path-utils.ts +27 -3
  237. package/src/tools/read.ts +3 -3
  238. package/src/tools/steer-fold.ts +107 -0
  239. package/src/tools/tool-catalog.generated.ts +29 -2
  240. package/src/tools/tool-errors.ts +4 -0
  241. package/src/utils/git.ts +53 -3
  242. package/src/utils/markit.ts +55 -41
  243. package/src/utils/mupdf-assets.d.ts +4 -0
  244. package/src/utils/mupdf-embedded.ts +4 -0
  245. package/src/utils/mupdf.ts +129 -0
  246. package/src/utils/session-title-generation.ts +29 -0
  247. package/src/utils/title-generator.ts +46 -2
  248. package/src/web/scrapers/types.ts +24 -3
  249. package/vendor/markit-ai/LICENSE +21 -0
  250. package/vendor/markit-ai/MANIFEST.json +412 -0
  251. package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
  252. package/vendor/markit-ai/dist/commands/config.js +139 -0
  253. package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
  254. package/vendor/markit-ai/dist/commands/convert.js +113 -0
  255. package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
  256. package/vendor/markit-ai/dist/commands/formats.js +92 -0
  257. package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
  258. package/vendor/markit-ai/dist/commands/init.js +29 -0
  259. package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
  260. package/vendor/markit-ai/dist/commands/onboard.js +61 -0
  261. package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
  262. package/vendor/markit-ai/dist/commands/plugin.js +58 -0
  263. package/vendor/markit-ai/dist/config.d.ts +26 -0
  264. package/vendor/markit-ai/dist/config.js +42 -0
  265. package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
  266. package/vendor/markit-ai/dist/converters/audio.js +104 -0
  267. package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
  268. package/vendor/markit-ai/dist/converters/csv.js +83 -0
  269. package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
  270. package/vendor/markit-ai/dist/converters/docx.js +57 -0
  271. package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
  272. package/vendor/markit-ai/dist/converters/epub.js +128 -0
  273. package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
  274. package/vendor/markit-ai/dist/converters/github.js +148 -0
  275. package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
  276. package/vendor/markit-ai/dist/converters/html.js +30 -0
  277. package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
  278. package/vendor/markit-ai/dist/converters/image.js +124 -0
  279. package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
  280. package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
  281. package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
  282. package/vendor/markit-ai/dist/converters/iwork.js +391 -0
  283. package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
  284. package/vendor/markit-ai/dist/converters/json.js +21 -0
  285. package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
  286. package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
  287. package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
  288. package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
  289. package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
  290. package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
  291. package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
  292. package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
  293. package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
  294. package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
  295. package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
  296. package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
  297. package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
  298. package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
  299. package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
  300. package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
  301. package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
  302. package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
  303. package/vendor/markit-ai/dist/converters/pptx.js +289 -0
  304. package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
  305. package/vendor/markit-ai/dist/converters/rss.js +138 -0
  306. package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
  307. package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
  308. package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
  309. package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
  310. package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
  311. package/vendor/markit-ai/dist/converters/xml.js +18 -0
  312. package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
  313. package/vendor/markit-ai/dist/converters/yaml.js +17 -0
  314. package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
  315. package/vendor/markit-ai/dist/converters/zip.js +57 -0
  316. package/vendor/markit-ai/dist/index.d.ts +30 -0
  317. package/vendor/markit-ai/dist/index.js +26 -0
  318. package/vendor/markit-ai/dist/main.d.ts +2 -0
  319. package/vendor/markit-ai/dist/main.js +191 -0
  320. package/vendor/markit-ai/dist/markit.d.ts +45 -0
  321. package/vendor/markit-ai/dist/markit.js +267 -0
  322. package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
  323. package/vendor/markit-ai/dist/plugins/api.js +42 -0
  324. package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
  325. package/vendor/markit-ai/dist/plugins/index.js +3 -0
  326. package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
  327. package/vendor/markit-ai/dist/plugins/installer.js +176 -0
  328. package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
  329. package/vendor/markit-ai/dist/plugins/loader.js +61 -0
  330. package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
  331. package/vendor/markit-ai/dist/plugins/types.js +1 -0
  332. package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
  333. package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
  334. package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
  335. package/vendor/markit-ai/dist/providers/index.js +56 -0
  336. package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
  337. package/vendor/markit-ai/dist/providers/openai.js +76 -0
  338. package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
  339. package/vendor/markit-ai/dist/providers/types.js +1 -0
  340. package/vendor/markit-ai/dist/types.d.ts +36 -0
  341. package/vendor/markit-ai/dist/types.js +1 -0
  342. package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
  343. package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
  344. package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
  345. package/vendor/markit-ai/dist/utils/output.js +31 -0
  346. package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
  347. package/vendor/markit-ai/dist/utils/turndown.js +70 -0
  348. package/vendor/markit-ai/package.json +74 -0
  349. package/vendor/markit-ai.patch +49 -0
@@ -26,7 +26,12 @@ import {
26
26
  import { isAuthenticated, kNoAuth } from "../../config/model-registry";
27
27
  import { resolveModelChainWithAuth, splitSelectorThinkingSuffix } from "../../config/model-resolver";
28
28
  import { type ModelSelectorValue, normalizeModelSelectorValue } from "../../config/model-selector-value";
29
- import { type Settings, validateSettingPatch } from "../../config/settings";
29
+ import {
30
+ resolveSdkPromptDeadlineMs,
31
+ resolveSdkPromptMaxRuntimeMs,
32
+ type Settings,
33
+ validateSettingPatch,
34
+ } from "../../config/settings";
30
35
  import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "../../extensibility/extensions";
31
36
  import type { AgentEndEvent } from "../../extensibility/shared-events";
32
37
  import { normalizeGoal } from "../../goals/state";
@@ -59,6 +64,7 @@ import {
59
64
  syntheticNamespaceCollision,
60
65
  } from "../model-profile-model";
61
66
  import { projectQ10Models } from "../models.js";
67
+ import { flushWorktreeOnPromptDeadline } from "../prompt-deadline-flush";
62
68
  import { PromptDeadlineManager, type PromptTerminalTransitionEvidence } from "../prompt-deadline-manager";
63
69
  import {
64
70
  assistantFailureCode,
@@ -92,7 +98,12 @@ import {
92
98
  BROKER_RUNTIME_CLOSE_CAPABILITY_FIELD,
93
99
  hasBrokerRuntimeAbortCapability,
94
100
  } from "./control/runtime-gate";
95
- import { SessionSdkHost, type SessionSdkHostOptions } from "./host";
101
+ import {
102
+ SESSION_HOST_OBSERVER_CAPABILITY,
103
+ SessionSdkHost,
104
+ type SessionSdkHostOptions,
105
+ TURN_STREAM_CAPABILITY,
106
+ } from "./host";
96
107
  import { clearAutoroutingInactive, isAutoroutingInactive, markAutoroutingInactive } from "./internal-autorouting-state";
97
108
  import { CursorRegistry, QueryHandlers, RevisionStore, type SessionSurface } from "./query";
98
109
  import { createSdkRunCapability } from "./sdk-run-capability";
@@ -196,6 +207,13 @@ export function verifyMasterCapabilityFrame(input: {
196
207
 
197
208
  /** Maximum time a replaced runtime may retain a lifecycle persistence task. */
198
209
  const LIFECYCLE_QUIESCENCE_MS = 1_000;
210
+ /**
211
+ * Keep a live session's broker discovery recoverable well inside the session
212
+ * index's two-minute heartbeat freshness window. `ensureBroker` has its own
213
+ * bounded startup/recovery budget; the in-flight guard below prevents those
214
+ * budgets from stacking when one attempt outlives this cadence.
215
+ */
216
+ export const SESSION_BROKER_RECOVERY_INTERVAL_MS = 30_000;
199
217
 
200
218
  class DiffQueryError extends Error {
201
219
  constructor(
@@ -357,6 +375,17 @@ export interface SdkOnlyTerminalAbortSeams {
357
375
  terminal?: { scope: "turn" | "owned"; expectedEpoch?: number; steeringSnapshotToken?: number };
358
376
  },
359
377
  ) => Promise<{ status: string; terminalScope?: unknown }>;
378
+ /**
379
+ * Live, SYNCHRONOUS view of the dispatched tool calls the run resource
380
+ * ledger holds for an execution handle (#5637). Strictly read-only: it
381
+ * never claims, reserves, seals or quarantines ledger state. Declared here
382
+ * so both hosts expose ONE seam contract — the bus route consumes it for its
383
+ * deadline tool-boundary wait, and this route threads it so a future boundary
384
+ * wait or deadline abort reads the ledger rather than silently falling back
385
+ * to the event-derived set. Optional so an older host that does not thread it
386
+ * degrades to that fallback instead of failing to construct.
387
+ */
388
+ pendingToolExecutions?: (handle: string) => readonly string[];
360
389
  /** Test override for the maximum durable terminal reservation rows. */
361
390
  maxDurableTerminalReservationsForTests?: number;
362
391
  }
@@ -372,6 +401,9 @@ export interface SdkOnlyTerminalAbortSeams {
372
401
  export class SessionSdkSessionRuntime {
373
402
  readonly host: SessionSdkHost;
374
403
  readonly transport: SessionSdkTransport;
404
+ readonly #connectionCapabilities = new Map<string, ReadonlySet<string>>();
405
+ readonly #connectionIds = new Set<string>();
406
+ readonly #connectionCapabilitiesProvider: (connectionId: string) => ReadonlySet<string> | undefined;
375
407
  readonly #connectionDisposer?: () => void;
376
408
  readonly #malformedDisposer?: () => void;
377
409
  readonly #capabilitiesDisposer?: () => void;
@@ -380,10 +412,11 @@ export class SessionSdkSessionRuntime {
380
412
 
381
413
  constructor(options: SessionSdkRuntimeOptions) {
382
414
  this.transport = options.transport;
383
- const capabilities = new Map<string, ReadonlySet<string>>();
415
+ this.#connectionCapabilitiesProvider =
416
+ options.connectionCapabilities ?? (connectionId => this.#connectionCapabilities.get(connectionId));
384
417
  this.host = new SessionSdkHost({
385
418
  ...options,
386
- connectionCapabilities: options.connectionCapabilities ?? (connectionId => capabilities.get(connectionId)),
419
+ connectionCapabilities: this.#connectionCapabilitiesProvider,
387
420
  sessionId: options.transport.sessionId,
388
421
  stateRoot: options.transport.stateRoot,
389
422
  token: options.transport.token,
@@ -392,14 +425,20 @@ export class SessionSdkSessionRuntime {
392
425
  if (result instanceof Promise) return result.then(outcome => outcome ?? "written");
393
426
  return result ?? "written";
394
427
  },
395
- onFrame: options.transport.onFrame,
428
+ onFrame: handler =>
429
+ options.transport.onFrame((connectionId, frame) => {
430
+ this.#connectionIds.add(connectionId);
431
+ handler(connectionId, frame);
432
+ }),
396
433
  });
397
434
  this.#connectionDisposer = options.transport.onConnectionClose?.(connectionId => {
398
- capabilities.delete(connectionId);
435
+ this.#connectionIds.delete(connectionId);
436
+ this.#connectionCapabilities.delete(connectionId);
399
437
  this.host.handleDisconnect(connectionId);
400
438
  });
401
439
  this.#capabilitiesDisposer = options.transport.onNegotiatedCapabilities?.((connectionId, negotiated) => {
402
- capabilities.set(connectionId, new Set(negotiated));
440
+ this.#connectionIds.add(connectionId);
441
+ this.#connectionCapabilities.set(connectionId, new Set(negotiated));
403
442
  });
404
443
  this.#malformedDisposer = options.transport.onMalformedFrame?.((connectionId, message) => {
405
444
  this.host.handleMalformedFrame(connectionId, message);
@@ -418,6 +457,11 @@ export class SessionSdkSessionRuntime {
418
457
  return this.host.getProviderDefinitions(capability);
419
458
  }
420
459
 
460
+ /** Persist the host's current observable activity for broker/session-list consumers. */
461
+ async reportActivity(state: "active" | "idle", at = Date.now()): Promise<void> {
462
+ await this.host.reportActivity(state, at);
463
+ }
464
+
421
465
  emitEvent(frame: SdkFrame): void {
422
466
  const eventInput =
423
467
  typeof frame.kind === "string"
@@ -447,6 +491,28 @@ export class SessionSdkSessionRuntime {
447
491
  }
448
492
  }
449
493
 
494
+ /** Snapshot connections that negotiated every capability in the requirement set. */
495
+ connectionIdsWithCapabilities(required: readonly string[]): string[] {
496
+ return [...this.#connectionIds].flatMap(connectionId => {
497
+ const capabilities = this.#connectionCapabilitiesProvider(connectionId);
498
+ return capabilities !== undefined && required.every(capability => capabilities.has(capability))
499
+ ? [connectionId]
500
+ : [];
501
+ });
502
+ }
503
+
504
+ /** Deliver a non-replayable frame to connections with an explicit capability intersection. */
505
+ sendFrameToCapabilities(
506
+ required: readonly string[],
507
+ frame: SdkFrame,
508
+ excluding: ReadonlySet<string> = new Set(),
509
+ ): void {
510
+ for (const connectionId of this.connectionIdsWithCapabilities(required)) {
511
+ if (excluding.has(connectionId)) continue;
512
+ this.sendFrameTo(connectionId, frame);
513
+ }
514
+ }
515
+
450
516
  publish(frame: SdkFrame): void {
451
517
  this.emitEvent(frame);
452
518
  }
@@ -500,7 +566,7 @@ export class SessionSdkSessionRuntime {
500
566
  }
501
567
  }
502
568
 
503
- async stop(options: { allowLockContention?: boolean } = {}): Promise<void> {
569
+ async stop(options: { allowLockContention?: boolean; unregisterReason?: "detached_idle" } = {}): Promise<void> {
504
570
  this.#connectionDisposer?.();
505
571
  this.#capabilitiesDisposer?.();
506
572
  this.#malformedDisposer?.();
@@ -558,6 +624,11 @@ export interface CreateSdkSessionRuntimeOptions {
558
624
  terminalAbortSeams?: SdkOnlyTerminalAbortSeams;
559
625
  /** Callback when a frame is admitted to the runtime (test harness). */
560
626
  onFrameAdmitted?: () => void;
627
+ /** Timer seams for deterministic broker-recovery lifecycle tests. */
628
+ setIntervalImpl?: typeof setInterval;
629
+ clearIntervalImpl?: typeof clearInterval;
630
+ /** Test-only broker ensure seam; production always uses the owner-fenced helper. */
631
+ ensureBrokerImpl?: typeof ensureBroker;
561
632
  /** Test-only observation of a genuinely timed-out lifecycle drain. */
562
633
  onLifecycleDrainTimeoutForTests?: () => void;
563
634
  /** Test-only observation of bounded failure-diagnostic deduplication state. */
@@ -1947,6 +2018,7 @@ function createQuerySurface(
1947
2018
  ...gate,
1948
2019
  id: `pending:${gate.gate_id}`,
1949
2020
  tag: "pending" as const,
2021
+ answer_recorded: false as const,
1950
2022
  })) ??
1951
2023
  []
1952
2024
  );
@@ -4167,6 +4239,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
4167
4239
  sdkRunToken: string;
4168
4240
  }>;
4169
4241
  registerBroker: () => Promise<void>;
4242
+ stopBrokerRecovery: () => void;
4170
4243
  quiesceInput: () => void;
4171
4244
  fenceGateResolutions: () => void;
4172
4245
  waitForGateResolutionQuiescence: () => Promise<void>;
@@ -4474,8 +4547,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
4474
4547
  ): void => {
4475
4548
  try {
4476
4549
  const payload = toAgentWireEventPayload(event);
4550
+ const delivered = new Set<string>();
4477
4551
  for (const invocation of invocations) {
4478
4552
  if (invocation.connectionId === undefined) continue;
4553
+ delivered.add(invocation.connectionId);
4479
4554
  current.runtime.sendFrameTo(invocation.connectionId, {
4480
4555
  type: "event",
4481
4556
  kind: event.type,
@@ -4483,6 +4558,14 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
4483
4558
  ...invocation.correlation,
4484
4559
  });
4485
4560
  }
4561
+ // A relay or dashboard can observe a turn submitted by another client. It
4562
+ // explicitly negotiates both the stream and observer capabilities, so give it
4563
+ // the same live content frames without inventing a submitter correlation.
4564
+ current.runtime.sendFrameToCapabilities(
4565
+ [TURN_STREAM_CAPABILITY, SESSION_HOST_OBSERVER_CAPABILITY],
4566
+ { type: "event", kind: event.type, payload },
4567
+ delivered,
4568
+ );
4486
4569
  } catch {
4487
4570
  // Streamed content is best-effort; the turn producing it is authoritative.
4488
4571
  }
@@ -5031,7 +5114,16 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5031
5114
  };
5032
5115
  api.on("agent_start", (event, ctx) => {
5033
5116
  const owner = lifecycleStateForEvent(ctx, "agent_start", event.sdkRunToken);
5034
- return trackLifecycle(
5117
+ // The activity checkpoint is already fire-and-forget and does not depend on
5118
+ // the handler awaiting trackLifecycle, so it composes with #5683 unchanged.
5119
+ void (owner?.runtime ?? lifecycleStateForContext(ctx, "agent_start")?.runtime)
5120
+ ?.reportActivity("active")
5121
+ .catch(error => logger.warn(`sdk: active activity checkpoint failed: ${String(error)}`));
5122
+ // Interactive/skill turns must not wait on durable start persist. Keep
5123
+ // trackLifecycle so drain, persist-then-publish, content-hold release, and
5124
+ // shutdown still run; do not return that promise to the extension runner
5125
+ // (EXTENSION_HANDLER_TIMEOUT_MS would otherwise stall the prompt).
5126
+ void trackLifecycle(
5035
5127
  async () =>
5036
5128
  emitLifecycle(
5037
5129
  "agent_start",
@@ -5047,12 +5139,19 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5047
5139
  event.sdkRunTokens,
5048
5140
  ),
5049
5141
  owner,
5050
- );
5142
+ ).catch(error => {
5143
+ logger.error("SDK agent_start lifecycle task failed", {
5144
+ error: sanitizePromptFailure(error),
5145
+ });
5146
+ });
5051
5147
  });
5052
5148
  api.on("agent_end", (event, ctx) => {
5053
5149
  const tokenBinding =
5054
5150
  typeof event.sdkRunToken === "string" ? lifecycleRunOwners.get(event.sdkRunToken) : undefined;
5055
5151
  const owner = tokenBinding?.state ?? lifecycleStateForEvent(ctx, "agent_end", event.sdkRunToken);
5152
+ void (owner?.runtime ?? lifecycleStateForContext(ctx, "agent_end")?.runtime)
5153
+ ?.reportActivity("idle")
5154
+ .catch(error => logger.warn(`sdk: idle activity checkpoint failed: ${String(error)}`));
5056
5155
  // Capture the oldest unmatched batch synchronously. A successor may start
5057
5156
  // while the failed diagnostic persists; that must not retarget the
5058
5157
  // predecessor's reason or terminal boundary to the successor invocation.
@@ -5218,10 +5317,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5218
5317
  * the session and see the turn settle but never receive a single word of the
5219
5318
  * answer.
5220
5319
  *
5221
- * Scoped to the owning invocations of the batch that is actually running: a
5222
- * turn nobody submitted over the SDK (an ordinary terminal prompt, an
5223
- * autonomous continuation, cron, monitor) has no owner connection and streams
5224
- * nothing at all, so a session with no attached client pays one map lookup.
5320
+ * Correlated content is scoped to the owning invocations of the batch that is
5321
+ * actually running. Explicit observer connections additionally receive the
5322
+ * uncorrelated copy, including for agent-owned runs with no SDK submitter.
5323
+ * A session with no owner or eligible observer pays one capability-map lookup.
5225
5324
  */
5226
5325
  const STREAMED_TURN_EVENT_TYPES: ReadonlySet<string> = new Set([
5227
5326
  "message_update",
@@ -5234,6 +5333,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5234
5333
  const current = lifecycleStateForContext(ctx, "agent_start");
5235
5334
  const activeInvocation = current?.activeInvocation;
5236
5335
  if (!current) return;
5336
+ const observerConnections = current.runtime.connectionIdsWithCapabilities([
5337
+ TURN_STREAM_CAPABILITY,
5338
+ SESSION_HOST_OBSERVER_CAPABILITY,
5339
+ ]);
5237
5340
  const batch = activeInvocation
5238
5341
  ? current.openLifecycleBatches.find(candidate =>
5239
5342
  candidate.invocations.some(
@@ -5248,7 +5351,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5248
5351
  : current.lifecycleActive
5249
5352
  ? (current.attachedInvocations ?? [])
5250
5353
  : [];
5251
- if (invocations.length === 0) return;
5354
+ if (invocations.length === 0 && observerConnections.length === 0) return;
5252
5355
  // Content bypasses the lifecycle replay ring and must never interrupt its producer.
5253
5356
  if (!event || typeof event.type !== "string" || !STREAMED_TURN_EVENT_TYPES.has(event.type)) return;
5254
5357
  // emitLifecycle("agent_start") awaits durable persistence before it
@@ -5291,6 +5394,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5291
5394
  typeof (error as { code?: unknown }).code === "string"
5292
5395
  ? (error as { code: string }).code
5293
5396
  : undefined;
5397
+ const brokerDiagnosticCode = (error: unknown): string => {
5398
+ const code = errorCode(error);
5399
+ return code !== undefined && /^[A-Za-z0-9_.-]{1,64}$/u.test(code) ? code : "unavailable";
5400
+ };
5294
5401
  const startRuntime = async (ctx: ExtensionContext): Promise<void> => {
5295
5402
  if (active) return;
5296
5403
  const sessionId = ctx.sessionManager.getSessionId();
@@ -5315,13 +5422,23 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5315
5422
  await steerReconciliation.hydrateFromStore();
5316
5423
  const deadlineManager = new PromptDeadlineManager({
5317
5424
  reconciliation,
5318
- getLeaseMs: () => {
5319
- const v = options.settings?.get("sdk.promptDeadlineMs" as never) as number | undefined;
5320
- return typeof v === "number" && Number.isFinite(v) ? v : 1_800_000;
5321
- },
5322
- getMaxMs: () => {
5323
- const v = options.settings?.get("sdk.promptMaxRuntimeMs" as never) as number | undefined;
5324
- return typeof v === "number" && Number.isFinite(v) ? v : 21_600_000;
5425
+ getLeaseMs: () => resolveSdkPromptDeadlineMs(options.settings?.get("sdk.promptDeadlineMs" as never)),
5426
+ getMaxMs: () => resolveSdkPromptMaxRuntimeMs(options.settings?.get("sdk.promptMaxRuntimeMs" as never)),
5427
+ // Persist the agent's uncommitted work before the retirement below tears
5428
+ // the session down (#5583). Best effort by contract: failures are logged
5429
+ // inside the flush and the deadline outcome is unaffected.
5430
+ onDeadlineExceeded: async (_correlation, signal, isCurrent) => {
5431
+ if (options.settings?.get("sdk.flushWorktreeOnDeadline" as never) === false) return;
5432
+ // `has` is true only for a value the user actually wrote, so this
5433
+ // separates an explicit opt-in from the schema default. The flush only
5434
+ // honours the default inside a linked worktree the session owns.
5435
+ const configured = options.settings?.get("sdk.flushWorktreeOnDeadline" as never);
5436
+ const hasExplicitSetting =
5437
+ typeof options.settings?.has === "function"
5438
+ ? options.settings.has("sdk.flushWorktreeOnDeadline" as never)
5439
+ : configured === true;
5440
+ const explicitOptIn = hasExplicitSetting === true && configured === true;
5441
+ await flushWorktreeOnPromptDeadline(ctx.cwd, { explicitOptIn, isCurrent, signal });
5325
5442
  },
5326
5443
  onExpired: (correlation, deadlineOutcome) => {
5327
5444
  const owner = lifecycleOwnerHolder.state;
@@ -6025,83 +6142,168 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
6025
6142
  };
6026
6143
  let publishedEndpointUrl: string | undefined;
6027
6144
  let brokerRegistered = false;
6145
+ let brokerRegistrationInFlight: Promise<void> | undefined;
6146
+ let brokerRecoveryStopped = false;
6028
6147
  const registerBroker = async (): Promise<void> => {
6029
6148
  if (brokerRegistered) return;
6149
+ if (brokerRegistrationInFlight !== undefined) {
6150
+ await brokerRegistrationInFlight;
6151
+ return;
6152
+ }
6153
+ const registration = (async (): Promise<void> => {
6154
+ try {
6155
+ if (options.brokerRegistrationRequired && !options.lifecycleRequestId)
6156
+ throw new Error("Lifecycle broker registration requires a request identity.");
6157
+ await (options.ensureBrokerImpl ?? ensureBroker)({ agentDir: options.agentDir });
6158
+ if (brokerRecoveryStopped) return;
6159
+ const index = await new SessionIndex(options.agentDir).open();
6160
+ if (brokerRecoveryStopped) return;
6161
+ const locator = await resolveSessionLocator(ctx.cwd, stateRoot);
6162
+ const effectiveIncarnation = processIncarnation(process.pid);
6163
+ const direct = await reattestMasterSessionIdentity({
6164
+ index,
6165
+ locator,
6166
+ masterCapability: options.masterCapability,
6167
+ attestationEpoch: options.masterAttestationEpoch,
6168
+ ownerSessionId: options.masterOwnerSessionId,
6169
+ sessionId,
6170
+ pid: process.pid,
6171
+ processIncarnation: effectiveIncarnation,
6172
+ });
6173
+ if (brokerRecoveryStopped) return;
6174
+ let brokerRegistrationEvent: SessionIndexEvent | undefined;
6175
+ await runtime.registerWithBroker({
6176
+ register: async input => {
6177
+ if (publishedEndpointUrl === undefined)
6178
+ throw new Error("SDK transport endpoint was not published before broker registration.");
6179
+ const endpointPath = path.join(input.stateRoot, "sdk", `${input.sessionId}.json`);
6180
+ const file = await readEndpointFile(endpointPath);
6181
+ if (!file) throw new Error("SDK endpoint could not be read as a stable regular file.");
6182
+ const endpoint = JSON.parse(file.source) as Record<string, unknown>;
6183
+ if (
6184
+ endpoint.sessionId !== input.sessionId ||
6185
+ endpoint.pid !== process.pid ||
6186
+ endpoint.url !== publishedEndpointUrl ||
6187
+ endpoint.token !== transport.token
6188
+ )
6189
+ throw new Error("SDK endpoint did not match the published transport authority.");
6190
+ const endpointMtimeMs = file.mtimeMs;
6191
+ const endpointFileId = `${file.dev}:${file.ino}`;
6192
+ const masterRole = masterAttestationForEffectiveHost({
6193
+ masterCapability: options.masterCapability,
6194
+ attestationEpoch: options.masterAttestationEpoch,
6195
+ ownerSessionId: options.masterOwnerSessionId,
6196
+ sessionId: input.sessionId,
6197
+ pid: process.pid,
6198
+ processIncarnation: effectiveIncarnation,
6199
+ direct,
6200
+ });
6201
+ const published = await index.append({
6202
+ type: "host_registered",
6203
+ ...input,
6204
+ locator,
6205
+ pid: process.pid,
6206
+ endpointMtimeMs,
6207
+ endpointFileId,
6208
+ ...(options.lifecycleRequestId ? { lifecycleRequestId: options.lifecycleRequestId } : {}),
6209
+ ...(masterRole ? { masterRole } : {}),
6210
+ });
6211
+ brokerRegistrationEvent = published;
6212
+ if (brokerRecoveryStopped) {
6213
+ // Teardown may have run before append returned its ownership proof.
6214
+ await index.unregisterIfCurrent(published);
6215
+ if (brokerRegistrationEvent === published) brokerRegistrationEvent = undefined;
6216
+ }
6217
+ },
6218
+ heartbeat: async input => {
6219
+ // Heartbeat only ever speaks for the publication this host proved it
6220
+ // owns. A stale or foreign generation must not renew liveness.
6221
+ const expected = brokerRegistrationEvent;
6222
+ if (
6223
+ !expected ||
6224
+ expected.sessionId !== input.sessionId ||
6225
+ expected.endpointGeneration !== input.endpointGeneration ||
6226
+ path.resolve(expected.locator.stateRoot) !== path.resolve(input.stateRoot)
6227
+ )
6228
+ return;
6229
+ await index.append({
6230
+ type: "host_heartbeat",
6231
+ sessionId: expected.sessionId,
6232
+ locator: expected.locator,
6233
+ endpointGeneration: expected.endpointGeneration,
6234
+ pid: expected.pid,
6235
+ ...(expected.processIncarnation === undefined
6236
+ ? {}
6237
+ : { processIncarnation: expected.processIncarnation }),
6238
+ ...(expected.hostIncarnation === undefined
6239
+ ? {}
6240
+ : { hostIncarnation: expected.hostIncarnation }),
6241
+ ...(expected.masterRole === undefined ? {} : { masterRole: expected.masterRole }),
6242
+ activity: input.activity,
6243
+ ts: input.activity.at,
6244
+ });
6245
+ },
6246
+ unregister: async input => {
6247
+ const expected = brokerRegistrationEvent;
6248
+ if (
6249
+ !expected ||
6250
+ expected.sessionId !== input.sessionId ||
6251
+ expected.endpointGeneration !== input.endpointGeneration ||
6252
+ path.resolve(expected.locator.stateRoot) !== path.resolve(input.stateRoot)
6253
+ )
6254
+ return;
6255
+ // Use the successful publication's proof, never a teardown-time file read.
6256
+ await index.unregisterIfCurrent(expected, input.reason);
6257
+ if (brokerRegistrationEvent === expected) brokerRegistrationEvent = undefined;
6258
+ },
6259
+ });
6260
+ if (brokerRecoveryStopped) return;
6261
+ brokerRegistered = true;
6262
+ } catch (error) {
6263
+ if (options.brokerRegistrationRequired) throw error;
6264
+ logger.warn("sdk broker registration unavailable", { code: brokerDiagnosticCode(error) });
6265
+ }
6266
+ })();
6267
+ brokerRegistrationInFlight = registration;
6030
6268
  try {
6031
- if (options.brokerRegistrationRequired && !options.lifecycleRequestId)
6032
- throw new Error("Lifecycle broker registration requires a request identity.");
6033
- await ensureBroker({ agentDir: options.agentDir });
6034
- const index = await new SessionIndex(options.agentDir).open();
6035
- const locator = await resolveSessionLocator(ctx.cwd, stateRoot);
6036
- const effectiveIncarnation = processIncarnation(process.pid);
6037
- const direct = await reattestMasterSessionIdentity({
6038
- index,
6039
- locator,
6040
- masterCapability: options.masterCapability,
6041
- attestationEpoch: options.masterAttestationEpoch,
6042
- ownerSessionId: options.masterOwnerSessionId,
6043
- sessionId,
6044
- pid: process.pid,
6045
- processIncarnation: effectiveIncarnation,
6046
- });
6047
- let registration: SessionIndexEvent | undefined;
6048
- await runtime.registerWithBroker({
6049
- register: async input => {
6050
- if (publishedEndpointUrl === undefined)
6051
- throw new Error("SDK transport endpoint was not published before broker registration.");
6052
- const endpointPath = path.join(input.stateRoot, "sdk", `${input.sessionId}.json`);
6053
- const file = await readEndpointFile(endpointPath);
6054
- if (!file) throw new Error("SDK endpoint could not be read as a stable regular file.");
6055
- const endpoint = JSON.parse(file.source) as Record<string, unknown>;
6056
- if (
6057
- endpoint.sessionId !== input.sessionId ||
6058
- endpoint.pid !== process.pid ||
6059
- endpoint.url !== publishedEndpointUrl ||
6060
- endpoint.token !== transport.token
6061
- )
6062
- throw new Error("SDK endpoint did not match the published transport authority.");
6063
- const endpointMtimeMs = file.mtimeMs;
6064
- const endpointFileId = `${file.dev}:${file.ino}`;
6065
- const masterRole = masterAttestationForEffectiveHost({
6066
- masterCapability: options.masterCapability,
6067
- attestationEpoch: options.masterAttestationEpoch,
6068
- ownerSessionId: options.masterOwnerSessionId,
6069
- sessionId: input.sessionId,
6070
- pid: process.pid,
6071
- processIncarnation: effectiveIncarnation,
6072
- direct,
6073
- });
6074
- registration = await index.append({
6075
- type: "host_registered",
6076
- ...input,
6077
- locator,
6078
- pid: process.pid,
6079
- endpointMtimeMs,
6080
- endpointFileId,
6081
- ...(options.lifecycleRequestId ? { lifecycleRequestId: options.lifecycleRequestId } : {}),
6082
- ...(masterRole ? { masterRole } : {}),
6083
- });
6084
- },
6085
- unregister: async input => {
6086
- const expected = registration;
6087
- if (
6088
- !expected ||
6089
- expected.sessionId !== input.sessionId ||
6090
- expected.endpointGeneration !== input.endpointGeneration ||
6091
- path.resolve(expected.locator.stateRoot) !== path.resolve(input.stateRoot)
6092
- )
6093
- return;
6094
- // Use the successful publication's proof, never a teardown-time file read.
6095
- await index.unregisterIfCurrent(expected);
6096
- if (registration === expected) registration = undefined;
6097
- },
6098
- });
6099
- brokerRegistered = true;
6269
+ await registration;
6270
+ } finally {
6271
+ if (brokerRegistrationInFlight === registration) brokerRegistrationInFlight = undefined;
6272
+ }
6273
+ };
6274
+ let brokerRecoveryTimer: NodeJS.Timeout | undefined;
6275
+ let brokerRecoveryInFlight: Promise<void> | undefined;
6276
+ const runBrokerRecovery = async (): Promise<void> => {
6277
+ if (brokerRecoveryStopped || brokerRecoveryInFlight !== undefined) return;
6278
+ const recovery = (async (): Promise<void> => {
6279
+ if (brokerRecoveryStopped) return;
6280
+ if (brokerRegistered) await (options.ensureBrokerImpl ?? ensureBroker)({ agentDir: options.agentDir });
6281
+ else await registerBroker();
6282
+ })();
6283
+ brokerRecoveryInFlight = recovery;
6284
+ try {
6285
+ await recovery;
6100
6286
  } catch (error) {
6101
- if (options.brokerRegistrationRequired) throw error;
6102
- logger.warn(`sdk broker registration unavailable: ${String(error)}`);
6287
+ logger.warn("sdk broker recovery unavailable", { code: brokerDiagnosticCode(error) });
6288
+ } finally {
6289
+ if (brokerRecoveryInFlight === recovery) brokerRecoveryInFlight = undefined;
6103
6290
  }
6104
6291
  };
6292
+ const startBrokerRecovery = (): void => {
6293
+ if (brokerRecoveryStopped || brokerRecoveryTimer !== undefined) return;
6294
+ const timer = (options.setIntervalImpl ?? setInterval)(
6295
+ () => void runBrokerRecovery(),
6296
+ SESSION_BROKER_RECOVERY_INTERVAL_MS,
6297
+ ) as NodeJS.Timeout;
6298
+ brokerRecoveryTimer = timer;
6299
+ timer.unref?.();
6300
+ };
6301
+ const stopBrokerRecovery = (): void => {
6302
+ brokerRecoveryStopped = true;
6303
+ if (brokerRecoveryTimer === undefined) return;
6304
+ (options.clearIntervalImpl ?? clearInterval)(brokerRecoveryTimer);
6305
+ brokerRecoveryTimer = undefined;
6306
+ };
6105
6307
  const runtimeOwner: RuntimeState = {
6106
6308
  sessionId,
6107
6309
  sessionIdentity,
@@ -6114,6 +6316,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
6114
6316
  pending,
6115
6317
  openLifecycleBatches,
6116
6318
  registerBroker,
6319
+ stopBrokerRecovery,
6117
6320
  quiesceInput: () => {
6118
6321
  inputGate.quiescing = true;
6119
6322
  lifecycleOwnerHolder.quiescing = true;
@@ -6129,13 +6332,19 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
6129
6332
  failureDiagnosticCodes: new Map(),
6130
6333
  lifecycleTasks: new Set(),
6131
6334
  };
6335
+ // Capture the SDK turn that owns a headless workflow gate before it is
6336
+ // persisted. Q12 can then explain both sides of a stalled turn: the gate
6337
+ // identity and whether an answer was recorded for that originating turn.
6338
+ ctx.workflowGate?.setRuntimeTurnProvider?.(() => active?.activeInvocation?.correlation.turnId);
6132
6339
  lifecycleOwnerHolder.state = runtimeOwner;
6133
6340
  active = runtimeOwner;
6134
6341
  try {
6135
6342
  publishedEndpointUrl = (await runtime.start()).url;
6136
6343
  await registerBroker();
6344
+ if (!brokerRecoveryStopped) startBrokerRecovery();
6137
6345
  } catch (error) {
6138
6346
  active = undefined;
6347
+ stopBrokerRecovery();
6139
6348
  disposeGate?.();
6140
6349
  try {
6141
6350
  await runtime.stop();
@@ -6156,6 +6365,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
6156
6365
  pending,
6157
6366
  openLifecycleBatches,
6158
6367
  registerBroker,
6368
+ stopBrokerRecovery,
6159
6369
  quiesceInput: () => {
6160
6370
  inputGate.quiescing = true;
6161
6371
  lifecycleOwnerHolder.quiescing = true;
@@ -6180,7 +6390,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
6180
6390
  throw error;
6181
6391
  }
6182
6392
  };
6183
- const stopActive = async (cancelSkillRecovery = false): Promise<void> => {
6393
+ const stopActive = async (cancelSkillRecovery = false, unregisterReason?: "detached_idle"): Promise<void> => {
6184
6394
  const current = active;
6185
6395
  if (!current) return;
6186
6396
  if (cancelSkillRecovery) {
@@ -6189,6 +6399,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
6189
6399
  }
6190
6400
  activePromptOwnerHolder.connectionIds = undefined;
6191
6401
  activePromptOwnerHolder.lifecycleEpoch = undefined;
6402
+ current.stopBrokerRecovery();
6192
6403
  current.quiesceInput();
6193
6404
  current.fenceGateResolutions();
6194
6405
  try {
@@ -6237,7 +6448,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
6237
6448
  retiredLifecycleOwnerTimers.set(current, timer);
6238
6449
  } else current.deadlineManager.clearAll();
6239
6450
  current.disposeGate?.();
6240
- await current.runtime.stop({ allowLockContention: cancelSkillRecovery });
6451
+ await current.runtime.stop({
6452
+ allowLockContention: cancelSkillRecovery,
6453
+ ...(unregisterReason === undefined ? {} : { unregisterReason }),
6454
+ });
6241
6455
  } catch (error) {
6242
6456
  // Keep the immutable owner available for a retry when transport teardown
6243
6457
  // fails after quiescing. Clearing `active` before stop prevents a second
@@ -6260,7 +6474,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
6260
6474
  await stopActive();
6261
6475
  await startRuntime(ctx);
6262
6476
  });
6263
- api.on("session_shutdown", async () => {
6264
- await stopActive(true);
6477
+ api.on("session_shutdown", async event => {
6478
+ await stopActive(true, event.reason);
6265
6479
  });
6266
6480
  }
@@ -25,5 +25,16 @@ export interface HostEndpointAdapters {
25
25
 
26
26
  export interface BrokerIndexWriter {
27
27
  register(input: { sessionId: string; stateRoot: string; endpointGeneration: number }): void | Promise<void>;
28
- unregister?(input: { sessionId: string; stateRoot: string; endpointGeneration: number }): void | Promise<void>;
28
+ heartbeat?(input: {
29
+ sessionId: string;
30
+ stateRoot: string;
31
+ endpointGeneration: number;
32
+ activity: { state: "active" | "idle"; at: number };
33
+ }): void | Promise<void>;
34
+ unregister?(input: {
35
+ sessionId: string;
36
+ stateRoot: string;
37
+ endpointGeneration: number;
38
+ reason?: "detached_idle";
39
+ }): void | Promise<void>;
29
40
  }