@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
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
3
3
  import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises";
4
4
  import * as os from "node:os";
5
5
  import * as path from "node:path";
6
+ import { markNonDispatchedToolEvent } from "@gajae-code/agent-core";
6
7
  import { logger } from "@gajae-code/utils";
7
8
  import { AsyncJobManager } from "../../async";
8
9
  import type { Settings } from "../../config/settings";
@@ -18,6 +19,7 @@ import { createKindAwareReconciliation } from "../bus/kind-aware-reconciliation"
18
19
  import { createPromptReconciliation } from "../bus/prompt-reconciliation";
19
20
  import { createReconciliationStore } from "../bus/reconciliation-store";
20
21
  import { BROKER_RUNTIME_ABORT_CAPABILITY_FIELD, setBrokerRuntimeAbortCapabilityForTest } from "./control/runtime-gate";
22
+ import { SESSION_HOST_OBSERVER_CAPABILITY, TURN_STREAM_CAPABILITY } from "./host";
21
23
  import { CursorRegistry, QueryHandlers, type QueryResponse, RevisionStore } from "./query";
22
24
  import {
23
25
  createInvocationReconciliation,
@@ -25,6 +27,7 @@ import {
25
27
  createSdkSurfaceFactory,
26
28
  type SdkOnlyInvocationRecord,
27
29
  type SdkOnlyReconciliationStore,
30
+ type SdkOnlyTerminalAbortSeams,
28
31
  SessionSdkSessionRuntime,
29
32
  type SessionSdkTransport,
30
33
  } from "./session-runtime";
@@ -1259,6 +1262,26 @@ describe("SessionSdkSessionRuntime", () => {
1259
1262
  );
1260
1263
  await runtime.stop();
1261
1264
  });
1265
+
1266
+ test("uses a custom capability provider for live delivery without transport negotiation hooks", async () => {
1267
+ const transport = memoryTransport();
1268
+ const runtime = new SessionSdkSessionRuntime({
1269
+ transport,
1270
+ connectionCapabilities: connectionId =>
1271
+ connectionId === "observer"
1272
+ ? new Set([TURN_STREAM_CAPABILITY, SESSION_HOST_OBSERVER_CAPABILITY])
1273
+ : new Set(),
1274
+ });
1275
+ await runtime.start();
1276
+ transport.feed("observer", { type: "event_replay", id: "replay", sinceSeq: 0 });
1277
+ runtime.sendFrameToCapabilities([TURN_STREAM_CAPABILITY, SESSION_HOST_OBSERVER_CAPABILITY], {
1278
+ type: "event",
1279
+ kind: "message_update",
1280
+ payload: {},
1281
+ });
1282
+ expect(transport.sent).toContainEqual({ type: "event", kind: "message_update", payload: {} });
1283
+ await runtime.stop();
1284
+ });
1262
1285
  test("SDK-only host publishes one replayable bash_folded frame per fold and drops the subscription on shutdown", async () => {
1263
1286
  const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-sdk-bash-folded-"));
1264
1287
  const handlers = new Map<string, (event: unknown, ctx: ExtensionContext) => Promise<void> | void>();
@@ -3774,11 +3797,17 @@ async function invocationHarness(
3774
3797
  persistInterceptor?: (transition: { type: string }) => void;
3775
3798
  /** Hold one matching durable transition until the test releases it. */
3776
3799
  persistHold?: { type: string; onEntered: () => void; release: Promise<void> };
3800
+ /** Hold successive matching durable transitions, consumed in arrival order. */
3801
+ persistHolds?: Array<{ type: string; onEntered: () => void; release: Promise<void> }>;
3802
+ /** Throw to inject a publication failure for a matching broadcast frame. */
3803
+ broadcastInterceptor?: (frame: SdkFrame) => void;
3777
3804
  onLifecycleDrainTimeout?: () => void;
3778
3805
  onFailureDiagnosticKeyCount?: (count: number) => void;
3779
3806
  agentFailedWriteFailures?: number;
3780
3807
  branch?: unknown[];
3781
3808
  onInvocationCompletionReconciled?: (kind: string, correlation: { commandId: string; turnId: string }) => void;
3809
+ /** Override/extend the INTERNAL terminal-abort seams the runtime is threaded. */
3810
+ terminalAbortSeams?: Partial<SdkOnlyTerminalAbortSeams>;
3782
3811
  },
3783
3812
  ): Promise<InvocationHarness> {
3784
3813
  const waiters = new Map<string, (frame: ResponseFrame) => void>();
@@ -3801,20 +3830,21 @@ async function invocationHarness(
3801
3830
  ? createInterceptorReconciliationStore(
3802
3831
  hooks.persistInterceptor,
3803
3832
  hooks.agentFailedWriteFailures,
3804
- hooks.persistHold,
3833
+ hooks.persistHolds ?? (hooks.persistHold ? [hooks.persistHold] : undefined),
3805
3834
  )
3806
3835
  : undefined;
3807
3836
  createSdkSessionRuntimeExtension(api, {
3808
3837
  agentDir: cwd,
3809
3838
  ...(hooks.onLifecycleDrainTimeout ? { onLifecycleDrainTimeoutForTests: hooks.onLifecycleDrainTimeout } : {}),
3810
- ...(interceptorStore
3839
+ ...(interceptorStore || hooks.terminalAbortSeams
3811
3840
  ? {
3812
3841
  terminalAbortSeams: {
3813
- getReconciliationStore: () => interceptorStore,
3814
3842
  getTerminalTurnEpoch: () => undefined,
3815
3843
  getActivePromptHandle: () => undefined,
3816
3844
  cancelPendingPreflightForTerminalAbort: () => {},
3817
3845
  abortPromptAndWaitWithTerminal: async () => ({ status: "settled", terminalScope: {} }),
3846
+ ...(interceptorStore ? { getReconciliationStore: () => interceptorStore } : {}),
3847
+ ...hooks.terminalAbortSeams,
3818
3848
  },
3819
3849
  }
3820
3850
  : {}),
@@ -3845,6 +3875,9 @@ async function invocationHarness(
3845
3875
  if (typeof response.id === "string") waiters.get(response.id)?.(response);
3846
3876
  },
3847
3877
  broadcastFrame(frame) {
3878
+ // Interception precedes recording: a frame whose publication throws never
3879
+ // reached the wire, so it must not appear in the observed broadcasts.
3880
+ hooks.broadcastInterceptor?.(frame);
3848
3881
  broadcasts.push(frame);
3849
3882
  },
3850
3883
  start: async () => ({ url: "ws://127.0.0.1:1" }),
@@ -3931,10 +3964,10 @@ async function invocationHarness(
3931
3964
  function createInterceptorReconciliationStore(
3932
3965
  interceptor: (transition: { type: string }) => void,
3933
3966
  agentFailedWriteFailures = 1,
3934
- persistHold?: { type: string; onEntered: () => void; release: Promise<void> },
3967
+ persistHolds?: Array<{ type: string; onEntered: () => void; release: Promise<void> }>,
3935
3968
  ): SdkOnlyReconciliationStore {
3936
3969
  const failureCounts = new Map<string, number>();
3937
- let holdConsumed = false;
3970
+ let nextHold = 0;
3938
3971
  const backing = createInterceptorBackingStore();
3939
3972
  return {
3940
3973
  path: null,
@@ -3966,10 +3999,11 @@ function createInterceptorReconciliationStore(
3966
3999
  interceptor({ type: "agent_end" });
3967
4000
  }
3968
4001
  }
3969
- if (persistHold && !holdConsumed && persistHold.type === transitionType) {
3970
- holdConsumed = true;
3971
- persistHold.onEntered();
3972
- await persistHold.release;
4002
+ const hold = persistHolds?.[nextHold];
4003
+ if (hold && hold.type === transitionType) {
4004
+ nextHold += 1;
4005
+ hold.onEntered();
4006
+ await hold.release;
3973
4007
  }
3974
4008
  backing.records = records;
3975
4009
  },
@@ -5656,6 +5690,599 @@ describe("accepted-control zero-execution bound (#4668)", () => {
5656
5690
  }
5657
5691
  });
5658
5692
 
5693
+ test("a running prompt deadline publishes one terminal frame pair and keeps normal terminals working", async () => {
5694
+ const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-deadline-terminal-frame-"));
5695
+ try {
5696
+ const harness = await invocationHarness("deadline-terminal-frame", cwd, {
5697
+ settings: zeroProgressSettings,
5698
+ sendUserMessage: async (_content, options) => {
5699
+ await options?.onPreflightAcceptCommit?.();
5700
+ await neverSettlingPromise();
5701
+ },
5702
+ });
5703
+ const accepted = await harness.control("turn.prompt", { text: "deadline" });
5704
+ expect(accepted.ok).toBe(true);
5705
+ const correlation = {
5706
+ commandId: accepted.result?.commandId,
5707
+ turnId: accepted.result?.turnId,
5708
+ };
5709
+ await harness.emit("agent_start");
5710
+
5711
+ // An abort can synthesize pairing-only tool boundaries for a call that
5712
+ // never ran. Those are cleanup, not fresh progress that may renew the
5713
+ // deadline currently terminating this prompt.
5714
+ const syntheticStart = {
5715
+ type: "tool_execution_start",
5716
+ toolCallId: "deadline-cleanup",
5717
+ toolName: "read",
5718
+ args: {},
5719
+ };
5720
+ const syntheticEnd = {
5721
+ type: "tool_execution_end",
5722
+ toolCallId: "deadline-cleanup",
5723
+ toolName: "read",
5724
+ result: { content: "aborted" },
5725
+ isError: true,
5726
+ };
5727
+ markNonDispatchedToolEvent(syntheticStart);
5728
+ markNonDispatchedToolEvent(syntheticEnd);
5729
+ await harness.emit("tool_execution_start", syntheticStart);
5730
+ await harness.emit("tool_execution_end", syntheticEnd);
5731
+
5732
+ const settled = await settledStatus(harness, "turn.prompt_status", correlation);
5733
+ expect(settled).toMatchObject({
5734
+ status: "failed",
5735
+ error: { code: "prompt_deadline_exceeded" },
5736
+ outcome: { kind: "failed", code: "prompt_deadline_exceeded", provenance: "deadline" },
5737
+ });
5738
+ const correlated = () =>
5739
+ harness.broadcasts.filter(frame => {
5740
+ const payload = frame.payload as { commandId?: string; turnId?: string } | undefined;
5741
+ return payload?.commandId === correlation.commandId && payload?.turnId === correlation.turnId;
5742
+ });
5743
+ // onExpired publishes the pair only after the durable finalize resolves, so
5744
+ // the terminal status can be observable a few microtasks before the frames
5745
+ // are. Wait for them within a bounded horizon rather than sampling once;
5746
+ // the deep-equality assertions below still pin "exactly one of each".
5747
+ const framesDeadline = Date.now() + 10_000;
5748
+ while (
5749
+ Date.now() < framesDeadline &&
5750
+ !(
5751
+ correlated().some(frame => frame.kind === "agent_failed") &&
5752
+ correlated().some(frame => frame.kind === "agent_end")
5753
+ )
5754
+ )
5755
+ await Bun.sleep(5);
5756
+ expect(correlated().filter(frame => frame.kind === "agent_failed")).toEqual([
5757
+ expect.objectContaining({
5758
+ payload: expect.objectContaining({
5759
+ error: expect.objectContaining({ code: "prompt_deadline_exceeded" }),
5760
+ }),
5761
+ }),
5762
+ ]);
5763
+ expect(correlated().filter(frame => frame.kind === "agent_end")).toEqual([
5764
+ expect.objectContaining({
5765
+ payload: expect.objectContaining({
5766
+ outcome: expect.objectContaining({
5767
+ kind: "failed",
5768
+ code: "prompt_deadline_exceeded",
5769
+ provenance: "deadline",
5770
+ }),
5771
+ }),
5772
+ }),
5773
+ ]);
5774
+
5775
+ // A late real boundary is idempotent for the retired correlation.
5776
+ await harness.emit("agent_end", { messages: [] });
5777
+ expect(correlated().filter(frame => frame.kind === "agent_failed")).toHaveLength(1);
5778
+ expect(correlated().filter(frame => frame.kind === "agent_end")).toHaveLength(1);
5779
+ expect(await harness.query("turn.prompt_status", correlation)).toMatchObject({ result: settled });
5780
+
5781
+ // Control: the ordinary agent-produced terminal path remains live.
5782
+ const control = await harness.control("turn.prompt", { text: "normal terminal" });
5783
+ const controlCorrelation = {
5784
+ commandId: control.result?.commandId,
5785
+ turnId: control.result?.turnId,
5786
+ };
5787
+ await harness.emit("agent_start");
5788
+ await harness.emit("agent_end", { messages: [{ role: "assistant", content: "done" }] });
5789
+ expect(await settledStatus(harness, "turn.prompt_status", controlCorrelation)).toMatchObject({
5790
+ status: "terminal_ok",
5791
+ });
5792
+ const controlTerminals = harness.broadcasts.filter(frame => {
5793
+ const payload = frame.payload as { commandId?: string; turnId?: string } | undefined;
5794
+ return (
5795
+ frame.kind === "agent_end" &&
5796
+ payload?.commandId === controlCorrelation.commandId &&
5797
+ payload?.turnId === controlCorrelation.turnId
5798
+ );
5799
+ });
5800
+ expect(controlTerminals).toHaveLength(1);
5801
+ await harness.stop();
5802
+ } finally {
5803
+ await Bun.sleep(50);
5804
+ await rm(cwd, { recursive: true, force: true });
5805
+ }
5806
+ });
5807
+
5808
+ /** Frames broadcast for one correlation, in publication order. */
5809
+ const correlatedFrames = (harness: InvocationHarness, correlation: { commandId?: string; turnId?: string }) =>
5810
+ harness.broadcasts.filter(frame => {
5811
+ const payload = frame.payload as { commandId?: string; turnId?: string } | undefined;
5812
+ return payload?.commandId === correlation.commandId && payload?.turnId === correlation.turnId;
5813
+ });
5814
+
5815
+ /** Waits for a bounded horizon until the correlated frames satisfy `ready`. */
5816
+ const awaitCorrelatedFrames = async (
5817
+ harness: InvocationHarness,
5818
+ correlation: { commandId?: string; turnId?: string },
5819
+ ready: (frames: SdkFrame[]) => boolean,
5820
+ ): Promise<void> => {
5821
+ // Terminal frames are published only after the durable finalize resolves, so a
5822
+ // single sample can precede them. Poll for the observable condition within a
5823
+ // bounded horizon; the callers' deep-equality assertions still pin "exactly one".
5824
+ const budgetEndsAt = Date.now() + 10_000;
5825
+ while (Date.now() < budgetEndsAt && !ready(correlatedFrames(harness, correlation))) await Bun.sleep(5);
5826
+ };
5827
+
5828
+ test("an SDK-only prompt deadline publishes its terminal frames without aborting the run", async () => {
5829
+ // #5637 review finding 1. The bus route's deadline fences the run through
5830
+ // `abortPromptAndWaitWithTerminal`, which aborts the cancellation domain
5831
+ // BEFORE it waits for settlement — that is the mid-tool kill the boundary
5832
+ // wait exists to prevent, and it is why the bus consumes the ledger seam.
5833
+ //
5834
+ // This route is architecturally different: its `PromptDeadlineManager.onExpired`
5835
+ // publishes `agent_failed` + `agent_end` and cleans up lifecycle references,
5836
+ // and aborts NOTHING. That is precisely why no boundary wait is required
5837
+ // here, and it is the fact this case pins. If someone later wires an abort
5838
+ // into this path, the `abortPromptAndWaitWithTerminal` assertion below fails
5839
+ // loudly — and whoever does it must add a boundary wait, for which the
5840
+ // `pendingToolExecutions` seam is now threaded (both hosts expose one
5841
+ // contract; only the bus consumes it today).
5842
+ const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-sdk-only-deadline-no-abort-"));
5843
+ const abortCalls: Array<{ handle: string; graceMs: number }> = [];
5844
+ const pendingLedgerReads: string[] = [];
5845
+ try {
5846
+ const harness = await invocationHarness("sdk-only-deadline-no-abort", cwd, {
5847
+ settings: zeroProgressSettings,
5848
+ sendUserMessage: async (_content, options) => {
5849
+ await options?.onPreflightAcceptCommit?.();
5850
+ await neverSettlingPromise();
5851
+ },
5852
+ terminalAbortSeams: {
5853
+ getActivePromptHandle: () => "sdk-only-deadline-handle",
5854
+ abortPromptAndWaitWithTerminal: async (handle, seamOptions) => {
5855
+ abortCalls.push({ handle, graceMs: seamOptions.graceMs });
5856
+ return { status: "settled", terminalScope: {} };
5857
+ },
5858
+ // Threaded through `SdkOnlyTerminalAbortSeams` (change 1): the seam
5859
+ // must type-check and the extension must construct with it present.
5860
+ // Reads are recorded rather than asserted non-empty — nothing on this
5861
+ // route consults it yet, which is the honest state of the contract.
5862
+ pendingToolExecutions: handle => {
5863
+ pendingLedgerReads.push(handle);
5864
+ return ["sdk-only-mutating-tool"];
5865
+ },
5866
+ },
5867
+ });
5868
+ const accepted = await harness.control("turn.prompt", { text: "sdk-only deadline" });
5869
+ expect(accepted.ok).toBe(true);
5870
+ const correlation = {
5871
+ commandId: accepted.result?.commandId,
5872
+ turnId: accepted.result?.turnId,
5873
+ };
5874
+ await harness.emit("agent_start");
5875
+ // A dispatched tool is running when the deadline expires: its start has
5876
+ // been delivered and no end ever follows.
5877
+ await harness.emit("tool_execution_start", {
5878
+ type: "tool_execution_start",
5879
+ toolCallId: "sdk-only-mutating-tool",
5880
+ toolName: "apply_patch",
5881
+ args: {},
5882
+ });
5883
+
5884
+ expect(await settledStatus(harness, "turn.prompt_status", correlation)).toMatchObject({
5885
+ status: "failed",
5886
+ error: { code: "prompt_deadline_exceeded" },
5887
+ outcome: { kind: "failed", code: "prompt_deadline_exceeded", provenance: "deadline" },
5888
+ });
5889
+ await awaitCorrelatedFrames(
5890
+ harness,
5891
+ correlation,
5892
+ frames =>
5893
+ frames.some(frame => frame.kind === "agent_failed") && frames.some(frame => frame.kind === "agent_end"),
5894
+ );
5895
+ const frames = correlatedFrames(harness, correlation);
5896
+ expect(frames.filter(frame => frame.kind === "agent_failed")).toEqual([
5897
+ expect.objectContaining({
5898
+ payload: expect.objectContaining({
5899
+ error: expect.objectContaining({ code: "prompt_deadline_exceeded" }),
5900
+ }),
5901
+ }),
5902
+ ]);
5903
+ expect(frames.filter(frame => frame.kind === "agent_end")).toEqual([
5904
+ expect.objectContaining({
5905
+ payload: expect.objectContaining({
5906
+ outcome: expect.objectContaining({
5907
+ kind: "failed",
5908
+ code: "prompt_deadline_exceeded",
5909
+ provenance: "deadline",
5910
+ }),
5911
+ }),
5912
+ }),
5913
+ ]);
5914
+
5915
+ // THE point of the case: the terminal was published, and the run was never
5916
+ // aborted — so there is no mid-tool kill to prevent on this route.
5917
+ expect(abortCalls).toEqual([]);
5918
+ // A late real boundary for the still-"running" tool changes nothing.
5919
+ await harness.emit("tool_execution_end", {
5920
+ type: "tool_execution_end",
5921
+ toolCallId: "sdk-only-mutating-tool",
5922
+ toolName: "apply_patch",
5923
+ isError: false,
5924
+ });
5925
+ await Bun.sleep(50);
5926
+ expect(abortCalls).toEqual([]);
5927
+ expect(correlatedFrames(harness, correlation).filter(frame => frame.kind === "agent_end")).toHaveLength(1);
5928
+ await harness.stop();
5929
+ } finally {
5930
+ await Bun.sleep(50);
5931
+ await rm(cwd, { recursive: true, force: true });
5932
+ }
5933
+ });
5934
+
5935
+ test("the deadline/agent_end overlap publishes exactly one correlated terminal boundary", async () => {
5936
+ // Review P1: the two terminal publishers genuinely overlap. The provider
5937
+ // agent_end handler captures its transitions synchronously and then awaits
5938
+ // durable writes; the deadline callback fires only after its own claim/finalize
5939
+ // awaits. Either can reach the wire first, neither can be stopped by removing
5940
+ // lifecycle references, and noteTransition is a no-op once the record is
5941
+ // terminal — so without a shared claim durable state records one outcome while
5942
+ // clients receive two boundaries. Sweeping the real agent_end across the lease
5943
+ // instant exercises BOTH orderings; whichever publisher loses must stay silent.
5944
+ //
5945
+ // The Bun.sleep below is the RACE DRIVER, not the sampling mechanism: every
5946
+ // observation is a bounded poll on the durable settle plus a quiet window that
5947
+ // proves no second frame follows.
5948
+ const ATTEMPTS = 20;
5949
+ const deadlineWins: number[] = [];
5950
+ const providerWins: number[] = [];
5951
+ for (let attempt = 0; attempt < ATTEMPTS; attempt += 1) {
5952
+ const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-deadline-overlap-"));
5953
+ try {
5954
+ const harness = await invocationHarness(`deadline-overlap-${attempt}`, cwd, {
5955
+ settings: zeroProgressSettings,
5956
+ sendUserMessage: async (_content, options) => {
5957
+ await options?.onPreflightAcceptCommit?.();
5958
+ await neverSettlingPromise();
5959
+ },
5960
+ });
5961
+ const accepted = await harness.control("turn.prompt", { text: "overlap" });
5962
+ expect(accepted.ok).toBe(true);
5963
+ const correlation = { commandId: accepted.result?.commandId, turnId: accepted.result?.turnId };
5964
+ await harness.emit("agent_start");
5965
+ // zeroProgressSettings leases 25ms; straddle that instant.
5966
+ await Bun.sleep(24 + attempt * 0.25);
5967
+ await harness.emit("agent_end", { messages: [{ role: "assistant", content: "done" }] });
5968
+ // Both publishers have run once the durable record is terminal and a
5969
+ // correlated boundary is on the wire; the quiet window then proves that
5970
+ // the loser did not publish a second one behind it.
5971
+ const settled = await settledStatus(harness, "turn.prompt_status", correlation);
5972
+ await awaitCorrelatedFrames(harness, correlation, frames =>
5973
+ frames.some(frame => frame.kind === "agent_end"),
5974
+ );
5975
+ await Bun.sleep(25);
5976
+ const correlated = correlatedFrames(harness, correlation);
5977
+ const boundaries = correlated.filter(frame => frame.kind === "agent_end");
5978
+ const diagnostics = correlated.filter(frame => frame.kind === "agent_failed");
5979
+ expect({ attempt, boundaries: boundaries.length }).toEqual({ attempt, boundaries: 1 });
5980
+ const outcome = (boundaries[0]?.payload as { outcome?: { provenance?: string } } | undefined)?.outcome;
5981
+ if (outcome?.provenance === "deadline") {
5982
+ // The deadline owns the pair atomically: its correlated diagnostic and
5983
+ // its correlated boundary reach the wire together, exactly once, and
5984
+ // the correlation still reconciles to a single durable outcome.
5985
+ deadlineWins.push(attempt);
5986
+ expect({ attempt, diagnostics: diagnostics.length }).toEqual({ attempt, diagnostics: 1 });
5987
+ expect(settled).toMatchObject({ outcome: expect.objectContaining({ kind: expect.any(String) }) });
5988
+ } else {
5989
+ // Control within the sweep: the ordinary provider terminal path still
5990
+ // publishes its one boundary, and no synthetic pair trails it.
5991
+ providerWins.push(attempt);
5992
+ expect({ attempt, diagnostics: diagnostics.length }).toEqual({ attempt, diagnostics: 0 });
5993
+ }
5994
+ await harness.stop();
5995
+ } finally {
5996
+ await Bun.sleep(10);
5997
+ await rm(cwd, { recursive: true, force: true });
5998
+ }
5999
+ }
6000
+ // A sweep that never reached the lease instant would prove nothing.
6001
+ expect(deadlineWins.length).toBeGreaterThan(0);
6002
+ expect(deadlineWins.length + providerWins.length).toBe(ATTEMPTS);
6003
+ });
6004
+
6005
+ test("the deadline publishes its correlated diagnostic before its correlated boundary", async () => {
6006
+ // Ordering is part of the pair's contract: a client that matches the boundary and
6007
+ // stops reading must never have the failure reason arrive behind it.
6008
+ const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-deadline-pair-order-"));
6009
+ try {
6010
+ const harness = await invocationHarness("deadline-pair-order", cwd, {
6011
+ settings: zeroProgressSettings,
6012
+ sendUserMessage: async (_content, options) => {
6013
+ await options?.onPreflightAcceptCommit?.();
6014
+ await neverSettlingPromise();
6015
+ },
6016
+ });
6017
+ const accepted = await harness.control("turn.prompt", { text: "order" });
6018
+ expect(accepted.ok).toBe(true);
6019
+ const correlation = { commandId: accepted.result?.commandId, turnId: accepted.result?.turnId };
6020
+ await harness.emit("agent_start");
6021
+ await awaitCorrelatedFrames(
6022
+ harness,
6023
+ correlation,
6024
+ frames =>
6025
+ frames.some(frame => frame.kind === "agent_failed") && frames.some(frame => frame.kind === "agent_end"),
6026
+ );
6027
+ const kinds = correlatedFrames(harness, correlation)
6028
+ .map(frame => frame.kind)
6029
+ .filter(kind => kind === "agent_failed" || kind === "agent_end");
6030
+ // One assertion over the indices, so an out-of-order pair cannot pass by
6031
+ // satisfying two independent presence checks.
6032
+ expect({
6033
+ failedAt: kinds.indexOf("agent_failed"),
6034
+ endAt: kinds.indexOf("agent_end"),
6035
+ failedCount: kinds.filter(kind => kind === "agent_failed").length,
6036
+ endCount: kinds.filter(kind => kind === "agent_end").length,
6037
+ }).toEqual({ failedAt: 0, endAt: 1, failedCount: 1, endCount: 1 });
6038
+
6039
+ // Control: the ordinary terminal path still publishes exactly one boundary.
6040
+ const control = await harness.control("turn.prompt", { text: "normal terminal" });
6041
+ const controlCorrelation = { commandId: control.result?.commandId, turnId: control.result?.turnId };
6042
+ await harness.emit("agent_start");
6043
+ await harness.emit("agent_end", { messages: [{ role: "assistant", content: "done" }] });
6044
+ expect(await settledStatus(harness, "turn.prompt_status", controlCorrelation)).toMatchObject({
6045
+ status: "terminal_ok",
6046
+ });
6047
+ expect(correlatedFrames(harness, controlCorrelation).filter(frame => frame.kind === "agent_end")).toHaveLength(
6048
+ 1,
6049
+ );
6050
+ await harness.stop();
6051
+ } finally {
6052
+ await Bun.sleep(50);
6053
+ await rm(cwd, { recursive: true, force: true });
6054
+ }
6055
+ });
6056
+
6057
+ test.each([
6058
+ { failing: "agent_failed" as const, surviving: "agent_end" as const },
6059
+ { failing: "agent_end" as const, surviving: "agent_failed" as const },
6060
+ ])("a deadline frame that fails to publish never suppresses the other ($failing)", async ({
6061
+ failing,
6062
+ surviving,
6063
+ }) => {
6064
+ // Review P2: the deadline's two frames were emitted as unguarded sequential
6065
+ // calls, so a throwing agent_failed skipped the agent_end while reconciliation
6066
+ // and the lifecycle cleanup still ran -- leaving the ACP request with NO
6067
+ // terminal boundary, the exact failure this path exists to prevent. Each frame
6068
+ // is now published independently, and neither failure may skip the cleanup.
6069
+ const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-deadline-frame-failure-"));
6070
+ const warnings: Array<{ message: string; data: Record<string, unknown> | undefined }> = [];
6071
+ const warn = spyOn(logger, "warn").mockImplementation((message, data) => {
6072
+ warnings.push({ message: String(message), data: data as Record<string, unknown> | undefined });
6073
+ });
6074
+ const errors: Array<{ message: string; data: Record<string, unknown> | undefined }> = [];
6075
+ const error = spyOn(logger, "error").mockImplementation((message, data) => {
6076
+ errors.push({ message: String(message), data: data as Record<string, unknown> | undefined });
6077
+ });
6078
+ try {
6079
+ let failCorrelation: { commandId?: string; turnId?: string } = {};
6080
+ const harness = await invocationHarness(`deadline-frame-failure-${failing}`, cwd, {
6081
+ settings: zeroProgressSettings,
6082
+ sendUserMessage: async (_content, options) => {
6083
+ await options?.onPreflightAcceptCommit?.();
6084
+ await neverSettlingPromise();
6085
+ },
6086
+ broadcastInterceptor: frame => {
6087
+ const payload = frame.payload as { commandId?: string; turnId?: string } | undefined;
6088
+ if (
6089
+ frame.kind === failing &&
6090
+ payload?.commandId === failCorrelation.commandId &&
6091
+ payload?.turnId === failCorrelation.turnId
6092
+ )
6093
+ throw Object.assign(new Error("injected publication failure"), { code: "io_error" });
6094
+ },
6095
+ });
6096
+ const accepted = await harness.control("turn.prompt", { text: "frame failure" });
6097
+ expect(accepted.ok).toBe(true);
6098
+ const correlation = { commandId: accepted.result?.commandId, turnId: accepted.result?.turnId };
6099
+ failCorrelation = correlation;
6100
+ await harness.emit("agent_start");
6101
+ await awaitCorrelatedFrames(harness, correlation, frames => frames.some(frame => frame.kind === surviving));
6102
+ const correlated = correlatedFrames(harness, correlation);
6103
+ // The frame whose publication threw never reached the wire; the other one
6104
+ // still did, carrying the deadline's terminal identity.
6105
+ expect({
6106
+ failing: correlated.filter(frame => frame.kind === failing).length,
6107
+ surviving: correlated.filter(frame => frame.kind === surviving).length,
6108
+ }).toEqual({ failing: 0, surviving: 1 });
6109
+ if (surviving === "agent_end")
6110
+ expect(correlated.find(frame => frame.kind === "agent_end")?.payload).toMatchObject({
6111
+ outcome: { kind: "failed", code: "prompt_deadline_exceeded", provenance: "deadline" },
6112
+ });
6113
+
6114
+ // The failure is reported, with the correlation and without provider text.
6115
+ expect([...warnings, ...errors]).toContainEqual(
6116
+ expect.objectContaining({
6117
+ message: expect.stringContaining("deadline correlated"),
6118
+ data: expect.objectContaining({ commandId: correlation.commandId, turnId: correlation.turnId }),
6119
+ }),
6120
+ );
6121
+
6122
+ // The cleanup after the publication still ran: the durable record is settled,
6123
+ // a late real boundary stays idempotent for the retired correlation, and the
6124
+ // session still terminalizes a fresh turn.
6125
+ expect(await settledStatus(harness, "turn.prompt_status", correlation)).toMatchObject({
6126
+ status: "failed",
6127
+ error: { code: "prompt_deadline_exceeded" },
6128
+ });
6129
+ await harness.emit("agent_end", { messages: [] });
6130
+ expect(correlatedFrames(harness, correlation).filter(frame => frame.kind === "agent_end")).toHaveLength(
6131
+ surviving === "agent_end" ? 1 : 0,
6132
+ );
6133
+ const control = await harness.control("turn.prompt", { text: "after frame failure" });
6134
+ const controlIds = { commandId: control.result?.commandId, turnId: control.result?.turnId };
6135
+ await harness.emit("agent_start");
6136
+ await harness.emit("agent_end", { messages: [{ role: "assistant", content: "done" }] });
6137
+ expect(await settledStatus(harness, "turn.prompt_status", controlIds)).toMatchObject({
6138
+ status: "terminal_ok",
6139
+ });
6140
+ expect(correlatedFrames(harness, controlIds).filter(frame => frame.kind === "agent_end")).toHaveLength(1);
6141
+ await harness.stop();
6142
+ } finally {
6143
+ warn.mockRestore();
6144
+ error.mockRestore();
6145
+ await Bun.sleep(50);
6146
+ await rm(cwd, { recursive: true, force: true });
6147
+ }
6148
+ });
6149
+
6150
+ test("boundary-claim eviction never lets a parked publisher republish a claimed boundary", async () => {
6151
+ // Review P1: the claim registry is FIFO-bounded, while a lifecycle handler
6152
+ // captures its transitions, awaits its durable writes, and only claims
6153
+ // immediately before the emit. Parked on those awaits, its key can be evicted
6154
+ // by later correlations, and its delayed agent_end then publishes a SECOND
6155
+ // correlated boundary for a correlation the deadline already terminalized.
6156
+ //
6157
+ // The interleaving is CONSTRUCTED, not sampled. The deadline's finalize write
6158
+ // is held so the real agent_end lands inside the finalization window and takes
6159
+ // the upgrade path; releasing it lets the deadline publish and claim the pair
6160
+ // while the handler parks on the NEXT invocation's write, before its claim
6161
+ // loop. The pressure then runs in a second session because one session's
6162
+ // durable writes are serialized -- a held write blocks that session entirely.
6163
+ const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-claim-eviction-"));
6164
+ try {
6165
+ const finalizeHold = { entered: Promise.withResolvers<void>(), release: Promise.withResolvers<void>() };
6166
+ const parkHold = { entered: Promise.withResolvers<void>(), release: Promise.withResolvers<void>() };
6167
+ // Read at each lease arming, so only the `expiring` prompt is short-leased.
6168
+ let promptDeadlineMs = 600_000;
6169
+ const settings = {
6170
+ get: (key: string) =>
6171
+ key === "sdk.promptDeadlineMs"
6172
+ ? promptDeadlineMs
6173
+ : key === "sdk.promptMaxRuntimeMs"
6174
+ ? 600_000
6175
+ : undefined,
6176
+ } as unknown as Settings;
6177
+ const promoted: Array<((promotion: { startsOwnRun: boolean }) => void) | undefined> = [];
6178
+ const harness = await invocationHarness("claim-eviction", cwd, {
6179
+ settings,
6180
+ sendUserMessage: async (_content, options) => {
6181
+ await options?.onPreflightAcceptCommit?.();
6182
+ if ((options as { deliverAs?: string } | undefined)?.deliverAs === "followUp") {
6183
+ promoted.push(
6184
+ (options as { onQueuedPromoted?: (promotion: { startsOwnRun: boolean }) => void } | undefined)
6185
+ ?.onQueuedPromoted,
6186
+ );
6187
+ return;
6188
+ }
6189
+ await neverSettlingPromise();
6190
+ },
6191
+ persistInterceptor: () => {},
6192
+ agentFailedWriteFailures: 0,
6193
+ persistHolds: [
6194
+ {
6195
+ type: "agent_end",
6196
+ onEntered: () => finalizeHold.entered.resolve(),
6197
+ release: finalizeHold.release.promise,
6198
+ },
6199
+ { type: "agent_end", onEntered: () => parkHold.entered.resolve(), release: parkHold.release.promise },
6200
+ ],
6201
+ onLifecycleDrainTimeout: () => {},
6202
+ });
6203
+ // A never-settling head turn keeps the session streaming so the two
6204
+ // follow-ups queue and promote into ONE run, giving the terminal handler a
6205
+ // second invocation to park on after the deadline publishes the first.
6206
+ expect((await harness.control("turn.prompt", { text: "head" })).ok).toBe(true);
6207
+ await harness.emit("agent_start");
6208
+ // `leading` is drained FIRST, so the terminal handler's first durable write
6209
+ // is its own -- not the deadline's pending finalization -- and parking on it
6210
+ // leaves `expiring` untouched for the deadline to publish.
6211
+ const leading = await harness.control("turn.follow_up", { text: "leading" });
6212
+ promoted[0]?.({ startsOwnRun: true });
6213
+ promptDeadlineMs = 1_500;
6214
+ const expiring = await harness.control("turn.follow_up", { text: "expiring" });
6215
+ promoted[1]?.({ startsOwnRun: true });
6216
+ promptDeadlineMs = 600_000;
6217
+ const expiringIds = { commandId: expiring.result?.commandId, turnId: expiring.result?.turnId };
6218
+ const leadingIds = { commandId: leading.result?.commandId, turnId: leading.result?.turnId };
6219
+ await harness.emit("agent_start");
6220
+ await awaitCorrelatedFrames(harness, expiringIds, frames =>
6221
+ frames.some(frame => frame.kind === "agent_start"),
6222
+ );
6223
+ await awaitCorrelatedFrames(harness, leadingIds, frames => frames.some(frame => frame.kind === "agent_start"));
6224
+ // Both must be in the drained batch: a lease that fired before the drain
6225
+ // would leave the handler with nothing to park on and prove nothing.
6226
+ expect({
6227
+ expiring: correlatedFrames(harness, expiringIds).filter(frame => frame.kind === "agent_start").length,
6228
+ leading: correlatedFrames(harness, leadingIds).filter(frame => frame.kind === "agent_start").length,
6229
+ }).toEqual({ expiring: 1, leading: 1 });
6230
+
6231
+ await finalizeHold.entered.promise;
6232
+ // The real boundary lands while the deadline's finalization is pending, so
6233
+ // the handler captures BOTH transitions and waits on the finalize commit.
6234
+ const parkedEnd = harness.emit("agent_end", {
6235
+ sdkRunToken: `${leadingIds.commandId}:${leadingIds.turnId}`,
6236
+ messages: [{ role: "assistant", content: "done" }],
6237
+ });
6238
+ finalizeHold.release.resolve();
6239
+ // The deadline now publishes and claims the pair; the handler parks on the
6240
+ // leading invocation's durable write, before its own claim loop.
6241
+ await parkHold.entered.promise;
6242
+ await awaitCorrelatedFrames(harness, expiringIds, frames => frames.some(frame => frame.kind === "agent_end"));
6243
+ const deadlineBoundary = correlatedFrames(harness, expiringIds).find(frame => frame.kind === "agent_end");
6244
+ expect(
6245
+ (deadlineBoundary?.payload as { outcome?: { provenance?: string } } | undefined)?.outcome?.provenance,
6246
+ ).toBe("deadline");
6247
+
6248
+ // Eviction pressure: more than MAX_PUBLISHED_TERMINAL_BOUNDARIES (1024)
6249
+ // other correlations claim their own boundary while the publisher is parked.
6250
+ await harness.switchSession("claim-eviction-pressure");
6251
+ const PRESSURE = 1_100;
6252
+ for (let index = 0; index < PRESSURE; index += 1) {
6253
+ const accepted = await harness.control("turn.prompt", { text: `pressure-${index}` });
6254
+ expect(accepted.ok).toBe(true);
6255
+ const token = `${accepted.result?.commandId}:${accepted.result?.turnId}`;
6256
+ await harness.emit("agent_start", { sdkRunToken: token });
6257
+ await harness.emit("agent_end", { sdkRunToken: token, messages: [{ role: "assistant", content: "ok" }] });
6258
+ }
6259
+ const claimedCorrelations = new Set(
6260
+ harness.broadcasts
6261
+ .filter(frame => frame.kind === "agent_end")
6262
+ .map(frame => {
6263
+ const payload = frame.payload as { commandId?: string; turnId?: string } | undefined;
6264
+ return `${payload?.commandId}:${payload?.turnId}`;
6265
+ }),
6266
+ );
6267
+ // A sweep that never passed the bound would evict nothing and prove nothing.
6268
+ expect(claimedCorrelations.size).toBeGreaterThan(1_024);
6269
+
6270
+ parkHold.release.resolve();
6271
+ await parkedEnd;
6272
+ await Bun.sleep(25);
6273
+ // The parked publisher lost its claim to the deadline before the pressure
6274
+ // ran, and must still lose it after: exactly one boundary on the wire.
6275
+ expect({
6276
+ expiring: correlatedFrames(harness, expiringIds).filter(frame => frame.kind === "agent_end").length,
6277
+ leading: correlatedFrames(harness, leadingIds).filter(frame => frame.kind === "agent_end").length,
6278
+ }).toEqual({ expiring: 1, leading: 1 });
6279
+ await harness.stop();
6280
+ } finally {
6281
+ await Bun.sleep(50);
6282
+ await rm(cwd, { recursive: true, force: true });
6283
+ }
6284
+ });
6285
+
5659
6286
  test("an abort_and_prompt replacement of a zero-execution turn is also bounded", async () => {
5660
6287
  // The issue observed both the original turn.prompt AND the replacement
5661
6288
  // turn.abort_and_prompt accepted with permanently zero activity. Both the