@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
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),
package/src/tools/read.ts CHANGED
@@ -14,7 +14,7 @@ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
14
14
  import { getActiveSkills } from "../extensibility/skills";
15
15
  import { formatHashLine, formatHashLines, formatLineHash, HL_BODY_SEP } from "../hashline/hash";
16
16
  import { InternalUrlRouter } from "../internal-urls";
17
- import { parseInternalUrl } from "../internal-urls/parse";
17
+ import { normalizeInternalUrlInput, parseInternalUrl } from "../internal-urls/parse";
18
18
  import type { InternalUrl } from "../internal-urls/types";
19
19
  import { getLanguageFromPath, type Theme } from "../modes/theme/theme";
20
20
  import readDescription from "../prompts/tools/read.md" with { type: "text" };
@@ -2745,7 +2745,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
2745
2745
  // off the URL and surfaced via parseSel rather than confusing handlers.
2746
2746
  const internalRouter = InternalUrlRouter.instance();
2747
2747
  if (internalRouter.canHandle(readPath)) {
2748
- const internalTarget = splitInternalUrlSel(readPath, {
2748
+ const internalTarget = splitInternalUrlSel(normalizeInternalUrlInput(readPath), {
2749
2749
  activeSkillNames: getActiveSkills().map(skill => skill.name),
2750
2750
  });
2751
2751
  const parsed = parseSel(internalTarget.sel);
@@ -3590,7 +3590,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
3590
3590
  sourcePath: resource.sourcePath,
3591
3591
  sourceInternal: url,
3592
3592
  entityLabel: "resource",
3593
- ignoreResultLimits: scheme === "skill",
3593
+ ignoreResultLimits: scheme === "skill" || scheme === "embedded",
3594
3594
  immutable: resource.immutable,
3595
3595
  raw,
3596
3596
  truncationDirection: direction,
@@ -0,0 +1,107 @@
1
+ import { logger } from "@gajae-code/utils";
2
+ import type { FoldReason } from "../async/job-manager";
3
+ import type { ToolSession } from ".";
4
+
5
+ /** A short foreground wait stays in the foreground even when a steer is admitted. */
6
+ export const STEER_FOLD_GRACE_MS = 2_000;
7
+
8
+ /** Model-facing line appended to a steer-folded background-start result. */
9
+ export function steerFoldReasonLine(jobId: string): string {
10
+ return `Folded into background job ${jobId} because a user steer arrived; the command keeps running with its original timeout and its result will wake a later turn.`;
11
+ }
12
+
13
+ /** Model-facing cause clause for a fold, keyed by the recorded {@link FoldReason}. */
14
+ export function foldReasonClause(reason: FoldReason): string {
15
+ switch (reason) {
16
+ case "steer":
17
+ return "because a user steer arrived";
18
+ case "chord":
19
+ return "because the user pressed the fold chord";
20
+ case "sdk_control":
21
+ return "because an SDK client requested it";
22
+ case "timer":
23
+ return "because the auto-background threshold elapsed";
24
+ }
25
+ }
26
+
27
+ /** Model-facing line used when the `job` tool's own wait is folded; states the real trigger. */
28
+ export function foldAwaitReasonLine(folded: ReadonlyMap<string, FoldReason>): string {
29
+ const jobIds = [...folded.keys()];
30
+ const names = jobIds.map(id => `\`${id}\``).join(", ");
31
+ const reasons = [...new Set(folded.values())];
32
+ const single = reasons.length === 1 ? reasons[0] : undefined;
33
+ const cause = single ? foldReasonClause(single) : "because the waits were folded";
34
+ const plural = jobIds.length !== 1;
35
+ return `Folded the job await for ${names} ${cause}; the job${plural ? "s keep" : " keeps"} running with ${plural ? "their original deadlines" : "its original deadline"}, and ${plural ? "their results" : "its result"} will wake a later turn.`;
36
+ }
37
+
38
+ type SteerFoldSession = Pick<ToolSession, "settings" | "waitForUserSteering" | "requestForegroundBashBackground">;
39
+
40
+ /** The session hooks a steer fold needs, resolved once so callers never re-read and non-null-assert them. */
41
+ interface SteerFoldHooks {
42
+ waitForSteer: NonNullable<ToolSession["waitForUserSteering"]>;
43
+ requestFold: NonNullable<ToolSession["requestForegroundBashBackground"]>;
44
+ }
45
+
46
+ /**
47
+ * Whether a queued user steer may fold the running foreground wait. Mirrors
48
+ * the loop's steer admission: `busyPromptMode=queue` never admits a steer
49
+ * into the busy run, so it never folds. `toolInterruptPolicy` is
50
+ * deliberately NOT a gate: `finish_tools` means "do not kill the batch to
51
+ * deliver a steer", and a fold kills nothing. It is the one way to deliver
52
+ * the steer now AND let the command finish, so it applies under both
53
+ * policies; the policy only decides what the loop does with sibling tools
54
+ * after the fold returns. The auto-background setting is likewise not
55
+ * consulted: like the chord, a steer fold is a user action.
56
+ *
57
+ * Fails closed: a session that cannot report newly admitted steering or
58
+ * accept a fold request is not steer-foldable.
59
+ */
60
+ function steerFoldHooks(session: SteerFoldSession): SteerFoldHooks | undefined {
61
+ const { waitForUserSteering, requestForegroundBashBackground } = session;
62
+ if (!waitForUserSteering || !requestForegroundBashBackground) return undefined;
63
+ if (session.settings.get("busyPromptMode") !== "steer") return undefined;
64
+ return { waitForSteer: waitForUserSteering, requestFold: requestForegroundBashBackground };
65
+ }
66
+
67
+ /**
68
+ * Fold on the first user steer that ARRIVES after the wait has run for
69
+ * {@link STEER_FOLD_GRACE_MS} since `startedAt`. A steer already queued when
70
+ * the wait starts, or one that arrives inside the grace window, never folds:
71
+ * the wait finishes normally and that steer is consumed at the ordinary tool
72
+ * boundary. The watcher keeps observing so a later qualifying steer still
73
+ * folds, and re-checks the gate at that moment so a `busyPromptMode` change
74
+ * during the wait is honored. `requestFold` receives the session's resolved
75
+ * fold request so the caller targets its own adapter without re-reading the
76
+ * optional session hook.
77
+ * Returns a stop function; call it when the wait settles. No-op when steer
78
+ * folding is gated off at start.
79
+ */
80
+ export function watchSteerForFold(
81
+ session: SteerFoldSession,
82
+ startedAt: number,
83
+ requestFold: (fold: SteerFoldHooks["requestFold"]) => Promise<unknown>,
84
+ jobId?: string,
85
+ ): () => void {
86
+ const hooks = steerFoldHooks(session);
87
+ if (!hooks) return () => {};
88
+ const watch = new AbortController();
89
+ const observe = async (): Promise<void> => {
90
+ while (!watch.signal.aborted) {
91
+ await hooks.waitForSteer(watch.signal);
92
+ if (watch.signal.aborted) return;
93
+ if (Date.now() - startedAt < STEER_FOLD_GRACE_MS) continue;
94
+ const current = steerFoldHooks(session);
95
+ if (!current) continue;
96
+ await requestFold(current.requestFold);
97
+ return;
98
+ }
99
+ };
100
+ observe().catch((error: unknown) => {
101
+ logger.warn("Steer-triggered fold failed", {
102
+ ...(jobId ? { jobId } : {}),
103
+ error: error instanceof Error ? error.message : String(error),
104
+ });
105
+ });
106
+ return () => watch.abort();
107
+ }
@@ -27,7 +27,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
27
27
  "read": {
28
28
  "name": "read",
29
29
  "label": "Read",
30
- "description": "Read files, directories, archives, SQLite databases, images, documents, internal resources, and web URLs through a single `path` string.\n\n<instruction>\n- One tool for filesystem, archives, SQLite, images, documents (PDF/DOCX/PPTX/XLSX/RTF/EPUB/ipynb), internal URIs, and web URLs (reader-mode by default).\n- You SHOULD parallelize independent reads when exploring related files.\n- You SHOULD reach for `read` — not a browser/puppeteer tool — for fetching web content.\n</instruction>\n\n## Parameters\n\n- `path` — required. Local path, internal URI (`agent://`, `artifact://`, `rule://`, `local://`), or URL. Append `:<sel>` for line ranges, raw mode, or special modes (e.g. `src/foo.ts:50-200`, `src/foo.ts:raw`, `db.sqlite:users:42`).\n- `truncation` — optional `head` | `last` | `both`; selects which end of an over-budget result to retain. Configured default: last (factory default: `last`); structural summaries and routes such as URLs, directories and converted documents default to `head`. A line-range selector still bounds the selection — this only picks which end of that selection survives the byte/line cap. SQLite row queries page via their own `limit`/`offset` and ignore it.\n## Selectors\nAppend `:<sel>` to `path`. The bare path falls back to the default mode.\n\n- _(none)_ — parseable code → structural summary (signatures kept, bodies elided); a plain text file → a bounded receipt of about undefined lines or undefined KiB, whichever is smaller; the configured truncation direction is last (factory default: last). Line+hash anchors keep their real file line numbers and a footer names the omitted range. Archive members use the larger 3000-line / 50 KiB budget. Converted documents, notebooks, URLs and directory listings still start from the beginning.\n- `:50` / `:50-` — read from line 50 onward.\n- `:50-200` — lines 50–200 inclusive.\n- `:50+150` — 150 lines starting at line 50.\n- `:20+1` — exactly one line.\n- `:5-16,960-973` — multiple ranges in one call (sorted, overlaps merged).\n- `:raw` — verbatim text; no anchors, no summary, no line prefixes.\n- `:2-4:raw` or `:raw:2-4` — range AND verbatim; the two compose in either order.\n- `:conflicts` — one-line-per-block index of every unresolved git merge conflict.\n\n# Files\n\n- Reading a directory path returns a depth-limited dirent listing.\n- Parseable code without a selector returns a **structural summary**: declarations kept, large bodies collapsed to `..` (merged brace pair) or `…` (standalone). Summarized output ends with a footer of the form:\n\n `[NN lines across MM elided regions; read <path>:raw or a line range like <path>:1-9999 for verbatim content]`\n\n If the elided body is what you actually need, re-issue the **exact selector the footer names**. NEVER guess what's inside `..` / `…` — those markers carry no content.\n- Structural summaries honor explicit `truncation`: `head` retains the beginning, `last` the end, and `both` both ends within the summary byte budget. Complete summary units and their original source anchors stay intact and in source order; budget omissions are marked where they occur. This does not switch to raw source. Summaries that fit are unchanged by direction.\n- Directional windows identify the retained first/last lines and the omitted range; use the `re-read <path>:1-<total>` or `:raw` hint in the footer to recover the full content.\n\n# Documents & Notebooks\n\nExtracts text from PDF, Word, PowerPoint, Excel, RTF, and EPUB. Notebooks (`.ipynb`) are shown as editable `# %% [type] cell:N` text; edits round-trip back to the underlying JSON preserving notebook metadata. Add `:raw` to a notebook to bypass the converter and read the JSON directly.\n\n# Images\n\nReading an image path returns the image itself for visual inspection by a vision-capable model.\n\n# Archives\n\nSupports `.tar`, `.tar.gz`, `.tgz`, `.zip`. Use `archive.ext:path/inside/archive` to read a member, and append a normal selector to the inner path: `archive.zip:dir/file.ts:50-60`.\n\n# SQLite\n\nFor `.sqlite`, `.sqlite3`, `.db`, `.db3`:\n- `file.db` — list tables with row counts\n- `file.db:table` — schema + sample rows\n- `file.db:table:key` — single row by primary key\n- `file.db:table?limit=50&offset=100` — paginated rows\n- `file.db:table?where=status='active'&order=created:desc` — filtered rows\n- `file.db?q=SELECT …` — read-only SELECT query\n\n# URLs\n\n- Default reader-mode: HTML pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom, JSON endpoints, PDFs → clean text/markdown.\n- `:raw` returns untouched HTML; line selectors (`:50`, `:50-100`, `:50+150`) paginate the cached fetched output.\n- Bare `host:port` URLs collide with the selector grammar — add a trailing slash before the selector: `https://example.com/:80`.\n\n# Internal URIs\n\n`agent://<id>`, `artifact://<id>`, `rule://<name>`, and `local://<name>.md` resolve transparently and accept the same line selectors as filesystem paths. Use `artifact://<id>` to recover full output that a previous bash/eval/tool result spilled or truncated.\n\n<critical>\n- Always include `path`; never call `read` with `{}`.\n- For line ranges, append the selector to `path`.\n- Re-issue the selector named by a summary footer before relying on elided content.\n</critical>",
30
+ "description": "Read files, directories, archives, SQLite databases, images, documents, internal resources, and web URLs through a single `path` string.\n\n<instruction>\n- One tool for filesystem, archives, SQLite, images, documents (PDF/DOCX/PPTX/XLSX/RTF/EPUB/ipynb), internal URIs, and web URLs (reader-mode by default).\n- You SHOULD parallelize independent reads when exploring related files.\n- You SHOULD reach for `read` — not a browser/puppeteer tool — for fetching web content.\n</instruction>\n\n## Parameters\n\n- `path` — required. Local path, internal URI (`agent://`, `artifact://`, `rule://`, `local://`), or URL. Append `:<sel>` for line ranges, raw mode, or special modes (e.g. `src/foo.ts:50-200`, `src/foo.ts:raw`, `db.sqlite:users:42`).\n- `truncation` — optional `head` | `last` | `both`; selects which end of an over-budget result to retain. Configured default: last (factory default: `last`); structural summaries and routes such as URLs, directories and converted documents default to `head`. A line-range selector still bounds the selection — this only picks which end of that selection survives the byte/line cap. SQLite row queries page via their own `limit`/`offset` and ignore it.\n## Selectors\nAppend `:<sel>` to `path`. The bare path falls back to the default mode.\n\n- _(none)_ — parseable code → structural summary (signatures kept, bodies elided); a plain text file → a bounded receipt of about undefined lines or undefined KiB, whichever is smaller; the configured truncation direction is last (factory default: last). Line+hash anchors keep their real file line numbers and a footer names the omitted range. Archive members use the larger 3000-line / 50 KiB budget. Converted documents, notebooks, URLs and directory listings still start from the beginning.\n- `:50` / `:50-` — read from line 50 onward.\n- `:50-200` — lines 50–200 inclusive.\n- `:50+150` — 150 lines starting at line 50.\n- `:20+1` — exactly one line.\n- `:5-16,960-973` — multiple ranges in one call (sorted, overlaps merged).\n- `:raw` — verbatim text; no anchors, no summary, no line prefixes.\n- `:2-4:raw` or `:raw:2-4` — range AND verbatim; the two compose in either order.\n- `:conflicts` — one-line-per-block index of every unresolved git merge conflict.\n\n# Files\n\n- Reading a directory path returns a depth-limited dirent listing.\n- Parseable code without a selector returns a **structural summary**: declarations kept, large bodies collapsed to `..` (merged brace pair) or `…` (standalone). Summarized output ends with a footer of the form:\n\n `[NN lines across MM elided regions; read <path>:raw or a line range like <path>:1-9999 for verbatim content]`\n\n If the elided body is what you actually need, re-issue the **exact selector the footer names**. NEVER guess what's inside `..` / `…` — those markers carry no content.\n- Structural summaries honor explicit `truncation`: `head` retains the beginning, `last` the end, and `both` both ends within the summary byte budget. Complete summary units and their original source anchors stay intact and in source order; budget omissions are marked where they occur. This does not switch to raw source. Summaries that fit are unchanged by direction.\n- Directional windows identify the retained first/last lines and the omitted range; use the `re-read <path>:1-<total>` or `:raw` hint in the footer to recover the full content.\n\n# Documents & Notebooks\n\nExtracts text from PDF, Word, PowerPoint, Excel, RTF, and EPUB. Notebooks (`.ipynb`) are shown as editable `# %% [type] cell:N` text; edits round-trip back to the underlying JSON preserving notebook metadata. Add `:raw` to a notebook to bypass the converter and read the JSON directly.\n\n# Images\n\nReading an image path returns the image itself for visual inspection by a vision-capable model.\n\n# Archives\n\nSupports `.tar`, `.tar.gz`, `.tgz`, `.zip`. Use `archive.ext:path/inside/archive` to read a member, and append a normal selector to the inner path: `archive.zip:dir/file.ts:50-60`.\n\n# SQLite\n\nFor `.sqlite`, `.sqlite3`, `.db`, `.db3`:\n- `file.db` — list tables with row counts\n- `file.db:table` — schema + sample rows\n- `file.db:table:key` — single row by primary key\n- `file.db:table?limit=50&offset=100` — paginated rows\n- `file.db:table?where=status='active'&order=created:desc` — filtered rows\n- `file.db?q=SELECT …` — read-only SELECT query\n\n# URLs\n\n- Default reader-mode: HTML pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom, JSON endpoints, PDFs → clean text/markdown.\n- `:raw` returns untouched HTML; line selectors (`:50`, `:50-100`, `:50+150`) paginate the cached fetched output.\n- Bare `host:port` URLs collide with the selector grammar — add a trailing slash before the selector: `https://example.com/:80`.\n\n# Internal URIs\n\n`agent://<id>`, `artifact://<id>`, `rule://<name>`, and `local://<name>.md` resolve transparently and accept the same line selectors as filesystem paths. Use `artifact://<id>` to recover full output that a previous bash/eval/tool result spilled or truncated.\n\nBundled skills have no filesystem home, so the skill tool and skill discovery report them as `embedded:gjc/skills/<name>/SKILL.md`; read that identifier verbatim. Bundled skill fragments, when surfaced, likewise use `embedded:gjc/skill-fragments/...` identifiers.\n\n<critical>\n- Always include `path`; never call `read` with `{}`.\n- For line ranges, append the selector to `path`.\n- Re-issue the selector named by a summary footer before relying on elided content.\n</critical>",
31
31
  "parameters": {
32
32
  "type": "object",
33
33
  "properties": {
@@ -89,6 +89,33 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
89
89
  "pty": {
90
90
  "type": "boolean",
91
91
  "description": "run in pty mode"
92
+ },
93
+ "activity": {
94
+ "description": "declare browser activity for the aside browser backend: repl or exec",
95
+ "type": "object",
96
+ "properties": {
97
+ "kind": {
98
+ "type": "string",
99
+ "const": "browser"
100
+ },
101
+ "provider": {
102
+ "type": "string",
103
+ "const": "aside"
104
+ },
105
+ "mode": {
106
+ "type": "string",
107
+ "enum": [
108
+ "repl",
109
+ "exec"
110
+ ]
111
+ }
112
+ },
113
+ "required": [
114
+ "kind",
115
+ "provider",
116
+ "mode"
117
+ ],
118
+ "additionalProperties": false
92
119
  }
93
120
  },
94
121
  "required": [
@@ -2326,7 +2353,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
2326
2353
  "job": {
2327
2354
  "name": "job",
2328
2355
  "label": "Job",
2329
- "description": "Inspects, waits, or cancels async jobs.\n\nBackground job results are delivered automatically when complete. Jobs that back task subagents should be controlled via the `subagent` tool when it is available; use `job` for non-subagent jobs (async bash, monitors) and as a compatibility fallback. Running job output stays quiet by default to avoid flooding the conversation; use `tail` when you explicitly want to show/reopen retained output. Reach for this tool only when you need to inspect or intervene.\n\nIn the interactive TUI, a running foreground command can be folded into a background job by pressing the fold chord twice (default `Alt+Shift+B`, plus `Cmd+B` on macOS; remappable via `app.tool.backgroundFold`), or automatically when the user sends a steer after the command has run for at least 2 seconds (with `busyPromptMode=steer` (`toolInterruptPolicy` is not a gate)). This covers managed non-PTY bash, editor/ACP client-terminal commands, and PTY-mode commands. A folded job keeps its original deadline, is never killed or restarted by folding, and its result wakes a later turn automatically; a folded PTY continues output-only and no longer accepts input. Job listings expose `foldReason` (`steer`, `chord`, `timer`, or `sdk_control`) beside `backgrounded` so you can tell why a job left the foreground. `task`/`subagent` waits are not foldable. Raw shell `Ctrl+Z`/`bg` is not the supported path inside GJC because it bypasses job ownership and output-routing contracts.\n\n# Operations\n\n## `list: true`\nUse to inspect what's running.\n\n## `tail: [id, …]`\nShow the retained output buffer for one or more background jobs without waiting.\n- Use this to reopen/tail a backgrounded long-running bash/tool output after folding it away.\n- Output is bounded by the manager retention window; stale cursors may report that only the retained tail is available.\n- Prefer `tail` over polling when you only need to peek at progress, so the conversation can continue without flooding the TUI.\n\n## `poll: [id, …]`\nBlock until the specified jobs finish or the wait window (~30 s, not configurable) elapses.\n- Use when you are genuinely blocked on a result and have no other work to do.\n- Returns the current snapshot when the timer elapses; running jobs remain running.\n- Completed jobs include their final output in the returned snapshot.\n\n## `cancel: [id, …]`\nStop running jobs.\n- Use when a job is stalled, hung, or no longer needed.\n- Returns immediately after cancelling.",
2356
+ "description": "Inspects, waits, or cancels async jobs.\n\nBackground job results are delivered automatically when complete. Jobs that back task subagents should be controlled via the `subagent` tool when it is available; use `job` for non-subagent jobs (async bash, monitors) and as a compatibility fallback. Running job output stays quiet by default to avoid flooding the conversation; use `tail` when you explicitly want to show/reopen retained output. Reach for this tool only when you need to inspect or intervene.\n\nIn the interactive TUI, a running foreground command can be folded into a background job by pressing the fold chord twice (default `Alt+Shift+B`, plus `Cmd+B` on macOS; remappable via `app.tool.backgroundFold`), or automatically when the user sends a steer after the command has run for at least 2 seconds (with `busyPromptMode=steer` (`toolInterruptPolicy` is not a gate)). This covers managed non-PTY bash, editor/ACP client-terminal commands, and PTY-mode commands. A folded job keeps its original deadline, is never killed or restarted by folding, and its result wakes a later turn automatically; a folded PTY continues output-only and no longer accepts input. A `job` await also folds after the same 2-second grace window when a steer arrives, returning immediately with the still-running job id(s) while those jobs keep their original deadlines and wake a later turn with their results. Job listings expose `foldReason` (`steer`, `chord`, `timer`, or `sdk_control`) beside `backgrounded` so you can tell why a job left the foreground. `task`/`subagent` waits are not foldable. Raw shell `Ctrl+Z`/`bg` is not the supported path inside GJC because it bypasses job ownership and output-routing contracts.\n\n# Operations\n\n## `list: true`\nUse to inspect what's running.\n\n## `tail: [id, …]`\nShow the retained output buffer for one or more background jobs without waiting.\n- Use this to reopen/tail a backgrounded long-running bash/tool output after folding it away.\n- Output is bounded by the manager retention window; stale cursors may report that only the retained tail is available.\n- Prefer `tail` over polling when you only need to peek at progress, so the conversation can continue without flooding the TUI.\n\n## `poll: [id, …]`\nBlock until the specified jobs finish or the wait window (~30 s, not configurable) elapses.\n- Use when you are genuinely blocked on a result and have no other work to do.\n- Returns the current snapshot when the timer elapses; running jobs remain running.\n- Completed jobs include their final output in the returned snapshot.\n\n## `cancel: [id, …]`\nStop running jobs.\n- Use when a job is stalled, hung, or no longer needed.\n- Returns immediately after cancelling.",
2330
2357
  "parameters": {
2331
2358
  "type": "object",
2332
2359
  "properties": {
@@ -1,3 +1,5 @@
1
+ import { markDesignedError } from "@gajae-code/utils/error-classification";
2
+
1
3
  /**
2
4
  * Standardized error types for tool execution.
3
5
  *
@@ -16,6 +18,7 @@ export class ToolError extends Error {
16
18
  ) {
17
19
  super(message);
18
20
  this.name = "ToolError";
21
+ markDesignedError(this);
19
22
  }
20
23
 
21
24
  /** Render error for LLM consumption. Override for custom formatting. */
@@ -33,6 +36,7 @@ export class ToolAbortError extends Error {
33
36
  constructor(message: string = ToolAbortError.MESSAGE) {
34
37
  super(message);
35
38
  this.name = "ToolAbortError";
39
+ markDesignedError(this);
36
40
  }
37
41
  }
38
42