@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
@@ -43,6 +43,13 @@ export interface MCPToolOptions {
43
43
  * owning manager/runtime. Resolved per call so late registration still applies.
44
44
  */
45
45
  inputHandler?: () => MCPInputRequestHandler | undefined;
46
+ /**
47
+ * Exact MCP session control: false while the owning manager has this server
48
+ * suspended. Checked alongside `#sessionControlInvalidated` so a wrapper that
49
+ * outlives a resume/reconnect still fails locally instead of reaching the
50
+ * transport for a server this session no longer has active.
51
+ */
52
+ isAvailable?: () => boolean;
46
53
  }
47
54
 
48
55
  function connectionForTool(target: MCPToolTarget): MCPServerConnection {
@@ -235,6 +242,7 @@ export function parseMCPToolName(name: string): { serverName: string; toolName:
235
242
  * CustomTool wrapping an MCP tool with an active connection.
236
243
  */
237
244
  export class MCPTool implements CustomTool<TSchema, MCPToolDetails> {
245
+ // Exact MCP stale wrappers fail before transport RPC.
238
246
  readonly name: string;
239
247
  readonly label: string;
240
248
  readonly description: string;
@@ -247,6 +255,14 @@ export class MCPTool implements CustomTool<TSchema, MCPToolDetails> {
247
255
  private connection: MCPServerConnection;
248
256
  #noReplay = false;
249
257
  #inputHandlerResolver: (() => MCPInputRequestHandler | undefined) | undefined;
258
+ #isAvailable: (() => boolean) | undefined;
259
+ #sessionControlInvalidated = false;
260
+ invalidateForSessionControl(): void {
261
+ this.#sessionControlInvalidated = true;
262
+ }
263
+ #isAvailableForSessionControl(): boolean {
264
+ return !this.#sessionControlInvalidated && this.#isAvailable?.() !== false;
265
+ }
250
266
 
251
267
  #callOptions(signal?: AbortSignal): MCPRequestOptions {
252
268
  const inputHandler = this.#inputHandlerResolver?.();
@@ -272,6 +288,7 @@ export class MCPTool implements CustomTool<TSchema, MCPToolDetails> {
272
288
  this.connection = resolvedConnection;
273
289
  this.#noReplay = options?.noReplay === true;
274
290
  this.#inputHandlerResolver = options?.inputHandler;
291
+ this.#isAvailable = options?.isAvailable;
275
292
  this.name = createMCPToolName(resolvedConnection.name, tool.name);
276
293
  this.label = `${resolvedConnection.name}/${tool.name}`;
277
294
  this.description = tool.description ?? `MCP tool from ${resolvedConnection.name}`;
@@ -299,6 +316,15 @@ export class MCPTool implements CustomTool<TSchema, MCPToolDetails> {
299
316
  const args = normalizeToolArgs(params);
300
317
  const provider = this.connection._source?.provider;
301
318
  const providerName = this.connection._source?.providerName;
319
+ if (!this.#isAvailableForSessionControl()) {
320
+ return buildErrorResult(
321
+ new Error("MCP server is suspended for this session"),
322
+ this.connection.name,
323
+ this.tool.name,
324
+ provider,
325
+ providerName,
326
+ );
327
+ }
302
328
 
303
329
  try {
304
330
  const result = await callTool(this.connection, this.tool.name, args, this.#callOptions(signal));
@@ -308,6 +334,15 @@ export class MCPTool implements CustomTool<TSchema, MCPToolDetails> {
308
334
  if (this.reconnect && isRetriableConnectionError(error)) {
309
335
  const newConn = await reconnectWithAbort(this.reconnect, signal);
310
336
  if (newConn) {
337
+ if (!this.#isAvailableForSessionControl()) {
338
+ return buildErrorResult(
339
+ new Error("MCP server is suspended for this session"),
340
+ this.connection.name,
341
+ this.tool.name,
342
+ provider,
343
+ providerName,
344
+ );
345
+ }
311
346
  if (this.#noReplay)
312
347
  return buildErrorResult(error, this.connection.name, this.tool.name, provider, providerName);
313
348
  // Rebind so subsequent calls on this instance use the fresh connection
@@ -350,6 +385,14 @@ export class DeferredMCPTool implements CustomTool<TSchema, MCPToolDetails> {
350
385
  readonly #fallbackProviderName: string | undefined;
351
386
  #noReplay = false;
352
387
  #inputHandlerResolver: (() => MCPInputRequestHandler | undefined) | undefined;
388
+ #isAvailable: (() => boolean) | undefined;
389
+ #sessionControlInvalidated = false;
390
+ invalidateForSessionControl(): void {
391
+ this.#sessionControlInvalidated = true;
392
+ }
393
+ #isAvailableForSessionControl(): boolean {
394
+ return !this.#sessionControlInvalidated && this.#isAvailable?.() !== false;
395
+ }
353
396
 
354
397
  #callOptions(signal?: AbortSignal): MCPRequestOptions {
355
398
  const inputHandler = this.#inputHandlerResolver?.();
@@ -386,6 +429,7 @@ export class DeferredMCPTool implements CustomTool<TSchema, MCPToolDetails> {
386
429
  this.#fallbackProviderName = source?.providerName;
387
430
  this.#noReplay = options?.noReplay === true;
388
431
  this.#inputHandlerResolver = options?.inputHandler;
432
+ this.#isAvailable = options?.isAvailable;
389
433
  }
390
434
 
391
435
  renderCall(args: unknown, _options: RenderResultOptions, theme: Theme) {
@@ -407,6 +451,15 @@ export class DeferredMCPTool implements CustomTool<TSchema, MCPToolDetails> {
407
451
  const args = normalizeToolArgs(params);
408
452
  const provider = this.#fallbackProvider;
409
453
  const providerName = this.#fallbackProviderName;
454
+ if (!this.#isAvailableForSessionControl()) {
455
+ return buildErrorResult(
456
+ new Error("MCP server is suspended for this session"),
457
+ this.serverName,
458
+ this.tool.name,
459
+ provider,
460
+ providerName,
461
+ );
462
+ }
410
463
 
411
464
  try {
412
465
  const connection = await untilAborted(signal, () => this.getConnection());
@@ -425,6 +478,15 @@ export class DeferredMCPTool implements CustomTool<TSchema, MCPToolDetails> {
425
478
  if (this.reconnect && isRetriableConnectionError(callError)) {
426
479
  const newConn = await reconnectWithAbort(this.reconnect, signal);
427
480
  if (newConn) {
481
+ if (!this.#isAvailableForSessionControl()) {
482
+ return buildErrorResult(
483
+ new Error("MCP server is suspended for this session"),
484
+ this.serverName,
485
+ this.tool.name,
486
+ provider,
487
+ providerName,
488
+ );
489
+ }
428
490
  if (this.#noReplay)
429
491
  return buildErrorResult(callError, this.serverName, this.tool.name, provider, providerName);
430
492
  const retryProvider = newConn._source?.provider ?? provider;
@@ -453,6 +515,15 @@ export class DeferredMCPTool implements CustomTool<TSchema, MCPToolDetails> {
453
515
  rethrowIfAborted(connError, signal);
454
516
  if (this.reconnect) {
455
517
  const newConn = await reconnectWithAbort(this.reconnect, signal);
518
+ if (newConn && !this.#isAvailableForSessionControl()) {
519
+ return buildErrorResult(
520
+ new Error("MCP server is suspended for this session"),
521
+ this.serverName,
522
+ this.tool.name,
523
+ provider,
524
+ providerName,
525
+ );
526
+ }
456
527
  if (newConn && !this.#noReplay) {
457
528
  try {
458
529
  const result = await callTool(newConn, this.tool.name, args, this.#callOptions(signal));
@@ -8,6 +8,7 @@
8
8
  * no sessions, no standalone stream, no replay. See ../protocol.ts.
9
9
  */
10
10
  import { logger, readSseJson, Snowflake } from "@gajae-code/utils";
11
+ import { isResourceOwnerDisposalActive, registerResourceOwner } from "../../runtime/process-lifecycle";
11
12
  import type {
12
13
  JsonRpcError,
13
14
  JsonRpcMessage,
@@ -30,6 +31,51 @@ import {
30
31
  import { fetchPluginMcpRequest, isPluginMcpPublicNetworkBound } from "../plugin-network-boundary";
31
32
  import { buildModernMcpHeaders, type MCPModernClientContext, type MCPProtocolEra, withModernMeta } from "../protocol";
32
33
 
34
+ /**
35
+ * Postmortem release of live HTTP/SSE MCP sessions.
36
+ *
37
+ * A stdio server dies with the agent because it is a `spawnOwnedProcess` child, but an
38
+ * HTTP/SSE server is a separate, already-running process that only learns a client is gone
39
+ * from the session-termination `DELETE`. A signal kill (`SIGINT`/`SIGTERM`/`SIGHUP`) or a
40
+ * fatal exit bypasses session dispose, so without this sweep the server keeps the
41
+ * `Mcp-Session-Id` and everything hanging off it for every agent run that ever died -
42
+ * unbounded growth on the server side. Mirrors the LSP/Python/browser owners.
43
+ */
44
+ const liveHttpTransports = new Set<HttpTransport>();
45
+ let httpTransportOwnerRelease: (() => void) | undefined;
46
+ /** Exit must not wait on a wedged endpoint; the per-request timeout is up to 30s. */
47
+ const POSTMORTEM_CLOSE_TIMEOUT_MS = 2_000;
48
+
49
+ function trackHttpTransport(transport: HttpTransport): void {
50
+ liveHttpTransports.add(transport);
51
+ httpTransportOwnerRelease ??= registerResourceOwner("mcp:http-transports", closeLiveHttpTransports);
52
+ }
53
+
54
+ function untrackHttpTransport(transport: HttpTransport): void {
55
+ if (!liveHttpTransports.delete(transport) || liveHttpTransports.size > 0) return;
56
+ // Nothing left to release: drop the registration so a long-lived host does not
57
+ // retain dead transports through the owner map.
58
+ httpTransportOwnerRelease?.();
59
+ httpTransportOwnerRelease = undefined;
60
+ }
61
+
62
+ async function closeLiveHttpTransports(): Promise<void> {
63
+ await Promise.all(
64
+ [...liveHttpTransports].map(async transport => {
65
+ try {
66
+ await transport.releaseForPostmortem(AbortSignal.timeout(POSTMORTEM_CLOSE_TIMEOUT_MS));
67
+ } catch (error) {
68
+ logger.debug("MCP HTTP postmortem release failed", { error });
69
+ }
70
+ }),
71
+ );
72
+ }
73
+
74
+ /** Live HTTP/SSE MCP transports awaiting postmortem release. Exposed for leak assertions/tests. */
75
+ export function liveHttpTransportCount(): number {
76
+ return liveHttpTransports.size;
77
+ }
78
+
33
79
  /** Best-effort JSON parse of an error body for structured era classification. */
34
80
  function tryParseJsonBody(text: string): unknown {
35
81
  try {
@@ -101,7 +147,11 @@ export class HttpTransport implements MCPTransport {
101
147
  */
102
148
  async connect(): Promise<void> {
103
149
  if (this.#connected) return;
150
+ if (isResourceOwnerDisposalActive()) {
151
+ throw new Error("Cannot connect MCP HTTP transport during process resource disposal");
152
+ }
104
153
  this.#connected = true;
154
+ trackHttpTransport(this);
105
155
  }
106
156
 
107
157
  #fetch(init: BunFetchRequestInit): Promise<Response> {
@@ -601,11 +651,8 @@ export class HttpTransport implements MCPTransport {
601
651
  }
602
652
  }
603
653
 
604
- async close(): Promise<void> {
605
- const wasConnected = this.#connected;
606
- this.#connected = false;
607
-
608
- // Abort all SSE/background readers and wait for them to settle.
654
+ /** Abort every SSE/background reader. Does not wait for them to settle. */
655
+ #abortStreams(): void {
609
656
  for (const controller of this.#streamControllers) {
610
657
  controller.abort();
611
658
  }
@@ -613,30 +660,66 @@ export class HttpTransport implements MCPTransport {
613
660
  this.#sseConnection.abort();
614
661
  this.#sseConnection = null;
615
662
  }
663
+ }
664
+
665
+ /**
666
+ * Send session termination (legacy era only; the modern era has no protocol
667
+ * session to terminate). This DELETE is the only signal an already-running
668
+ * HTTP/SSE server gets that its session may be dropped.
669
+ */
670
+ async #terminateSession(signal?: AbortSignal): Promise<void> {
671
+ if (this.#era === "modern") return;
672
+ const sessionId = this.#sessionId;
673
+ if (!sessionId) return;
674
+ // Claim the server session before awaiting the wire request. Graceful close
675
+ // and postmortem disposal can overlap during shutdown; only the caller that
676
+ // detached this id may emit its termination DELETE.
677
+ this.#sessionId = null;
678
+ try {
679
+ const timeout = this.config.timeout ?? 30000;
680
+ const headers: Record<string, string> = {
681
+ ...this.config.headers,
682
+ "Mcp-Session-Id": sessionId,
683
+ };
684
+
685
+ const timeoutSignal = AbortSignal.timeout(timeout);
686
+ await this.#fetch({
687
+ method: "DELETE",
688
+ headers,
689
+ signal: signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal,
690
+ });
691
+ } catch {
692
+ // Ignore termination errors
693
+ }
694
+ }
695
+
696
+ /**
697
+ * Terminate the server-side session on a signal or fatal exit, without the
698
+ * reader drain `close()` does: postmortem runs inside the signal handler,
699
+ * where an aborted SSE body read is never observed by JS, so awaiting the
700
+ * readers there never settles and the DELETE stays unreachable. `onClose` is
701
+ * deliberately not fired - the manager reacts to it by reconnecting, which
702
+ * would open a fresh server session on the way out.
703
+ */
704
+ async releaseForPostmortem(signal: AbortSignal): Promise<void> {
705
+ untrackHttpTransport(this);
706
+ this.#connected = false;
707
+ this.#abortStreams();
708
+ await this.#terminateSession(signal);
709
+ }
710
+
711
+ async close(): Promise<void> {
712
+ untrackHttpTransport(this);
713
+ const wasConnected = this.#connected;
714
+ this.#connected = false;
715
+
716
+ // Abort all SSE/background readers and wait for them to settle.
717
+ this.#abortStreams();
616
718
  await Promise.allSettled(Array.from(this.#streamReaders));
617
719
 
618
720
  if (!wasConnected && !this.#sessionId) return;
619
721
 
620
- // Send session termination if we have a session (legacy era only;
621
- // the modern era has no protocol session to terminate)
622
- if (this.#era !== "modern" && this.#sessionId) {
623
- try {
624
- const timeout = this.config.timeout ?? 30000;
625
- const headers: Record<string, string> = {
626
- ...this.config.headers,
627
- "Mcp-Session-Id": this.#sessionId,
628
- };
629
-
630
- await this.#fetch({
631
- method: "DELETE",
632
- headers,
633
- signal: AbortSignal.timeout(timeout),
634
- });
635
- } catch {
636
- // Ignore termination errors
637
- }
638
- this.#sessionId = null;
639
- }
722
+ await this.#terminateSession();
640
723
 
641
724
  this.onClose?.();
642
725
  this.onClose = undefined;
@@ -9,6 +9,7 @@ import {
9
9
  type SessionReconcileUncertainTarget,
10
10
  validateSessionReconcileUncertainTarget,
11
11
  } from "../lifecycle/service";
12
+ import { ACP_PROMPT_INACTIVITY_TIMEOUT_MS } from "../prompt-watchdog";
12
13
  import {
13
14
  validateAdapterControl,
14
15
  validateAdapterSecretFields,
@@ -19,11 +20,47 @@ import { type SessionAttachment, type SessionRouter, SessionRouterError } from "
19
20
  import { ACP_SESSION_RECONNECT, SESSION_ABORT_TIMEOUT_MS } from "../session-reconnect";
20
21
  import type { SessionLifecycleMcpServer } from "./mcp";
21
22
 
23
+ /**
24
+ * Registration rounds one provider activation may spend chasing a moving attachment.
25
+ *
26
+ * The activation loop restarts whenever the Router attachment changes identity while it
27
+ * is registering, because the leases it just took belong to a connection that no longer
28
+ * exists. One reconnect costs one restart, so a handful covers any churn that is actually
29
+ * converging; beyond that the attachment is rotating faster than registration completes
30
+ * and another round only re-takes leases that will be invalidated again. Bounding this is
31
+ * what turns "many session hosts contend for one agent dir" from an unbounded spin into a
32
+ * reported failure (issue #5658).
33
+ */
34
+ export const PROVIDER_ACTIVATION_MAX_ATTEMPTS = 8;
35
+
36
+ /**
37
+ * Wall-clock budget for the same loop.
38
+ *
39
+ * The attempt cap alone bounds rounds, not time: every round is a `#requestSession` round
40
+ * trip per provider, so under lease contention attempts are slow rather than fast-spinning
41
+ * and eight of them can still outlast the caller. The budget is sized to a third of
42
+ * {@link ACP_PROMPT_INACTIVITY_TIMEOUT_MS} so a prompt stalled in provider preflight fails
43
+ * with this error — which names the contended capability — with room to spare before the
44
+ * ACP prompt watchdog reports the same stall as undifferentiated silence.
45
+ *
46
+ * It gates *starting* another round, never interrupting one in flight: a single slow but
47
+ * healthy registration keeps whatever time it needs, and only a retry decided after the
48
+ * deadline is refused.
49
+ */
50
+ export const PROVIDER_ACTIVATION_BUDGET_MS = Math.floor(ACP_PROMPT_INACTIVITY_TIMEOUT_MS / 3);
51
+
22
52
  type JsonObject = Record<string, unknown>;
23
53
  function object(value: unknown): JsonObject | undefined {
24
54
  return value !== null && typeof value === "object" && !Array.isArray(value) ? (value as JsonObject) : undefined;
25
55
  }
26
56
 
57
+ function preservePromptFailureMetadata(target: Error, source: JsonObject | undefined): void {
58
+ if (!source) return;
59
+ const providerCode = source.providerCode ?? object(source.transportFailure)?.providerCode;
60
+ if (providerCode !== undefined) Object.assign(target, { providerCode });
61
+ if (source.phase !== undefined) Object.assign(target, { phase: source.phase });
62
+ }
63
+
27
64
  function providerErrorCode(error: unknown): string | undefined {
28
65
  if (error instanceof AcpSdkAdapterError || error instanceof SdkClientError || error instanceof ReverseLeaseError)
29
66
  return error.code;
@@ -445,10 +482,16 @@ export class AcpSdkAdapter {
445
482
  const envelope = object(response);
446
483
  if (envelope?.ok === false) {
447
484
  const error = object(envelope.error);
448
- throw new AcpSdkAdapterError(
485
+ const adapterError = new AcpSdkAdapterError(
449
486
  typeof error?.code === "string" ? error.code : "request_failed",
450
487
  typeof error?.message === "string" ? error.message : "SDK session request failed.",
451
488
  );
489
+ // Prompt failures may carry the bounded classifier computed by the host. Keep
490
+ // those fields on the adapter error so the ACP boundary can project them instead
491
+ // of reconstructing a bare `{ code, details }` pair. `acpRequestFailure` performs
492
+ // the final safe-token validation before anything reaches the wire.
493
+ preservePromptFailureMetadata(adapterError, error);
494
+ throw adapterError;
452
495
  }
453
496
  return envelope?.result ?? response;
454
497
  }
@@ -464,13 +507,19 @@ export class AcpSdkAdapter {
464
507
  const sessionId = this.#sessionId;
465
508
  if (!attachment || !sessionId || !attachment.isCurrent())
466
509
  throw new SessionRouterError("pre_send", "SDK session attachment is stale.");
467
- const response = await router.request(
468
- sessionId,
469
- { ...frame, id: randomUUID() },
470
- attachment.generation,
471
- attachment,
472
- options,
473
- );
510
+ let response: Record<string, unknown>;
511
+ try {
512
+ response = await router.request(
513
+ sessionId,
514
+ { ...frame, id: randomUUID() },
515
+ attachment.generation,
516
+ attachment,
517
+ options,
518
+ );
519
+ } catch (error) {
520
+ if (error instanceof SdkClientError) preservePromptFailureMetadata(error, object(error.details));
521
+ throw error;
522
+ }
474
523
  if (!attachment.isCurrent())
475
524
  throw new SessionRouterError("ambiguous", "SDK session attachment changed while awaiting command response.");
476
525
  return raw ? response : this.#unwrapSessionResponse(response);
@@ -600,7 +649,12 @@ export class AcpSdkAdapter {
600
649
  }
601
650
 
602
651
  const activation = (async () => {
603
- for (;;) {
652
+ const startedAt = Date.now();
653
+ // Exhaustion is checked here, outside the try below, so the throw cannot be caught
654
+ // by the same `continue` branch that spent the budget.
655
+ for (let attempt = 1; ; attempt++) {
656
+ if (attempt > PROVIDER_ACTIVATION_MAX_ATTEMPTS || Date.now() - startedAt >= PROVIDER_ACTIVATION_BUDGET_MS)
657
+ throw this.#providerActivationExhausted(attempt - 1, startedAt);
604
658
  const attachment = this.#attachment;
605
659
  const connectionId = attachment?.connectionId;
606
660
  try {
@@ -652,6 +706,33 @@ export class AcpSdkAdapter {
652
706
  }
653
707
  }
654
708
 
709
+ /**
710
+ * Names what the exhausted activation loop was still fighting over, so a stalled
711
+ * preflight is attributable to a capability instead of surfacing as generic silence.
712
+ */
713
+ #providerActivationExhausted(attempts: number, startedAt: number): AcpSdkAdapterError {
714
+ const elapsedMs = Date.now() - startedAt;
715
+ const contended = this.#providers
716
+ .filter(provider => !this.#leases.has(provider.capability))
717
+ .map(provider => provider.capability);
718
+ const detail =
719
+ contended.length > 0
720
+ ? `still unleased: ${contended.join(", ")}`
721
+ : "the SDK session attachment changed identity on every attempt";
722
+ logger.error("acp_provider_activation_exhausted", {
723
+ sessionId: this.#sessionId,
724
+ attempts,
725
+ elapsedMs,
726
+ contended,
727
+ leaseCount: this.#leases.size,
728
+ });
729
+ return new AcpSdkAdapterError(
730
+ "provider_activation_exhausted",
731
+ `SDK provider activation did not converge after ${attempts} attempts over ${Math.round(elapsedMs / 1_000)}s ` +
732
+ `(${detail}). The session host is contending for provider leases it cannot hold.`,
733
+ );
734
+ }
735
+
655
736
  #assertGenericDisposition(kind: "control" | "global", sdkId: string): void {
656
737
  const operation = OPERATIONS.find(candidate => candidate.kind === kind && candidate.sdkId === sdkId);
657
738
  if (!operation) throw new AcpSdkAdapterError("unknown_operation", `Unknown SDK ${kind}: ${sdkId}`);
@@ -31,10 +31,27 @@ export function acpFinalTextFromMessage(message: unknown): BoundedAcpFinalText {
31
31
  return boundAcpFinalText(parts.join(""));
32
32
  }
33
33
 
34
+ /**
35
+ * Assistant-content presence is trimmed everywhere in the prompt pipeline: whitespace carries no
36
+ * assistant content. The ACP retry gate, prompt reconciliation
37
+ * (`sdk/bus/prompt-reconciliation.ts`) and terminal publication must agree, otherwise a terminal
38
+ * the retry gate treats as "no output" can still publish a whitespace chunk alongside the retry's
39
+ * real answer (review P2 on #5628).
40
+ */
41
+ export function hasAcpFinalTextContent(value: string): boolean {
42
+ return value.trim().length > 0;
43
+ }
44
+
34
45
  export function resolveAcpFinalText(streamed: string, finalText: string): AcpFinalTextResolution {
35
46
  const final = boundAcpFinalText(finalText);
36
- if (!final.text || streamed === final.text || streamed.includes(final.text)) return { kind: "none", final };
47
+ if (!hasAcpFinalTextContent(final.text) || streamed === final.text || streamed.includes(final.text))
48
+ return { kind: "none", final };
37
49
  if (!streamed) return { kind: "emit", final, text: final.text };
38
- if (final.text.startsWith(streamed)) return { kind: "emit", final, text: final.text.slice(streamed.length) };
50
+ if (final.text.startsWith(streamed)) {
51
+ // A pure-whitespace delta is the same defect class as whitespace-only final text: it adds no
52
+ // assistant content, so it must not reach consumers either.
53
+ const tail = final.text.slice(streamed.length);
54
+ return hasAcpFinalTextContent(tail) ? { kind: "emit", final, text: tail } : { kind: "none", final };
55
+ }
39
56
  return { kind: "divergent", final };
40
57
  }