@gajae-code/coding-agent 0.17.2 → 0.17.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (349) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/dist/types/cli/daemon-cli.d.ts +3 -1
  3. package/dist/types/cli/github-release.d.ts +17 -0
  4. package/dist/types/cli/public-command-entry.d.ts +56 -0
  5. package/dist/types/cli/public-command-errors.d.ts +126 -0
  6. package/dist/types/cli/public-command-evidence.d.ts +99 -0
  7. package/dist/types/cli/public-command-help.d.ts +65 -0
  8. package/dist/types/cli/public-command-registry.d.ts +69 -0
  9. package/dist/types/cli/session-picker.d.ts +1 -1
  10. package/dist/types/cli/setup-cli.d.ts +1 -0
  11. package/dist/types/cli/skills-cli.d.ts +5 -0
  12. package/dist/types/cli.d.ts +3 -0
  13. package/dist/types/commands/daemon.d.ts +4 -50
  14. package/dist/types/commands/sdk.d.ts +20 -37
  15. package/dist/types/commands/setup.d.ts +3 -0
  16. package/dist/types/commands/skills.d.ts +9 -0
  17. package/dist/types/config/autorouting-tier-map.d.ts +18 -0
  18. package/dist/types/config/file-lock.d.ts +9 -1
  19. package/dist/types/config/model-profile-activation.d.ts +45 -1
  20. package/dist/types/config/model-registry.d.ts +11 -2
  21. package/dist/types/config/settings-schema.d.ts +11 -0
  22. package/dist/types/coordinator-mcp/question-state.d.ts +6 -1
  23. package/dist/types/daemon/operator-contract.d.ts +5 -1
  24. package/dist/types/eval/js/worker-protocol.d.ts +2 -0
  25. package/dist/types/extensibility/extensions/types.d.ts +12 -0
  26. package/dist/types/extensibility/gjc-plugins/registry.d.ts +4 -0
  27. package/dist/types/extensibility/runtime-skill-discovery.d.ts +18 -0
  28. package/dist/types/extensibility/shared-events.d.ts +3 -1
  29. package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
  30. package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
  31. package/dist/types/internal-urls/index.d.ts +1 -0
  32. package/dist/types/internal-urls/parse.d.ts +9 -1
  33. package/dist/types/main.d.ts +22 -0
  34. package/dist/types/modes/acp/acp-agent.d.ts +55 -1
  35. package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
  36. package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
  37. package/dist/types/modes/components/session-selector.d.ts +5 -2
  38. package/dist/types/modes/components/user-message-selector.d.ts +5 -2
  39. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  40. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  41. package/dist/types/modes/interactive-mode.d.ts +1 -1
  42. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
  43. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
  44. package/dist/types/modes/types.d.ts +2 -2
  45. package/dist/types/runtime/process-lifecycle.d.ts +2 -0
  46. package/dist/types/runtime-mcp/manager.d.ts +56 -0
  47. package/dist/types/runtime-mcp/pool.d.ts +17 -0
  48. package/dist/types/runtime-mcp/redaction.d.ts +20 -0
  49. package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
  50. package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
  51. package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
  52. package/dist/types/sdk/broker/broker.d.ts +13 -2
  53. package/dist/types/sdk/broker/identity.d.ts +3 -1
  54. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
  55. package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
  56. package/dist/types/sdk/broker/runtime.d.ts +1 -1
  57. package/dist/types/sdk/broker/session-index.d.ts +22 -3
  58. package/dist/types/sdk/broker/spawn-authority.d.ts +4 -0
  59. package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
  60. package/dist/types/sdk/bus/index.d.ts +19 -0
  61. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
  62. package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
  63. package/dist/types/sdk/cli/master-cli.d.ts +6 -10
  64. package/dist/types/sdk/cli/rows.d.ts +19 -1
  65. package/dist/types/sdk/cli/session-cli.d.ts +22 -5
  66. package/dist/types/sdk/client/client.d.ts +2 -0
  67. package/dist/types/sdk/guides/cli.d.ts +3 -4
  68. package/dist/types/sdk/guides/verify.d.ts +2 -0
  69. package/dist/types/sdk/host/host.d.ts +9 -0
  70. package/dist/types/sdk/host/session-runtime.d.ts +31 -0
  71. package/dist/types/sdk/host/types.d.ts +10 -0
  72. package/dist/types/sdk/index.d.ts +1 -0
  73. package/dist/types/sdk/prompt-deadline-flush.d.ts +83 -0
  74. package/dist/types/sdk/prompt-deadline-manager.d.ts +37 -0
  75. package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
  76. package/dist/types/sdk/router/session-router.d.ts +2 -0
  77. package/dist/types/sdk/startup-capability.d.ts +2 -0
  78. package/dist/types/sdk/transport/relay.d.ts +1 -0
  79. package/dist/types/sdk/transport/serve-cli.d.ts +48 -4
  80. package/dist/types/sdk/transport/stdio.d.ts +5 -1
  81. package/dist/types/session/agent-session.d.ts +168 -21
  82. package/dist/types/session/fallback-chain-controller.d.ts +6 -6
  83. package/dist/types/session/fold-coordinator.d.ts +17 -3
  84. package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
  85. package/dist/types/session/session-manager.d.ts +64 -1
  86. package/dist/types/session/session-work-lease.d.ts +14 -0
  87. package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
  88. package/dist/types/setup/provider-onboarding.d.ts +81 -1
  89. package/dist/types/tools/bash.d.ts +37 -11
  90. package/dist/types/tools/job.d.ts +1 -1
  91. package/dist/types/tools/steer-fold.d.ts +31 -0
  92. package/dist/types/utils/git.d.ts +28 -2
  93. package/dist/types/utils/mupdf-embedded.d.ts +2 -0
  94. package/dist/types/utils/mupdf.d.ts +4 -0
  95. package/dist/types/utils/session-title-generation.d.ts +6 -0
  96. package/dist/types/utils/title-generator.d.ts +11 -2
  97. package/dist/types/web/scrapers/types.d.ts +3 -0
  98. package/package.json +18 -10
  99. package/scripts/build-binary.ts +9 -3
  100. package/scripts/compile-args.ts +0 -9
  101. package/scripts/embed-mupdf.ts +52 -0
  102. package/scripts/generate-sdk-operation-inventory.ts +31 -1
  103. package/scripts/vendor-markit.ts +196 -0
  104. package/src/async/job-manager.ts +22 -1
  105. package/src/cli/daemon-cli.ts +53 -18
  106. package/src/cli/github-release.ts +87 -5
  107. package/src/cli/mcp-cli.ts +38 -4
  108. package/src/cli/public-command-entry.ts +450 -0
  109. package/src/cli/public-command-errors.ts +581 -0
  110. package/src/cli/public-command-evidence.ts +717 -0
  111. package/src/cli/public-command-help.ts +360 -0
  112. package/src/cli/public-command-registry.ts +535 -0
  113. package/src/cli/session-picker.ts +2 -1
  114. package/src/cli/setup-cli.ts +9 -1
  115. package/src/cli/skills-cli.ts +46 -2
  116. package/src/cli.ts +61 -1
  117. package/src/commands/daemon.ts +65 -69
  118. package/src/commands/sdk.ts +175 -117
  119. package/src/commands/setup.ts +2 -0
  120. package/src/commands/skills.ts +11 -0
  121. package/src/config/autorouting-tier-map.ts +14 -0
  122. package/src/config/file-lock.ts +147 -51
  123. package/src/config/model-profile-activation.ts +334 -43
  124. package/src/config/model-registry.ts +183 -25
  125. package/src/config/settings-schema.ts +26 -2
  126. package/src/coordinator-mcp/policy.ts +10 -5
  127. package/src/coordinator-mcp/question-gate-codec.ts +17 -0
  128. package/src/coordinator-mcp/question-state.ts +175 -8
  129. package/src/coordinator-mcp/server.ts +507 -81
  130. package/src/coordinator-mcp/session-reaper.ts +6 -4
  131. package/src/crash/record-loader.ts +6 -2
  132. package/src/daemon/operator-contract.ts +26 -12
  133. package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
  134. package/src/eval/js/context-manager.ts +8 -8
  135. package/src/eval/js/worker-core.ts +9 -4
  136. package/src/eval/js/worker-protocol.ts +2 -0
  137. package/src/extensibility/extensions/runner.ts +52 -4
  138. package/src/extensibility/extensions/types.ts +10 -0
  139. package/src/extensibility/gjc-plugins/registry.ts +196 -37
  140. package/src/extensibility/runtime-skill-discovery.ts +61 -3
  141. package/src/extensibility/shared-events.ts +3 -1
  142. package/src/gjc-runtime/session-state-lock.ts +165 -24
  143. package/src/gjc-runtime/ultragoal-succession.ts +31 -3
  144. package/src/internal-urls/docs-index.generated.ts +20 -19
  145. package/src/internal-urls/embedded-protocol.ts +83 -0
  146. package/src/internal-urls/index.ts +1 -0
  147. package/src/internal-urls/parse.ts +21 -1
  148. package/src/internal-urls/router.ts +4 -2
  149. package/src/main.ts +123 -55
  150. package/src/modes/acp/acp-agent.ts +408 -35
  151. package/src/modes/acp/startup-provenance.ts +59 -0
  152. package/src/modes/components/custom-provider-wizard.ts +308 -7
  153. package/src/modes/components/oauth-selector.ts +9 -0
  154. package/src/modes/components/session-selector.ts +47 -4
  155. package/src/modes/components/user-message-selector.ts +57 -15
  156. package/src/modes/controllers/command-controller.ts +58 -32
  157. package/src/modes/controllers/extension-ui-controller.ts +33 -7
  158. package/src/modes/controllers/input-controller.ts +4 -0
  159. package/src/modes/controllers/selector-controller.ts +161 -15
  160. package/src/modes/interactive-mode.ts +21 -7
  161. package/src/modes/runtime-init.ts +1 -0
  162. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
  163. package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
  164. package/src/modes/types.ts +2 -2
  165. package/src/prompts/agent-fragments/browser-aside.md +1 -0
  166. package/src/prompts/system/title-system.md +1 -1
  167. package/src/prompts/tools/bash.md +1 -1
  168. package/src/prompts/tools/job.md +1 -1
  169. package/src/prompts/tools/read.md +2 -0
  170. package/src/runtime/network-prewarm-service.test.ts +36 -1
  171. package/src/runtime/network-prewarm-service.ts +28 -3
  172. package/src/runtime/process-lifecycle.ts +22 -11
  173. package/src/runtime-mcp/config.ts +16 -1
  174. package/src/runtime-mcp/manager.ts +689 -55
  175. package/src/runtime-mcp/pool.test.ts +31 -0
  176. package/src/runtime-mcp/pool.ts +304 -28
  177. package/src/runtime-mcp/redaction.ts +42 -0
  178. package/src/runtime-mcp/startup-policy.ts +7 -0
  179. package/src/runtime-mcp/tool-bridge.ts +71 -0
  180. package/src/runtime-mcp/transports/http.ts +108 -25
  181. package/src/sdk/acp/adapter.ts +27 -8
  182. package/src/sdk/broker/broker.ts +378 -52
  183. package/src/sdk/broker/identity.ts +9 -1
  184. package/src/sdk/broker/lifecycle-ledger.ts +95 -31
  185. package/src/sdk/broker/lifecycle.ts +690 -141
  186. package/src/sdk/broker/runtime.ts +1 -1
  187. package/src/sdk/broker/session-index.ts +97 -4
  188. package/src/sdk/broker/spawn-authority.ts +35 -7
  189. package/src/sdk/broker/stderr-drainer.ts +59 -0
  190. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  191. package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
  192. package/src/sdk/bus/index.ts +690 -80
  193. package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
  194. package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
  195. package/src/sdk/bus/telegram-daemon.ts +10 -3
  196. package/src/sdk/cli/master-cli.ts +47 -36
  197. package/src/sdk/cli/rows.ts +30 -7
  198. package/src/sdk/cli/session-cli.ts +687 -184
  199. package/src/sdk/client/client.ts +18 -0
  200. package/src/sdk/guides/catalog.ts +2 -2
  201. package/src/sdk/guides/cli.ts +20 -62
  202. package/src/sdk/guides/verify.ts +14 -0
  203. package/src/sdk/host/host.ts +49 -5
  204. package/src/sdk/host/session-runtime.test.ts +138 -2
  205. package/src/sdk/host/session-runtime.ts +311 -97
  206. package/src/sdk/host/types.ts +12 -1
  207. package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
  208. package/src/sdk/host/websocket-transport.ts +40 -6
  209. package/src/sdk/index.ts +11 -0
  210. package/src/sdk/prompt-deadline-flush.ts +334 -0
  211. package/src/sdk/prompt-deadline-manager.ts +133 -1
  212. package/src/sdk/prompt-tool-boundary.ts +121 -0
  213. package/src/sdk/protocol/operation-inventory.generated.json +106 -0
  214. package/src/sdk/router/session-router.ts +9 -1
  215. package/src/sdk/session.ts +182 -7
  216. package/src/sdk/startup-capability.ts +1 -1
  217. package/src/sdk/transport/relay.ts +71 -6
  218. package/src/sdk/transport/serve-cli.ts +397 -42
  219. package/src/sdk/transport/socket.ts +12 -11
  220. package/src/sdk/transport/stdio.ts +7 -3
  221. package/src/session/agent-session.ts +2437 -400
  222. package/src/session/fallback-chain-controller.ts +6 -7
  223. package/src/session/fold-coordinator.ts +24 -5
  224. package/src/session/internal/managed-session-scope.ts +58 -18
  225. package/src/session/internal/managed-session-storage.ts +304 -11
  226. package/src/session/session-manager.ts +382 -44
  227. package/src/session/session-work-lease.ts +34 -0
  228. package/src/setup/model-onboarding-guidance.ts +1 -1
  229. package/src/setup/provider-onboarding.ts +397 -24
  230. package/src/slash-commands/builtin-registry.ts +258 -14
  231. package/src/task/index.ts +14 -2
  232. package/src/tools/bash.ts +98 -78
  233. package/src/tools/fetch.ts +119 -20
  234. package/src/tools/job.ts +151 -25
  235. package/src/tools/output-meta.ts +15 -2
  236. package/src/tools/path-utils.ts +27 -3
  237. package/src/tools/read.ts +3 -3
  238. package/src/tools/steer-fold.ts +107 -0
  239. package/src/tools/tool-catalog.generated.ts +29 -2
  240. package/src/tools/tool-errors.ts +4 -0
  241. package/src/utils/git.ts +53 -3
  242. package/src/utils/markit.ts +55 -41
  243. package/src/utils/mupdf-assets.d.ts +4 -0
  244. package/src/utils/mupdf-embedded.ts +4 -0
  245. package/src/utils/mupdf.ts +129 -0
  246. package/src/utils/session-title-generation.ts +29 -0
  247. package/src/utils/title-generator.ts +46 -2
  248. package/src/web/scrapers/types.ts +24 -3
  249. package/vendor/markit-ai/LICENSE +21 -0
  250. package/vendor/markit-ai/MANIFEST.json +412 -0
  251. package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
  252. package/vendor/markit-ai/dist/commands/config.js +139 -0
  253. package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
  254. package/vendor/markit-ai/dist/commands/convert.js +113 -0
  255. package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
  256. package/vendor/markit-ai/dist/commands/formats.js +92 -0
  257. package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
  258. package/vendor/markit-ai/dist/commands/init.js +29 -0
  259. package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
  260. package/vendor/markit-ai/dist/commands/onboard.js +61 -0
  261. package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
  262. package/vendor/markit-ai/dist/commands/plugin.js +58 -0
  263. package/vendor/markit-ai/dist/config.d.ts +26 -0
  264. package/vendor/markit-ai/dist/config.js +42 -0
  265. package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
  266. package/vendor/markit-ai/dist/converters/audio.js +104 -0
  267. package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
  268. package/vendor/markit-ai/dist/converters/csv.js +83 -0
  269. package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
  270. package/vendor/markit-ai/dist/converters/docx.js +57 -0
  271. package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
  272. package/vendor/markit-ai/dist/converters/epub.js +128 -0
  273. package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
  274. package/vendor/markit-ai/dist/converters/github.js +148 -0
  275. package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
  276. package/vendor/markit-ai/dist/converters/html.js +30 -0
  277. package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
  278. package/vendor/markit-ai/dist/converters/image.js +124 -0
  279. package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
  280. package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
  281. package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
  282. package/vendor/markit-ai/dist/converters/iwork.js +391 -0
  283. package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
  284. package/vendor/markit-ai/dist/converters/json.js +21 -0
  285. package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
  286. package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
  287. package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
  288. package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
  289. package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
  290. package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
  291. package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
  292. package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
  293. package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
  294. package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
  295. package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
  296. package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
  297. package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
  298. package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
  299. package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
  300. package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
  301. package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
  302. package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
  303. package/vendor/markit-ai/dist/converters/pptx.js +289 -0
  304. package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
  305. package/vendor/markit-ai/dist/converters/rss.js +138 -0
  306. package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
  307. package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
  308. package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
  309. package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
  310. package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
  311. package/vendor/markit-ai/dist/converters/xml.js +18 -0
  312. package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
  313. package/vendor/markit-ai/dist/converters/yaml.js +17 -0
  314. package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
  315. package/vendor/markit-ai/dist/converters/zip.js +57 -0
  316. package/vendor/markit-ai/dist/index.d.ts +30 -0
  317. package/vendor/markit-ai/dist/index.js +26 -0
  318. package/vendor/markit-ai/dist/main.d.ts +2 -0
  319. package/vendor/markit-ai/dist/main.js +191 -0
  320. package/vendor/markit-ai/dist/markit.d.ts +45 -0
  321. package/vendor/markit-ai/dist/markit.js +267 -0
  322. package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
  323. package/vendor/markit-ai/dist/plugins/api.js +42 -0
  324. package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
  325. package/vendor/markit-ai/dist/plugins/index.js +3 -0
  326. package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
  327. package/vendor/markit-ai/dist/plugins/installer.js +176 -0
  328. package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
  329. package/vendor/markit-ai/dist/plugins/loader.js +61 -0
  330. package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
  331. package/vendor/markit-ai/dist/plugins/types.js +1 -0
  332. package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
  333. package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
  334. package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
  335. package/vendor/markit-ai/dist/providers/index.js +56 -0
  336. package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
  337. package/vendor/markit-ai/dist/providers/openai.js +76 -0
  338. package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
  339. package/vendor/markit-ai/dist/providers/types.js +1 -0
  340. package/vendor/markit-ai/dist/types.d.ts +36 -0
  341. package/vendor/markit-ai/dist/types.js +1 -0
  342. package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
  343. package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
  344. package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
  345. package/vendor/markit-ai/dist/utils/output.js +31 -0
  346. package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
  347. package/vendor/markit-ai/dist/utils/turndown.js +70 -0
  348. package/vendor/markit-ai/package.json +74 -0
  349. package/vendor/markit-ai.patch +49 -0
@@ -60,7 +60,11 @@ import {
60
60
  promptFailureRetryability,
61
61
  rephaseFailedOutcome,
62
62
  } from "../../sdk/prompt-failure";
63
- import type { SdkPromptTerminalOutcome } from "../../sdk/prompt-status";
63
+ import type {
64
+ SdkPromptFailureCategory,
65
+ SdkPromptFailurePhase,
66
+ SdkPromptTerminalOutcome,
67
+ } from "../../sdk/prompt-status";
64
68
  import { PromptActivity, type PromptWatchdogClock, systemPromptWatchdogClock } from "../../sdk/prompt-watchdog";
65
69
  import { validateRequiredPromptText } from "../../sdk/protocol/adapter-validation";
66
70
  import { type SessionAttachment, SessionRouter, type SessionRouterFrame } from "../../sdk/router";
@@ -74,6 +78,7 @@ import {
74
78
  } from "./acp-event-mapper";
75
79
  import { resolveAcpPermissionMode } from "./permission-mode";
76
80
  import type { AcpStartupOptions } from "./startup-options";
81
+ import { resolveStartupProvenance } from "./startup-provenance";
77
82
  import { ACP_TERMINAL_AUTH_FLAG } from "./terminal-auth";
78
83
 
79
84
  const ACP_DEFAULT_MODE_ID = "default";
@@ -159,6 +164,9 @@ const ACP_FIRST_PROMPT_RETRY_BASE_DELAY_MS = 250;
159
164
  type JsonObject = Record<string, unknown>;
160
165
  interface PromptWaiter {
161
166
  acknowledged: boolean;
167
+ invocationKind: "prompt" | "skill";
168
+ /** ACP-owned identity, never inherited from caller metadata or reused for replay. */
169
+ clientRef: string;
162
170
  /** True once turn.prompt / skill.invoke has been sent; cancel must not fake-settle after this. */
163
171
  dispatched: boolean;
164
172
  /** True only while the dispatched control request can still reveal its correlation. */
@@ -253,6 +261,8 @@ type SessionRecord = {
253
261
  /** Replayable startup notice captured before ACP bootstrap; emitted once after the session id is known. */
254
262
  routingInactiveNotice?: string;
255
263
  activePrompt?: PromptWaiter;
264
+ /** One bounded read-only recovery owner for this attachment and waiter. */
265
+ statusRecovery?: PromptWaiter;
256
266
  /** Set by `session/cancel` so an in-flight prompt settles as `cancelled`, never as an error. */
257
267
  cancelRequested?: boolean;
258
268
  /** True once the session's first logical prompt has settled; gates first-turn readiness retries. */
@@ -640,6 +650,46 @@ export class AcpPromptFailureError extends AcpSdkAdapterError {
640
650
  }
641
651
  }
642
652
 
653
+ /**
654
+ * Recover the shared prompt-failure projection for a direct SDK rejection.
655
+ *
656
+ * A terminal frame normally reaches {@link AcpPromptFailureError} through
657
+ * `#settlePrompt`, but a host may reject the `turn.prompt` control request
658
+ * itself with `code: "prompt_failed"`. That path never has a terminal outcome
659
+ * to retain, so translating the raw adapter error used to bypass
660
+ * `promptFailureWireData` and publish only the bare `code`/`details` pair.
661
+ * Keep the fallback deliberately narrow: only the two prompt terminal codes
662
+ * are upgraded, and any optional provider classifier is re-validated before
663
+ * it enters the existing classifier/projection seam.
664
+ */
665
+ function promptFailureFromDirectError(error: unknown): SdkPromptFailedOutcome | undefined {
666
+ if (error instanceof AcpPromptFailureError) return error.failure;
667
+ let code: unknown;
668
+ let providerCode: unknown;
669
+ let phase: unknown;
670
+ try {
671
+ const candidate = error as {
672
+ code?: unknown;
673
+ providerCode?: unknown;
674
+ transportFailure?: { providerCode?: unknown };
675
+ phase?: unknown;
676
+ };
677
+ code = candidate?.code;
678
+ providerCode = candidate?.providerCode ?? candidate?.transportFailure?.providerCode;
679
+ phase = candidate?.phase;
680
+ } catch {
681
+ return undefined;
682
+ }
683
+ if (code !== "prompt_failed" && code !== "prompt_deadline_exceeded") return undefined;
684
+ return failedPromptOutcome({
685
+ code,
686
+ provenance: code === "prompt_deadline_exceeded" ? "deadline" : "agent_failed",
687
+ ...(isSafePromptFailureCode(providerCode) ? { providerCode } : {}),
688
+ ...(isSdkPromptFailurePhase(phase) ? { phase } : {}),
689
+ evidence: {},
690
+ });
691
+ }
692
+
643
693
  /**
644
694
  * The wire projection of a prompt terminal's classification (issue #5615).
645
695
  *
@@ -655,12 +705,71 @@ export class AcpPromptFailureError extends AcpSdkAdapterError {
655
705
  * dropped instead of becoming a leak of provider text (the redaction contract in
656
706
  * `sanitizePromptFailure`). A dropped or absent code is omitted, never nulled.
657
707
  */
708
+ export const OPERATOR_UPSTREAM_LABEL = "Upstream provider failure";
709
+ export const OPERATOR_UPSTREAM_SUFFIX = ": the model provider ended this turn.";
710
+ /**
711
+ * `post_start` proves execution began, so tools may already have run and had effects before the
712
+ * provider died. The wording therefore qualifies the turn rather than absolving the task: saying
713
+ * the task was unaffected would assert something the bounded classifiers do not establish.
714
+ */
715
+ export const OPERATOR_PARTIAL_WORK_NOTE =
716
+ " Work this turn had already performed may have taken effect; check before retrying.";
717
+
718
+ /**
719
+ * Operator-facing wording for a post-start terminal, assembled ONLY from bounded safe tokens: the
720
+ * classifier `phase`, the `category`, and a `providerCode` that passes `isSafePromptFailureCode`.
721
+ * Raw provider text never reaches this function and must never reach it.
722
+ *
723
+ * Gated on BOTH `phase === "post_start"` and `category === "provider_transport"`. A submission-phase
724
+ * provider failure is rejected by the phase gate: nothing executed, so wording about a turn the
725
+ * provider ended would describe an event that never occurred.
726
+ *
727
+ * It claims only what those tokens prove — that the upstream provider ended the turn — and says
728
+ * nothing about whether the operator's work survived. `post_start` proves execution began, so the
729
+ * wording qualifies that earlier work may already have taken effect instead of absolving the task.
730
+ *
731
+ * This is additive. The wire `code`/`details` pair and every `PROMPT_FAILURE_MESSAGE_*` constant
732
+ * stay exactly as they are — pinned ACP core-v1 conformance asserts on them — so this wording
733
+ * travels alongside the redacted message instead of repurposing it.
734
+ *
735
+ * It says NOTHING about the worktree, deliberately. Nothing on this path inspects the worktree, so
736
+ * any sentence about uncommitted work would assert a check that never ran — reporting an
737
+ * uninspected tree as clean is the absence-of-evidence-as-evidence-of-absence error, and an
738
+ * operator who believed it would sweep work away. Silence is the only honest option until something
739
+ * actually looks.
740
+ *
741
+ * Returns `undefined` for every other category: `phase` and `category` are already on the wire, so
742
+ * restating them in prose tells an operator nothing they do not already have.
743
+ */
744
+ export function postStartOperatorMessage(input: {
745
+ phase: SdkPromptFailurePhase;
746
+ category: SdkPromptFailureCategory;
747
+ providerCode?: string;
748
+ }): string | undefined {
749
+ // Phase first: a submission-phase provider failure never started executing, so wording about a
750
+ // turn that ended mid-flight would describe something that did not happen. `phase` is a bounded
751
+ // classifier already on the wire, so gating on it widens nothing.
752
+ if (input.phase !== "post_start") return undefined;
753
+ if (input.category !== "provider_transport") return undefined;
754
+ const code = isSafePromptFailureCode(input.providerCode) ? ` (${input.providerCode})` : "";
755
+ return `${OPERATOR_UPSTREAM_LABEL}${code}${OPERATOR_UPSTREAM_SUFFIX}${OPERATOR_PARTIAL_WORK_NOTE}`;
756
+ }
757
+
658
758
  function promptFailureWireData(failure: SdkPromptFailedOutcome): Record<string, string> {
759
+ // `operatorMessage` says, in words, what the classifier tokens already say in codes: that the
760
+ // cause was an upstream provider problem rather than a failure of the operator's task. Built
761
+ // only from those same bounded tokens, so it widens nothing that reaches the wire.
762
+ const operatorMessage = postStartOperatorMessage({
763
+ phase: failure.phase,
764
+ category: failure.category,
765
+ ...(failure.providerCode === undefined ? {} : { providerCode: failure.providerCode }),
766
+ });
659
767
  return {
660
768
  phase: failure.phase,
661
769
  category: failure.category,
662
770
  retryability: promptFailureRetryability(failure.category),
663
771
  ...(isSafePromptFailureCode(failure.providerCode) ? { providerCode: failure.providerCode } : {}),
772
+ ...(operatorMessage === undefined ? {} : { operatorMessage }),
664
773
  };
665
774
  }
666
775
 
@@ -1366,9 +1475,10 @@ export function acpRequestFailure(error: unknown): unknown {
1366
1475
  // spread is empty for every other error, so their payloads are byte-identical to
1367
1476
  // before. `code`/`details` and the JSON-RPC code itself are untouched either way:
1368
1477
  // this enriches `data`, it does not restate the redacted message.
1478
+ const promptFailure = promptFailureFromDirectError(error);
1369
1479
  const data =
1370
- error instanceof AcpPromptFailureError
1371
- ? { code, details: message, ...promptFailureWireData(error.failure) }
1480
+ promptFailure !== undefined
1481
+ ? { code, details: message, ...promptFailureWireData(promptFailure) }
1372
1482
  : { code, details: message };
1373
1483
  // An abandoned prompt additionally publishes the plan it never finished (issue #5669).
1374
1484
  // Same rule as above: `code`/`details` and the JSON-RPC code are untouched, and an abandon
@@ -1510,7 +1620,7 @@ export class AcpAgent implements Agent {
1510
1620
  /** Retain settled prompt identities across automatic transport reattachment. */
1511
1621
  readonly #retiredPromptCorrelations = new Map<string, PromptCorrelation[]>();
1512
1622
  /** Prevent a successor admission while a retired prompt can still reveal its identity. */
1513
- readonly #retiredPromptAcknowledgements = new Set<string>();
1623
+ readonly #retiredPromptAcknowledgements = new Map<string, PromptWaiter>();
1514
1624
  /** Ready terminal metadata writes retain ownership across same-id record replacement. */
1515
1625
  readonly #terminalMetadataTails = new Map<string, Promise<void>>();
1516
1626
  /** Unstreamed terminal text retains transcript ownership across same-id replacement. */
@@ -2112,6 +2222,7 @@ export class AcpAgent implements Agent {
2112
2222
 
2113
2223
  const payload = acpPromptPayload(params.prompt);
2114
2224
  const skillInvocation = acpSkillInvocation(params.prompt);
2225
+ const clientRef = randomUUID();
2115
2226
  if (!skillInvocation) {
2116
2227
  const promptError = validateRequiredPromptText("turn.prompt", {
2117
2228
  text: payload.text,
@@ -2146,7 +2257,7 @@ export class AcpAgent implements Agent {
2146
2257
  JSON.stringify({
2147
2258
  type: "control_request",
2148
2259
  operation: skillInvocation ? "skill.invoke" : "turn.prompt",
2149
- input: skillInvocation ?? { text: payload.text, images: payload.images },
2260
+ input: { ...(skillInvocation ?? { text: payload.text, images: payload.images }), clientRef },
2150
2261
  // AcpSdkAdapter.control passes `confirm: false` to SdkClient, which serializes
2151
2262
  // the field even though it is not part of the skill input payload.
2152
2263
  ...(skillInvocation ? { confirm: false } : {}),
@@ -2168,6 +2279,8 @@ export class AcpAgent implements Agent {
2168
2279
  record.promptObservedAssistantOutput = false;
2169
2280
  const { promise: response, resolve, reject } = Promise.withResolvers<PromptResponse>();
2170
2281
  const waiter: PromptWaiter = {
2282
+ invocationKind: skillInvocation ? "skill" : "prompt",
2283
+ clientRef,
2171
2284
  acknowledged: false,
2172
2285
  dispatched: false,
2173
2286
  acknowledgementPending: false,
@@ -2212,11 +2325,11 @@ export class AcpAgent implements Agent {
2212
2325
  } catch (error) {
2213
2326
  waiter.awaitingProviderPreflight = false;
2214
2327
  if (waiter.settled || record.activePrompt !== waiter) {
2215
- this.#retiredPromptAcknowledgements.delete(params.sessionId);
2328
+ this.#releaseRetiredPromptAcknowledgement(params.sessionId, waiter);
2216
2329
  return await response;
2217
2330
  }
2218
2331
  if (record.cancelRequested && waiter.cancelAcknowledged) {
2219
- this.#retiredPromptAcknowledgements.delete(params.sessionId);
2332
+ this.#releaseRetiredPromptAcknowledgement(params.sessionId, waiter);
2220
2333
  await this.#settleCancelledPrompt(params.sessionId, record, waiter);
2221
2334
  return await response;
2222
2335
  }
@@ -2224,18 +2337,22 @@ export class AcpAgent implements Agent {
2224
2337
  record.activePrompt = undefined;
2225
2338
  record.busy = record.backgroundBusy;
2226
2339
  clearPromptWatchdog(waiter);
2227
- this.#retiredPromptAcknowledgements.delete(params.sessionId);
2340
+ // #5526's guarded release replaces the unguarded delete: it only clears
2341
+ // the entry when it still belongs to THIS waiter, so a successor's
2342
+ // acknowledgement is never dropped. Strictly narrower than the delete
2343
+ // it supersedes; dev's watchdog clear above is unaffected.
2344
+ this.#releaseRetiredPromptAcknowledgement(params.sessionId, waiter);
2228
2345
  void this.#publishPromptPhaseIdle(params.sessionId, record.adapter);
2229
2346
  }
2230
2347
  throw error;
2231
2348
  }
2232
2349
  waiter.awaitingProviderPreflight = false;
2233
2350
  if (waiter.settled || record.activePrompt !== waiter) {
2234
- this.#retiredPromptAcknowledgements.delete(params.sessionId);
2351
+ this.#releaseRetiredPromptAcknowledgement(params.sessionId, waiter);
2235
2352
  return await response;
2236
2353
  }
2237
2354
  if (record.cancelRequested && waiter.cancelAcknowledged) {
2238
- this.#retiredPromptAcknowledgements.delete(params.sessionId);
2355
+ this.#releaseRetiredPromptAcknowledgement(params.sessionId, waiter);
2239
2356
  await this.#settleCancelledPrompt(params.sessionId, record, waiter);
2240
2357
  return await response;
2241
2358
  }
@@ -2278,14 +2395,18 @@ export class AcpAgent implements Agent {
2278
2395
  }
2279
2396
 
2280
2397
  waiter.acknowledgementPending = true;
2398
+ const promptAdapter = record.adapter;
2281
2399
  const acknowledgementTask = (async (): Promise<PromptResponse> => {
2282
2400
  if (waiter.settled || record.activePrompt !== waiter) return await response;
2283
2401
  const acknowledgement = skillInvocation
2284
- ? await record.adapter.control("skill.invoke", skillInvocation)
2285
- : await record.adapter.prompt({
2402
+ ? await promptAdapter.control("skill.invoke", { ...skillInvocation, clientRef })
2403
+ : await promptAdapter.prompt({
2286
2404
  text: payload.text,
2405
+ clientRef,
2287
2406
  ...(payload.images.length ? { images: payload.images } : {}),
2288
2407
  });
2408
+ // A recovered waiter already owns its exact identity; a late ack cannot rebind it.
2409
+ if (waiter.settled && waiter.acknowledged) return await response;
2289
2410
 
2290
2411
  const acknowledgementCorrelation = promptAcknowledgement(acknowledgement);
2291
2412
  if (!acknowledgementCorrelation)
@@ -2293,6 +2414,14 @@ export class AcpAgent implements Agent {
2293
2414
  "invalid_prompt_acknowledgement",
2294
2415
  "SDK prompt acknowledgement must accept the prompt and include commandId and turnId.",
2295
2416
  );
2417
+ if (
2418
+ this.#sessions.get(params.sessionId) !== record ||
2419
+ record.adapter !== promptAdapter ||
2420
+ record.activePrompt !== waiter
2421
+ ) {
2422
+ this.#rememberSettledPromptCorrelation(params.sessionId, record, acknowledgementCorrelation);
2423
+ return await response;
2424
+ }
2296
2425
  if (this.#hasRetiredPromptCorrelation(params.sessionId, record, acknowledgementCorrelation)) {
2297
2426
  try {
2298
2427
  const abortAcknowledgement = await record.adapter.cancel("turn");
@@ -2427,10 +2556,20 @@ export class AcpAgent implements Agent {
2427
2556
  }
2428
2557
  this.#settlePrompt(params.sessionId, record, waiter);
2429
2558
  return await response;
2430
- })().finally(() => {
2431
- waiter.acknowledgementPending = false;
2432
- this.#retiredPromptAcknowledgements.delete(params.sessionId);
2433
- });
2559
+ })()
2560
+ .catch(async error => {
2561
+ if (
2562
+ !(error instanceof SdkClientError || error instanceof AcpSdkAdapterError) ||
2563
+ error.code !== "uncertain_after_send"
2564
+ )
2565
+ throw error;
2566
+ if (record.adapter === promptAdapter) this.#startUncertainPromptRecovery(params.sessionId, record, waiter);
2567
+ return await response;
2568
+ })
2569
+ .finally(() => {
2570
+ waiter.acknowledgementPending = false;
2571
+ this.#releaseRetiredPromptAcknowledgement(params.sessionId, waiter);
2572
+ });
2434
2573
  // Settlement can win before the SDK answers `turn.prompt`; acknowledgement processing
2435
2574
  // must remain alive to tombstone its eventual correlation without holding the ACP caller.
2436
2575
  void acknowledgementTask.catch(() => undefined);
@@ -2889,24 +3028,23 @@ export class AcpAgent implements Agent {
2889
3028
  // a broker-launched SDK host keeps ACP ownership across reconnects.
2890
3029
  await adapter.start({ activateProviders: false });
2891
3030
  let capabilities: JsonObject | undefined;
3031
+ // A query that never answered and a host that answered without provenance are
3032
+ // different operator problems, so the failure is retained instead of discarded.
3033
+ let capabilitiesFailure: unknown;
2892
3034
  try {
2893
3035
  const response = object(await adapter.query("runtime.capabilities"));
2894
3036
  const result = object(response?.result) ?? response;
2895
3037
  // Q18 is a paged query surface: the capability object arrives as the single
2896
3038
  // page item, so fall back to the envelope only for direct-result hosts.
2897
3039
  capabilities = object(pageItems(result)[0]) ?? result;
2898
- } catch {}
2899
- const primaryControlSurface =
2900
- capabilities?.primaryControlSurface === "sdk"
2901
- ? "sdk"
2902
- : capabilities?.primaryControlSurface === "cli"
2903
- ? "cli"
2904
- : undefined;
2905
- if (capabilities?.promptTerminalOutcomeVersion !== 1 || primaryControlSurface === undefined)
2906
- throw new AcpSdkAdapterError(
2907
- "unavailable",
2908
- "This ACP client requires a newer GJC SDK session with startup control provenance; restart the session.",
2909
- );
3040
+ } catch (error) {
3041
+ capabilitiesFailure = error;
3042
+ }
3043
+ const primaryControlSurface = resolveStartupProvenance({
3044
+ sessionId: id,
3045
+ capabilities,
3046
+ queryFailure: capabilitiesFailure,
3047
+ });
2910
3048
  if (primaryControlSurface === "sdk") {
2911
3049
  adapter.authorizeProviderActivation();
2912
3050
  await adapter.ensureProviders();
@@ -3017,7 +3155,11 @@ export class AcpAgent implements Agent {
3017
3155
 
3018
3156
  #fenceRetiredPromptAcknowledgement(id: string, waiter: PromptWaiter): void {
3019
3157
  if (waiter.dispatched && waiter.acknowledgementPending && !waiter.acknowledged)
3020
- this.#retiredPromptAcknowledgements.add(id);
3158
+ this.#retiredPromptAcknowledgements.set(id, waiter);
3159
+ }
3160
+
3161
+ #releaseRetiredPromptAcknowledgement(id: string, waiter: PromptWaiter): void {
3162
+ if (this.#retiredPromptAcknowledgements.get(id) === waiter) this.#retiredPromptAcknowledgements.delete(id);
3021
3163
  }
3022
3164
 
3023
3165
  #advanceTerminalGeneration(record: SessionRecord): void {
@@ -3027,6 +3169,15 @@ export class AcpAgent implements Agent {
3027
3169
  #recoverSessionAfterTransportFailure(id: string, adapter: AcpSdkAdapter, error: Error): void {
3028
3170
  const record = this.#sessions.get(id);
3029
3171
  if (!record || record.adapter !== adapter) return;
3172
+ if (error instanceof SdkClientError && error.code === "uncertain_after_send") {
3173
+ // Provider-registration requests share this transport error. Recover only when the
3174
+ // prompt's own dispatched acknowledgement is still pending; an acknowledged turn
3175
+ // must continue until its terminal frame arrives.
3176
+ const waiter = record.activePrompt;
3177
+ if (waiter?.dispatched && waiter.acknowledgementPending && !waiter.acknowledged)
3178
+ this.#startUncertainPromptRecovery(id, record, waiter);
3179
+ return;
3180
+ }
3030
3181
  if (
3031
3182
  error instanceof SdkClientError &&
3032
3183
  error.code !== "reconnect_exhausted" &&
@@ -3041,11 +3192,191 @@ export class AcpAgent implements Agent {
3041
3192
  logger.warn(`ACP session ${id} non-transport error reached reconnect handler; ignoring terminal recovery.`);
3042
3193
  return;
3043
3194
  }
3195
+ const waiter = record.activePrompt;
3196
+ if (waiter?.dispatched) {
3197
+ // A prior uncertainty notification already owns recovery for this waiter. Keep
3198
+ // the in-flight lookup alive; only declined, non-recovering waiters fall through
3199
+ // to the ordinary transport-loss teardown and reattachment path.
3200
+ if (record.statusRecovery === waiter || this.#startUncertainPromptRecovery(id, record, waiter)) return;
3201
+ }
3044
3202
  const detail = error.message || "SDK transport reconnect failed.";
3045
3203
  const terminal = new AcpSdkAdapterError("connection_closed", `ACP session transport was lost: ${detail}`);
3046
3204
  void this.#recoverSessionAfterTransportFailureAsync(id, adapter, record.cwd, terminal);
3047
3205
  }
3048
3206
 
3207
+ #startUncertainPromptRecovery(id: string, record: SessionRecord, waiter: PromptWaiter): boolean {
3208
+ if (
3209
+ this.#sessions.get(id) !== record ||
3210
+ promptWaiterRetired(record, waiter) ||
3211
+ !waiter.dispatched ||
3212
+ waiter.terminalReserved ||
3213
+ record.statusRecovery === waiter
3214
+ )
3215
+ return false;
3216
+ record.statusRecovery = waiter;
3217
+ clearPromptWatchdog(waiter);
3218
+ void this.#recoverUncertainPrompt(id, record, record.adapter, waiter);
3219
+ return true;
3220
+ }
3221
+
3222
+ async #recoverUncertainPrompt(
3223
+ id: string,
3224
+ record: SessionRecord,
3225
+ adapter: AcpSdkAdapter,
3226
+ waiter: PromptWaiter,
3227
+ ): Promise<void> {
3228
+ const ownsRecovery = (): boolean =>
3229
+ this.#sessions.get(id) === record &&
3230
+ record.adapter === adapter &&
3231
+ record.statusRecovery === waiter &&
3232
+ !promptWaiterRetired(record, waiter) &&
3233
+ !waiter.terminalReserved;
3234
+ // Keep the lookup selector's authority even if an ack arrives during the read.
3235
+ const acknowledgedAtLookup = waiter.acknowledged;
3236
+ let detail = "status query unavailable";
3237
+ let timer: NodeJS.Timeout | undefined;
3238
+ let timedOut = false;
3239
+ try {
3240
+ const deadline = Promise.withResolvers<never>();
3241
+ timer = setTimeout(() => {
3242
+ timedOut = true;
3243
+ deadline.reject(new Error("status query timeout"));
3244
+ }, 5_000);
3245
+ const refreshAttachment = async (): Promise<void> => {
3246
+ if (this.#sessions.get(id) !== record || record.adapter !== adapter) return;
3247
+ // A reconnect can revoke the adapter's opaque attachment before Router has
3248
+ // published its replacement. Force an authority pass first, then hand the
3249
+ // exact current capability back to the adapter before issuing the read-only
3250
+ // lookup. This pass does not replay the prompt mutation.
3251
+ await this.#router.reconcile({ waitForReplay: false });
3252
+ if (!ownsRecovery()) return;
3253
+ const refreshed = this.#router.attachment(id);
3254
+ if (!refreshed?.isCurrent())
3255
+ throw new AcpSdkAdapterError(
3256
+ "unavailable",
3257
+ "ACP session has no current Router attachment for recovery.",
3258
+ );
3259
+ if (refreshed !== record.attachment || !record.attachment.isCurrent()) {
3260
+ await adapter.attachmentReady(refreshed);
3261
+ record.attachment = refreshed;
3262
+ }
3263
+ };
3264
+ await Promise.race([refreshAttachment(), deadline.promise]);
3265
+ if (!ownsRecovery()) return;
3266
+ // Bound observation only: the mutation and the read are never replayed or aborted.
3267
+ const response = object(
3268
+ await Promise.race([
3269
+ adapter.query("turn.result", {
3270
+ kind: waiter.invocationKind,
3271
+ ...(acknowledgedAtLookup ? waiter.correlation : { clientRef: waiter.clientRef }),
3272
+ }),
3273
+ deadline.promise,
3274
+ ]),
3275
+ );
3276
+ if (!ownsRecovery()) return;
3277
+ const status = object(response?.result) ?? response;
3278
+ const correlation = strictCorrelationFrom(response, status);
3279
+ const outcome = status ? terminalOutcome(status) : undefined;
3280
+ const content = object(status?.content);
3281
+ const readableText =
3282
+ content?.version === 1 &&
3283
+ content.type === "text" &&
3284
+ typeof content.text === "string" &&
3285
+ content.text.trim().length > 0;
3286
+ const failure = object(status?.error);
3287
+ const conflictingIdentity = [response, status].some(
3288
+ value =>
3289
+ (value?.kind !== undefined && value.kind !== waiter.invocationKind) ||
3290
+ (value?.sessionId !== undefined && value.sessionId !== id) ||
3291
+ (value?.clientRef !== undefined && value.clientRef !== waiter.clientRef),
3292
+ );
3293
+ if (
3294
+ status?.kind !== waiter.invocationKind ||
3295
+ conflictingIdentity ||
3296
+ (!acknowledgedAtLookup && status.clientRef !== waiter.clientRef) ||
3297
+ !correlation ||
3298
+ !hasCompleteCorrelation(correlation) ||
3299
+ (waiter.acknowledged && !correlationsExactlyMatch(waiter.correlation, correlation)) ||
3300
+ this.#hasRetiredPromptCorrelation(id, record, correlation)
3301
+ ) {
3302
+ detail = "status correlation is missing, retired, or mismatched";
3303
+ } else if (
3304
+ status.status === "failed" &&
3305
+ status.outcome === undefined &&
3306
+ (status.receiptState === "present" ||
3307
+ status.receiptState === "missing" ||
3308
+ status.receiptState === "unknown") &&
3309
+ typeof failure?.code === "string" &&
3310
+ /^[a-zA-Z0-9_.-]{1,64}$/.test(failure.code) &&
3311
+ typeof failure.message === "string" &&
3312
+ failure.message.trim().length > 0 &&
3313
+ failure.message.length <= 512
3314
+ ) {
3315
+ waiter.correlation = correlation;
3316
+ waiter.acknowledged = true;
3317
+ record.busy = record.backgroundBusy;
3318
+ this.#advanceTerminalGeneration(record);
3319
+ void this.#rejectPrompt(record, id, waiter, new AcpSdkAdapterError(failure.code, failure.message));
3320
+ return;
3321
+ } else if (
3322
+ (status.status === "terminal_ok" &&
3323
+ outcome?.kind === "stopped" &&
3324
+ (status.receiptState === "present" || status.receiptState === "missing") &&
3325
+ (outcome.reason !== "end_turn" || (status.receiptState === "present" && readableText))) ||
3326
+ (status.status === "failed" &&
3327
+ outcome?.kind === "failed" &&
3328
+ (status.receiptState === "present" ||
3329
+ status.receiptState === "missing" ||
3330
+ status.receiptState === "unknown"))
3331
+ ) {
3332
+ waiter.correlation = correlation;
3333
+ waiter.acknowledged = true;
3334
+ record.busy = record.backgroundBusy;
3335
+ this.#advanceTerminalGeneration(record);
3336
+ const settledOutcome =
3337
+ record.cancelRequested && outcome.kind === "stopped" && outcome.reason === "end_turn"
3338
+ ? { ...outcome, reason: "cancelled" as const, provenance: "client_cancel" as const }
3339
+ : outcome;
3340
+ const publicationBarrier = record.frameTail;
3341
+ waiter.terminal = { outcome: settledOutcome, correlation };
3342
+ // Recovered final text is published on the async terminal tail below. Record it
3343
+ // before settling so a first-turn startup-readiness failure cannot be retried
3344
+ // after this retained answer has already been committed for publication.
3345
+ if (readableText) record.promptObservedAssistantOutput = true;
3346
+ this.#settlePrompt(id, record, waiter);
3347
+ this.#scheduleTerminalUpdates(
3348
+ id,
3349
+ adapter,
3350
+ record.publicationGeneration,
3351
+ {
3352
+ type: settledOutcome.kind === "failed" ? "agent_failed" : "agent_end",
3353
+ outcome: settledOutcome,
3354
+ finalText: readableText ? content.text : undefined,
3355
+ finalTextTruncated: content?.truncated === true,
3356
+ },
3357
+ waiter,
3358
+ publicationBarrier,
3359
+ );
3360
+ return;
3361
+ } else {
3362
+ detail = "status has no usable retained terminal outcome and receipt";
3363
+ }
3364
+ } catch {
3365
+ detail = timedOut ? "status query timed out after 5000ms" : "status query unavailable";
3366
+ } finally {
3367
+ if (timer !== undefined) clearTimeout(timer);
3368
+ }
3369
+ if (!ownsRecovery()) return;
3370
+ await this.#failSession(
3371
+ id,
3372
+ adapter,
3373
+ new AcpSdkAdapterError(
3374
+ "terminal_uncertain",
3375
+ `ACP prompt outcome remains uncertain: ${detail}. No mutation was replayed. Inspect turn.result for kind=${waiter.invocationKind}, clientRef=${waiter.clientRef}, commandId=${waiter.correlation.commandId ?? "unknown"}, turnId=${waiter.correlation.turnId ?? "unknown"} before submitting more work.`,
3376
+ ),
3377
+ );
3378
+ }
3379
+
3049
3380
  async #recoverSessionAfterTransportFailureAsync(
3050
3381
  id: string,
3051
3382
  adapter: AcpSdkAdapter,
@@ -3093,13 +3424,15 @@ export class AcpAgent implements Agent {
3093
3424
  if (attaching) await Promise.allSettled([attaching.task]);
3094
3425
  // A CLI-primary host owns its own process lifecycle: release this ACP
3095
3426
  // attachment locally and never ask the broker to close the session.
3096
- await this.#teardownSession(id, "closed", this.#ownedSessionIds.has(id));
3427
+ const closesHost = this.#ownedSessionIds.has(id);
3428
+ await this.#teardownSession(id, "closed", closesHost);
3097
3429
  this.#knownSessionCwds.delete(id);
3098
3430
  this.#ownedSessionIds.delete(id);
3099
3431
  this.#knownSessionMcpServers.delete(id);
3100
3432
  this.#knownSessionMetadata.delete(id);
3101
3433
  this.#retiredPromptCorrelations.delete(id);
3102
- this.#retiredPromptAcknowledgements.delete(id);
3434
+ // Detaching locally cannot retire an outstanding host acknowledgement.
3435
+ if (closesHost) this.#retiredPromptAcknowledgements.delete(id);
3103
3436
  return {};
3104
3437
  } finally {
3105
3438
  this.#finishTeardown(id);
@@ -3337,7 +3670,7 @@ export class AcpAgent implements Agent {
3337
3670
  * running forever behind a dead session.
3338
3671
  */
3339
3672
  #armPromptWatchdog(id: string, record: SessionRecord, waiter: PromptWaiter): void {
3340
- if (waiter.settled) return;
3673
+ if (waiter.settled || record.statusRecovery === waiter) return;
3341
3674
  waiter.cancelWatchdog?.();
3342
3675
  // No frame can still arrive once the transport is known to be gone, so waiting out
3343
3676
  // the full bound would only add dead time to an outcome that is already decided.
@@ -3400,7 +3733,13 @@ export class AcpAgent implements Agent {
3400
3733
  * turn can no longer be observed, it does not cancel or tear down the session.
3401
3734
  */
3402
3735
  async #expirePromptWatchdog(id: string, record: SessionRecord, waiter: PromptWaiter): Promise<void> {
3403
- if (record.activePrompt !== waiter || waiter.settled || waiter.terminalReserved) return;
3736
+ if (
3737
+ record.activePrompt !== waiter ||
3738
+ waiter.settled ||
3739
+ waiter.terminalReserved ||
3740
+ record.statusRecovery === waiter
3741
+ )
3742
+ return;
3404
3743
  const silenceMs = Math.max(0, this.#promptWatchdogClock.now() - waiter.lastFrameAt);
3405
3744
  // A prompt still in preflight has never reached the host, so "stopped producing frames"
3406
3745
  // would point the reader at the wrong phase: nothing was ever dispatched to produce them.
@@ -3506,6 +3845,18 @@ export class AcpAgent implements Agent {
3506
3845
  await this.#settleCancelledPrompt(id, record, waiter);
3507
3846
  return;
3508
3847
  }
3848
+ if (waiter.terminalReserved) return;
3849
+ if (record.statusRecovery === waiter || (waiter.dispatched && !waiter.acknowledged)) {
3850
+ await this.#failSession(
3851
+ id,
3852
+ adapter,
3853
+ new AcpSdkAdapterError(
3854
+ "terminal_uncertain",
3855
+ `Prompt owner connection changed before retained evidence was resolved. No mutation was replayed. Inspect turn.result for kind=${waiter.invocationKind}, clientRef=${waiter.clientRef}.`,
3856
+ ),
3857
+ );
3858
+ return;
3859
+ }
3509
3860
  record.activePrompt = undefined;
3510
3861
  record.busy = record.backgroundBusy;
3511
3862
  clearPromptWatchdog(waiter);
@@ -3796,13 +4147,15 @@ export class AcpAgent implements Agent {
3796
4147
  publicationGeneration: number,
3797
4148
  event: JsonObject,
3798
4149
  promptOwner: PromptWaiter | undefined,
4150
+ publicationBarrier?: Promise<void>,
3799
4151
  ): void {
3800
4152
  const failedTerminal = event.type === "agent_failed";
3801
4153
  if (promptOwner) this.#flushFailureDiagnostics(id, promptOwner, adapter);
3802
- let decorationStart = Promise.resolve();
4154
+ let decorationStart = publicationBarrier ?? Promise.resolve();
3803
4155
  const finalText = typeof event.finalText === "string" ? event.finalText : "";
3804
4156
  if (promptOwner && hasAcpFinalTextContent(finalText)) {
3805
4157
  const finalTextTask = (async () => {
4158
+ if (publicationBarrier) await publicationBarrier;
3806
4159
  await Bun.sleep(0);
3807
4160
  const resolution = resolveAcpFinalText(promptOwner.emittedAssistantText, finalText);
3808
4161
  if (resolution.kind === "emit") {
@@ -3814,7 +4167,9 @@ export class AcpAgent implements Agent {
3814
4167
  update: {
3815
4168
  sessionUpdate: "agent_message_chunk",
3816
4169
  content: { type: "text", text: resolution.text },
3817
- ...(resolution.final.truncated ? { _meta: { gjcFinalTextTruncated: true } } : {}),
4170
+ ...(resolution.final.truncated || event.finalTextTruncated === true
4171
+ ? { _meta: { gjcFinalTextTruncated: true } }
4172
+ : {}),
3818
4173
  },
3819
4174
  },
3820
4175
  adapter,
@@ -3829,6 +4184,24 @@ export class AcpAgent implements Agent {
3829
4184
  finalLength: resolution.final.text.length,
3830
4185
  });
3831
4186
  }
4187
+ if (resolution.kind !== "emit" && event.finalTextTruncated === true) {
4188
+ // A retained prefix may already equal the streamed text, leaving no suffix for
4189
+ // `resolveAcpFinalText` to emit. Still publish a metadata-only chunk so the
4190
+ // caller can distinguish an incomplete retained result from a complete answer.
4191
+ await this.#publishSessionUpdate(
4192
+ id,
4193
+ {
4194
+ sessionId: id,
4195
+ update: {
4196
+ sessionUpdate: "agent_message_chunk",
4197
+ content: { type: "text", text: "" },
4198
+ _meta: { gjcFinalTextTruncated: true },
4199
+ },
4200
+ },
4201
+ adapter,
4202
+ publicationGeneration,
4203
+ );
4204
+ }
3832
4205
  })();
3833
4206
  let finalTextTail: Promise<void>;
3834
4207
  finalTextTail = finalTextTask