@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/utils/git.ts CHANGED
@@ -805,10 +805,18 @@ export const status = Object.assign(
805
805
  // ════════════════════════════════════════════════════════════════════════════
806
806
 
807
807
  export const stage = {
808
- /** Stage files. Empty array stages all (`git add -A`). */
809
- async files(cwd: string, files: readonly string[] = [], signal?: AbortSignal): Promise<void> {
808
+ /**
809
+ * Stage files. Empty array stages all (`git add -A`). Accepts a bare signal or
810
+ * `{ env, signal }`; `env` exists so a caller can stage into an isolated
811
+ * `GIT_INDEX_FILE` instead of the repository's real index.
812
+ */
813
+ async files(
814
+ cwd: string,
815
+ files: readonly string[] = [],
816
+ options: AbortSignal | Pick<CommandOptions, "env" | "signal"> = {},
817
+ ): Promise<void> {
810
818
  const args = files.length === 0 ? ["add", "-A"] : ["add", "--", ...files];
811
- await runEffect(cwd, args, { signal });
819
+ await runEffect(cwd, args, options instanceof AbortSignal ? { signal: options } : options);
812
820
  },
813
821
 
814
822
  /** Selectively stage hunks from the provided diff or the current working tree diff. */
@@ -902,6 +910,28 @@ export async function writeTree(cwd: string, options: Pick<CommandOptions, "env"
902
910
  return (await runText(cwd, ["write-tree"], options)).trim();
903
911
  }
904
912
 
913
+ export interface CommitTreeOptions {
914
+ readonly env?: Record<string, string | undefined>;
915
+ readonly parents?: readonly string[];
916
+ readonly signal?: AbortSignal;
917
+ }
918
+
919
+ /**
920
+ * Create a commit object from an existing tree and return its SHA. Plumbing:
921
+ * unlike `commit` this touches neither the index nor any ref, and runs no
922
+ * `pre-commit`/`commit-msg` hook.
923
+ */
924
+ export async function commitTree(
925
+ cwd: string,
926
+ tree: string,
927
+ message: string,
928
+ options: CommitTreeOptions = {},
929
+ ): Promise<string> {
930
+ const args = ["commit-tree", tree];
931
+ for (const parent of options.parents ?? []) args.push("-p", parent);
932
+ return (await runText(cwd, args, { env: options.env, signal: options.signal, stdin: message })).trim();
933
+ }
934
+
905
935
  // ════════════════════════════════════════════════════════════════════════════
906
936
  // API: show
907
937
  // ════════════════════════════════════════════════════════════════════════════
@@ -1075,6 +1105,26 @@ export const ref = {
1075
1105
  return result.stdout.trim() || null;
1076
1106
  },
1077
1107
 
1108
+ /**
1109
+ * Move a ref to `newValue`, but only while it still holds `expectedOldValue`
1110
+ * (the empty string asserts the ref does not exist yet). Git verifies the old
1111
+ * value and writes the new one under its own ref lock, so this is an atomic
1112
+ * compare-and-swap: a concurrent writer that moved the ref first makes this
1113
+ * throw instead of silently clobbering their commit.
1114
+ */
1115
+ async update(
1116
+ cwd: string,
1117
+ refName: string,
1118
+ newValue: string,
1119
+ expectedOldValue: string,
1120
+ options: { reason?: string; signal?: AbortSignal } = {},
1121
+ ): Promise<void> {
1122
+ const args = ["update-ref"];
1123
+ if (options.reason) args.push("-m", options.reason);
1124
+ args.push(refName, newValue, expectedOldValue);
1125
+ await runEffect(cwd, args, { signal: options.signal });
1126
+ },
1127
+
1078
1128
  /** Tags pointing at a ref. */
1079
1129
  async tags(cwd: string, refName = "HEAD", signal?: AbortSignal): Promise<string[]> {
1080
1130
  return splitLines(
@@ -1,7 +1,7 @@
1
1
  import { untilAborted } from "@gajae-code/utils";
2
- import type { Markit, StreamInfo } from "markit-ai";
2
+ import type { Markit, StreamInfo } from "../../vendor/markit-ai/dist/index.js";
3
3
  import { ToolAbortError } from "../tools/tool-errors";
4
- import { ensureMupdfWasmResolution } from "./mupdf-wasm";
4
+ import { prepareMuPdf, sanitizeMuPdfDiagnostic, withMuPdfDiagnostic } from "./mupdf";
5
5
 
6
6
  export interface MarkitConversionResult {
7
7
  content: string;
@@ -9,20 +9,23 @@ export interface MarkitConversionResult {
9
9
  error?: string;
10
10
  }
11
11
 
12
- let markit: () => Markit | Promise<Markit> = async () => {
13
- const promise = import("markit-ai").then(({ Markit }) => {
14
- const instance = new Markit();
15
- markit = () => instance;
16
- return instance;
17
- });
18
- markit = () => promise;
19
- return promise;
20
- };
12
+ let instance: Markit | undefined;
13
+ let instancePromise: Promise<Markit> | undefined;
21
14
 
22
- // #5433: mupdf's wasm asset is not reachable at the Emscripten loader's
23
- // default bunfs path in compiled binaries. Seed the embedded-asset hook
24
- // before any markit conversion (and therefore before any mupdf import).
25
- ensureMupdfWasmResolution();
15
+ async function loadMarkit(): Promise<Markit> {
16
+ if (instancePromise === undefined) {
17
+ instancePromise = import("../../vendor/markit-ai/dist/index.js")
18
+ .then(({ Markit: MarkitConstructor }) => {
19
+ instance = new MarkitConstructor();
20
+ return instance;
21
+ })
22
+ .catch(error => {
23
+ instancePromise = undefined;
24
+ throw error;
25
+ });
26
+ }
27
+ return instancePromise;
28
+ }
26
29
 
27
30
  function normalizeExtension(extension: string): string {
28
31
  const trimmed = extension.trim().toLowerCase();
@@ -30,33 +33,27 @@ function normalizeExtension(extension: string): string {
30
33
  return trimmed.startsWith(".") ? trimmed : `.${trimmed}`;
31
34
  }
32
35
 
33
- function normalizeError(error: unknown): string {
34
- if (error instanceof Error && error.message.trim().length > 0) {
35
- return error.message.trim();
36
- }
37
- return "Conversion failed";
38
- }
39
-
40
- /**
41
- * markit-ai's PDF converter swallows the real mupdf import failure behind a
42
- * generic "Install it" message (#5433). Re-run the import here so release
43
- * diagnostics carry the actual module/asset/initialization error.
44
- */
45
- async function describeConversionError(error: unknown): Promise<string> {
46
- const base = normalizeError(error);
47
- if (!base.includes("PDF support requires 'mupdf'")) return base;
48
- try {
49
- await import("mupdf");
50
- } catch (cause) {
51
- return `${base} (mupdf import failed: ${normalizeError(cause)})`;
36
+ function normalizeError(error: unknown, pdf = false): string {
37
+ const messages: string[] = [];
38
+ const seen = new Set<unknown>();
39
+ while (error !== undefined && !seen.has(error)) {
40
+ seen.add(error);
41
+ if (error instanceof Error) {
42
+ const message = `${error.name}: ${error.message}`;
43
+ messages.push(pdf ? sanitizeMuPdfDiagnostic(message) : message);
44
+ error = error.cause;
45
+ } else {
46
+ messages.push(pdf ? sanitizeMuPdfDiagnostic(String(error)) : String(error));
47
+ break;
48
+ }
52
49
  }
53
- return base;
50
+ return messages.join("; caused by: ") || "Conversion failed";
54
51
  }
55
52
 
56
53
  async function runMarkitConversion<T>(task: (markit: Markit) => Promise<T>, signal?: AbortSignal): Promise<T> {
57
54
  try {
58
- const instance = await markit();
59
- return signal ? await untilAborted(signal, () => task(instance)) : await task(instance);
55
+ const markit = instance ?? (await loadMarkit());
56
+ return signal ? await untilAborted(signal, () => task(markit)) : await task(markit);
60
57
  } catch (error) {
61
58
  if (error instanceof ToolAbortError) {
62
59
  throw error;
@@ -78,13 +75,22 @@ function finalizeConversion(markdown?: string): MarkitConversionResult {
78
75
 
79
76
  export async function convertFileWithMarkit(filePath: string, signal?: AbortSignal): Promise<MarkitConversionResult> {
80
77
  try {
81
- const result = await runMarkitConversion(markit => markit.convertFile(filePath), signal);
78
+ if (filePath.toLowerCase().endsWith(".pdf")) await prepareMuPdf();
79
+ const result = await runMarkitConversion(async markit => {
80
+ return markit.convertFile(filePath);
81
+ }, signal);
82
82
  return finalizeConversion(result.markdown);
83
83
  } catch (error) {
84
84
  if (error instanceof ToolAbortError) {
85
85
  throw error;
86
86
  }
87
- return { content: "", ok: false, error: await describeConversionError(error) };
87
+ return {
88
+ content: "",
89
+ ok: false,
90
+ error: filePath.toLowerCase().endsWith(".pdf")
91
+ ? normalizeError(withMuPdfDiagnostic(error), true)
92
+ : normalizeError(error),
93
+ };
88
94
  }
89
95
  }
90
96
 
@@ -100,12 +106,20 @@ export async function convertBufferWithMarkit(
100
106
  };
101
107
 
102
108
  try {
103
- const result = await runMarkitConversion(markit => markit.convert(Buffer.from(buffer), streamInfo), signal);
109
+ if (normalizedExtension === ".pdf") await prepareMuPdf();
110
+ const result = await runMarkitConversion(async markit => {
111
+ return markit.convert(Buffer.from(buffer), streamInfo);
112
+ }, signal);
104
113
  return finalizeConversion(result.markdown);
105
114
  } catch (error) {
106
115
  if (error instanceof ToolAbortError) {
107
116
  throw error;
108
117
  }
109
- return { content: "", ok: false, error: await describeConversionError(error) };
118
+ return {
119
+ content: "",
120
+ ok: false,
121
+ error:
122
+ normalizedExtension === ".pdf" ? normalizeError(withMuPdfDiagnostic(error), true) : normalizeError(error),
123
+ };
110
124
  }
111
125
  }
@@ -0,0 +1,4 @@
1
+ declare module "*.wasm" {
2
+ const assetPath: string;
3
+ export default assetPath;
4
+ }
@@ -0,0 +1,4 @@
1
+ // AUTOGENERATED FILE -- DO NOT EDIT DIRECTLY
2
+ // See scripts/embed-mupdf.ts. Source installs resolve the package lazily.
3
+ export const embeddedMuPdfWasm: string | undefined = undefined;
4
+ export const embeddedMuPdfModule: string | undefined = undefined;
@@ -0,0 +1,3 @@
1
+ import mupdfWasmPath from "../../../../node_modules/mupdf/dist/mupdf-wasm.wasm" with { type: "file" };
2
+
3
+ export default mupdfWasmPath;
@@ -14,13 +14,35 @@
14
14
  * read the wasm from the bunfs directly — no disk sidecar needed.
15
15
  *
16
16
  * This must run before the first `import("mupdf")` anywhere in the process.
17
+ *
18
+ * The embedded asset lives behind a separate module (#5663) so that an
19
+ * npm-installed source tree never resolves the monorepo-only
20
+ * `node_modules/mupdf/...` specifier at import time. That module is pulled in
21
+ * with a synchronous `require`, NOT a top-level `await import(...)`: a
22
+ * top-level await here makes this module async, and bun 1.4.0 fails to
23
+ * propagate that async-ness through the `model-registry` <-> `model-resolver`
24
+ * import cycle. The resulting bundle emits a non-async `__esm(() => { ...
25
+ * await init_model_registry(); ... })` wrapper, so every compiled binary died
26
+ * at parse time with `SyntaxError: Unexpected identifier
27
+ * 'init_model_registry'` (#5674). Keep this resolution synchronous.
17
28
  */
18
- import mupdfWasmPath from "../../../../node_modules/mupdf/dist/mupdf-wasm.wasm" with { type: "file" };
29
+ import { isCompiledBinary } from "@gajae-code/utils/env";
19
30
 
20
31
  const MODULE_CONFIG_KEY = "$libmupdf_wasm_Module";
21
32
 
33
+ function loadEmbeddedMupdfWasmPath(): string | undefined {
34
+ if (!isCompiledBinary()) return undefined;
35
+ // Only reached inside a compiled binary, where the embedded module — and
36
+ // therefore the bunfs asset it points at — is always bundled in.
37
+ const embedded = require("./mupdf-wasm-embedded") as { default?: unknown };
38
+ const embeddedPath = embedded.default;
39
+ return typeof embeddedPath === "string" ? embeddedPath : undefined;
40
+ }
41
+
42
+ const mupdfWasmPath = loadEmbeddedMupdfWasmPath();
43
+
22
44
  export function ensureMupdfWasmResolution(): void {
23
45
  const globalScope = globalThis as typeof globalThis & Record<string, unknown>;
24
- if (globalScope[MODULE_CONFIG_KEY] !== undefined) return;
46
+ if (globalScope[MODULE_CONFIG_KEY] !== undefined || mupdfWasmPath === undefined) return;
25
47
  globalScope[MODULE_CONFIG_KEY] = { locateFile: () => String(mupdfWasmPath) };
26
48
  }
@@ -0,0 +1,129 @@
1
+ import * as path from "node:path";
2
+ import * as url from "node:url";
3
+ import * as util from "node:util";
4
+ import { logger } from "@gajae-code/utils";
5
+ import { embeddedMuPdfModule, embeddedMuPdfWasm } from "./mupdf-embedded";
6
+
7
+ export let mupdfAssetMapping = "module mupdf; WASM mupdf-wasm.wasm -> unresolved";
8
+ let wasmAsset: string | undefined;
9
+ let initializationFailure: unknown;
10
+ let preparation: Promise<void> | undefined;
11
+
12
+ interface MuPdfModuleConfiguration {
13
+ wasmBinary?: Uint8Array;
14
+ locateFile?: (filename?: string) => string;
15
+ onAbort?: (error: unknown) => void;
16
+ }
17
+
18
+ type MuPdfGlobal = typeof globalThis & {
19
+ $libmupdf_wasm_Module?: MuPdfModuleConfiguration;
20
+ };
21
+
22
+ function resolveWasmAsset(): string {
23
+ if (wasmAsset) return wasmAsset;
24
+ let moduleMapping: string;
25
+ if (embeddedMuPdfWasm) {
26
+ wasmAsset = embeddedMuPdfWasm;
27
+ moduleMapping = `build-time provenance ${embeddedMuPdfModule}`;
28
+ } else {
29
+ if (process.env.PI_COMPILED || /\$bunfs|~BUN|%7EBUN/.test(import.meta.url)) {
30
+ throw new Error("Compiled MuPDF WASM mapping is missing; run scripts/embed-mupdf.ts before compiling.");
31
+ }
32
+ const markitModule = url.fileURLToPath(new URL("../../vendor/markit-ai/dist/index.js", import.meta.url));
33
+ moduleMapping = Bun.resolveSync("mupdf", path.dirname(markitModule));
34
+ wasmAsset = path.join(path.dirname(moduleMapping), "mupdf-wasm.wasm");
35
+ }
36
+ mupdfAssetMapping = `module mupdf -> ${moduleMapping}; WASM mupdf-wasm.wasm -> ${wasmAsset}`;
37
+ return wasmAsset;
38
+ }
39
+
40
+ // The official Emscripten hook is consumed by markit-ai's lazy MuPDF import.
41
+ // Capture initialization aborts while preserving the import error's cause chain.
42
+ const globalScope = globalThis as MuPdfGlobal;
43
+ const hostConfiguration = globalScope.$libmupdf_wasm_Module;
44
+ const configuration: MuPdfModuleConfiguration = hostConfiguration ?? {
45
+ locateFile: resolveWasmAsset,
46
+ onAbort(error: unknown) {
47
+ initializationFailure = error;
48
+ },
49
+ };
50
+
51
+ if (hostConfiguration === undefined) {
52
+ globalScope.$libmupdf_wasm_Module = configuration;
53
+ } else {
54
+ // Reuse an SDK host's configuration and hooks. Only fill absent fields; do
55
+ // not replace locateFile/onAbort or discard caller-owned settings.
56
+ if (configuration.locateFile === undefined) configuration.locateFile = resolveWasmAsset;
57
+ if (configuration.onAbort === undefined) {
58
+ configuration.onAbort = error => {
59
+ initializationFailure = error;
60
+ };
61
+ }
62
+ }
63
+
64
+ function preparationAsset(): string {
65
+ const configuredPath = configuration.locateFile?.("mupdf-wasm.wasm");
66
+ return configuredPath ?? resolveWasmAsset();
67
+ }
68
+
69
+ export function prepareMuPdf(): Promise<void> {
70
+ // A new conversion attempt must not inherit an abort from a prior attempt.
71
+ initializationFailure = undefined;
72
+ const previousWasmBinary = configuration.wasmBinary;
73
+ preparation ??= Promise.resolve()
74
+ .then(async () => {
75
+ const bytes = await Bun.file(preparationAsset()).bytes();
76
+ // Reject corrupt assets before Emscripten starts: its abort path can also
77
+ // reject a secondary promise even when the module import is caught.
78
+ await WebAssembly.compile(bytes);
79
+ if (configuration.wasmBinary === undefined) configuration.wasmBinary = bytes;
80
+ })
81
+ .catch(error => {
82
+ preparation = undefined;
83
+ configuration.wasmBinary = previousWasmBinary;
84
+ throw error;
85
+ });
86
+ return preparation;
87
+ }
88
+
89
+ export function withMuPdfDiagnostic(error: unknown): Error {
90
+ const capturedInitializationFailure = initializationFailure;
91
+ initializationFailure = undefined;
92
+ const cause = capturedInitializationFailure ?? error;
93
+ const debugDetails: Record<string, unknown> = {
94
+ asset: embeddedMuPdfWasm ? "embedded" : "package",
95
+ error: formatMuPdfCauseChain(error),
96
+ };
97
+ if (capturedInitializationFailure !== undefined) {
98
+ debugDetails.initializationFailure = formatMuPdfCauseChain(capturedInitializationFailure);
99
+ }
100
+ logger.debug("MuPDF conversion failed", debugDetails);
101
+ const asset = embeddedMuPdfWasm ? "embedded asset" : "package asset";
102
+ return new Error(`PDF conversion failed [MuPDF; ${asset}; mupdf-wasm.wasm]`, { cause });
103
+ }
104
+
105
+ function formatMuPdfCauseChain(error: unknown): string {
106
+ const messages: string[] = [];
107
+ const seen = new Set<unknown>();
108
+ while (error !== undefined && !seen.has(error)) {
109
+ seen.add(error);
110
+ if (error instanceof Error) {
111
+ messages.push(sanitizeMuPdfDiagnostic(`${error.name}: ${error.message}`));
112
+ error = error.cause;
113
+ } else {
114
+ messages.push(sanitizeMuPdfDiagnostic(String(error)));
115
+ break;
116
+ }
117
+ }
118
+ return messages.join("; caused by: ") || "Conversion failed";
119
+ }
120
+
121
+ // Only the model-facing rendering is redacted; Error.cause remains intact for
122
+ // internal inspection and operational debug logging. Conservatively redact the
123
+ // rest of a path-bearing field so spaces in install paths cannot leak suffixes.
124
+ export function sanitizeMuPdfDiagnostic(message: string): string {
125
+ return util
126
+ .stripVTControlCharacters(message)
127
+ .replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029\u202a-\u202e\u2066-\u2069]/g, "")
128
+ .replace(/[^\s"'`]*(?:[/\\]|%2f|%5c)[^"'`]*(?=["'`]|$)/gi, "[path redacted]");
129
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * In-memory generation lease for asynchronous session-title regeneration.
3
+ *
4
+ * The lease is keyed by the session manager instance so TUI and ACP command
5
+ * dispatchers share the same ordering state without adding protocol fields to
6
+ * the persisted session format.
7
+ */
8
+ const generations = new WeakMap<object, number>();
9
+
10
+ function currentGeneration(owner: object): number {
11
+ return generations.get(owner) ?? 0;
12
+ }
13
+
14
+ /** Start a new regeneration and invalidate every older completion. */
15
+ export function beginSessionTitleGeneration(owner: object): number {
16
+ const generation = currentGeneration(owner) + 1;
17
+ generations.set(owner, generation);
18
+ return generation;
19
+ }
20
+
21
+ /** Invalidate pending regeneration after a newer session mutation is accepted. */
22
+ export function invalidateSessionTitleGeneration(owner: object): void {
23
+ generations.set(owner, currentGeneration(owner) + 1);
24
+ }
25
+
26
+ /** Return whether a regeneration still owns the latest generation lease. */
27
+ export function isSessionTitleGenerationCurrent(owner: object, generation: number): boolean {
28
+ return currentGeneration(owner) === generation;
29
+ }
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import * as path from "node:path";
5
5
 
6
+ import type { AgentMessage } from "@gajae-code/agent-core";
6
7
  import { type Api, type AssistantMessage, completeSimple, type Model, type Tool } from "@gajae-code/ai/core";
7
8
  import { logger, prompt } from "@gajae-code/utils";
8
9
  import type { ModelRegistry } from "../config/model-registry";
@@ -17,6 +18,9 @@ const TITLE_SYSTEM_PROMPT = prompt.render(titleSystemPrompt);
17
18
  const DEFAULT_TERMINAL_TITLE = "GJC";
18
19
  const TERMINAL_TITLE_CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f]/g;
19
20
 
21
+ const MAX_TITLE_CONVERSATION_MESSAGES = 6;
22
+ const MAX_TITLE_MESSAGE_CHARS = 400;
23
+
20
24
  const MAX_INPUT_CHARS = 2000;
21
25
  const TITLE_MAX_TOKENS = 30;
22
26
  const REASONING_SAFE_MAX_TOKENS = 1024;
@@ -29,6 +33,44 @@ const SET_TITLE_TOOL_NAME = "set_title";
29
33
  const MAX_TITLE_CHARS = 80;
30
34
  const MAX_TITLE_WORDS = 12;
31
35
 
36
+ /**
37
+ * Build a bounded conversation digest for title regeneration. Regeneration
38
+ * reads several messages so a throwaway first prompt can be corrected, while
39
+ * the automatic first-message path intentionally uses only its initial input.
40
+ */
41
+ export function buildConversationTitleInput(messages: readonly AgentMessage[]): string | undefined {
42
+ const userMessages: string[] = [];
43
+ for (const message of messages) {
44
+ if (message.role !== "user") continue;
45
+ const content = message.content;
46
+ const text =
47
+ typeof content === "string"
48
+ ? content
49
+ : content
50
+ .filter(block => block.type === "text")
51
+ .map(block => block.text)
52
+ .join("\n");
53
+ const trimmed = text.trim();
54
+ if (trimmed) userMessages.push(trimmed);
55
+ }
56
+
57
+ if (userMessages.length === 0) return undefined;
58
+
59
+ const keptMessages =
60
+ userMessages.length > MAX_TITLE_CONVERSATION_MESSAGES
61
+ ? [userMessages[0]!, ...userMessages.slice(-(MAX_TITLE_CONVERSATION_MESSAGES - 1))]
62
+ : userMessages;
63
+ // Reserve separator and ellipsis room so the digest is already within the
64
+ // generator's MAX_INPUT_CHARS bound; its existing truncation is a no-op here.
65
+ const maxMessageChars = Math.min(
66
+ MAX_TITLE_MESSAGE_CHARS,
67
+ Math.floor((MAX_INPUT_CHARS - (keptMessages.length - 1) - keptMessages.length) / keptMessages.length),
68
+ );
69
+ return keptMessages
70
+ .map(message => (message.length > maxMessageChars ? `${message.slice(0, maxMessageChars)}…` : message))
71
+ .join("\n");
72
+ }
73
+
32
74
  const setTitleTool: Tool = {
33
75
  name: SET_TITLE_TOOL_NAME,
34
76
  description: "Set the generated session title.",
@@ -58,9 +100,11 @@ function getTitleModel(registry: ModelRegistry, settings: Settings, currentModel
58
100
  }
59
101
 
60
102
  /**
61
- * Generate a title for a session based on the first user message.
103
+ * Generate a title for a session based on the provided user-message input.
104
+ * Automatic titles pass the first user message; regeneration passes a bounded
105
+ * conversation digest.
62
106
  *
63
- * @param firstMessage The first user message
107
+ * @param firstMessage The first user message or bounded conversation digest
64
108
  * @param registry Model registry
65
109
  * @param settings Settings used to resolve the smol role
66
110
  * @param sessionId Optional session id for sticky API key selection
@@ -78,7 +78,10 @@ export interface LoadPageOptions {
78
78
 
79
79
  export interface LoadPageResult {
80
80
  content: string;
81
+ /** The bounded response bytes retained for binary classification/conversion. */
82
+ buffer?: Uint8Array;
81
83
  contentType: string;
84
+ contentDisposition?: string;
82
85
  finalUrl: string;
83
86
  ok: boolean;
84
87
  status?: number;
@@ -173,6 +176,7 @@ export async function loadPage(url: string, options: LoadPageOptions = {}): Prom
173
176
  }
174
177
 
175
178
  const contentType = response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() ?? "";
179
+ const contentDisposition = response.headers.get("content-disposition") || undefined;
176
180
  const finalUrl = logicalUrl;
177
181
 
178
182
  const reader = response.body?.getReader();
@@ -196,16 +200,33 @@ export async function loadPage(url: string, options: LoadPageOptions = {}): Prom
196
200
  }
197
201
  }
198
202
 
199
- const content = Buffer.concat(chunks).toString("utf-8");
203
+ const bytes = Buffer.concat(chunks);
204
+ const content = bytes.toString("utf-8");
205
+ const buffer =
206
+ contentType.length === 0 ||
207
+ contentType === "application/octet-stream" ||
208
+ contentType === "binary/octet-stream" ||
209
+ contentType === "unknown" ||
210
+ contentDisposition !== undefined
211
+ ? bytes
212
+ : undefined;
200
213
  if (isBotBlocked(response.status, content) && attempt < USER_AGENTS.length - 1) {
201
214
  continue attempts;
202
215
  }
203
216
 
204
217
  if (!response.ok) {
205
- return { content, contentType, finalUrl, ok: false, status: response.status };
218
+ return {
219
+ content,
220
+ buffer,
221
+ contentType,
222
+ contentDisposition,
223
+ finalUrl,
224
+ ok: false,
225
+ status: response.status,
226
+ };
206
227
  }
207
228
 
208
- return { content, contentType, finalUrl, ok: true, status: response.status };
229
+ return { content, buffer, contentType, contentDisposition, finalUrl, ok: true, status: response.status };
209
230
  }
210
231
  return {
211
232
  content: "",
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael Liv
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.