@f5-sales-demo/xcsh 19.51.2

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 (899) hide show
  1. package/CHANGELOG.md +7073 -0
  2. package/README.md +14 -0
  3. package/examples/README.md +21 -0
  4. package/examples/custom-tools/README.md +109 -0
  5. package/examples/custom-tools/hello/index.ts +20 -0
  6. package/examples/custom-tools/todo/index.ts +206 -0
  7. package/examples/extensions/README.md +143 -0
  8. package/examples/extensions/api-demo.ts +89 -0
  9. package/examples/extensions/chalk-logger.ts +25 -0
  10. package/examples/extensions/hello.ts +32 -0
  11. package/examples/extensions/pirate.ts +43 -0
  12. package/examples/extensions/plan-mode.ts +551 -0
  13. package/examples/extensions/reload-runtime.ts +37 -0
  14. package/examples/extensions/todo.ts +296 -0
  15. package/examples/extensions/tools.ts +145 -0
  16. package/examples/extensions/with-deps/index.ts +36 -0
  17. package/examples/extensions/with-deps/package-lock.json +31 -0
  18. package/examples/extensions/with-deps/package.json +16 -0
  19. package/examples/hooks/README.md +56 -0
  20. package/examples/hooks/auto-commit-on-exit.ts +48 -0
  21. package/examples/hooks/confirm-destructive.ts +58 -0
  22. package/examples/hooks/custom-compaction.ts +115 -0
  23. package/examples/hooks/dirty-repo-guard.ts +51 -0
  24. package/examples/hooks/file-trigger.ts +40 -0
  25. package/examples/hooks/git-checkpoint.ts +52 -0
  26. package/examples/hooks/handoff.ts +149 -0
  27. package/examples/hooks/permission-gate.ts +33 -0
  28. package/examples/hooks/protected-paths.ts +29 -0
  29. package/examples/hooks/qna.ts +118 -0
  30. package/examples/hooks/status-line.ts +39 -0
  31. package/examples/sdk/01-minimal.ts +21 -0
  32. package/examples/sdk/02-custom-model.ts +49 -0
  33. package/examples/sdk/03-custom-prompt.ts +43 -0
  34. package/examples/sdk/04-skills.ts +43 -0
  35. package/examples/sdk/06-extensions.ts +80 -0
  36. package/examples/sdk/06-hooks.ts +61 -0
  37. package/examples/sdk/07-context-files.ts +35 -0
  38. package/examples/sdk/08-prompt-templates.ts +41 -0
  39. package/examples/sdk/08-slash-commands.ts +46 -0
  40. package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
  41. package/examples/sdk/11-sessions.ts +47 -0
  42. package/examples/sdk/README.md +172 -0
  43. package/package.json +539 -0
  44. package/scripts/build-info/resolvers.ts +51 -0
  45. package/scripts/capture-ax-fixture.ts +84 -0
  46. package/scripts/capture-login-fixture.ts +30 -0
  47. package/scripts/check-workflow-field-coverage.ts +101 -0
  48. package/scripts/extension-uat-harness.ts +340 -0
  49. package/scripts/format-prompts.ts +68 -0
  50. package/scripts/generate-api-spec-index.ts +580 -0
  51. package/scripts/generate-branding-index.ts +81 -0
  52. package/scripts/generate-build-info.ts +106 -0
  53. package/scripts/generate-console-catalog.ts +88 -0
  54. package/scripts/generate-console-field-metadata.ts +74 -0
  55. package/scripts/generate-docs-index.ts +40 -0
  56. package/scripts/generate-template.ts +32 -0
  57. package/scripts/generate-terraform-index.ts +81 -0
  58. package/scripts/mermaid-gallery.ts +132 -0
  59. package/scripts/uat-matrix-modalities.ts +451 -0
  60. package/scripts/uat-matrix-report.ts +175 -0
  61. package/scripts/uat-matrix.ts +353 -0
  62. package/src/async/index.ts +2 -0
  63. package/src/async/job-manager.ts +416 -0
  64. package/src/async/support.ts +5 -0
  65. package/src/autoresearch/apply-contract-to-state.ts +1 -0
  66. package/src/autoresearch/command-resume.md +17 -0
  67. package/src/autoresearch/contract.ts +205 -0
  68. package/src/autoresearch/dashboard.ts +374 -0
  69. package/src/autoresearch/git.ts +191 -0
  70. package/src/autoresearch/helpers.ts +328 -0
  71. package/src/autoresearch/index.ts +402 -0
  72. package/src/autoresearch/prompt.md +236 -0
  73. package/src/autoresearch/resume-message.md +16 -0
  74. package/src/autoresearch/state.ts +303 -0
  75. package/src/autoresearch/tools/init-experiment.ts +400 -0
  76. package/src/autoresearch/tools/log-experiment.ts +793 -0
  77. package/src/autoresearch/tools/run-experiment.ts +679 -0
  78. package/src/autoresearch/types.ts +114 -0
  79. package/src/browser/acquire.ts +249 -0
  80. package/src/browser/actions.ts +126 -0
  81. package/src/browser/auth.ts +176 -0
  82. package/src/browser/ax.ts +134 -0
  83. package/src/browser/cdp-core.ts +45 -0
  84. package/src/browser/cdp-page-actions.ts +51 -0
  85. package/src/browser/chrome-locate.ts +66 -0
  86. package/src/browser/dom-context.ts +89 -0
  87. package/src/browser/dt-context.ts +100 -0
  88. package/src/browser/extension-bridge.ts +182 -0
  89. package/src/browser/extension-page-actions.ts +397 -0
  90. package/src/browser/extension-provider.ts +373 -0
  91. package/src/browser/index.ts +17 -0
  92. package/src/browser/input-commit.ts +74 -0
  93. package/src/browser/native-messaging.ts +20 -0
  94. package/src/browser/page-actions.ts +18 -0
  95. package/src/browser/provider.ts +155 -0
  96. package/src/browser/resolver.ts +126 -0
  97. package/src/browser/selector.ts +40 -0
  98. package/src/bun-imports.d.ts +22 -0
  99. package/src/capability/context-file.ts +43 -0
  100. package/src/capability/extension-module.ts +34 -0
  101. package/src/capability/extension.ts +47 -0
  102. package/src/capability/fs.ts +107 -0
  103. package/src/capability/hook.ts +40 -0
  104. package/src/capability/index.ts +438 -0
  105. package/src/capability/instruction.ts +37 -0
  106. package/src/capability/mcp.ts +74 -0
  107. package/src/capability/prompt.ts +35 -0
  108. package/src/capability/rule.ts +224 -0
  109. package/src/capability/settings.ts +34 -0
  110. package/src/capability/skill.ts +50 -0
  111. package/src/capability/slash-command.ts +40 -0
  112. package/src/capability/ssh.ts +41 -0
  113. package/src/capability/system-prompt.ts +34 -0
  114. package/src/capability/tool.ts +38 -0
  115. package/src/capability/types.ts +168 -0
  116. package/src/cli/agents-cli.ts +138 -0
  117. package/src/cli/args.ts +286 -0
  118. package/src/cli/chrome-cli.ts +88 -0
  119. package/src/cli/classify-install-target.ts +50 -0
  120. package/src/cli/commands/init-xdg.ts +27 -0
  121. package/src/cli/config-cli.ts +418 -0
  122. package/src/cli/file-processor.ts +121 -0
  123. package/src/cli/grep-cli.ts +160 -0
  124. package/src/cli/grievances-cli.ts +78 -0
  125. package/src/cli/initial-message.ts +58 -0
  126. package/src/cli/jupyter-cli.ts +106 -0
  127. package/src/cli/list-models.ts +128 -0
  128. package/src/cli/plugin-cli.ts +964 -0
  129. package/src/cli/read-cli.ts +68 -0
  130. package/src/cli/session-picker.ts +52 -0
  131. package/src/cli/setup-cli.ts +479 -0
  132. package/src/cli/shell-cli.ts +174 -0
  133. package/src/cli/ssh-cli.ts +179 -0
  134. package/src/cli/stats-cli.ts +182 -0
  135. package/src/cli/update-cli.ts +446 -0
  136. package/src/cli/web-search-cli.ts +203 -0
  137. package/src/cli.ts +123 -0
  138. package/src/commands/agents.ts +57 -0
  139. package/src/commands/chrome-host.ts +71 -0
  140. package/src/commands/chrome.ts +28 -0
  141. package/src/commands/commit.ts +36 -0
  142. package/src/commands/config.ts +51 -0
  143. package/src/commands/grep.ts +48 -0
  144. package/src/commands/grievances.ts +20 -0
  145. package/src/commands/jupyter.ts +32 -0
  146. package/src/commands/launch.ts +144 -0
  147. package/src/commands/plugin.ts +78 -0
  148. package/src/commands/read.ts +33 -0
  149. package/src/commands/setup.ts +42 -0
  150. package/src/commands/shell.ts +29 -0
  151. package/src/commands/ssh.ts +60 -0
  152. package/src/commands/stats.ts +29 -0
  153. package/src/commands/update.ts +21 -0
  154. package/src/commands/web-search.ts +46 -0
  155. package/src/commit/agentic/agent.ts +314 -0
  156. package/src/commit/agentic/fallback.ts +96 -0
  157. package/src/commit/agentic/index.ts +354 -0
  158. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  159. package/src/commit/agentic/prompts/session-user.md +25 -0
  160. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  161. package/src/commit/agentic/prompts/system.md +38 -0
  162. package/src/commit/agentic/state.ts +60 -0
  163. package/src/commit/agentic/tools/analyze-file.ts +127 -0
  164. package/src/commit/agentic/tools/git-file-diff.ts +191 -0
  165. package/src/commit/agentic/tools/git-hunk.ts +50 -0
  166. package/src/commit/agentic/tools/git-overview.ts +81 -0
  167. package/src/commit/agentic/tools/index.ts +54 -0
  168. package/src/commit/agentic/tools/propose-changelog.ts +139 -0
  169. package/src/commit/agentic/tools/propose-commit.ts +122 -0
  170. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  171. package/src/commit/agentic/tools/schemas.ts +31 -0
  172. package/src/commit/agentic/tools/split-commit.ts +251 -0
  173. package/src/commit/agentic/topo-sort.ts +44 -0
  174. package/src/commit/agentic/trivial.ts +51 -0
  175. package/src/commit/agentic/validation.ts +183 -0
  176. package/src/commit/analysis/conventional.ts +122 -0
  177. package/src/commit/analysis/index.ts +4 -0
  178. package/src/commit/analysis/scope.ts +242 -0
  179. package/src/commit/analysis/summary.ts +105 -0
  180. package/src/commit/analysis/validation.ts +66 -0
  181. package/src/commit/changelog/detect.ts +40 -0
  182. package/src/commit/changelog/generate.ts +101 -0
  183. package/src/commit/changelog/index.ts +234 -0
  184. package/src/commit/changelog/parse.ts +44 -0
  185. package/src/commit/cli.ts +85 -0
  186. package/src/commit/git/diff.ts +148 -0
  187. package/src/commit/index.ts +5 -0
  188. package/src/commit/map-reduce/index.ts +69 -0
  189. package/src/commit/map-reduce/map-phase.ts +193 -0
  190. package/src/commit/map-reduce/reduce-phase.ts +108 -0
  191. package/src/commit/map-reduce/utils.ts +9 -0
  192. package/src/commit/message.ts +11 -0
  193. package/src/commit/model-selection.ts +66 -0
  194. package/src/commit/pipeline.ts +242 -0
  195. package/src/commit/prompts/analysis-system.md +148 -0
  196. package/src/commit/prompts/analysis-user.md +38 -0
  197. package/src/commit/prompts/changelog-system.md +50 -0
  198. package/src/commit/prompts/changelog-user.md +18 -0
  199. package/src/commit/prompts/file-observer-system.md +24 -0
  200. package/src/commit/prompts/file-observer-user.md +8 -0
  201. package/src/commit/prompts/reduce-system.md +50 -0
  202. package/src/commit/prompts/reduce-user.md +17 -0
  203. package/src/commit/prompts/summary-retry.md +3 -0
  204. package/src/commit/prompts/summary-system.md +38 -0
  205. package/src/commit/prompts/summary-user.md +13 -0
  206. package/src/commit/prompts/types-description.md +2 -0
  207. package/src/commit/types.ts +118 -0
  208. package/src/commit/utils/exclusions.ts +42 -0
  209. package/src/commit/utils.ts +44 -0
  210. package/src/config/auto-config.ts +698 -0
  211. package/src/config/context-schema.json +115 -0
  212. package/src/config/file-lock.ts +121 -0
  213. package/src/config/keybindings.ts +493 -0
  214. package/src/config/mcp-schema.json +230 -0
  215. package/src/config/model-equivalence.ts +675 -0
  216. package/src/config/model-registry.ts +2265 -0
  217. package/src/config/model-resolver.ts +1289 -0
  218. package/src/config/prompt-templates.ts +271 -0
  219. package/src/config/resolve-config-value.ts +110 -0
  220. package/src/config/settings-schema.ts +2155 -0
  221. package/src/config/settings.ts +730 -0
  222. package/src/config.ts +418 -0
  223. package/src/cursor.ts +323 -0
  224. package/src/dap/client.ts +674 -0
  225. package/src/dap/config.ts +150 -0
  226. package/src/dap/defaults.json +211 -0
  227. package/src/dap/index.ts +4 -0
  228. package/src/dap/session.ts +1255 -0
  229. package/src/dap/types.ts +600 -0
  230. package/src/debug/index.ts +440 -0
  231. package/src/debug/log-formatting.ts +58 -0
  232. package/src/debug/log-viewer.ts +908 -0
  233. package/src/debug/profiler.ts +158 -0
  234. package/src/debug/report-bundle.ts +365 -0
  235. package/src/debug/system-info.ts +107 -0
  236. package/src/discovery/agents-md.ts +67 -0
  237. package/src/discovery/agents.ts +219 -0
  238. package/src/discovery/builtin.ts +851 -0
  239. package/src/discovery/claude-plugins.ts +297 -0
  240. package/src/discovery/claude.ts +543 -0
  241. package/src/discovery/cline.ts +83 -0
  242. package/src/discovery/codex.ts +535 -0
  243. package/src/discovery/cursor.ts +220 -0
  244. package/src/discovery/gemini.ts +396 -0
  245. package/src/discovery/github.ts +118 -0
  246. package/src/discovery/helpers.ts +934 -0
  247. package/src/discovery/index.ts +79 -0
  248. package/src/discovery/language.ts +46 -0
  249. package/src/discovery/mcp-json.ts +171 -0
  250. package/src/discovery/opencode.ts +393 -0
  251. package/src/discovery/plugin-dir-roots.ts +28 -0
  252. package/src/discovery/ssh.ts +153 -0
  253. package/src/discovery/substitute-plugin-root.ts +29 -0
  254. package/src/discovery/vscode.ts +105 -0
  255. package/src/discovery/windsurf.ts +147 -0
  256. package/src/edit/diff.ts +818 -0
  257. package/src/edit/index.ts +481 -0
  258. package/src/edit/line-hash.ts +67 -0
  259. package/src/edit/modes/chunk.ts +762 -0
  260. package/src/edit/modes/hashline.ts +1366 -0
  261. package/src/edit/modes/patch.ts +1791 -0
  262. package/src/edit/modes/replace.ts +1115 -0
  263. package/src/edit/normalize.ts +375 -0
  264. package/src/edit/renderer.ts +627 -0
  265. package/src/exa/factory.ts +61 -0
  266. package/src/exa/index.ts +27 -0
  267. package/src/exa/mcp-client.ts +293 -0
  268. package/src/exa/render.ts +232 -0
  269. package/src/exa/researcher.ts +47 -0
  270. package/src/exa/search.ts +86 -0
  271. package/src/exa/types.ts +166 -0
  272. package/src/exa/websets.ts +247 -0
  273. package/src/exec/bash-executor.ts +332 -0
  274. package/src/exec/exec.ts +53 -0
  275. package/src/exec/idle-timeout-watchdog.ts +126 -0
  276. package/src/exec/non-interactive-env.ts +48 -0
  277. package/src/export/custom-share.ts +65 -0
  278. package/src/export/html/index.ts +161 -0
  279. package/src/export/html/template.css +1001 -0
  280. package/src/export/html/template.generated.ts +2 -0
  281. package/src/export/html/template.html +46 -0
  282. package/src/export/html/template.js +1950 -0
  283. package/src/export/html/template.macro.ts +24 -0
  284. package/src/export/html/vendor/highlight.min.js +1213 -0
  285. package/src/export/html/vendor/marked.min.js +6 -0
  286. package/src/export/ttsr.ts +434 -0
  287. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +25 -0
  288. package/src/extensibility/custom-commands/bundled/review/index.ts +456 -0
  289. package/src/extensibility/custom-commands/index.ts +2 -0
  290. package/src/extensibility/custom-commands/loader.ts +236 -0
  291. package/src/extensibility/custom-commands/types.ts +111 -0
  292. package/src/extensibility/custom-tools/index.ts +7 -0
  293. package/src/extensibility/custom-tools/loader.ts +238 -0
  294. package/src/extensibility/custom-tools/types.ts +251 -0
  295. package/src/extensibility/custom-tools/wrapper.ts +47 -0
  296. package/src/extensibility/extensions/index.ts +15 -0
  297. package/src/extensibility/extensions/loader.ts +572 -0
  298. package/src/extensibility/extensions/runner.ts +848 -0
  299. package/src/extensibility/extensions/types.ts +1394 -0
  300. package/src/extensibility/extensions/wrapper.ts +191 -0
  301. package/src/extensibility/hooks/index.ts +5 -0
  302. package/src/extensibility/hooks/loader.ts +255 -0
  303. package/src/extensibility/hooks/runner.ts +425 -0
  304. package/src/extensibility/hooks/tool-wrapper.ts +108 -0
  305. package/src/extensibility/hooks/types.ts +826 -0
  306. package/src/extensibility/plugins/doctor.ts +66 -0
  307. package/src/extensibility/plugins/git-url.ts +281 -0
  308. package/src/extensibility/plugins/index.ts +9 -0
  309. package/src/extensibility/plugins/installer.ts +192 -0
  310. package/src/extensibility/plugins/loader.ts +267 -0
  311. package/src/extensibility/plugins/manager.ts +731 -0
  312. package/src/extensibility/plugins/marketplace/cache.ts +136 -0
  313. package/src/extensibility/plugins/marketplace/fetcher.ts +318 -0
  314. package/src/extensibility/plugins/marketplace/index.ts +6 -0
  315. package/src/extensibility/plugins/marketplace/manager.ts +781 -0
  316. package/src/extensibility/plugins/marketplace/prerequisites.ts +255 -0
  317. package/src/extensibility/plugins/marketplace/registry.ts +196 -0
  318. package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
  319. package/src/extensibility/plugins/marketplace/types.ts +201 -0
  320. package/src/extensibility/plugins/parser.ts +105 -0
  321. package/src/extensibility/plugins/types.ts +194 -0
  322. package/src/extensibility/skills.ts +269 -0
  323. package/src/extensibility/slash-commands.ts +246 -0
  324. package/src/extensibility/tool-proxy.ts +25 -0
  325. package/src/extensibility/utils.ts +38 -0
  326. package/src/index.ts +61 -0
  327. package/src/internal-urls/agent-protocol.ts +136 -0
  328. package/src/internal-urls/api-catalog-resolve.ts +495 -0
  329. package/src/internal-urls/api-catalog-types.ts +94 -0
  330. package/src/internal-urls/api-spec-resolve.ts +1036 -0
  331. package/src/internal-urls/api-spec-types.ts +239 -0
  332. package/src/internal-urls/artifact-protocol.ts +97 -0
  333. package/src/internal-urls/branding-index.generated.ts +88 -0
  334. package/src/internal-urls/build-info-runtime.ts +302 -0
  335. package/src/internal-urls/build-info.generated.ts +33 -0
  336. package/src/internal-urls/computer-profile.ts +692 -0
  337. package/src/internal-urls/console-catalog-types.ts +21 -0
  338. package/src/internal-urls/console-catalog.generated.ts +532 -0
  339. package/src/internal-urls/console-field-metadata-types.ts +59 -0
  340. package/src/internal-urls/console-field-metadata.generated.ts +4841 -0
  341. package/src/internal-urls/console-resolve.ts +178 -0
  342. package/src/internal-urls/docs-index.generated.ts +698 -0
  343. package/src/internal-urls/extension-api.md +147 -0
  344. package/src/internal-urls/index.ts +46 -0
  345. package/src/internal-urls/jobs-protocol.ts +119 -0
  346. package/src/internal-urls/json-query.ts +126 -0
  347. package/src/internal-urls/local-protocol.ts +206 -0
  348. package/src/internal-urls/mcp-protocol.ts +156 -0
  349. package/src/internal-urls/memory-protocol.ts +133 -0
  350. package/src/internal-urls/parse.ts +72 -0
  351. package/src/internal-urls/profile-collectors.ts +106 -0
  352. package/src/internal-urls/router.ts +55 -0
  353. package/src/internal-urls/rule-protocol.ts +55 -0
  354. package/src/internal-urls/skill-protocol.ts +111 -0
  355. package/src/internal-urls/terraform-index.generated.ts +1852 -0
  356. package/src/internal-urls/terraform-resolve.ts +184 -0
  357. package/src/internal-urls/terraform-types.ts +45 -0
  358. package/src/internal-urls/types.ts +52 -0
  359. package/src/internal-urls/user-profile.ts +363 -0
  360. package/src/internal-urls/xcsh-protocol.ts +644 -0
  361. package/src/ipy/cancellation.ts +28 -0
  362. package/src/ipy/executor.ts +1155 -0
  363. package/src/ipy/gateway-coordinator.ts +424 -0
  364. package/src/ipy/kernel.ts +1091 -0
  365. package/src/ipy/modules.ts +144 -0
  366. package/src/ipy/prelude.py +849 -0
  367. package/src/ipy/prelude.ts +3 -0
  368. package/src/ipy/runtime.ts +221 -0
  369. package/src/locales/ar.json +512 -0
  370. package/src/locales/de.json +512 -0
  371. package/src/locales/en.json +549 -0
  372. package/src/locales/es.json +512 -0
  373. package/src/locales/fr.json +512 -0
  374. package/src/locales/hi.json +512 -0
  375. package/src/locales/index.ts +29 -0
  376. package/src/locales/it.json +512 -0
  377. package/src/locales/ja.json +512 -0
  378. package/src/locales/ko.json +512 -0
  379. package/src/locales/pt-br.json +512 -0
  380. package/src/locales/th.json +512 -0
  381. package/src/locales/zh-cn.json +512 -0
  382. package/src/locales/zh-tw.json +512 -0
  383. package/src/lsp/client.ts +941 -0
  384. package/src/lsp/clients/biome-client.ts +202 -0
  385. package/src/lsp/clients/index.ts +50 -0
  386. package/src/lsp/clients/lsp-linter-client.ts +93 -0
  387. package/src/lsp/clients/swiftlint-client.ts +120 -0
  388. package/src/lsp/config.ts +418 -0
  389. package/src/lsp/defaults.json +999 -0
  390. package/src/lsp/edits.ts +109 -0
  391. package/src/lsp/index.ts +1746 -0
  392. package/src/lsp/lspmux.ts +233 -0
  393. package/src/lsp/render.ts +692 -0
  394. package/src/lsp/startup-events.ts +13 -0
  395. package/src/lsp/types.ts +444 -0
  396. package/src/lsp/utils.ts +691 -0
  397. package/src/main.ts +944 -0
  398. package/src/mcp/client.ts +482 -0
  399. package/src/mcp/config-writer.ts +225 -0
  400. package/src/mcp/config.ts +365 -0
  401. package/src/mcp/discoverable-tool-metadata.ts +202 -0
  402. package/src/mcp/index.ts +29 -0
  403. package/src/mcp/json-rpc.ts +84 -0
  404. package/src/mcp/loader.ts +124 -0
  405. package/src/mcp/manager.ts +1152 -0
  406. package/src/mcp/oauth-discovery.ts +349 -0
  407. package/src/mcp/oauth-flow.ts +387 -0
  408. package/src/mcp/render.ts +157 -0
  409. package/src/mcp/smithery-auth.ts +104 -0
  410. package/src/mcp/smithery-connect.ts +145 -0
  411. package/src/mcp/smithery-registry.ts +477 -0
  412. package/src/mcp/tool-bridge.ts +417 -0
  413. package/src/mcp/tool-cache.ts +117 -0
  414. package/src/mcp/transports/http.ts +475 -0
  415. package/src/mcp/transports/index.ts +6 -0
  416. package/src/mcp/transports/stdio.ts +325 -0
  417. package/src/mcp/types.ts +423 -0
  418. package/src/memories/index.ts +1115 -0
  419. package/src/memories/storage.ts +577 -0
  420. package/src/modes/acp/acp-agent.ts +1361 -0
  421. package/src/modes/acp/acp-event-mapper.ts +538 -0
  422. package/src/modes/acp/acp-mode.ts +15 -0
  423. package/src/modes/acp/index.ts +2 -0
  424. package/src/modes/components/agent-dashboard.ts +1124 -0
  425. package/src/modes/components/assistant-message.ts +213 -0
  426. package/src/modes/components/bash-execution.ts +345 -0
  427. package/src/modes/components/bordered-loader.ts +41 -0
  428. package/src/modes/components/branch-summary-message.ts +45 -0
  429. package/src/modes/components/btw-panel.ts +103 -0
  430. package/src/modes/components/compaction-summary-message.ts +51 -0
  431. package/src/modes/components/context-add-wizard.ts +534 -0
  432. package/src/modes/components/countdown-timer.ts +75 -0
  433. package/src/modes/components/custom-editor.ts +238 -0
  434. package/src/modes/components/custom-message.ts +91 -0
  435. package/src/modes/components/diff.ts +210 -0
  436. package/src/modes/components/dynamic-border.ts +25 -0
  437. package/src/modes/components/extensions/extension-dashboard.ts +326 -0
  438. package/src/modes/components/extensions/extension-list.ts +492 -0
  439. package/src/modes/components/extensions/index.ts +9 -0
  440. package/src/modes/components/extensions/inspector-panel.ts +317 -0
  441. package/src/modes/components/extensions/state-manager.ts +587 -0
  442. package/src/modes/components/extensions/types.ts +191 -0
  443. package/src/modes/components/footer.ts +262 -0
  444. package/src/modes/components/gutter-block.ts +315 -0
  445. package/src/modes/components/history-search.ts +158 -0
  446. package/src/modes/components/hook-editor.ts +151 -0
  447. package/src/modes/components/hook-input.ts +81 -0
  448. package/src/modes/components/hook-message.ts +100 -0
  449. package/src/modes/components/hook-selector.ts +192 -0
  450. package/src/modes/components/index.ts +36 -0
  451. package/src/modes/components/keybinding-hints.ts +65 -0
  452. package/src/modes/components/login-dialog.ts +164 -0
  453. package/src/modes/components/mcp-add-wizard.ts +1314 -0
  454. package/src/modes/components/model-selector.ts +946 -0
  455. package/src/modes/components/oauth-selector.ts +211 -0
  456. package/src/modes/components/plugin-selector.ts +95 -0
  457. package/src/modes/components/plugin-settings.ts +477 -0
  458. package/src/modes/components/plugins/index.ts +5 -0
  459. package/src/modes/components/plugins/plugin-dashboard.ts +523 -0
  460. package/src/modes/components/plugins/plugin-inspector-pane.ts +84 -0
  461. package/src/modes/components/plugins/plugin-list-pane.ts +109 -0
  462. package/src/modes/components/plugins/state-manager.ts +229 -0
  463. package/src/modes/components/plugins/types.ts +51 -0
  464. package/src/modes/components/plugins/utils.ts +6 -0
  465. package/src/modes/components/python-execution.ts +244 -0
  466. package/src/modes/components/queue-mode-selector.ts +56 -0
  467. package/src/modes/components/read-tool-group.ts +137 -0
  468. package/src/modes/components/session-observer-overlay.ts +483 -0
  469. package/src/modes/components/session-selector.ts +343 -0
  470. package/src/modes/components/settings-defs.ts +568 -0
  471. package/src/modes/components/settings-selector.ts +641 -0
  472. package/src/modes/components/show-images-selector.ts +45 -0
  473. package/src/modes/components/skill-message.ts +90 -0
  474. package/src/modes/components/status-line/context-gradient.ts +64 -0
  475. package/src/modes/components/status-line/git-utils.ts +42 -0
  476. package/src/modes/components/status-line/hex-ansi.ts +29 -0
  477. package/src/modes/components/status-line/index.ts +4 -0
  478. package/src/modes/components/status-line/presets.ts +174 -0
  479. package/src/modes/components/status-line/segments.ts +494 -0
  480. package/src/modes/components/status-line/separators.ts +55 -0
  481. package/src/modes/components/status-line/token-rate.ts +66 -0
  482. package/src/modes/components/status-line/types.ts +96 -0
  483. package/src/modes/components/status-line-segment-editor.ts +361 -0
  484. package/src/modes/components/status-line.ts +729 -0
  485. package/src/modes/components/theme-selector.ts +63 -0
  486. package/src/modes/components/thinking-selector.ts +52 -0
  487. package/src/modes/components/todo-reminder.ts +54 -0
  488. package/src/modes/components/tool-execution.ts +781 -0
  489. package/src/modes/components/tree-selector.ts +908 -0
  490. package/src/modes/components/ttsr-notification.ts +80 -0
  491. package/src/modes/components/user-message-selector.ts +141 -0
  492. package/src/modes/components/user-message.ts +100 -0
  493. package/src/modes/components/visual-truncate.ts +63 -0
  494. package/src/modes/components/welcome-checks.ts +313 -0
  495. package/src/modes/components/welcome.ts +270 -0
  496. package/src/modes/controllers/btw-controller.ts +193 -0
  497. package/src/modes/controllers/chord-routing.ts +37 -0
  498. package/src/modes/controllers/command-controller.ts +1293 -0
  499. package/src/modes/controllers/context-command-controller.ts +61 -0
  500. package/src/modes/controllers/event-controller.ts +743 -0
  501. package/src/modes/controllers/extension-ui-controller.ts +983 -0
  502. package/src/modes/controllers/input-controller.ts +877 -0
  503. package/src/modes/controllers/mcp-command-controller.ts +1940 -0
  504. package/src/modes/controllers/selector-controller.ts +1292 -0
  505. package/src/modes/controllers/ssh-command-controller.ts +421 -0
  506. package/src/modes/index.ts +34 -0
  507. package/src/modes/interactive-mode.ts +1781 -0
  508. package/src/modes/oauth-manual-input.ts +42 -0
  509. package/src/modes/print-mode.ts +196 -0
  510. package/src/modes/prompt-action-autocomplete.ts +231 -0
  511. package/src/modes/rpc/host-tools.ts +186 -0
  512. package/src/modes/rpc/rpc-client.ts +762 -0
  513. package/src/modes/rpc/rpc-mode.ts +877 -0
  514. package/src/modes/rpc/rpc-types.ts +322 -0
  515. package/src/modes/session-observer-registry.ts +146 -0
  516. package/src/modes/shared.ts +30 -0
  517. package/src/modes/theme/defaults/index.ts +7 -0
  518. package/src/modes/theme/defaults/xcsh-dark.json +113 -0
  519. package/src/modes/theme/defaults/xcsh-light.json +115 -0
  520. package/src/modes/theme/mermaid-cache.ts +151 -0
  521. package/src/modes/theme/mermaid-palette.ts +39 -0
  522. package/src/modes/theme/theme-schema.json +553 -0
  523. package/src/modes/theme/theme.ts +2563 -0
  524. package/src/modes/types.ts +273 -0
  525. package/src/modes/utils/hotkeys-markdown.ts +60 -0
  526. package/src/modes/utils/keybinding-matchers.ts +30 -0
  527. package/src/modes/utils/read-group-outcome-aggregator.ts +55 -0
  528. package/src/modes/utils/sanitize-error-message.ts +60 -0
  529. package/src/modes/utils/tools-markdown.ts +28 -0
  530. package/src/modes/utils/ui-helpers.ts +734 -0
  531. package/src/plan-mode/approved-plan.ts +55 -0
  532. package/src/plan-mode/state.ts +6 -0
  533. package/src/priority.json +37 -0
  534. package/src/prompts/agents/designer.md +67 -0
  535. package/src/prompts/agents/explore.md +61 -0
  536. package/src/prompts/agents/frontmatter.md +10 -0
  537. package/src/prompts/agents/init.md +36 -0
  538. package/src/prompts/agents/librarian.md +121 -0
  539. package/src/prompts/agents/plan.md +49 -0
  540. package/src/prompts/agents/reviewer.md +127 -0
  541. package/src/prompts/agents/task.md +16 -0
  542. package/src/prompts/ci-green-request.md +35 -0
  543. package/src/prompts/compaction/branch-summary-context.md +5 -0
  544. package/src/prompts/compaction/branch-summary-preamble.md +2 -0
  545. package/src/prompts/compaction/branch-summary.md +30 -0
  546. package/src/prompts/compaction/compaction-short-summary.md +9 -0
  547. package/src/prompts/compaction/compaction-summary-context.md +5 -0
  548. package/src/prompts/compaction/compaction-summary.md +38 -0
  549. package/src/prompts/compaction/compaction-turn-prefix.md +17 -0
  550. package/src/prompts/compaction/compaction-update-summary.md +45 -0
  551. package/src/prompts/memories/consolidation.md +30 -0
  552. package/src/prompts/memories/read-path.md +11 -0
  553. package/src/prompts/memories/stage_one_input.md +6 -0
  554. package/src/prompts/memories/stage_one_system.md +21 -0
  555. package/src/prompts/review-request.md +70 -0
  556. package/src/prompts/system/agent-creation-architect.md +65 -0
  557. package/src/prompts/system/agent-creation-user.md +6 -0
  558. package/src/prompts/system/auto-handoff-threshold-focus.md +1 -0
  559. package/src/prompts/system/btw-user.md +8 -0
  560. package/src/prompts/system/commit-message-system.md +2 -0
  561. package/src/prompts/system/custom-system-prompt.md +82 -0
  562. package/src/prompts/system/eager-todo.md +13 -0
  563. package/src/prompts/system/file-operations.md +10 -0
  564. package/src/prompts/system/handoff-document.md +48 -0
  565. package/src/prompts/system/plan-mode-active.md +114 -0
  566. package/src/prompts/system/plan-mode-approved.md +23 -0
  567. package/src/prompts/system/plan-mode-reference.md +14 -0
  568. package/src/prompts/system/plan-mode-subagent.md +34 -0
  569. package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
  570. package/src/prompts/system/subagent-submit-reminder.md +7 -0
  571. package/src/prompts/system/subagent-system-prompt.md +36 -0
  572. package/src/prompts/system/subagent-user-prompt.md +12 -0
  573. package/src/prompts/system/summarization-system.md +3 -0
  574. package/src/prompts/system/system-prompt.md +698 -0
  575. package/src/prompts/system/title-system.md +2 -0
  576. package/src/prompts/system/ttsr-interrupt.md +7 -0
  577. package/src/prompts/system/web-search.md +28 -0
  578. package/src/prompts/tools/ask.md +28 -0
  579. package/src/prompts/tools/ast-edit.md +48 -0
  580. package/src/prompts/tools/ast-grep.md +54 -0
  581. package/src/prompts/tools/async-result.md +5 -0
  582. package/src/prompts/tools/bash.md +67 -0
  583. package/src/prompts/tools/browser.md +26 -0
  584. package/src/prompts/tools/calculator.md +10 -0
  585. package/src/prompts/tools/cancel-job.md +5 -0
  586. package/src/prompts/tools/catalog-workflow-runner.md +13 -0
  587. package/src/prompts/tools/checkpoint.md +16 -0
  588. package/src/prompts/tools/chunk-edit.md +279 -0
  589. package/src/prompts/tools/debug.md +43 -0
  590. package/src/prompts/tools/display-image.md +23 -0
  591. package/src/prompts/tools/exit-plan-mode.md +41 -0
  592. package/src/prompts/tools/find.md +26 -0
  593. package/src/prompts/tools/gemini-image.md +7 -0
  594. package/src/prompts/tools/grep.md +29 -0
  595. package/src/prompts/tools/hashline.md +131 -0
  596. package/src/prompts/tools/inspect-image-system.md +20 -0
  597. package/src/prompts/tools/inspect-image.md +32 -0
  598. package/src/prompts/tools/lsp.md +33 -0
  599. package/src/prompts/tools/patch.md +77 -0
  600. package/src/prompts/tools/poll.md +5 -0
  601. package/src/prompts/tools/python.md +60 -0
  602. package/src/prompts/tools/read-chunk.md +35 -0
  603. package/src/prompts/tools/read.md +63 -0
  604. package/src/prompts/tools/render-mermaid.md +9 -0
  605. package/src/prompts/tools/replace.md +36 -0
  606. package/src/prompts/tools/resolve.md +8 -0
  607. package/src/prompts/tools/rewind.md +13 -0
  608. package/src/prompts/tools/search-tool-bm25.md +34 -0
  609. package/src/prompts/tools/ssh.md +44 -0
  610. package/src/prompts/tools/task-summary.md +28 -0
  611. package/src/prompts/tools/task.md +131 -0
  612. package/src/prompts/tools/todo-write.md +89 -0
  613. package/src/prompts/tools/vim.md +98 -0
  614. package/src/prompts/tools/web-search.md +10 -0
  615. package/src/prompts/tools/write.md +14 -0
  616. package/src/prompts/tools/xcsh-api.md +283 -0
  617. package/src/resource-management/index.ts +42 -0
  618. package/src/sdk.ts +2123 -0
  619. package/src/secrets/index.ts +146 -0
  620. package/src/secrets/obfuscator.ts +290 -0
  621. package/src/secrets/regex.ts +21 -0
  622. package/src/services/context-env.ts +102 -0
  623. package/src/services/xcsh-api-client.ts +230 -0
  624. package/src/services/xcsh-context-command.ts +803 -0
  625. package/src/services/xcsh-context-display.ts +49 -0
  626. package/src/services/xcsh-context-indicators.ts +17 -0
  627. package/src/services/xcsh-context-segment.ts +39 -0
  628. package/src/services/xcsh-context.ts +1410 -0
  629. package/src/services/xcsh-env.ts +107 -0
  630. package/src/services/xcsh-knowledge.ts +177 -0
  631. package/src/services/xcsh-table.ts +164 -0
  632. package/src/session/agent-session.ts +6690 -0
  633. package/src/session/agent-storage.ts +451 -0
  634. package/src/session/artifacts.ts +132 -0
  635. package/src/session/auth-storage.ts +17 -0
  636. package/src/session/blob-store.ts +135 -0
  637. package/src/session/compaction/branch-summarization.ts +322 -0
  638. package/src/session/compaction/compaction.ts +1388 -0
  639. package/src/session/compaction/index.ts +7 -0
  640. package/src/session/compaction/pruning.ts +91 -0
  641. package/src/session/compaction/utils.ts +184 -0
  642. package/src/session/history-storage.ts +253 -0
  643. package/src/session/messages.ts +597 -0
  644. package/src/session/session-dump-format.ts +203 -0
  645. package/src/session/session-manager.ts +2816 -0
  646. package/src/session/session-storage.ts +370 -0
  647. package/src/session/streaming-output.ts +770 -0
  648. package/src/session/tool-choice-queue.ts +213 -0
  649. package/src/slash-commands/builtin-registry.ts +1614 -0
  650. package/src/slash-commands/export-command.ts +178 -0
  651. package/src/slash-commands/marketplace-install-parser.ts +99 -0
  652. package/src/slash-commands/resource-commands.ts +250 -0
  653. package/src/ssh/config-writer.ts +183 -0
  654. package/src/ssh/connection-manager.ts +445 -0
  655. package/src/ssh/ssh-executor.ts +131 -0
  656. package/src/ssh/sshfs-mount.ts +137 -0
  657. package/src/ssh/utils.ts +8 -0
  658. package/src/stt/downloader.ts +71 -0
  659. package/src/stt/index.ts +3 -0
  660. package/src/stt/recorder.ts +351 -0
  661. package/src/stt/setup.ts +52 -0
  662. package/src/stt/stt-controller.ts +160 -0
  663. package/src/stt/transcribe.py +70 -0
  664. package/src/stt/transcriber.ts +91 -0
  665. package/src/system-prompt.ts +688 -0
  666. package/src/task/agents.ts +166 -0
  667. package/src/task/commands.ts +131 -0
  668. package/src/task/discovery.ts +126 -0
  669. package/src/task/executor.ts +1324 -0
  670. package/src/task/index.ts +1197 -0
  671. package/src/task/isolation-backend.ts +72 -0
  672. package/src/task/name-generator.ts +1577 -0
  673. package/src/task/output-manager.ts +107 -0
  674. package/src/task/parallel.ts +116 -0
  675. package/src/task/render.ts +1035 -0
  676. package/src/task/subprocess-tool-registry.ts +88 -0
  677. package/src/task/template.ts +33 -0
  678. package/src/task/types.ts +230 -0
  679. package/src/task/worktree.ts +580 -0
  680. package/src/task/xcsh-command.ts +26 -0
  681. package/src/thinking.ts +87 -0
  682. package/src/tools/action-renderer.ts +198 -0
  683. package/src/tools/archive-reader.ts +315 -0
  684. package/src/tools/ask.ts +791 -0
  685. package/src/tools/ast-edit.ts +492 -0
  686. package/src/tools/ast-grep.ts +453 -0
  687. package/src/tools/auto-generated-guard.ts +305 -0
  688. package/src/tools/bash-interactive.ts +383 -0
  689. package/src/tools/bash-interceptor.ts +67 -0
  690. package/src/tools/bash-normalize.ts +107 -0
  691. package/src/tools/bash-skill-urls.ts +246 -0
  692. package/src/tools/bash.ts +878 -0
  693. package/src/tools/browser-renderer.ts +129 -0
  694. package/src/tools/browser.ts +1366 -0
  695. package/src/tools/calculator.ts +543 -0
  696. package/src/tools/cancel-job.ts +100 -0
  697. package/src/tools/catalog-workflow-runner.ts +718 -0
  698. package/src/tools/checkpoint.ts +133 -0
  699. package/src/tools/context.ts +39 -0
  700. package/src/tools/debug.ts +1004 -0
  701. package/src/tools/display-image-renderer.ts +96 -0
  702. package/src/tools/display-image.ts +109 -0
  703. package/src/tools/exit-plan-mode.ts +101 -0
  704. package/src/tools/fetch.ts +1462 -0
  705. package/src/tools/find.ts +437 -0
  706. package/src/tools/fs-cache-invalidation.ts +33 -0
  707. package/src/tools/gemini-image.ts +1004 -0
  708. package/src/tools/grep.ts +705 -0
  709. package/src/tools/index.ts +442 -0
  710. package/src/tools/inspect-image-renderer.ts +101 -0
  711. package/src/tools/inspect-image.ts +176 -0
  712. package/src/tools/json-tree.ts +266 -0
  713. package/src/tools/jtd-to-json-schema.ts +199 -0
  714. package/src/tools/jtd-to-typescript.ts +136 -0
  715. package/src/tools/jtd-utils.ts +102 -0
  716. package/src/tools/list-limit.ts +40 -0
  717. package/src/tools/mermaid-renderer.ts +127 -0
  718. package/src/tools/notebook.ts +288 -0
  719. package/src/tools/output-meta.ts +588 -0
  720. package/src/tools/path-utils.ts +560 -0
  721. package/src/tools/plan-mode-guard.ts +41 -0
  722. package/src/tools/poll-tool.ts +177 -0
  723. package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
  724. package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
  725. package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
  726. package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
  727. package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
  728. package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
  729. package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
  730. package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
  731. package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
  732. package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
  733. package/src/tools/puppeteer/10_stealth_plugins.txt +206 -0
  734. package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
  735. package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
  736. package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
  737. package/src/tools/python.ts +1184 -0
  738. package/src/tools/read.ts +1591 -0
  739. package/src/tools/render-mermaid.ts +93 -0
  740. package/src/tools/render-utils.ts +677 -0
  741. package/src/tools/renderers.ts +82 -0
  742. package/src/tools/report-tool-issue.ts +80 -0
  743. package/src/tools/resolve.ts +196 -0
  744. package/src/tools/review.ts +238 -0
  745. package/src/tools/search-tool-bm25.ts +288 -0
  746. package/src/tools/sqlite-reader.ts +623 -0
  747. package/src/tools/ssh.ts +322 -0
  748. package/src/tools/submit-result.ts +229 -0
  749. package/src/tools/todo-render.ts +33 -0
  750. package/src/tools/todo-write.ts +440 -0
  751. package/src/tools/tool-errors.ts +62 -0
  752. package/src/tools/tool-result.ts +93 -0
  753. package/src/tools/tool-timeouts.ts +30 -0
  754. package/src/tools/vim.ts +981 -0
  755. package/src/tools/write.ts +671 -0
  756. package/src/tools/xcsh-api-renderer.ts +402 -0
  757. package/src/tools/xcsh-api.ts +887 -0
  758. package/src/tui/code-cell.ts +101 -0
  759. package/src/tui/file-list.ts +47 -0
  760. package/src/tui/index.ts +11 -0
  761. package/src/tui/output-block.ts +175 -0
  762. package/src/tui/status-line.ts +39 -0
  763. package/src/tui/tree-list.ts +84 -0
  764. package/src/tui/types.ts +15 -0
  765. package/src/tui/utils.ts +103 -0
  766. package/src/utils/changelog.ts +98 -0
  767. package/src/utils/clipboard.ts +80 -0
  768. package/src/utils/command-args.ts +76 -0
  769. package/src/utils/commit-message-generator.ts +137 -0
  770. package/src/utils/edit-mode.ts +50 -0
  771. package/src/utils/event-bus.ts +33 -0
  772. package/src/utils/external-editor.ts +65 -0
  773. package/src/utils/file-display-mode.ts +37 -0
  774. package/src/utils/file-mentions.ts +376 -0
  775. package/src/utils/fuzzy.ts +108 -0
  776. package/src/utils/git.ts +1495 -0
  777. package/src/utils/gitstatus.ts +140 -0
  778. package/src/utils/image-convert.ts +27 -0
  779. package/src/utils/image-loading.ts +98 -0
  780. package/src/utils/image-passthrough.ts +118 -0
  781. package/src/utils/image-resize.ts +272 -0
  782. package/src/utils/image-viewer.ts +17 -0
  783. package/src/utils/lang-from-path.ts +239 -0
  784. package/src/utils/markit.ts +81 -0
  785. package/src/utils/open.ts +20 -0
  786. package/src/utils/session-color.ts +55 -0
  787. package/src/utils/shell-snapshot.ts +188 -0
  788. package/src/utils/title-generator.ts +193 -0
  789. package/src/utils/tool-choice.ts +28 -0
  790. package/src/utils/tools-manager.ts +356 -0
  791. package/src/vim/buffer.ts +309 -0
  792. package/src/vim/commands.ts +382 -0
  793. package/src/vim/engine.ts +2426 -0
  794. package/src/vim/parser.ts +151 -0
  795. package/src/vim/render.ts +252 -0
  796. package/src/vim/types.ts +197 -0
  797. package/src/web/kagi.ts +174 -0
  798. package/src/web/parallel.ts +346 -0
  799. package/src/web/scrapers/artifacthub.ts +207 -0
  800. package/src/web/scrapers/arxiv.ts +83 -0
  801. package/src/web/scrapers/aur.ts +162 -0
  802. package/src/web/scrapers/biorxiv.ts +133 -0
  803. package/src/web/scrapers/bluesky.ts +262 -0
  804. package/src/web/scrapers/brew.ts +172 -0
  805. package/src/web/scrapers/cheatsh.ts +68 -0
  806. package/src/web/scrapers/chocolatey.ts +196 -0
  807. package/src/web/scrapers/choosealicense.ts +95 -0
  808. package/src/web/scrapers/cisa-kev.ts +87 -0
  809. package/src/web/scrapers/clojars.ts +154 -0
  810. package/src/web/scrapers/coingecko.ts +177 -0
  811. package/src/web/scrapers/crates-io.ts +110 -0
  812. package/src/web/scrapers/crossref.ts +136 -0
  813. package/src/web/scrapers/devto.ts +147 -0
  814. package/src/web/scrapers/discogs.ts +306 -0
  815. package/src/web/scrapers/discourse.ts +197 -0
  816. package/src/web/scrapers/dockerhub.ts +138 -0
  817. package/src/web/scrapers/docs-rs.ts +653 -0
  818. package/src/web/scrapers/fdroid.ts +134 -0
  819. package/src/web/scrapers/firefox-addons.ts +191 -0
  820. package/src/web/scrapers/flathub.ts +223 -0
  821. package/src/web/scrapers/github-gist.ts +58 -0
  822. package/src/web/scrapers/github.ts +452 -0
  823. package/src/web/scrapers/gitlab.ts +401 -0
  824. package/src/web/scrapers/go-pkg.ts +266 -0
  825. package/src/web/scrapers/hackage.ts +140 -0
  826. package/src/web/scrapers/hackernews.ts +189 -0
  827. package/src/web/scrapers/hex.ts +105 -0
  828. package/src/web/scrapers/huggingface.ts +321 -0
  829. package/src/web/scrapers/iacr.ts +89 -0
  830. package/src/web/scrapers/index.ts +252 -0
  831. package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
  832. package/src/web/scrapers/lemmy.ts +203 -0
  833. package/src/web/scrapers/lobsters.ts +175 -0
  834. package/src/web/scrapers/mastodon.ts +292 -0
  835. package/src/web/scrapers/maven.ts +138 -0
  836. package/src/web/scrapers/mdn.ts +169 -0
  837. package/src/web/scrapers/metacpan.ts +222 -0
  838. package/src/web/scrapers/musicbrainz.ts +250 -0
  839. package/src/web/scrapers/npm.ts +98 -0
  840. package/src/web/scrapers/nuget.ts +183 -0
  841. package/src/web/scrapers/nvd.ts +222 -0
  842. package/src/web/scrapers/ollama.ts +239 -0
  843. package/src/web/scrapers/open-vsx.ts +106 -0
  844. package/src/web/scrapers/opencorporates.ts +292 -0
  845. package/src/web/scrapers/openlibrary.ts +336 -0
  846. package/src/web/scrapers/orcid.ts +286 -0
  847. package/src/web/scrapers/osv.ts +176 -0
  848. package/src/web/scrapers/packagist.ts +160 -0
  849. package/src/web/scrapers/pub-dev.ts +143 -0
  850. package/src/web/scrapers/pubmed.ts +211 -0
  851. package/src/web/scrapers/pypi.ts +112 -0
  852. package/src/web/scrapers/rawg.ts +110 -0
  853. package/src/web/scrapers/readthedocs.ts +120 -0
  854. package/src/web/scrapers/reddit.ts +95 -0
  855. package/src/web/scrapers/repology.ts +251 -0
  856. package/src/web/scrapers/rfc.ts +201 -0
  857. package/src/web/scrapers/rubygems.ts +103 -0
  858. package/src/web/scrapers/searchcode.ts +190 -0
  859. package/src/web/scrapers/sec-edgar.ts +261 -0
  860. package/src/web/scrapers/semantic-scholar.ts +171 -0
  861. package/src/web/scrapers/snapcraft.ts +187 -0
  862. package/src/web/scrapers/sourcegraph.ts +336 -0
  863. package/src/web/scrapers/spdx.ts +108 -0
  864. package/src/web/scrapers/spotify.ts +198 -0
  865. package/src/web/scrapers/stackoverflow.ts +120 -0
  866. package/src/web/scrapers/terraform.ts +277 -0
  867. package/src/web/scrapers/tldr.ts +47 -0
  868. package/src/web/scrapers/twitter.ts +93 -0
  869. package/src/web/scrapers/types.ts +267 -0
  870. package/src/web/scrapers/utils.ts +109 -0
  871. package/src/web/scrapers/vimeo.ts +133 -0
  872. package/src/web/scrapers/vscode-marketplace.ts +187 -0
  873. package/src/web/scrapers/w3c.ts +156 -0
  874. package/src/web/scrapers/wikidata.ts +344 -0
  875. package/src/web/scrapers/wikipedia.ts +84 -0
  876. package/src/web/scrapers/youtube.ts +313 -0
  877. package/src/web/search/errors.ts +120 -0
  878. package/src/web/search/index.ts +346 -0
  879. package/src/web/search/params.ts +129 -0
  880. package/src/web/search/provider.ts +97 -0
  881. package/src/web/search/providers/anthropic.ts +339 -0
  882. package/src/web/search/providers/base.ts +35 -0
  883. package/src/web/search/providers/brave.ts +150 -0
  884. package/src/web/search/providers/codex.ts +402 -0
  885. package/src/web/search/providers/exa.ts +264 -0
  886. package/src/web/search/providers/firecrawl.ts +115 -0
  887. package/src/web/search/providers/gemini.ts +566 -0
  888. package/src/web/search/providers/jina.ts +99 -0
  889. package/src/web/search/providers/kagi.ts +70 -0
  890. package/src/web/search/providers/kimi.ts +157 -0
  891. package/src/web/search/providers/parallel.ts +63 -0
  892. package/src/web/search/providers/perplexity.ts +546 -0
  893. package/src/web/search/providers/synthetic.ts +113 -0
  894. package/src/web/search/providers/tavily.ts +162 -0
  895. package/src/web/search/providers/utils.ts +36 -0
  896. package/src/web/search/providers/zai.ts +311 -0
  897. package/src/web/search/render.ts +348 -0
  898. package/src/web/search/types.ts +438 -0
  899. package/src/web/search/utils.ts +17 -0
@@ -0,0 +1,1366 @@
1
+ /**
2
+ * Hashline edit mode — a line-addressable edit format using text hashes.
3
+ *
4
+ * Each line in a file is identified by its 1-indexed line number and a short
5
+ * hexadecimal hash derived from the normalized line text (xxHash32, truncated to 2
6
+ * hex chars).
7
+ * The combined `LINE#ID` reference acts as both an address and a staleness check:
8
+ * if the file has changed since the caller last read it, hash mismatches are caught
9
+ * before any mutation occurs.
10
+ *
11
+ * Displayed format: `LINENUM#HASH:TEXT`
12
+ * Reference format: `"LINENUM#HASH"` (e.g. `"5#aa"`)
13
+ */
14
+
15
+ import * as fs from "node:fs/promises";
16
+ import * as nodePath from "node:path";
17
+ import type { AgentToolResult } from "@f5xc-salesdemos/pi-agent-core";
18
+ import { isEnoent } from "@f5xc-salesdemos/pi-utils";
19
+ import { type Static, Type } from "@sinclair/typebox";
20
+ import type { BunFile } from "bun";
21
+ import type { WritethroughCallback, WritethroughDeferredHandle } from "../../lsp";
22
+ import type { ToolSession } from "../../tools";
23
+ import { assertEditableFileContent } from "../../tools/auto-generated-guard";
24
+ import {
25
+ invalidateFsScanAfterDelete,
26
+ invalidateFsScanAfterRename,
27
+ invalidateFsScanAfterWrite,
28
+ } from "../../tools/fs-cache-invalidation";
29
+ import { outputMeta } from "../../tools/output-meta";
30
+ import { resolveToCwd } from "../../tools/path-utils";
31
+ import { enforcePlanModeWrite, resolvePlanPath } from "../../tools/plan-mode-guard";
32
+ import { generateDiffString } from "../diff";
33
+ import { computeLineHash, formatLineHash } from "../line-hash";
34
+ import { detectLineEnding, normalizeToLF, restoreLineEndings, stripBom } from "../normalize";
35
+ import type { EditToolDetails, LspBatchRequest } from "../renderer";
36
+
37
+ export interface HashMismatch {
38
+ line: number;
39
+ expected: string;
40
+ actual: string;
41
+ }
42
+
43
+ export type Anchor = { line: number; hash: string };
44
+ export type HashlineEdit =
45
+ | { op: "replace_line"; pos: Anchor; lines: string[] }
46
+ | { op: "replace_range"; pos: Anchor; end: Anchor; lines: string[] }
47
+ | { op: "append_at"; pos: Anchor; lines: string[] }
48
+ | { op: "prepend_at"; pos: Anchor; lines: string[] }
49
+ | { op: "append_file"; lines: string[] }
50
+ | { op: "prepend_file"; lines: string[] };
51
+
52
+ const HASHLINE_PREFIX_RE = /^\s*(?:>>>|>>)?\s*(?:\+?\s*(?:\d+\s*#\s*|#\s*)|\+)\s*[ZPMQVRWSNKTXJBYH]{2}:/;
53
+ const HASHLINE_PREFIX_PLUS_RE = /^\s*(?:>>>|>>)?\s*\+\s*(?:\d+\s*#\s*|#\s*)?[ZPMQVRWSNKTXJBYH]{2}:/;
54
+ const DIFF_PLUS_RE = /^[+](?![+])/;
55
+
56
+ type LinePrefixStats = {
57
+ nonEmpty: number;
58
+ hashPrefixCount: number;
59
+ diffPlusHashPrefixCount: number;
60
+ diffPlusCount: number;
61
+ };
62
+
63
+ function collectLinePrefixStats(lines: string[]): LinePrefixStats {
64
+ const stats: LinePrefixStats = {
65
+ nonEmpty: 0,
66
+ hashPrefixCount: 0,
67
+ diffPlusHashPrefixCount: 0,
68
+ diffPlusCount: 0,
69
+ };
70
+
71
+ for (const line of lines) {
72
+ if (line.length === 0) continue;
73
+ stats.nonEmpty++;
74
+ if (HASHLINE_PREFIX_RE.test(line)) stats.hashPrefixCount++;
75
+ if (HASHLINE_PREFIX_PLUS_RE.test(line)) stats.diffPlusHashPrefixCount++;
76
+ if (DIFF_PLUS_RE.test(line)) stats.diffPlusCount++;
77
+ }
78
+
79
+ return stats;
80
+ }
81
+
82
+ export function stripNewLinePrefixes(lines: string[]): string[] {
83
+ const { nonEmpty, hashPrefixCount, diffPlusHashPrefixCount, diffPlusCount } = collectLinePrefixStats(lines);
84
+ if (nonEmpty === 0) return lines;
85
+
86
+ const stripHash = hashPrefixCount > 0 && hashPrefixCount === nonEmpty;
87
+ const stripPlus =
88
+ !stripHash && diffPlusHashPrefixCount === 0 && diffPlusCount > 0 && diffPlusCount >= nonEmpty * 0.5;
89
+ if (!stripHash && !stripPlus && diffPlusHashPrefixCount === 0) return lines;
90
+
91
+ return lines.map(line => {
92
+ if (stripHash) return line.replace(HASHLINE_PREFIX_RE, "");
93
+ if (stripPlus) return line.replace(DIFF_PLUS_RE, "");
94
+ if (diffPlusHashPrefixCount > 0 && HASHLINE_PREFIX_PLUS_RE.test(line)) {
95
+ return line.replace(HASHLINE_PREFIX_RE, "");
96
+ }
97
+ return line;
98
+ });
99
+ }
100
+
101
+ export function stripHashlinePrefixes(lines: string[]): string[] {
102
+ const { nonEmpty, hashPrefixCount } = collectLinePrefixStats(lines);
103
+ if (nonEmpty === 0 || hashPrefixCount !== nonEmpty) return lines;
104
+ return lines.map(line => line.replace(HASHLINE_PREFIX_RE, ""));
105
+ }
106
+
107
+ const linesSchema = Type.Union([
108
+ Type.Array(Type.String(), { description: "content (preferred format)" }),
109
+ Type.String(),
110
+ Type.Null(),
111
+ ]);
112
+
113
+ const locSchema = Type.Union(
114
+ [
115
+ Type.Literal("append"),
116
+ Type.Literal("prepend"),
117
+ Type.Object({ append: Type.String({ description: "anchor" }) }),
118
+ Type.Object({ prepend: Type.String({ description: "anchor" }) }),
119
+ Type.Object({
120
+ range: Type.Object({
121
+ pos: Type.String({ description: "first line to edit (inclusive)" }),
122
+ end: Type.String({ description: "last line to edit (inclusive)" }),
123
+ }),
124
+ }),
125
+ ],
126
+ { description: "insert location" },
127
+ );
128
+
129
+ export const hashlineEditSchema = Type.Object(
130
+ {
131
+ path: Type.String({ description: "File path" }),
132
+ loc: Type.Optional(locSchema),
133
+ content: Type.Optional(linesSchema),
134
+ delete: Type.Optional(Type.Boolean({ description: "Delete the file" })),
135
+ move: Type.Optional(Type.String({ description: "Move/rename the file to this path" })),
136
+ },
137
+ { additionalProperties: false },
138
+ );
139
+
140
+ export const hashlineEditParamsSchema = Type.Object(
141
+ {
142
+ edits: Type.Array(hashlineEditSchema, { description: "edits" }),
143
+ },
144
+ { additionalProperties: false },
145
+ );
146
+
147
+ export type HashlineToolEdit = Static<typeof hashlineEditSchema>;
148
+ export type HashlineParams = Static<typeof hashlineEditParamsSchema>;
149
+
150
+ export interface ExecuteHashlineSingleOptions {
151
+ session: ToolSession;
152
+ path: string;
153
+ edits: HashlineToolEdit[];
154
+ signal?: AbortSignal;
155
+ batchRequest?: LspBatchRequest;
156
+ writethrough: WritethroughCallback;
157
+ beginDeferredDiagnosticsForPath: (path: string) => WritethroughDeferredHandle;
158
+ }
159
+
160
+ export function hashlineParseText(edit: string[] | string | null | undefined): string[] {
161
+ if (edit == null) return [];
162
+ if (typeof edit === "string") {
163
+ const normalizedEdit = edit.endsWith("\n") ? edit.slice(0, -1) : edit;
164
+ edit = normalizedEdit.replaceAll("\r", "").split("\n");
165
+ }
166
+ return stripNewLinePrefixes(edit);
167
+ }
168
+
169
+ export function isHashlineParams(params: unknown): params is HashlineParams {
170
+ if (typeof params !== "object" || params === null || !("edits" in params) || !Array.isArray(params.edits))
171
+ return false;
172
+ if (params.edits.length === 0) return true;
173
+ const first = params.edits[0];
174
+ if (typeof first !== "object" || first === null) return false;
175
+ return "loc" in first || "delete" in first || "move" in first;
176
+ }
177
+
178
+ function resolveEditAnchors(edits: HashlineToolEdit[]): HashlineEdit[] {
179
+ return edits.map(resolveEditAnchor);
180
+ }
181
+
182
+ type HashlineEditInput = HashlineToolEdit | HashlineEdit;
183
+
184
+ function resolveHashlineEditsForDiff(edits: HashlineEditInput[]): HashlineEdit[] {
185
+ return edits.map((edit, editIndex) => {
186
+ if (!edit || typeof edit !== "object") {
187
+ throw new Error(`Invalid hashline edit at index ${editIndex}: expected object.`);
188
+ }
189
+
190
+ if ("op" in edit) {
191
+ return edit;
192
+ }
193
+
194
+ if ("loc" in edit) {
195
+ return resolveEditAnchor(edit);
196
+ }
197
+
198
+ throw new Error(`Invalid hashline edit at index ${editIndex}: expected op/loc payload.`);
199
+ });
200
+ }
201
+
202
+ function tryParseTag(raw: string): Anchor | undefined {
203
+ try {
204
+ return parseTag(raw);
205
+ } catch {
206
+ return undefined;
207
+ }
208
+ }
209
+
210
+ function requireParsedAnchor(raw: string, op: "append" | "prepend"): Anchor {
211
+ const anchor = tryParseTag(raw);
212
+ if (!anchor) throw new Error(`${op} requires a valid anchor.`);
213
+ return anchor;
214
+ }
215
+
216
+ function requireParsedRange(range: { pos: string; end: string }): { pos: Anchor; end: Anchor } {
217
+ const pos = tryParseTag(range.pos);
218
+ const end = tryParseTag(range.end);
219
+ if (!pos || !end) throw new Error("range requires valid pos and end anchors.");
220
+ return { pos, end };
221
+ }
222
+
223
+ function resolveEditAnchor(edit: HashlineToolEdit): HashlineEdit {
224
+ const lines = hashlineParseText(edit.content);
225
+ const loc = edit.loc;
226
+
227
+ if (loc === "append") {
228
+ return { op: "append_file", lines };
229
+ }
230
+
231
+ if (loc === "prepend") {
232
+ return { op: "prepend_file", lines };
233
+ }
234
+
235
+ if (typeof loc !== "object") {
236
+ throw new Error(`Invalid loc value: ${JSON.stringify(loc)}`);
237
+ }
238
+
239
+ if ("append" in loc) {
240
+ return { op: "append_at", pos: requireParsedAnchor(loc.append, "append"), lines };
241
+ }
242
+
243
+ if ("prepend" in loc) {
244
+ return { op: "prepend_at", pos: requireParsedAnchor(loc.prepend, "prepend"), lines };
245
+ }
246
+
247
+ if ("range" in loc) {
248
+ const { pos, end } = requireParsedRange(loc.range);
249
+ return { op: "replace_range", pos, end, lines };
250
+ }
251
+
252
+ throw new Error("Unknown loc shape. Expected append, prepend, or range.");
253
+ }
254
+
255
+ // ═══════════════════════════════════════════════════════════════════════════
256
+ // Hashline streaming formatter
257
+ // ═══════════════════════════════════════════════════════════════════════════
258
+
259
+ export interface HashlineStreamOptions {
260
+ /** First line number to use when formatting (1-indexed). */
261
+ startLine?: number;
262
+ /** Maximum formatted lines per yielded chunk (default: 200). */
263
+ maxChunkLines?: number;
264
+ /** Maximum UTF-8 bytes per yielded chunk (default: 64 KiB). */
265
+ maxChunkBytes?: number;
266
+ }
267
+
268
+ interface ResolvedHashlineStreamOptions {
269
+ startLine: number;
270
+ maxChunkLines: number;
271
+ maxChunkBytes: number;
272
+ }
273
+
274
+ type HashlineLineFormatter = (lineNumber: number, line: string) => string;
275
+
276
+ interface HashlineChunkEmitter {
277
+ pushLine: (line: string) => string[];
278
+ flush: () => string | undefined;
279
+ }
280
+
281
+ function resolveHashlineStreamOptions(options: HashlineStreamOptions): ResolvedHashlineStreamOptions {
282
+ return {
283
+ startLine: options.startLine ?? 1,
284
+ maxChunkLines: options.maxChunkLines ?? 200,
285
+ maxChunkBytes: options.maxChunkBytes ?? 64 * 1024,
286
+ };
287
+ }
288
+
289
+ function createHashlineChunkEmitter(
290
+ options: ResolvedHashlineStreamOptions,
291
+ formatLine: HashlineLineFormatter,
292
+ ): HashlineChunkEmitter {
293
+ let lineNumber = options.startLine;
294
+ let outLines: string[] = [];
295
+ let outBytes = 0;
296
+
297
+ const flush = (): string | undefined => {
298
+ if (outLines.length === 0) return undefined;
299
+ const chunk = outLines.join("\n");
300
+ outLines = [];
301
+ outBytes = 0;
302
+ return chunk;
303
+ };
304
+
305
+ const pushLine = (line: string): string[] => {
306
+ const formatted = formatLine(lineNumber, line);
307
+ lineNumber++;
308
+
309
+ const chunksToYield: string[] = [];
310
+ const sepBytes = outLines.length === 0 ? 0 : 1;
311
+ const lineBytes = Buffer.byteLength(formatted, "utf-8");
312
+
313
+ if (
314
+ outLines.length > 0 &&
315
+ (outLines.length >= options.maxChunkLines || outBytes + sepBytes + lineBytes > options.maxChunkBytes)
316
+ ) {
317
+ const flushed = flush();
318
+ if (flushed) chunksToYield.push(flushed);
319
+ }
320
+
321
+ outLines.push(formatted);
322
+ outBytes += (outLines.length === 1 ? 0 : 1) + lineBytes;
323
+
324
+ if (outLines.length >= options.maxChunkLines || outBytes >= options.maxChunkBytes) {
325
+ const flushed = flush();
326
+ if (flushed) chunksToYield.push(flushed);
327
+ }
328
+
329
+ return chunksToYield;
330
+ };
331
+
332
+ return { pushLine, flush };
333
+ }
334
+
335
+ function formatHashlineStreamLine(lineNumber: number, line: string): string {
336
+ return `${formatLineHash(lineNumber, line)}:${line}`;
337
+ }
338
+
339
+ function isReadableStream(value: unknown): value is ReadableStream<Uint8Array> {
340
+ return (
341
+ typeof value === "object" &&
342
+ value !== null &&
343
+ "getReader" in value &&
344
+ typeof (value as { getReader?: unknown }).getReader === "function"
345
+ );
346
+ }
347
+
348
+ async function* bytesFromReadableStream(stream: ReadableStream<Uint8Array>): AsyncGenerator<Uint8Array> {
349
+ const reader = stream.getReader();
350
+ try {
351
+ while (true) {
352
+ const { done, value } = await reader.read();
353
+ if (done) return;
354
+ if (value) yield value;
355
+ }
356
+ } finally {
357
+ reader.releaseLock();
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Stream hashline-formatted output from a UTF-8 byte source.
363
+ *
364
+ * This is intended for large files where callers want incremental output
365
+ * (e.g. while reading from a file handle) rather than allocating a single
366
+ * large string.
367
+ */
368
+ export async function* streamHashLinesFromUtf8(
369
+ source: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>,
370
+ options: HashlineStreamOptions = {},
371
+ ): AsyncGenerator<string> {
372
+ const resolvedOptions = resolveHashlineStreamOptions(options);
373
+ const decoder = new TextDecoder("utf-8");
374
+ const chunks = isReadableStream(source) ? bytesFromReadableStream(source) : source;
375
+ let pending = "";
376
+ let sawAnyText = false;
377
+ let endedWithNewline = false;
378
+ const emitter = createHashlineChunkEmitter(resolvedOptions, formatHashlineStreamLine);
379
+
380
+ const consumeText = (text: string): string[] => {
381
+ if (text.length === 0) return [];
382
+ sawAnyText = true;
383
+ pending += text;
384
+ const chunksToYield: string[] = [];
385
+ while (true) {
386
+ const idx = pending.indexOf("\n");
387
+ if (idx === -1) break;
388
+ const line = pending.slice(0, idx);
389
+ pending = pending.slice(idx + 1);
390
+ endedWithNewline = true;
391
+ chunksToYield.push(...emitter.pushLine(line));
392
+ }
393
+ if (pending.length > 0) endedWithNewline = false;
394
+ return chunksToYield;
395
+ };
396
+ for await (const chunk of chunks) {
397
+ for (const out of consumeText(decoder.decode(chunk, { stream: true }))) {
398
+ yield out;
399
+ }
400
+ }
401
+
402
+ for (const out of consumeText(decoder.decode())) {
403
+ yield out;
404
+ }
405
+ if (!sawAnyText) {
406
+ // Mirror `"".split("\n")` behavior: one empty line.
407
+ for (const out of emitter.pushLine("")) {
408
+ yield out;
409
+ }
410
+ } else if (pending.length > 0 || endedWithNewline) {
411
+ // Emit the final line (may be empty if the file ended with a newline).
412
+ for (const out of emitter.pushLine(pending)) {
413
+ yield out;
414
+ }
415
+ }
416
+
417
+ const last = emitter.flush();
418
+ if (last) yield last;
419
+ }
420
+
421
+ /**
422
+ * Stream hashline-formatted output from an (async) iterable of lines.
423
+ *
424
+ * Each yielded chunk is a `\n`-joined string of one or more formatted lines.
425
+ */
426
+ export async function* streamHashLinesFromLines(
427
+ lines: Iterable<string> | AsyncIterable<string>,
428
+ options: HashlineStreamOptions = {},
429
+ ): AsyncGenerator<string> {
430
+ const resolvedOptions = resolveHashlineStreamOptions(options);
431
+ const emitter = createHashlineChunkEmitter(resolvedOptions, formatHashlineStreamLine);
432
+ let sawAnyLine = false;
433
+
434
+ const asyncIterator = (lines as AsyncIterable<string>)[Symbol.asyncIterator];
435
+ if (typeof asyncIterator === "function") {
436
+ for await (const line of lines as AsyncIterable<string>) {
437
+ sawAnyLine = true;
438
+ for (const out of emitter.pushLine(line)) {
439
+ yield out;
440
+ }
441
+ }
442
+ } else {
443
+ for (const line of lines as Iterable<string>) {
444
+ sawAnyLine = true;
445
+ for (const out of emitter.pushLine(line)) {
446
+ yield out;
447
+ }
448
+ }
449
+ }
450
+ if (!sawAnyLine) {
451
+ // Mirror `"".split("\n")` behavior: one empty line.
452
+ for (const out of emitter.pushLine("")) {
453
+ yield out;
454
+ }
455
+ }
456
+
457
+ const last = emitter.flush();
458
+ if (last) yield last;
459
+ }
460
+
461
+ /**
462
+ * Parse a line reference string like `"5#abcd"` into structured form.
463
+ *
464
+ * @throws Error if the format is invalid (not `NUMBER#HEXHASH`)
465
+ */
466
+ export function parseTag(ref: string): { line: number; hash: string } {
467
+ // This regex captures:
468
+ // 1. optional leading ">+" and whitespace
469
+ // 2. line number (1+ digits)
470
+ // 3. "#" with optional surrounding spaces
471
+ // 4. hash (2 hex chars)
472
+ // 5. optional trailing display suffix (":..." or " ...")
473
+ const match = ref.match(/^\s*[>+-]*\s*(\d+)\s*#\s*([ZPMQVRWSNKTXJBYH]{2})/);
474
+ if (!match) {
475
+ throw new Error(`Invalid line reference "${ref}". Expected format "LINE#ID" (e.g. "5#aa").`);
476
+ }
477
+ const line = Number.parseInt(match[1], 10);
478
+ if (line < 1) {
479
+ throw new Error(`Line number must be >= 1, got ${line} in "${ref}".`);
480
+ }
481
+ return { line, hash: match[2] };
482
+ }
483
+
484
+ // ═══════════════════════════════════════════════════════════════════════════
485
+ // Hash Mismatch Error
486
+ // ═══════════════════════════════════════════════════════════════════════════
487
+
488
+ /** Number of context lines shown above/below each mismatched line */
489
+ const MISMATCH_CONTEXT = 2;
490
+
491
+ /**
492
+ * Error thrown when one or more hashline references have stale hashes.
493
+ *
494
+ * Displays grep-style output with `>>>` markers on mismatched lines,
495
+ * showing the correct `LINE#ID` so the caller can fix all refs at once.
496
+ */
497
+ export class HashlineMismatchError extends Error {
498
+ readonly remaps: ReadonlyMap<string, string>;
499
+ constructor(
500
+ public readonly mismatches: HashMismatch[],
501
+ public readonly fileLines: string[],
502
+ ) {
503
+ super(HashlineMismatchError.formatMessage(mismatches, fileLines));
504
+ this.name = "HashlineMismatchError";
505
+ const remaps = new Map<string, string>();
506
+ for (const m of mismatches) {
507
+ const actual = computeLineHash(m.line, fileLines[m.line - 1]);
508
+ remaps.set(`${m.line}#${m.expected}`, `${m.line}#${actual}`);
509
+ }
510
+ this.remaps = remaps;
511
+ }
512
+
513
+ static formatMessage(mismatches: HashMismatch[], fileLines: string[]): string {
514
+ const mismatchSet = new Map<number, HashMismatch>();
515
+ for (const m of mismatches) {
516
+ mismatchSet.set(m.line, m);
517
+ }
518
+
519
+ // Collect line ranges to display (mismatch lines + context)
520
+ const displayLines = new Set<number>();
521
+ for (const m of mismatches) {
522
+ const lo = Math.max(1, m.line - MISMATCH_CONTEXT);
523
+ const hi = Math.min(fileLines.length, m.line + MISMATCH_CONTEXT);
524
+ for (let i = lo; i <= hi; i++) {
525
+ displayLines.add(i);
526
+ }
527
+ }
528
+
529
+ const sorted = [...displayLines].sort((a, b) => a - b);
530
+ const lines: string[] = [];
531
+
532
+ lines.push(
533
+ `${mismatches.length} line${mismatches.length > 1 ? "s have" : " has"} changed since last read. Use the updated LINE#ID references shown below (>>> marks changed lines).`,
534
+ );
535
+ lines.push("");
536
+
537
+ let prevLine = -1;
538
+ for (const lineNum of sorted) {
539
+ // Gap separator between non-contiguous regions
540
+ if (prevLine !== -1 && lineNum > prevLine + 1) {
541
+ lines.push(" ...");
542
+ }
543
+ prevLine = lineNum;
544
+
545
+ const text = fileLines[lineNum - 1];
546
+ const hash = computeLineHash(lineNum, text);
547
+ const prefix = `${lineNum}#${hash}`;
548
+
549
+ if (mismatchSet.has(lineNum)) {
550
+ lines.push(`>>> ${prefix}:${text}`);
551
+ } else {
552
+ lines.push(` ${prefix}:${text}`);
553
+ }
554
+ }
555
+ return lines.join("\n");
556
+ }
557
+ }
558
+
559
+ /**
560
+ * Validate that a line reference points to an existing line with a matching hash.
561
+ *
562
+ * @param ref - Parsed line reference (1-indexed line number + expected hash)
563
+ * @param fileLines - Array of file lines (0-indexed)
564
+ * @throws HashlineMismatchError if the hash doesn't match (includes correct hashes in context)
565
+ * @throws Error if the line is out of range
566
+ */
567
+ export function validateLineRef(ref: { line: number; hash: string }, fileLines: string[]): void {
568
+ if (ref.line < 1 || ref.line > fileLines.length) {
569
+ throw new Error(`Line ${ref.line} does not exist (file has ${fileLines.length} lines)`);
570
+ }
571
+ const actualHash = computeLineHash(ref.line, fileLines[ref.line - 1]);
572
+ if (actualHash !== ref.hash) {
573
+ throw new HashlineMismatchError([{ line: ref.line, expected: ref.hash, actual: actualHash }], fileLines);
574
+ }
575
+ }
576
+
577
+ function isEscapedTabAutocorrectEnabled(): boolean {
578
+ switch (Bun.env.PI_HASHLINE_AUTOCORRECT_ESCAPED_TABS) {
579
+ case "0":
580
+ return false;
581
+ case "1":
582
+ return true;
583
+ default:
584
+ return true;
585
+ }
586
+ }
587
+
588
+ function maybeAutocorrectEscapedTabIndentation(edits: HashlineEdit[], warnings: string[]): void {
589
+ if (!isEscapedTabAutocorrectEnabled()) return;
590
+ for (const edit of edits) {
591
+ if (edit.lines.length === 0) continue;
592
+ const hasEscapedTabs = edit.lines.some(line => line.includes("\\t"));
593
+ if (!hasEscapedTabs) continue;
594
+ const hasRealTabs = edit.lines.some(line => line.includes("\t"));
595
+ if (hasRealTabs) continue;
596
+ let correctedCount = 0;
597
+ const corrected = edit.lines.map(line =>
598
+ line.replace(/^((?:\\t)+)/, escaped => {
599
+ correctedCount += escaped.length / 2;
600
+ return "\t".repeat(escaped.length / 2);
601
+ }),
602
+ );
603
+ if (correctedCount === 0) continue;
604
+ edit.lines = corrected;
605
+ warnings.push(
606
+ `Auto-corrected escaped tab indentation in edit: converted leading \\t sequence(s) to real tab characters`,
607
+ );
608
+ }
609
+ }
610
+
611
+ function maybeWarnSuspiciousUnicodeEscapePlaceholder(edits: HashlineEdit[], warnings: string[]): void {
612
+ for (const edit of edits) {
613
+ if (edit.lines.length === 0) continue;
614
+ if (!edit.lines.some(line => /\\uDDDD/i.test(line))) continue;
615
+ warnings.push(
616
+ `Detected literal \\uDDDD in edit content; no autocorrection applied. Verify whether this should be a real Unicode escape or plain text.`,
617
+ );
618
+ }
619
+ }
620
+
621
+ function runHashlinePreflightSanitizers(edits: HashlineEdit[], warnings: string[]): void {
622
+ maybeAutocorrectEscapedTabIndentation(edits, warnings);
623
+ maybeWarnSuspiciousUnicodeEscapePlaceholder(edits, warnings);
624
+ }
625
+
626
+ function ensureHashlineEditHasContent(edit: HashlineEdit): void {
627
+ if (edit.lines.length === 0) {
628
+ edit.lines = [""];
629
+ }
630
+ }
631
+
632
+ function collectBoundaryDuplicationWarning(edit: HashlineEdit, originalFileLines: string[], warnings: string[]): void {
633
+ let endLine: number;
634
+ switch (edit.op) {
635
+ case "replace_line":
636
+ endLine = edit.pos.line;
637
+ break;
638
+ case "replace_range":
639
+ endLine = edit.end.line;
640
+ break;
641
+ default:
642
+ return;
643
+ }
644
+
645
+ if (edit.lines.length === 0) return;
646
+ const nextSurvivingIdx = endLine;
647
+ if (nextSurvivingIdx >= originalFileLines.length) return;
648
+ const nextSurvivingLine = originalFileLines[nextSurvivingIdx];
649
+ const lastInsertedLine = edit.lines[edit.lines.length - 1];
650
+ const trimmedNext = nextSurvivingLine.trim();
651
+ const trimmedLast = lastInsertedLine.trim();
652
+ if (trimmedLast.length > 0 && trimmedLast === trimmedNext) {
653
+ const tag = formatLineHash(endLine + 1, nextSurvivingLine);
654
+ warnings.push(
655
+ `Possible boundary duplication: your last replacement line \`${trimmedLast}\` is identical to the next surviving line ${tag}. ` +
656
+ `If you meant to replace the entire block, set \`end\` to ${tag} instead.`,
657
+ );
658
+ }
659
+ }
660
+
661
+ function dedupeHashlineEdits(edits: HashlineEdit[]): void {
662
+ const seenEditKeys = new Map<string, number>();
663
+ const dedupIndices = new Set<number>();
664
+ for (let i = 0; i < edits.length; i++) {
665
+ const edit = edits[i];
666
+ let lineKey: string;
667
+ switch (edit.op) {
668
+ case "replace_line":
669
+ lineKey = `s:${edit.pos.line}`;
670
+ break;
671
+ case "replace_range":
672
+ lineKey = `r:${edit.pos.line}:${edit.end.line}`;
673
+ break;
674
+ case "append_at":
675
+ lineKey = `i:${edit.pos.line}`;
676
+ break;
677
+ case "prepend_at":
678
+ lineKey = `ib:${edit.pos.line}`;
679
+ break;
680
+ case "append_file":
681
+ lineKey = "ieof";
682
+ break;
683
+ case "prepend_file":
684
+ lineKey = "ibef";
685
+ break;
686
+ }
687
+ const dstKey = `${lineKey}:${edit.lines.join("\n")}`;
688
+ if (seenEditKeys.has(dstKey)) {
689
+ dedupIndices.add(i);
690
+ } else {
691
+ seenEditKeys.set(dstKey, i);
692
+ }
693
+ }
694
+ if (dedupIndices.size === 0) return;
695
+ for (let i = edits.length - 1; i >= 0; i--) {
696
+ if (dedupIndices.has(i)) edits.splice(i, 1);
697
+ }
698
+ }
699
+
700
+ function getHashlineEditSortKey(edit: HashlineEdit, fileLineCount: number): { sortLine: number; precedence: number } {
701
+ switch (edit.op) {
702
+ case "replace_line":
703
+ return { sortLine: edit.pos.line, precedence: 0 };
704
+ case "replace_range":
705
+ return { sortLine: edit.end.line, precedence: 0 };
706
+ case "append_at":
707
+ return { sortLine: edit.pos.line, precedence: 1 };
708
+ case "prepend_at":
709
+ return { sortLine: edit.pos.line, precedence: 2 };
710
+ case "append_file":
711
+ return { sortLine: fileLineCount + 1, precedence: 1 };
712
+ case "prepend_file":
713
+ return { sortLine: 0, precedence: 2 };
714
+ }
715
+ }
716
+
717
+ function applyHashlineEditToLines(
718
+ edit: HashlineEdit,
719
+ fileLines: string[],
720
+ originalFileLines: string[],
721
+ editIndex: number,
722
+ noopEdits: Array<{ editIndex: number; loc: string; current: string }>,
723
+ trackFirstChanged: (line: number) => void,
724
+ ): void {
725
+ switch (edit.op) {
726
+ case "replace_line": {
727
+ const origLines = originalFileLines.slice(edit.pos.line - 1, edit.pos.line);
728
+ const newLines = edit.lines;
729
+ if (origLines.length === newLines.length && origLines.every((line, i) => line === newLines[i])) {
730
+ noopEdits.push({
731
+ editIndex,
732
+ loc: `${edit.pos.line}#${edit.pos.hash}`,
733
+ current: origLines.join("\n"),
734
+ });
735
+ break;
736
+ }
737
+ fileLines.splice(edit.pos.line - 1, 1, ...newLines);
738
+ trackFirstChanged(edit.pos.line);
739
+ break;
740
+ }
741
+ case "replace_range": {
742
+ const count = edit.end.line - edit.pos.line + 1;
743
+ fileLines.splice(edit.pos.line - 1, count, ...edit.lines);
744
+ trackFirstChanged(edit.pos.line);
745
+ break;
746
+ }
747
+ case "append_at": {
748
+ const inserted = edit.lines;
749
+ if (inserted.length === 0) {
750
+ noopEdits.push({
751
+ editIndex,
752
+ loc: `${edit.pos.line}#${edit.pos.hash}`,
753
+ current: originalFileLines[edit.pos.line - 1],
754
+ });
755
+ break;
756
+ }
757
+ fileLines.splice(edit.pos.line, 0, ...inserted);
758
+ trackFirstChanged(edit.pos.line + 1);
759
+ break;
760
+ }
761
+ case "prepend_at": {
762
+ const inserted = edit.lines;
763
+ if (inserted.length === 0) {
764
+ noopEdits.push({
765
+ editIndex,
766
+ loc: `${edit.pos.line}#${edit.pos.hash}`,
767
+ current: originalFileLines[edit.pos.line - 1],
768
+ });
769
+ break;
770
+ }
771
+ fileLines.splice(edit.pos.line - 1, 0, ...inserted);
772
+ trackFirstChanged(edit.pos.line);
773
+ break;
774
+ }
775
+ case "append_file": {
776
+ const inserted = edit.lines;
777
+ if (inserted.length === 0) {
778
+ noopEdits.push({ editIndex, loc: "EOF", current: "" });
779
+ break;
780
+ }
781
+ if (fileLines.length === 1 && fileLines[0] === "") {
782
+ fileLines.splice(0, 1, ...inserted);
783
+ trackFirstChanged(1);
784
+ } else {
785
+ fileLines.splice(fileLines.length, 0, ...inserted);
786
+ trackFirstChanged(fileLines.length - inserted.length + 1);
787
+ }
788
+ break;
789
+ }
790
+ case "prepend_file": {
791
+ const inserted = edit.lines;
792
+ if (inserted.length === 0) {
793
+ noopEdits.push({ editIndex, loc: "BOF", current: "" });
794
+ break;
795
+ }
796
+ if (fileLines.length === 1 && fileLines[0] === "") {
797
+ fileLines.splice(0, 1, ...inserted);
798
+ } else {
799
+ fileLines.splice(0, 0, ...inserted);
800
+ }
801
+ trackFirstChanged(1);
802
+ break;
803
+ }
804
+ }
805
+ }
806
+
807
+ function buildHashlineEditResult(params: {
808
+ fileLines: string[];
809
+ firstChangedLine: number | undefined;
810
+ warnings: string[];
811
+ noopEdits: Array<{ editIndex: number; loc: string; current: string }>;
812
+ }): {
813
+ lines: string;
814
+ firstChangedLine: number | undefined;
815
+ warnings?: string[];
816
+ noopEdits?: Array<{ editIndex: number; loc: string; current: string }>;
817
+ } {
818
+ const { fileLines, firstChangedLine, warnings, noopEdits } = params;
819
+ return {
820
+ lines: fileLines.join("\n"),
821
+ firstChangedLine,
822
+ ...(warnings.length > 0 ? { warnings } : {}),
823
+ ...(noopEdits.length > 0 ? { noopEdits } : {}),
824
+ };
825
+ }
826
+
827
+ function validateHashlineEditRefs(edits: HashlineEdit[], fileLines: string[]): HashMismatch[] {
828
+ const mismatches: HashMismatch[] = [];
829
+ for (const edit of edits) {
830
+ switch (edit.op) {
831
+ case "replace_line":
832
+ validateHashlineRef(edit.pos);
833
+ break;
834
+ case "replace_range":
835
+ validateHashlineRef(edit.pos);
836
+ validateHashlineRef(edit.end);
837
+ if (edit.pos.line > edit.end.line) {
838
+ throw new Error(`Range start line ${edit.pos.line} must be <= end line ${edit.end.line}`);
839
+ }
840
+ break;
841
+ case "append_at":
842
+ case "prepend_at":
843
+ validateHashlineRef(edit.pos);
844
+ ensureHashlineEditHasContent(edit);
845
+ break;
846
+ case "append_file":
847
+ case "prepend_file":
848
+ ensureHashlineEditHasContent(edit);
849
+ break;
850
+ }
851
+ }
852
+ return mismatches;
853
+
854
+ function validateHashlineRef(ref: { line: number; hash: string }): void {
855
+ if (ref.line < 1 || ref.line > fileLines.length) {
856
+ throw new Error(`Line ${ref.line} does not exist (file has ${fileLines.length} lines)`);
857
+ }
858
+ const actualHash = computeLineHash(ref.line, fileLines[ref.line - 1]);
859
+ if (actualHash === ref.hash) {
860
+ return;
861
+ }
862
+ mismatches.push({ line: ref.line, expected: ref.hash, actual: actualHash });
863
+ }
864
+ }
865
+ // ═══════════════════════════════════════════════════════════════════════════
866
+ // Edit Application
867
+ // ═══════════════════════════════════════════════════════════════════════════
868
+
869
+ /**
870
+ * Apply an array of hashline edits to file content.
871
+ *
872
+ * Each edit operation identifies target lines directly (`replace`,
873
+ * `append`, `prepend`). Line references are resolved via {@link parseTag}
874
+ * and hashes validated before any mutation.
875
+ *
876
+ * Edits are sorted bottom-up (highest effective line first) so earlier
877
+ * splices don't invalidate later line numbers.
878
+ *
879
+ * @returns The modified content and the 1-indexed first changed line number
880
+ */
881
+ export function applyHashlineEdits(
882
+ text: string,
883
+ edits: HashlineEdit[],
884
+ ): {
885
+ lines: string;
886
+ firstChangedLine: number | undefined;
887
+ warnings?: string[];
888
+ noopEdits?: Array<{ editIndex: number; loc: string; current: string }>;
889
+ } {
890
+ if (edits.length === 0) {
891
+ return { lines: text, firstChangedLine: undefined };
892
+ }
893
+
894
+ const fileLines = text.split("\n");
895
+ const originalFileLines = [...fileLines];
896
+ let firstChangedLine: number | undefined;
897
+ const noopEdits: Array<{ editIndex: number; loc: string; current: string }> = [];
898
+ const warnings: string[] = [];
899
+
900
+ const mismatches = validateHashlineEditRefs(edits, fileLines);
901
+ if (mismatches.length > 0) {
902
+ throw new HashlineMismatchError(mismatches, fileLines);
903
+ }
904
+ runHashlinePreflightSanitizers(edits, warnings);
905
+ for (const edit of edits) {
906
+ collectBoundaryDuplicationWarning(edit, originalFileLines, warnings);
907
+ }
908
+ dedupeHashlineEdits(edits);
909
+
910
+ const annotated = edits
911
+ .map((edit, idx) => {
912
+ const { sortLine, precedence } = getHashlineEditSortKey(edit, fileLines.length);
913
+ return { edit, idx, sortLine, precedence };
914
+ })
915
+ .sort((a, b) => b.sortLine - a.sortLine || a.precedence - b.precedence || a.idx - b.idx);
916
+
917
+ for (const { edit, idx } of annotated) {
918
+ applyHashlineEditToLines(edit, fileLines, originalFileLines, idx, noopEdits, trackFirstChanged);
919
+ }
920
+
921
+ return buildHashlineEditResult({ fileLines, firstChangedLine, warnings, noopEdits });
922
+
923
+ function trackFirstChanged(line: number): void {
924
+ if (firstChangedLine === undefined || line < firstChangedLine) {
925
+ firstChangedLine = line;
926
+ }
927
+ }
928
+ }
929
+
930
+ export interface CompactHashlineDiffPreview {
931
+ preview: string;
932
+ addedLines: number;
933
+ removedLines: number;
934
+ }
935
+
936
+ export interface CompactHashlineDiffOptions {
937
+ maxUnchangedRun?: number;
938
+ maxAdditionRun?: number;
939
+ maxDeletionRun?: number;
940
+ maxOutputLines?: number;
941
+ }
942
+
943
+ const NUMBERED_DIFF_LINE_RE = /^([ +-])(\s*\d+)\|(.*)$/;
944
+ const HASHLINE_PREVIEW_PLACEHOLDER = " ";
945
+
946
+ type DiffRunKind = " " | "+" | "-" | "meta";
947
+ type DiffRun = { kind: DiffRunKind; lines: string[] };
948
+
949
+ interface ParsedNumberedDiffLine {
950
+ kind: " " | "+" | "-";
951
+ lineNumber: number;
952
+ lineWidth: number;
953
+ content: string;
954
+ raw: string;
955
+ }
956
+
957
+ interface CompactPreviewCounters {
958
+ oldLine?: number;
959
+ newLine?: number;
960
+ }
961
+
962
+ function parseNumberedDiffLine(line: string): ParsedNumberedDiffLine | undefined {
963
+ const match = NUMBERED_DIFF_LINE_RE.exec(line);
964
+ if (!match) return undefined;
965
+
966
+ const kind = match[1];
967
+ if (kind !== " " && kind !== "+" && kind !== "-") return undefined;
968
+
969
+ const lineField = match[2];
970
+ const lineNumber = Number(lineField.trim());
971
+ if (!Number.isInteger(lineNumber)) return undefined;
972
+
973
+ return { kind, lineNumber, lineWidth: lineField.length, content: match[3], raw: line };
974
+ }
975
+
976
+ function syncOldLineCounters(counters: CompactPreviewCounters, lineNumber: number): void {
977
+ if (counters.oldLine === undefined || counters.newLine === undefined) {
978
+ counters.oldLine = lineNumber;
979
+ counters.newLine = lineNumber;
980
+ return;
981
+ }
982
+
983
+ const delta = lineNumber - counters.oldLine;
984
+ counters.oldLine = lineNumber;
985
+ counters.newLine += delta;
986
+ }
987
+
988
+ function syncNewLineCounters(counters: CompactPreviewCounters, lineNumber: number): void {
989
+ if (counters.oldLine === undefined || counters.newLine === undefined) {
990
+ counters.oldLine = lineNumber;
991
+ counters.newLine = lineNumber;
992
+ return;
993
+ }
994
+
995
+ const delta = lineNumber - counters.newLine;
996
+ counters.oldLine += delta;
997
+ counters.newLine = lineNumber;
998
+ }
999
+
1000
+ function formatCompactHashlineLine(kind: " " | "+", lineNumber: number, width: number, content: string): string {
1001
+ const padded = String(lineNumber).padStart(width, " ");
1002
+ return `${kind}${padded}#${computeLineHash(lineNumber, content)}|${content}`;
1003
+ }
1004
+
1005
+ function formatCompactRemovedLine(lineNumber: number, width: number, content: string): string {
1006
+ const padded = String(lineNumber).padStart(width, " ");
1007
+ return `-${padded}${HASHLINE_PREVIEW_PLACEHOLDER}|${content}`;
1008
+ }
1009
+
1010
+ function formatCompactPreviewLine(line: string, counters: CompactPreviewCounters): { kind: DiffRunKind; text: string } {
1011
+ const parsed = parseNumberedDiffLine(line);
1012
+ if (!parsed) return { kind: "meta", text: line };
1013
+
1014
+ if (parsed.content === "...") {
1015
+ if (parsed.kind === "+") {
1016
+ syncNewLineCounters(counters, parsed.lineNumber);
1017
+ } else {
1018
+ syncOldLineCounters(counters, parsed.lineNumber);
1019
+ }
1020
+ return { kind: parsed.kind, text: parsed.raw };
1021
+ }
1022
+
1023
+ switch (parsed.kind) {
1024
+ case "+": {
1025
+ syncNewLineCounters(counters, parsed.lineNumber);
1026
+ const newLine = counters.newLine;
1027
+ if (newLine === undefined) return { kind: "+", text: parsed.raw };
1028
+ const text = formatCompactHashlineLine("+", newLine, parsed.lineWidth, parsed.content);
1029
+ counters.newLine = newLine + 1;
1030
+ return { kind: "+", text };
1031
+ }
1032
+ case "-": {
1033
+ syncOldLineCounters(counters, parsed.lineNumber);
1034
+ const text = formatCompactRemovedLine(parsed.lineNumber, parsed.lineWidth, parsed.content);
1035
+ counters.oldLine = parsed.lineNumber + 1;
1036
+ return { kind: "-", text };
1037
+ }
1038
+ case " ": {
1039
+ syncOldLineCounters(counters, parsed.lineNumber);
1040
+ const newLine = counters.newLine;
1041
+ if (newLine === undefined) return { kind: " ", text: parsed.raw };
1042
+ const text = formatCompactHashlineLine(" ", newLine, parsed.lineWidth, parsed.content);
1043
+ counters.oldLine = parsed.lineNumber + 1;
1044
+ counters.newLine = newLine + 1;
1045
+ return { kind: " ", text };
1046
+ }
1047
+ }
1048
+ }
1049
+
1050
+ function splitDiffRuns(lines: string[]): DiffRun[] {
1051
+ const runs: DiffRun[] = [];
1052
+ const counters: CompactPreviewCounters = {};
1053
+
1054
+ for (const line of lines) {
1055
+ const formatted = formatCompactPreviewLine(line, counters);
1056
+ const prev = runs[runs.length - 1];
1057
+ if (prev && prev.kind === formatted.kind) {
1058
+ prev.lines.push(formatted.text);
1059
+ continue;
1060
+ }
1061
+ runs.push({ kind: formatted.kind, lines: [formatted.text] });
1062
+ }
1063
+
1064
+ return runs;
1065
+ }
1066
+
1067
+ function collapseFromStart(lines: string[], maxLines: number, label: string): string[] {
1068
+ if (lines.length <= maxLines) return lines;
1069
+ const hidden = lines.length - maxLines;
1070
+ return [...lines.slice(0, maxLines), ` ... ${hidden} more ${label} lines`];
1071
+ }
1072
+
1073
+ function collapseFromEnd(lines: string[], maxLines: number, label: string): string[] {
1074
+ if (lines.length <= maxLines) return lines;
1075
+ const hidden = lines.length - maxLines;
1076
+ return [` ... ${hidden} more ${label} lines`, ...lines.slice(-maxLines)];
1077
+ }
1078
+
1079
+ function collapseFromMiddle(lines: string[], maxLines: number, label: string): string[] {
1080
+ if (lines.length <= maxLines * 2) return lines;
1081
+ const hidden = lines.length - maxLines * 2;
1082
+ return [...lines.slice(0, maxLines), ` ... ${hidden} more ${label} lines`, ...lines.slice(-maxLines)];
1083
+ }
1084
+
1085
+ /**
1086
+ * Build a compact diff preview suitable for model-visible tool responses.
1087
+ *
1088
+ * Collapses long unchanged runs and long consecutive additions/removals so the
1089
+ * model sees the shape of edits without replaying full file content.
1090
+ */
1091
+ export function buildCompactHashlineDiffPreview(
1092
+ diff: string,
1093
+ options: CompactHashlineDiffOptions = {},
1094
+ ): CompactHashlineDiffPreview {
1095
+ const maxUnchangedRun = options.maxUnchangedRun ?? 2;
1096
+ const maxAdditionRun = options.maxAdditionRun ?? 2;
1097
+ const maxDeletionRun = options.maxDeletionRun ?? 2;
1098
+ const maxOutputLines = options.maxOutputLines ?? 16;
1099
+
1100
+ const inputLines = diff.length === 0 ? [] : diff.split("\n");
1101
+ const runs = splitDiffRuns(inputLines);
1102
+
1103
+ const out: string[] = [];
1104
+ let addedLines = 0;
1105
+ let removedLines = 0;
1106
+
1107
+ for (let runIndex = 0; runIndex < runs.length; runIndex++) {
1108
+ const run = runs[runIndex];
1109
+ switch (run.kind) {
1110
+ case "meta":
1111
+ out.push(...run.lines);
1112
+ break;
1113
+ case "+":
1114
+ addedLines += run.lines.length;
1115
+ out.push(...collapseFromStart(run.lines, maxAdditionRun, "added"));
1116
+ break;
1117
+ case "-":
1118
+ removedLines += run.lines.length;
1119
+ out.push(...collapseFromStart(run.lines, maxDeletionRun, "removed"));
1120
+ break;
1121
+ case " ":
1122
+ if (runIndex === 0) {
1123
+ out.push(...collapseFromEnd(run.lines, maxUnchangedRun, "unchanged"));
1124
+ break;
1125
+ }
1126
+ if (runIndex === runs.length - 1) {
1127
+ out.push(...collapseFromStart(run.lines, maxUnchangedRun, "unchanged"));
1128
+ break;
1129
+ }
1130
+ out.push(...collapseFromMiddle(run.lines, maxUnchangedRun, "unchanged"));
1131
+ break;
1132
+ }
1133
+ }
1134
+
1135
+ if (out.length > maxOutputLines) {
1136
+ const hidden = out.length - maxOutputLines;
1137
+ return {
1138
+ preview: [...out.slice(0, maxOutputLines), ` ... ${hidden} more preview lines`].join("\n"),
1139
+ addedLines,
1140
+ removedLines,
1141
+ };
1142
+ }
1143
+
1144
+ return { preview: out.join("\n"), addedLines, removedLines };
1145
+ }
1146
+
1147
+ export async function computeHashlineDiff(
1148
+ input: { path: string; edits: HashlineEditInput[]; move?: string },
1149
+ cwd: string,
1150
+ ): Promise<
1151
+ | {
1152
+ diff: string;
1153
+ firstChangedLine: number | undefined;
1154
+ }
1155
+ | {
1156
+ error: string;
1157
+ }
1158
+ > {
1159
+ const { path, edits, move } = input;
1160
+ const absolutePath = resolveToCwd(path, cwd);
1161
+ const movePath = move ? resolveToCwd(move, cwd) : undefined;
1162
+ const isMoveOnly = Boolean(movePath) && movePath !== absolutePath && edits.length === 0;
1163
+
1164
+ try {
1165
+ const resolvedEdits = resolveHashlineEditsForDiff(edits);
1166
+ const file = Bun.file(absolutePath);
1167
+
1168
+ if (movePath === absolutePath) {
1169
+ return { error: "move path is the same as source path" };
1170
+ }
1171
+ if (isMoveOnly) {
1172
+ return { diff: "", firstChangedLine: undefined };
1173
+ }
1174
+
1175
+ const rawContent = await readHashlineFileText(file, path);
1176
+
1177
+ const { text: content } = stripBom(rawContent);
1178
+ const normalizedContent = normalizeToLF(content);
1179
+ const result = applyHashlineEdits(normalizedContent, resolvedEdits);
1180
+ if (normalizedContent === result.lines && !move) {
1181
+ return { error: `No changes would be made to ${path}. The edits produce identical content.` };
1182
+ }
1183
+
1184
+ return generateDiffString(normalizedContent, result.lines);
1185
+ } catch (err) {
1186
+ return { error: err instanceof Error ? err.message : String(err) };
1187
+ }
1188
+ }
1189
+
1190
+ async function readHashlineFileText(file: BunFile, path: string): Promise<string> {
1191
+ try {
1192
+ return await file.text();
1193
+ } catch (error) {
1194
+ if (isEnoent(error)) {
1195
+ throw new Error(`File not found: ${path}`);
1196
+ }
1197
+ const message = error instanceof Error ? error.message : String(error);
1198
+ throw new Error(message || `Unable to read ${path}`);
1199
+ }
1200
+ }
1201
+
1202
+ export async function executeHashlineSingle(
1203
+ options: ExecuteHashlineSingleOptions,
1204
+ ): Promise<AgentToolResult<EditToolDetails, typeof hashlineEditParamsSchema>> {
1205
+ const { session, path, edits, signal, batchRequest, writethrough, beginDeferredDiagnosticsForPath } = options;
1206
+
1207
+ // Extract file-level ops from edits
1208
+ const deleteFile = edits.some(e => e.delete);
1209
+ const move = edits.find(e => e.move)?.move;
1210
+ // Filter to content edits only (those with loc)
1211
+ const contentEdits = edits.filter(e => e.loc != null);
1212
+
1213
+ enforcePlanModeWrite(session, path, { op: deleteFile ? "delete" : "update", move });
1214
+
1215
+ if (path.endsWith(".ipynb") && contentEdits.length > 0) {
1216
+ throw new Error("Cannot edit Jupyter notebooks with the Edit tool. Use the NotebookEdit tool instead.");
1217
+ }
1218
+
1219
+ const absolutePath = resolvePlanPath(session, path);
1220
+ const resolvedMove = move ? resolvePlanPath(session, move) : undefined;
1221
+ if (resolvedMove === absolutePath) {
1222
+ throw new Error("move path is the same as source path");
1223
+ }
1224
+
1225
+ const sourceFile = Bun.file(absolutePath);
1226
+ const sourceExists = await sourceFile.exists();
1227
+ const isMoveOnly = Boolean(resolvedMove) && contentEdits.length === 0;
1228
+
1229
+ if (deleteFile) {
1230
+ if (sourceExists) {
1231
+ await sourceFile.unlink();
1232
+ }
1233
+ invalidateFsScanAfterDelete(absolutePath);
1234
+ return {
1235
+ content: [{ type: "text", text: `Deleted ${path}` }],
1236
+ details: {
1237
+ diff: "",
1238
+ op: "delete",
1239
+ meta: outputMeta().get(),
1240
+ },
1241
+ };
1242
+ }
1243
+
1244
+ if (isMoveOnly && resolvedMove) {
1245
+ if (!sourceExists) {
1246
+ throw new Error(`File not found: ${path}`);
1247
+ }
1248
+ const parentDir = nodePath.dirname(resolvedMove);
1249
+ if (parentDir && parentDir !== ".") {
1250
+ await fs.mkdir(parentDir, { recursive: true });
1251
+ }
1252
+ await fs.rename(absolutePath, resolvedMove);
1253
+ invalidateFsScanAfterRename(absolutePath, resolvedMove);
1254
+ return {
1255
+ content: [{ type: "text", text: `Moved ${path} to ${move}` }],
1256
+ details: {
1257
+ diff: "",
1258
+ op: "update",
1259
+ move,
1260
+ meta: outputMeta().get(),
1261
+ },
1262
+ };
1263
+ }
1264
+
1265
+ if (!sourceExists) {
1266
+ const lines: string[] = [];
1267
+ for (const edit of contentEdits) {
1268
+ if (edit.loc === "append") {
1269
+ lines.push(...hashlineParseText(edit.content));
1270
+ } else if (edit.loc === "prepend") {
1271
+ lines.unshift(...hashlineParseText(edit.content));
1272
+ } else {
1273
+ throw new Error(`File not found: ${path}`);
1274
+ }
1275
+ }
1276
+
1277
+ await Bun.write(absolutePath, lines.join("\n"));
1278
+ invalidateFsScanAfterWrite(absolutePath);
1279
+ return {
1280
+ content: [{ type: "text", text: `Created ${path}` }],
1281
+ details: {
1282
+ diff: "",
1283
+ op: "create",
1284
+ meta: outputMeta().get(),
1285
+ },
1286
+ };
1287
+ }
1288
+
1289
+ const anchorEdits = resolveEditAnchors(contentEdits);
1290
+ const rawContent = await sourceFile.text();
1291
+ assertEditableFileContent(rawContent, path);
1292
+
1293
+ const { bom, text } = stripBom(rawContent);
1294
+ const originalEnding = detectLineEnding(text);
1295
+ const originalNormalized = normalizeToLF(text);
1296
+ let normalizedText = originalNormalized;
1297
+
1298
+ const anchorResult = applyHashlineEdits(normalizedText, anchorEdits);
1299
+ normalizedText = anchorResult.lines;
1300
+
1301
+ const result = {
1302
+ text: normalizedText,
1303
+ firstChangedLine: anchorResult.firstChangedLine,
1304
+ warnings: anchorResult.warnings,
1305
+ noopEdits: anchorResult.noopEdits,
1306
+ };
1307
+ if (originalNormalized === result.text && !move) {
1308
+ let diagnostic = `No changes made to ${path}. The edits produced identical content.`;
1309
+ if (result.noopEdits && result.noopEdits.length > 0) {
1310
+ const details = result.noopEdits
1311
+ .map(
1312
+ edit =>
1313
+ `Edit ${edit.editIndex}: replacement for ${edit.loc} is identical to current content:\n ${edit.loc}| ${edit.current}`,
1314
+ )
1315
+ .join("\n");
1316
+ diagnostic += `\n${details}`;
1317
+ if (result.noopEdits.length === 1 && result.noopEdits[0]?.current) {
1318
+ const preview = result.noopEdits[0].current.trimEnd();
1319
+ if (preview.length > 0) {
1320
+ diagnostic += `\nThe file currently contains these lines:\n${preview}\nYour edits were normalized back to the original content (whitespace-only differences are preserved as-is). Ensure your replacement changes actual code, not just formatting.`;
1321
+ }
1322
+ }
1323
+ }
1324
+ throw new Error(diagnostic);
1325
+ }
1326
+
1327
+ const writePath = resolvedMove ?? absolutePath;
1328
+ const finalContent = bom + restoreLineEndings(result.text, originalEnding);
1329
+ const diagnostics = await writethrough(writePath, finalContent, signal, Bun.file(writePath), batchRequest, dst =>
1330
+ dst === writePath ? beginDeferredDiagnosticsForPath(writePath) : undefined,
1331
+ );
1332
+ if (resolvedMove && resolvedMove !== absolutePath) {
1333
+ await sourceFile.unlink();
1334
+ invalidateFsScanAfterRename(absolutePath, resolvedMove);
1335
+ } else {
1336
+ invalidateFsScanAfterWrite(absolutePath);
1337
+ }
1338
+
1339
+ const diffResult = generateDiffString(originalNormalized, result.text);
1340
+ const meta = outputMeta()
1341
+ .diagnostics(diagnostics?.summary ?? "", diagnostics?.messages ?? [])
1342
+ .get();
1343
+
1344
+ const resultText = move ? `Moved ${path} to ${move}` : `Updated ${path}`;
1345
+ const preview = buildCompactHashlineDiffPreview(diffResult.diff);
1346
+ const summaryLine = `Changes: +${preview.addedLines} -${preview.removedLines}${preview.preview ? "" : " (no textual diff preview)"}`;
1347
+ const warningsBlock = result.warnings?.length ? `\n\nWarnings:\n${result.warnings.join("\n")}` : "";
1348
+ const previewBlock = preview.preview ? `\n\nDiff preview:\n${preview.preview}` : "";
1349
+
1350
+ return {
1351
+ content: [
1352
+ {
1353
+ type: "text",
1354
+ text: `${resultText}\n${summaryLine}${previewBlock}${warningsBlock}`,
1355
+ },
1356
+ ],
1357
+ details: {
1358
+ diff: diffResult.diff,
1359
+ firstChangedLine: result.firstChangedLine ?? diffResult.firstChangedLine,
1360
+ diagnostics,
1361
+ op: "update",
1362
+ move,
1363
+ meta,
1364
+ },
1365
+ };
1366
+ }