@gajae-code/coding-agent 0.17.2 → 0.17.5

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 (390) hide show
  1. package/CHANGELOG.md +267 -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 +1049 -3
  18. package/dist/types/config/file-lock.d.ts +13 -3
  19. package/dist/types/config/model-profile-activation.d.ts +45 -1
  20. package/dist/types/config/model-registry.d.ts +14 -3
  21. package/dist/types/config/model-resolver.d.ts +1 -1
  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/daemon/operator-contract.d.ts +5 -1
  25. package/dist/types/eval/js/worker-protocol.d.ts +2 -0
  26. package/dist/types/exec/bash-executor.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/gjc-plugins/runtime-adapters.d.ts +11 -2
  30. package/dist/types/extensibility/runtime-skill-discovery.d.ts +18 -0
  31. package/dist/types/extensibility/shared-events.d.ts +3 -1
  32. package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
  33. package/dist/types/gjc-runtime/state-writer.d.ts +14 -1
  34. package/dist/types/harness-control-plane/control-endpoint.d.ts +4 -1
  35. package/dist/types/harness-control-plane/finalize.d.ts +14 -3
  36. package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
  37. package/dist/types/internal-urls/index.d.ts +1 -0
  38. package/dist/types/internal-urls/parse.d.ts +9 -1
  39. package/dist/types/main.d.ts +23 -1
  40. package/dist/types/modes/acp/acp-agent.d.ts +55 -1
  41. package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
  42. package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
  43. package/dist/types/modes/components/session-selector.d.ts +5 -2
  44. package/dist/types/modes/components/user-message-selector.d.ts +5 -2
  45. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  46. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  47. package/dist/types/modes/interactive-mode.d.ts +1 -1
  48. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
  49. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
  50. package/dist/types/modes/types.d.ts +2 -2
  51. package/dist/types/runtime/process-lifecycle.d.ts +2 -0
  52. package/dist/types/runtime-mcp/loader.d.ts +2 -0
  53. package/dist/types/runtime-mcp/manager.d.ts +69 -6
  54. package/dist/types/runtime-mcp/pool.d.ts +17 -0
  55. package/dist/types/runtime-mcp/redaction.d.ts +20 -0
  56. package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
  57. package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
  58. package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
  59. package/dist/types/sdk/broker/broker.d.ts +17 -4
  60. package/dist/types/sdk/broker/ensure.d.ts +3 -0
  61. package/dist/types/sdk/broker/identity.d.ts +5 -1
  62. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
  63. package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
  64. package/dist/types/sdk/broker/managed-task-dag.d.ts +1335 -0
  65. package/dist/types/sdk/broker/managed-task-verification.d.ts +37 -0
  66. package/dist/types/sdk/broker/runtime.d.ts +1 -1
  67. package/dist/types/sdk/broker/session-index.d.ts +22 -3
  68. package/dist/types/sdk/broker/spawn-authority.d.ts +12 -0
  69. package/dist/types/sdk/broker/startup-failure.d.ts +4 -0
  70. package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
  71. package/dist/types/sdk/bus/index.d.ts +19 -0
  72. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -1
  73. package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
  74. package/dist/types/sdk/cli/master-cli.d.ts +6 -10
  75. package/dist/types/sdk/cli/rows.d.ts +19 -1
  76. package/dist/types/sdk/cli/session-cli.d.ts +22 -5
  77. package/dist/types/sdk/client/client.d.ts +2 -0
  78. package/dist/types/sdk/guides/cli.d.ts +3 -4
  79. package/dist/types/sdk/guides/verify.d.ts +2 -0
  80. package/dist/types/sdk/host/host.d.ts +9 -0
  81. package/dist/types/sdk/host/session-runtime.d.ts +31 -0
  82. package/dist/types/sdk/host/types.d.ts +10 -0
  83. package/dist/types/sdk/index.d.ts +1 -0
  84. package/dist/types/sdk/prompt-deadline-flush.d.ts +85 -0
  85. package/dist/types/sdk/prompt-deadline-manager.d.ts +37 -0
  86. package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
  87. package/dist/types/sdk/router/session-router.d.ts +2 -0
  88. package/dist/types/sdk/session.d.ts +3 -3
  89. package/dist/types/sdk/startup-capability.d.ts +2 -0
  90. package/dist/types/sdk/transport/relay.d.ts +1 -0
  91. package/dist/types/sdk/transport/serve-cli.d.ts +48 -4
  92. package/dist/types/sdk/transport/stdio.d.ts +5 -1
  93. package/dist/types/session/agent-session.d.ts +175 -23
  94. package/dist/types/session/fallback-chain-controller.d.ts +6 -6
  95. package/dist/types/session/fold-coordinator.d.ts +17 -3
  96. package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
  97. package/dist/types/session/session-manager.d.ts +64 -1
  98. package/dist/types/session/session-work-lease.d.ts +14 -0
  99. package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
  100. package/dist/types/setup/provider-onboarding.d.ts +81 -1
  101. package/dist/types/task/types.d.ts +2 -0
  102. package/dist/types/tools/bash-interactive.d.ts +1 -0
  103. package/dist/types/tools/bash.d.ts +37 -11
  104. package/dist/types/tools/fetch.d.ts +10 -1
  105. package/dist/types/tools/job.d.ts +1 -1
  106. package/dist/types/tools/steer-fold.d.ts +31 -0
  107. package/dist/types/tools/subagent.d.ts +8 -1
  108. package/dist/types/utils/git.d.ts +28 -2
  109. package/dist/types/utils/mupdf-embedded.d.ts +2 -0
  110. package/dist/types/utils/mupdf.d.ts +4 -0
  111. package/dist/types/utils/session-title-generation.d.ts +6 -0
  112. package/dist/types/utils/title-generator.d.ts +11 -2
  113. package/dist/types/web/scrapers/types.d.ts +3 -0
  114. package/package.json +18 -10
  115. package/scripts/build-binary.ts +9 -3
  116. package/scripts/compile-args.ts +0 -9
  117. package/scripts/embed-mupdf.ts +52 -0
  118. package/scripts/generate-sdk-operation-inventory.ts +31 -1
  119. package/scripts/vendor-markit.ts +196 -0
  120. package/src/async/job-manager.ts +22 -1
  121. package/src/cli/daemon-cli.ts +53 -18
  122. package/src/cli/github-release.ts +87 -5
  123. package/src/cli/mcp-cli.ts +38 -4
  124. package/src/cli/public-command-entry.ts +450 -0
  125. package/src/cli/public-command-errors.ts +581 -0
  126. package/src/cli/public-command-evidence.ts +717 -0
  127. package/src/cli/public-command-help.ts +360 -0
  128. package/src/cli/public-command-registry.ts +541 -0
  129. package/src/cli/session-picker.ts +2 -1
  130. package/src/cli/setup-cli.ts +10 -1
  131. package/src/cli/skills-cli.ts +46 -2
  132. package/src/cli/stats-cli.ts +11 -1
  133. package/src/cli.ts +61 -1
  134. package/src/commands/daemon.ts +65 -69
  135. package/src/commands/sdk.ts +175 -117
  136. package/src/commands/setup.ts +2 -0
  137. package/src/commands/skills.ts +11 -0
  138. package/src/config/autorouting-tier-map.ts +543 -3
  139. package/src/config/file-lock.ts +206 -65
  140. package/src/config/model-discovery-manager.ts +4 -4
  141. package/src/config/model-profile-activation.ts +417 -66
  142. package/src/config/model-registry.ts +254 -36
  143. package/src/config/model-resolver.ts +6 -1
  144. package/src/config/settings-schema.ts +26 -2
  145. package/src/coordinator-mcp/policy.ts +10 -5
  146. package/src/coordinator-mcp/question-gate-codec.ts +17 -0
  147. package/src/coordinator-mcp/question-state.ts +179 -9
  148. package/src/coordinator-mcp/server.ts +521 -87
  149. package/src/coordinator-mcp/session-reaper.ts +6 -4
  150. package/src/crash/record-loader.ts +6 -2
  151. package/src/daemon/operator-contract.ts +26 -12
  152. package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
  153. package/src/eval/js/context-manager.ts +8 -8
  154. package/src/eval/js/worker-core.ts +9 -4
  155. package/src/eval/js/worker-protocol.ts +2 -0
  156. package/src/exec/bash-executor.ts +3 -0
  157. package/src/extensibility/extensions/runner.ts +52 -4
  158. package/src/extensibility/extensions/types.ts +10 -0
  159. package/src/extensibility/gjc-plugins/registry.ts +196 -37
  160. package/src/extensibility/gjc-plugins/runtime-adapters.ts +46 -8
  161. package/src/extensibility/runtime-skill-discovery.ts +61 -3
  162. package/src/extensibility/shared-events.ts +3 -1
  163. package/src/gjc-runtime/session-state-lock.ts +165 -24
  164. package/src/gjc-runtime/state-writer.ts +317 -6
  165. package/src/gjc-runtime/ultragoal-succession.ts +31 -3
  166. package/src/harness-control-plane/control-endpoint.ts +24 -6
  167. package/src/harness-control-plane/finalize.ts +312 -25
  168. package/src/harness-control-plane/owner.ts +323 -89
  169. package/src/internal-urls/docs-index.generated.ts +23 -20
  170. package/src/internal-urls/embedded-protocol.ts +83 -0
  171. package/src/internal-urls/index.ts +1 -0
  172. package/src/internal-urls/parse.ts +21 -1
  173. package/src/internal-urls/router.ts +4 -2
  174. package/src/main.ts +192 -74
  175. package/src/modes/acp/acp-agent.ts +973 -56
  176. package/src/modes/acp/startup-provenance.ts +59 -0
  177. package/src/modes/components/custom-provider-wizard.ts +308 -7
  178. package/src/modes/components/gjc-bundle-settings.ts +21 -1
  179. package/src/modes/components/oauth-selector.ts +9 -0
  180. package/src/modes/components/session-selector.ts +47 -4
  181. package/src/modes/components/tool-status-header.ts +72 -6
  182. package/src/modes/components/user-message-selector.ts +57 -15
  183. package/src/modes/controllers/command-controller.ts +58 -32
  184. package/src/modes/controllers/extension-ui-controller.ts +33 -7
  185. package/src/modes/controllers/input-controller.ts +4 -0
  186. package/src/modes/controllers/runtime-mcp-command-controller.ts +1 -1
  187. package/src/modes/controllers/selector-controller.ts +161 -15
  188. package/src/modes/interactive-mode.ts +21 -7
  189. package/src/modes/runtime-init.ts +1 -0
  190. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
  191. package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
  192. package/src/modes/types.ts +2 -2
  193. package/src/prompts/agent-fragments/browser-aside.md +1 -0
  194. package/src/prompts/system/title-system.md +1 -1
  195. package/src/prompts/tools/bash.md +1 -1
  196. package/src/prompts/tools/job.md +1 -1
  197. package/src/prompts/tools/read.md +2 -0
  198. package/src/runtime/network-prewarm-service.test.ts +36 -1
  199. package/src/runtime/network-prewarm-service.ts +28 -3
  200. package/src/runtime/process-lifecycle.ts +22 -11
  201. package/src/runtime-mcp/config.ts +16 -1
  202. package/src/runtime-mcp/loader.ts +6 -3
  203. package/src/runtime-mcp/manager.ts +845 -109
  204. package/src/runtime-mcp/pool.test.ts +31 -0
  205. package/src/runtime-mcp/pool.ts +304 -28
  206. package/src/runtime-mcp/redaction.ts +42 -0
  207. package/src/runtime-mcp/startup-policy.ts +7 -0
  208. package/src/runtime-mcp/tool-bridge.ts +71 -0
  209. package/src/runtime-mcp/transports/http.ts +108 -25
  210. package/src/sdk/acp/adapter.ts +27 -8
  211. package/src/sdk/broker/broker.ts +1431 -198
  212. package/src/sdk/broker/ensure.ts +21 -3
  213. package/src/sdk/broker/identity.ts +22 -1
  214. package/src/sdk/broker/lifecycle-ledger.ts +95 -31
  215. package/src/sdk/broker/lifecycle.ts +690 -141
  216. package/src/sdk/broker/managed-task-dag.ts +1927 -0
  217. package/src/sdk/broker/managed-task-verification.ts +642 -0
  218. package/src/sdk/broker/runtime.ts +1 -1
  219. package/src/sdk/broker/session-index.ts +97 -4
  220. package/src/sdk/broker/spawn-authority.ts +175 -14
  221. package/src/sdk/broker/startup-failure.ts +80 -1
  222. package/src/sdk/broker/stderr-drainer.ts +59 -0
  223. package/src/sdk/broker/transport.ts +1 -0
  224. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  225. package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
  226. package/src/sdk/bus/index.ts +707 -81
  227. package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
  228. package/src/sdk/bus/telegram-daemon-contract.ts +7 -1
  229. package/src/sdk/bus/telegram-daemon.ts +10 -3
  230. package/src/sdk/cli/master-cli.ts +47 -36
  231. package/src/sdk/cli/rows.ts +30 -7
  232. package/src/sdk/cli/session-cli.ts +705 -184
  233. package/src/sdk/client/client.ts +18 -0
  234. package/src/sdk/guides/catalog.ts +2 -2
  235. package/src/sdk/guides/cli.ts +20 -62
  236. package/src/sdk/guides/verify.ts +14 -0
  237. package/src/sdk/host/host.ts +49 -5
  238. package/src/sdk/host/session-runtime.test.ts +184 -2
  239. package/src/sdk/host/session-runtime.ts +322 -101
  240. package/src/sdk/host/types.ts +12 -1
  241. package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
  242. package/src/sdk/host/websocket-transport.ts +40 -6
  243. package/src/sdk/index.ts +11 -0
  244. package/src/sdk/lifecycle/service.ts +2 -0
  245. package/src/sdk/prompt-deadline-flush.ts +356 -0
  246. package/src/sdk/prompt-deadline-manager.ts +133 -1
  247. package/src/sdk/prompt-tool-boundary.ts +121 -0
  248. package/src/sdk/protocol/operation-inventory.generated.json +106 -0
  249. package/src/sdk/router/session-router.ts +9 -1
  250. package/src/sdk/session.ts +763 -116
  251. package/src/sdk/startup-capability.ts +1 -1
  252. package/src/sdk/transport/relay.ts +71 -6
  253. package/src/sdk/transport/serve-cli.ts +397 -42
  254. package/src/sdk/transport/socket.ts +12 -11
  255. package/src/sdk/transport/stdio.ts +7 -3
  256. package/src/session/agent-session.ts +2472 -407
  257. package/src/session/fallback-chain-controller.ts +6 -7
  258. package/src/session/fold-coordinator.ts +24 -5
  259. package/src/session/internal/managed-session-scope.ts +68 -18
  260. package/src/session/internal/managed-session-storage.ts +334 -12
  261. package/src/session/session-manager.ts +382 -44
  262. package/src/session/session-work-lease.ts +34 -0
  263. package/src/setup/model-onboarding-guidance.ts +1 -1
  264. package/src/setup/provider-onboarding.ts +397 -24
  265. package/src/setup/provider-presets.json +2 -1
  266. package/src/slash-commands/builtin-registry.ts +259 -15
  267. package/src/task/executor.ts +46 -17
  268. package/src/task/index.ts +14 -2
  269. package/src/task/types.ts +2 -0
  270. package/src/tools/bash-interactive.ts +2 -0
  271. package/src/tools/bash.ts +118 -78
  272. package/src/tools/fetch.ts +140 -25
  273. package/src/tools/job.ts +151 -25
  274. package/src/tools/output-meta.ts +15 -2
  275. package/src/tools/path-utils.ts +27 -3
  276. package/src/tools/read.ts +3 -3
  277. package/src/tools/steer-fold.ts +107 -0
  278. package/src/tools/subagent-render.ts +24 -1
  279. package/src/tools/subagent.ts +27 -1
  280. package/src/tools/tool-catalog.generated.ts +29 -2
  281. package/src/tools/tool-errors.ts +4 -0
  282. package/src/utils/git.ts +53 -3
  283. package/src/utils/markit.ts +55 -41
  284. package/src/utils/mupdf-assets.d.ts +4 -0
  285. package/src/utils/mupdf-embedded.ts +4 -0
  286. package/src/utils/mupdf.ts +136 -0
  287. package/src/utils/session-title-generation.ts +29 -0
  288. package/src/utils/title-generator.ts +46 -2
  289. package/src/web/scrapers/types.ts +24 -3
  290. package/vendor/markit-ai/LICENSE +21 -0
  291. package/vendor/markit-ai/MANIFEST.json +412 -0
  292. package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
  293. package/vendor/markit-ai/dist/commands/config.js +139 -0
  294. package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
  295. package/vendor/markit-ai/dist/commands/convert.js +113 -0
  296. package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
  297. package/vendor/markit-ai/dist/commands/formats.js +92 -0
  298. package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
  299. package/vendor/markit-ai/dist/commands/init.js +29 -0
  300. package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
  301. package/vendor/markit-ai/dist/commands/onboard.js +61 -0
  302. package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
  303. package/vendor/markit-ai/dist/commands/plugin.js +58 -0
  304. package/vendor/markit-ai/dist/config.d.ts +26 -0
  305. package/vendor/markit-ai/dist/config.js +42 -0
  306. package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
  307. package/vendor/markit-ai/dist/converters/audio.js +104 -0
  308. package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
  309. package/vendor/markit-ai/dist/converters/csv.js +83 -0
  310. package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
  311. package/vendor/markit-ai/dist/converters/docx.js +57 -0
  312. package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
  313. package/vendor/markit-ai/dist/converters/epub.js +128 -0
  314. package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
  315. package/vendor/markit-ai/dist/converters/github.js +148 -0
  316. package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
  317. package/vendor/markit-ai/dist/converters/html.js +30 -0
  318. package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
  319. package/vendor/markit-ai/dist/converters/image.js +124 -0
  320. package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
  321. package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
  322. package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
  323. package/vendor/markit-ai/dist/converters/iwork.js +391 -0
  324. package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
  325. package/vendor/markit-ai/dist/converters/json.js +21 -0
  326. package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
  327. package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
  328. package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
  329. package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
  330. package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
  331. package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
  332. package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
  333. package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
  334. package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
  335. package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
  336. package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
  337. package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
  338. package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
  339. package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
  340. package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
  341. package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
  342. package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
  343. package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
  344. package/vendor/markit-ai/dist/converters/pptx.js +289 -0
  345. package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
  346. package/vendor/markit-ai/dist/converters/rss.js +138 -0
  347. package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
  348. package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
  349. package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
  350. package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
  351. package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
  352. package/vendor/markit-ai/dist/converters/xml.js +18 -0
  353. package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
  354. package/vendor/markit-ai/dist/converters/yaml.js +17 -0
  355. package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
  356. package/vendor/markit-ai/dist/converters/zip.js +57 -0
  357. package/vendor/markit-ai/dist/index.d.ts +30 -0
  358. package/vendor/markit-ai/dist/index.js +26 -0
  359. package/vendor/markit-ai/dist/main.d.ts +2 -0
  360. package/vendor/markit-ai/dist/main.js +191 -0
  361. package/vendor/markit-ai/dist/markit.d.ts +45 -0
  362. package/vendor/markit-ai/dist/markit.js +267 -0
  363. package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
  364. package/vendor/markit-ai/dist/plugins/api.js +42 -0
  365. package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
  366. package/vendor/markit-ai/dist/plugins/index.js +3 -0
  367. package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
  368. package/vendor/markit-ai/dist/plugins/installer.js +176 -0
  369. package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
  370. package/vendor/markit-ai/dist/plugins/loader.js +61 -0
  371. package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
  372. package/vendor/markit-ai/dist/plugins/types.js +1 -0
  373. package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
  374. package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
  375. package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
  376. package/vendor/markit-ai/dist/providers/index.js +56 -0
  377. package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
  378. package/vendor/markit-ai/dist/providers/openai.js +76 -0
  379. package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
  380. package/vendor/markit-ai/dist/providers/types.js +1 -0
  381. package/vendor/markit-ai/dist/types.d.ts +36 -0
  382. package/vendor/markit-ai/dist/types.js +1 -0
  383. package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
  384. package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
  385. package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
  386. package/vendor/markit-ai/dist/utils/output.js +31 -0
  387. package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
  388. package/vendor/markit-ai/dist/utils/turndown.js +70 -0
  389. package/vendor/markit-ai/package.json +74 -0
  390. package/vendor/markit-ai.patch +49 -0
@@ -11,15 +11,20 @@ import { type Theme, theme } from "../modes/theme/theme";
11
11
  import type { ToolSession } from "../sdk";
12
12
  import type { AgentStorage } from "../session/agent-storage";
13
13
  import { DEFAULT_MAX_BYTES, type TruncationDirection, truncateContent } from "../session/streaming-output";
14
- import { renderStatusLine } from "../tui";
15
14
  import { CachedOutputBlock } from "../tui/output-block";
15
+ import { renderStatusLine } from "../tui/status-line";
16
16
  import { formatDimensionNote, resizeImage } from "../utils/image-resize";
17
17
  import { parseHtmlLazy } from "../utils/linkedom";
18
18
  import { INSANE_NOTES } from "../web/insane/bridge";
19
19
  import { validatePublicHttpUrl } from "../web/insane/url-guard";
20
- import { specialHandlers } from "../web/scrapers";
21
- import type { RenderResult } from "../web/scrapers/types";
22
- import { finalizeOutput, loadPage, looksLikeHtml, MAX_OUTPUT_CHARS } from "../web/scrapers/types";
20
+ import {
21
+ finalizeOutput,
22
+ loadPage,
23
+ looksLikeHtml,
24
+ MAX_OUTPUT_CHARS,
25
+ type RenderResult,
26
+ type SpecialHandler,
27
+ } from "../web/scrapers/types";
23
28
  import { convertWithMarkit, fetchBinary } from "../web/scrapers/utils";
24
29
  import { applyListLimit } from "./list-limit";
25
30
  import { formatStyledArtifactReference, type OutputMeta } from "./output-meta";
@@ -30,6 +35,12 @@ import { clampTimeout } from "./tool-timeouts";
30
35
 
31
36
  type NativeHtmlBindings = { htmlToMarkdown: typeof htmlToMarkdownFn };
32
37
  let nativeHtmlBindings: NativeHtmlBindings | undefined;
38
+ let specialHandlersCache: SpecialHandler[] | undefined;
39
+
40
+ function getSpecialHandlers(): SpecialHandler[] {
41
+ specialHandlersCache ??= (require("../web/scrapers") as { specialHandlers: SpecialHandler[] }).specialHandlers;
42
+ return specialHandlersCache;
43
+ }
33
44
 
34
45
  /**
35
46
  * Lazy native access for HTML conversion. The module is cached, never the
@@ -49,6 +60,7 @@ const FETCH_DEFAULT_MAX_LINES = 300;
49
60
  // Convertible document types handled by markit.
50
61
  const CONVERTIBLE_MIMES = new Set([
51
62
  "application/pdf",
63
+ "application/x-pdf",
52
64
  "application/msword",
53
65
  "application/vnd.ms-powerpoint",
54
66
  "application/vnd.ms-excel",
@@ -228,17 +240,52 @@ function normalizeMime(contentType: string): string {
228
240
  return contentType.split(";")[0].trim().toLowerCase();
229
241
  }
230
242
 
243
+ // Bound both parsing work and decoded filename size to a single modest header.
244
+ function getDispositionFilename(header: string): string | undefined {
245
+ if (header.length > 8192 || /[\x00-\x1f\x7f]/.test(header.replaceAll("\t", ""))) return undefined;
246
+ const separator = header.indexOf(";");
247
+ if (separator < 0) return undefined;
248
+ const parameter = /;[ \t]*([!#$%&'*+.^_`|~\w-]+)[ \t]*=[ \t]*(?:"((?:[^"\\]|\\.)*)"|([!#$%&'*+.^_`|~\w-]+))[ \t]*/y;
249
+ let offset = separator;
250
+ let plain: string | undefined;
251
+ let extended: string | undefined;
252
+ const seen = new Set<string>();
253
+ while (offset < header.length) {
254
+ parameter.lastIndex = offset;
255
+ const match = parameter.exec(header);
256
+ if (!match) return undefined;
257
+ offset = parameter.lastIndex;
258
+ const name = match[1].toLowerCase();
259
+ if (name !== "filename" && name !== "filename*") continue;
260
+ // Duplicate filename parameters are ambiguous, not an ordering override.
261
+ if (seen.has(name)) return undefined;
262
+ seen.add(name);
263
+ const value = match[2] === undefined ? match[3] : match[2].replace(/\\(.)/g, "$1");
264
+ if (name === "filename") {
265
+ if (value) plain = value;
266
+ continue;
267
+ }
268
+ // RFC 8187 ext-value: only UTF-8 is supported; quoted ext-values are invalid.
269
+ const encoded =
270
+ match[2] === undefined ? /^UTF-8'[A-Za-z0-9-]*'((?:[!#$&+.^_`|~\w-]|%[\da-fA-F]{2})*)$/i.exec(value) : null;
271
+ if (!encoded) continue;
272
+ try {
273
+ const decoded = decodeURIComponent(encoded[1]);
274
+ if (decoded && !/[\x00-\x1f\x7f]/.test(decoded)) extended = decoded;
275
+ } catch {
276
+ // Invalid UTF-8 or percent encoding must not escape classification.
277
+ }
278
+ }
279
+ return extended ?? plain;
280
+ }
281
+
231
282
  /**
232
283
  * Get extension from URL or Content-Disposition
233
284
  */
234
285
  function getExtensionHint(url: string, contentDisposition?: string): string {
235
- // Try Content-Disposition filename first
236
286
  if (contentDisposition) {
237
- const match = contentDisposition.match(/filename[*]?=["']?([^"';\n]+)/i);
238
- if (match) {
239
- const ext = path.extname(match[1]).toLowerCase();
240
- if (ext) return ext;
241
- }
287
+ const filename = getDispositionFilename(contentDisposition);
288
+ if (filename !== undefined) return path.extname(filename).toLowerCase();
242
289
  }
243
290
 
244
291
  // Fall back to URL path
@@ -251,21 +298,25 @@ function getExtensionHint(url: string, contentDisposition?: string): string {
251
298
  return "";
252
299
  }
253
300
 
301
+ function isGenericMimeType(mime: string): boolean {
302
+ return (
303
+ mime.length === 0 || mime === "application/octet-stream" || mime === "binary/octet-stream" || mime === "unknown"
304
+ );
305
+ }
306
+
254
307
  /**
255
308
  * Check if content type is convertible via markit.
256
309
  */
257
310
  function isConvertible(mime: string, extensionHint: string): boolean {
258
- if (CONVERTIBLE_MIMES.has(mime)) return true;
259
- if (mime === "application/octet-stream" && CONVERTIBLE_EXTENSIONS.has(extensionHint)) return true;
260
- if (CONVERTIBLE_EXTENSIONS.has(extensionHint)) return true;
261
- return false;
311
+ return (
312
+ CONVERTIBLE_MIMES.has(mime) ||
313
+ (CONVERTIBLE_EXTENSIONS.has(extensionHint) && (extensionHint !== ".pdf" || isGenericMimeType(mime)))
314
+ );
262
315
  }
263
316
 
264
317
  function resolveImageMimeType(mime: string, extensionHint: string): string | null {
265
318
  if (mime.startsWith("image/")) return mime;
266
- const shouldUseExtensionHint =
267
- mime.length === 0 || mime === "application/octet-stream" || mime === "binary/octet-stream" || mime === "unknown";
268
- if (!shouldUseExtensionHint) return null;
319
+ if (!isGenericMimeType(mime)) return null;
269
320
  return IMAGE_MIME_BY_EXTENSION.get(extensionHint) ?? null;
270
321
  }
271
322
 
@@ -619,7 +670,7 @@ async function handleSpecialUrls(
619
670
  signal: AbortSignal | undefined,
620
671
  storage: AgentStorage | null,
621
672
  ): Promise<FetchRenderResult | null> {
622
- for (const handler of specialHandlers) {
673
+ for (const handler of getSpecialHandlers()) {
623
674
  if (signal?.aborted) {
624
675
  throw new ToolAbortError();
625
676
  }
@@ -629,6 +680,11 @@ async function handleSpecialUrls(
629
680
  return null;
630
681
  }
631
682
 
683
+ /** Test-only seam for verifying lazy special-handler loading and dispatch. */
684
+ export const fetchSpecialHandlerTestHooks = {
685
+ dispatch: handleSpecialUrls,
686
+ };
687
+
632
688
  // =============================================================================
633
689
  // Main Render Function
634
690
  // =============================================================================
@@ -741,15 +797,60 @@ async function renderUrl(
741
797
  const { finalUrl, content: rawContent } = response;
742
798
  const mime = normalizeMime(response.contentType);
743
799
  const extHint = getExtensionHint(finalUrl);
800
+ let isPdf =
801
+ mime === "application/pdf" || mime === "application/x-pdf" || (extHint === ".pdf" && isGenericMimeType(mime));
802
+
803
+ // Raw PDF inspection is explicit; never substitute PDF bytes for failed text extraction.
804
+ if (raw && isPdf) {
805
+ const output = finalizeOutput(rawContent);
806
+ return {
807
+ url,
808
+ finalUrl,
809
+ contentType: mime,
810
+ method: "raw",
811
+ content: output.content,
812
+ fetchedAt,
813
+ truncated: output.truncated,
814
+ notes,
815
+ };
816
+ }
744
817
 
745
- const imageMimeType = resolveImageMimeType(mime, extHint);
818
+ // Classify generic downloads from the bounded binary response before dispatch.
819
+ // Both image rendering and document conversion reuse these bytes.
820
+ const dispositionBinary =
821
+ !raw && isGenericMimeType(mime)
822
+ ? response.buffer
823
+ ? { ok: true as const, buffer: response.buffer, contentDisposition: response.contentDisposition }
824
+ : await fetchBinary(finalUrl, timeout, signal)
825
+ : undefined;
826
+ if (dispositionBinary && !dispositionBinary.ok) {
827
+ notes.push(dispositionBinary.error ? `Binary fetch failed: ${dispositionBinary.error}` : "Binary fetch failed");
828
+ return {
829
+ url,
830
+ finalUrl,
831
+ contentType: mime,
832
+ method: "failed",
833
+ content: "",
834
+ fetchedAt,
835
+ truncated: false,
836
+ notes,
837
+ };
838
+ }
839
+ const effectiveExt = dispositionBinary?.ok
840
+ ? getExtensionHint(finalUrl, dispositionBinary.contentDisposition)
841
+ : extHint;
842
+ isPdf =
843
+ mime === "application/pdf" ||
844
+ mime === "application/x-pdf" ||
845
+ (effectiveExt === ".pdf" && isGenericMimeType(mime));
846
+ const imageMimeType = isPdf ? undefined : resolveImageMimeType(mime, effectiveExt);
746
847
  let skipConvertibleBinaryRetry = false;
747
848
  if (imageMimeType) {
748
849
  if (!isInlineImageMimeTypeSupported(imageMimeType)) {
749
850
  notes.push(
750
851
  `Image MIME type ${imageMimeType} is unsupported for inline model serialization; returning text metadata only`,
751
852
  );
752
- const shouldTryConvertibleFallback = isConvertible(mime, extHint);
853
+ const shouldTryConvertibleFallback = isConvertible(mime, effectiveExt);
753
854
  if (shouldTryConvertibleFallback) {
754
855
  notes.push("Attempting binary conversion fallback for unsupported image MIME type");
755
856
  } else {
@@ -757,7 +858,7 @@ async function renderUrl(
757
858
  }
758
859
  skipConvertibleBinaryRetry = !shouldTryConvertibleFallback;
759
860
  } else {
760
- const binary = await fetchBinary(finalUrl, timeout, signal);
861
+ const binary = dispositionBinary ?? (await fetchBinary(finalUrl, timeout, signal));
761
862
  if (binary.ok) {
762
863
  notes.push("Fetched image binary");
763
864
  const conversionExtension = getExtensionHint(finalUrl, binary.contentDisposition) || extHint;
@@ -868,10 +969,10 @@ async function renderUrl(
868
969
  }
869
970
 
870
971
  // Step 3: Handle convertible binary files (PDF, DOCX, etc.)
871
- if (!skipConvertibleBinaryRetry && isConvertible(mime, extHint)) {
872
- const binary = await fetchBinary(finalUrl, timeout, signal);
972
+ if (!skipConvertibleBinaryRetry && (isPdf || isConvertible(mime, effectiveExt))) {
973
+ const binary = dispositionBinary ?? (await fetchBinary(finalUrl, timeout, signal));
873
974
  if (binary.ok) {
874
- const ext = getExtensionHint(finalUrl, binary.contentDisposition) || extHint;
975
+ const ext = isPdf ? ".pdf" : getExtensionHint(finalUrl, binary.contentDisposition) || extHint;
875
976
  const converted = await convertWithMarkit(binary.buffer, ext, timeout, signal);
876
977
  if (converted.ok) {
877
978
  // Any non-empty markit conversion is preferable to a raw-bytes
@@ -902,6 +1003,18 @@ async function renderUrl(
902
1003
  } else {
903
1004
  notes.push("Binary fetch failed");
904
1005
  }
1006
+ if (isPdf) {
1007
+ return {
1008
+ url,
1009
+ finalUrl,
1010
+ contentType: mime,
1011
+ method: "failed",
1012
+ content: "",
1013
+ fetchedAt,
1014
+ truncated: false,
1015
+ notes,
1016
+ };
1017
+ }
905
1018
  }
906
1019
 
907
1020
  // Step 4: Handle non-HTML text content
@@ -1066,7 +1179,7 @@ async function renderUrl(
1066
1179
  if (binary.ok) {
1067
1180
  const ext = getExtensionHint(docUrl, binary.contentDisposition);
1068
1181
  const converted = await convertWithMarkit(binary.buffer, ext, timeout, signal);
1069
- if (converted.ok && converted.content.trim().length > htmlResult.content.length) {
1182
+ if (converted.ok && converted.content.trim().length > (ext === ".pdf" ? 0 : htmlResult.content.length)) {
1070
1183
  notes.push(`Extracted and converted document: ${docUrl}`);
1071
1184
  const output = finalizeOutput(converted.content);
1072
1185
  return {
@@ -1082,6 +1195,8 @@ async function renderUrl(
1082
1195
  }
1083
1196
  if (!converted.ok && converted.error) {
1084
1197
  notes.push(`markit conversion failed: ${converted.error}`);
1198
+ } else if (converted.ok && !converted.content.trim()) {
1199
+ notes.push("markit conversion produced no usable output");
1085
1200
  }
1086
1201
  } else if (binary.error) {
1087
1202
  notes.push(`Binary fetch failed: ${binary.error}`);
package/src/tools/job.ts CHANGED
@@ -7,6 +7,7 @@ import { type AsyncJob, AsyncJobManager, type FoldReason, isBackgroundJobSupport
7
7
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
8
8
  import type { Theme } from "../modes/theme/theme";
9
9
  import jobDescription from "../prompts/tools/job.md" with { type: "text" };
10
+ import type { FoldAdapter } from "../session/fold-coordinator";
10
11
  import { lookupOwnedRegistration, unregisterOwnedRegistration } from "../session/terminal-abort";
11
12
  import { Ellipsis, Hasher, type RenderCache, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
12
13
  import type { ToolSession } from "./index";
@@ -21,6 +22,7 @@ import {
21
22
  type ToolUIColor,
22
23
  type ToolUIStatus,
23
24
  } from "./render-utils";
25
+ import { foldAwaitReasonLine, watchSteerForFold } from "./steer-fold";
24
26
  import { ToolError } from "./tool-errors";
25
27
 
26
28
  const jobSchema = z.object({
@@ -47,7 +49,9 @@ function parseWaitDurationMs(value: string | undefined): number {
47
49
  interface JobSnapshot {
48
50
  id: string;
49
51
  type: "bash" | "task";
50
- status: "running" | "completed" | "failed" | "cancelled";
52
+ // Mirrors the manager's job statuses, including "paused": a folded subagent
53
+ // stays listed and resumable, so its snapshot is paused rather than terminal.
54
+ status: "running" | "paused" | "completed" | "failed" | "cancelled";
51
55
  label: string;
52
56
  durationMs: number;
53
57
  /** Present when the job was folded out of a foreground wait. */
@@ -56,6 +60,11 @@ interface JobSnapshot {
56
60
  errorText?: string;
57
61
  }
58
62
 
63
+ /** Terminal statuses are the ones a consumer may treat as finished work. */
64
+ function isTerminalJobStatus(status: JobSnapshot["status"]): boolean {
65
+ return status === "completed" || status === "failed" || status === "cancelled";
66
+ }
67
+
59
68
  type CancelStatus = "cancelled" | "not_found" | "already_completed" | "already_cancelled";
60
69
 
61
70
  interface CancelOutcome {
@@ -234,6 +243,73 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
234
243
  const waitMs = parseWaitDurationMs(this.session.settings.get("async.pollWaitDuration"));
235
244
  const { promise: timeoutPromise, resolve: timeoutResolve } = Promise.withResolvers<void>();
236
245
  const timeoutHandle = setTimeout(() => timeoutResolve(), waitMs);
246
+ // Settled the moment ANY watched wait is released by a fold, so the await
247
+ // never outlives its own fold. `job-await` adapters are never implicit
248
+ // chord / SDK-control targets (the jobs are already in the background);
249
+ // only the steer watcher below folds them, explicitly. `task`/`subagent`
250
+ // waits are a non-goal, so task-backed jobs are watched but never folded.
251
+ const foldedAwait = Promise.withResolvers<void>();
252
+ const foldedJobs = new Map<string, FoldReason>();
253
+ const foldTargets: Array<{ adapter: FoldAdapter; release: (reason: FoldReason) => boolean }> = [];
254
+ const unregisterFoldAdapters: Array<() => void> = [];
255
+ const startedAt = Date.now();
256
+ for (const job of runningJobs) {
257
+ if (job.type === "task" || job.metadata?.subagent !== undefined) continue;
258
+ const generation = job.generation;
259
+ let detached = false;
260
+ const release = (reason: FoldReason): boolean => {
261
+ if (detached) return false;
262
+ detached = true;
263
+ manager.markBackgrounded(job.id, generation, reason);
264
+ foldedJobs.set(job.id, reason);
265
+ foldedAwait.resolve();
266
+ return true;
267
+ };
268
+ const adapter: FoldAdapter = {
269
+ kind: "job-await",
270
+ jobId: job.id,
271
+ jobGeneration: generation,
272
+ label: job.label,
273
+ cwdSensitive: false,
274
+ signal,
275
+ originatingTurn: false,
276
+ outputRef: {
277
+ jobId: job.id,
278
+ generation,
279
+ instruction: `Use the job tool's tail operation for ${job.id} to read this job's output.`,
280
+ },
281
+ getJob: () => {
282
+ const current = manager.getJob(job.id);
283
+ return current?.generation === generation ? current : undefined;
284
+ },
285
+ detachObserver: receipt => (release(receipt.reason) ? "resolved" : "already-settled"),
286
+ resolveForegroundObserver: () => (detached ? "already-settled" : "resolved"),
287
+ };
288
+ foldTargets.push({ adapter, release });
289
+ unregisterFoldAdapters.push(this.session.registerForegroundFoldParticipant?.(adapter) ?? (() => {}));
290
+ }
291
+ // Fold every foldable wait on one steer. A job that an earlier fold already
292
+ // moved still owns its receipt slot until it completes, so the coordinator
293
+ // refuses a second fold for it; that receipt already guarantees the wake,
294
+ // so this await is simply released for it. Every other job folds through
295
+ // the coordinator, whose `detachObserver` call releases the await with the
296
+ // recorded reason.
297
+ const stopSteerWatch =
298
+ foldTargets.length > 0
299
+ ? watchSteerForFold(this.session, startedAt, async fold => {
300
+ await Promise.all(
301
+ foldTargets.map(async ({ adapter, release }) => {
302
+ const current = adapter.getJob();
303
+ if (current?.status !== "running") return;
304
+ if (current.metadata?.foldReason !== undefined) {
305
+ release("steer");
306
+ return;
307
+ }
308
+ await fold("steer", adapter);
309
+ }),
310
+ );
311
+ })
312
+ : () => {};
237
313
  racePromises.push(timeoutPromise);
238
314
 
239
315
  const watchedJobIds = runningJobs.map(job => job.id);
@@ -277,25 +353,35 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
277
353
  signal.addEventListener("abort", onAbort, { once: true });
278
354
  racePromises.push(abortPromise);
279
355
  try {
280
- await Promise.race(racePromises);
356
+ await Promise.race([...racePromises, foldedAwait.promise]);
281
357
  } finally {
282
358
  signal.removeEventListener("abort", onAbort);
283
359
  }
284
360
  } else {
285
- await Promise.race(racePromises);
361
+ await Promise.race([...racePromises, foldedAwait.promise]);
286
362
  }
287
363
  } finally {
364
+ stopSteerWatch();
365
+ for (const unregister of unregisterFoldAdapters) unregister();
288
366
  manager.unwatchJobs(watchedJobIds);
289
367
  clearTimeout(timeoutHandle);
290
368
  if (progressTimer) clearInterval(progressTimer);
291
369
  }
292
370
 
293
- return this.#buildResult(
371
+ const result = this.#buildResult(
294
372
  manager,
295
373
  allTrackedJobs,
296
374
  cancelOutcomes,
297
375
  this.#readOutputTails(manager, params.tail, ownerFilter),
298
376
  );
377
+ if (foldedJobs.size > 0) {
378
+ const existingText = result.content.find(block => block.type === "text")?.text ?? "";
379
+ return {
380
+ ...result,
381
+ content: [{ type: "text", text: `${existingText}\n\n${foldAwaitReasonLine(foldedJobs)}`.trim() }],
382
+ };
383
+ }
384
+ return result;
299
385
  }
300
386
 
301
387
  /**
@@ -396,9 +482,7 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
396
482
  // — otherwise a scope:"owned" abort could find an empty causal set and
397
483
  // report stopped_owned while the job remains visibly paused (review
398
484
  // thread P2).
399
- const terminalJobs = jobResults.filter(
400
- j => j.status === "completed" || j.status === "failed" || j.status === "cancelled",
401
- );
485
+ const terminalJobs = jobResults.filter(j => isTerminalJobStatus(j.status));
402
486
  manager.acknowledgeDeliveries(terminalJobs.map(j => j.id));
403
487
  // A terminal job whose deliveries were just acknowledged is
404
488
  // synchronously consumed: its owned registration is settled and must
@@ -417,8 +501,11 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
417
501
  if (registration) unregisterOwnedRegistration(registration);
418
502
  }
419
503
 
420
- const completed = jobResults.filter(j => j.status !== "running");
421
- const running = jobResults.filter(j => j.status === "running");
504
+ const runningJobs = jobResults.filter(j => j.status === "running");
505
+ // Paused work (a folded or queued-resume subagent) and any status this build
506
+ // does not know are non-terminal: reporting them under ## Completed would
507
+ // tell consumers that resumable work finished.
508
+ const waitingJobs = jobResults.filter(j => !isTerminalJobStatus(j.status) && j.status !== "running");
422
509
 
423
510
  const lines: string[] = [];
424
511
 
@@ -428,9 +515,9 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
428
515
  lines.push("");
429
516
  }
430
517
 
431
- if (completed.length > 0) {
432
- lines.push(`## Completed (${completed.length})\n`);
433
- for (const j of completed) {
518
+ if (terminalJobs.length > 0) {
519
+ lines.push(`## Completed (${terminalJobs.length})\n`);
520
+ for (const j of terminalJobs) {
434
521
  lines.push(`### ${j.id} [${j.type}] — ${j.status}`);
435
522
  lines.push(`Label: ${j.label}`);
436
523
  if (j.resultText) {
@@ -443,13 +530,20 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
443
530
  }
444
531
  }
445
532
 
446
- if (running.length > 0) {
447
- lines.push(`## Still Running (${running.length})\n`);
448
- for (const j of running) {
533
+ if (runningJobs.length > 0) {
534
+ lines.push(`## Still Running (${runningJobs.length})\n`);
535
+ for (const j of runningJobs) {
449
536
  lines.push(`- \`${j.id}\` [${j.type}] — ${j.label}`);
450
537
  }
451
538
  }
452
539
 
540
+ if (waitingJobs.length > 0) {
541
+ lines.push(`## Waiting (${waitingJobs.length})\n`);
542
+ for (const j of waitingJobs) {
543
+ lines.push(`- \`${j.id}\` [${j.type}] — ${j.label} (${j.status})`);
544
+ }
545
+ }
546
+
453
547
  if (outputTails.length > 0) {
454
548
  lines.push("", `## Retained Output (${outputTails.length})\n`);
455
549
  for (const tail of outputTails) {
@@ -498,6 +592,12 @@ function statusToIcon(status: JobSnapshot["status"]): ToolUIStatus {
498
592
  return "aborted";
499
593
  case "running":
500
594
  return "running";
595
+ case "paused":
596
+ return "pending";
597
+ // Snapshot details are read back from persisted sessions, so a status this
598
+ // build does not know must still render instead of dropping the component.
599
+ default:
600
+ return "pending";
501
601
  }
502
602
  }
503
603
 
@@ -511,6 +611,12 @@ function statusToColor(status: JobSnapshot["status"]): ToolUIColor {
511
611
  return "warning";
512
612
  case "running":
513
613
  return "accent";
614
+ case "paused":
615
+ return "muted";
616
+ // Never fall through to undefined: theme.fg() throws on an undefined color
617
+ // and would take the whole job list render down with it.
618
+ default:
619
+ return "muted";
514
620
  }
515
621
  }
516
622
 
@@ -554,19 +660,36 @@ export const jobToolRenderer = {
554
660
  return new Text([header, formatEmptyMessage(fallback, uiTheme)].join("\n"), 0, 0);
555
661
  }
556
662
 
557
- const counts = { completed: 0, failed: 0, cancelled: 0, running: 0 };
558
- for (const job of jobs) counts[job.status]++;
663
+ const counts: Record<JobSnapshot["status"], number> = {
664
+ running: 0,
665
+ paused: 0,
666
+ completed: 0,
667
+ failed: 0,
668
+ cancelled: 0,
669
+ };
670
+ // Snapshots are read back from persisted sessions, so a status this build
671
+ // does not know must stay out of the typed counts instead of becoming NaN.
672
+ let unknownStatusCount = 0;
673
+ for (const job of jobs) {
674
+ if (job.status in counts) counts[job.status] += 1;
675
+ else unknownStatusCount += 1;
676
+ }
559
677
 
560
678
  const meta: string[] = [];
561
679
  if (counts.completed > 0) meta.push(uiTheme.fg("success", `${counts.completed} done`));
562
680
  if (counts.failed > 0) meta.push(uiTheme.fg("error", `${counts.failed} failed`));
563
681
  if (counts.cancelled > 0) meta.push(uiTheme.fg("warning", `${counts.cancelled} cancelled`));
682
+ if (counts.paused > 0) meta.push(uiTheme.fg("muted", `${counts.paused} paused`));
564
683
  if (counts.running > 0) meta.push(uiTheme.fg("accent", `${counts.running} running`));
684
+ if (unknownStatusCount > 0) meta.push(uiTheme.fg("muted", `${unknownStatusCount} unknown`));
565
685
 
566
- const headerIcon: ToolUIStatus = counts.failed > 0 ? "warning" : counts.running > 0 ? "info" : "success";
686
+ // Paused and unknown-status rows are unsettled work: reporting success and a
687
+ // "settled" description for a resumable snapshot would contradict the rows.
688
+ const pendingCount = counts.running + counts.paused + unknownStatusCount;
689
+ const headerIcon: ToolUIStatus = counts.failed > 0 ? "warning" : pendingCount > 0 ? "info" : "success";
567
690
  const description =
568
- counts.running > 0
569
- ? `waiting on ${counts.running} of ${jobs.length}`
691
+ pendingCount > 0
692
+ ? `waiting on ${pendingCount} of ${jobs.length}`
570
693
  : `${jobs.length} ${jobs.length === 1 ? "job" : "jobs"} settled`;
571
694
 
572
695
  const header = renderStatusLine(
@@ -580,15 +703,18 @@ export const jobToolRenderer = {
580
703
  uiTheme,
581
704
  );
582
705
 
583
- // Sort: running first (so user sees what's still pending), then failed, then completed/cancelled.
706
+ // Sort: running first (so user sees what's still pending), then paused, then
707
+ // failed, then completed/cancelled.
584
708
  const statusOrder: Record<JobSnapshot["status"], number> = {
585
709
  running: 0,
586
- failed: 1,
587
- cancelled: 2,
588
- completed: 3,
710
+ paused: 1,
711
+ failed: 2,
712
+ cancelled: 3,
713
+ completed: 4,
589
714
  };
715
+ const statusRank = (status: JobSnapshot["status"]): number => statusOrder[status] ?? Number.MAX_SAFE_INTEGER;
590
716
  const sortedJobs = [...jobs].sort((a, b) => {
591
- const diff = statusOrder[a.status] - statusOrder[b.status];
717
+ const diff = statusRank(a.status) - statusRank(b.status);
592
718
  if (diff !== 0) return diff;
593
719
  return b.durationMs - a.durationMs;
594
720
  });
@@ -26,7 +26,7 @@ import {
26
26
  } from "../session/streaming-output";
27
27
 
28
28
  import { formatBytes, wrapBrackets } from "./render-utils";
29
- import { renderError } from "./tool-errors";
29
+ import { renderError, ToolError } from "./tool-errors";
30
30
 
31
31
  /**
32
32
  * Truncation metadata for the output notice.
@@ -1130,7 +1130,20 @@ async function wrappedExecute(
1130
1130
  }
1131
1131
  return result;
1132
1132
  } catch (e) {
1133
- // Re-throw with formatted message so agent-loop sets isError flag
1133
+ // Preserve the original Error class and stack so designed tool outcomes can
1134
+ // be classified by the agent telemetry path and genuine failures retain the
1135
+ // frame where they actually occurred. Non-Error throws still need a stable
1136
+ // Error envelope for the agent loop.
1137
+ if (e instanceof ToolError) {
1138
+ const rendered = renderError(e);
1139
+ if (rendered !== e.message) {
1140
+ const renderedError = new ToolError(rendered, e.context);
1141
+ if (e.stack) renderedError.stack = e.stack;
1142
+ throw renderedError;
1143
+ }
1144
+ throw e;
1145
+ }
1146
+ if (e instanceof Error) throw e;
1134
1147
  throw new Error(renderError(e));
1135
1148
  }
1136
1149
  }
@@ -48,6 +48,18 @@ function rawSkillPrefixForName(authority: string, skillName: string): string | u
48
48
  return undefined;
49
49
  }
50
50
 
51
+ function splitSkillPathSel(rawPath: string, authorityEnd: number): { path: string; sel?: string } {
52
+ const suffix = rawPath.slice(authorityEnd);
53
+ const suffixQueryOrFragment = suffix.search(/[?#]/);
54
+ const pathnameEnd = suffixQueryOrFragment === -1 ? rawPath.length : authorityEnd + suffixQueryOrFragment;
55
+ const pathnameTarget = splitPathAndSel(rawPath.slice(authorityEnd, pathnameEnd));
56
+ if (pathnameTarget.sel === undefined) return { path: rawPath };
57
+ return {
58
+ path: `${rawPath.slice(0, authorityEnd)}${pathnameTarget.path}${rawPath.slice(pathnameEnd)}`,
59
+ sel: pathnameTarget.sel,
60
+ };
61
+ }
62
+
51
63
  function tryMacOSScreenshotPath(filePath: string): string {
52
64
  // macOS writes a narrow no-break space before AM/PM, but the model normalizes it
53
65
  // to a plain space. The original name is not always `…PM.png`: attachment paths
@@ -177,6 +189,9 @@ export function splitInternalUrlSel(
177
189
  const schemeMatch = rawPath.match(INTERNAL_URL_SCHEME_RE);
178
190
  if (!schemeMatch) return { path: rawPath };
179
191
  const scheme = schemeMatch[1].toLowerCase();
192
+ // `embedded://gjc/<path>` identifies a bundled file, so the selector always
193
+ // trails the path rather than the identifier-shaped authority.
194
+ if (scheme === "embedded") return splitPathAndSel(rawPath);
180
195
  if (!INTERNAL_SCHEMES_WITH_SELECTORS[scheme]) return { path: rawPath };
181
196
 
182
197
  const schemeEnd = schemeMatch[0].length;
@@ -185,7 +200,14 @@ export function splitInternalUrlSel(
185
200
  const authority = rawPath.slice(schemeEnd, authorityEnd);
186
201
  const authoritySuffix = rawPath.slice(authorityEnd);
187
202
  const firstColon = authority.indexOf(":");
188
- if (firstColon === -1) return { path: rawPath };
203
+ if (firstColon === -1) {
204
+ if (scheme === "skill") {
205
+ // Skill relative paths carry selectors after the authority (for example,
206
+ // `skill://foo/docs/reference.md:10-20`).
207
+ return splitSkillPathSel(rawPath, authorityEnd);
208
+ }
209
+ return { path: rawPath };
210
+ }
189
211
  if (firstColon === 0) return { path: rawPath };
190
212
 
191
213
  if (scheme === "skill") {
@@ -197,7 +219,9 @@ export function splitInternalUrlSel(
197
219
  // Let the resolver report malformed URL encoding when no selector boundary
198
220
  // can be established from the raw authority.
199
221
  }
200
- if (decodedAuthority !== undefined && activeSkillNames.includes(decodedAuthority)) return { path: rawPath };
222
+ if (decodedAuthority !== undefined && activeSkillNames.includes(decodedAuthority)) {
223
+ return splitSkillPathSel(rawPath, authorityEnd);
224
+ }
201
225
  const rawSkillPrefix = activeSkillNames
202
226
  .map(name => ({ name, prefix: rawSkillPrefixForName(authority, name) }))
203
227
  .filter(item => item.prefix !== undefined)
@@ -217,7 +241,7 @@ export function splitInternalUrlSel(
217
241
  return { path: `${rawPath.slice(0, schemeEnd + firstColon)}${authoritySuffix}`, sel: firstTail };
218
242
  }
219
243
  const selectorColon = authority.indexOf(":", firstColon + 1);
220
- if (selectorColon === -1) return { path: rawPath };
244
+ if (selectorColon === -1) return splitSkillPathSel(rawPath, authorityEnd);
221
245
  return {
222
246
  path: `${rawPath.slice(0, schemeEnd + selectorColon)}${authoritySuffix}`,
223
247
  sel: authority.slice(selectorColon + 1),