@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,1217 @@
1
+ import { Database } from "bun:sqlite";
2
+ import * as fs from "node:fs/promises";
3
+ import * as path from "node:path";
4
+
5
+ import { formatHashlineHeader, stripHashlinePrefixes } from "@oh-my-pi/hashline";
6
+ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
7
+ import type { Component } from './stubs/tui/index.ts';
8
+ import { isEnoent, isRecord, prompt, untilAborted } from "@oh-my-pi/pi-utils";
9
+ import * as z from "zod/v4";
10
+
11
+ import { canonicalSnapshotKey, getFileSnapshotStore } from "../edit/file-snapshot-store";
12
+ import { normalizeToLF } from "../edit/normalize";
13
+ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
14
+ import { InternalUrlRouter } from "../internal-urls";
15
+ import { parseInternalUrl } from "../internal-urls/parse";
16
+ import { createLspWritethrough, type FileDiagnosticsResult, type WritethroughCallback, writethroughNoop } from "../lsp";
17
+ import { getDiagnosticsLedger } from "../lsp/diagnostics-ledger";
18
+ import { getLanguageFromPath, highlightCode, type Theme } from "../modes/theme/theme";
19
+ import writeDescription from "../prompts/tools/write.md" with { type: "text" };
20
+ import type { ToolSession } from "../sdk";
21
+ import { fileHyperlink, framedBlock, renderStatusLine } from "../tui";
22
+ import { resolveFileDisplayMode } from "../utils/file-display-mode";
23
+ import { truncateForPrompt } from "./approval";
24
+ import { parseArchivePathCandidates } from "./archive-reader";
25
+ import { assertEditableFile } from "./auto-generated-guard";
26
+ import {
27
+ type ConflictEntry,
28
+ conflictRegionPresent,
29
+ conflictRegionsEqual,
30
+ expandContentTokens,
31
+ getConflictHistory,
32
+ parseConflictUri,
33
+ spliceConflict,
34
+ } from "./conflict-detect";
35
+ import { invalidateFsScanAfterWrite } from "./fs-cache-invalidation";
36
+ import { type OutputMeta, outputMeta } from "./output-meta";
37
+ import { formatPathRelativeToCwd, isInternalUrlPath } from "./path-utils";
38
+ import { enforcePlanModeWrite, resolvePlanPath } from "./plan-mode-guard";
39
+ import {
40
+ formatDiagnostics,
41
+ formatErrorDetail,
42
+ formatExpandHint,
43
+ formatMoreItems,
44
+ formatStatusIcon,
45
+ getLspBatchRequest,
46
+ replaceTabs,
47
+ shortenPath,
48
+ } from "./render-utils";
49
+ import {
50
+ deleteRowByKey,
51
+ deleteRowByRowId,
52
+ insertRow,
53
+ isSqliteFile,
54
+ parseSqlitePathCandidates,
55
+ resolveTableRowLookup,
56
+ updateRowByKey,
57
+ updateRowByRowId,
58
+ } from "./sqlite-reader";
59
+ import { ToolError } from "./tool-errors";
60
+ import { toolResult } from "./tool-result";
61
+
62
+ const LOOSE_HASHLINE_HEADER_RE = /^\s*\[[^#\r\n]+#[^ \t\r\n]*\]\s*$/;
63
+
64
+ let fflateModulePromise: Promise<typeof import("fflate")> | undefined;
65
+ async function loadFflate(): Promise<typeof import("fflate")> {
66
+ if (!fflateModulePromise) fflateModulePromise = import("fflate");
67
+ return fflateModulePromise;
68
+ }
69
+
70
+ const writeSchema = z.object({
71
+ path: z.string().describe("file path"),
72
+ content: z.string().describe("file content"),
73
+ });
74
+
75
+ export type WriteToolInput = z.infer<typeof writeSchema>;
76
+
77
+ /** Details returned by the write tool for TUI rendering */
78
+ export interface WriteToolDetails {
79
+ diagnostics?: FileDiagnosticsResult;
80
+ meta?: OutputMeta;
81
+ /** Set when the file was auto-chmod'd because content begins with a `#!` shebang. */
82
+ madeExecutable?: boolean;
83
+ /** Absolute filesystem path the write resolved to. Used by the renderer to wrap
84
+ * the (possibly cwd-relative) header path in an OSC 8 `file://` hyperlink. */
85
+ resolvedPath?: string;
86
+ }
87
+
88
+ /**
89
+ * Strip hashline display prefixes from write content.
90
+ *
91
+ * Includes a fallback for loosely-formed section headers that still carry
92
+ * line-number prefixes (for example legacy or malformed hashline echoes).
93
+ */
94
+ function stripWriteContentWithPotentialLooseHeader(lines: string[]): { text: string; stripped: boolean } {
95
+ const cleaned = stripHashlinePrefixes(lines);
96
+ if (cleaned !== lines) {
97
+ return { text: cleaned.join("\n"), stripped: true };
98
+ }
99
+
100
+ const headerIndex = lines.findIndex(line => line.trim().length > 0);
101
+ if (headerIndex === -1 || !LOOSE_HASHLINE_HEADER_RE.test(lines[headerIndex])) {
102
+ return { text: lines.join("\n"), stripped: false };
103
+ }
104
+
105
+ const linesWithoutHeader = lines.slice(0, headerIndex).concat(lines.slice(headerIndex + 1));
106
+ const cleanedWithoutHeader = stripHashlinePrefixes(linesWithoutHeader);
107
+ if (cleanedWithoutHeader === linesWithoutHeader) {
108
+ return { text: lines.join("\n"), stripped: false };
109
+ }
110
+ return { text: cleanedWithoutHeader.join("\n"), stripped: true };
111
+ }
112
+
113
+ /**
114
+ * Strip hashline display prefixes from write content.
115
+ *
116
+ * Only active when hashline edit mode is enabled — the model sees `[PATH#HASH]`
117
+ * headers plus `LINE:` prefixes in read output and sometimes copies them into write content.
118
+ */
119
+ function stripWriteContent(session: ToolSession, content: string): { text: string; stripped: boolean } {
120
+ if (!resolveFileDisplayMode(session).hashLines) {
121
+ return { text: content, stripped: false };
122
+ }
123
+ return stripWriteContentWithPotentialLooseHeader(content.split("\n"));
124
+ }
125
+
126
+ /**
127
+ * Record a snapshot of the freshly-written `content` for `absolutePath`
128
+ * so subsequent hashline edits address the new file with a current tag,
129
+ * and return the matching `[displayPath#TAG]` header. Returns `undefined`
130
+ * when the session is not in hashline mode so callers can no-op cheaply.
131
+ *
132
+ * Mirrors the post-commit snapshot recording the hashline patcher performs
133
+ * after a successful edit: the model gets a tag without an extra `read`.
134
+ */
135
+ function maybeWriteSnapshotHeader(session: ToolSession, absolutePath: string, content: string): string | undefined {
136
+ if (!resolveFileDisplayMode(session).hashLines) return undefined;
137
+ const normalized = normalizeToLF(content);
138
+ const tag = getFileSnapshotStore(session).record(canonicalSnapshotKey(absolutePath), normalized);
139
+ return formatHashlineHeader(formatPathRelativeToCwd(absolutePath, session.cwd), tag);
140
+ }
141
+
142
+ function shouldRouteWriteThroughBridge(session: ToolSession, requestedPath: string, absolutePath: string): boolean {
143
+ if (isInternalUrlPath(requestedPath)) return false;
144
+
145
+ const state = session.getPlanModeState?.();
146
+ if (!state?.enabled || !isInternalUrlPath(state.planFilePath)) return true;
147
+
148
+ return absolutePath !== resolvePlanPath(session, state.planFilePath);
149
+ }
150
+
151
+ /**
152
+ * Append a trailing note line to the first text block of a tool result.
153
+ * Mutates `result` in place (the result object is owned by this call).
154
+ */
155
+ function appendNoteToResult(result: AgentToolResult<WriteToolDetails>, note: string): void {
156
+ const firstText = result.content.find(
157
+ (block): block is { type: "text"; text: string } => block.type === "text" && typeof block.text === "string",
158
+ );
159
+ if (firstText) {
160
+ firstText.text = firstText.text.length > 0 ? `${firstText.text}\n${note}` : note;
161
+ } else {
162
+ result.content.push({ type: "text", text: note });
163
+ }
164
+ }
165
+
166
+ /**
167
+ * If `content` begins with a `#!` shebang, ensure the file is executable.
168
+ *
169
+ * Mirrors `chmod a+x` (adds user/group/other execute bits to existing mode).
170
+ * Errors are swallowed: chmod failure (e.g. Windows ACL, read-only mount)
171
+ * MUST NOT fail an otherwise successful write. Returns whether the mode
172
+ * actually changed so the caller can surface a note.
173
+ */
174
+ async function maybeMarkExecutableForShebang(absolutePath: string, content: string): Promise<boolean> {
175
+ if (!content.startsWith("#!")) return false;
176
+ try {
177
+ const stat = await fs.stat(absolutePath);
178
+ const currentMode = stat.mode & 0o7777;
179
+ const newMode = currentMode | 0o111;
180
+ if (newMode === currentMode) return false;
181
+ await fs.chmod(absolutePath, newMode);
182
+ return true;
183
+ } catch {
184
+ return false;
185
+ }
186
+ }
187
+
188
+ // ═══════════════════════════════════════════════════════════════════════════
189
+ // Tool Class
190
+ // ═══════════════════════════════════════════════════════════════════════════
191
+
192
+ type WriteParams = WriteToolInput;
193
+
194
+ interface ResolvedArchiveWritePath {
195
+ absolutePath: string;
196
+ archivePath: string;
197
+ archiveSubPath: string;
198
+ exists: boolean;
199
+ }
200
+
201
+ interface ResolvedSqliteWritePath {
202
+ absolutePath: string;
203
+ sqlitePath: string;
204
+ table: string;
205
+ key?: string;
206
+ exists: boolean;
207
+ }
208
+
209
+ function isArchivePathNotFound(error: unknown): boolean {
210
+ if (isEnoent(error)) return true;
211
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOTDIR";
212
+ }
213
+
214
+ function normalizeArchiveWriteSubPath(rawPath: string): string {
215
+ const normalized = rawPath.replace(/\\/g, "/");
216
+ if (normalized.length === 0) {
217
+ throw new ToolError("Archive write path must target a file inside the archive");
218
+ }
219
+ if (normalized.endsWith("/")) {
220
+ throw new ToolError("Archive write path must target a file, not a directory");
221
+ }
222
+
223
+ const parts = normalized.split("/");
224
+ const normalizedParts: string[] = [];
225
+ for (const part of parts) {
226
+ if (!part || part === ".") continue;
227
+ if (part === "..") {
228
+ throw new ToolError("Archive path cannot contain '..'");
229
+ }
230
+ normalizedParts.push(part);
231
+ }
232
+
233
+ if (normalizedParts.length === 0) {
234
+ throw new ToolError("Archive write path must target a file inside the archive");
235
+ }
236
+
237
+ return normalizedParts.join("/");
238
+ }
239
+
240
+ function parseSqliteWriteTarget(subPath: string, queryString: string): { table: string; key?: string } {
241
+ if (queryString.trim().length > 0) {
242
+ throw new ToolError("SQLite write paths do not support query parameters");
243
+ }
244
+
245
+ const normalized = subPath.replace(/^:+/, "").trim();
246
+ if (!normalized) {
247
+ throw new ToolError("SQLite write path must target a table");
248
+ }
249
+
250
+ const separatorIndex = normalized.indexOf(":");
251
+ const table = separatorIndex === -1 ? normalized : normalized.slice(0, separatorIndex);
252
+ const key = separatorIndex === -1 ? undefined : normalized.slice(separatorIndex + 1);
253
+ if (!table) {
254
+ throw new ToolError("SQLite write path must target a table");
255
+ }
256
+ if (key !== undefined && key.length === 0) {
257
+ throw new ToolError("SQLite row writes require a non-empty row key");
258
+ }
259
+
260
+ return { table, key };
261
+ }
262
+
263
+ /**
264
+ * Write tool implementation.
265
+ *
266
+ * Creates or overwrites files with optional LSP formatting and diagnostics.
267
+ */
268
+ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails> {
269
+ readonly name = "write";
270
+ readonly approval = (args: unknown) => {
271
+ const rawPath = (args as Partial<WriteParams>).path;
272
+ if (typeof rawPath !== "string" || !isInternalUrlPath(rawPath)) return "write";
273
+ // Internal URLs are usually session-local artifacts (read tier), but a
274
+ // scheme whose handler exposes a `write` hook mutates handler-owned
275
+ // user data (e.g. vault:// notes, host-owned mcp:// URIs) and must take
276
+ // the write tier so always-ask mode actually prompts.
277
+ const match = /^([a-z][a-z0-9+.-]*):\/\//i.exec(rawPath.trim());
278
+ const handler = match ? InternalUrlRouter.instance().getHandler(match[1]!.toLowerCase()) : undefined;
279
+ return handler?.write ? "write" : "read";
280
+ };
281
+ readonly formatApprovalDetails = (args: unknown): string[] => {
282
+ const params = args as Partial<WriteParams>;
283
+ const targetPath = typeof params.path === "string" ? params.path : "(missing)";
284
+ const content = typeof params.content === "string" ? params.content : "";
285
+ return [`Path: ${truncateForPrompt(targetPath)}`, `Content:\n${truncateForPrompt(content)}`];
286
+ };
287
+ readonly label = "Write";
288
+ readonly description: string;
289
+ readonly parameters = writeSchema;
290
+ readonly strict = true;
291
+ readonly concurrency = "exclusive";
292
+ readonly loadMode = "discoverable";
293
+ readonly summary = "Write content to a file (creates or overwrites)";
294
+
295
+ /** Stream matchers should see the real file content, not its JSON-escaped argument encoding. */
296
+ matcherDigest(args: unknown): string | undefined {
297
+ const content = (args as Partial<WriteParams>).content;
298
+ return typeof content === "string" ? content : undefined;
299
+ }
300
+
301
+ readonly #writethrough: WritethroughCallback;
302
+
303
+ constructor(private readonly session: ToolSession) {
304
+ const enableLsp = session.enableLsp ?? true;
305
+ const enableFormat = enableLsp && session.settings.get("lsp.formatOnWrite");
306
+ const enableDiagnostics = enableLsp && session.settings.get("lsp.diagnosticsOnWrite");
307
+ const dedup = enableDiagnostics && session.settings.get("lsp.diagnosticsDeduplicate");
308
+ this.#writethrough = enableLsp
309
+ ? createLspWritethrough(session.cwd, {
310
+ enableFormat,
311
+ enableDiagnostics,
312
+ transformDiagnostics: dedup
313
+ ? (path, result) => getDiagnosticsLedger(session).reduce(path, result)
314
+ : undefined,
315
+ })
316
+ : writethroughNoop;
317
+ this.description = prompt.render(writeDescription);
318
+ }
319
+
320
+ async #resolveArchiveWritePath(writePath: string): Promise<ResolvedArchiveWritePath | null> {
321
+ const candidates = parseArchivePathCandidates(writePath).filter(candidate => candidate.archivePath !== writePath);
322
+ if (candidates.length === 0) {
323
+ return null;
324
+ }
325
+
326
+ const fallbackCandidate = candidates[candidates.length - 1]!;
327
+ const fallback: ResolvedArchiveWritePath = {
328
+ absolutePath: resolvePlanPath(this.session, fallbackCandidate.archivePath),
329
+ archivePath: fallbackCandidate.archivePath,
330
+ archiveSubPath: normalizeArchiveWriteSubPath(fallbackCandidate.subPath),
331
+ exists: false,
332
+ };
333
+
334
+ for (const candidate of candidates) {
335
+ const absolutePath = resolvePlanPath(this.session, candidate.archivePath);
336
+ try {
337
+ const stat = await Bun.file(absolutePath).stat();
338
+ if (stat.isDirectory()) {
339
+ continue;
340
+ }
341
+
342
+ return {
343
+ absolutePath,
344
+ archivePath: candidate.archivePath,
345
+ archiveSubPath: normalizeArchiveWriteSubPath(candidate.subPath),
346
+ exists: true,
347
+ };
348
+ } catch (error) {
349
+ if (!isArchivePathNotFound(error)) {
350
+ throw error;
351
+ }
352
+ }
353
+ }
354
+
355
+ return fallback;
356
+ }
357
+
358
+ async #writeArchiveEntry(
359
+ content: string,
360
+ resolvedArchivePath: ResolvedArchiveWritePath,
361
+ ): Promise<AgentToolResult<WriteToolDetails>> {
362
+ // Resolve symlinks before the tmp+rename swap: renaming over a symlink
363
+ // replaces the link itself with a regular file instead of writing
364
+ // through to its target.
365
+ const finalPath = resolvedArchivePath.exists
366
+ ? await fs.realpath(resolvedArchivePath.absolutePath).catch(() => resolvedArchivePath.absolutePath)
367
+ : resolvedArchivePath.absolutePath;
368
+ const lowerPath = finalPath.toLowerCase();
369
+ const isZip = lowerPath.endsWith(".zip");
370
+ const isGzip = lowerPath.endsWith(".tar.gz") || lowerPath.endsWith(".tgz");
371
+ // Rewrites are whole-archive: write to a temp file and rename so a
372
+ // crash/disk-full mid-write can't destroy the original archive.
373
+ const tmpPath = `${finalPath}.tmp-${process.pid}`;
374
+
375
+ const parentDir = path.dirname(resolvedArchivePath.absolutePath);
376
+ if (parentDir && parentDir !== ".") {
377
+ await fs.mkdir(parentDir, { recursive: true });
378
+ }
379
+
380
+ if (isZip) {
381
+ const zipEntries: Record<string, Uint8Array> = {};
382
+
383
+ if (resolvedArchivePath.exists) {
384
+ try {
385
+ const bytes = await Bun.file(resolvedArchivePath.absolutePath).bytes();
386
+ const { unzipSync } = await loadFflate();
387
+ const existing = unzipSync(new Uint8Array(bytes));
388
+ for (const [entryPath, data] of Object.entries(existing)) {
389
+ zipEntries[entryPath.replace(/\\/g, "/")] = data;
390
+ }
391
+ } catch (error) {
392
+ throw new ToolError(error instanceof Error ? error.message : String(error));
393
+ }
394
+ }
395
+
396
+ zipEntries[resolvedArchivePath.archiveSubPath] = new TextEncoder().encode(content);
397
+
398
+ try {
399
+ const { zipSync } = await loadFflate();
400
+ const zipBuffer = zipSync(zipEntries);
401
+ await Bun.write(tmpPath, zipBuffer);
402
+ await fs.rename(tmpPath, finalPath);
403
+ } catch (error) {
404
+ await fs.rm(tmpPath, { force: true }).catch(() => {});
405
+ throw new ToolError(error instanceof Error ? error.message : String(error));
406
+ }
407
+ } else {
408
+ const archiveEntries: Record<string, string | File> = {};
409
+ if (resolvedArchivePath.exists) {
410
+ let archive: Bun.Archive;
411
+ try {
412
+ archive = new Bun.Archive(await Bun.file(resolvedArchivePath.absolutePath).bytes());
413
+ } catch (error) {
414
+ throw new ToolError(error instanceof Error ? error.message : String(error));
415
+ }
416
+
417
+ let files: Map<string, File>;
418
+ try {
419
+ files = await archive.files();
420
+ } catch (error) {
421
+ throw new ToolError(error instanceof Error ? error.message : String(error));
422
+ }
423
+
424
+ for (const [entryPath, file] of files) {
425
+ archiveEntries[entryPath.replace(/\\/g, "/")] = file;
426
+ }
427
+ }
428
+
429
+ archiveEntries[resolvedArchivePath.archiveSubPath] = content;
430
+
431
+ try {
432
+ // `Bun.Archive.write` never infers compression from the extension;
433
+ // request gzip explicitly so `.tar.gz`/`.tgz` stay compressed.
434
+ await Bun.Archive.write(tmpPath, archiveEntries, isGzip ? { compress: "gzip" } : undefined);
435
+ await fs.rename(tmpPath, finalPath);
436
+ } catch (error) {
437
+ await fs.rm(tmpPath, { force: true }).catch(() => {});
438
+ throw new ToolError(error instanceof Error ? error.message : String(error));
439
+ }
440
+ }
441
+
442
+ invalidateFsScanAfterWrite(resolvedArchivePath.absolutePath);
443
+ const outputPath = `${formatPathRelativeToCwd(resolvedArchivePath.absolutePath, this.session.cwd)}:${
444
+ resolvedArchivePath.archiveSubPath
445
+ }`;
446
+ return {
447
+ content: [{ type: "text", text: `Successfully wrote ${content.length} bytes to ${outputPath}` }],
448
+ details: { resolvedPath: resolvedArchivePath.absolutePath },
449
+ };
450
+ }
451
+
452
+ async #resolveSqliteWritePath(writePath: string): Promise<ResolvedSqliteWritePath | null> {
453
+ const candidates = parseSqlitePathCandidates(writePath).filter(candidate => candidate.sqlitePath !== writePath);
454
+ if (candidates.length === 0) {
455
+ return null;
456
+ }
457
+
458
+ const fallbackCandidate = candidates[candidates.length - 1]!;
459
+ const fallbackTarget = parseSqliteWriteTarget(fallbackCandidate.subPath, fallbackCandidate.queryString);
460
+ const fallback: ResolvedSqliteWritePath = {
461
+ absolutePath: resolvePlanPath(this.session, fallbackCandidate.sqlitePath),
462
+ sqlitePath: fallbackCandidate.sqlitePath,
463
+ table: fallbackTarget.table,
464
+ key: fallbackTarget.key,
465
+ exists: false,
466
+ };
467
+
468
+ let sawExistingNonSqlite = false;
469
+ for (const candidate of candidates) {
470
+ const target = parseSqliteWriteTarget(candidate.subPath, candidate.queryString);
471
+ const absolutePath = resolvePlanPath(this.session, candidate.sqlitePath);
472
+ try {
473
+ const stat = await Bun.file(absolutePath).stat();
474
+ if (stat.isDirectory()) {
475
+ continue;
476
+ }
477
+ if (!(await isSqliteFile(absolutePath))) {
478
+ sawExistingNonSqlite = true;
479
+ continue;
480
+ }
481
+
482
+ return {
483
+ absolutePath,
484
+ sqlitePath: candidate.sqlitePath,
485
+ table: target.table,
486
+ key: target.key,
487
+ exists: true,
488
+ };
489
+ } catch (error) {
490
+ if (!isArchivePathNotFound(error)) {
491
+ throw error;
492
+ }
493
+ }
494
+ }
495
+
496
+ if (sawExistingNonSqlite) {
497
+ return null;
498
+ }
499
+
500
+ return fallback;
501
+ }
502
+
503
+ async #writeSqliteRow(
504
+ displayPath: string,
505
+ content: string,
506
+ resolvedSqlitePath: ResolvedSqliteWritePath,
507
+ ): Promise<AgentToolResult<WriteToolDetails>> {
508
+ let db: Database | null = null;
509
+ try {
510
+ if (!resolvedSqlitePath.exists) {
511
+ throw new ToolError(`SQLite database '${displayPath}' not found`);
512
+ }
513
+
514
+ db = new Database(resolvedSqlitePath.absolutePath, { create: false, strict: true });
515
+ db.run("PRAGMA busy_timeout = 3000");
516
+
517
+ const trimmedContent = content.trim();
518
+ let resultText: string;
519
+ if (trimmedContent.length === 0) {
520
+ if (!resolvedSqlitePath.key) {
521
+ throw new ToolError("SQLite deletes require a row key in the path");
522
+ }
523
+
524
+ const lookup = resolveTableRowLookup(db, resolvedSqlitePath.table);
525
+ const deleted =
526
+ lookup.kind === "pk"
527
+ ? deleteRowByKey(db, resolvedSqlitePath.table, lookup, resolvedSqlitePath.key)
528
+ : deleteRowByRowId(db, resolvedSqlitePath.table, resolvedSqlitePath.key);
529
+ resultText =
530
+ deleted > 0
531
+ ? `Deleted row '${resolvedSqlitePath.key}' from ${resolvedSqlitePath.table}`
532
+ : `No row deleted from ${resolvedSqlitePath.table} for key '${resolvedSqlitePath.key}'`;
533
+ } else {
534
+ let parsedContent: unknown;
535
+ try {
536
+ parsedContent = Bun.JSON5.parse(content);
537
+ } catch (error) {
538
+ throw new ToolError(
539
+ `SQLite write content must be valid JSON5: ${error instanceof Error ? error.message : String(error)}`,
540
+ );
541
+ }
542
+
543
+ if (!isRecord(parsedContent)) {
544
+ throw new ToolError("SQLite write content must be a JSON object");
545
+ }
546
+
547
+ if (resolvedSqlitePath.key) {
548
+ const lookup = resolveTableRowLookup(db, resolvedSqlitePath.table);
549
+ const updated =
550
+ lookup.kind === "pk"
551
+ ? updateRowByKey(db, resolvedSqlitePath.table, lookup, resolvedSqlitePath.key, parsedContent)
552
+ : updateRowByRowId(db, resolvedSqlitePath.table, resolvedSqlitePath.key, parsedContent);
553
+ resultText =
554
+ updated > 0
555
+ ? `Updated row '${resolvedSqlitePath.key}' in ${resolvedSqlitePath.table}`
556
+ : `No row updated in ${resolvedSqlitePath.table} for key '${resolvedSqlitePath.key}'`;
557
+ } else {
558
+ insertRow(db, resolvedSqlitePath.table, parsedContent);
559
+ resultText = `Inserted row into ${resolvedSqlitePath.table}`;
560
+ }
561
+ }
562
+
563
+ invalidateFsScanAfterWrite(resolvedSqlitePath.absolutePath);
564
+ return toolResult<WriteToolDetails>({ resolvedPath: resolvedSqlitePath.absolutePath })
565
+ .text(resultText)
566
+ .sourcePath(resolvedSqlitePath.absolutePath)
567
+ .done();
568
+ } catch (error) {
569
+ if (isEnoent(error)) {
570
+ throw new ToolError(`SQLite database '${displayPath}' not found`);
571
+ }
572
+ if (error instanceof ToolError) {
573
+ throw error;
574
+ }
575
+ throw new ToolError(error instanceof Error ? error.message : String(error));
576
+ } finally {
577
+ db?.close();
578
+ }
579
+ }
580
+
581
+ /**
582
+ * Resolve a single `conflict://<N>` write by splicing the recorded
583
+ * marker region in the registered file with `replacementContent`,
584
+ * then routing the new file content through the normal writethrough
585
+ * pipeline so LSP format/diagnostics still run.
586
+ *
587
+ * Entry ids are session-stable: they keep working even after later
588
+ * writes resolve other blocks in the same file. The recorded range
589
+ * is re-validated on disk before splicing so an out-of-band edit
590
+ * surfaces as a clear error instead of corrupting the file.
591
+ */
592
+ async #resolveConflict(
593
+ entry: ConflictEntry,
594
+ replacementContent: string,
595
+ stripped: boolean,
596
+ signal: AbortSignal | undefined,
597
+ context: AgentToolContext | undefined,
598
+ ): Promise<AgentToolResult<WriteToolDetails>> {
599
+ const absolutePath = entry.absolutePath;
600
+ if (!(await fs.exists(absolutePath))) {
601
+ throw new ToolError(`Conflict #${entry.id} target '${entry.displayPath}' no longer exists.`);
602
+ }
603
+
604
+ const expanded = expandContentTokens(replacementContent, entry);
605
+ const originalText = await Bun.file(absolutePath).text();
606
+ const newContent = spliceConflict(originalText, entry, expanded);
607
+
608
+ const batchRequest = getLspBatchRequest(context?.toolCall);
609
+ const diagnostics = await this.#writethrough(absolutePath, newContent, signal, undefined, batchRequest);
610
+ invalidateFsScanAfterWrite(absolutePath);
611
+ this.session.bumpFileMutationVersion?.(absolutePath);
612
+ this.session.fileSnapshotStore?.invalidate(absolutePath);
613
+ const history = this.session.conflictHistory;
614
+ history?.invalidate(entry.id);
615
+ if (history) {
616
+ // Drop stale duplicate registrations of the same region: a re-read
617
+ // after an out-of-band shift registers a fresh id at the new
618
+ // startLine while the stale twin persists at the old one. A DISTINCT
619
+ // conflict block that is merely byte-identical still occurs in the
620
+ // post-splice content and must stay addressable.
621
+ for (const other of history.entries()) {
622
+ if (
623
+ other.absolutePath === absolutePath &&
624
+ conflictRegionsEqual(other, entry) &&
625
+ !conflictRegionPresent(newContent, other)
626
+ ) {
627
+ history.invalidate(other.id);
628
+ }
629
+ }
630
+ }
631
+
632
+ const header = maybeWriteSnapshotHeader(this.session, absolutePath, newContent);
633
+ const range =
634
+ entry.startLine === entry.endLine
635
+ ? `line ${entry.startLine}`
636
+ : `lines ${entry.startLine}\u2013${entry.endLine}`;
637
+ const summary = `Resolved conflict #${entry.id} at ${range} in ${entry.displayPath}.`;
638
+ let resultText = header ? `${header}\n${summary}` : summary;
639
+ if (stripped) {
640
+ resultText += `\nNote: auto-stripped hashline display prefixes from content before writing.`;
641
+ }
642
+
643
+ if (!diagnostics) {
644
+ return {
645
+ content: [{ type: "text", text: resultText }],
646
+ details: { resolvedPath: absolutePath },
647
+ };
648
+ }
649
+ return {
650
+ content: [{ type: "text", text: resultText }],
651
+ details: {
652
+ resolvedPath: absolutePath,
653
+ diagnostics,
654
+ meta: outputMeta()
655
+ .diagnostics(diagnostics.summary, diagnostics.messages ?? [])
656
+ .get(),
657
+ },
658
+ };
659
+ }
660
+
661
+ /**
662
+ * Look up a single conflict entry by id and dispatch to {@link #resolveConflict}.
663
+ * Throws a clear `not found` error when the id has been invalidated.
664
+ */
665
+ async #resolveSingleConflictById(
666
+ id: number,
667
+ replacementContent: string,
668
+ stripped: boolean,
669
+ signal: AbortSignal | undefined,
670
+ context: AgentToolContext | undefined,
671
+ ): Promise<AgentToolResult<WriteToolDetails>> {
672
+ const entry = getConflictHistory(this.session).get(id);
673
+ if (!entry) {
674
+ throw new ToolError(
675
+ `Conflict #${id} not found. Conflict ids are registered when \`read\` surfaces a marker block; re-read the file to get a current id.`,
676
+ );
677
+ }
678
+ return this.#resolveConflict(entry, replacementContent, stripped, signal, context);
679
+ }
680
+
681
+ /**
682
+ * Bulk-resolve every registered conflict via `conflict://*`.
683
+ *
684
+ * Entries are grouped by file and applied bottom-up by recorded start
685
+ * line so each splice keeps later anchors valid. `content` tokens are
686
+ * expanded *per entry*, so `content: "@ours"` keeps each block's own
687
+ * ours side rather than collapsing every conflict to the first
688
+ * block's ours.
689
+ *
690
+ * All-or-nothing semantics within a file: if any splice for a file
691
+ * fails (stale anchors, missing base for `@base`, etc.), that file is
692
+ * left untouched and the error is surfaced. Files that succeed are
693
+ * still written. The result text reports per-file counts so the agent
694
+ * can re-read the failed files and retry.
695
+ */
696
+ async #resolveAllConflicts(
697
+ replacementContent: string,
698
+ stripped: boolean,
699
+ signal: AbortSignal | undefined,
700
+ context: AgentToolContext | undefined,
701
+ ): Promise<AgentToolResult<WriteToolDetails>> {
702
+ const history = getConflictHistory(this.session);
703
+ const allEntries = history.entries();
704
+ if (allEntries.length === 0) {
705
+ throw new ToolError(
706
+ "`conflict://*` has nothing to resolve — no conflicts are currently registered. Re-read the file(s) with conflicts first.",
707
+ );
708
+ }
709
+
710
+ const byFile = new Map<string, ConflictEntry[]>();
711
+ for (const entry of allEntries) {
712
+ const bucket = byFile.get(entry.absolutePath) ?? [];
713
+ bucket.push(entry);
714
+ byFile.set(entry.absolutePath, bucket);
715
+ }
716
+
717
+ const batchRequest = getLspBatchRequest(context?.toolCall);
718
+ const allDiagnostics: FileDiagnosticsResult[] = [];
719
+ const succeededFiles: { displayPath: string; count: number; header?: string }[] = [];
720
+ const failedFiles: { displayPath: string; count: number; error: string }[] = [];
721
+ let totalResolvedIds = 0;
722
+
723
+ for (const [absolutePath, fileEntries] of byFile) {
724
+ const sample = fileEntries[0]!;
725
+ if (!(await fs.exists(absolutePath))) {
726
+ failedFiles.push({
727
+ displayPath: sample.displayPath,
728
+ count: fileEntries.length,
729
+ error: "file no longer exists",
730
+ });
731
+ continue;
732
+ }
733
+
734
+ fileEntries.sort((a, b) => b.startLine - a.startLine);
735
+
736
+ let text: string;
737
+ const resolvedEntries: ConflictEntry[] = [];
738
+ const staleEntries: ConflictEntry[] = [];
739
+ let failure: string | undefined;
740
+ try {
741
+ text = await Bun.file(absolutePath).text();
742
+ } catch (error) {
743
+ failedFiles.push({
744
+ displayPath: sample.displayPath,
745
+ count: fileEntries.length,
746
+ error: error instanceof Error ? error.message : String(error),
747
+ });
748
+ continue;
749
+ }
750
+ for (const entry of fileEntries) {
751
+ try {
752
+ const expanded = expandContentTokens(replacementContent, entry);
753
+ text = spliceConflict(text, entry, expanded);
754
+ resolvedEntries.push(entry);
755
+ } catch (error) {
756
+ // A locate-miss for a region an earlier entry already spliced
757
+ // in this pass is a stale duplicate registration (re-read after
758
+ // an out-of-band shift) — treat it as already resolved.
759
+ if (resolvedEntries.some(done => conflictRegionsEqual(done, entry))) {
760
+ staleEntries.push(entry);
761
+ continue;
762
+ }
763
+ failure = error instanceof Error ? error.message : String(error);
764
+ break;
765
+ }
766
+ }
767
+ if (failure !== undefined) {
768
+ failedFiles.push({
769
+ displayPath: sample.displayPath,
770
+ count: fileEntries.length,
771
+ error: failure,
772
+ });
773
+ continue;
774
+ }
775
+
776
+ const diagnostics = await this.#writethrough(absolutePath, text, signal, undefined, batchRequest);
777
+ invalidateFsScanAfterWrite(absolutePath);
778
+ this.session.bumpFileMutationVersion?.(absolutePath);
779
+ this.session.fileSnapshotStore?.invalidate(absolutePath);
780
+ for (const entry of resolvedEntries) history.invalidate(entry.id);
781
+ for (const entry of staleEntries) history.invalidate(entry.id);
782
+ const header = maybeWriteSnapshotHeader(this.session, absolutePath, text);
783
+ succeededFiles.push({ displayPath: sample.displayPath, count: resolvedEntries.length, header });
784
+ totalResolvedIds += resolvedEntries.length;
785
+ if (diagnostics) allDiagnostics.push(diagnostics);
786
+ }
787
+
788
+ const summaryLines: string[] = [];
789
+ const fileWord = (n: number) => (n === 1 ? "file" : "files");
790
+ const conflictWord = (n: number) => (n === 1 ? "conflict" : "conflicts");
791
+ if (succeededFiles.length > 0) {
792
+ summaryLines.push(
793
+ `Resolved ${totalResolvedIds} ${conflictWord(totalResolvedIds)} across ${succeededFiles.length} ${fileWord(succeededFiles.length)}:`,
794
+ );
795
+ for (const file of succeededFiles) {
796
+ summaryLines.push(` ${file.displayPath}: ${file.count} ${conflictWord(file.count)}`);
797
+ }
798
+ }
799
+ if (failedFiles.length > 0) {
800
+ summaryLines.push(
801
+ `Failed to resolve ${failedFiles.length} ${fileWord(failedFiles.length)} — registered entries left intact for retry:`,
802
+ );
803
+ for (const file of failedFiles) {
804
+ summaryLines.push(` ${file.displayPath}: ${file.count} ${conflictWord(file.count)} (${file.error})`);
805
+ }
806
+ }
807
+ const headerLines = succeededFiles
808
+ .map(file => file.header)
809
+ .filter((header): header is string => header !== undefined);
810
+ if (headerLines.length > 0) {
811
+ summaryLines.push("Snapshots:");
812
+ for (const header of headerLines) summaryLines.push(` ${header}`);
813
+ }
814
+ if (stripped) {
815
+ summaryLines.push("Note: auto-stripped hashline display prefixes from content before writing.");
816
+ }
817
+ const resultText = summaryLines.join("\n");
818
+
819
+ if (allDiagnostics.length === 0) {
820
+ if (failedFiles.length > 0 && succeededFiles.length === 0) {
821
+ throw new ToolError(resultText);
822
+ }
823
+ return {
824
+ content: [{ type: "text", text: resultText }],
825
+ details: {},
826
+ isError: failedFiles.length > 0 ? true : undefined,
827
+ };
828
+ }
829
+ const mergedSummary = allDiagnostics.map(d => d.summary).join("\n");
830
+ const mergedMessages = allDiagnostics.flatMap(d => d.messages ?? []);
831
+ return {
832
+ content: [{ type: "text", text: resultText }],
833
+ details: {
834
+ meta: outputMeta().diagnostics(mergedSummary, mergedMessages).get(),
835
+ },
836
+ isError: failedFiles.length > 0 ? true : undefined,
837
+ };
838
+ }
839
+
840
+ #routeWriteThroughBridge(absolutePath: string, content: string): Promise<void> | undefined {
841
+ const bridge = this.session.getClientBridge?.();
842
+ if (!bridge?.capabilities.writeTextFile || !bridge.writeTextFile) return undefined;
843
+ return bridge.writeTextFile({ path: absolutePath, content });
844
+ }
845
+ async execute(
846
+ _toolCallId: string,
847
+ { path, content }: WriteParams,
848
+ signal?: AbortSignal,
849
+ _onUpdate?: AgentToolUpdateCallback<WriteToolDetails>,
850
+ context?: AgentToolContext,
851
+ ): Promise<AgentToolResult<WriteToolDetails>> {
852
+ return untilAborted(signal, async () => {
853
+ // Strip hashline display prefixes ([PATH#HASH] + LINE:) if the model copied them from read output
854
+ const { text: cleanContent, stripped } = stripWriteContent(this.session, content);
855
+ const internalRouter = InternalUrlRouter.instance();
856
+ if (internalRouter.canHandle(path)) {
857
+ const parsed = parseInternalUrl(path);
858
+ const scheme = parsed.protocol.replace(/:$/, "").toLowerCase();
859
+ const handler = internalRouter.getHandler(scheme);
860
+ if (handler?.write) {
861
+ // Handler-owned writes (vault:// notes, host URIs) mutate user
862
+ // data outside the local sandbox — plan mode must reject them.
863
+ enforcePlanModeWrite(this.session, path, { op: "update" });
864
+ await handler.write(parsed, cleanContent, { cwd: this.session.cwd, signal });
865
+ let resultText = `Successfully wrote ${cleanContent.length} bytes to ${path}`;
866
+ if (stripped) {
867
+ resultText += `\nNote: auto-stripped hashline display prefixes from content before writing.`;
868
+ }
869
+ return { content: [{ type: "text", text: resultText }], details: {} };
870
+ }
871
+ // Schemes without a `write` hook fall through to existing logic
872
+ // (local:// resolves to a backing file via plan-mode-guard) or are
873
+ // rejected downstream when no backing file exists.
874
+ }
875
+
876
+ const conflictUri = parseConflictUri(path);
877
+ if (conflictUri) {
878
+ if (conflictUri.scope) {
879
+ throw new ToolError(
880
+ `Conflict URI scope '/${conflictUri.scope}' is read-only — read \`conflict://${conflictUri.id}/${conflictUri.scope}\` to inspect that side. To write, drop the scope (\`conflict://${conflictUri.id}\`) and put the chosen content (or shorthand like \`@${conflictUri.scope}\`) in \`content\`.`,
881
+ );
882
+ }
883
+ const result =
884
+ conflictUri.id === "*"
885
+ ? await this.#resolveAllConflicts(cleanContent, stripped, signal, context)
886
+ : await this.#resolveSingleConflictById(conflictUri.id, cleanContent, stripped, signal, context);
887
+ if (conflictUri.recoveredPrefix !== undefined) {
888
+ appendNoteToResult(
889
+ result,
890
+ `Note: stripped erroneous '${conflictUri.recoveredPrefix}:' prefix from path; conflict URIs are global (use \`conflict://${conflictUri.id}\`, not \`<file>:conflict://${conflictUri.id}\`).`,
891
+ );
892
+ }
893
+ return result;
894
+ }
895
+ const resolvedArchivePath = await this.#resolveArchiveWritePath(path);
896
+ if (resolvedArchivePath) {
897
+ enforcePlanModeWrite(this.session, resolvedArchivePath.archivePath, {
898
+ op: resolvedArchivePath.exists ? "update" : "create",
899
+ });
900
+
901
+ const archiveResult = await this.#writeArchiveEntry(cleanContent, resolvedArchivePath);
902
+ if (stripped) {
903
+ const firstText = archiveResult.content.find(
904
+ (block): block is { type: "text"; text: string } =>
905
+ block.type === "text" && typeof block.text === "string",
906
+ );
907
+ if (firstText) {
908
+ firstText.text += `\nNote: auto-stripped hashline display prefixes from content before writing.`;
909
+ }
910
+ }
911
+ return archiveResult;
912
+ }
913
+
914
+ const resolvedSqlitePath = await this.#resolveSqliteWritePath(path);
915
+ if (resolvedSqlitePath) {
916
+ enforcePlanModeWrite(this.session, resolvedSqlitePath.sqlitePath, { op: "update" });
917
+
918
+ const sqliteResult = await this.#writeSqliteRow(path, cleanContent, resolvedSqlitePath);
919
+ if (stripped) {
920
+ const firstText = sqliteResult.content.find(
921
+ (block): block is { type: "text"; text: string } =>
922
+ block.type === "text" && typeof block.text === "string",
923
+ );
924
+ if (firstText) {
925
+ firstText.text += `\nNote: auto-stripped hashline display prefixes from content before writing.`;
926
+ }
927
+ }
928
+ return sqliteResult;
929
+ }
930
+
931
+ enforcePlanModeWrite(this.session, path, { op: "create" });
932
+ const absolutePath = resolvePlanPath(this.session, path);
933
+ const batchRequest = getLspBatchRequest(context?.toolCall);
934
+
935
+ // Check if file exists and is auto-generated before overwriting
936
+ if (await fs.exists(absolutePath)) {
937
+ await assertEditableFile(absolutePath, path);
938
+ }
939
+
940
+ // Try ACP bridge first for editor-visible filesystem paths. Internal
941
+ // artifacts such as local:// plans are owned by OMP, not the editor.
942
+ const bridgePromise = shouldRouteWriteThroughBridge(this.session, path, absolutePath)
943
+ ? this.#routeWriteThroughBridge(absolutePath, cleanContent)
944
+ : undefined;
945
+ if (bridgePromise !== undefined) {
946
+ try {
947
+ await bridgePromise;
948
+ } catch (error) {
949
+ throw new ToolError(error instanceof Error ? error.message : String(error));
950
+ }
951
+ invalidateFsScanAfterWrite(absolutePath);
952
+ this.session.bumpFileMutationVersion?.(absolutePath);
953
+ const madeExecutable = await maybeMarkExecutableForShebang(absolutePath, cleanContent);
954
+ const displayPath = formatPathRelativeToCwd(absolutePath, this.session.cwd);
955
+ const header = maybeWriteSnapshotHeader(this.session, absolutePath, cleanContent);
956
+ const writeLine = `Successfully wrote ${cleanContent.length} bytes to ${displayPath}`;
957
+ let resultText = header ? `${header}\n${writeLine}` : writeLine;
958
+ if (stripped) {
959
+ resultText += `\nNote: auto-stripped hashline display prefixes from content before writing.`;
960
+ }
961
+ return {
962
+ content: [{ type: "text", text: resultText }],
963
+ details: { resolvedPath: absolutePath, madeExecutable: madeExecutable || undefined },
964
+ };
965
+ }
966
+
967
+ const diagnostics = await this.#writethrough(absolutePath, cleanContent, signal, undefined, batchRequest);
968
+ invalidateFsScanAfterWrite(absolutePath);
969
+ this.session.bumpFileMutationVersion?.(absolutePath);
970
+ const madeExecutable = await maybeMarkExecutableForShebang(absolutePath, cleanContent);
971
+
972
+ const displayPath = formatPathRelativeToCwd(absolutePath, this.session.cwd);
973
+ const header = maybeWriteSnapshotHeader(this.session, absolutePath, cleanContent);
974
+ const writeLine = `Successfully wrote ${cleanContent.length} bytes to ${displayPath}`;
975
+ let resultText = header ? `${header}\n${writeLine}` : writeLine;
976
+ if (stripped) {
977
+ resultText += `\nNote: auto-stripped hashline display prefixes from content before writing.`;
978
+ }
979
+ if (!diagnostics) {
980
+ return {
981
+ content: [{ type: "text", text: resultText }],
982
+ details: { resolvedPath: absolutePath, madeExecutable: madeExecutable || undefined },
983
+ };
984
+ }
985
+
986
+ return {
987
+ content: [{ type: "text", text: resultText }],
988
+ details: {
989
+ resolvedPath: absolutePath,
990
+ diagnostics,
991
+ madeExecutable: madeExecutable || undefined,
992
+ meta: outputMeta()
993
+ .diagnostics(diagnostics.summary, diagnostics.messages ?? [])
994
+ .get(),
995
+ },
996
+ };
997
+ });
998
+ }
999
+ }
1000
+
1001
+ // =============================================================================
1002
+ // TUI Renderer
1003
+ // =============================================================================
1004
+
1005
+ interface WriteRenderArgs {
1006
+ path?: string;
1007
+ file_path?: string;
1008
+ content?: string;
1009
+ }
1010
+
1011
+ const WRITE_PREVIEW_LINES = 6;
1012
+ const WRITE_STREAMING_PREVIEW_LINES = 12;
1013
+
1014
+ function countLines(text: string): number {
1015
+ if (!text) return 0;
1016
+ return text.split("\n").length;
1017
+ }
1018
+
1019
+ function formatLineCountSuffix(lineCount: number, uiTheme: Theme): string {
1020
+ if (lineCount <= 0) return "";
1021
+ return uiTheme.fg("dim", ` · ${lineCount} line${lineCount === 1 ? "" : "s"}`);
1022
+ }
1023
+
1024
+ function normalizeDisplayText(text: string): string {
1025
+ return text.replace(/\r/g, "");
1026
+ }
1027
+
1028
+ /**
1029
+ * Minimum line-number gutter width for write previews. The streaming preview's
1030
+ * gutter must stay byte-stable as the line count grows: a width derived purely
1031
+ * from `String(totalLines).length` widens at the 10/100/1000-line crossings,
1032
+ * rewriting every already-rendered row — which forces the transcript's commit
1033
+ * audit to recommit the block's committed prefix (a full duplicate in native
1034
+ * scrollback). Reserving 3 digits keeps the gutter constant through 999 lines
1035
+ * and keeps the streamed rows byte-identical to the final result render.
1036
+ */
1037
+ const WRITE_GUTTER_MIN_WIDTH = 3;
1038
+
1039
+ function formatStreamingContent(
1040
+ content: string,
1041
+ expanded: boolean,
1042
+ language: string | undefined,
1043
+ uiTheme: Theme,
1044
+ spinnerFrame?: number,
1045
+ ): string {
1046
+ if (!content) return "";
1047
+ const lines = normalizeDisplayText(content).split("\n");
1048
+ const totalLines = lines.length;
1049
+ // Collapsed: follow the streaming edge with a bounded tail window so the box
1050
+ // stays short enough not to strand its scrolled-off head above the viewport
1051
+ // while the block is volatile. `Ctrl+O` (expanded) lifts the cap for a
1052
+ // deliberate full view — matching the eval streaming preview.
1053
+ const startIndex = expanded ? 0 : Math.max(0, totalLines - WRITE_STREAMING_PREVIEW_LINES);
1054
+ const visibleLines = lines.slice(startIndex);
1055
+ const hidden = startIndex;
1056
+ const highlighted = highlightCode(visibleLines.join("\n"), language);
1057
+ const lineNumberWidth = Math.max(WRITE_GUTTER_MIN_WIDTH, String(totalLines).length);
1058
+
1059
+ let text = "\n\n";
1060
+ if (hidden > 0) {
1061
+ text += `${uiTheme.fg("dim", `… (${hidden} earlier line${hidden === 1 ? "" : "s"})`)}\n`;
1062
+ }
1063
+ for (let i = 0; i < highlighted.length; i++) {
1064
+ const lineNum = startIndex + i + 1;
1065
+ const gutter = uiTheme.fg("dim", `${String(lineNum).padStart(lineNumberWidth, " ")} `);
1066
+ const body = replaceTabs(highlighted[i] ?? "");
1067
+ text += `${gutter}${body}\n`;
1068
+ }
1069
+ // The animated glyph lives on this trailing line — inside the transcript's
1070
+ // volatile-tail holdback — never in the header: an animating head row pins
1071
+ // the native-scrollback commit boundary at the top of the block, so a long
1072
+ // expanded preview could never scroll-append mid-stream.
1073
+ const spinner = spinnerFrame !== undefined ? `${formatStatusIcon("running", uiTheme, spinnerFrame)} ` : "";
1074
+ text += `${spinner}${uiTheme.fg("dim", `… (streaming)`)}`;
1075
+ return text;
1076
+ }
1077
+
1078
+ function renderContentPreview(
1079
+ content: string,
1080
+ expanded: boolean,
1081
+ language: string | undefined,
1082
+ uiTheme: Theme,
1083
+ ): string {
1084
+ if (!content) return "";
1085
+ const rawLines = normalizeDisplayText(content).split("\n");
1086
+ const totalLines = rawLines.length;
1087
+ const maxLines = expanded ? totalLines : Math.min(totalLines, WRITE_PREVIEW_LINES);
1088
+ const visibleLines = rawLines.slice(0, maxLines);
1089
+ const highlighted = highlightCode(visibleLines.join("\n"), language);
1090
+ const lineNumberWidth = Math.max(WRITE_GUTTER_MIN_WIDTH, String(totalLines).length);
1091
+ const hidden = totalLines - maxLines;
1092
+
1093
+ let text = "\n\n";
1094
+ for (let i = 0; i < highlighted.length; i++) {
1095
+ const lineNum = i + 1;
1096
+ const gutter = uiTheme.fg("dim", `${String(lineNum).padStart(lineNumberWidth, " ")} `);
1097
+ const body = replaceTabs(highlighted[i] ?? "");
1098
+ text += `${gutter}${body}\n`;
1099
+ }
1100
+ if (!expanded && hidden > 0) {
1101
+ const hint = formatExpandHint(uiTheme, expanded, hidden > 0);
1102
+ const moreLine = `${formatMoreItems(hidden, "line")}${hint ? ` ${hint}` : ""}`;
1103
+ text += uiTheme.fg("dim", moreLine);
1104
+ }
1105
+ return text.trimEnd();
1106
+ }
1107
+
1108
+ export const writeToolRenderer = {
1109
+ renderCall(args: WriteRenderArgs, options: RenderResultOptions, uiTheme: Theme): Component {
1110
+ const rawPath = args.file_path || args.path || "";
1111
+ const filePath = shortenPath(rawPath);
1112
+ const lang = getLanguageFromPath(rawPath) ?? "text";
1113
+ const langIcon = uiTheme.fg("muted", uiTheme.getLangIcon(lang));
1114
+ const pathDisplay = filePath ? uiTheme.fg("accent", filePath) : uiTheme.fg("toolOutput", "…");
1115
+ // Static pending icon, never the animated glyph: the header is the head
1116
+ // row of the framed block, and native-scrollback commits are prefix-only
1117
+ // — an animating head row would pin the commit boundary at the top and
1118
+ // keep a tall expanded preview from scroll-appending mid-stream. The
1119
+ // liveness cue rides the trailing "(streaming)" line instead.
1120
+ const header = renderStatusLine(
1121
+ {
1122
+ icon: "pending",
1123
+ title: "Write",
1124
+ description: `${langIcon} ${pathDisplay}`,
1125
+ },
1126
+ uiTheme,
1127
+ );
1128
+ return framedBlock(uiTheme, width => {
1129
+ const body = args.content
1130
+ ? formatStreamingContent(args.content, Boolean(options?.expanded), lang, uiTheme, options?.spinnerFrame)
1131
+ : "";
1132
+ const bodyLines = body ? body.split("\n") : [];
1133
+ while (bodyLines.length > 0 && bodyLines[0].trim() === "") bodyLines.shift();
1134
+ return {
1135
+ header,
1136
+ sections: bodyLines.length > 0 ? [{ lines: bodyLines }] : [],
1137
+ state: "pending",
1138
+ borderColor: "borderMuted",
1139
+ width,
1140
+ };
1141
+ });
1142
+ },
1143
+
1144
+ renderResult(
1145
+ result: { content: Array<{ type: string; text?: string }>; details?: WriteToolDetails; isError?: boolean },
1146
+ options: RenderResultOptions,
1147
+ uiTheme: Theme,
1148
+ args?: WriteRenderArgs,
1149
+ ): Component {
1150
+ const rawPath = args?.file_path || args?.path || "";
1151
+ const filePath = shortenPath(rawPath);
1152
+ const fileContent = args?.content || "";
1153
+ const lang = getLanguageFromPath(rawPath);
1154
+ const langIcon = uiTheme.fg("muted", uiTheme.getLangIcon(lang));
1155
+ // The header shows the cwd-relative path but links to the absolute path the
1156
+ // write resolved to (args.path may be relative, which would yield a broken
1157
+ // `file://` URI). Falls back to plain text when the result lacks a path.
1158
+ const linkTarget = result.details?.resolvedPath;
1159
+ const styledPath = filePath ? uiTheme.fg("accent", filePath) : uiTheme.fg("toolOutput", "…");
1160
+ const pathDisplay = filePath && linkTarget ? fileHyperlink(linkTarget, styledPath) : styledPath;
1161
+
1162
+ if (result.isError) {
1163
+ const errorText = result.content?.find(c => c.type === "text")?.text ?? "";
1164
+ const header = renderStatusLine(
1165
+ { icon: "error", title: "Write", description: `${langIcon} ${pathDisplay}` },
1166
+ uiTheme,
1167
+ );
1168
+ return framedBlock(uiTheme, width => ({
1169
+ header,
1170
+ sections: [{ lines: formatErrorDetail(errorText, uiTheme).split("\n") }],
1171
+ state: "error",
1172
+ borderColor: "error",
1173
+ width,
1174
+ }));
1175
+ }
1176
+
1177
+ const lineCount = countLines(fileContent);
1178
+ const lineSuffix = formatLineCountSuffix(lineCount, uiTheme);
1179
+ const execSuffix = result.details?.madeExecutable
1180
+ ? `${uiTheme.fg("dim", " · ")}${uiTheme.fg("success", "made executable!")}`
1181
+ : "";
1182
+ const header = renderStatusLine(
1183
+ {
1184
+ iconOverride: uiTheme.styledSymbol("tool.write", "accent"),
1185
+ title: "Write",
1186
+ description: `${langIcon} ${pathDisplay}${lineSuffix}${execSuffix}`,
1187
+ },
1188
+ uiTheme,
1189
+ );
1190
+ const diagnostics = result.details?.diagnostics;
1191
+
1192
+ return framedBlock(uiTheme, width => {
1193
+ const { expanded } = options;
1194
+ let body = renderContentPreview(fileContent, expanded, lang, uiTheme);
1195
+ if (diagnostics) {
1196
+ const diagText = formatDiagnostics(diagnostics, expanded, uiTheme, fp =>
1197
+ uiTheme.getLangIcon(getLanguageFromPath(fp)),
1198
+ );
1199
+ if (diagText.trim()) {
1200
+ const diagLines = diagText.split("\n");
1201
+ const firstNonEmpty = diagLines.findIndex(line => line.trim());
1202
+ if (firstNonEmpty >= 0) body += `\n${diagLines.slice(firstNonEmpty).join("\n")}`;
1203
+ }
1204
+ }
1205
+ const bodyLines = body.split("\n");
1206
+ while (bodyLines.length > 0 && bodyLines[0].trim() === "") bodyLines.shift();
1207
+ return {
1208
+ header,
1209
+ sections: bodyLines.length > 0 ? [{ lines: bodyLines }] : [],
1210
+ state: "success",
1211
+ borderColor: "borderMuted",
1212
+ width,
1213
+ };
1214
+ });
1215
+ },
1216
+ mergeCallAndResult: true,
1217
+ };