@wahack/pi-coding-agent 15.11.0

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 (1152) hide show
  1. package/CHANGELOG.md +10031 -0
  2. package/README.md +36 -0
  3. package/examples/README.md +21 -0
  4. package/examples/custom-tools/README.md +104 -0
  5. package/examples/custom-tools/hello/index.ts +20 -0
  6. package/examples/extensions/README.md +142 -0
  7. package/examples/extensions/api-demo.ts +79 -0
  8. package/examples/extensions/chalk-logger.ts +25 -0
  9. package/examples/extensions/hello.ts +31 -0
  10. package/examples/extensions/pirate.ts +43 -0
  11. package/examples/extensions/plan-mode.ts +549 -0
  12. package/examples/extensions/reload-runtime.ts +38 -0
  13. package/examples/extensions/thinking-note.ts +13 -0
  14. package/examples/extensions/tools.ts +145 -0
  15. package/examples/extensions/with-deps/index.ts +36 -0
  16. package/examples/extensions/with-deps/package-lock.json +31 -0
  17. package/examples/extensions/with-deps/package.json +17 -0
  18. package/examples/hooks/README.md +56 -0
  19. package/examples/hooks/auto-commit-on-exit.ts +48 -0
  20. package/examples/hooks/confirm-destructive.ts +58 -0
  21. package/examples/hooks/custom-compaction.ts +115 -0
  22. package/examples/hooks/dirty-repo-guard.ts +51 -0
  23. package/examples/hooks/file-trigger.ts +40 -0
  24. package/examples/hooks/git-checkpoint.ts +52 -0
  25. package/examples/hooks/handoff.ts +149 -0
  26. package/examples/hooks/permission-gate.ts +33 -0
  27. package/examples/hooks/protected-paths.ts +29 -0
  28. package/examples/hooks/qna.ts +118 -0
  29. package/examples/hooks/status-line.ts +39 -0
  30. package/examples/sdk/01-minimal.ts +21 -0
  31. package/examples/sdk/02-custom-model.ts +49 -0
  32. package/examples/sdk/03-custom-prompt.ts +46 -0
  33. package/examples/sdk/04-skills.ts +43 -0
  34. package/examples/sdk/06-extensions.ts +82 -0
  35. package/examples/sdk/06-hooks.ts +61 -0
  36. package/examples/sdk/07-context-files.ts +35 -0
  37. package/examples/sdk/08-prompt-templates.ts +41 -0
  38. package/examples/sdk/08-slash-commands.ts +46 -0
  39. package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
  40. package/examples/sdk/11-sessions.ts +47 -0
  41. package/examples/sdk/12-redis-sessions.ts +54 -0
  42. package/examples/sdk/13-sql-sessions.ts +61 -0
  43. package/examples/sdk/README.md +172 -0
  44. package/package.json +554 -0
  45. package/scripts/build-binary.ts +100 -0
  46. package/scripts/bundle-dist.ts +90 -0
  47. package/scripts/format-prompts.ts +68 -0
  48. package/scripts/generate-docs-index.ts +40 -0
  49. package/scripts/generate-template.ts +33 -0
  50. package/scripts/omp +42 -0
  51. package/scripts/omp.ts +19 -0
  52. package/src/async/index.ts +1 -0
  53. package/src/async/job-manager.ts +625 -0
  54. package/src/auto-thinking/classifier.ts +185 -0
  55. package/src/autoresearch/command-resume.md +14 -0
  56. package/src/autoresearch/dashboard.ts +436 -0
  57. package/src/autoresearch/git.ts +319 -0
  58. package/src/autoresearch/helpers.ts +218 -0
  59. package/src/autoresearch/index.ts +536 -0
  60. package/src/autoresearch/prompt-setup.md +43 -0
  61. package/src/autoresearch/prompt.md +103 -0
  62. package/src/autoresearch/resume-message.md +10 -0
  63. package/src/autoresearch/state.ts +273 -0
  64. package/src/autoresearch/storage.ts +699 -0
  65. package/src/autoresearch/tools/init-experiment.ts +272 -0
  66. package/src/autoresearch/tools/log-experiment.ts +524 -0
  67. package/src/autoresearch/tools/run-experiment.ts +407 -0
  68. package/src/autoresearch/tools/update-notes.ts +109 -0
  69. package/src/autoresearch/types.ts +168 -0
  70. package/src/bun-imports.d.ts +28 -0
  71. package/src/capability/context-file.ts +44 -0
  72. package/src/capability/extension-module.ts +34 -0
  73. package/src/capability/extension.ts +47 -0
  74. package/src/capability/fs.ts +117 -0
  75. package/src/capability/hook.ts +40 -0
  76. package/src/capability/index.ts +436 -0
  77. package/src/capability/instruction.ts +37 -0
  78. package/src/capability/mcp.ts +74 -0
  79. package/src/capability/prompt.ts +35 -0
  80. package/src/capability/rule-buckets.ts +66 -0
  81. package/src/capability/rule.ts +261 -0
  82. package/src/capability/settings.ts +34 -0
  83. package/src/capability/skill.ts +63 -0
  84. package/src/capability/slash-command.ts +40 -0
  85. package/src/capability/ssh.ts +41 -0
  86. package/src/capability/system-prompt.ts +34 -0
  87. package/src/capability/tool.ts +38 -0
  88. package/src/capability/types.ts +168 -0
  89. package/src/cli/agents-cli.ts +138 -0
  90. package/src/cli/args.ts +340 -0
  91. package/src/cli/auth-broker-cli.ts +895 -0
  92. package/src/cli/auth-gateway-cli.ts +611 -0
  93. package/src/cli/classify-install-target.ts +76 -0
  94. package/src/cli/claude-trace-cli.ts +795 -0
  95. package/src/cli/commands/init-xdg.ts +27 -0
  96. package/src/cli/completion-gen.ts +550 -0
  97. package/src/cli/config-cli.ts +418 -0
  98. package/src/cli/dry-balance-cli.ts +856 -0
  99. package/src/cli/extension-flags.ts +48 -0
  100. package/src/cli/file-processor.ts +133 -0
  101. package/src/cli/gallery-cli.ts +230 -0
  102. package/src/cli/gallery-fixtures/agentic.ts +407 -0
  103. package/src/cli/gallery-fixtures/codeintel.ts +187 -0
  104. package/src/cli/gallery-fixtures/edit.ts +194 -0
  105. package/src/cli/gallery-fixtures/fs.ts +220 -0
  106. package/src/cli/gallery-fixtures/index.ts +40 -0
  107. package/src/cli/gallery-fixtures/interaction.ts +49 -0
  108. package/src/cli/gallery-fixtures/memory.ts +81 -0
  109. package/src/cli/gallery-fixtures/misc.ts +250 -0
  110. package/src/cli/gallery-fixtures/search.ts +213 -0
  111. package/src/cli/gallery-fixtures/shell.ts +167 -0
  112. package/src/cli/gallery-fixtures/types.ts +57 -0
  113. package/src/cli/gallery-fixtures/web.ts +158 -0
  114. package/src/cli/gallery-screenshot.ts +279 -0
  115. package/src/cli/grep-cli.ts +160 -0
  116. package/src/cli/grievances-cli.ts +256 -0
  117. package/src/cli/initial-message.ts +58 -0
  118. package/src/cli/list-models.ts +194 -0
  119. package/src/cli/plugin-cli.ts +996 -0
  120. package/src/cli/read-cli.ts +57 -0
  121. package/src/cli/session-picker.ts +79 -0
  122. package/src/cli/setup-cli.ts +231 -0
  123. package/src/cli/shell-cli.ts +176 -0
  124. package/src/cli/ssh-cli.ts +179 -0
  125. package/src/cli/startup-cwd.ts +68 -0
  126. package/src/cli/stats-cli.ts +238 -0
  127. package/src/cli/tiny-models-cli.ts +127 -0
  128. package/src/cli/update-cli.ts +611 -0
  129. package/src/cli/usage-cli.ts +603 -0
  130. package/src/cli/web-search-cli.ts +132 -0
  131. package/src/cli/worktree-cli.ts +291 -0
  132. package/src/cli-commands.ts +79 -0
  133. package/src/cli.ts +200 -0
  134. package/src/commands/acp.ts +24 -0
  135. package/src/commands/agents.ts +57 -0
  136. package/src/commands/auth-broker.ts +99 -0
  137. package/src/commands/auth-gateway.ts +69 -0
  138. package/src/commands/commit.ts +46 -0
  139. package/src/commands/complete.ts +66 -0
  140. package/src/commands/completions.ts +60 -0
  141. package/src/commands/config.ts +51 -0
  142. package/src/commands/dry-balance.ts +43 -0
  143. package/src/commands/gallery.ts +52 -0
  144. package/src/commands/grep.ts +48 -0
  145. package/src/commands/grievances.ts +51 -0
  146. package/src/commands/install.ts +107 -0
  147. package/src/commands/launch.ts +169 -0
  148. package/src/commands/plugin.ts +78 -0
  149. package/src/commands/read.ts +38 -0
  150. package/src/commands/setup.ts +67 -0
  151. package/src/commands/shell.ts +29 -0
  152. package/src/commands/ssh.ts +60 -0
  153. package/src/commands/stats.ts +29 -0
  154. package/src/commands/tiny-models.ts +36 -0
  155. package/src/commands/update.ts +21 -0
  156. package/src/commands/usage.ts +35 -0
  157. package/src/commands/web-search.ts +42 -0
  158. package/src/commands/worktree.ts +56 -0
  159. package/src/commit/agentic/agent.ts +317 -0
  160. package/src/commit/agentic/fallback.ts +96 -0
  161. package/src/commit/agentic/index.ts +355 -0
  162. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  163. package/src/commit/agentic/prompts/session-user.md +25 -0
  164. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  165. package/src/commit/agentic/prompts/system.md +38 -0
  166. package/src/commit/agentic/state.ts +60 -0
  167. package/src/commit/agentic/tools/analyze-file.ts +146 -0
  168. package/src/commit/agentic/tools/git-file-diff.ts +191 -0
  169. package/src/commit/agentic/tools/git-hunk.ts +50 -0
  170. package/src/commit/agentic/tools/git-overview.ts +81 -0
  171. package/src/commit/agentic/tools/index.ts +54 -0
  172. package/src/commit/agentic/tools/propose-changelog.ts +144 -0
  173. package/src/commit/agentic/tools/propose-commit.ts +109 -0
  174. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  175. package/src/commit/agentic/tools/schemas.ts +23 -0
  176. package/src/commit/agentic/tools/split-commit.ts +245 -0
  177. package/src/commit/agentic/topo-sort.ts +44 -0
  178. package/src/commit/agentic/trivial.ts +51 -0
  179. package/src/commit/agentic/validation.ts +183 -0
  180. package/src/commit/analysis/conventional.ts +64 -0
  181. package/src/commit/analysis/index.ts +4 -0
  182. package/src/commit/analysis/scope.ts +242 -0
  183. package/src/commit/analysis/summary.ts +105 -0
  184. package/src/commit/analysis/validation.ts +66 -0
  185. package/src/commit/changelog/detect.ts +40 -0
  186. package/src/commit/changelog/generate.ts +97 -0
  187. package/src/commit/changelog/index.ts +234 -0
  188. package/src/commit/changelog/parse.ts +44 -0
  189. package/src/commit/cli.ts +85 -0
  190. package/src/commit/git/diff.ts +148 -0
  191. package/src/commit/index.ts +5 -0
  192. package/src/commit/map-reduce/index.ts +69 -0
  193. package/src/commit/map-reduce/map-phase.ts +193 -0
  194. package/src/commit/map-reduce/reduce-phase.ts +49 -0
  195. package/src/commit/map-reduce/utils.ts +9 -0
  196. package/src/commit/message.ts +11 -0
  197. package/src/commit/model-selection.ts +92 -0
  198. package/src/commit/pipeline.ts +243 -0
  199. package/src/commit/prompts/analysis-system.md +148 -0
  200. package/src/commit/prompts/analysis-user.md +38 -0
  201. package/src/commit/prompts/changelog-system.md +50 -0
  202. package/src/commit/prompts/changelog-user.md +18 -0
  203. package/src/commit/prompts/file-observer-system.md +24 -0
  204. package/src/commit/prompts/file-observer-user.md +8 -0
  205. package/src/commit/prompts/reduce-system.md +50 -0
  206. package/src/commit/prompts/reduce-user.md +17 -0
  207. package/src/commit/prompts/summary-retry.md +3 -0
  208. package/src/commit/prompts/summary-system.md +38 -0
  209. package/src/commit/prompts/summary-user.md +13 -0
  210. package/src/commit/prompts/types-description.md +2 -0
  211. package/src/commit/shared-llm.ts +77 -0
  212. package/src/commit/types.ts +118 -0
  213. package/src/commit/utils/exclusions.ts +42 -0
  214. package/src/commit/utils.ts +58 -0
  215. package/src/config/api-key-resolver.ts +60 -0
  216. package/src/config/append-only-context-mode.ts +31 -0
  217. package/src/config/config-file.ts +317 -0
  218. package/src/config/file-lock.ts +164 -0
  219. package/src/config/keybindings.ts +628 -0
  220. package/src/config/mcp-schema.json +230 -0
  221. package/src/config/model-discovery.ts +554 -0
  222. package/src/config/model-registry.ts +2090 -0
  223. package/src/config/model-resolver.ts +1502 -0
  224. package/src/config/model-roles.ts +74 -0
  225. package/src/config/models-config-schema.ts +226 -0
  226. package/src/config/models-config.ts +129 -0
  227. package/src/config/prompt-templates.ts +185 -0
  228. package/src/config/resolve-config-value.ts +94 -0
  229. package/src/config/settings-schema.ts +3530 -0
  230. package/src/config/settings.ts +1178 -0
  231. package/src/config.ts +242 -0
  232. package/src/cursor.ts +340 -0
  233. package/src/dap/client.ts +760 -0
  234. package/src/dap/config.ts +189 -0
  235. package/src/dap/defaults.json +212 -0
  236. package/src/dap/index.ts +4 -0
  237. package/src/dap/session.ts +1441 -0
  238. package/src/dap/types.ts +610 -0
  239. package/src/debug/index.ts +515 -0
  240. package/src/debug/log-formatting.ts +58 -0
  241. package/src/debug/log-viewer.ts +908 -0
  242. package/src/debug/profiler.ts +162 -0
  243. package/src/debug/protocol-probe.ts +267 -0
  244. package/src/debug/raw-sse-buffer.ts +273 -0
  245. package/src/debug/raw-sse.ts +292 -0
  246. package/src/debug/report-bundle.ts +374 -0
  247. package/src/debug/system-info.ts +111 -0
  248. package/src/debug/terminal-info.ts +124 -0
  249. package/src/discovery/agents-md.ts +67 -0
  250. package/src/discovery/agents.ts +230 -0
  251. package/src/discovery/at-imports.ts +273 -0
  252. package/src/discovery/builtin-defaults.ts +39 -0
  253. package/src/discovery/builtin-rules/index.ts +54 -0
  254. package/src/discovery/builtin-rules/rs-box-leak.md +48 -0
  255. package/src/discovery/builtin-rules/rs-future-prelude.md +23 -0
  256. package/src/discovery/builtin-rules/rs-lazylock.md +51 -0
  257. package/src/discovery/builtin-rules/rs-match-ergonomics.md +67 -0
  258. package/src/discovery/builtin-rules/rs-parking-lot.md +44 -0
  259. package/src/discovery/builtin-rules/rs-result-type.md +19 -0
  260. package/src/discovery/builtin-rules/ts-bare-catch.md +38 -0
  261. package/src/discovery/builtin-rules/ts-import-type.md +42 -0
  262. package/src/discovery/builtin-rules/ts-no-any.md +56 -0
  263. package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +44 -0
  264. package/src/discovery/builtin-rules/ts-no-dynamic-import.md +39 -0
  265. package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
  266. package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
  267. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +51 -0
  268. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +65 -0
  269. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
  270. package/src/discovery/builtin-rules/ts-set-map.md +28 -0
  271. package/src/discovery/builtin.ts +906 -0
  272. package/src/discovery/claude-plugins.ts +386 -0
  273. package/src/discovery/claude.ts +584 -0
  274. package/src/discovery/cline.ts +83 -0
  275. package/src/discovery/codex.ts +522 -0
  276. package/src/discovery/cursor.ts +220 -0
  277. package/src/discovery/gemini.ts +383 -0
  278. package/src/discovery/github.ts +154 -0
  279. package/src/discovery/helpers.ts +1016 -0
  280. package/src/discovery/index.ts +81 -0
  281. package/src/discovery/mcp-json.ts +171 -0
  282. package/src/discovery/omp-extension-roots.ts +190 -0
  283. package/src/discovery/omp-plugins.ts +383 -0
  284. package/src/discovery/opencode.ts +398 -0
  285. package/src/discovery/plugin-dir-roots.ts +28 -0
  286. package/src/discovery/ssh.ts +153 -0
  287. package/src/discovery/substitute-plugin-root.ts +29 -0
  288. package/src/discovery/vscode.ts +105 -0
  289. package/src/discovery/windsurf.ts +147 -0
  290. package/src/edit/apply-patch/index.ts +87 -0
  291. package/src/edit/apply-patch/parser.ts +174 -0
  292. package/src/edit/diff.ts +999 -0
  293. package/src/edit/file-snapshot-store.ts +91 -0
  294. package/src/edit/hashline/block-resolver.ts +33 -0
  295. package/src/edit/hashline/diff.ts +290 -0
  296. package/src/edit/hashline/execute.ts +242 -0
  297. package/src/edit/hashline/filesystem.ts +130 -0
  298. package/src/edit/hashline/index.ts +5 -0
  299. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  300. package/src/edit/hashline/params.ts +18 -0
  301. package/src/edit/index.ts +571 -0
  302. package/src/edit/modes/apply-patch.lark +19 -0
  303. package/src/edit/modes/apply-patch.ts +53 -0
  304. package/src/edit/modes/patch.ts +1891 -0
  305. package/src/edit/modes/replace.ts +1137 -0
  306. package/src/edit/normalize.ts +345 -0
  307. package/src/edit/notebook.ts +242 -0
  308. package/src/edit/read-file.ts +25 -0
  309. package/src/edit/renderer.ts +769 -0
  310. package/src/edit/streaming.ts +517 -0
  311. package/src/eval/__tests__/agent-bridge.test.ts +708 -0
  312. package/src/eval/__tests__/bridge-timeout.test.ts +64 -0
  313. package/src/eval/__tests__/budget-bridge.test.ts +69 -0
  314. package/src/eval/__tests__/completion-bridge.test.ts +412 -0
  315. package/src/eval/__tests__/helpers-local-roots.test.ts +58 -0
  316. package/src/eval/__tests__/idle-timeout.test.ts +80 -0
  317. package/src/eval/__tests__/js-context-manager.test.ts +241 -0
  318. package/src/eval/__tests__/kernel-spawn.test.ts +103 -0
  319. package/src/eval/agent-bridge.ts +319 -0
  320. package/src/eval/backend.ts +71 -0
  321. package/src/eval/bridge-timeout.ts +44 -0
  322. package/src/eval/budget-bridge.ts +48 -0
  323. package/src/eval/completion-bridge.ts +207 -0
  324. package/src/eval/concurrency-bridge.ts +34 -0
  325. package/src/eval/idle-timeout.ts +91 -0
  326. package/src/eval/index.ts +4 -0
  327. package/src/eval/js/context-manager.ts +502 -0
  328. package/src/eval/js/executor.ts +173 -0
  329. package/src/eval/js/index.ts +51 -0
  330. package/src/eval/js/shared/helpers.ts +283 -0
  331. package/src/eval/js/shared/indirect-eval.ts +30 -0
  332. package/src/eval/js/shared/local-module-loader.ts +342 -0
  333. package/src/eval/js/shared/prelude.ts +2 -0
  334. package/src/eval/js/shared/prelude.txt +246 -0
  335. package/src/eval/js/shared/rewrite-imports.ts +532 -0
  336. package/src/eval/js/shared/runtime.ts +352 -0
  337. package/src/eval/js/shared/types.ts +18 -0
  338. package/src/eval/js/tool-bridge.ts +162 -0
  339. package/src/eval/js/worker-core.ts +132 -0
  340. package/src/eval/js/worker-entry.ts +30 -0
  341. package/src/eval/js/worker-protocol.ts +47 -0
  342. package/src/eval/py/__tests__/prelude.test.ts +19 -0
  343. package/src/eval/py/display.ts +71 -0
  344. package/src/eval/py/executor.ts +742 -0
  345. package/src/eval/py/index.ts +68 -0
  346. package/src/eval/py/kernel.ts +748 -0
  347. package/src/eval/py/prelude.py +658 -0
  348. package/src/eval/py/prelude.ts +3 -0
  349. package/src/eval/py/runner.py +1133 -0
  350. package/src/eval/py/runtime.ts +276 -0
  351. package/src/eval/py/spawn-options.ts +126 -0
  352. package/src/eval/py/tool-bridge.ts +182 -0
  353. package/src/eval/session-id.ts +8 -0
  354. package/src/eval/types.ts +48 -0
  355. package/src/exa/index.ts +2 -0
  356. package/src/exa/mcp-client.ts +370 -0
  357. package/src/exa/types.ts +69 -0
  358. package/src/exec/bash-executor.ts +419 -0
  359. package/src/exec/exec.ts +53 -0
  360. package/src/exec/non-interactive-env.ts +48 -0
  361. package/src/export/custom-share.ts +65 -0
  362. package/src/export/html/index.ts +164 -0
  363. package/src/export/html/template.css +1051 -0
  364. package/src/export/html/template.generated.ts +2 -0
  365. package/src/export/html/template.html +46 -0
  366. package/src/export/html/template.js +2271 -0
  367. package/src/export/html/template.macro.ts +25 -0
  368. package/src/export/html/vendor/highlight.min.js +1213 -0
  369. package/src/export/html/vendor/marked.min.js +6 -0
  370. package/src/export/ttsr.ts +583 -0
  371. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +54 -0
  372. package/src/extensibility/custom-commands/bundled/review/index.ts +489 -0
  373. package/src/extensibility/custom-commands/index.ts +2 -0
  374. package/src/extensibility/custom-commands/loader.ts +238 -0
  375. package/src/extensibility/custom-commands/types.ts +113 -0
  376. package/src/extensibility/custom-tools/index.ts +7 -0
  377. package/src/extensibility/custom-tools/loader.ts +269 -0
  378. package/src/extensibility/custom-tools/types.ts +270 -0
  379. package/src/extensibility/custom-tools/wrapper.ts +47 -0
  380. package/src/extensibility/extensions/compact-handler.ts +40 -0
  381. package/src/extensibility/extensions/get-commands-handler.ts +78 -0
  382. package/src/extensibility/extensions/index.ts +16 -0
  383. package/src/extensibility/extensions/loader.ts +572 -0
  384. package/src/extensibility/extensions/runner.ts +922 -0
  385. package/src/extensibility/extensions/types.ts +1322 -0
  386. package/src/extensibility/extensions/wrapper.ts +223 -0
  387. package/src/extensibility/hooks/index.ts +5 -0
  388. package/src/extensibility/hooks/loader.ts +257 -0
  389. package/src/extensibility/hooks/runner.ts +425 -0
  390. package/src/extensibility/hooks/tool-wrapper.ts +107 -0
  391. package/src/extensibility/hooks/types.ts +606 -0
  392. package/src/extensibility/legacy-pi-ai-shim.ts +24 -0
  393. package/src/extensibility/legacy-pi-coding-agent-shim.ts +15 -0
  394. package/src/extensibility/plugins/doctor.ts +65 -0
  395. package/src/extensibility/plugins/git-url.ts +367 -0
  396. package/src/extensibility/plugins/index.ts +9 -0
  397. package/src/extensibility/plugins/installer.ts +192 -0
  398. package/src/extensibility/plugins/legacy-pi-compat.ts +682 -0
  399. package/src/extensibility/plugins/loader.ts +313 -0
  400. package/src/extensibility/plugins/manager.ts +827 -0
  401. package/src/extensibility/plugins/marketplace/cache.ts +136 -0
  402. package/src/extensibility/plugins/marketplace/fetcher.ts +317 -0
  403. package/src/extensibility/plugins/marketplace/index.ts +6 -0
  404. package/src/extensibility/plugins/marketplace/manager.ts +770 -0
  405. package/src/extensibility/plugins/marketplace/registry.ts +196 -0
  406. package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
  407. package/src/extensibility/plugins/marketplace/types.ts +191 -0
  408. package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
  409. package/src/extensibility/plugins/parser.ts +105 -0
  410. package/src/extensibility/plugins/types.ts +194 -0
  411. package/src/extensibility/shared-events.ts +343 -0
  412. package/src/extensibility/skills.ts +312 -0
  413. package/src/extensibility/slash-commands.ts +227 -0
  414. package/src/extensibility/tool-proxy.ts +25 -0
  415. package/src/extensibility/typebox.ts +418 -0
  416. package/src/extensibility/utils.ts +44 -0
  417. package/src/goals/index.ts +3 -0
  418. package/src/goals/runtime.ts +528 -0
  419. package/src/goals/state.ts +37 -0
  420. package/src/goals/tools/goal-tool.ts +251 -0
  421. package/src/hindsight/backend.ts +354 -0
  422. package/src/hindsight/bank.ts +156 -0
  423. package/src/hindsight/client.ts +598 -0
  424. package/src/hindsight/config.ts +175 -0
  425. package/src/hindsight/content.ts +210 -0
  426. package/src/hindsight/index.ts +8 -0
  427. package/src/hindsight/mental-models.ts +429 -0
  428. package/src/hindsight/seeds.json +32 -0
  429. package/src/hindsight/state.ts +488 -0
  430. package/src/hindsight/transcript.ts +71 -0
  431. package/src/index.ts +59 -0
  432. package/src/internal-urls/agent-protocol.ts +146 -0
  433. package/src/internal-urls/artifact-protocol.ts +107 -0
  434. package/src/internal-urls/docs-index.generated.ts +106 -0
  435. package/src/internal-urls/history-protocol.ts +113 -0
  436. package/src/internal-urls/index.ts +25 -0
  437. package/src/internal-urls/issue-pr-protocol.ts +584 -0
  438. package/src/internal-urls/json-query.ts +126 -0
  439. package/src/internal-urls/local-protocol.ts +287 -0
  440. package/src/internal-urls/mcp-protocol.ts +151 -0
  441. package/src/internal-urls/memory-protocol.ts +169 -0
  442. package/src/internal-urls/omp-protocol.ts +93 -0
  443. package/src/internal-urls/parse.ts +72 -0
  444. package/src/internal-urls/registry-helpers.ts +25 -0
  445. package/src/internal-urls/router.ts +105 -0
  446. package/src/internal-urls/rule-protocol.ts +45 -0
  447. package/src/internal-urls/skill-protocol.ts +96 -0
  448. package/src/internal-urls/types.ts +152 -0
  449. package/src/internal-urls/vault-protocol.ts +936 -0
  450. package/src/irc/bus.ts +292 -0
  451. package/src/lib/xai-http.ts +124 -0
  452. package/src/lsp/client.ts +1193 -0
  453. package/src/lsp/clients/biome-client.ts +264 -0
  454. package/src/lsp/clients/index.ts +50 -0
  455. package/src/lsp/clients/lsp-linter-client.ts +93 -0
  456. package/src/lsp/clients/swiftlint-client.ts +120 -0
  457. package/src/lsp/config.ts +502 -0
  458. package/src/lsp/defaults.json +493 -0
  459. package/src/lsp/diagnostics-ledger.ts +51 -0
  460. package/src/lsp/edits.ts +267 -0
  461. package/src/lsp/index.ts +2477 -0
  462. package/src/lsp/lspmux.ts +233 -0
  463. package/src/lsp/render.ts +694 -0
  464. package/src/lsp/startup-events.ts +13 -0
  465. package/src/lsp/types.ts +455 -0
  466. package/src/lsp/utils.ts +718 -0
  467. package/src/main.ts +1325 -0
  468. package/src/mcp/client.ts +484 -0
  469. package/src/mcp/config-writer.ts +225 -0
  470. package/src/mcp/config.ts +365 -0
  471. package/src/mcp/index.ts +29 -0
  472. package/src/mcp/json-rpc.ts +122 -0
  473. package/src/mcp/loader.ts +124 -0
  474. package/src/mcp/manager.ts +1275 -0
  475. package/src/mcp/oauth-discovery.ts +442 -0
  476. package/src/mcp/oauth-flow.ts +442 -0
  477. package/src/mcp/render.ts +132 -0
  478. package/src/mcp/smithery-auth.ts +104 -0
  479. package/src/mcp/smithery-connect.ts +145 -0
  480. package/src/mcp/smithery-registry.ts +477 -0
  481. package/src/mcp/timeout.ts +59 -0
  482. package/src/mcp/tool-bridge.ts +426 -0
  483. package/src/mcp/tool-cache.ts +117 -0
  484. package/src/mcp/transports/http.ts +519 -0
  485. package/src/mcp/transports/index.ts +6 -0
  486. package/src/mcp/transports/stdio.ts +528 -0
  487. package/src/mcp/types.ts +423 -0
  488. package/src/memories/index.ts +1150 -0
  489. package/src/memories/storage.ts +577 -0
  490. package/src/memory-backend/index.ts +18 -0
  491. package/src/memory-backend/local-backend.ts +39 -0
  492. package/src/memory-backend/off-backend.ts +25 -0
  493. package/src/memory-backend/resolve.ts +25 -0
  494. package/src/memory-backend/runtime.ts +66 -0
  495. package/src/memory-backend/types.ts +166 -0
  496. package/src/mnemopi/backend.ts +547 -0
  497. package/src/mnemopi/config.ts +160 -0
  498. package/src/mnemopi/index.ts +3 -0
  499. package/src/mnemopi/state.ts +584 -0
  500. package/src/modes/acp/acp-agent.ts +2407 -0
  501. package/src/modes/acp/acp-client-bridge.ts +154 -0
  502. package/src/modes/acp/acp-event-mapper.ts +929 -0
  503. package/src/modes/acp/acp-mode.ts +23 -0
  504. package/src/modes/acp/index.ts +2 -0
  505. package/src/modes/acp/terminal-auth.ts +37 -0
  506. package/src/modes/components/agent-dashboard.ts +1206 -0
  507. package/src/modes/components/agent-hub.ts +1071 -0
  508. package/src/modes/components/assistant-message.ts +307 -0
  509. package/src/modes/components/bash-execution.ts +220 -0
  510. package/src/modes/components/bordered-loader.ts +41 -0
  511. package/src/modes/components/branch-summary-message.ts +45 -0
  512. package/src/modes/components/btw-panel.ts +104 -0
  513. package/src/modes/components/chat-block.ts +111 -0
  514. package/src/modes/components/compaction-summary-message.ts +87 -0
  515. package/src/modes/components/copy-selector.ts +206 -0
  516. package/src/modes/components/countdown-timer.ts +75 -0
  517. package/src/modes/components/custom-editor.ts +398 -0
  518. package/src/modes/components/custom-message.ts +63 -0
  519. package/src/modes/components/diff.ts +277 -0
  520. package/src/modes/components/dynamic-border.ts +34 -0
  521. package/src/modes/components/error-banner.ts +33 -0
  522. package/src/modes/components/eval-execution.ts +158 -0
  523. package/src/modes/components/execution-shared.ts +101 -0
  524. package/src/modes/components/extensions/extension-dashboard.ts +399 -0
  525. package/src/modes/components/extensions/extension-list.ts +502 -0
  526. package/src/modes/components/extensions/index.ts +9 -0
  527. package/src/modes/components/extensions/inspector-panel.ts +317 -0
  528. package/src/modes/components/extensions/state-manager.ts +627 -0
  529. package/src/modes/components/extensions/types.ts +186 -0
  530. package/src/modes/components/footer.ts +274 -0
  531. package/src/modes/components/history-search.ts +280 -0
  532. package/src/modes/components/hook-editor.ts +167 -0
  533. package/src/modes/components/hook-input.ts +87 -0
  534. package/src/modes/components/hook-message.ts +66 -0
  535. package/src/modes/components/hook-selector.ts +660 -0
  536. package/src/modes/components/index.ts +38 -0
  537. package/src/modes/components/keybinding-hints.ts +65 -0
  538. package/src/modes/components/late-diagnostics-message.ts +60 -0
  539. package/src/modes/components/login-dialog.ts +164 -0
  540. package/src/modes/components/mcp-add-wizard.ts +1340 -0
  541. package/src/modes/components/message-frame.ts +88 -0
  542. package/src/modes/components/model-selector.ts +1271 -0
  543. package/src/modes/components/oauth-selector.ts +368 -0
  544. package/src/modes/components/omfg-panel.ts +141 -0
  545. package/src/modes/components/overlay-box.ts +108 -0
  546. package/src/modes/components/plan-review-overlay.ts +820 -0
  547. package/src/modes/components/plan-toc.ts +138 -0
  548. package/src/modes/components/plugin-selector.ts +95 -0
  549. package/src/modes/components/plugin-settings.ts +722 -0
  550. package/src/modes/components/queue-mode-selector.ts +56 -0
  551. package/src/modes/components/read-tool-group.ts +670 -0
  552. package/src/modes/components/segment-track.ts +52 -0
  553. package/src/modes/components/session-selector.ts +625 -0
  554. package/src/modes/components/settings-defs.ts +189 -0
  555. package/src/modes/components/settings-selector.ts +651 -0
  556. package/src/modes/components/show-images-selector.ts +45 -0
  557. package/src/modes/components/skill-message.ts +89 -0
  558. package/src/modes/components/status-line/component.ts +869 -0
  559. package/src/modes/components/status-line/context-thresholds.ts +79 -0
  560. package/src/modes/components/status-line/git-utils.ts +42 -0
  561. package/src/modes/components/status-line/index.ts +5 -0
  562. package/src/modes/components/status-line/presets.ts +106 -0
  563. package/src/modes/components/status-line/segments.ts +584 -0
  564. package/src/modes/components/status-line/separators.ts +55 -0
  565. package/src/modes/components/status-line/token-rate.ts +66 -0
  566. package/src/modes/components/status-line/types.ts +108 -0
  567. package/src/modes/components/theme-selector.ts +63 -0
  568. package/src/modes/components/thinking-selector.ts +52 -0
  569. package/src/modes/components/tiny-title-download-progress.ts +90 -0
  570. package/src/modes/components/tips.txt +19 -0
  571. package/src/modes/components/todo-reminder.ts +38 -0
  572. package/src/modes/components/tool-execution.ts +1024 -0
  573. package/src/modes/components/transcript-container.ts +608 -0
  574. package/src/modes/components/tree-selector.ts +978 -0
  575. package/src/modes/components/ttsr-notification.ts +122 -0
  576. package/src/modes/components/user-message-selector.ts +227 -0
  577. package/src/modes/components/user-message.ts +66 -0
  578. package/src/modes/components/visual-truncate.ts +63 -0
  579. package/src/modes/components/welcome.ts +493 -0
  580. package/src/modes/controllers/btw-controller.ts +105 -0
  581. package/src/modes/controllers/command-controller-shared.ts +109 -0
  582. package/src/modes/controllers/command-controller.ts +1566 -0
  583. package/src/modes/controllers/event-controller.ts +1054 -0
  584. package/src/modes/controllers/extension-ui-controller.ts +886 -0
  585. package/src/modes/controllers/input-controller.ts +1073 -0
  586. package/src/modes/controllers/mcp-command-controller.ts +2017 -0
  587. package/src/modes/controllers/omfg-controller.ts +283 -0
  588. package/src/modes/controllers/omfg-rule.ts +647 -0
  589. package/src/modes/controllers/selector-controller.ts +1108 -0
  590. package/src/modes/controllers/ssh-command-controller.ts +384 -0
  591. package/src/modes/controllers/streaming-reveal.ts +279 -0
  592. package/src/modes/controllers/tan-command-controller.ts +173 -0
  593. package/src/modes/controllers/todo-command-controller.ts +485 -0
  594. package/src/modes/data/emojis.json +1 -0
  595. package/src/modes/emoji-autocomplete.ts +285 -0
  596. package/src/modes/gradient-highlight.ts +87 -0
  597. package/src/modes/image-references.ts +117 -0
  598. package/src/modes/index.ts +17 -0
  599. package/src/modes/interactive-mode.ts +3370 -0
  600. package/src/modes/internal-url-autocomplete.ts +143 -0
  601. package/src/modes/loop-limit.ts +140 -0
  602. package/src/modes/magic-keywords.ts +20 -0
  603. package/src/modes/markdown-prose.ts +247 -0
  604. package/src/modes/oauth-manual-input.ts +69 -0
  605. package/src/modes/orchestrate.ts +42 -0
  606. package/src/modes/print-mode.ts +126 -0
  607. package/src/modes/prompt-action-autocomplete.ts +260 -0
  608. package/src/modes/rpc/host-tools.ts +186 -0
  609. package/src/modes/rpc/host-uris.ts +235 -0
  610. package/src/modes/rpc/rpc-client.ts +963 -0
  611. package/src/modes/rpc/rpc-mode.ts +947 -0
  612. package/src/modes/rpc/rpc-subagents.ts +265 -0
  613. package/src/modes/rpc/rpc-types.ts +458 -0
  614. package/src/modes/runtime-init.ts +116 -0
  615. package/src/modes/session-observer-registry.ts +146 -0
  616. package/src/modes/setup-version.ts +11 -0
  617. package/src/modes/setup-wizard/index.ts +99 -0
  618. package/src/modes/setup-wizard/lazy.ts +16 -0
  619. package/src/modes/setup-wizard/scenes/glyph.ts +96 -0
  620. package/src/modes/setup-wizard/scenes/outro.ts +35 -0
  621. package/src/modes/setup-wizard/scenes/providers.ts +69 -0
  622. package/src/modes/setup-wizard/scenes/sign-in.ts +205 -0
  623. package/src/modes/setup-wizard/scenes/splash.ts +201 -0
  624. package/src/modes/setup-wizard/scenes/theme.ts +299 -0
  625. package/src/modes/setup-wizard/scenes/types.ts +48 -0
  626. package/src/modes/setup-wizard/scenes/web-search.ts +129 -0
  627. package/src/modes/setup-wizard/wizard-overlay.ts +275 -0
  628. package/src/modes/shared.ts +47 -0
  629. package/src/modes/theme/dark.json +95 -0
  630. package/src/modes/theme/defaults/alabaster.json +93 -0
  631. package/src/modes/theme/defaults/amethyst.json +96 -0
  632. package/src/modes/theme/defaults/anthracite.json +93 -0
  633. package/src/modes/theme/defaults/basalt.json +91 -0
  634. package/src/modes/theme/defaults/birch.json +95 -0
  635. package/src/modes/theme/defaults/dark-abyss.json +91 -0
  636. package/src/modes/theme/defaults/dark-arctic.json +104 -0
  637. package/src/modes/theme/defaults/dark-aurora.json +95 -0
  638. package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
  639. package/src/modes/theme/defaults/dark-cavern.json +91 -0
  640. package/src/modes/theme/defaults/dark-copper.json +95 -0
  641. package/src/modes/theme/defaults/dark-cosmos.json +90 -0
  642. package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
  643. package/src/modes/theme/defaults/dark-dracula.json +98 -0
  644. package/src/modes/theme/defaults/dark-eclipse.json +91 -0
  645. package/src/modes/theme/defaults/dark-ember.json +95 -0
  646. package/src/modes/theme/defaults/dark-equinox.json +90 -0
  647. package/src/modes/theme/defaults/dark-forest.json +96 -0
  648. package/src/modes/theme/defaults/dark-github.json +105 -0
  649. package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
  650. package/src/modes/theme/defaults/dark-lavender.json +95 -0
  651. package/src/modes/theme/defaults/dark-lunar.json +89 -0
  652. package/src/modes/theme/defaults/dark-midnight.json +95 -0
  653. package/src/modes/theme/defaults/dark-monochrome.json +94 -0
  654. package/src/modes/theme/defaults/dark-monokai.json +98 -0
  655. package/src/modes/theme/defaults/dark-nebula.json +90 -0
  656. package/src/modes/theme/defaults/dark-nord.json +97 -0
  657. package/src/modes/theme/defaults/dark-ocean.json +101 -0
  658. package/src/modes/theme/defaults/dark-one.json +100 -0
  659. package/src/modes/theme/defaults/dark-poimandres.json +142 -0
  660. package/src/modes/theme/defaults/dark-rainforest.json +91 -0
  661. package/src/modes/theme/defaults/dark-reef.json +91 -0
  662. package/src/modes/theme/defaults/dark-retro.json +92 -0
  663. package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
  664. package/src/modes/theme/defaults/dark-sakura.json +95 -0
  665. package/src/modes/theme/defaults/dark-slate.json +95 -0
  666. package/src/modes/theme/defaults/dark-solarized.json +97 -0
  667. package/src/modes/theme/defaults/dark-solstice.json +90 -0
  668. package/src/modes/theme/defaults/dark-starfall.json +91 -0
  669. package/src/modes/theme/defaults/dark-sunset.json +99 -0
  670. package/src/modes/theme/defaults/dark-swamp.json +90 -0
  671. package/src/modes/theme/defaults/dark-synthwave.json +103 -0
  672. package/src/modes/theme/defaults/dark-taiga.json +91 -0
  673. package/src/modes/theme/defaults/dark-terminal.json +95 -0
  674. package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
  675. package/src/modes/theme/defaults/dark-tundra.json +91 -0
  676. package/src/modes/theme/defaults/dark-twilight.json +91 -0
  677. package/src/modes/theme/defaults/dark-volcanic.json +91 -0
  678. package/src/modes/theme/defaults/graphite.json +92 -0
  679. package/src/modes/theme/defaults/index.ts +199 -0
  680. package/src/modes/theme/defaults/light-arctic.json +107 -0
  681. package/src/modes/theme/defaults/light-aurora-day.json +91 -0
  682. package/src/modes/theme/defaults/light-canyon.json +91 -0
  683. package/src/modes/theme/defaults/light-catppuccin.json +106 -0
  684. package/src/modes/theme/defaults/light-cirrus.json +90 -0
  685. package/src/modes/theme/defaults/light-coral.json +95 -0
  686. package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
  687. package/src/modes/theme/defaults/light-dawn.json +90 -0
  688. package/src/modes/theme/defaults/light-dunes.json +91 -0
  689. package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
  690. package/src/modes/theme/defaults/light-forest.json +100 -0
  691. package/src/modes/theme/defaults/light-frost.json +95 -0
  692. package/src/modes/theme/defaults/light-github.json +115 -0
  693. package/src/modes/theme/defaults/light-glacier.json +91 -0
  694. package/src/modes/theme/defaults/light-gruvbox.json +108 -0
  695. package/src/modes/theme/defaults/light-haze.json +90 -0
  696. package/src/modes/theme/defaults/light-honeycomb.json +95 -0
  697. package/src/modes/theme/defaults/light-lagoon.json +91 -0
  698. package/src/modes/theme/defaults/light-lavender.json +95 -0
  699. package/src/modes/theme/defaults/light-meadow.json +91 -0
  700. package/src/modes/theme/defaults/light-mint.json +95 -0
  701. package/src/modes/theme/defaults/light-monochrome.json +101 -0
  702. package/src/modes/theme/defaults/light-ocean.json +99 -0
  703. package/src/modes/theme/defaults/light-one.json +99 -0
  704. package/src/modes/theme/defaults/light-opal.json +91 -0
  705. package/src/modes/theme/defaults/light-orchard.json +91 -0
  706. package/src/modes/theme/defaults/light-paper.json +95 -0
  707. package/src/modes/theme/defaults/light-poimandres.json +142 -0
  708. package/src/modes/theme/defaults/light-prism.json +90 -0
  709. package/src/modes/theme/defaults/light-retro.json +98 -0
  710. package/src/modes/theme/defaults/light-sand.json +95 -0
  711. package/src/modes/theme/defaults/light-savanna.json +91 -0
  712. package/src/modes/theme/defaults/light-solarized.json +102 -0
  713. package/src/modes/theme/defaults/light-soleil.json +90 -0
  714. package/src/modes/theme/defaults/light-sunset.json +99 -0
  715. package/src/modes/theme/defaults/light-synthwave.json +98 -0
  716. package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
  717. package/src/modes/theme/defaults/light-wetland.json +91 -0
  718. package/src/modes/theme/defaults/light-zenith.json +89 -0
  719. package/src/modes/theme/defaults/limestone.json +94 -0
  720. package/src/modes/theme/defaults/mahogany.json +97 -0
  721. package/src/modes/theme/defaults/marble.json +93 -0
  722. package/src/modes/theme/defaults/obsidian.json +91 -0
  723. package/src/modes/theme/defaults/onyx.json +91 -0
  724. package/src/modes/theme/defaults/pearl.json +93 -0
  725. package/src/modes/theme/defaults/porcelain.json +91 -0
  726. package/src/modes/theme/defaults/quartz.json +96 -0
  727. package/src/modes/theme/defaults/sandstone.json +95 -0
  728. package/src/modes/theme/defaults/titanium.json +90 -0
  729. package/src/modes/theme/light.json +93 -0
  730. package/src/modes/theme/mermaid-cache.ts +29 -0
  731. package/src/modes/theme/shimmer.ts +235 -0
  732. package/src/modes/theme/theme-schema.json +459 -0
  733. package/src/modes/theme/theme.ts +2676 -0
  734. package/src/modes/turn-budget.ts +31 -0
  735. package/src/modes/types.ts +359 -0
  736. package/src/modes/ultrathink.ts +41 -0
  737. package/src/modes/utils/context-usage.ts +339 -0
  738. package/src/modes/utils/copy-targets.ts +360 -0
  739. package/src/modes/utils/hotkeys-markdown.ts +61 -0
  740. package/src/modes/utils/keybinding-matchers.ts +51 -0
  741. package/src/modes/utils/tools-markdown.ts +27 -0
  742. package/src/modes/utils/ui-helpers.ts +801 -0
  743. package/src/modes/workflow.ts +42 -0
  744. package/src/plan-mode/approved-plan.ts +186 -0
  745. package/src/plan-mode/plan-handoff.ts +37 -0
  746. package/src/plan-mode/plan-protection.ts +31 -0
  747. package/src/plan-mode/state.ts +6 -0
  748. package/src/priority.json +41 -0
  749. package/src/prompts/agents/designer.md +66 -0
  750. package/src/prompts/agents/explore.md +58 -0
  751. package/src/prompts/agents/frontmatter.md +11 -0
  752. package/src/prompts/agents/init.md +33 -0
  753. package/src/prompts/agents/librarian.md +119 -0
  754. package/src/prompts/agents/oracle.md +55 -0
  755. package/src/prompts/agents/plan.md +48 -0
  756. package/src/prompts/agents/reviewer.md +140 -0
  757. package/src/prompts/agents/task.md +16 -0
  758. package/src/prompts/ci-green-request.md +36 -0
  759. package/src/prompts/dry-balance-bench.md +8 -0
  760. package/src/prompts/goals/goal-budget-limit.md +16 -0
  761. package/src/prompts/goals/goal-continuation.md +28 -0
  762. package/src/prompts/goals/goal-mode-active.md +23 -0
  763. package/src/prompts/memories/consolidation.md +30 -0
  764. package/src/prompts/memories/read-path.md +11 -0
  765. package/src/prompts/memories/stage_one_input.md +6 -0
  766. package/src/prompts/memories/stage_one_system.md +21 -0
  767. package/src/prompts/review-custom-request.md +22 -0
  768. package/src/prompts/review-headless-request.md +16 -0
  769. package/src/prompts/review-request.md +69 -0
  770. package/src/prompts/steering/user-interjection.md +10 -0
  771. package/src/prompts/system/agent-creation-architect.md +50 -0
  772. package/src/prompts/system/agent-creation-user.md +6 -0
  773. package/src/prompts/system/auto-continue.md +1 -0
  774. package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
  775. package/src/prompts/system/auto-thinking-difficulty.md +12 -0
  776. package/src/prompts/system/background-tan-dispatch.md +8 -0
  777. package/src/prompts/system/btw-user.md +8 -0
  778. package/src/prompts/system/commit-message-system.md +14 -0
  779. package/src/prompts/system/custom-system-prompt.md +64 -0
  780. package/src/prompts/system/eager-todo.md +13 -0
  781. package/src/prompts/system/empty-stop-retry.md +6 -0
  782. package/src/prompts/system/irc-incoming.md +7 -0
  783. package/src/prompts/system/manual-continue.md +7 -0
  784. package/src/prompts/system/memory-consolidation-system.md +8 -0
  785. package/src/prompts/system/memory-extraction-system.md +26 -0
  786. package/src/prompts/system/omfg-user.md +50 -0
  787. package/src/prompts/system/orchestrate-notice.md +40 -0
  788. package/src/prompts/system/plan-mode-active.md +109 -0
  789. package/src/prompts/system/plan-mode-approved.md +25 -0
  790. package/src/prompts/system/plan-mode-compact-instructions.md +16 -0
  791. package/src/prompts/system/plan-mode-reference.md +11 -0
  792. package/src/prompts/system/plan-mode-subagent.md +33 -0
  793. package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
  794. package/src/prompts/system/project-prompt.md +52 -0
  795. package/src/prompts/system/subagent-system-prompt.md +64 -0
  796. package/src/prompts/system/subagent-user-prompt.md +3 -0
  797. package/src/prompts/system/subagent-yield-reminder.md +12 -0
  798. package/src/prompts/system/system-prompt.md +258 -0
  799. package/src/prompts/system/tiny-title-system.md +8 -0
  800. package/src/prompts/system/title-system.md +16 -0
  801. package/src/prompts/system/ttsr-interrupt.md +7 -0
  802. package/src/prompts/system/ttsr-tool-reminder.md +5 -0
  803. package/src/prompts/system/ultrathink-notice.md +3 -0
  804. package/src/prompts/system/web-search.md +25 -0
  805. package/src/prompts/system/workflow-notice.md +70 -0
  806. package/src/prompts/tools/apply-patch.md +65 -0
  807. package/src/prompts/tools/ask.md +30 -0
  808. package/src/prompts/tools/ast-edit.md +39 -0
  809. package/src/prompts/tools/ast-grep.md +42 -0
  810. package/src/prompts/tools/async-result.md +8 -0
  811. package/src/prompts/tools/bash.md +46 -0
  812. package/src/prompts/tools/browser.md +73 -0
  813. package/src/prompts/tools/checkpoint.md +16 -0
  814. package/src/prompts/tools/debug.md +34 -0
  815. package/src/prompts/tools/eval.md +92 -0
  816. package/src/prompts/tools/find.md +36 -0
  817. package/src/prompts/tools/github.md +21 -0
  818. package/src/prompts/tools/goal.md +18 -0
  819. package/src/prompts/tools/image-gen.md +7 -0
  820. package/src/prompts/tools/inspect-image-system.md +20 -0
  821. package/src/prompts/tools/inspect-image.md +32 -0
  822. package/src/prompts/tools/irc.md +59 -0
  823. package/src/prompts/tools/job.md +19 -0
  824. package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
  825. package/src/prompts/tools/lsp.md +42 -0
  826. package/src/prompts/tools/memory-edit.md +8 -0
  827. package/src/prompts/tools/patch.md +70 -0
  828. package/src/prompts/tools/read.md +84 -0
  829. package/src/prompts/tools/recall.md +5 -0
  830. package/src/prompts/tools/reflect.md +5 -0
  831. package/src/prompts/tools/render-mermaid.md +9 -0
  832. package/src/prompts/tools/replace.md +30 -0
  833. package/src/prompts/tools/resolve.md +9 -0
  834. package/src/prompts/tools/retain.md +6 -0
  835. package/src/prompts/tools/rewind.md +13 -0
  836. package/src/prompts/tools/search-tool-bm25.md +32 -0
  837. package/src/prompts/tools/search.md +24 -0
  838. package/src/prompts/tools/ssh.md +31 -0
  839. package/src/prompts/tools/task-summary.md +17 -0
  840. package/src/prompts/tools/task.md +88 -0
  841. package/src/prompts/tools/todo.md +62 -0
  842. package/src/prompts/tools/web-search.md +10 -0
  843. package/src/prompts/tools/write.md +14 -0
  844. package/src/registry/agent-lifecycle.ts +218 -0
  845. package/src/registry/agent-registry.ts +151 -0
  846. package/src/sdk.ts +2558 -0
  847. package/src/secrets/index.ts +123 -0
  848. package/src/secrets/obfuscator.ts +298 -0
  849. package/src/secrets/regex.ts +21 -0
  850. package/src/session/agent-session.ts +10121 -0
  851. package/src/session/agent-storage.ts +455 -0
  852. package/src/session/artifacts.ts +135 -0
  853. package/src/session/auth-broker-config.ts +131 -0
  854. package/src/session/auth-storage.ts +29 -0
  855. package/src/session/blob-store.ts +255 -0
  856. package/src/session/client-bridge.ts +85 -0
  857. package/src/session/history-storage.ts +348 -0
  858. package/src/session/indexed-session-storage.ts +430 -0
  859. package/src/session/messages.ts +541 -0
  860. package/src/session/redis-session-storage.ts +170 -0
  861. package/src/session/session-dump-format.ts +209 -0
  862. package/src/session/session-history-format.ts +246 -0
  863. package/src/session/session-manager.ts +3676 -0
  864. package/src/session/session-storage.ts +529 -0
  865. package/src/session/shake-types.ts +43 -0
  866. package/src/session/sql-session-storage.ts +314 -0
  867. package/src/session/streaming-output.ts +1330 -0
  868. package/src/session/tool-choice-queue.ts +213 -0
  869. package/src/session/yield-queue.ts +173 -0
  870. package/src/slash-commands/acp-builtins.ts +70 -0
  871. package/src/slash-commands/builtin-registry.ts +1798 -0
  872. package/src/slash-commands/helpers/context-report.ts +39 -0
  873. package/src/slash-commands/helpers/format.ts +46 -0
  874. package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
  875. package/src/slash-commands/helpers/mcp.ts +532 -0
  876. package/src/slash-commands/helpers/parse.ts +85 -0
  877. package/src/slash-commands/helpers/ssh.ts +195 -0
  878. package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
  879. package/src/slash-commands/helpers/todo.ts +279 -0
  880. package/src/slash-commands/helpers/usage-report.ts +95 -0
  881. package/src/slash-commands/marketplace-install-parser.ts +99 -0
  882. package/src/slash-commands/types.ts +135 -0
  883. package/src/ssh/config-writer.ts +183 -0
  884. package/src/ssh/connection-manager.ts +509 -0
  885. package/src/ssh/ssh-executor.ts +189 -0
  886. package/src/ssh/sshfs-mount.ts +140 -0
  887. package/src/ssh/utils.ts +8 -0
  888. package/src/stt/downloader.ts +71 -0
  889. package/src/stt/index.ts +3 -0
  890. package/src/stt/recorder.ts +351 -0
  891. package/src/stt/setup.ts +52 -0
  892. package/src/stt/stt-controller.ts +160 -0
  893. package/src/stt/transcribe.py +70 -0
  894. package/src/stt/transcriber.ts +91 -0
  895. package/src/stubs/natives/index.ts +814 -0
  896. package/src/stubs/natives/package.json +7 -0
  897. package/src/stubs/tui/index.ts +282 -0
  898. package/src/stubs/tui/package.json +7 -0
  899. package/src/system-prompt.ts +611 -0
  900. package/src/task/agents.ts +167 -0
  901. package/src/task/commands.ts +132 -0
  902. package/src/task/discovery.ts +122 -0
  903. package/src/task/executor.ts +2133 -0
  904. package/src/task/index.ts +1419 -0
  905. package/src/task/name-generator.ts +1577 -0
  906. package/src/task/omp-command.ts +26 -0
  907. package/src/task/output-manager.ts +88 -0
  908. package/src/task/parallel.ts +116 -0
  909. package/src/task/render.ts +1381 -0
  910. package/src/task/repair-args.ts +129 -0
  911. package/src/task/subprocess-tool-registry.ts +88 -0
  912. package/src/task/types.ts +336 -0
  913. package/src/task/worktree.ts +514 -0
  914. package/src/telemetry-export.ts +144 -0
  915. package/src/thinking.ts +167 -0
  916. package/src/tiny/compiled-runtime.ts +179 -0
  917. package/src/tiny/device.ts +111 -0
  918. package/src/tiny/dtype.ts +101 -0
  919. package/src/tiny/models.ts +242 -0
  920. package/src/tiny/text.ts +165 -0
  921. package/src/tiny/title-client.ts +543 -0
  922. package/src/tiny/title-protocol.ts +56 -0
  923. package/src/tiny/worker.ts +568 -0
  924. package/src/tool-discovery/mode.ts +24 -0
  925. package/src/tool-discovery/tool-index.ts +256 -0
  926. package/src/tools/approval.ts +189 -0
  927. package/src/tools/archive-reader.ts +721 -0
  928. package/src/tools/ask.ts +928 -0
  929. package/src/tools/ast-edit.ts +642 -0
  930. package/src/tools/ast-grep.ts +452 -0
  931. package/src/tools/auto-generated-guard.ts +322 -0
  932. package/src/tools/bash-command-fixup.ts +37 -0
  933. package/src/tools/bash-interactive.ts +408 -0
  934. package/src/tools/bash-interceptor.ts +67 -0
  935. package/src/tools/bash-pty-selection.ts +14 -0
  936. package/src/tools/bash-skill-urls.ts +248 -0
  937. package/src/tools/bash.ts +1386 -0
  938. package/src/tools/browser/attach.ts +175 -0
  939. package/src/tools/browser/launch.ts +660 -0
  940. package/src/tools/browser/readable.ts +112 -0
  941. package/src/tools/browser/registry.ts +197 -0
  942. package/src/tools/browser/render.ts +216 -0
  943. package/src/tools/browser/tab-protocol.ts +105 -0
  944. package/src/tools/browser/tab-supervisor.ts +628 -0
  945. package/src/tools/browser/tab-worker-entry.ts +21 -0
  946. package/src/tools/browser/tab-worker.ts +1226 -0
  947. package/src/tools/browser.ts +343 -0
  948. package/src/tools/checkpoint.ts +136 -0
  949. package/src/tools/conflict-detect.ts +718 -0
  950. package/src/tools/context.ts +39 -0
  951. package/src/tools/debug.ts +1067 -0
  952. package/src/tools/eval-backends.ts +27 -0
  953. package/src/tools/eval-render.ts +752 -0
  954. package/src/tools/eval.ts +577 -0
  955. package/src/tools/fetch.ts +1926 -0
  956. package/src/tools/file-recorder.ts +35 -0
  957. package/src/tools/find.ts +609 -0
  958. package/src/tools/fs-cache-invalidation.ts +28 -0
  959. package/src/tools/gh-cache-invalidation.ts +255 -0
  960. package/src/tools/gh-format.ts +12 -0
  961. package/src/tools/gh-renderer.ts +481 -0
  962. package/src/tools/gh.ts +3720 -0
  963. package/src/tools/github-cache.ts +637 -0
  964. package/src/tools/grouped-file-output.ts +210 -0
  965. package/src/tools/image-gen.ts +1517 -0
  966. package/src/tools/index.ts +599 -0
  967. package/src/tools/inspect-image-renderer.ts +132 -0
  968. package/src/tools/inspect-image.ts +174 -0
  969. package/src/tools/irc.ts +723 -0
  970. package/src/tools/job.ts +557 -0
  971. package/src/tools/json-tree.ts +243 -0
  972. package/src/tools/jtd-to-json-schema.ts +219 -0
  973. package/src/tools/jtd-to-typescript.ts +136 -0
  974. package/src/tools/jtd-utils.ts +102 -0
  975. package/src/tools/list-limit.ts +40 -0
  976. package/src/tools/match-line-format.ts +20 -0
  977. package/src/tools/memory-edit.ts +59 -0
  978. package/src/tools/memory-recall.ts +100 -0
  979. package/src/tools/memory-reflect.ts +88 -0
  980. package/src/tools/memory-render.ts +202 -0
  981. package/src/tools/memory-retain.ts +91 -0
  982. package/src/tools/output-meta.ts +754 -0
  983. package/src/tools/output-schema-validator.ts +132 -0
  984. package/src/tools/path-utils.ts +1054 -0
  985. package/src/tools/plan-mode-guard.ts +108 -0
  986. package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
  987. package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
  988. package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
  989. package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
  990. package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
  991. package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
  992. package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
  993. package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
  994. package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
  995. package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
  996. package/src/tools/puppeteer/10_stealth_plugins.txt +206 -0
  997. package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
  998. package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
  999. package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
  1000. package/src/tools/read.ts +2929 -0
  1001. package/src/tools/render-mermaid.ts +69 -0
  1002. package/src/tools/render-utils.ts +838 -0
  1003. package/src/tools/renderers.ts +77 -0
  1004. package/src/tools/report-tool-issue.ts +534 -0
  1005. package/src/tools/resolve.ts +276 -0
  1006. package/src/tools/review.ts +253 -0
  1007. package/src/tools/search-tool-bm25.ts +351 -0
  1008. package/src/tools/search.ts +1580 -0
  1009. package/src/tools/sqlite-reader.ts +828 -0
  1010. package/src/tools/ssh.ts +349 -0
  1011. package/src/tools/todo.ts +982 -0
  1012. package/src/tools/tool-errors.ts +62 -0
  1013. package/src/tools/tool-result.ts +94 -0
  1014. package/src/tools/tool-timeouts.ts +30 -0
  1015. package/src/tools/tts.ts +133 -0
  1016. package/src/tools/write.ts +1217 -0
  1017. package/src/tools/yield.ts +269 -0
  1018. package/src/tui/code-cell.ts +216 -0
  1019. package/src/tui/file-list.ts +55 -0
  1020. package/src/tui/hyperlink.ts +175 -0
  1021. package/src/tui/index.ts +12 -0
  1022. package/src/tui/output-block.ts +240 -0
  1023. package/src/tui/status-line.ts +54 -0
  1024. package/src/tui/tree-list.ts +84 -0
  1025. package/src/tui/types.ts +15 -0
  1026. package/src/tui/utils.ts +103 -0
  1027. package/src/utils/block-context.ts +312 -0
  1028. package/src/utils/changelog.ts +132 -0
  1029. package/src/utils/clipboard.ts +193 -0
  1030. package/src/utils/command-args.ts +76 -0
  1031. package/src/utils/commit-message-generator.ts +151 -0
  1032. package/src/utils/edit-mode.ts +41 -0
  1033. package/src/utils/enhanced-paste.ts +230 -0
  1034. package/src/utils/event-bus.ts +33 -0
  1035. package/src/utils/external-editor.ts +65 -0
  1036. package/src/utils/file-display-mode.ts +45 -0
  1037. package/src/utils/file-mentions.ts +281 -0
  1038. package/src/utils/git.ts +1833 -0
  1039. package/src/utils/image-loading.ts +132 -0
  1040. package/src/utils/image-resize.ts +309 -0
  1041. package/src/utils/jj.ts +248 -0
  1042. package/src/utils/lang-from-path.ts +239 -0
  1043. package/src/utils/markit.ts +89 -0
  1044. package/src/utils/open.ts +55 -0
  1045. package/src/utils/session-color.ts +68 -0
  1046. package/src/utils/shell-snapshot.ts +187 -0
  1047. package/src/utils/sixel.ts +69 -0
  1048. package/src/utils/title-generator.ts +373 -0
  1049. package/src/utils/tool-choice.ts +33 -0
  1050. package/src/utils/tools-manager.ts +363 -0
  1051. package/src/web/kagi.ts +305 -0
  1052. package/src/web/parallel.ts +353 -0
  1053. package/src/web/scrapers/artifacthub.ts +207 -0
  1054. package/src/web/scrapers/arxiv.ts +83 -0
  1055. package/src/web/scrapers/aur.ts +162 -0
  1056. package/src/web/scrapers/biorxiv.ts +133 -0
  1057. package/src/web/scrapers/bluesky.ts +262 -0
  1058. package/src/web/scrapers/brew.ts +172 -0
  1059. package/src/web/scrapers/cheatsh.ts +68 -0
  1060. package/src/web/scrapers/chocolatey.ts +196 -0
  1061. package/src/web/scrapers/choosealicense.ts +95 -0
  1062. package/src/web/scrapers/cisa-kev.ts +87 -0
  1063. package/src/web/scrapers/clojars.ts +154 -0
  1064. package/src/web/scrapers/coingecko.ts +177 -0
  1065. package/src/web/scrapers/crates-io.ts +97 -0
  1066. package/src/web/scrapers/crossref.ts +136 -0
  1067. package/src/web/scrapers/devto.ts +147 -0
  1068. package/src/web/scrapers/discogs.ts +306 -0
  1069. package/src/web/scrapers/discourse.ts +197 -0
  1070. package/src/web/scrapers/dockerhub.ts +138 -0
  1071. package/src/web/scrapers/docs-rs.ts +653 -0
  1072. package/src/web/scrapers/fdroid.ts +134 -0
  1073. package/src/web/scrapers/firefox-addons.ts +191 -0
  1074. package/src/web/scrapers/flathub.ts +223 -0
  1075. package/src/web/scrapers/github-gist.ts +58 -0
  1076. package/src/web/scrapers/github.ts +704 -0
  1077. package/src/web/scrapers/gitlab.ts +401 -0
  1078. package/src/web/scrapers/go-pkg.ts +266 -0
  1079. package/src/web/scrapers/hackage.ts +140 -0
  1080. package/src/web/scrapers/hackernews.ts +189 -0
  1081. package/src/web/scrapers/hex.ts +105 -0
  1082. package/src/web/scrapers/huggingface.ts +321 -0
  1083. package/src/web/scrapers/iacr.ts +89 -0
  1084. package/src/web/scrapers/index.ts +252 -0
  1085. package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
  1086. package/src/web/scrapers/lemmy.ts +203 -0
  1087. package/src/web/scrapers/lobsters.ts +175 -0
  1088. package/src/web/scrapers/mastodon.ts +292 -0
  1089. package/src/web/scrapers/maven.ts +138 -0
  1090. package/src/web/scrapers/mdn.ts +173 -0
  1091. package/src/web/scrapers/metacpan.ts +222 -0
  1092. package/src/web/scrapers/musicbrainz.ts +250 -0
  1093. package/src/web/scrapers/npm.ts +98 -0
  1094. package/src/web/scrapers/nuget.ts +183 -0
  1095. package/src/web/scrapers/nvd.ts +222 -0
  1096. package/src/web/scrapers/ollama.ts +239 -0
  1097. package/src/web/scrapers/open-vsx.ts +106 -0
  1098. package/src/web/scrapers/opencorporates.ts +292 -0
  1099. package/src/web/scrapers/openlibrary.ts +336 -0
  1100. package/src/web/scrapers/orcid.ts +286 -0
  1101. package/src/web/scrapers/osv.ts +176 -0
  1102. package/src/web/scrapers/packagist.ts +160 -0
  1103. package/src/web/scrapers/pub-dev.ts +143 -0
  1104. package/src/web/scrapers/pubmed.ts +211 -0
  1105. package/src/web/scrapers/pypi.ts +112 -0
  1106. package/src/web/scrapers/rawg.ts +110 -0
  1107. package/src/web/scrapers/readthedocs.ts +120 -0
  1108. package/src/web/scrapers/reddit.ts +95 -0
  1109. package/src/web/scrapers/repology.ts +251 -0
  1110. package/src/web/scrapers/rfc.ts +201 -0
  1111. package/src/web/scrapers/rubygems.ts +103 -0
  1112. package/src/web/scrapers/searchcode.ts +189 -0
  1113. package/src/web/scrapers/sec-edgar.ts +261 -0
  1114. package/src/web/scrapers/semantic-scholar.ts +171 -0
  1115. package/src/web/scrapers/snapcraft.ts +187 -0
  1116. package/src/web/scrapers/sourcegraph.ts +336 -0
  1117. package/src/web/scrapers/spdx.ts +108 -0
  1118. package/src/web/scrapers/spotify.ts +198 -0
  1119. package/src/web/scrapers/stackoverflow.ts +120 -0
  1120. package/src/web/scrapers/terraform.ts +277 -0
  1121. package/src/web/scrapers/tldr.ts +47 -0
  1122. package/src/web/scrapers/twitter.ts +94 -0
  1123. package/src/web/scrapers/types.ts +397 -0
  1124. package/src/web/scrapers/utils.ts +109 -0
  1125. package/src/web/scrapers/vimeo.ts +133 -0
  1126. package/src/web/scrapers/vscode-marketplace.ts +187 -0
  1127. package/src/web/scrapers/w3c.ts +156 -0
  1128. package/src/web/scrapers/wikidata.ts +344 -0
  1129. package/src/web/scrapers/wikipedia.ts +84 -0
  1130. package/src/web/scrapers/youtube.ts +325 -0
  1131. package/src/web/search/index.ts +292 -0
  1132. package/src/web/search/provider.ts +157 -0
  1133. package/src/web/search/providers/anthropic.ts +318 -0
  1134. package/src/web/search/providers/base.ts +89 -0
  1135. package/src/web/search/providers/brave.ts +152 -0
  1136. package/src/web/search/providers/codex.ts +591 -0
  1137. package/src/web/search/providers/exa.ts +400 -0
  1138. package/src/web/search/providers/gemini.ts +460 -0
  1139. package/src/web/search/providers/jina.ts +111 -0
  1140. package/src/web/search/providers/kagi.ts +86 -0
  1141. package/src/web/search/providers/kimi.ts +196 -0
  1142. package/src/web/search/providers/parallel.ts +225 -0
  1143. package/src/web/search/providers/perplexity.ts +730 -0
  1144. package/src/web/search/providers/searxng.ts +313 -0
  1145. package/src/web/search/providers/synthetic.ts +114 -0
  1146. package/src/web/search/providers/tavily.ts +176 -0
  1147. package/src/web/search/providers/utils.ts +128 -0
  1148. package/src/web/search/providers/zai.ts +333 -0
  1149. package/src/web/search/render.ts +262 -0
  1150. package/src/web/search/types.ts +482 -0
  1151. package/src/web/search/utils.ts +17 -0
  1152. package/src/workspace-tree.ts +286 -0
@@ -0,0 +1,1330 @@
1
+ import type { AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
2
+ import { sanitizeText } from "@oh-my-pi/pi-utils";
3
+ import { formatBytes } from "../tools/render-utils";
4
+ import { sanitizeWithOptionalSixelPassthrough } from "../utils/sixel";
5
+
6
+ // =============================================================================
7
+ // Constants
8
+ // =============================================================================
9
+
10
+ export const DEFAULT_MAX_LINES = 3000;
11
+ export const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB
12
+ export const DEFAULT_MAX_COLUMN = 512; // Max chars per grep match line
13
+
14
+ /**
15
+ * Default artifact-on-disk cap for {@link OutputSink}.
16
+ *
17
+ * `0` means unbounded: by default, `artifact://<id>` references preserve the
18
+ * complete raw stream instead of a capped head/tail sample.
19
+ */
20
+ export const ARTIFACT_DEFAULT_MAX_BYTES = 0;
21
+ /** Default head budget; the remainder becomes the rolling tail window. */
22
+ export const ARTIFACT_DEFAULT_HEAD_BYTES = 3 * 1024 * 1024; // 3 MiB
23
+
24
+ const NL = "\n";
25
+ const ELLIPSIS = "…";
26
+
27
+ // =============================================================================
28
+ // Interfaces
29
+ // =============================================================================
30
+
31
+ export interface OutputSummary {
32
+ output: string;
33
+ truncated: boolean;
34
+ totalLines: number;
35
+ totalBytes: number;
36
+ outputLines: number;
37
+ outputBytes: number;
38
+ /** Bytes elided from the middle when head-retain mode is active. */
39
+ elidedBytes?: number;
40
+ /** Lines elided from the middle when head-retain mode is active. */
41
+ elidedLines?: number;
42
+ /** Bytes dropped by the per-line column cap (sum across all lines). */
43
+ columnDroppedBytes?: number;
44
+ /** Number of distinct lines that hit the per-line column cap. */
45
+ columnTruncatedLines?: number;
46
+ /** Artifact ID for internal URL access (artifact://<id>) when truncated */
47
+ artifactId?: string;
48
+ }
49
+
50
+ export interface OutputSinkOptions {
51
+ artifactPath?: string;
52
+ artifactId?: string;
53
+ /** Tail buffer budget (bytes). Default DEFAULT_MAX_BYTES. */
54
+ spillThreshold?: number;
55
+ /**
56
+ * When > 0, the sink keeps the first `headBytes` of output in addition to
57
+ * the rolling tail window. Output between the two windows is elided
58
+ * (middle elision). Default 0 = tail-only behavior.
59
+ */
60
+ headBytes?: number;
61
+ /**
62
+ * Per-line byte cap. When > 0, lines wider than `maxColumns` bytes are
63
+ * truncated with an ellipsis at write time; remaining bytes up to the next
64
+ * `\n` are dropped. Cap state persists across chunks so split-mid-line
65
+ * writes still respect the budget. Default 0 = no per-line cap.
66
+ */
67
+ maxColumns?: number;
68
+ onChunk?: (chunk: string) => void;
69
+ /** Minimum ms between onChunk calls. 0 = every chunk (default). */
70
+ chunkThrottleMs?: number;
71
+ /**
72
+ * Optional cap on bytes written to the artifact-on-disk file. When the cap
73
+ * is hit, the head window is preserved verbatim and subsequent output feeds
74
+ * a rolling tail window; on close, the sink writes a single
75
+ * `[ARTIFACT TRUNCATED: …]` notice between them. Default
76
+ * {@link ARTIFACT_DEFAULT_MAX_BYTES} (unbounded).
77
+ */
78
+ artifactMaxBytes?: number;
79
+ /**
80
+ * Bytes reserved for the head window of the capped artifact file. The
81
+ * tail window receives `artifactMaxBytes - artifactHeadBytes`. Default
82
+ * {@link ARTIFACT_DEFAULT_HEAD_BYTES}; clamped to `[0, artifactMaxBytes]`.
83
+ */
84
+ artifactHeadBytes?: number;
85
+ }
86
+
87
+ export interface TruncationResult {
88
+ content: string;
89
+ truncated?: boolean;
90
+ truncatedBy?: "lines" | "bytes" | "middle";
91
+ totalLines: number;
92
+ totalBytes: number;
93
+ outputLines?: number;
94
+ outputBytes?: number;
95
+ /** Bytes elided from the middle (truncateMiddle only). */
96
+ elidedBytes?: number;
97
+ /** Lines elided from the middle (truncateMiddle only). */
98
+ elidedLines?: number;
99
+ lastLinePartial?: boolean;
100
+ firstLineExceedsLimit?: boolean;
101
+ }
102
+
103
+ export interface TruncationOptions {
104
+ /** Maximum number of lines (default: 3000) */
105
+ maxLines?: number;
106
+ /** Maximum number of bytes (default: 50KB) */
107
+ maxBytes?: number;
108
+ /**
109
+ * For `truncateMiddle`: bytes reserved for the head window. The tail
110
+ * window receives `maxBytes - maxHeadBytes`. Default `floor(maxBytes/2)`.
111
+ */
112
+ maxHeadBytes?: number;
113
+ /**
114
+ * For `truncateMiddle`: lines reserved for the head window. The tail
115
+ * window receives `maxLines - maxHeadLines`. Default `floor(maxLines/2)`.
116
+ */
117
+ maxHeadLines?: number;
118
+ }
119
+
120
+ /** Result from byte-level truncation helpers. */
121
+ export interface ByteTruncationResult {
122
+ text: string;
123
+ bytes: number;
124
+ }
125
+
126
+ export interface TailTruncationNoticeOptions {
127
+ fullOutputPath?: string;
128
+ originalContent?: string;
129
+ suffix?: string;
130
+ }
131
+
132
+ export interface HeadTruncationNoticeOptions {
133
+ startLine?: number;
134
+ totalFileLines?: number;
135
+ }
136
+
137
+ // =============================================================================
138
+ // Internal low-level helpers
139
+ // =============================================================================
140
+
141
+ /** Count newline characters via native substring search. */
142
+ function countNewlines(text: string): number {
143
+ let count = 0;
144
+ let pos = text.indexOf(NL);
145
+ while (pos !== -1) {
146
+ count++;
147
+ pos = text.indexOf(NL, pos + 1);
148
+ }
149
+ return count;
150
+ }
151
+
152
+ /** Zero-copy view of a Uint8Array as a Buffer (copies only if already a Buffer). */
153
+ function asBuffer(data: Uint8Array): Buffer {
154
+ return Buffer.isBuffer(data) ? (data as Buffer) : Buffer.from(data.buffer, data.byteOffset, data.byteLength);
155
+ }
156
+
157
+ /** Advance past UTF-8 continuation bytes (10xxxxxx) to a leading byte. */
158
+ function findUtf8BoundaryForward(buf: Buffer, pos: number): number {
159
+ let i = Math.max(0, pos);
160
+ while (i < buf.length && (buf[i] & 0xc0) === 0x80) i++;
161
+ return i;
162
+ }
163
+
164
+ /** Retreat past UTF-8 continuation bytes to land on a leading byte. */
165
+ function findUtf8BoundaryBackward(buf: Buffer, cut: number): number {
166
+ let i = Math.min(buf.length, Math.max(0, cut));
167
+ // If the cut is at end-of-buffer, it's already a valid boundary.
168
+ if (i >= buf.length) return buf.length;
169
+ while (i > 0 && (buf[i] & 0xc0) === 0x80) i--;
170
+ return i;
171
+ }
172
+
173
+ // =============================================================================
174
+ // Byte-level truncation (windowed encoding)
175
+ // =============================================================================
176
+
177
+ function truncateBytesWindowed(
178
+ data: string | Uint8Array,
179
+ maxBytesRaw: number,
180
+ mode: "head" | "tail",
181
+ ): ByteTruncationResult {
182
+ const maxBytes = maxBytesRaw;
183
+ if (maxBytes === 0) return { text: "", bytes: 0 };
184
+
185
+ // --------------------------
186
+ // String path (windowed)
187
+ // --------------------------
188
+ if (typeof data === "string") {
189
+ // Fast non-truncation check only when it *might* fit.
190
+ if (data.length <= maxBytes) {
191
+ const len = Buffer.byteLength(data, "utf-8");
192
+ if (len <= maxBytes) return { text: data, bytes: len };
193
+ // else: multibyte-heavy string; fall through to truncation using full string as window.
194
+ }
195
+
196
+ const window =
197
+ mode === "head"
198
+ ? data.substring(0, Math.min(data.length, maxBytes))
199
+ : data.substring(Math.max(0, data.length - maxBytes));
200
+
201
+ const buf = Buffer.from(window, "utf-8");
202
+
203
+ if (mode === "head") {
204
+ const end = findUtf8BoundaryBackward(buf, maxBytes);
205
+ if (end <= 0) return { text: "", bytes: 0 };
206
+ const slice = buf.subarray(0, end);
207
+ return { text: slice.toString("utf-8"), bytes: slice.length };
208
+ } else {
209
+ const startAt = Math.max(0, buf.length - maxBytes);
210
+ const start = findUtf8BoundaryForward(buf, startAt);
211
+ const slice = buf.subarray(start);
212
+ return { text: slice.toString("utf-8"), bytes: slice.length };
213
+ }
214
+ }
215
+
216
+ // --------------------------
217
+ // Uint8Array / Buffer path
218
+ // --------------------------
219
+ const buf = asBuffer(data);
220
+ if (buf.length <= maxBytes) return { text: buf.toString("utf-8"), bytes: buf.length };
221
+
222
+ if (mode === "head") {
223
+ const end = findUtf8BoundaryBackward(buf, maxBytes);
224
+ if (end <= 0) return { text: "", bytes: 0 };
225
+ const slice = buf.subarray(0, end);
226
+ return { text: slice.toString("utf-8"), bytes: slice.length };
227
+ } else {
228
+ const startAt = buf.length - maxBytes;
229
+ const start = findUtf8BoundaryForward(buf, startAt);
230
+ const slice = buf.subarray(start);
231
+ return { text: slice.toString("utf-8"), bytes: slice.length };
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Truncate a string/buffer to fit within a byte limit, keeping the tail.
237
+ * Handles multi-byte UTF-8 boundaries correctly.
238
+ */
239
+ export function truncateTailBytes(data: string | Uint8Array, maxBytes: number): ByteTruncationResult {
240
+ return truncateBytesWindowed(data, maxBytes, "tail");
241
+ }
242
+
243
+ /**
244
+ * Truncate a string/buffer to fit within a byte limit, keeping the head.
245
+ * Handles multi-byte UTF-8 boundaries correctly.
246
+ */
247
+ export function truncateHeadBytes(data: string | Uint8Array, maxBytes: number): ByteTruncationResult {
248
+ return truncateBytesWindowed(data, maxBytes, "head");
249
+ }
250
+
251
+ // =============================================================================
252
+ // Line-level utilities
253
+ // =============================================================================
254
+
255
+ /**
256
+ * Truncate a single line to max characters, appending '…' if truncated.
257
+ */
258
+ export function truncateLine(
259
+ line: string,
260
+ maxChars: number = DEFAULT_MAX_COLUMN,
261
+ ): { text: string; wasTruncated: boolean } {
262
+ if (line.length <= maxChars) return { text: line, wasTruncated: false };
263
+ return { text: `${line.slice(0, maxChars)}…`, wasTruncated: true };
264
+ }
265
+
266
+ // =============================================================================
267
+ // Content truncation (line + byte aware, no full Buffer allocation)
268
+ // =============================================================================
269
+
270
+ /** Shared helper to build a no-truncation result. */
271
+ export function noTruncResult(content: string, totalLines?: number, totalBytes?: number): TruncationResult {
272
+ if (totalLines == null) totalLines = countNewlines(content) + 1;
273
+ if (totalBytes == null) totalBytes = Buffer.byteLength(content, "utf-8");
274
+ return { content, totalLines, totalBytes };
275
+ }
276
+
277
+ /**
278
+ * Truncate content from the head (keep first N lines/bytes).
279
+ * Never returns partial lines. If the first line exceeds the byte limit,
280
+ * returns empty content with firstLineExceedsLimit=true.
281
+ *
282
+ * This implementation avoids Buffer.from(content) for the whole input.
283
+ * It only computes UTF-8 byteLength for candidate lines that can still fit.
284
+ */
285
+ export function truncateHead(content: string, options: TruncationOptions = {}): TruncationResult {
286
+ const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
287
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
288
+
289
+ const totalBytes = Buffer.byteLength(content, "utf-8");
290
+ const totalLines = countNewlines(content) + 1;
291
+
292
+ if (totalLines <= maxLines && totalBytes <= maxBytes) {
293
+ return noTruncResult(content, totalLines, totalBytes);
294
+ }
295
+
296
+ let includedLines = 0;
297
+ let bytesUsed = 0;
298
+ let cutIndex = 0; // char index where we cut (exclusive)
299
+ let cursor = 0;
300
+
301
+ let truncatedBy: "lines" | "bytes" = "lines";
302
+
303
+ while (includedLines < maxLines) {
304
+ const nl = content.indexOf(NL, cursor);
305
+ const lineEnd = nl === -1 ? content.length : nl;
306
+
307
+ const sepBytes = includedLines > 0 ? 1 : 0;
308
+ const remaining = maxBytes - bytesUsed - sepBytes;
309
+
310
+ // No room even for separators / bytes.
311
+ if (remaining < 0) {
312
+ truncatedBy = "bytes";
313
+ break;
314
+ }
315
+
316
+ // Fast reject huge lines without slicing/encoding:
317
+ // UTF-8 bytes >= UTF-16 code units, so if code units exceed remaining, bytes must exceed too.
318
+ const lineCodeUnits = lineEnd - cursor;
319
+ if (lineCodeUnits > remaining) {
320
+ truncatedBy = "bytes";
321
+ if (includedLines === 0) {
322
+ return {
323
+ content: "",
324
+ truncated: true,
325
+ truncatedBy: "bytes",
326
+ totalLines,
327
+ totalBytes,
328
+ outputLines: 0,
329
+ outputBytes: 0,
330
+ lastLinePartial: false,
331
+ firstLineExceedsLimit: true,
332
+ };
333
+ }
334
+ break;
335
+ }
336
+
337
+ // Small slice (bounded by remaining <= maxBytes) for exact UTF-8 byte count.
338
+ const lineText = content.slice(cursor, lineEnd);
339
+ const lineBytes = Buffer.byteLength(lineText, "utf-8");
340
+
341
+ if (lineBytes > remaining) {
342
+ truncatedBy = "bytes";
343
+ if (includedLines === 0) {
344
+ return {
345
+ content: "",
346
+ truncated: true,
347
+ truncatedBy: "bytes",
348
+ totalLines,
349
+ totalBytes,
350
+ outputLines: 0,
351
+ outputBytes: 0,
352
+ lastLinePartial: false,
353
+ firstLineExceedsLimit: true,
354
+ };
355
+ }
356
+ break;
357
+ }
358
+
359
+ // Include the line (join semantics: no trailing newline after the last included line).
360
+ bytesUsed += sepBytes + lineBytes;
361
+ includedLines++;
362
+
363
+ cutIndex = nl === -1 ? content.length : nl; // exclude the newline after the last included line
364
+ if (nl === -1) break;
365
+ cursor = nl + 1;
366
+ }
367
+
368
+ if (includedLines >= maxLines && bytesUsed <= maxBytes) truncatedBy = "lines";
369
+
370
+ return {
371
+ content: content.slice(0, cutIndex),
372
+ truncated: true,
373
+ truncatedBy,
374
+ totalLines,
375
+ totalBytes,
376
+ outputLines: includedLines,
377
+ outputBytes: bytesUsed,
378
+ lastLinePartial: false,
379
+ firstLineExceedsLimit: false,
380
+ };
381
+ }
382
+
383
+ /**
384
+ * Truncate content from the tail (keep last N lines/bytes).
385
+ * May return a partial first line if the last line exceeds the byte limit.
386
+ *
387
+ * Also avoids Buffer.from(content) for the whole input.
388
+ */
389
+ export function truncateTail(content: string, options: TruncationOptions = {}): TruncationResult {
390
+ const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
391
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
392
+
393
+ const totalBytes = Buffer.byteLength(content, "utf-8");
394
+ const totalLines = countNewlines(content) + 1;
395
+
396
+ if (totalLines <= maxLines && totalBytes <= maxBytes) {
397
+ return noTruncResult(content, totalLines, totalBytes);
398
+ }
399
+
400
+ let includedLines = 0;
401
+ let bytesUsed = 0;
402
+ let startIndex = content.length; // char index where output starts
403
+ let end = content.length; // char index where current line ends (exclusive)
404
+
405
+ let truncatedBy: "lines" | "bytes" = "lines";
406
+
407
+ while (includedLines < maxLines) {
408
+ const nl = content.lastIndexOf(NL, end - 1);
409
+ const lineStart = nl === -1 ? 0 : nl + 1;
410
+
411
+ const sepBytes = includedLines > 0 ? 1 : 0;
412
+ const remaining = maxBytes - bytesUsed - sepBytes;
413
+
414
+ if (remaining < 0) {
415
+ truncatedBy = "bytes";
416
+ break;
417
+ }
418
+
419
+ const lineCodeUnits = end - lineStart;
420
+
421
+ // Fast reject huge line without slicing/encoding.
422
+ if (lineCodeUnits > remaining) {
423
+ truncatedBy = "bytes";
424
+ if (includedLines === 0) {
425
+ // Window the line substring to avoid materializing a giant string.
426
+ const windowStart = Math.max(lineStart, end - maxBytes);
427
+ const window = content.substring(windowStart, end);
428
+ const tail = truncateTailBytes(window, maxBytes);
429
+ return {
430
+ content: tail.text,
431
+ truncated: true,
432
+ truncatedBy: "bytes",
433
+ totalLines,
434
+ totalBytes,
435
+ outputLines: 1,
436
+ outputBytes: tail.bytes,
437
+ lastLinePartial: true,
438
+ firstLineExceedsLimit: false,
439
+ };
440
+ }
441
+ break;
442
+ }
443
+
444
+ const lineText = content.slice(lineStart, end);
445
+ const lineBytes = Buffer.byteLength(lineText, "utf-8");
446
+
447
+ if (lineBytes > remaining) {
448
+ truncatedBy = "bytes";
449
+ if (includedLines === 0) {
450
+ const tail = truncateTailBytes(lineText, maxBytes);
451
+ return {
452
+ content: tail.text,
453
+ truncated: true,
454
+ truncatedBy: "bytes",
455
+ totalLines,
456
+ totalBytes,
457
+ outputLines: 1,
458
+ outputBytes: tail.bytes,
459
+ lastLinePartial: true,
460
+ firstLineExceedsLimit: false,
461
+ };
462
+ }
463
+ break;
464
+ }
465
+
466
+ bytesUsed += sepBytes + lineBytes;
467
+ includedLines++;
468
+ startIndex = lineStart;
469
+
470
+ if (nl === -1) break;
471
+ end = nl; // exclude the newline itself; it'll be accounted as sepBytes in the next iteration
472
+ }
473
+
474
+ if (includedLines >= maxLines && bytesUsed <= maxBytes) truncatedBy = "lines";
475
+
476
+ return {
477
+ content: content.slice(startIndex),
478
+ truncated: true,
479
+ truncatedBy,
480
+ totalLines,
481
+ totalBytes,
482
+ outputLines: includedLines,
483
+ outputBytes: bytesUsed,
484
+ lastLinePartial: false,
485
+ firstLineExceedsLimit: false,
486
+ };
487
+ }
488
+
489
+ // =============================================================================
490
+ // Middle elision (keep head + tail, drop middle)
491
+ // =============================================================================
492
+
493
+ /**
494
+ * Format the inline marker substituted for the elided middle region.
495
+ * Returned without surrounding newlines so callers can position it freely.
496
+ */
497
+ export function formatMiddleElisionMarker(elidedLines: number, elidedBytes: number): string {
498
+ const linesPart = `${elidedLines.toLocaleString()} line${elidedLines === 1 ? "" : "s"}`;
499
+ return `[… ${linesPart} elided (${formatBytes(elidedBytes)}) …]`;
500
+ }
501
+
502
+ /**
503
+ * Truncate content keeping a head window and a tail window, eliding the middle.
504
+ *
505
+ * The combined output is `<head>\n<marker>\n<tail>` when truncation is needed.
506
+ * `maxHeadBytes` defaults to `floor(maxBytes / 2)`; the tail receives the
507
+ * remainder. Falls back to `truncateTail` / `truncateHead` if either side's
508
+ * budget is empty or the content already fits.
509
+ */
510
+ export function truncateMiddle(content: string, options: TruncationOptions = {}): TruncationResult {
511
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
512
+ const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
513
+ const headBytes = options.maxHeadBytes ?? Math.floor(maxBytes / 2);
514
+ const tailBytes = Math.max(0, maxBytes - headBytes);
515
+ const headLines = options.maxHeadLines ?? Math.max(1, Math.floor(maxLines / 2));
516
+ const tailLines = Math.max(0, maxLines - headLines);
517
+
518
+ const totalBytes = Buffer.byteLength(content, "utf-8");
519
+ const totalLines = countNewlines(content) + 1;
520
+
521
+ if (totalBytes <= maxBytes && totalLines <= maxLines) {
522
+ return noTruncResult(content, totalLines, totalBytes);
523
+ }
524
+
525
+ // Degenerate budgets → fall back to one-sided truncation.
526
+ if (headBytes <= 0 || headLines <= 0) {
527
+ return truncateTail(content, { maxBytes: tailBytes || maxBytes, maxLines: tailLines || maxLines });
528
+ }
529
+ if (tailBytes <= 0 || tailLines <= 0) {
530
+ return truncateHead(content, { maxBytes: headBytes, maxLines: headLines });
531
+ }
532
+
533
+ const head = truncateHead(content, { maxBytes: headBytes, maxLines: headLines });
534
+ const tail = truncateTail(content, { maxBytes: tailBytes, maxLines: tailLines });
535
+
536
+ const headLinesKept = head.outputLines ?? 0;
537
+ const tailLinesKept = tail.outputLines ?? 0;
538
+ const headBytesKept = head.outputBytes ?? Buffer.byteLength(head.content, "utf-8");
539
+ const tailBytesKept = tail.outputBytes ?? Buffer.byteLength(tail.content, "utf-8");
540
+
541
+ // Head unusable (first line exceeds budget) → tail-only.
542
+ if (headLinesKept === 0 || head.firstLineExceedsLimit) return tail;
543
+ // Tail unusable → head-only.
544
+ if (tailLinesKept === 0) return head;
545
+ // Windows overlap → no meaningful elision; return content untruncated.
546
+ if (headLinesKept + tailLinesKept >= totalLines) {
547
+ return noTruncResult(content, totalLines, totalBytes);
548
+ }
549
+
550
+ const elidedLines = totalLines - headLinesKept - tailLinesKept;
551
+ // `totalBytes - headBytesKept - tailBytesKept` includes newline separators
552
+ // between the kept windows and the elided region; close enough for a notice.
553
+ const elidedBytes = Math.max(0, totalBytes - headBytesKept - tailBytesKept);
554
+ const marker = formatMiddleElisionMarker(elidedLines, elidedBytes);
555
+ const composed = `${head.content}\n${marker}\n${tail.content}`;
556
+ const markerBytes = Buffer.byteLength(marker, "utf-8");
557
+
558
+ return {
559
+ content: composed,
560
+ truncated: true,
561
+ truncatedBy: "middle",
562
+ totalLines,
563
+ totalBytes,
564
+ outputLines: headLinesKept + tailLinesKept + 1,
565
+ outputBytes: headBytesKept + tailBytesKept + markerBytes + 2,
566
+ elidedLines,
567
+ elidedBytes,
568
+ lastLinePartial: tail.lastLinePartial,
569
+ firstLineExceedsLimit: false,
570
+ };
571
+ }
572
+
573
+ // =============================================================================
574
+ // Inline byte cap — final defense at the tool-result boundary
575
+ // =============================================================================
576
+
577
+ /** Options for {@link enforceInlineByteCap}. */
578
+ export interface InlineByteCapOptions {
579
+ /** Inline byte budget. Defaults to {@link DEFAULT_MAX_BYTES}. */
580
+ maxBytes?: number;
581
+ /** What the text is, for the elision marker (e.g. "bash output"). */
582
+ label: string;
583
+ /**
584
+ * Persist the full text as a session artifact. When an artifact id is
585
+ * returned, a `[raw output: artifact://<id>]` footer is appended so the
586
+ * elided bytes stay recoverable.
587
+ */
588
+ saveArtifact?: (full: string) => string | undefined | Promise<string | undefined>;
589
+ }
590
+
591
+ /** Drop the partial last line of a head window (keep it if there is no newline at all). */
592
+ function trimHeadToLineBoundary(text: string): string {
593
+ const idx = text.lastIndexOf(NL);
594
+ return idx > 0 ? text.substring(0, idx) : text;
595
+ }
596
+
597
+ /** Drop the partial first line of a tail window (keep it if there is no newline at all). */
598
+ function trimTailToLineBoundary(text: string): string {
599
+ const idx = text.indexOf(NL);
600
+ if (idx < 0 || idx === text.length - 1) return text;
601
+ return text.substring(idx + 1);
602
+ }
603
+
604
+ /**
605
+ * Final-defense inline size guard for tool results.
606
+ *
607
+ * No-op when `text` fits within `maxBytes` (the common path). Otherwise keeps
608
+ * ~60% of the budget from the head and ~25% from the tail — cut on line
609
+ * boundaries, never splitting a multi-byte UTF-8 sequence — with an elision
610
+ * marker between. The remaining ~15% is slack for the marker and the optional
611
+ * `[raw output: artifact://<id>]` footer, so the result stays under `maxBytes`.
612
+ */
613
+ export async function enforceInlineByteCap(text: string, options: InlineByteCapOptions): Promise<string> {
614
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
615
+ if (maxBytes <= 0) return text;
616
+ const totalBytes = Buffer.byteLength(text, "utf-8");
617
+ if (totalBytes <= maxBytes) return text;
618
+
619
+ const head = trimHeadToLineBoundary(truncateHeadBytes(text, Math.floor(maxBytes * 0.6)).text);
620
+ const tail = trimTailToLineBoundary(truncateTailBytes(text, Math.floor(maxBytes * 0.25)).text);
621
+ const elidedBytes = Math.max(0, totalBytes - Buffer.byteLength(head, "utf-8") - Buffer.byteLength(tail, "utf-8"));
622
+ const marker = `[… elided ${elidedBytes} bytes of ${options.label} …]`;
623
+ let composed = `${head}\n${marker}\n${tail}`;
624
+
625
+ const artifactId = await options.saveArtifact?.(text);
626
+ if (artifactId) {
627
+ const sep = composed.endsWith(NL) ? "" : NL;
628
+ composed += `${sep}[raw output: artifact://${artifactId}]`;
629
+ }
630
+ return composed;
631
+ }
632
+
633
+ // =============================================================================
634
+ // TailBuffer — ring-style tail buffer with lazy joining
635
+ // =============================================================================
636
+
637
+ const MAX_PENDING = 10;
638
+
639
+ export class TailBuffer {
640
+ #pending: string[] = [];
641
+ #pos = 0; // byte count of the currently-held tail (after trims)
642
+
643
+ constructor(readonly maxBytes: number) {}
644
+
645
+ append(text: string): void {
646
+ if (!text) return;
647
+
648
+ const max = this.maxBytes;
649
+ if (max === 0) {
650
+ this.#pending.length = 0;
651
+ this.#pos = 0;
652
+ return;
653
+ }
654
+
655
+ const n = Buffer.byteLength(text, "utf-8");
656
+
657
+ // If the incoming chunk alone is >= budget, it fully dominates the tail.
658
+ if (n >= max) {
659
+ const { text: t, bytes } = truncateTailBytes(text, max);
660
+ this.#pending[0] = t;
661
+ this.#pending.length = 1;
662
+ this.#pos = bytes;
663
+ return;
664
+ }
665
+
666
+ this.#pos += n;
667
+
668
+ if (this.#pending.length === 0) {
669
+ this.#pending[0] = text;
670
+ this.#pending.length = 1;
671
+ } else {
672
+ this.#pending.push(text);
673
+ if (this.#pending.length > MAX_PENDING) this.#compact();
674
+ }
675
+
676
+ // Trim when we exceed 2× budget to amortize cost.
677
+ if (this.#pos > max * 2) this.#trimTo(max);
678
+ }
679
+
680
+ text(): string {
681
+ const max = this.maxBytes;
682
+ this.#trimTo(max);
683
+ return this.#flush();
684
+ }
685
+
686
+ bytes(): number {
687
+ const max = this.maxBytes;
688
+ this.#trimTo(max);
689
+ return this.#pos;
690
+ }
691
+
692
+ // -- private ---------------------------------------------------------------
693
+
694
+ #compact(): void {
695
+ this.#pending[0] = this.#pending.join("");
696
+ this.#pending.length = 1;
697
+ }
698
+
699
+ #flush(): string {
700
+ if (this.#pending.length === 0) return "";
701
+ if (this.#pending.length > 1) this.#compact();
702
+ return this.#pending[0];
703
+ }
704
+
705
+ #trimTo(max: number): void {
706
+ if (max === 0) {
707
+ this.#pending.length = 0;
708
+ this.#pos = 0;
709
+ return;
710
+ }
711
+ if (this.#pos <= max) return;
712
+
713
+ const joined = this.#flush();
714
+ const { text, bytes } = truncateTailBytes(joined, max);
715
+ this.#pos = bytes;
716
+ this.#pending[0] = text;
717
+ this.#pending.length = 1;
718
+ }
719
+ }
720
+
721
+ // =============================================================================
722
+ // OutputSink — line-buffered output with file spill support
723
+ // =============================================================================
724
+
725
+ export class OutputSink {
726
+ #buffer = "";
727
+ #bufferBytes = 0;
728
+ #head = "";
729
+ #headBytes = 0;
730
+ #headLines = 0; // newline count inside #head
731
+ #headRetentionDisabled = false;
732
+ #totalLines = 0; // newline count
733
+ #totalBytes = 0;
734
+ #sawData = false;
735
+ #truncated = false;
736
+ #lastChunkTime = 0;
737
+ #pendingChunk = "";
738
+
739
+ // Per-line column cap streaming state (persists across `push` calls so a
740
+ // long line split across chunks still trips the same trigger).
741
+ #currentLineBytes = 0;
742
+ #columnEllipsisAdded = false;
743
+ #columnDroppedBytes = 0;
744
+ #columnTruncatedLines = 0;
745
+ #file?: {
746
+ path: string;
747
+ artifactId?: string;
748
+ sink: Bun.FileSink;
749
+ };
750
+
751
+ // Queue of chunks waiting for the file sink to be created.
752
+ #pendingFileWrites?: string[];
753
+ #fileReady = false;
754
+
755
+ readonly #artifactPath?: string;
756
+ readonly #artifactId?: string;
757
+ readonly #spillThreshold: number;
758
+ readonly #headLimit: number;
759
+ readonly #onChunk?: (chunk: string) => void;
760
+ readonly #chunkThrottleMs: number;
761
+ readonly #maxColumns: number;
762
+
763
+ // Optional artifact-on-disk cap. When `#artifactMaxBytes > 0` the file sink
764
+ // owns a head budget + a rolling tail buffer; once the head is closed,
765
+ // subsequent chunks are diverted into `#artifactTailRing` (bounded by
766
+ // `#artifactTailBudget`). On `dump()` the tail is flushed back to the sink
767
+ // behind a `[ARTIFACT TRUNCATED: …]` notice. The default cap is disabled so
768
+ // advertised `artifact://<id>` captures are lossless.
769
+ readonly #artifactMaxBytes: number;
770
+ readonly #artifactHeadBudget: number;
771
+ readonly #artifactTailBudget: number;
772
+ #artifactHeadBytesWritten = 0;
773
+ #artifactHeadClosed = false;
774
+ #artifactTailRing = "";
775
+ #artifactTailRingBytes = 0;
776
+ #artifactTailIncomingBytes = 0;
777
+
778
+ constructor(options?: OutputSinkOptions) {
779
+ const {
780
+ artifactPath,
781
+ artifactId,
782
+ spillThreshold = DEFAULT_MAX_BYTES,
783
+ headBytes = 0,
784
+ maxColumns = 0,
785
+ onChunk,
786
+ chunkThrottleMs = 0,
787
+ artifactMaxBytes = ARTIFACT_DEFAULT_MAX_BYTES,
788
+ artifactHeadBytes = ARTIFACT_DEFAULT_HEAD_BYTES,
789
+ } = options ?? {};
790
+ this.#artifactPath = artifactPath;
791
+ this.#artifactId = artifactId;
792
+ this.#spillThreshold = spillThreshold;
793
+ this.#headLimit = Math.max(0, headBytes);
794
+ this.#maxColumns = Math.max(0, maxColumns);
795
+ this.#onChunk = onChunk;
796
+ this.#chunkThrottleMs = chunkThrottleMs;
797
+ this.#artifactMaxBytes = Math.max(0, artifactMaxBytes);
798
+ this.#artifactHeadBudget = Math.max(0, Math.min(artifactHeadBytes, this.#artifactMaxBytes));
799
+ this.#artifactTailBudget = Math.max(0, this.#artifactMaxBytes - this.#artifactHeadBudget);
800
+ }
801
+
802
+ /**
803
+ * Push a chunk of output. The buffer management and onChunk callback run
804
+ * synchronously. File sink writes are deferred and serialized internally.
805
+ */
806
+ push(chunk: string): void {
807
+ chunk = sanitizeWithOptionalSixelPassthrough(chunk, sanitizeText);
808
+
809
+ // Throttled onChunk: coalesce chunks arriving inside the throttle window
810
+ // and flush the buffered concatenation on the next eligible tick (plus a
811
+ // final flush in dump()) so the preview never has silent gaps.
812
+ // Live preview gets the raw (pre-cap) chunk so the TUI never lags behind
813
+ // what reached the sink — the column cap is for the persisted LLM view.
814
+ if (this.#onChunk) {
815
+ const now = Date.now();
816
+ if (now - this.#lastChunkTime >= this.#chunkThrottleMs) {
817
+ this.#lastChunkTime = now;
818
+ const merged = this.#pendingChunk + chunk;
819
+ this.#pendingChunk = "";
820
+ this.#onChunk(merged);
821
+ } else {
822
+ this.#pendingChunk += chunk;
823
+ }
824
+ }
825
+
826
+ const rawBytes = Buffer.byteLength(chunk, "utf-8");
827
+ this.#totalBytes += rawBytes;
828
+
829
+ if (chunk.length > 0) {
830
+ this.#sawData = true;
831
+ this.#totalLines += countNewlines(chunk);
832
+ }
833
+
834
+ // Per-line column cap. State persists across chunks so a mid-line split
835
+ // still respects the budget. Operates on the sanitized chunk; the cap is
836
+ // applied before head/tail accounting but after artifact mirroring decides.
837
+ const capped = this.#maxColumns > 0 ? this.#applyColumnCap(chunk) : chunk;
838
+ const cappedBytes = capped === chunk ? rawBytes : Buffer.byteLength(capped, "utf-8");
839
+ const cappedThisChunk = cappedBytes < rawBytes;
840
+ if (cappedThisChunk) this.#truncated = true;
841
+
842
+ // Mirror RAW chunk to the artifact file so the on-disk record is the full
843
+ // uncapped stream. Mirror triggers on: in-memory overflow OR this chunk's
844
+ // column cap dropped bytes (otherwise we'd lose data) OR file already open.
845
+ if (this.#artifactPath && (this.#file != null || cappedThisChunk || this.#willOverflow(cappedBytes))) {
846
+ this.#writeToFile(chunk);
847
+ }
848
+
849
+ if (cappedBytes === 0) return;
850
+
851
+ // Head retention: drain the (capped) chunk into #head until the budget is
852
+ // exhausted, then forward any leftover to the tail buffer.
853
+ let tailChunk = capped;
854
+ let tailBytes = cappedBytes;
855
+ if (this.#headLimit > 0 && !this.#headRetentionDisabled && this.#headBytes < this.#headLimit) {
856
+ const room = this.#headLimit - this.#headBytes;
857
+ if (cappedBytes <= room) {
858
+ this.#head += capped;
859
+ this.#headBytes += cappedBytes;
860
+ this.#headLines += countNewlines(capped);
861
+ return;
862
+ }
863
+ // Split: head takes a UTF-8-safe prefix; remainder flows to tail.
864
+ const headSlice = truncateHeadBytes(capped, room);
865
+ if (headSlice.bytes > 0) {
866
+ this.#head += headSlice.text;
867
+ this.#headBytes += headSlice.bytes;
868
+ this.#headLines += countNewlines(headSlice.text);
869
+ tailChunk = capped.substring(headSlice.text.length);
870
+ tailBytes = cappedBytes - headSlice.bytes;
871
+ }
872
+ }
873
+
874
+ this.#pushTail(tailChunk, tailBytes);
875
+ }
876
+
877
+ /**
878
+ * Apply the per-line byte cap to `chunk`, dropping bytes that would push the
879
+ * current line beyond `#maxColumns`. Emits a single `…` once a line trips the
880
+ * cap; subsequent bytes are skipped until the next `\n`. State persists
881
+ * across calls so a long line split across chunks still produces one marker.
882
+ */
883
+ #applyColumnCap(chunk: string): string {
884
+ if (chunk.length === 0) return chunk;
885
+ const max = this.#maxColumns;
886
+ const parts: string[] = [];
887
+ let cursor = 0;
888
+ while (cursor < chunk.length) {
889
+ const nlIdx = chunk.indexOf(NL, cursor);
890
+ const segEnd = nlIdx === -1 ? chunk.length : nlIdx;
891
+ if (segEnd > cursor) {
892
+ const segment = chunk.substring(cursor, segEnd);
893
+ if (this.#columnEllipsisAdded) {
894
+ // Past the cap; drop until newline.
895
+ this.#columnDroppedBytes += Buffer.byteLength(segment, "utf-8");
896
+ } else {
897
+ const segBytes = Buffer.byteLength(segment, "utf-8");
898
+ const remaining = max - this.#currentLineBytes;
899
+ if (segBytes <= remaining) {
900
+ parts.push(segment);
901
+ this.#currentLineBytes += segBytes;
902
+ } else {
903
+ // First overflow on this line: keep what fits, append ellipsis,
904
+ // arm the skip-until-newline flag.
905
+ const ellipsisBytes = 3; // "…" in UTF-8
906
+ const headRoom = Math.max(0, remaining - ellipsisBytes);
907
+ let kept = "";
908
+ let keptBytes = 0;
909
+ if (headRoom > 0) {
910
+ const sliced = truncateHeadBytes(segment, headRoom);
911
+ kept = sliced.text;
912
+ keptBytes = sliced.bytes;
913
+ parts.push(kept);
914
+ }
915
+ parts.push(ELLIPSIS);
916
+ this.#columnDroppedBytes += segBytes - keptBytes;
917
+ this.#columnTruncatedLines++;
918
+ this.#currentLineBytes += keptBytes + ellipsisBytes;
919
+ this.#columnEllipsisAdded = true;
920
+ }
921
+ }
922
+ }
923
+ if (nlIdx === -1) break;
924
+ parts.push(NL);
925
+ this.#currentLineBytes = 0;
926
+ this.#columnEllipsisAdded = false;
927
+ cursor = nlIdx + 1;
928
+ }
929
+ return parts.join("");
930
+ }
931
+
932
+ #willOverflow(dataBytes: number): boolean {
933
+ // Triggers file mirroring as soon as the next chunk would push us over
934
+ // the tail budget (head retention does not change spill-to-artifact).
935
+ return this.#bufferBytes + dataBytes > this.#spillThreshold;
936
+ }
937
+
938
+ #pushTail(chunk: string, dataBytes: number): void {
939
+ if (dataBytes === 0) return;
940
+
941
+ const threshold = this.#spillThreshold;
942
+ const willOverflow = this.#bufferBytes + dataBytes > threshold;
943
+
944
+ if (!willOverflow) {
945
+ this.#buffer += chunk;
946
+ this.#bufferBytes += dataBytes;
947
+ return;
948
+ }
949
+
950
+ // Overflow: keep only a tail window in memory.
951
+ this.#truncated = true;
952
+
953
+ // Avoid creating a giant intermediate string when chunk alone dominates.
954
+ if (dataBytes >= threshold) {
955
+ const { text, bytes } = truncateTailBytes(chunk, threshold);
956
+ this.#buffer = text;
957
+ this.#bufferBytes = bytes;
958
+ } else {
959
+ // Intermediate size is bounded (<= threshold + dataBytes), safe to concat.
960
+ this.#buffer += chunk;
961
+ this.#bufferBytes += dataBytes;
962
+
963
+ const { text, bytes } = truncateTailBytes(this.#buffer, threshold);
964
+ this.#buffer = text;
965
+ this.#bufferBytes = bytes;
966
+ }
967
+ }
968
+
969
+ /**
970
+ * Write a chunk to the artifact file. Handles the async file sink creation
971
+ * by queuing writes until the sink is ready, then draining synchronously.
972
+ * Once the sink is up, every byte flows through {@link #emitToSink} which
973
+ * owns the head + tail cap so artifacts cannot grow beyond
974
+ * `#artifactMaxBytes` on disk.
975
+ */
976
+ #writeToFile(chunk: string): void {
977
+ if (this.#fileReady && this.#file) {
978
+ this.#emitToSink(chunk);
979
+ return;
980
+ }
981
+ // File sink not yet created — queue this chunk and kick off creation.
982
+ // The queue is bounded only by how many chunks arrive before the open
983
+ // resolves (typically <2). The cap is enforced on drain.
984
+ if (!this.#pendingFileWrites) {
985
+ this.#pendingFileWrites = [chunk];
986
+ void this.#createFileSink();
987
+ } else {
988
+ this.#pendingFileWrites.push(chunk);
989
+ }
990
+ }
991
+
992
+ /**
993
+ * Cap-aware sink writer. Bytes flow into the head window verbatim until the
994
+ * budget is exhausted; subsequent bytes are diverted into a rolling tail
995
+ * ring, evicted from the front so total RAM stays bounded by
996
+ * `#artifactTailBudget`. `dump()` replays the ring behind a single notice
997
+ * line before closing the sink.
998
+ *
999
+ * When the cap is disabled (`#artifactMaxBytes === 0`) this collapses to a
1000
+ * straight pass-through, preserving the historical "stream everything"
1001
+ * contract.
1002
+ */
1003
+ #emitToSink(chunk: string): void {
1004
+ if (!this.#file || chunk.length === 0) return;
1005
+ if (this.#artifactMaxBytes === 0) {
1006
+ this.#file.sink.write(chunk);
1007
+ return;
1008
+ }
1009
+ const chunkBytes = Buffer.byteLength(chunk, "utf-8");
1010
+ const room = this.#artifactHeadClosed ? 0 : this.#artifactHeadBudget - this.#artifactHeadBytesWritten;
1011
+ if (room >= chunkBytes) {
1012
+ this.#file.sink.write(chunk);
1013
+ this.#artifactHeadBytesWritten += chunkBytes;
1014
+ return;
1015
+ }
1016
+ let overflow = chunk;
1017
+ if (room > 0) {
1018
+ const headSlice = truncateHeadBytes(chunk, room);
1019
+ if (headSlice.bytes > 0) {
1020
+ this.#file.sink.write(headSlice.text);
1021
+ this.#artifactHeadBytesWritten += headSlice.bytes;
1022
+ }
1023
+ // Even when UTF-8 boundary safety leaves a few bytes of nominal room,
1024
+ // this chunk has already overflowed the head window. Close it now so a
1025
+ // later small ASCII chunk cannot be written before this overflow tail.
1026
+ this.#artifactHeadClosed = true;
1027
+ overflow = chunk.substring(headSlice.text.length);
1028
+ }
1029
+ if (overflow.length === 0 || this.#artifactTailBudget === 0) {
1030
+ // No tail budget: count the dropped bytes so the notice reflects them.
1031
+ if (overflow.length > 0) {
1032
+ this.#artifactTailIncomingBytes += Buffer.byteLength(overflow, "utf-8");
1033
+ }
1034
+ return;
1035
+ }
1036
+ this.#pushArtifactTail(overflow);
1037
+ }
1038
+
1039
+ #pushArtifactTail(chunk: string): void {
1040
+ const chunkBytes = Buffer.byteLength(chunk, "utf-8");
1041
+ this.#artifactTailIncomingBytes += chunkBytes;
1042
+ const budget = this.#artifactTailBudget;
1043
+ if (chunkBytes >= budget) {
1044
+ // Chunk alone dominates — keep only its tail slice.
1045
+ const { text, bytes } = truncateTailBytes(chunk, budget);
1046
+ this.#artifactTailRing = text;
1047
+ this.#artifactTailRingBytes = bytes;
1048
+ return;
1049
+ }
1050
+ this.#artifactTailRing += chunk;
1051
+ this.#artifactTailRingBytes += chunkBytes;
1052
+ if (this.#artifactTailRingBytes > budget) {
1053
+ const { text, bytes } = truncateTailBytes(this.#artifactTailRing, budget);
1054
+ this.#artifactTailRing = text;
1055
+ this.#artifactTailRingBytes = bytes;
1056
+ }
1057
+ }
1058
+
1059
+ async #createFileSink(): Promise<void> {
1060
+ if (!this.#artifactPath || this.#fileReady) return;
1061
+ try {
1062
+ const sink = Bun.file(this.#artifactPath).writer();
1063
+ this.#file = { path: this.#artifactPath, artifactId: this.#artifactId, sink };
1064
+ this.#fileReady = true;
1065
+
1066
+ // Head-retained bytes precede the rolling tail buffer in the capture.
1067
+ // Route through #emitToSink so they count against the artifact head
1068
+ // budget — a direct sink.write would let them escape the cap.
1069
+ if (this.#head.length > 0) {
1070
+ this.#emitToSink(this.#head);
1071
+ }
1072
+
1073
+ // Flush existing buffer to file BEFORE it gets trimmed further.
1074
+ if (this.#buffer.length > 0) {
1075
+ this.#emitToSink(this.#buffer);
1076
+ }
1077
+
1078
+ // Drain any chunks that arrived while the sink was being created.
1079
+ if (this.#pendingFileWrites) {
1080
+ for (const pending of this.#pendingFileWrites) {
1081
+ this.#emitToSink(pending);
1082
+ }
1083
+ this.#pendingFileWrites = undefined;
1084
+ }
1085
+ } catch {
1086
+ try {
1087
+ await this.#file?.sink?.end();
1088
+ } catch {
1089
+ /* ignore */
1090
+ }
1091
+ this.#file = undefined;
1092
+ this.#pendingFileWrites = undefined;
1093
+ this.#fileReady = false;
1094
+ }
1095
+ }
1096
+
1097
+ createInput(): WritableStream<Uint8Array | string> {
1098
+ const dec = new TextDecoder("utf-8", { ignoreBOM: true });
1099
+ const finalize = () => {
1100
+ this.push(dec.decode());
1101
+ };
1102
+ return new WritableStream({
1103
+ write: chunk => {
1104
+ this.push(typeof chunk === "string" ? chunk : dec.decode(chunk, { stream: true }));
1105
+ },
1106
+ close: finalize,
1107
+ abort: finalize,
1108
+ });
1109
+ }
1110
+
1111
+ /**
1112
+ * Replace the in-memory buffer with the given text. Used when an upstream
1113
+ * minimizer rewrites the captured output after the raw bytes have already
1114
+ * been streamed.
1115
+ *
1116
+ * After this call the buffer is authoritative: streaming counters realign
1117
+ * to the replacement, the retained head window is cleared, and head
1118
+ * retention is disabled so subsequent `push()` calls append directly to the
1119
+ * tail buffer instead of repopulating the (now meaningless) head window
1120
+ * — which would otherwise reorder content and trip the middle-elision
1121
+ * branch in `dump()` against stale totals.
1122
+ */
1123
+ replace(text: string): void {
1124
+ this.#buffer = text;
1125
+ this.#bufferBytes = Buffer.byteLength(text, "utf-8");
1126
+ this.#head = "";
1127
+ this.#headBytes = 0;
1128
+ this.#headLines = 0;
1129
+ this.#headRetentionDisabled = true;
1130
+ this.#totalBytes = this.#bufferBytes;
1131
+ this.#totalLines = countNewlines(text);
1132
+ this.#sawData = text.length > 0;
1133
+ this.#truncated = false;
1134
+ this.#currentLineBytes = 0;
1135
+ this.#columnEllipsisAdded = false;
1136
+ this.#columnDroppedBytes = 0;
1137
+ this.#columnTruncatedLines = 0;
1138
+ this.#pendingChunk = "";
1139
+ }
1140
+
1141
+ /**
1142
+ * Replay the rolling tail ring back into the artifact sink. When bytes
1143
+ * were actually dropped from the middle (the head budget was exhausted
1144
+ * *and* the tail ring evicted), a single `[ARTIFACT TRUNCATED: …]`
1145
+ * notice is injected between head and tail so a reader of
1146
+ * `artifact://<id>` understands the gap. When the total stream simply
1147
+ * spilled past the head budget but still fits below `artifactMaxBytes`,
1148
+ * `droppedBytes` is zero — head + tail together are the verbatim stream
1149
+ * and the notice is suppressed so we don't corrupt the artifact with a
1150
+ * misleading "0 B elided" marker (PR #2083 review by codex).
1151
+ *
1152
+ * No-op when the cap was never hit at all (head budget never exhausted,
1153
+ * tail ring empty).
1154
+ */
1155
+ #flushArtifactTailIfCapped(): void {
1156
+ if (!this.#file) return;
1157
+ if (this.#artifactMaxBytes === 0) return;
1158
+ const tailBytes = this.#artifactTailRingBytes;
1159
+ const droppedBytes = Math.max(0, this.#artifactTailIncomingBytes - tailBytes);
1160
+ if (tailBytes === 0 && droppedBytes === 0) return;
1161
+
1162
+ if (droppedBytes > 0) {
1163
+ const headWritten = this.#artifactHeadBytesWritten;
1164
+ const totalCapped = headWritten + this.#artifactTailIncomingBytes;
1165
+ const headSep = headWritten > 0 ? "\n" : "";
1166
+ const tailSep = tailBytes > 0 && !this.#artifactTailRing.startsWith("\n") ? "\n" : "";
1167
+ const notice =
1168
+ `${headSep}[ARTIFACT TRUNCATED: kept first ${formatBytes(headWritten)} + last ${formatBytes(tailBytes)} ` +
1169
+ `of ${formatBytes(totalCapped)}; ${formatBytes(droppedBytes)} elided from the middle]${tailSep}`;
1170
+ this.#file.sink.write(notice);
1171
+ }
1172
+ if (tailBytes > 0) {
1173
+ this.#file.sink.write(this.#artifactTailRing);
1174
+ }
1175
+ }
1176
+
1177
+ async dump(notice?: string): Promise<OutputSummary> {
1178
+ const noticeLine = notice ? `[${notice}]\n` : "";
1179
+
1180
+ // Flush any chunk still held back by the throttle so the live preview
1181
+ // ends with the complete stream.
1182
+ if (this.#onChunk && this.#pendingChunk.length > 0) {
1183
+ const pending = this.#pendingChunk;
1184
+ this.#pendingChunk = "";
1185
+ this.#onChunk(pending);
1186
+ }
1187
+ const totalLines = this.#sawData ? this.#totalLines + 1 : 0;
1188
+
1189
+ if (this.#file) {
1190
+ this.#flushArtifactTailIfCapped();
1191
+ await this.#file.sink.end();
1192
+ }
1193
+
1194
+ // Compose the visible output. With head retention, splice head + marker
1195
+ // + tail when content was elided. Otherwise return the rolling buffer.
1196
+ const headBytes = this.#headBytes;
1197
+ const tailBuf = this.#buffer;
1198
+ const tailBytes = this.#bufferBytes;
1199
+ const headLines = this.#headLines + (headBytes > 0 && !this.#head.endsWith("\n") ? 1 : 0);
1200
+ const tailLines = tailBuf.length > 0 ? countNewlines(tailBuf) + 1 : 0;
1201
+
1202
+ // Bytes that survived the column cap. Middle elision operates on these,
1203
+ // so column-dropped bytes don't inflate the "elided from middle" count.
1204
+ const effectiveTotalBytes = Math.max(0, this.#totalBytes - this.#columnDroppedBytes);
1205
+
1206
+ let body: string;
1207
+ let outputBytes: number;
1208
+ let outputLines: number;
1209
+ let elidedBytes: number | undefined;
1210
+ let elidedLines: number | undefined;
1211
+
1212
+ if (headBytes > 0 && effectiveTotalBytes > headBytes + tailBytes) {
1213
+ // Middle was elided. Emit head + marker + tail.
1214
+ elidedBytes = Math.max(0, effectiveTotalBytes - headBytes - tailBytes);
1215
+ elidedLines = Math.max(0, totalLines - headLines - tailLines);
1216
+ const marker = formatMiddleElisionMarker(elidedLines, elidedBytes);
1217
+ const markerBytes = Buffer.byteLength(marker, "utf-8");
1218
+ const headSep = this.#head.endsWith("\n") ? "" : "\n";
1219
+ const tailSep = tailBuf.startsWith("\n") ? "" : "\n";
1220
+ body = `${this.#head}${headSep}${marker}${tailSep}${tailBuf}`;
1221
+ outputBytes =
1222
+ headBytes +
1223
+ markerBytes +
1224
+ tailBytes +
1225
+ Buffer.byteLength(headSep, "utf-8") +
1226
+ Buffer.byteLength(tailSep, "utf-8");
1227
+ outputLines = headLines + 1 + tailLines;
1228
+ this.#truncated = true;
1229
+ } else if (headBytes > 0) {
1230
+ // Head + tail combine into the full buffered output (no overlap or elision).
1231
+ body = `${this.#head}${tailBuf}`;
1232
+ outputBytes = headBytes + tailBytes;
1233
+ outputLines = body.length > 0 ? countNewlines(body) + 1 : 0;
1234
+ } else {
1235
+ body = tailBuf;
1236
+ outputBytes = tailBytes;
1237
+ outputLines = tailLines;
1238
+ }
1239
+
1240
+ return {
1241
+ output: `${noticeLine}${body}`,
1242
+ truncated: this.#truncated,
1243
+ totalLines,
1244
+ totalBytes: this.#totalBytes,
1245
+ outputLines,
1246
+ outputBytes,
1247
+ elidedBytes,
1248
+ elidedLines,
1249
+ columnDroppedBytes: this.#columnDroppedBytes > 0 ? this.#columnDroppedBytes : undefined,
1250
+ columnTruncatedLines: this.#columnTruncatedLines > 0 ? this.#columnTruncatedLines : undefined,
1251
+ artifactId: this.#file?.artifactId,
1252
+ };
1253
+ }
1254
+ }
1255
+
1256
+ // =============================================================================
1257
+ // Truncation notice formatting
1258
+ // =============================================================================
1259
+
1260
+ /**
1261
+ * Format a truncation notice for tail-truncated output (bash, python, ssh).
1262
+ * Returns empty string if not truncated.
1263
+ */
1264
+ export function formatTailTruncationNotice(
1265
+ truncation: TruncationResult,
1266
+ options: TailTruncationNoticeOptions = {},
1267
+ ): string {
1268
+ if (!truncation.truncated) return "";
1269
+
1270
+ const { fullOutputPath, originalContent, suffix = "" } = options;
1271
+ const startLine = truncation.totalLines - (truncation.outputLines ?? truncation.totalLines) + 1;
1272
+ const endLine = truncation.totalLines;
1273
+ const fullOutputPart = fullOutputPath ? `. Full output: ${fullOutputPath}` : "";
1274
+
1275
+ let notice: string;
1276
+ if (truncation.lastLinePartial) {
1277
+ let lastLineSizePart = "";
1278
+ if (originalContent) {
1279
+ const lastNl = originalContent.lastIndexOf(NL);
1280
+ const lastLine = lastNl === -1 ? originalContent : originalContent.substring(lastNl + 1);
1281
+ lastLineSizePart = ` (line is ${formatBytes(Buffer.byteLength(lastLine, "utf-8"))})`;
1282
+ }
1283
+ notice = `[Showing last ${formatBytes(truncation.outputBytes ?? truncation.totalBytes)} of line ${endLine}${lastLineSizePart}${fullOutputPart}${suffix}]`;
1284
+ } else {
1285
+ notice = `[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}${fullOutputPart}${suffix}]`;
1286
+ }
1287
+
1288
+ return `\n\n${notice}`;
1289
+ }
1290
+
1291
+ /**
1292
+ * Format a truncation notice for head-truncated output (read tool).
1293
+ * Returns empty string if not truncated.
1294
+ */
1295
+ export function formatHeadTruncationNotice(
1296
+ truncation: TruncationResult,
1297
+ options: HeadTruncationNoticeOptions = {},
1298
+ ): string {
1299
+ if (!truncation.truncated) return "";
1300
+
1301
+ const startLineDisplay = options.startLine ?? 1;
1302
+ const totalFileLines = options.totalFileLines ?? truncation.totalLines;
1303
+ const endLineDisplay = startLineDisplay + (truncation.outputLines ?? truncation.totalLines) - 1;
1304
+ const nextOffset = endLineDisplay + 1;
1305
+ const notice = `[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use :${nextOffset} to continue]`;
1306
+ return `\n\n${notice}`;
1307
+ }
1308
+
1309
+ // =============================================================================
1310
+ // Streaming tail update helper (shared by bash/ssh tools)
1311
+ // =============================================================================
1312
+
1313
+ /**
1314
+ * Build an onChunk handler that appends to a TailBuffer and emits a streaming
1315
+ * update (when `onUpdate` is defined) with the buffer's current text.
1316
+ */
1317
+ export function streamTailUpdates<TDetails, TInput = unknown>(
1318
+ tailBuffer: TailBuffer,
1319
+ onUpdate: AgentToolUpdateCallback<TDetails, TInput> | undefined,
1320
+ ): (chunk: string) => void {
1321
+ return chunk => {
1322
+ tailBuffer.append(chunk);
1323
+ if (onUpdate) {
1324
+ onUpdate({
1325
+ content: [{ type: "text", text: tailBuffer.text() }],
1326
+ details: {} as TDetails,
1327
+ });
1328
+ }
1329
+ };
1330
+ }