@gajae-code/coding-agent 0.17.1 → 0.17.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (375) hide show
  1. package/CHANGELOG.md +266 -0
  2. package/dist/types/async/job-manager.d.ts +7 -6
  3. package/dist/types/cli/daemon-cli.d.ts +3 -1
  4. package/dist/types/cli/github-release.d.ts +17 -0
  5. package/dist/types/cli/public-command-entry.d.ts +56 -0
  6. package/dist/types/cli/public-command-errors.d.ts +126 -0
  7. package/dist/types/cli/public-command-evidence.d.ts +99 -0
  8. package/dist/types/cli/public-command-help.d.ts +65 -0
  9. package/dist/types/cli/public-command-registry.d.ts +69 -0
  10. package/dist/types/cli/session-picker.d.ts +1 -1
  11. package/dist/types/cli/setup-cli.d.ts +1 -0
  12. package/dist/types/cli/skills-cli.d.ts +5 -0
  13. package/dist/types/cli.d.ts +3 -0
  14. package/dist/types/commands/daemon.d.ts +4 -50
  15. package/dist/types/commands/sdk.d.ts +20 -37
  16. package/dist/types/commands/setup.d.ts +3 -0
  17. package/dist/types/commands/skills.d.ts +9 -0
  18. package/dist/types/config/autorouting-tier-map.d.ts +24 -0
  19. package/dist/types/config/file-lock.d.ts +10 -1
  20. package/dist/types/config/model-profile-activation.d.ts +63 -4
  21. package/dist/types/config/model-registry.d.ts +20 -2
  22. package/dist/types/config/settings-schema.d.ts +11 -0
  23. package/dist/types/coordinator-mcp/question-state.d.ts +6 -1
  24. package/dist/types/coordinator-mcp/server.d.ts +3 -0
  25. package/dist/types/daemon/operator-contract.d.ts +5 -1
  26. package/dist/types/eval/js/worker-protocol.d.ts +2 -0
  27. package/dist/types/extensibility/extensions/types.d.ts +12 -0
  28. package/dist/types/extensibility/gjc-plugins/registry.d.ts +4 -0
  29. package/dist/types/extensibility/runtime-skill-discovery.d.ts +18 -0
  30. package/dist/types/extensibility/shared-events.d.ts +3 -1
  31. package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
  32. package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
  33. package/dist/types/internal-urls/index.d.ts +1 -0
  34. package/dist/types/internal-urls/parse.d.ts +9 -1
  35. package/dist/types/main.d.ts +22 -0
  36. package/dist/types/modes/acp/acp-agent.d.ts +94 -1
  37. package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
  38. package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
  39. package/dist/types/modes/components/session-selector.d.ts +5 -2
  40. package/dist/types/modes/components/thinking-selector.d.ts +2 -1
  41. package/dist/types/modes/components/user-message-selector.d.ts +5 -2
  42. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  43. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  44. package/dist/types/modes/interactive-mode.d.ts +1 -1
  45. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
  46. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
  47. package/dist/types/modes/types.d.ts +2 -2
  48. package/dist/types/runtime/process-lifecycle.d.ts +2 -0
  49. package/dist/types/runtime-mcp/manager.d.ts +56 -0
  50. package/dist/types/runtime-mcp/pool.d.ts +17 -0
  51. package/dist/types/runtime-mcp/redaction.d.ts +20 -0
  52. package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
  53. package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
  54. package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
  55. package/dist/types/sdk/acp/adapter.d.ts +27 -0
  56. package/dist/types/sdk/acp/final-text.d.ts +8 -0
  57. package/dist/types/sdk/broker/broker.d.ts +13 -2
  58. package/dist/types/sdk/broker/ensure.d.ts +46 -1
  59. package/dist/types/sdk/broker/identity.d.ts +3 -1
  60. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
  61. package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
  62. package/dist/types/sdk/broker/runtime.d.ts +1 -1
  63. package/dist/types/sdk/broker/session-index.d.ts +22 -3
  64. package/dist/types/sdk/broker/spawn-authority.d.ts +4 -0
  65. package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
  66. package/dist/types/sdk/bus/index.d.ts +19 -0
  67. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
  68. package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
  69. package/dist/types/sdk/cli/master-cli.d.ts +6 -10
  70. package/dist/types/sdk/cli/rows.d.ts +19 -1
  71. package/dist/types/sdk/cli/session-cli.d.ts +22 -5
  72. package/dist/types/sdk/client/client.d.ts +2 -0
  73. package/dist/types/sdk/guides/cli.d.ts +3 -4
  74. package/dist/types/sdk/guides/verify.d.ts +2 -0
  75. package/dist/types/sdk/host/host.d.ts +9 -0
  76. package/dist/types/sdk/host/session-runtime.d.ts +31 -0
  77. package/dist/types/sdk/host/types.d.ts +10 -0
  78. package/dist/types/sdk/index.d.ts +1 -0
  79. package/dist/types/sdk/prompt-deadline-flush.d.ts +83 -0
  80. package/dist/types/sdk/prompt-deadline-manager.d.ts +38 -1
  81. package/dist/types/sdk/prompt-failure.d.ts +30 -1
  82. package/dist/types/sdk/prompt-status.d.ts +8 -0
  83. package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
  84. package/dist/types/sdk/router/replay-retention-gap-cache.d.ts +13 -0
  85. package/dist/types/sdk/router/session-router.d.ts +2 -0
  86. package/dist/types/sdk/startup-capability.d.ts +2 -0
  87. package/dist/types/sdk/transport/relay.d.ts +1 -0
  88. package/dist/types/sdk/transport/serve-cli.d.ts +73 -2
  89. package/dist/types/sdk/transport/stdio.d.ts +5 -1
  90. package/dist/types/session/agent-session.d.ts +168 -21
  91. package/dist/types/session/fallback-chain-controller.d.ts +6 -6
  92. package/dist/types/session/fold-coordinator.d.ts +17 -3
  93. package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
  94. package/dist/types/session/session-manager.d.ts +64 -1
  95. package/dist/types/session/session-work-lease.d.ts +14 -0
  96. package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
  97. package/dist/types/setup/provider-onboarding.d.ts +81 -1
  98. package/dist/types/thinking.d.ts +3 -1
  99. package/dist/types/tools/bash.d.ts +37 -11
  100. package/dist/types/tools/job.d.ts +1 -1
  101. package/dist/types/tools/steer-fold.d.ts +31 -0
  102. package/dist/types/utils/git.d.ts +28 -2
  103. package/dist/types/utils/mupdf-embedded.d.ts +2 -0
  104. package/dist/types/utils/mupdf-wasm-embedded.d.ts +2 -0
  105. package/dist/types/utils/mupdf.d.ts +4 -0
  106. package/dist/types/utils/session-title-generation.d.ts +6 -0
  107. package/dist/types/utils/title-generator.d.ts +11 -2
  108. package/dist/types/web/scrapers/types.d.ts +3 -0
  109. package/package.json +18 -10
  110. package/scripts/build-binary.ts +9 -3
  111. package/scripts/compile-args.ts +0 -9
  112. package/scripts/embed-mupdf.ts +52 -0
  113. package/scripts/generate-sdk-operation-inventory.ts +31 -1
  114. package/scripts/vendor-markit.ts +196 -0
  115. package/src/async/job-manager.ts +60 -9
  116. package/src/cli/daemon-cli.ts +53 -18
  117. package/src/cli/github-release.ts +87 -5
  118. package/src/cli/mcp-cli.ts +38 -4
  119. package/src/cli/public-command-entry.ts +450 -0
  120. package/src/cli/public-command-errors.ts +581 -0
  121. package/src/cli/public-command-evidence.ts +717 -0
  122. package/src/cli/public-command-help.ts +360 -0
  123. package/src/cli/public-command-registry.ts +535 -0
  124. package/src/cli/session-picker.ts +2 -1
  125. package/src/cli/setup-cli.ts +9 -1
  126. package/src/cli/skills-cli.ts +46 -2
  127. package/src/cli.ts +61 -1
  128. package/src/commands/daemon.ts +65 -69
  129. package/src/commands/sdk.ts +232 -100
  130. package/src/commands/setup.ts +2 -0
  131. package/src/commands/skills.ts +11 -0
  132. package/src/config/autorouting-tier-map.ts +16 -0
  133. package/src/config/file-lock.ts +199 -68
  134. package/src/config/model-profile-activation.ts +406 -53
  135. package/src/config/model-registry.ts +204 -25
  136. package/src/config/settings-schema.ts +26 -2
  137. package/src/coordinator-mcp/policy.ts +10 -5
  138. package/src/coordinator-mcp/question-gate-codec.ts +17 -0
  139. package/src/coordinator-mcp/question-state.ts +175 -8
  140. package/src/coordinator-mcp/server.ts +528 -87
  141. package/src/coordinator-mcp/session-reaper.ts +6 -4
  142. package/src/crash/record-loader.ts +6 -2
  143. package/src/daemon/operator-contract.ts +26 -12
  144. package/src/debug/report-bundle.ts +114 -24
  145. package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
  146. package/src/eval/js/context-manager.ts +8 -8
  147. package/src/eval/js/worker-core.ts +9 -4
  148. package/src/eval/js/worker-protocol.ts +2 -0
  149. package/src/extensibility/extensions/runner.ts +52 -4
  150. package/src/extensibility/extensions/types.ts +10 -0
  151. package/src/extensibility/gjc-plugins/registry.ts +196 -37
  152. package/src/extensibility/runtime-skill-discovery.ts +61 -3
  153. package/src/extensibility/shared-events.ts +3 -1
  154. package/src/gjc-runtime/managed-owner-admission.ts +67 -50
  155. package/src/gjc-runtime/session-state-lock.ts +165 -24
  156. package/src/gjc-runtime/ultragoal-succession.ts +31 -3
  157. package/src/internal-urls/docs-index.generated.ts +21 -19
  158. package/src/internal-urls/embedded-protocol.ts +83 -0
  159. package/src/internal-urls/index.ts +1 -0
  160. package/src/internal-urls/parse.ts +21 -1
  161. package/src/internal-urls/router.ts +4 -2
  162. package/src/main.ts +123 -55
  163. package/src/modes/acp/acp-agent.ts +593 -47
  164. package/src/modes/acp/acp-event-mapper.ts +9 -0
  165. package/src/modes/acp/startup-provenance.ts +59 -0
  166. package/src/modes/components/custom-provider-wizard.ts +308 -7
  167. package/src/modes/components/model-selector.ts +31 -15
  168. package/src/modes/components/oauth-selector.ts +9 -0
  169. package/src/modes/components/session-selector.ts +47 -4
  170. package/src/modes/components/status-line/segments.ts +6 -1
  171. package/src/modes/components/thinking-selector.ts +7 -3
  172. package/src/modes/components/user-message-selector.ts +57 -15
  173. package/src/modes/controllers/command-controller.ts +58 -32
  174. package/src/modes/controllers/extension-ui-controller.ts +33 -7
  175. package/src/modes/controllers/input-controller.ts +4 -0
  176. package/src/modes/controllers/selector-controller.ts +177 -18
  177. package/src/modes/interactive-mode.ts +21 -7
  178. package/src/modes/runtime-init.ts +1 -0
  179. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
  180. package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
  181. package/src/modes/types.ts +2 -2
  182. package/src/prompts/agent-fragments/browser-aside.md +1 -0
  183. package/src/prompts/system/title-system.md +1 -1
  184. package/src/prompts/tools/bash.md +1 -1
  185. package/src/prompts/tools/job.md +1 -1
  186. package/src/prompts/tools/read.md +2 -0
  187. package/src/runtime/network-prewarm-service.test.ts +36 -1
  188. package/src/runtime/network-prewarm-service.ts +28 -3
  189. package/src/runtime/process-lifecycle.ts +22 -11
  190. package/src/runtime-mcp/config.ts +16 -1
  191. package/src/runtime-mcp/manager.ts +689 -55
  192. package/src/runtime-mcp/pool.test.ts +31 -0
  193. package/src/runtime-mcp/pool.ts +304 -28
  194. package/src/runtime-mcp/redaction.ts +42 -0
  195. package/src/runtime-mcp/startup-policy.ts +7 -0
  196. package/src/runtime-mcp/tool-bridge.ts +71 -0
  197. package/src/runtime-mcp/transports/http.ts +108 -25
  198. package/src/sdk/acp/adapter.ts +90 -9
  199. package/src/sdk/acp/final-text.ts +19 -2
  200. package/src/sdk/broker/broker.ts +378 -52
  201. package/src/sdk/broker/ensure.ts +97 -19
  202. package/src/sdk/broker/identity.ts +9 -1
  203. package/src/sdk/broker/lifecycle-ledger.ts +95 -31
  204. package/src/sdk/broker/lifecycle.ts +690 -141
  205. package/src/sdk/broker/runtime.ts +1 -1
  206. package/src/sdk/broker/session-index.ts +97 -4
  207. package/src/sdk/broker/spawn-authority.ts +35 -7
  208. package/src/sdk/broker/stderr-drainer.ts +59 -0
  209. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  210. package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
  211. package/src/sdk/bus/index.ts +721 -84
  212. package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
  213. package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
  214. package/src/sdk/bus/telegram-daemon.ts +10 -3
  215. package/src/sdk/cli/master-cli.ts +47 -36
  216. package/src/sdk/cli/rows.ts +30 -7
  217. package/src/sdk/cli/session-cli.ts +687 -184
  218. package/src/sdk/client/client.ts +18 -0
  219. package/src/sdk/guides/catalog.ts +2 -2
  220. package/src/sdk/guides/cli.ts +20 -62
  221. package/src/sdk/guides/verify.ts +14 -0
  222. package/src/sdk/host/host.ts +49 -5
  223. package/src/sdk/host/session-runtime.test.ts +636 -9
  224. package/src/sdk/host/session-runtime.ts +499 -118
  225. package/src/sdk/host/types.ts +12 -1
  226. package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
  227. package/src/sdk/host/websocket-transport.ts +40 -6
  228. package/src/sdk/index.ts +11 -0
  229. package/src/sdk/prompt-deadline-flush.ts +334 -0
  230. package/src/sdk/prompt-deadline-manager.ts +136 -4
  231. package/src/sdk/prompt-failure.ts +43 -1
  232. package/src/sdk/prompt-status.ts +8 -0
  233. package/src/sdk/prompt-tool-boundary.ts +121 -0
  234. package/src/sdk/protocol/operation-inventory.generated.json +106 -0
  235. package/src/sdk/router/replay-retention-gap-cache.ts +27 -0
  236. package/src/sdk/router/session-router.ts +31 -4
  237. package/src/sdk/session.ts +194 -16
  238. package/src/sdk/startup-capability.ts +1 -1
  239. package/src/sdk/transport/relay.ts +71 -6
  240. package/src/sdk/transport/serve-cli.ts +494 -49
  241. package/src/sdk/transport/socket.ts +12 -11
  242. package/src/sdk/transport/stdio.ts +7 -3
  243. package/src/session/agent-session.ts +2437 -400
  244. package/src/session/fallback-chain-controller.ts +6 -7
  245. package/src/session/fold-coordinator.ts +24 -5
  246. package/src/session/internal/managed-session-scope.ts +58 -18
  247. package/src/session/internal/managed-session-storage.ts +304 -11
  248. package/src/session/session-manager.ts +382 -44
  249. package/src/session/session-work-lease.ts +34 -0
  250. package/src/session-import/codex.ts +12 -3
  251. package/src/setup/model-onboarding-guidance.ts +1 -1
  252. package/src/setup/provider-onboarding.ts +397 -24
  253. package/src/slash-commands/builtin-registry.ts +258 -14
  254. package/src/task/index.ts +14 -2
  255. package/src/thinking.ts +21 -2
  256. package/src/tools/bash.ts +98 -78
  257. package/src/tools/fetch.ts +119 -20
  258. package/src/tools/job.ts +151 -25
  259. package/src/tools/output-meta.ts +15 -2
  260. package/src/tools/path-utils.ts +27 -3
  261. package/src/tools/read.ts +3 -3
  262. package/src/tools/steer-fold.ts +107 -0
  263. package/src/tools/tool-catalog.generated.ts +29 -2
  264. package/src/tools/tool-errors.ts +4 -0
  265. package/src/utils/git.ts +53 -3
  266. package/src/utils/markit.ts +55 -41
  267. package/src/utils/mupdf-assets.d.ts +4 -0
  268. package/src/utils/mupdf-embedded.ts +4 -0
  269. package/src/utils/mupdf-wasm-embedded.ts +3 -0
  270. package/src/utils/mupdf-wasm.ts +24 -2
  271. package/src/utils/mupdf.ts +129 -0
  272. package/src/utils/session-title-generation.ts +29 -0
  273. package/src/utils/title-generator.ts +46 -2
  274. package/src/web/scrapers/types.ts +24 -3
  275. package/vendor/markit-ai/LICENSE +21 -0
  276. package/vendor/markit-ai/MANIFEST.json +412 -0
  277. package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
  278. package/vendor/markit-ai/dist/commands/config.js +139 -0
  279. package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
  280. package/vendor/markit-ai/dist/commands/convert.js +113 -0
  281. package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
  282. package/vendor/markit-ai/dist/commands/formats.js +92 -0
  283. package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
  284. package/vendor/markit-ai/dist/commands/init.js +29 -0
  285. package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
  286. package/vendor/markit-ai/dist/commands/onboard.js +61 -0
  287. package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
  288. package/vendor/markit-ai/dist/commands/plugin.js +58 -0
  289. package/vendor/markit-ai/dist/config.d.ts +26 -0
  290. package/vendor/markit-ai/dist/config.js +42 -0
  291. package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
  292. package/vendor/markit-ai/dist/converters/audio.js +104 -0
  293. package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
  294. package/vendor/markit-ai/dist/converters/csv.js +83 -0
  295. package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
  296. package/vendor/markit-ai/dist/converters/docx.js +57 -0
  297. package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
  298. package/vendor/markit-ai/dist/converters/epub.js +128 -0
  299. package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
  300. package/vendor/markit-ai/dist/converters/github.js +148 -0
  301. package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
  302. package/vendor/markit-ai/dist/converters/html.js +30 -0
  303. package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
  304. package/vendor/markit-ai/dist/converters/image.js +124 -0
  305. package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
  306. package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
  307. package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
  308. package/vendor/markit-ai/dist/converters/iwork.js +391 -0
  309. package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
  310. package/vendor/markit-ai/dist/converters/json.js +21 -0
  311. package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
  312. package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
  313. package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
  314. package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
  315. package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
  316. package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
  317. package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
  318. package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
  319. package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
  320. package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
  321. package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
  322. package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
  323. package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
  324. package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
  325. package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
  326. package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
  327. package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
  328. package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
  329. package/vendor/markit-ai/dist/converters/pptx.js +289 -0
  330. package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
  331. package/vendor/markit-ai/dist/converters/rss.js +138 -0
  332. package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
  333. package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
  334. package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
  335. package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
  336. package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
  337. package/vendor/markit-ai/dist/converters/xml.js +18 -0
  338. package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
  339. package/vendor/markit-ai/dist/converters/yaml.js +17 -0
  340. package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
  341. package/vendor/markit-ai/dist/converters/zip.js +57 -0
  342. package/vendor/markit-ai/dist/index.d.ts +30 -0
  343. package/vendor/markit-ai/dist/index.js +26 -0
  344. package/vendor/markit-ai/dist/main.d.ts +2 -0
  345. package/vendor/markit-ai/dist/main.js +191 -0
  346. package/vendor/markit-ai/dist/markit.d.ts +45 -0
  347. package/vendor/markit-ai/dist/markit.js +267 -0
  348. package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
  349. package/vendor/markit-ai/dist/plugins/api.js +42 -0
  350. package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
  351. package/vendor/markit-ai/dist/plugins/index.js +3 -0
  352. package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
  353. package/vendor/markit-ai/dist/plugins/installer.js +176 -0
  354. package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
  355. package/vendor/markit-ai/dist/plugins/loader.js +61 -0
  356. package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
  357. package/vendor/markit-ai/dist/plugins/types.js +1 -0
  358. package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
  359. package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
  360. package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
  361. package/vendor/markit-ai/dist/providers/index.js +56 -0
  362. package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
  363. package/vendor/markit-ai/dist/providers/openai.js +76 -0
  364. package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
  365. package/vendor/markit-ai/dist/providers/types.js +1 -0
  366. package/vendor/markit-ai/dist/types.d.ts +36 -0
  367. package/vendor/markit-ai/dist/types.js +1 -0
  368. package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
  369. package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
  370. package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
  371. package/vendor/markit-ai/dist/utils/output.js +31 -0
  372. package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
  373. package/vendor/markit-ai/dist/utils/turndown.js +70 -0
  374. package/vendor/markit-ai/package.json +74 -0
  375. package/vendor/markit-ai.patch +49 -0
@@ -4,13 +4,14 @@ import * as crypto from "node:crypto";
4
4
  import * as fs from "node:fs/promises";
5
5
  import * as path from "node:path";
6
6
  import { promisify } from "node:util";
7
- import { isContinuingMidRunMaintenanceOutcome, ThinkingLevel } from "@gajae-code/agent-core";
7
+ import { isContinuingMidRunMaintenanceOutcome, isNonDispatchedToolEvent, ThinkingLevel } from "@gajae-code/agent-core";
8
8
  import type { Api, ImageContent, Model } from "@gajae-code/ai/core";
9
9
  import { logger } from "@gajae-code/utils";
10
10
  import { AsyncJobManager } from "../../async";
11
11
  import {
12
12
  getProxyRoutableProviders,
13
13
  inspectProxyProviderId,
14
+ isModelProfileProxyConfigured,
14
15
  requiresQualifiedModelProfileRoleResolution,
15
16
  resolveProxyMode,
16
17
  rewriteSelectorForProxy,
@@ -25,7 +26,12 @@ import {
25
26
  import { isAuthenticated, kNoAuth } from "../../config/model-registry";
26
27
  import { resolveModelChainWithAuth, splitSelectorThinkingSuffix } from "../../config/model-resolver";
27
28
  import { type ModelSelectorValue, normalizeModelSelectorValue } from "../../config/model-selector-value";
28
- import { type Settings, validateSettingPatch } from "../../config/settings";
29
+ import {
30
+ resolveSdkPromptDeadlineMs,
31
+ resolveSdkPromptMaxRuntimeMs,
32
+ type Settings,
33
+ validateSettingPatch,
34
+ } from "../../config/settings";
29
35
  import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "../../extensibility/extensions";
30
36
  import type { AgentEndEvent } from "../../extensibility/shared-events";
31
37
  import { normalizeGoal } from "../../goals/state";
@@ -58,6 +64,7 @@ import {
58
64
  syntheticNamespaceCollision,
59
65
  } from "../model-profile-model";
60
66
  import { projectQ10Models } from "../models.js";
67
+ import { flushWorktreeOnPromptDeadline } from "../prompt-deadline-flush";
61
68
  import { PromptDeadlineManager, type PromptTerminalTransitionEvidence } from "../prompt-deadline-manager";
62
69
  import {
63
70
  assistantFailureCode,
@@ -91,7 +98,12 @@ import {
91
98
  BROKER_RUNTIME_CLOSE_CAPABILITY_FIELD,
92
99
  hasBrokerRuntimeAbortCapability,
93
100
  } from "./control/runtime-gate";
94
- 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";
95
107
  import { clearAutoroutingInactive, isAutoroutingInactive, markAutoroutingInactive } from "./internal-autorouting-state";
96
108
  import { CursorRegistry, QueryHandlers, RevisionStore, type SessionSurface } from "./query";
97
109
  import { createSdkRunCapability } from "./sdk-run-capability";
@@ -195,6 +207,13 @@ export function verifyMasterCapabilityFrame(input: {
195
207
 
196
208
  /** Maximum time a replaced runtime may retain a lifecycle persistence task. */
197
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;
198
217
 
199
218
  class DiffQueryError extends Error {
200
219
  constructor(
@@ -356,6 +375,17 @@ export interface SdkOnlyTerminalAbortSeams {
356
375
  terminal?: { scope: "turn" | "owned"; expectedEpoch?: number; steeringSnapshotToken?: number };
357
376
  },
358
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[];
359
389
  /** Test override for the maximum durable terminal reservation rows. */
360
390
  maxDurableTerminalReservationsForTests?: number;
361
391
  }
@@ -371,6 +401,9 @@ export interface SdkOnlyTerminalAbortSeams {
371
401
  export class SessionSdkSessionRuntime {
372
402
  readonly host: SessionSdkHost;
373
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;
374
407
  readonly #connectionDisposer?: () => void;
375
408
  readonly #malformedDisposer?: () => void;
376
409
  readonly #capabilitiesDisposer?: () => void;
@@ -379,10 +412,11 @@ export class SessionSdkSessionRuntime {
379
412
 
380
413
  constructor(options: SessionSdkRuntimeOptions) {
381
414
  this.transport = options.transport;
382
- const capabilities = new Map<string, ReadonlySet<string>>();
415
+ this.#connectionCapabilitiesProvider =
416
+ options.connectionCapabilities ?? (connectionId => this.#connectionCapabilities.get(connectionId));
383
417
  this.host = new SessionSdkHost({
384
418
  ...options,
385
- connectionCapabilities: options.connectionCapabilities ?? (connectionId => capabilities.get(connectionId)),
419
+ connectionCapabilities: this.#connectionCapabilitiesProvider,
386
420
  sessionId: options.transport.sessionId,
387
421
  stateRoot: options.transport.stateRoot,
388
422
  token: options.transport.token,
@@ -391,14 +425,20 @@ export class SessionSdkSessionRuntime {
391
425
  if (result instanceof Promise) return result.then(outcome => outcome ?? "written");
392
426
  return result ?? "written";
393
427
  },
394
- onFrame: options.transport.onFrame,
428
+ onFrame: handler =>
429
+ options.transport.onFrame((connectionId, frame) => {
430
+ this.#connectionIds.add(connectionId);
431
+ handler(connectionId, frame);
432
+ }),
395
433
  });
396
434
  this.#connectionDisposer = options.transport.onConnectionClose?.(connectionId => {
397
- capabilities.delete(connectionId);
435
+ this.#connectionIds.delete(connectionId);
436
+ this.#connectionCapabilities.delete(connectionId);
398
437
  this.host.handleDisconnect(connectionId);
399
438
  });
400
439
  this.#capabilitiesDisposer = options.transport.onNegotiatedCapabilities?.((connectionId, negotiated) => {
401
- capabilities.set(connectionId, new Set(negotiated));
440
+ this.#connectionIds.add(connectionId);
441
+ this.#connectionCapabilities.set(connectionId, new Set(negotiated));
402
442
  });
403
443
  this.#malformedDisposer = options.transport.onMalformedFrame?.((connectionId, message) => {
404
444
  this.host.handleMalformedFrame(connectionId, message);
@@ -417,6 +457,11 @@ export class SessionSdkSessionRuntime {
417
457
  return this.host.getProviderDefinitions(capability);
418
458
  }
419
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
+
420
465
  emitEvent(frame: SdkFrame): void {
421
466
  const eventInput =
422
467
  typeof frame.kind === "string"
@@ -446,6 +491,28 @@ export class SessionSdkSessionRuntime {
446
491
  }
447
492
  }
448
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
+
449
516
  publish(frame: SdkFrame): void {
450
517
  this.emitEvent(frame);
451
518
  }
@@ -499,7 +566,7 @@ export class SessionSdkSessionRuntime {
499
566
  }
500
567
  }
501
568
 
502
- async stop(options: { allowLockContention?: boolean } = {}): Promise<void> {
569
+ async stop(options: { allowLockContention?: boolean; unregisterReason?: "detached_idle" } = {}): Promise<void> {
503
570
  this.#connectionDisposer?.();
504
571
  this.#capabilitiesDisposer?.();
505
572
  this.#malformedDisposer?.();
@@ -557,6 +624,11 @@ export interface CreateSdkSessionRuntimeOptions {
557
624
  terminalAbortSeams?: SdkOnlyTerminalAbortSeams;
558
625
  /** Callback when a frame is admitted to the runtime (test harness). */
559
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;
560
632
  /** Test-only observation of a genuinely timed-out lifecycle drain. */
561
633
  onLifecycleDrainTimeoutForTests?: () => void;
562
634
  /** Test-only observation of bounded failure-diagnostic deduplication state. */
@@ -1645,18 +1717,18 @@ function createQuerySurface(
1645
1717
  };
1646
1718
  try {
1647
1719
  const proxyMode = profile.source === "user" ? "fallback" : resolveProxyMode(profileSettings);
1648
- if (profile.source !== "user") {
1649
- const configuredProviders = ctx.modelRegistry.getConfiguredProviderIds?.() ?? [];
1650
- if (proxyProvider !== undefined && !configuredProviders.includes(proxyProvider))
1720
+ if (proxyProvider !== undefined) {
1721
+ const configuredProviders = ctx.modelRegistry.getConfiguredProviderIds?.();
1722
+ const credentialless =
1723
+ proxyProvider === "opencodex" &&
1724
+ !configuredProviders?.includes(proxyProvider) &&
1725
+ (await ctx.modelRegistry.getApiKeyForProvider(proxyProvider, getProfileCredentialSessionId())) ===
1726
+ kNoAuth;
1727
+ if (!isModelProfileProxyConfigured(proxyProvider, configuredProviders, credentialless))
1651
1728
  return { available: false };
1652
1729
  }
1653
1730
  if (profile.source !== "user" && proxyMode === "always") {
1654
- if (
1655
- proxyProvider === undefined ||
1656
- !proxyAuthenticated ||
1657
- !(ctx.modelRegistry.getConfiguredProviderIds?.() ?? []).includes(proxyProvider)
1658
- )
1659
- return { available: false };
1731
+ if (proxyProvider === undefined || !proxyAuthenticated) return { available: false };
1660
1732
  }
1661
1733
  const bindings = resolveProfileBindings(profile);
1662
1734
  const assignments: Array<{ value: ModelSelectorValue; isDefault: boolean }> = [];
@@ -1946,6 +2018,7 @@ function createQuerySurface(
1946
2018
  ...gate,
1947
2019
  id: `pending:${gate.gate_id}`,
1948
2020
  tag: "pending" as const,
2021
+ answer_recorded: false as const,
1949
2022
  })) ??
1950
2023
  []
1951
2024
  );
@@ -4166,6 +4239,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
4166
4239
  sdkRunToken: string;
4167
4240
  }>;
4168
4241
  registerBroker: () => Promise<void>;
4242
+ stopBrokerRecovery: () => void;
4169
4243
  quiesceInput: () => void;
4170
4244
  fenceGateResolutions: () => void;
4171
4245
  waitForGateResolutionQuiescence: () => Promise<void>;
@@ -4378,6 +4452,78 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
4378
4452
  const activePromptOwnerHolder: { connectionIds?: Set<string>; lifecycleEpoch?: number } = {};
4379
4453
  let nextLifecycleEpoch = 0;
4380
4454
  const skillTerminalRecoveryKeys = new Set<string>();
4455
+ /**
4456
+ * Exactly one correlated terminal BOUNDARY (agent_end) may reach the wire per
4457
+ * correlation. The provider agent_end handler and the prompt-deadline expiry can both
4458
+ * reach their emission after the other has already published: the handler captures its
4459
+ * transitions synchronously and then awaits durable writes, while the deadline callback
4460
+ * fires only after its own claim/finalize awaits have settled. Neither can be stopped by
4461
+ * removing lifecycle references, and noteTransition is a no-op once the record is
4462
+ * terminal, so durable state would record one outcome while clients received two. This
4463
+ * claim is the shared arbiter, taken synchronously -- no await between the check and the
4464
+ * set -- by whoever is about to publish the boundary.
4465
+ *
4466
+ * The key is never removed at cleanup: dropping it when the correlation retires would
4467
+ * re-open the window, and would stop a late real boundary from being idempotent for an
4468
+ * already-retired correlation. Growth is bounded by FIFO eviction instead (a Set
4469
+ * preserves insertion order). A correlation displaced by 1024 later boundaries is long
4470
+ * retired, and its durable record stays authoritative for anything a consumer missed.
4471
+ *
4472
+ * Eviction must never reach a correlation that can still be published (review P1). A
4473
+ * handler captures its transitions, awaits durable writes, and claims only immediately
4474
+ * before its emit; parked on those awaits, 1024 later boundaries would otherwise evict
4475
+ * its key and let its delayed agent_end publish a SECOND boundary for a correlation the
4476
+ * deadline already terminalized. Every would-be publisher therefore RETAINS its
4477
+ * correlations for as long as it can still reach a claim, and a retained key is skipped
4478
+ * as an eviction victim.
4479
+ */
4480
+ const MAX_PUBLISHED_TERMINAL_BOUNDARIES = 1024;
4481
+ const publishedTerminalBoundaries = new Set<string>();
4482
+ /** Correlations with at least one publisher still able to reach a claim. */
4483
+ const retainedTerminalBoundaries = new Map<string, number>();
4484
+ /**
4485
+ * Protect every correlation this publisher may still claim, and return its release.
4486
+ * Callers MUST release on every exit path -- published, claim lost, or threw -- so a
4487
+ * failure cannot leak a permanent protection.
4488
+ */
4489
+ const retainTerminalBoundaries = (
4490
+ invocations: ReadonlyArray<{ correlation: InvocationCorrelation }>,
4491
+ ): (() => void) => {
4492
+ const keys = invocations.map(({ correlation }) => lifecycleCorrelationKey(correlation));
4493
+ for (const key of keys) retainedTerminalBoundaries.set(key, (retainedTerminalBoundaries.get(key) ?? 0) + 1);
4494
+ let released = false;
4495
+ return () => {
4496
+ if (released) return;
4497
+ released = true;
4498
+ for (const key of keys) {
4499
+ const retained = retainedTerminalBoundaries.get(key);
4500
+ if (retained === undefined) continue;
4501
+ if (retained > 1) retainedTerminalBoundaries.set(key, retained - 1);
4502
+ else retainedTerminalBoundaries.delete(key);
4503
+ }
4504
+ };
4505
+ };
4506
+ const claimTerminalBoundary = (correlation: InvocationCorrelation): boolean => {
4507
+ const key = lifecycleCorrelationKey(correlation);
4508
+ if (publishedTerminalBoundaries.has(key)) return false;
4509
+ publishedTerminalBoundaries.add(key);
4510
+ while (publishedTerminalBoundaries.size > MAX_PUBLISHED_TERMINAL_BOUNDARIES) {
4511
+ let victim: string | undefined;
4512
+ for (const candidate of publishedTerminalBoundaries) {
4513
+ if (retainedTerminalBoundaries.has(candidate)) continue;
4514
+ victim = candidate;
4515
+ break;
4516
+ }
4517
+ // Every key is still publishable: exceed the bound rather than evict a live
4518
+ // one. Correctness beats the bound, and the excess is bounded by the live
4519
+ // lifecycles the session already bounds.
4520
+ if (victim === undefined) break;
4521
+ publishedTerminalBoundaries.delete(victim);
4522
+ }
4523
+ return true;
4524
+ };
4525
+ const hasClaimedTerminalBoundary = (correlation: InvocationCorrelation): boolean =>
4526
+ publishedTerminalBoundaries.has(lifecycleCorrelationKey(correlation));
4381
4527
  const trackLifecycle = (handler: () => Promise<void>, owner: RuntimeState | undefined): Promise<void> => {
4382
4528
  if (!owner) return Promise.resolve();
4383
4529
  let task: Promise<void>;
@@ -4401,8 +4547,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
4401
4547
  ): void => {
4402
4548
  try {
4403
4549
  const payload = toAgentWireEventPayload(event);
4550
+ const delivered = new Set<string>();
4404
4551
  for (const invocation of invocations) {
4405
4552
  if (invocation.connectionId === undefined) continue;
4553
+ delivered.add(invocation.connectionId);
4406
4554
  current.runtime.sendFrameTo(invocation.connectionId, {
4407
4555
  type: "event",
4408
4556
  kind: event.type,
@@ -4410,6 +4558,14 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
4410
4558
  ...invocation.correlation,
4411
4559
  });
4412
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
+ );
4413
4569
  } catch {
4414
4570
  // Streamed content is best-effort; the turn producing it is authoritative.
4415
4571
  }
@@ -4728,6 +4884,11 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
4728
4884
  // recorded as observed=false, never rethrown into the api handler.
4729
4885
  let observed = true;
4730
4886
  const failedTransitions: Array<{ kind: InvocationKind; correlation: InvocationCorrelation }> = [];
4887
+ // Retained BEFORE the first durable await: from here this publisher can still
4888
+ // reach the claim below, so no later boundary may evict its correlations while
4889
+ // it is parked. A start retains too -- its keys are not published yet, so the
4890
+ // protection is inert, and one unconditional release path cannot leak.
4891
+ const releaseTerminalRetention = retainTerminalBoundaries(transitions);
4731
4892
  try {
4732
4893
  for (const invocation of transitions) {
4733
4894
  try {
@@ -4809,6 +4970,15 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
4809
4970
  failureCause ?? Object.assign(new Error("agent run failed"), { code: "agent_failed" }),
4810
4971
  );
4811
4972
  for (const invocation of transitions) {
4973
+ // A correlation whose terminal boundary is already published was
4974
+ // terminalized by the deadline, which emitted its own diagnostic together
4975
+ // with the pair; a second correlated agent_failed would duplicate it. The
4976
+ // check is READ-ONLY: the claim is taken by would-be agent_end publishers
4977
+ // alone, because the real path emits its agent_failed and its agent_end
4978
+ // from two SEPARATE emitLifecycle invocations, so claiming here would make
4979
+ // the publisher block its own boundary. In the ordinary flow this changes
4980
+ // nothing -- agent_failed precedes agent_end and the claim is untaken.
4981
+ if (hasClaimedTerminalBoundary(invocation.correlation)) continue;
4812
4982
  try {
4813
4983
  current.runtime.emitEvent({
4814
4984
  type,
@@ -4856,6 +5026,15 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
4856
5026
  // above with the one uncorrelated frame, so it cannot settle a live prompt.
4857
5027
  const outcome = terminalOutcome ?? terminalStoppedOutcome(stopReason, maintenanceOutcome);
4858
5028
  for (const invocation of transitions) {
5029
+ // Claim synchronously, immediately before the emit: the durable awaits
5030
+ // above give the deadline expiry room to publish this correlation's
5031
+ // boundary first. On a lost claim skip ONLY this frame -- every durable
5032
+ // write, diagnostic cleanup, batch retirement and waiter resolution below
5033
+ // still runs. `observed` deliberately stays true: it means "the correlated
5034
+ // publication reached the wire", and it did, the deadline put it there.
5035
+ // Flipping it would make a terminal abort's durable row refuse to claim
5036
+ // terminalPublished for a boundary that IS on the wire.
5037
+ if (!claimTerminalBoundary(invocation.correlation)) continue;
4859
5038
  try {
4860
5039
  current.runtime.emitEvent({ type, sessionId, ...invocation.correlation, outcome });
4861
5040
  } catch {
@@ -4872,6 +5051,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
4872
5051
  }
4873
5052
  } catch {
4874
5053
  observed = false;
5054
+ } finally {
5055
+ // Every exit path -- published, claim lost, or threw -- releases: this
5056
+ // publisher can no longer reach a claim.
5057
+ releaseTerminalRetention();
4875
5058
  }
4876
5059
  if (type === "agent_end" && isContinuingMidRunMaintenanceOutcome(maintenanceOutcome)) return;
4877
5060
  if (type === "agent_end") {
@@ -4931,7 +5114,16 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
4931
5114
  };
4932
5115
  api.on("agent_start", (event, ctx) => {
4933
5116
  const owner = lifecycleStateForEvent(ctx, "agent_start", event.sdkRunToken);
4934
- 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(
4935
5127
  async () =>
4936
5128
  emitLifecycle(
4937
5129
  "agent_start",
@@ -4947,12 +5139,19 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
4947
5139
  event.sdkRunTokens,
4948
5140
  ),
4949
5141
  owner,
4950
- );
5142
+ ).catch(error => {
5143
+ logger.error("SDK agent_start lifecycle task failed", {
5144
+ error: sanitizePromptFailure(error),
5145
+ });
5146
+ });
4951
5147
  });
4952
5148
  api.on("agent_end", (event, ctx) => {
4953
5149
  const tokenBinding =
4954
5150
  typeof event.sdkRunToken === "string" ? lifecycleRunOwners.get(event.sdkRunToken) : undefined;
4955
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)}`));
4956
5155
  // Capture the oldest unmatched batch synchronously. A successor may start
4957
5156
  // while the failed diagnostic persists; that must not retarget the
4958
5157
  // predecessor's reason or terminal boundary to the successor invocation.
@@ -5055,7 +5254,16 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5055
5254
  // the active run — the root invocation plus any in-run consumed follow-ups
5056
5255
  // sharing it — not only the head, or an attached correlation would
5057
5256
  // false-fire prompt_deadline_exceeded during a long shared run.
5058
- const renewAttributableProgress = (eventType: string, ctx: ExtensionContext): void => {
5257
+ const renewAttributableProgress = (
5258
+ eventType: "tool_execution_start" | "tool_execution_update" | "tool_execution_end",
5259
+ event: AgentSessionEvent,
5260
+ ctx: ExtensionContext,
5261
+ ): void => {
5262
+ // Pairing-only tool boundaries synthesized while an abort unwinds never
5263
+ // entered a tool. Treating them as progress lets a deadline renew its own
5264
+ // lease after claiming the terminal outcome, leaving that claim pending
5265
+ // instead of durably finalizing it.
5266
+ if (isNonDispatchedToolEvent(event)) return;
5059
5267
  // Tool events do not carry an SDK run token. Prefer the lifecycle-active
5060
5268
  // runtime for the current session so a retained predecessor cannot make a
5061
5269
  // live replacement look ambiguous and suppress its lease renewal.
@@ -5109,10 +5317,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5109
5317
  * the session and see the turn settle but never receive a single word of the
5110
5318
  * answer.
5111
5319
  *
5112
- * Scoped to the owning invocations of the batch that is actually running: a
5113
- * turn nobody submitted over the SDK (an ordinary terminal prompt, an
5114
- * autonomous continuation, cron, monitor) has no owner connection and streams
5115
- * 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.
5116
5324
  */
5117
5325
  const STREAMED_TURN_EVENT_TYPES: ReadonlySet<string> = new Set([
5118
5326
  "message_update",
@@ -5125,6 +5333,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5125
5333
  const current = lifecycleStateForContext(ctx, "agent_start");
5126
5334
  const activeInvocation = current?.activeInvocation;
5127
5335
  if (!current) return;
5336
+ const observerConnections = current.runtime.connectionIdsWithCapabilities([
5337
+ TURN_STREAM_CAPABILITY,
5338
+ SESSION_HOST_OBSERVER_CAPABILITY,
5339
+ ]);
5128
5340
  const batch = activeInvocation
5129
5341
  ? current.openLifecycleBatches.find(candidate =>
5130
5342
  candidate.invocations.some(
@@ -5139,7 +5351,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5139
5351
  : current.lifecycleActive
5140
5352
  ? (current.attachedInvocations ?? [])
5141
5353
  : [];
5142
- if (invocations.length === 0) return;
5354
+ if (invocations.length === 0 && observerConnections.length === 0) return;
5143
5355
  // Content bypasses the lifecycle replay ring and must never interrupt its producer.
5144
5356
  if (!event || typeof event.type !== "string" || !STREAMED_TURN_EVENT_TYPES.has(event.type)) return;
5145
5357
  // emitLifecycle("agent_start") awaits durable persistence before it
@@ -5166,14 +5378,14 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5166
5378
  }
5167
5379
  publishContentFrames(current, event as AgentSessionEvent, invocations);
5168
5380
  };
5169
- api.on("tool_execution_start", async (_event, ctx) => {
5170
- renewAttributableProgress("tool_execution_start", ctx);
5381
+ api.on("tool_execution_start", async (event, ctx) => {
5382
+ renewAttributableProgress("tool_execution_start", event as AgentSessionEvent, ctx);
5171
5383
  });
5172
- api.on("tool_execution_update", async (_event, ctx) => {
5173
- renewAttributableProgress("tool_execution_update", ctx);
5384
+ api.on("tool_execution_update", async (event, ctx) => {
5385
+ renewAttributableProgress("tool_execution_update", event as AgentSessionEvent, ctx);
5174
5386
  });
5175
- api.on("tool_execution_end", async (_event, ctx) => {
5176
- renewAttributableProgress("tool_execution_end", ctx);
5387
+ api.on("tool_execution_end", async (event, ctx) => {
5388
+ renewAttributableProgress("tool_execution_end", event as AgentSessionEvent, ctx);
5177
5389
  });
5178
5390
  const errorCode = (error: unknown): string | undefined =>
5179
5391
  typeof error === "object" &&
@@ -5182,6 +5394,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5182
5394
  typeof (error as { code?: unknown }).code === "string"
5183
5395
  ? (error as { code: string }).code
5184
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
+ };
5185
5401
  const startRuntime = async (ctx: ExtensionContext): Promise<void> => {
5186
5402
  if (active) return;
5187
5403
  const sessionId = ctx.sessionManager.getSessionId();
@@ -5206,19 +5422,87 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5206
5422
  await steerReconciliation.hydrateFromStore();
5207
5423
  const deadlineManager = new PromptDeadlineManager({
5208
5424
  reconciliation,
5209
- getLeaseMs: () => {
5210
- const v = options.settings?.get("sdk.promptDeadlineMs" as never) as number | undefined;
5211
- return typeof v === "number" && Number.isFinite(v) ? v : 1_800_000;
5212
- },
5213
- getMaxMs: () => {
5214
- const v = options.settings?.get("sdk.promptMaxRuntimeMs" as never) as number | undefined;
5215
- 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 });
5216
5442
  },
5217
- onExpired: correlation => {
5443
+ onExpired: (correlation, deadlineOutcome) => {
5218
5444
  const owner = lifecycleOwnerHolder.state;
5219
- if (!owner) return;
5220
- removeLifecycleReferences(owner, correlation);
5221
- maybeRetireLifecycleOwner(owner);
5445
+ if (deadlineOutcome === undefined) {
5446
+ if (!owner) return;
5447
+ removeLifecycleReferences(owner, correlation);
5448
+ maybeRetireLifecycleOwner(owner);
5449
+ return;
5450
+ }
5451
+ const failure = sanitizePromptFailure(
5452
+ Object.assign(new Error("Prompt deadline exceeded."), { code: deadlineOutcome.code }),
5453
+ );
5454
+ // The deadline publishes a failed/end PAIR and owns it atomically, so the
5455
+ // boundary claim is taken once, here, covering both frames. Losing it means
5456
+ // a real agent_end already reached the wire for this correlation: emit
5457
+ // NEITHER frame, but still run the cleanup below, and still let #expire
5458
+ // finish its durable reconciliation. Losing the boundary must not skip
5459
+ // cleanup.
5460
+ //
5461
+ // Retained for the whole publication: this path owns the correlation from
5462
+ // here, so a concurrent boundary must not evict its key mid-flight.
5463
+ const releaseTerminalRetention = retainTerminalBoundaries([{ correlation }]);
5464
+ try {
5465
+ if (claimTerminalBoundary(correlation)) {
5466
+ // Each required frame is emitted INDEPENDENTLY (review P2): a throwing
5467
+ // diagnostic must never suppress the boundary, which is the frame this
5468
+ // path exists to deliver. Both failures are reported, never rethrown --
5469
+ // the cleanup below still has to run.
5470
+ try {
5471
+ runtime.emitEvent({
5472
+ type: "agent_failed",
5473
+ sessionId,
5474
+ ...correlation,
5475
+ error: failure,
5476
+ });
5477
+ } catch (error) {
5478
+ logger.warn("sdk: deadline correlated diagnostic publication failed", {
5479
+ commandId: correlation.commandId,
5480
+ turnId: correlation.turnId,
5481
+ error: sanitizePromptFailure(error),
5482
+ });
5483
+ }
5484
+ try {
5485
+ runtime.emitEvent({
5486
+ type: "agent_end",
5487
+ sessionId,
5488
+ ...correlation,
5489
+ outcome: canonicalFailedOutcome(failure, "deadline"),
5490
+ });
5491
+ } catch (error) {
5492
+ logger.error("sdk: deadline correlated boundary publication failed", {
5493
+ commandId: correlation.commandId,
5494
+ turnId: correlation.turnId,
5495
+ error: sanitizePromptFailure(error),
5496
+ });
5497
+ }
5498
+ }
5499
+ } finally {
5500
+ releaseTerminalRetention();
5501
+ }
5502
+ if (owner) {
5503
+ removeLifecycleReferences(owner, correlation);
5504
+ maybeRetireLifecycleOwner(owner);
5505
+ }
5222
5506
  },
5223
5507
  });
5224
5508
  const pending: Array<{
@@ -5858,83 +6142,168 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5858
6142
  };
5859
6143
  let publishedEndpointUrl: string | undefined;
5860
6144
  let brokerRegistered = false;
6145
+ let brokerRegistrationInFlight: Promise<void> | undefined;
6146
+ let brokerRecoveryStopped = false;
5861
6147
  const registerBroker = async (): Promise<void> => {
5862
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;
5863
6268
  try {
5864
- if (options.brokerRegistrationRequired && !options.lifecycleRequestId)
5865
- throw new Error("Lifecycle broker registration requires a request identity.");
5866
- await ensureBroker({ agentDir: options.agentDir });
5867
- const index = await new SessionIndex(options.agentDir).open();
5868
- const locator = await resolveSessionLocator(ctx.cwd, stateRoot);
5869
- const effectiveIncarnation = processIncarnation(process.pid);
5870
- const direct = await reattestMasterSessionIdentity({
5871
- index,
5872
- locator,
5873
- masterCapability: options.masterCapability,
5874
- attestationEpoch: options.masterAttestationEpoch,
5875
- ownerSessionId: options.masterOwnerSessionId,
5876
- sessionId,
5877
- pid: process.pid,
5878
- processIncarnation: effectiveIncarnation,
5879
- });
5880
- let registration: SessionIndexEvent | undefined;
5881
- await runtime.registerWithBroker({
5882
- register: async input => {
5883
- if (publishedEndpointUrl === undefined)
5884
- throw new Error("SDK transport endpoint was not published before broker registration.");
5885
- const endpointPath = path.join(input.stateRoot, "sdk", `${input.sessionId}.json`);
5886
- const file = await readEndpointFile(endpointPath);
5887
- if (!file) throw new Error("SDK endpoint could not be read as a stable regular file.");
5888
- const endpoint = JSON.parse(file.source) as Record<string, unknown>;
5889
- if (
5890
- endpoint.sessionId !== input.sessionId ||
5891
- endpoint.pid !== process.pid ||
5892
- endpoint.url !== publishedEndpointUrl ||
5893
- endpoint.token !== transport.token
5894
- )
5895
- throw new Error("SDK endpoint did not match the published transport authority.");
5896
- const endpointMtimeMs = file.mtimeMs;
5897
- const endpointFileId = `${file.dev}:${file.ino}`;
5898
- const masterRole = masterAttestationForEffectiveHost({
5899
- masterCapability: options.masterCapability,
5900
- attestationEpoch: options.masterAttestationEpoch,
5901
- ownerSessionId: options.masterOwnerSessionId,
5902
- sessionId: input.sessionId,
5903
- pid: process.pid,
5904
- processIncarnation: effectiveIncarnation,
5905
- direct,
5906
- });
5907
- registration = await index.append({
5908
- type: "host_registered",
5909
- ...input,
5910
- locator,
5911
- pid: process.pid,
5912
- endpointMtimeMs,
5913
- endpointFileId,
5914
- ...(options.lifecycleRequestId ? { lifecycleRequestId: options.lifecycleRequestId } : {}),
5915
- ...(masterRole ? { masterRole } : {}),
5916
- });
5917
- },
5918
- unregister: async input => {
5919
- const expected = registration;
5920
- if (
5921
- !expected ||
5922
- expected.sessionId !== input.sessionId ||
5923
- expected.endpointGeneration !== input.endpointGeneration ||
5924
- path.resolve(expected.locator.stateRoot) !== path.resolve(input.stateRoot)
5925
- )
5926
- return;
5927
- // Use the successful publication's proof, never a teardown-time file read.
5928
- await index.unregisterIfCurrent(expected);
5929
- if (registration === expected) registration = undefined;
5930
- },
5931
- });
5932
- 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;
5933
6286
  } catch (error) {
5934
- if (options.brokerRegistrationRequired) throw error;
5935
- 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;
5936
6290
  }
5937
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
+ };
5938
6307
  const runtimeOwner: RuntimeState = {
5939
6308
  sessionId,
5940
6309
  sessionIdentity,
@@ -5947,6 +6316,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5947
6316
  pending,
5948
6317
  openLifecycleBatches,
5949
6318
  registerBroker,
6319
+ stopBrokerRecovery,
5950
6320
  quiesceInput: () => {
5951
6321
  inputGate.quiescing = true;
5952
6322
  lifecycleOwnerHolder.quiescing = true;
@@ -5962,13 +6332,19 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5962
6332
  failureDiagnosticCodes: new Map(),
5963
6333
  lifecycleTasks: new Set(),
5964
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);
5965
6339
  lifecycleOwnerHolder.state = runtimeOwner;
5966
6340
  active = runtimeOwner;
5967
6341
  try {
5968
6342
  publishedEndpointUrl = (await runtime.start()).url;
5969
6343
  await registerBroker();
6344
+ if (!brokerRecoveryStopped) startBrokerRecovery();
5970
6345
  } catch (error) {
5971
6346
  active = undefined;
6347
+ stopBrokerRecovery();
5972
6348
  disposeGate?.();
5973
6349
  try {
5974
6350
  await runtime.stop();
@@ -5989,6 +6365,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
5989
6365
  pending,
5990
6366
  openLifecycleBatches,
5991
6367
  registerBroker,
6368
+ stopBrokerRecovery,
5992
6369
  quiesceInput: () => {
5993
6370
  inputGate.quiescing = true;
5994
6371
  lifecycleOwnerHolder.quiescing = true;
@@ -6013,7 +6390,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
6013
6390
  throw error;
6014
6391
  }
6015
6392
  };
6016
- const stopActive = async (cancelSkillRecovery = false): Promise<void> => {
6393
+ const stopActive = async (cancelSkillRecovery = false, unregisterReason?: "detached_idle"): Promise<void> => {
6017
6394
  const current = active;
6018
6395
  if (!current) return;
6019
6396
  if (cancelSkillRecovery) {
@@ -6022,6 +6399,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
6022
6399
  }
6023
6400
  activePromptOwnerHolder.connectionIds = undefined;
6024
6401
  activePromptOwnerHolder.lifecycleEpoch = undefined;
6402
+ current.stopBrokerRecovery();
6025
6403
  current.quiesceInput();
6026
6404
  current.fenceGateResolutions();
6027
6405
  try {
@@ -6070,7 +6448,10 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
6070
6448
  retiredLifecycleOwnerTimers.set(current, timer);
6071
6449
  } else current.deadlineManager.clearAll();
6072
6450
  current.disposeGate?.();
6073
- await current.runtime.stop({ allowLockContention: cancelSkillRecovery });
6451
+ await current.runtime.stop({
6452
+ allowLockContention: cancelSkillRecovery,
6453
+ ...(unregisterReason === undefined ? {} : { unregisterReason }),
6454
+ });
6074
6455
  } catch (error) {
6075
6456
  // Keep the immutable owner available for a retry when transport teardown
6076
6457
  // fails after quiescing. Clearing `active` before stop prevents a second
@@ -6093,7 +6474,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
6093
6474
  await stopActive();
6094
6475
  await startRuntime(ctx);
6095
6476
  });
6096
- api.on("session_shutdown", async () => {
6097
- await stopActive(true);
6477
+ api.on("session_shutdown", async event => {
6478
+ await stopActive(true, event.reason);
6098
6479
  });
6099
6480
  }