@wahack/pi-coding-agent 15.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1152) hide show
  1. package/CHANGELOG.md +10031 -0
  2. package/README.md +36 -0
  3. package/examples/README.md +21 -0
  4. package/examples/custom-tools/README.md +104 -0
  5. package/examples/custom-tools/hello/index.ts +20 -0
  6. package/examples/extensions/README.md +142 -0
  7. package/examples/extensions/api-demo.ts +79 -0
  8. package/examples/extensions/chalk-logger.ts +25 -0
  9. package/examples/extensions/hello.ts +31 -0
  10. package/examples/extensions/pirate.ts +43 -0
  11. package/examples/extensions/plan-mode.ts +549 -0
  12. package/examples/extensions/reload-runtime.ts +38 -0
  13. package/examples/extensions/thinking-note.ts +13 -0
  14. package/examples/extensions/tools.ts +145 -0
  15. package/examples/extensions/with-deps/index.ts +36 -0
  16. package/examples/extensions/with-deps/package-lock.json +31 -0
  17. package/examples/extensions/with-deps/package.json +17 -0
  18. package/examples/hooks/README.md +56 -0
  19. package/examples/hooks/auto-commit-on-exit.ts +48 -0
  20. package/examples/hooks/confirm-destructive.ts +58 -0
  21. package/examples/hooks/custom-compaction.ts +115 -0
  22. package/examples/hooks/dirty-repo-guard.ts +51 -0
  23. package/examples/hooks/file-trigger.ts +40 -0
  24. package/examples/hooks/git-checkpoint.ts +52 -0
  25. package/examples/hooks/handoff.ts +149 -0
  26. package/examples/hooks/permission-gate.ts +33 -0
  27. package/examples/hooks/protected-paths.ts +29 -0
  28. package/examples/hooks/qna.ts +118 -0
  29. package/examples/hooks/status-line.ts +39 -0
  30. package/examples/sdk/01-minimal.ts +21 -0
  31. package/examples/sdk/02-custom-model.ts +49 -0
  32. package/examples/sdk/03-custom-prompt.ts +46 -0
  33. package/examples/sdk/04-skills.ts +43 -0
  34. package/examples/sdk/06-extensions.ts +82 -0
  35. package/examples/sdk/06-hooks.ts +61 -0
  36. package/examples/sdk/07-context-files.ts +35 -0
  37. package/examples/sdk/08-prompt-templates.ts +41 -0
  38. package/examples/sdk/08-slash-commands.ts +46 -0
  39. package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
  40. package/examples/sdk/11-sessions.ts +47 -0
  41. package/examples/sdk/12-redis-sessions.ts +54 -0
  42. package/examples/sdk/13-sql-sessions.ts +61 -0
  43. package/examples/sdk/README.md +172 -0
  44. package/package.json +554 -0
  45. package/scripts/build-binary.ts +100 -0
  46. package/scripts/bundle-dist.ts +90 -0
  47. package/scripts/format-prompts.ts +68 -0
  48. package/scripts/generate-docs-index.ts +40 -0
  49. package/scripts/generate-template.ts +33 -0
  50. package/scripts/omp +42 -0
  51. package/scripts/omp.ts +19 -0
  52. package/src/async/index.ts +1 -0
  53. package/src/async/job-manager.ts +625 -0
  54. package/src/auto-thinking/classifier.ts +185 -0
  55. package/src/autoresearch/command-resume.md +14 -0
  56. package/src/autoresearch/dashboard.ts +436 -0
  57. package/src/autoresearch/git.ts +319 -0
  58. package/src/autoresearch/helpers.ts +218 -0
  59. package/src/autoresearch/index.ts +536 -0
  60. package/src/autoresearch/prompt-setup.md +43 -0
  61. package/src/autoresearch/prompt.md +103 -0
  62. package/src/autoresearch/resume-message.md +10 -0
  63. package/src/autoresearch/state.ts +273 -0
  64. package/src/autoresearch/storage.ts +699 -0
  65. package/src/autoresearch/tools/init-experiment.ts +272 -0
  66. package/src/autoresearch/tools/log-experiment.ts +524 -0
  67. package/src/autoresearch/tools/run-experiment.ts +407 -0
  68. package/src/autoresearch/tools/update-notes.ts +109 -0
  69. package/src/autoresearch/types.ts +168 -0
  70. package/src/bun-imports.d.ts +28 -0
  71. package/src/capability/context-file.ts +44 -0
  72. package/src/capability/extension-module.ts +34 -0
  73. package/src/capability/extension.ts +47 -0
  74. package/src/capability/fs.ts +117 -0
  75. package/src/capability/hook.ts +40 -0
  76. package/src/capability/index.ts +436 -0
  77. package/src/capability/instruction.ts +37 -0
  78. package/src/capability/mcp.ts +74 -0
  79. package/src/capability/prompt.ts +35 -0
  80. package/src/capability/rule-buckets.ts +66 -0
  81. package/src/capability/rule.ts +261 -0
  82. package/src/capability/settings.ts +34 -0
  83. package/src/capability/skill.ts +63 -0
  84. package/src/capability/slash-command.ts +40 -0
  85. package/src/capability/ssh.ts +41 -0
  86. package/src/capability/system-prompt.ts +34 -0
  87. package/src/capability/tool.ts +38 -0
  88. package/src/capability/types.ts +168 -0
  89. package/src/cli/agents-cli.ts +138 -0
  90. package/src/cli/args.ts +340 -0
  91. package/src/cli/auth-broker-cli.ts +895 -0
  92. package/src/cli/auth-gateway-cli.ts +611 -0
  93. package/src/cli/classify-install-target.ts +76 -0
  94. package/src/cli/claude-trace-cli.ts +795 -0
  95. package/src/cli/commands/init-xdg.ts +27 -0
  96. package/src/cli/completion-gen.ts +550 -0
  97. package/src/cli/config-cli.ts +418 -0
  98. package/src/cli/dry-balance-cli.ts +856 -0
  99. package/src/cli/extension-flags.ts +48 -0
  100. package/src/cli/file-processor.ts +133 -0
  101. package/src/cli/gallery-cli.ts +230 -0
  102. package/src/cli/gallery-fixtures/agentic.ts +407 -0
  103. package/src/cli/gallery-fixtures/codeintel.ts +187 -0
  104. package/src/cli/gallery-fixtures/edit.ts +194 -0
  105. package/src/cli/gallery-fixtures/fs.ts +220 -0
  106. package/src/cli/gallery-fixtures/index.ts +40 -0
  107. package/src/cli/gallery-fixtures/interaction.ts +49 -0
  108. package/src/cli/gallery-fixtures/memory.ts +81 -0
  109. package/src/cli/gallery-fixtures/misc.ts +250 -0
  110. package/src/cli/gallery-fixtures/search.ts +213 -0
  111. package/src/cli/gallery-fixtures/shell.ts +167 -0
  112. package/src/cli/gallery-fixtures/types.ts +57 -0
  113. package/src/cli/gallery-fixtures/web.ts +158 -0
  114. package/src/cli/gallery-screenshot.ts +279 -0
  115. package/src/cli/grep-cli.ts +160 -0
  116. package/src/cli/grievances-cli.ts +256 -0
  117. package/src/cli/initial-message.ts +58 -0
  118. package/src/cli/list-models.ts +194 -0
  119. package/src/cli/plugin-cli.ts +996 -0
  120. package/src/cli/read-cli.ts +57 -0
  121. package/src/cli/session-picker.ts +79 -0
  122. package/src/cli/setup-cli.ts +231 -0
  123. package/src/cli/shell-cli.ts +176 -0
  124. package/src/cli/ssh-cli.ts +179 -0
  125. package/src/cli/startup-cwd.ts +68 -0
  126. package/src/cli/stats-cli.ts +238 -0
  127. package/src/cli/tiny-models-cli.ts +127 -0
  128. package/src/cli/update-cli.ts +611 -0
  129. package/src/cli/usage-cli.ts +603 -0
  130. package/src/cli/web-search-cli.ts +132 -0
  131. package/src/cli/worktree-cli.ts +291 -0
  132. package/src/cli-commands.ts +79 -0
  133. package/src/cli.ts +200 -0
  134. package/src/commands/acp.ts +24 -0
  135. package/src/commands/agents.ts +57 -0
  136. package/src/commands/auth-broker.ts +99 -0
  137. package/src/commands/auth-gateway.ts +69 -0
  138. package/src/commands/commit.ts +46 -0
  139. package/src/commands/complete.ts +66 -0
  140. package/src/commands/completions.ts +60 -0
  141. package/src/commands/config.ts +51 -0
  142. package/src/commands/dry-balance.ts +43 -0
  143. package/src/commands/gallery.ts +52 -0
  144. package/src/commands/grep.ts +48 -0
  145. package/src/commands/grievances.ts +51 -0
  146. package/src/commands/install.ts +107 -0
  147. package/src/commands/launch.ts +169 -0
  148. package/src/commands/plugin.ts +78 -0
  149. package/src/commands/read.ts +38 -0
  150. package/src/commands/setup.ts +67 -0
  151. package/src/commands/shell.ts +29 -0
  152. package/src/commands/ssh.ts +60 -0
  153. package/src/commands/stats.ts +29 -0
  154. package/src/commands/tiny-models.ts +36 -0
  155. package/src/commands/update.ts +21 -0
  156. package/src/commands/usage.ts +35 -0
  157. package/src/commands/web-search.ts +42 -0
  158. package/src/commands/worktree.ts +56 -0
  159. package/src/commit/agentic/agent.ts +317 -0
  160. package/src/commit/agentic/fallback.ts +96 -0
  161. package/src/commit/agentic/index.ts +355 -0
  162. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  163. package/src/commit/agentic/prompts/session-user.md +25 -0
  164. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  165. package/src/commit/agentic/prompts/system.md +38 -0
  166. package/src/commit/agentic/state.ts +60 -0
  167. package/src/commit/agentic/tools/analyze-file.ts +146 -0
  168. package/src/commit/agentic/tools/git-file-diff.ts +191 -0
  169. package/src/commit/agentic/tools/git-hunk.ts +50 -0
  170. package/src/commit/agentic/tools/git-overview.ts +81 -0
  171. package/src/commit/agentic/tools/index.ts +54 -0
  172. package/src/commit/agentic/tools/propose-changelog.ts +144 -0
  173. package/src/commit/agentic/tools/propose-commit.ts +109 -0
  174. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  175. package/src/commit/agentic/tools/schemas.ts +23 -0
  176. package/src/commit/agentic/tools/split-commit.ts +245 -0
  177. package/src/commit/agentic/topo-sort.ts +44 -0
  178. package/src/commit/agentic/trivial.ts +51 -0
  179. package/src/commit/agentic/validation.ts +183 -0
  180. package/src/commit/analysis/conventional.ts +64 -0
  181. package/src/commit/analysis/index.ts +4 -0
  182. package/src/commit/analysis/scope.ts +242 -0
  183. package/src/commit/analysis/summary.ts +105 -0
  184. package/src/commit/analysis/validation.ts +66 -0
  185. package/src/commit/changelog/detect.ts +40 -0
  186. package/src/commit/changelog/generate.ts +97 -0
  187. package/src/commit/changelog/index.ts +234 -0
  188. package/src/commit/changelog/parse.ts +44 -0
  189. package/src/commit/cli.ts +85 -0
  190. package/src/commit/git/diff.ts +148 -0
  191. package/src/commit/index.ts +5 -0
  192. package/src/commit/map-reduce/index.ts +69 -0
  193. package/src/commit/map-reduce/map-phase.ts +193 -0
  194. package/src/commit/map-reduce/reduce-phase.ts +49 -0
  195. package/src/commit/map-reduce/utils.ts +9 -0
  196. package/src/commit/message.ts +11 -0
  197. package/src/commit/model-selection.ts +92 -0
  198. package/src/commit/pipeline.ts +243 -0
  199. package/src/commit/prompts/analysis-system.md +148 -0
  200. package/src/commit/prompts/analysis-user.md +38 -0
  201. package/src/commit/prompts/changelog-system.md +50 -0
  202. package/src/commit/prompts/changelog-user.md +18 -0
  203. package/src/commit/prompts/file-observer-system.md +24 -0
  204. package/src/commit/prompts/file-observer-user.md +8 -0
  205. package/src/commit/prompts/reduce-system.md +50 -0
  206. package/src/commit/prompts/reduce-user.md +17 -0
  207. package/src/commit/prompts/summary-retry.md +3 -0
  208. package/src/commit/prompts/summary-system.md +38 -0
  209. package/src/commit/prompts/summary-user.md +13 -0
  210. package/src/commit/prompts/types-description.md +2 -0
  211. package/src/commit/shared-llm.ts +77 -0
  212. package/src/commit/types.ts +118 -0
  213. package/src/commit/utils/exclusions.ts +42 -0
  214. package/src/commit/utils.ts +58 -0
  215. package/src/config/api-key-resolver.ts +60 -0
  216. package/src/config/append-only-context-mode.ts +31 -0
  217. package/src/config/config-file.ts +317 -0
  218. package/src/config/file-lock.ts +164 -0
  219. package/src/config/keybindings.ts +628 -0
  220. package/src/config/mcp-schema.json +230 -0
  221. package/src/config/model-discovery.ts +554 -0
  222. package/src/config/model-registry.ts +2090 -0
  223. package/src/config/model-resolver.ts +1502 -0
  224. package/src/config/model-roles.ts +74 -0
  225. package/src/config/models-config-schema.ts +226 -0
  226. package/src/config/models-config.ts +129 -0
  227. package/src/config/prompt-templates.ts +185 -0
  228. package/src/config/resolve-config-value.ts +94 -0
  229. package/src/config/settings-schema.ts +3530 -0
  230. package/src/config/settings.ts +1178 -0
  231. package/src/config.ts +242 -0
  232. package/src/cursor.ts +340 -0
  233. package/src/dap/client.ts +760 -0
  234. package/src/dap/config.ts +189 -0
  235. package/src/dap/defaults.json +212 -0
  236. package/src/dap/index.ts +4 -0
  237. package/src/dap/session.ts +1441 -0
  238. package/src/dap/types.ts +610 -0
  239. package/src/debug/index.ts +515 -0
  240. package/src/debug/log-formatting.ts +58 -0
  241. package/src/debug/log-viewer.ts +908 -0
  242. package/src/debug/profiler.ts +162 -0
  243. package/src/debug/protocol-probe.ts +267 -0
  244. package/src/debug/raw-sse-buffer.ts +273 -0
  245. package/src/debug/raw-sse.ts +292 -0
  246. package/src/debug/report-bundle.ts +374 -0
  247. package/src/debug/system-info.ts +111 -0
  248. package/src/debug/terminal-info.ts +124 -0
  249. package/src/discovery/agents-md.ts +67 -0
  250. package/src/discovery/agents.ts +230 -0
  251. package/src/discovery/at-imports.ts +273 -0
  252. package/src/discovery/builtin-defaults.ts +39 -0
  253. package/src/discovery/builtin-rules/index.ts +54 -0
  254. package/src/discovery/builtin-rules/rs-box-leak.md +48 -0
  255. package/src/discovery/builtin-rules/rs-future-prelude.md +23 -0
  256. package/src/discovery/builtin-rules/rs-lazylock.md +51 -0
  257. package/src/discovery/builtin-rules/rs-match-ergonomics.md +67 -0
  258. package/src/discovery/builtin-rules/rs-parking-lot.md +44 -0
  259. package/src/discovery/builtin-rules/rs-result-type.md +19 -0
  260. package/src/discovery/builtin-rules/ts-bare-catch.md +38 -0
  261. package/src/discovery/builtin-rules/ts-import-type.md +42 -0
  262. package/src/discovery/builtin-rules/ts-no-any.md +56 -0
  263. package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +44 -0
  264. package/src/discovery/builtin-rules/ts-no-dynamic-import.md +39 -0
  265. package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
  266. package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
  267. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +51 -0
  268. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +65 -0
  269. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
  270. package/src/discovery/builtin-rules/ts-set-map.md +28 -0
  271. package/src/discovery/builtin.ts +906 -0
  272. package/src/discovery/claude-plugins.ts +386 -0
  273. package/src/discovery/claude.ts +584 -0
  274. package/src/discovery/cline.ts +83 -0
  275. package/src/discovery/codex.ts +522 -0
  276. package/src/discovery/cursor.ts +220 -0
  277. package/src/discovery/gemini.ts +383 -0
  278. package/src/discovery/github.ts +154 -0
  279. package/src/discovery/helpers.ts +1016 -0
  280. package/src/discovery/index.ts +81 -0
  281. package/src/discovery/mcp-json.ts +171 -0
  282. package/src/discovery/omp-extension-roots.ts +190 -0
  283. package/src/discovery/omp-plugins.ts +383 -0
  284. package/src/discovery/opencode.ts +398 -0
  285. package/src/discovery/plugin-dir-roots.ts +28 -0
  286. package/src/discovery/ssh.ts +153 -0
  287. package/src/discovery/substitute-plugin-root.ts +29 -0
  288. package/src/discovery/vscode.ts +105 -0
  289. package/src/discovery/windsurf.ts +147 -0
  290. package/src/edit/apply-patch/index.ts +87 -0
  291. package/src/edit/apply-patch/parser.ts +174 -0
  292. package/src/edit/diff.ts +999 -0
  293. package/src/edit/file-snapshot-store.ts +91 -0
  294. package/src/edit/hashline/block-resolver.ts +33 -0
  295. package/src/edit/hashline/diff.ts +290 -0
  296. package/src/edit/hashline/execute.ts +242 -0
  297. package/src/edit/hashline/filesystem.ts +130 -0
  298. package/src/edit/hashline/index.ts +5 -0
  299. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  300. package/src/edit/hashline/params.ts +18 -0
  301. package/src/edit/index.ts +571 -0
  302. package/src/edit/modes/apply-patch.lark +19 -0
  303. package/src/edit/modes/apply-patch.ts +53 -0
  304. package/src/edit/modes/patch.ts +1891 -0
  305. package/src/edit/modes/replace.ts +1137 -0
  306. package/src/edit/normalize.ts +345 -0
  307. package/src/edit/notebook.ts +242 -0
  308. package/src/edit/read-file.ts +25 -0
  309. package/src/edit/renderer.ts +769 -0
  310. package/src/edit/streaming.ts +517 -0
  311. package/src/eval/__tests__/agent-bridge.test.ts +708 -0
  312. package/src/eval/__tests__/bridge-timeout.test.ts +64 -0
  313. package/src/eval/__tests__/budget-bridge.test.ts +69 -0
  314. package/src/eval/__tests__/completion-bridge.test.ts +412 -0
  315. package/src/eval/__tests__/helpers-local-roots.test.ts +58 -0
  316. package/src/eval/__tests__/idle-timeout.test.ts +80 -0
  317. package/src/eval/__tests__/js-context-manager.test.ts +241 -0
  318. package/src/eval/__tests__/kernel-spawn.test.ts +103 -0
  319. package/src/eval/agent-bridge.ts +319 -0
  320. package/src/eval/backend.ts +71 -0
  321. package/src/eval/bridge-timeout.ts +44 -0
  322. package/src/eval/budget-bridge.ts +48 -0
  323. package/src/eval/completion-bridge.ts +207 -0
  324. package/src/eval/concurrency-bridge.ts +34 -0
  325. package/src/eval/idle-timeout.ts +91 -0
  326. package/src/eval/index.ts +4 -0
  327. package/src/eval/js/context-manager.ts +502 -0
  328. package/src/eval/js/executor.ts +173 -0
  329. package/src/eval/js/index.ts +51 -0
  330. package/src/eval/js/shared/helpers.ts +283 -0
  331. package/src/eval/js/shared/indirect-eval.ts +30 -0
  332. package/src/eval/js/shared/local-module-loader.ts +342 -0
  333. package/src/eval/js/shared/prelude.ts +2 -0
  334. package/src/eval/js/shared/prelude.txt +246 -0
  335. package/src/eval/js/shared/rewrite-imports.ts +532 -0
  336. package/src/eval/js/shared/runtime.ts +352 -0
  337. package/src/eval/js/shared/types.ts +18 -0
  338. package/src/eval/js/tool-bridge.ts +162 -0
  339. package/src/eval/js/worker-core.ts +132 -0
  340. package/src/eval/js/worker-entry.ts +30 -0
  341. package/src/eval/js/worker-protocol.ts +47 -0
  342. package/src/eval/py/__tests__/prelude.test.ts +19 -0
  343. package/src/eval/py/display.ts +71 -0
  344. package/src/eval/py/executor.ts +742 -0
  345. package/src/eval/py/index.ts +68 -0
  346. package/src/eval/py/kernel.ts +748 -0
  347. package/src/eval/py/prelude.py +658 -0
  348. package/src/eval/py/prelude.ts +3 -0
  349. package/src/eval/py/runner.py +1133 -0
  350. package/src/eval/py/runtime.ts +276 -0
  351. package/src/eval/py/spawn-options.ts +126 -0
  352. package/src/eval/py/tool-bridge.ts +182 -0
  353. package/src/eval/session-id.ts +8 -0
  354. package/src/eval/types.ts +48 -0
  355. package/src/exa/index.ts +2 -0
  356. package/src/exa/mcp-client.ts +370 -0
  357. package/src/exa/types.ts +69 -0
  358. package/src/exec/bash-executor.ts +419 -0
  359. package/src/exec/exec.ts +53 -0
  360. package/src/exec/non-interactive-env.ts +48 -0
  361. package/src/export/custom-share.ts +65 -0
  362. package/src/export/html/index.ts +164 -0
  363. package/src/export/html/template.css +1051 -0
  364. package/src/export/html/template.generated.ts +2 -0
  365. package/src/export/html/template.html +46 -0
  366. package/src/export/html/template.js +2271 -0
  367. package/src/export/html/template.macro.ts +25 -0
  368. package/src/export/html/vendor/highlight.min.js +1213 -0
  369. package/src/export/html/vendor/marked.min.js +6 -0
  370. package/src/export/ttsr.ts +583 -0
  371. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +54 -0
  372. package/src/extensibility/custom-commands/bundled/review/index.ts +489 -0
  373. package/src/extensibility/custom-commands/index.ts +2 -0
  374. package/src/extensibility/custom-commands/loader.ts +238 -0
  375. package/src/extensibility/custom-commands/types.ts +113 -0
  376. package/src/extensibility/custom-tools/index.ts +7 -0
  377. package/src/extensibility/custom-tools/loader.ts +269 -0
  378. package/src/extensibility/custom-tools/types.ts +270 -0
  379. package/src/extensibility/custom-tools/wrapper.ts +47 -0
  380. package/src/extensibility/extensions/compact-handler.ts +40 -0
  381. package/src/extensibility/extensions/get-commands-handler.ts +78 -0
  382. package/src/extensibility/extensions/index.ts +16 -0
  383. package/src/extensibility/extensions/loader.ts +572 -0
  384. package/src/extensibility/extensions/runner.ts +922 -0
  385. package/src/extensibility/extensions/types.ts +1322 -0
  386. package/src/extensibility/extensions/wrapper.ts +223 -0
  387. package/src/extensibility/hooks/index.ts +5 -0
  388. package/src/extensibility/hooks/loader.ts +257 -0
  389. package/src/extensibility/hooks/runner.ts +425 -0
  390. package/src/extensibility/hooks/tool-wrapper.ts +107 -0
  391. package/src/extensibility/hooks/types.ts +606 -0
  392. package/src/extensibility/legacy-pi-ai-shim.ts +24 -0
  393. package/src/extensibility/legacy-pi-coding-agent-shim.ts +15 -0
  394. package/src/extensibility/plugins/doctor.ts +65 -0
  395. package/src/extensibility/plugins/git-url.ts +367 -0
  396. package/src/extensibility/plugins/index.ts +9 -0
  397. package/src/extensibility/plugins/installer.ts +192 -0
  398. package/src/extensibility/plugins/legacy-pi-compat.ts +682 -0
  399. package/src/extensibility/plugins/loader.ts +313 -0
  400. package/src/extensibility/plugins/manager.ts +827 -0
  401. package/src/extensibility/plugins/marketplace/cache.ts +136 -0
  402. package/src/extensibility/plugins/marketplace/fetcher.ts +317 -0
  403. package/src/extensibility/plugins/marketplace/index.ts +6 -0
  404. package/src/extensibility/plugins/marketplace/manager.ts +770 -0
  405. package/src/extensibility/plugins/marketplace/registry.ts +196 -0
  406. package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
  407. package/src/extensibility/plugins/marketplace/types.ts +191 -0
  408. package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
  409. package/src/extensibility/plugins/parser.ts +105 -0
  410. package/src/extensibility/plugins/types.ts +194 -0
  411. package/src/extensibility/shared-events.ts +343 -0
  412. package/src/extensibility/skills.ts +312 -0
  413. package/src/extensibility/slash-commands.ts +227 -0
  414. package/src/extensibility/tool-proxy.ts +25 -0
  415. package/src/extensibility/typebox.ts +418 -0
  416. package/src/extensibility/utils.ts +44 -0
  417. package/src/goals/index.ts +3 -0
  418. package/src/goals/runtime.ts +528 -0
  419. package/src/goals/state.ts +37 -0
  420. package/src/goals/tools/goal-tool.ts +251 -0
  421. package/src/hindsight/backend.ts +354 -0
  422. package/src/hindsight/bank.ts +156 -0
  423. package/src/hindsight/client.ts +598 -0
  424. package/src/hindsight/config.ts +175 -0
  425. package/src/hindsight/content.ts +210 -0
  426. package/src/hindsight/index.ts +8 -0
  427. package/src/hindsight/mental-models.ts +429 -0
  428. package/src/hindsight/seeds.json +32 -0
  429. package/src/hindsight/state.ts +488 -0
  430. package/src/hindsight/transcript.ts +71 -0
  431. package/src/index.ts +59 -0
  432. package/src/internal-urls/agent-protocol.ts +146 -0
  433. package/src/internal-urls/artifact-protocol.ts +107 -0
  434. package/src/internal-urls/docs-index.generated.ts +106 -0
  435. package/src/internal-urls/history-protocol.ts +113 -0
  436. package/src/internal-urls/index.ts +25 -0
  437. package/src/internal-urls/issue-pr-protocol.ts +584 -0
  438. package/src/internal-urls/json-query.ts +126 -0
  439. package/src/internal-urls/local-protocol.ts +287 -0
  440. package/src/internal-urls/mcp-protocol.ts +151 -0
  441. package/src/internal-urls/memory-protocol.ts +169 -0
  442. package/src/internal-urls/omp-protocol.ts +93 -0
  443. package/src/internal-urls/parse.ts +72 -0
  444. package/src/internal-urls/registry-helpers.ts +25 -0
  445. package/src/internal-urls/router.ts +105 -0
  446. package/src/internal-urls/rule-protocol.ts +45 -0
  447. package/src/internal-urls/skill-protocol.ts +96 -0
  448. package/src/internal-urls/types.ts +152 -0
  449. package/src/internal-urls/vault-protocol.ts +936 -0
  450. package/src/irc/bus.ts +292 -0
  451. package/src/lib/xai-http.ts +124 -0
  452. package/src/lsp/client.ts +1193 -0
  453. package/src/lsp/clients/biome-client.ts +264 -0
  454. package/src/lsp/clients/index.ts +50 -0
  455. package/src/lsp/clients/lsp-linter-client.ts +93 -0
  456. package/src/lsp/clients/swiftlint-client.ts +120 -0
  457. package/src/lsp/config.ts +502 -0
  458. package/src/lsp/defaults.json +493 -0
  459. package/src/lsp/diagnostics-ledger.ts +51 -0
  460. package/src/lsp/edits.ts +267 -0
  461. package/src/lsp/index.ts +2477 -0
  462. package/src/lsp/lspmux.ts +233 -0
  463. package/src/lsp/render.ts +694 -0
  464. package/src/lsp/startup-events.ts +13 -0
  465. package/src/lsp/types.ts +455 -0
  466. package/src/lsp/utils.ts +718 -0
  467. package/src/main.ts +1325 -0
  468. package/src/mcp/client.ts +484 -0
  469. package/src/mcp/config-writer.ts +225 -0
  470. package/src/mcp/config.ts +365 -0
  471. package/src/mcp/index.ts +29 -0
  472. package/src/mcp/json-rpc.ts +122 -0
  473. package/src/mcp/loader.ts +124 -0
  474. package/src/mcp/manager.ts +1275 -0
  475. package/src/mcp/oauth-discovery.ts +442 -0
  476. package/src/mcp/oauth-flow.ts +442 -0
  477. package/src/mcp/render.ts +132 -0
  478. package/src/mcp/smithery-auth.ts +104 -0
  479. package/src/mcp/smithery-connect.ts +145 -0
  480. package/src/mcp/smithery-registry.ts +477 -0
  481. package/src/mcp/timeout.ts +59 -0
  482. package/src/mcp/tool-bridge.ts +426 -0
  483. package/src/mcp/tool-cache.ts +117 -0
  484. package/src/mcp/transports/http.ts +519 -0
  485. package/src/mcp/transports/index.ts +6 -0
  486. package/src/mcp/transports/stdio.ts +528 -0
  487. package/src/mcp/types.ts +423 -0
  488. package/src/memories/index.ts +1150 -0
  489. package/src/memories/storage.ts +577 -0
  490. package/src/memory-backend/index.ts +18 -0
  491. package/src/memory-backend/local-backend.ts +39 -0
  492. package/src/memory-backend/off-backend.ts +25 -0
  493. package/src/memory-backend/resolve.ts +25 -0
  494. package/src/memory-backend/runtime.ts +66 -0
  495. package/src/memory-backend/types.ts +166 -0
  496. package/src/mnemopi/backend.ts +547 -0
  497. package/src/mnemopi/config.ts +160 -0
  498. package/src/mnemopi/index.ts +3 -0
  499. package/src/mnemopi/state.ts +584 -0
  500. package/src/modes/acp/acp-agent.ts +2407 -0
  501. package/src/modes/acp/acp-client-bridge.ts +154 -0
  502. package/src/modes/acp/acp-event-mapper.ts +929 -0
  503. package/src/modes/acp/acp-mode.ts +23 -0
  504. package/src/modes/acp/index.ts +2 -0
  505. package/src/modes/acp/terminal-auth.ts +37 -0
  506. package/src/modes/components/agent-dashboard.ts +1206 -0
  507. package/src/modes/components/agent-hub.ts +1071 -0
  508. package/src/modes/components/assistant-message.ts +307 -0
  509. package/src/modes/components/bash-execution.ts +220 -0
  510. package/src/modes/components/bordered-loader.ts +41 -0
  511. package/src/modes/components/branch-summary-message.ts +45 -0
  512. package/src/modes/components/btw-panel.ts +104 -0
  513. package/src/modes/components/chat-block.ts +111 -0
  514. package/src/modes/components/compaction-summary-message.ts +87 -0
  515. package/src/modes/components/copy-selector.ts +206 -0
  516. package/src/modes/components/countdown-timer.ts +75 -0
  517. package/src/modes/components/custom-editor.ts +398 -0
  518. package/src/modes/components/custom-message.ts +63 -0
  519. package/src/modes/components/diff.ts +277 -0
  520. package/src/modes/components/dynamic-border.ts +34 -0
  521. package/src/modes/components/error-banner.ts +33 -0
  522. package/src/modes/components/eval-execution.ts +158 -0
  523. package/src/modes/components/execution-shared.ts +101 -0
  524. package/src/modes/components/extensions/extension-dashboard.ts +399 -0
  525. package/src/modes/components/extensions/extension-list.ts +502 -0
  526. package/src/modes/components/extensions/index.ts +9 -0
  527. package/src/modes/components/extensions/inspector-panel.ts +317 -0
  528. package/src/modes/components/extensions/state-manager.ts +627 -0
  529. package/src/modes/components/extensions/types.ts +186 -0
  530. package/src/modes/components/footer.ts +274 -0
  531. package/src/modes/components/history-search.ts +280 -0
  532. package/src/modes/components/hook-editor.ts +167 -0
  533. package/src/modes/components/hook-input.ts +87 -0
  534. package/src/modes/components/hook-message.ts +66 -0
  535. package/src/modes/components/hook-selector.ts +660 -0
  536. package/src/modes/components/index.ts +38 -0
  537. package/src/modes/components/keybinding-hints.ts +65 -0
  538. package/src/modes/components/late-diagnostics-message.ts +60 -0
  539. package/src/modes/components/login-dialog.ts +164 -0
  540. package/src/modes/components/mcp-add-wizard.ts +1340 -0
  541. package/src/modes/components/message-frame.ts +88 -0
  542. package/src/modes/components/model-selector.ts +1271 -0
  543. package/src/modes/components/oauth-selector.ts +368 -0
  544. package/src/modes/components/omfg-panel.ts +141 -0
  545. package/src/modes/components/overlay-box.ts +108 -0
  546. package/src/modes/components/plan-review-overlay.ts +820 -0
  547. package/src/modes/components/plan-toc.ts +138 -0
  548. package/src/modes/components/plugin-selector.ts +95 -0
  549. package/src/modes/components/plugin-settings.ts +722 -0
  550. package/src/modes/components/queue-mode-selector.ts +56 -0
  551. package/src/modes/components/read-tool-group.ts +670 -0
  552. package/src/modes/components/segment-track.ts +52 -0
  553. package/src/modes/components/session-selector.ts +625 -0
  554. package/src/modes/components/settings-defs.ts +189 -0
  555. package/src/modes/components/settings-selector.ts +651 -0
  556. package/src/modes/components/show-images-selector.ts +45 -0
  557. package/src/modes/components/skill-message.ts +89 -0
  558. package/src/modes/components/status-line/component.ts +869 -0
  559. package/src/modes/components/status-line/context-thresholds.ts +79 -0
  560. package/src/modes/components/status-line/git-utils.ts +42 -0
  561. package/src/modes/components/status-line/index.ts +5 -0
  562. package/src/modes/components/status-line/presets.ts +106 -0
  563. package/src/modes/components/status-line/segments.ts +584 -0
  564. package/src/modes/components/status-line/separators.ts +55 -0
  565. package/src/modes/components/status-line/token-rate.ts +66 -0
  566. package/src/modes/components/status-line/types.ts +108 -0
  567. package/src/modes/components/theme-selector.ts +63 -0
  568. package/src/modes/components/thinking-selector.ts +52 -0
  569. package/src/modes/components/tiny-title-download-progress.ts +90 -0
  570. package/src/modes/components/tips.txt +19 -0
  571. package/src/modes/components/todo-reminder.ts +38 -0
  572. package/src/modes/components/tool-execution.ts +1024 -0
  573. package/src/modes/components/transcript-container.ts +608 -0
  574. package/src/modes/components/tree-selector.ts +978 -0
  575. package/src/modes/components/ttsr-notification.ts +122 -0
  576. package/src/modes/components/user-message-selector.ts +227 -0
  577. package/src/modes/components/user-message.ts +66 -0
  578. package/src/modes/components/visual-truncate.ts +63 -0
  579. package/src/modes/components/welcome.ts +493 -0
  580. package/src/modes/controllers/btw-controller.ts +105 -0
  581. package/src/modes/controllers/command-controller-shared.ts +109 -0
  582. package/src/modes/controllers/command-controller.ts +1566 -0
  583. package/src/modes/controllers/event-controller.ts +1054 -0
  584. package/src/modes/controllers/extension-ui-controller.ts +886 -0
  585. package/src/modes/controllers/input-controller.ts +1073 -0
  586. package/src/modes/controllers/mcp-command-controller.ts +2017 -0
  587. package/src/modes/controllers/omfg-controller.ts +283 -0
  588. package/src/modes/controllers/omfg-rule.ts +647 -0
  589. package/src/modes/controllers/selector-controller.ts +1108 -0
  590. package/src/modes/controllers/ssh-command-controller.ts +384 -0
  591. package/src/modes/controllers/streaming-reveal.ts +279 -0
  592. package/src/modes/controllers/tan-command-controller.ts +173 -0
  593. package/src/modes/controllers/todo-command-controller.ts +485 -0
  594. package/src/modes/data/emojis.json +1 -0
  595. package/src/modes/emoji-autocomplete.ts +285 -0
  596. package/src/modes/gradient-highlight.ts +87 -0
  597. package/src/modes/image-references.ts +117 -0
  598. package/src/modes/index.ts +17 -0
  599. package/src/modes/interactive-mode.ts +3370 -0
  600. package/src/modes/internal-url-autocomplete.ts +143 -0
  601. package/src/modes/loop-limit.ts +140 -0
  602. package/src/modes/magic-keywords.ts +20 -0
  603. package/src/modes/markdown-prose.ts +247 -0
  604. package/src/modes/oauth-manual-input.ts +69 -0
  605. package/src/modes/orchestrate.ts +42 -0
  606. package/src/modes/print-mode.ts +126 -0
  607. package/src/modes/prompt-action-autocomplete.ts +260 -0
  608. package/src/modes/rpc/host-tools.ts +186 -0
  609. package/src/modes/rpc/host-uris.ts +235 -0
  610. package/src/modes/rpc/rpc-client.ts +963 -0
  611. package/src/modes/rpc/rpc-mode.ts +947 -0
  612. package/src/modes/rpc/rpc-subagents.ts +265 -0
  613. package/src/modes/rpc/rpc-types.ts +458 -0
  614. package/src/modes/runtime-init.ts +116 -0
  615. package/src/modes/session-observer-registry.ts +146 -0
  616. package/src/modes/setup-version.ts +11 -0
  617. package/src/modes/setup-wizard/index.ts +99 -0
  618. package/src/modes/setup-wizard/lazy.ts +16 -0
  619. package/src/modes/setup-wizard/scenes/glyph.ts +96 -0
  620. package/src/modes/setup-wizard/scenes/outro.ts +35 -0
  621. package/src/modes/setup-wizard/scenes/providers.ts +69 -0
  622. package/src/modes/setup-wizard/scenes/sign-in.ts +205 -0
  623. package/src/modes/setup-wizard/scenes/splash.ts +201 -0
  624. package/src/modes/setup-wizard/scenes/theme.ts +299 -0
  625. package/src/modes/setup-wizard/scenes/types.ts +48 -0
  626. package/src/modes/setup-wizard/scenes/web-search.ts +129 -0
  627. package/src/modes/setup-wizard/wizard-overlay.ts +275 -0
  628. package/src/modes/shared.ts +47 -0
  629. package/src/modes/theme/dark.json +95 -0
  630. package/src/modes/theme/defaults/alabaster.json +93 -0
  631. package/src/modes/theme/defaults/amethyst.json +96 -0
  632. package/src/modes/theme/defaults/anthracite.json +93 -0
  633. package/src/modes/theme/defaults/basalt.json +91 -0
  634. package/src/modes/theme/defaults/birch.json +95 -0
  635. package/src/modes/theme/defaults/dark-abyss.json +91 -0
  636. package/src/modes/theme/defaults/dark-arctic.json +104 -0
  637. package/src/modes/theme/defaults/dark-aurora.json +95 -0
  638. package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
  639. package/src/modes/theme/defaults/dark-cavern.json +91 -0
  640. package/src/modes/theme/defaults/dark-copper.json +95 -0
  641. package/src/modes/theme/defaults/dark-cosmos.json +90 -0
  642. package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
  643. package/src/modes/theme/defaults/dark-dracula.json +98 -0
  644. package/src/modes/theme/defaults/dark-eclipse.json +91 -0
  645. package/src/modes/theme/defaults/dark-ember.json +95 -0
  646. package/src/modes/theme/defaults/dark-equinox.json +90 -0
  647. package/src/modes/theme/defaults/dark-forest.json +96 -0
  648. package/src/modes/theme/defaults/dark-github.json +105 -0
  649. package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
  650. package/src/modes/theme/defaults/dark-lavender.json +95 -0
  651. package/src/modes/theme/defaults/dark-lunar.json +89 -0
  652. package/src/modes/theme/defaults/dark-midnight.json +95 -0
  653. package/src/modes/theme/defaults/dark-monochrome.json +94 -0
  654. package/src/modes/theme/defaults/dark-monokai.json +98 -0
  655. package/src/modes/theme/defaults/dark-nebula.json +90 -0
  656. package/src/modes/theme/defaults/dark-nord.json +97 -0
  657. package/src/modes/theme/defaults/dark-ocean.json +101 -0
  658. package/src/modes/theme/defaults/dark-one.json +100 -0
  659. package/src/modes/theme/defaults/dark-poimandres.json +142 -0
  660. package/src/modes/theme/defaults/dark-rainforest.json +91 -0
  661. package/src/modes/theme/defaults/dark-reef.json +91 -0
  662. package/src/modes/theme/defaults/dark-retro.json +92 -0
  663. package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
  664. package/src/modes/theme/defaults/dark-sakura.json +95 -0
  665. package/src/modes/theme/defaults/dark-slate.json +95 -0
  666. package/src/modes/theme/defaults/dark-solarized.json +97 -0
  667. package/src/modes/theme/defaults/dark-solstice.json +90 -0
  668. package/src/modes/theme/defaults/dark-starfall.json +91 -0
  669. package/src/modes/theme/defaults/dark-sunset.json +99 -0
  670. package/src/modes/theme/defaults/dark-swamp.json +90 -0
  671. package/src/modes/theme/defaults/dark-synthwave.json +103 -0
  672. package/src/modes/theme/defaults/dark-taiga.json +91 -0
  673. package/src/modes/theme/defaults/dark-terminal.json +95 -0
  674. package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
  675. package/src/modes/theme/defaults/dark-tundra.json +91 -0
  676. package/src/modes/theme/defaults/dark-twilight.json +91 -0
  677. package/src/modes/theme/defaults/dark-volcanic.json +91 -0
  678. package/src/modes/theme/defaults/graphite.json +92 -0
  679. package/src/modes/theme/defaults/index.ts +199 -0
  680. package/src/modes/theme/defaults/light-arctic.json +107 -0
  681. package/src/modes/theme/defaults/light-aurora-day.json +91 -0
  682. package/src/modes/theme/defaults/light-canyon.json +91 -0
  683. package/src/modes/theme/defaults/light-catppuccin.json +106 -0
  684. package/src/modes/theme/defaults/light-cirrus.json +90 -0
  685. package/src/modes/theme/defaults/light-coral.json +95 -0
  686. package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
  687. package/src/modes/theme/defaults/light-dawn.json +90 -0
  688. package/src/modes/theme/defaults/light-dunes.json +91 -0
  689. package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
  690. package/src/modes/theme/defaults/light-forest.json +100 -0
  691. package/src/modes/theme/defaults/light-frost.json +95 -0
  692. package/src/modes/theme/defaults/light-github.json +115 -0
  693. package/src/modes/theme/defaults/light-glacier.json +91 -0
  694. package/src/modes/theme/defaults/light-gruvbox.json +108 -0
  695. package/src/modes/theme/defaults/light-haze.json +90 -0
  696. package/src/modes/theme/defaults/light-honeycomb.json +95 -0
  697. package/src/modes/theme/defaults/light-lagoon.json +91 -0
  698. package/src/modes/theme/defaults/light-lavender.json +95 -0
  699. package/src/modes/theme/defaults/light-meadow.json +91 -0
  700. package/src/modes/theme/defaults/light-mint.json +95 -0
  701. package/src/modes/theme/defaults/light-monochrome.json +101 -0
  702. package/src/modes/theme/defaults/light-ocean.json +99 -0
  703. package/src/modes/theme/defaults/light-one.json +99 -0
  704. package/src/modes/theme/defaults/light-opal.json +91 -0
  705. package/src/modes/theme/defaults/light-orchard.json +91 -0
  706. package/src/modes/theme/defaults/light-paper.json +95 -0
  707. package/src/modes/theme/defaults/light-poimandres.json +142 -0
  708. package/src/modes/theme/defaults/light-prism.json +90 -0
  709. package/src/modes/theme/defaults/light-retro.json +98 -0
  710. package/src/modes/theme/defaults/light-sand.json +95 -0
  711. package/src/modes/theme/defaults/light-savanna.json +91 -0
  712. package/src/modes/theme/defaults/light-solarized.json +102 -0
  713. package/src/modes/theme/defaults/light-soleil.json +90 -0
  714. package/src/modes/theme/defaults/light-sunset.json +99 -0
  715. package/src/modes/theme/defaults/light-synthwave.json +98 -0
  716. package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
  717. package/src/modes/theme/defaults/light-wetland.json +91 -0
  718. package/src/modes/theme/defaults/light-zenith.json +89 -0
  719. package/src/modes/theme/defaults/limestone.json +94 -0
  720. package/src/modes/theme/defaults/mahogany.json +97 -0
  721. package/src/modes/theme/defaults/marble.json +93 -0
  722. package/src/modes/theme/defaults/obsidian.json +91 -0
  723. package/src/modes/theme/defaults/onyx.json +91 -0
  724. package/src/modes/theme/defaults/pearl.json +93 -0
  725. package/src/modes/theme/defaults/porcelain.json +91 -0
  726. package/src/modes/theme/defaults/quartz.json +96 -0
  727. package/src/modes/theme/defaults/sandstone.json +95 -0
  728. package/src/modes/theme/defaults/titanium.json +90 -0
  729. package/src/modes/theme/light.json +93 -0
  730. package/src/modes/theme/mermaid-cache.ts +29 -0
  731. package/src/modes/theme/shimmer.ts +235 -0
  732. package/src/modes/theme/theme-schema.json +459 -0
  733. package/src/modes/theme/theme.ts +2676 -0
  734. package/src/modes/turn-budget.ts +31 -0
  735. package/src/modes/types.ts +359 -0
  736. package/src/modes/ultrathink.ts +41 -0
  737. package/src/modes/utils/context-usage.ts +339 -0
  738. package/src/modes/utils/copy-targets.ts +360 -0
  739. package/src/modes/utils/hotkeys-markdown.ts +61 -0
  740. package/src/modes/utils/keybinding-matchers.ts +51 -0
  741. package/src/modes/utils/tools-markdown.ts +27 -0
  742. package/src/modes/utils/ui-helpers.ts +801 -0
  743. package/src/modes/workflow.ts +42 -0
  744. package/src/plan-mode/approved-plan.ts +186 -0
  745. package/src/plan-mode/plan-handoff.ts +37 -0
  746. package/src/plan-mode/plan-protection.ts +31 -0
  747. package/src/plan-mode/state.ts +6 -0
  748. package/src/priority.json +41 -0
  749. package/src/prompts/agents/designer.md +66 -0
  750. package/src/prompts/agents/explore.md +58 -0
  751. package/src/prompts/agents/frontmatter.md +11 -0
  752. package/src/prompts/agents/init.md +33 -0
  753. package/src/prompts/agents/librarian.md +119 -0
  754. package/src/prompts/agents/oracle.md +55 -0
  755. package/src/prompts/agents/plan.md +48 -0
  756. package/src/prompts/agents/reviewer.md +140 -0
  757. package/src/prompts/agents/task.md +16 -0
  758. package/src/prompts/ci-green-request.md +36 -0
  759. package/src/prompts/dry-balance-bench.md +8 -0
  760. package/src/prompts/goals/goal-budget-limit.md +16 -0
  761. package/src/prompts/goals/goal-continuation.md +28 -0
  762. package/src/prompts/goals/goal-mode-active.md +23 -0
  763. package/src/prompts/memories/consolidation.md +30 -0
  764. package/src/prompts/memories/read-path.md +11 -0
  765. package/src/prompts/memories/stage_one_input.md +6 -0
  766. package/src/prompts/memories/stage_one_system.md +21 -0
  767. package/src/prompts/review-custom-request.md +22 -0
  768. package/src/prompts/review-headless-request.md +16 -0
  769. package/src/prompts/review-request.md +69 -0
  770. package/src/prompts/steering/user-interjection.md +10 -0
  771. package/src/prompts/system/agent-creation-architect.md +50 -0
  772. package/src/prompts/system/agent-creation-user.md +6 -0
  773. package/src/prompts/system/auto-continue.md +1 -0
  774. package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
  775. package/src/prompts/system/auto-thinking-difficulty.md +12 -0
  776. package/src/prompts/system/background-tan-dispatch.md +8 -0
  777. package/src/prompts/system/btw-user.md +8 -0
  778. package/src/prompts/system/commit-message-system.md +14 -0
  779. package/src/prompts/system/custom-system-prompt.md +64 -0
  780. package/src/prompts/system/eager-todo.md +13 -0
  781. package/src/prompts/system/empty-stop-retry.md +6 -0
  782. package/src/prompts/system/irc-incoming.md +7 -0
  783. package/src/prompts/system/manual-continue.md +7 -0
  784. package/src/prompts/system/memory-consolidation-system.md +8 -0
  785. package/src/prompts/system/memory-extraction-system.md +26 -0
  786. package/src/prompts/system/omfg-user.md +50 -0
  787. package/src/prompts/system/orchestrate-notice.md +40 -0
  788. package/src/prompts/system/plan-mode-active.md +109 -0
  789. package/src/prompts/system/plan-mode-approved.md +25 -0
  790. package/src/prompts/system/plan-mode-compact-instructions.md +16 -0
  791. package/src/prompts/system/plan-mode-reference.md +11 -0
  792. package/src/prompts/system/plan-mode-subagent.md +33 -0
  793. package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
  794. package/src/prompts/system/project-prompt.md +52 -0
  795. package/src/prompts/system/subagent-system-prompt.md +64 -0
  796. package/src/prompts/system/subagent-user-prompt.md +3 -0
  797. package/src/prompts/system/subagent-yield-reminder.md +12 -0
  798. package/src/prompts/system/system-prompt.md +258 -0
  799. package/src/prompts/system/tiny-title-system.md +8 -0
  800. package/src/prompts/system/title-system.md +16 -0
  801. package/src/prompts/system/ttsr-interrupt.md +7 -0
  802. package/src/prompts/system/ttsr-tool-reminder.md +5 -0
  803. package/src/prompts/system/ultrathink-notice.md +3 -0
  804. package/src/prompts/system/web-search.md +25 -0
  805. package/src/prompts/system/workflow-notice.md +70 -0
  806. package/src/prompts/tools/apply-patch.md +65 -0
  807. package/src/prompts/tools/ask.md +30 -0
  808. package/src/prompts/tools/ast-edit.md +39 -0
  809. package/src/prompts/tools/ast-grep.md +42 -0
  810. package/src/prompts/tools/async-result.md +8 -0
  811. package/src/prompts/tools/bash.md +46 -0
  812. package/src/prompts/tools/browser.md +73 -0
  813. package/src/prompts/tools/checkpoint.md +16 -0
  814. package/src/prompts/tools/debug.md +34 -0
  815. package/src/prompts/tools/eval.md +92 -0
  816. package/src/prompts/tools/find.md +36 -0
  817. package/src/prompts/tools/github.md +21 -0
  818. package/src/prompts/tools/goal.md +18 -0
  819. package/src/prompts/tools/image-gen.md +7 -0
  820. package/src/prompts/tools/inspect-image-system.md +20 -0
  821. package/src/prompts/tools/inspect-image.md +32 -0
  822. package/src/prompts/tools/irc.md +59 -0
  823. package/src/prompts/tools/job.md +19 -0
  824. package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
  825. package/src/prompts/tools/lsp.md +42 -0
  826. package/src/prompts/tools/memory-edit.md +8 -0
  827. package/src/prompts/tools/patch.md +70 -0
  828. package/src/prompts/tools/read.md +84 -0
  829. package/src/prompts/tools/recall.md +5 -0
  830. package/src/prompts/tools/reflect.md +5 -0
  831. package/src/prompts/tools/render-mermaid.md +9 -0
  832. package/src/prompts/tools/replace.md +30 -0
  833. package/src/prompts/tools/resolve.md +9 -0
  834. package/src/prompts/tools/retain.md +6 -0
  835. package/src/prompts/tools/rewind.md +13 -0
  836. package/src/prompts/tools/search-tool-bm25.md +32 -0
  837. package/src/prompts/tools/search.md +24 -0
  838. package/src/prompts/tools/ssh.md +31 -0
  839. package/src/prompts/tools/task-summary.md +17 -0
  840. package/src/prompts/tools/task.md +88 -0
  841. package/src/prompts/tools/todo.md +62 -0
  842. package/src/prompts/tools/web-search.md +10 -0
  843. package/src/prompts/tools/write.md +14 -0
  844. package/src/registry/agent-lifecycle.ts +218 -0
  845. package/src/registry/agent-registry.ts +151 -0
  846. package/src/sdk.ts +2558 -0
  847. package/src/secrets/index.ts +123 -0
  848. package/src/secrets/obfuscator.ts +298 -0
  849. package/src/secrets/regex.ts +21 -0
  850. package/src/session/agent-session.ts +10121 -0
  851. package/src/session/agent-storage.ts +455 -0
  852. package/src/session/artifacts.ts +135 -0
  853. package/src/session/auth-broker-config.ts +131 -0
  854. package/src/session/auth-storage.ts +29 -0
  855. package/src/session/blob-store.ts +255 -0
  856. package/src/session/client-bridge.ts +85 -0
  857. package/src/session/history-storage.ts +348 -0
  858. package/src/session/indexed-session-storage.ts +430 -0
  859. package/src/session/messages.ts +541 -0
  860. package/src/session/redis-session-storage.ts +170 -0
  861. package/src/session/session-dump-format.ts +209 -0
  862. package/src/session/session-history-format.ts +246 -0
  863. package/src/session/session-manager.ts +3676 -0
  864. package/src/session/session-storage.ts +529 -0
  865. package/src/session/shake-types.ts +43 -0
  866. package/src/session/sql-session-storage.ts +314 -0
  867. package/src/session/streaming-output.ts +1330 -0
  868. package/src/session/tool-choice-queue.ts +213 -0
  869. package/src/session/yield-queue.ts +173 -0
  870. package/src/slash-commands/acp-builtins.ts +70 -0
  871. package/src/slash-commands/builtin-registry.ts +1798 -0
  872. package/src/slash-commands/helpers/context-report.ts +39 -0
  873. package/src/slash-commands/helpers/format.ts +46 -0
  874. package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
  875. package/src/slash-commands/helpers/mcp.ts +532 -0
  876. package/src/slash-commands/helpers/parse.ts +85 -0
  877. package/src/slash-commands/helpers/ssh.ts +195 -0
  878. package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
  879. package/src/slash-commands/helpers/todo.ts +279 -0
  880. package/src/slash-commands/helpers/usage-report.ts +95 -0
  881. package/src/slash-commands/marketplace-install-parser.ts +99 -0
  882. package/src/slash-commands/types.ts +135 -0
  883. package/src/ssh/config-writer.ts +183 -0
  884. package/src/ssh/connection-manager.ts +509 -0
  885. package/src/ssh/ssh-executor.ts +189 -0
  886. package/src/ssh/sshfs-mount.ts +140 -0
  887. package/src/ssh/utils.ts +8 -0
  888. package/src/stt/downloader.ts +71 -0
  889. package/src/stt/index.ts +3 -0
  890. package/src/stt/recorder.ts +351 -0
  891. package/src/stt/setup.ts +52 -0
  892. package/src/stt/stt-controller.ts +160 -0
  893. package/src/stt/transcribe.py +70 -0
  894. package/src/stt/transcriber.ts +91 -0
  895. package/src/stubs/natives/index.ts +814 -0
  896. package/src/stubs/natives/package.json +7 -0
  897. package/src/stubs/tui/index.ts +282 -0
  898. package/src/stubs/tui/package.json +7 -0
  899. package/src/system-prompt.ts +611 -0
  900. package/src/task/agents.ts +167 -0
  901. package/src/task/commands.ts +132 -0
  902. package/src/task/discovery.ts +122 -0
  903. package/src/task/executor.ts +2133 -0
  904. package/src/task/index.ts +1419 -0
  905. package/src/task/name-generator.ts +1577 -0
  906. package/src/task/omp-command.ts +26 -0
  907. package/src/task/output-manager.ts +88 -0
  908. package/src/task/parallel.ts +116 -0
  909. package/src/task/render.ts +1381 -0
  910. package/src/task/repair-args.ts +129 -0
  911. package/src/task/subprocess-tool-registry.ts +88 -0
  912. package/src/task/types.ts +336 -0
  913. package/src/task/worktree.ts +514 -0
  914. package/src/telemetry-export.ts +144 -0
  915. package/src/thinking.ts +167 -0
  916. package/src/tiny/compiled-runtime.ts +179 -0
  917. package/src/tiny/device.ts +111 -0
  918. package/src/tiny/dtype.ts +101 -0
  919. package/src/tiny/models.ts +242 -0
  920. package/src/tiny/text.ts +165 -0
  921. package/src/tiny/title-client.ts +543 -0
  922. package/src/tiny/title-protocol.ts +56 -0
  923. package/src/tiny/worker.ts +568 -0
  924. package/src/tool-discovery/mode.ts +24 -0
  925. package/src/tool-discovery/tool-index.ts +256 -0
  926. package/src/tools/approval.ts +189 -0
  927. package/src/tools/archive-reader.ts +721 -0
  928. package/src/tools/ask.ts +928 -0
  929. package/src/tools/ast-edit.ts +642 -0
  930. package/src/tools/ast-grep.ts +452 -0
  931. package/src/tools/auto-generated-guard.ts +322 -0
  932. package/src/tools/bash-command-fixup.ts +37 -0
  933. package/src/tools/bash-interactive.ts +408 -0
  934. package/src/tools/bash-interceptor.ts +67 -0
  935. package/src/tools/bash-pty-selection.ts +14 -0
  936. package/src/tools/bash-skill-urls.ts +248 -0
  937. package/src/tools/bash.ts +1386 -0
  938. package/src/tools/browser/attach.ts +175 -0
  939. package/src/tools/browser/launch.ts +660 -0
  940. package/src/tools/browser/readable.ts +112 -0
  941. package/src/tools/browser/registry.ts +197 -0
  942. package/src/tools/browser/render.ts +216 -0
  943. package/src/tools/browser/tab-protocol.ts +105 -0
  944. package/src/tools/browser/tab-supervisor.ts +628 -0
  945. package/src/tools/browser/tab-worker-entry.ts +21 -0
  946. package/src/tools/browser/tab-worker.ts +1226 -0
  947. package/src/tools/browser.ts +343 -0
  948. package/src/tools/checkpoint.ts +136 -0
  949. package/src/tools/conflict-detect.ts +718 -0
  950. package/src/tools/context.ts +39 -0
  951. package/src/tools/debug.ts +1067 -0
  952. package/src/tools/eval-backends.ts +27 -0
  953. package/src/tools/eval-render.ts +752 -0
  954. package/src/tools/eval.ts +577 -0
  955. package/src/tools/fetch.ts +1926 -0
  956. package/src/tools/file-recorder.ts +35 -0
  957. package/src/tools/find.ts +609 -0
  958. package/src/tools/fs-cache-invalidation.ts +28 -0
  959. package/src/tools/gh-cache-invalidation.ts +255 -0
  960. package/src/tools/gh-format.ts +12 -0
  961. package/src/tools/gh-renderer.ts +481 -0
  962. package/src/tools/gh.ts +3720 -0
  963. package/src/tools/github-cache.ts +637 -0
  964. package/src/tools/grouped-file-output.ts +210 -0
  965. package/src/tools/image-gen.ts +1517 -0
  966. package/src/tools/index.ts +599 -0
  967. package/src/tools/inspect-image-renderer.ts +132 -0
  968. package/src/tools/inspect-image.ts +174 -0
  969. package/src/tools/irc.ts +723 -0
  970. package/src/tools/job.ts +557 -0
  971. package/src/tools/json-tree.ts +243 -0
  972. package/src/tools/jtd-to-json-schema.ts +219 -0
  973. package/src/tools/jtd-to-typescript.ts +136 -0
  974. package/src/tools/jtd-utils.ts +102 -0
  975. package/src/tools/list-limit.ts +40 -0
  976. package/src/tools/match-line-format.ts +20 -0
  977. package/src/tools/memory-edit.ts +59 -0
  978. package/src/tools/memory-recall.ts +100 -0
  979. package/src/tools/memory-reflect.ts +88 -0
  980. package/src/tools/memory-render.ts +202 -0
  981. package/src/tools/memory-retain.ts +91 -0
  982. package/src/tools/output-meta.ts +754 -0
  983. package/src/tools/output-schema-validator.ts +132 -0
  984. package/src/tools/path-utils.ts +1054 -0
  985. package/src/tools/plan-mode-guard.ts +108 -0
  986. package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
  987. package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
  988. package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
  989. package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
  990. package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
  991. package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
  992. package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
  993. package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
  994. package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
  995. package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
  996. package/src/tools/puppeteer/10_stealth_plugins.txt +206 -0
  997. package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
  998. package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
  999. package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
  1000. package/src/tools/read.ts +2929 -0
  1001. package/src/tools/render-mermaid.ts +69 -0
  1002. package/src/tools/render-utils.ts +838 -0
  1003. package/src/tools/renderers.ts +77 -0
  1004. package/src/tools/report-tool-issue.ts +534 -0
  1005. package/src/tools/resolve.ts +276 -0
  1006. package/src/tools/review.ts +253 -0
  1007. package/src/tools/search-tool-bm25.ts +351 -0
  1008. package/src/tools/search.ts +1580 -0
  1009. package/src/tools/sqlite-reader.ts +828 -0
  1010. package/src/tools/ssh.ts +349 -0
  1011. package/src/tools/todo.ts +982 -0
  1012. package/src/tools/tool-errors.ts +62 -0
  1013. package/src/tools/tool-result.ts +94 -0
  1014. package/src/tools/tool-timeouts.ts +30 -0
  1015. package/src/tools/tts.ts +133 -0
  1016. package/src/tools/write.ts +1217 -0
  1017. package/src/tools/yield.ts +269 -0
  1018. package/src/tui/code-cell.ts +216 -0
  1019. package/src/tui/file-list.ts +55 -0
  1020. package/src/tui/hyperlink.ts +175 -0
  1021. package/src/tui/index.ts +12 -0
  1022. package/src/tui/output-block.ts +240 -0
  1023. package/src/tui/status-line.ts +54 -0
  1024. package/src/tui/tree-list.ts +84 -0
  1025. package/src/tui/types.ts +15 -0
  1026. package/src/tui/utils.ts +103 -0
  1027. package/src/utils/block-context.ts +312 -0
  1028. package/src/utils/changelog.ts +132 -0
  1029. package/src/utils/clipboard.ts +193 -0
  1030. package/src/utils/command-args.ts +76 -0
  1031. package/src/utils/commit-message-generator.ts +151 -0
  1032. package/src/utils/edit-mode.ts +41 -0
  1033. package/src/utils/enhanced-paste.ts +230 -0
  1034. package/src/utils/event-bus.ts +33 -0
  1035. package/src/utils/external-editor.ts +65 -0
  1036. package/src/utils/file-display-mode.ts +45 -0
  1037. package/src/utils/file-mentions.ts +281 -0
  1038. package/src/utils/git.ts +1833 -0
  1039. package/src/utils/image-loading.ts +132 -0
  1040. package/src/utils/image-resize.ts +309 -0
  1041. package/src/utils/jj.ts +248 -0
  1042. package/src/utils/lang-from-path.ts +239 -0
  1043. package/src/utils/markit.ts +89 -0
  1044. package/src/utils/open.ts +55 -0
  1045. package/src/utils/session-color.ts +68 -0
  1046. package/src/utils/shell-snapshot.ts +187 -0
  1047. package/src/utils/sixel.ts +69 -0
  1048. package/src/utils/title-generator.ts +373 -0
  1049. package/src/utils/tool-choice.ts +33 -0
  1050. package/src/utils/tools-manager.ts +363 -0
  1051. package/src/web/kagi.ts +305 -0
  1052. package/src/web/parallel.ts +353 -0
  1053. package/src/web/scrapers/artifacthub.ts +207 -0
  1054. package/src/web/scrapers/arxiv.ts +83 -0
  1055. package/src/web/scrapers/aur.ts +162 -0
  1056. package/src/web/scrapers/biorxiv.ts +133 -0
  1057. package/src/web/scrapers/bluesky.ts +262 -0
  1058. package/src/web/scrapers/brew.ts +172 -0
  1059. package/src/web/scrapers/cheatsh.ts +68 -0
  1060. package/src/web/scrapers/chocolatey.ts +196 -0
  1061. package/src/web/scrapers/choosealicense.ts +95 -0
  1062. package/src/web/scrapers/cisa-kev.ts +87 -0
  1063. package/src/web/scrapers/clojars.ts +154 -0
  1064. package/src/web/scrapers/coingecko.ts +177 -0
  1065. package/src/web/scrapers/crates-io.ts +97 -0
  1066. package/src/web/scrapers/crossref.ts +136 -0
  1067. package/src/web/scrapers/devto.ts +147 -0
  1068. package/src/web/scrapers/discogs.ts +306 -0
  1069. package/src/web/scrapers/discourse.ts +197 -0
  1070. package/src/web/scrapers/dockerhub.ts +138 -0
  1071. package/src/web/scrapers/docs-rs.ts +653 -0
  1072. package/src/web/scrapers/fdroid.ts +134 -0
  1073. package/src/web/scrapers/firefox-addons.ts +191 -0
  1074. package/src/web/scrapers/flathub.ts +223 -0
  1075. package/src/web/scrapers/github-gist.ts +58 -0
  1076. package/src/web/scrapers/github.ts +704 -0
  1077. package/src/web/scrapers/gitlab.ts +401 -0
  1078. package/src/web/scrapers/go-pkg.ts +266 -0
  1079. package/src/web/scrapers/hackage.ts +140 -0
  1080. package/src/web/scrapers/hackernews.ts +189 -0
  1081. package/src/web/scrapers/hex.ts +105 -0
  1082. package/src/web/scrapers/huggingface.ts +321 -0
  1083. package/src/web/scrapers/iacr.ts +89 -0
  1084. package/src/web/scrapers/index.ts +252 -0
  1085. package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
  1086. package/src/web/scrapers/lemmy.ts +203 -0
  1087. package/src/web/scrapers/lobsters.ts +175 -0
  1088. package/src/web/scrapers/mastodon.ts +292 -0
  1089. package/src/web/scrapers/maven.ts +138 -0
  1090. package/src/web/scrapers/mdn.ts +173 -0
  1091. package/src/web/scrapers/metacpan.ts +222 -0
  1092. package/src/web/scrapers/musicbrainz.ts +250 -0
  1093. package/src/web/scrapers/npm.ts +98 -0
  1094. package/src/web/scrapers/nuget.ts +183 -0
  1095. package/src/web/scrapers/nvd.ts +222 -0
  1096. package/src/web/scrapers/ollama.ts +239 -0
  1097. package/src/web/scrapers/open-vsx.ts +106 -0
  1098. package/src/web/scrapers/opencorporates.ts +292 -0
  1099. package/src/web/scrapers/openlibrary.ts +336 -0
  1100. package/src/web/scrapers/orcid.ts +286 -0
  1101. package/src/web/scrapers/osv.ts +176 -0
  1102. package/src/web/scrapers/packagist.ts +160 -0
  1103. package/src/web/scrapers/pub-dev.ts +143 -0
  1104. package/src/web/scrapers/pubmed.ts +211 -0
  1105. package/src/web/scrapers/pypi.ts +112 -0
  1106. package/src/web/scrapers/rawg.ts +110 -0
  1107. package/src/web/scrapers/readthedocs.ts +120 -0
  1108. package/src/web/scrapers/reddit.ts +95 -0
  1109. package/src/web/scrapers/repology.ts +251 -0
  1110. package/src/web/scrapers/rfc.ts +201 -0
  1111. package/src/web/scrapers/rubygems.ts +103 -0
  1112. package/src/web/scrapers/searchcode.ts +189 -0
  1113. package/src/web/scrapers/sec-edgar.ts +261 -0
  1114. package/src/web/scrapers/semantic-scholar.ts +171 -0
  1115. package/src/web/scrapers/snapcraft.ts +187 -0
  1116. package/src/web/scrapers/sourcegraph.ts +336 -0
  1117. package/src/web/scrapers/spdx.ts +108 -0
  1118. package/src/web/scrapers/spotify.ts +198 -0
  1119. package/src/web/scrapers/stackoverflow.ts +120 -0
  1120. package/src/web/scrapers/terraform.ts +277 -0
  1121. package/src/web/scrapers/tldr.ts +47 -0
  1122. package/src/web/scrapers/twitter.ts +94 -0
  1123. package/src/web/scrapers/types.ts +397 -0
  1124. package/src/web/scrapers/utils.ts +109 -0
  1125. package/src/web/scrapers/vimeo.ts +133 -0
  1126. package/src/web/scrapers/vscode-marketplace.ts +187 -0
  1127. package/src/web/scrapers/w3c.ts +156 -0
  1128. package/src/web/scrapers/wikidata.ts +344 -0
  1129. package/src/web/scrapers/wikipedia.ts +84 -0
  1130. package/src/web/scrapers/youtube.ts +325 -0
  1131. package/src/web/search/index.ts +292 -0
  1132. package/src/web/search/provider.ts +157 -0
  1133. package/src/web/search/providers/anthropic.ts +318 -0
  1134. package/src/web/search/providers/base.ts +89 -0
  1135. package/src/web/search/providers/brave.ts +152 -0
  1136. package/src/web/search/providers/codex.ts +591 -0
  1137. package/src/web/search/providers/exa.ts +400 -0
  1138. package/src/web/search/providers/gemini.ts +460 -0
  1139. package/src/web/search/providers/jina.ts +111 -0
  1140. package/src/web/search/providers/kagi.ts +86 -0
  1141. package/src/web/search/providers/kimi.ts +196 -0
  1142. package/src/web/search/providers/parallel.ts +225 -0
  1143. package/src/web/search/providers/perplexity.ts +730 -0
  1144. package/src/web/search/providers/searxng.ts +313 -0
  1145. package/src/web/search/providers/synthetic.ts +114 -0
  1146. package/src/web/search/providers/tavily.ts +176 -0
  1147. package/src/web/search/providers/utils.ts +128 -0
  1148. package/src/web/search/providers/zai.ts +333 -0
  1149. package/src/web/search/render.ts +262 -0
  1150. package/src/web/search/types.ts +482 -0
  1151. package/src/web/search/utils.ts +17 -0
  1152. package/src/workspace-tree.ts +286 -0
@@ -0,0 +1,1580 @@
1
+ import { mkdtemp, rm, stat, writeFile } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import * as path from "node:path";
4
+ import { formatHashlineHeader } from "@oh-my-pi/hashline";
5
+ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
6
+ import { type GrepMatch, GrepOutputMode, type GrepResult, grep } from './stubs/natives/index.ts';
7
+ import type { Component } from './stubs/tui/index.ts';
8
+ import { Text } from './stubs/tui/index.ts';
9
+ import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
10
+ import * as z from "zod/v4";
11
+ import { recordFileSnapshot } from "../edit/file-snapshot-store";
12
+ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
13
+ import type { LocalProtocolOptions } from "../internal-urls/local-protocol";
14
+ import { InternalUrlRouter } from "../internal-urls/router";
15
+ import type { InternalResource, ResolveContext } from "../internal-urls/types";
16
+ import type { Theme } from "../modes/theme/theme";
17
+ import searchDescription from "../prompts/tools/search.md" with { type: "text" };
18
+ import { DEFAULT_MAX_COLUMN, type TruncationResult, truncateHead, truncateLine } from "../session/streaming-output";
19
+ import {
20
+ Ellipsis,
21
+ fileHyperlink,
22
+ getTreeBranch,
23
+ getTreeContinuePrefix,
24
+ renderStatusLine,
25
+ renderTreeList,
26
+ truncateToWidth,
27
+ tryResolveInternalUrlSync,
28
+ uriHyperlink,
29
+ } from "../tui";
30
+ import { resolveFileDisplayMode } from "../utils/file-display-mode";
31
+ import type { ToolSession } from ".";
32
+ import {
33
+ type ArchiveReader,
34
+ type ExtractedArchiveFile,
35
+ openArchive,
36
+ parseArchivePathCandidates,
37
+ } from "./archive-reader";
38
+ import { createFileRecorder, formatResultPath } from "./file-recorder";
39
+ import { classifyGroupedLines, formatGroupedFiles, groupLineIndicesByBlank } from "./grouped-file-output";
40
+ import { formatMatchLine } from "./match-line-format";
41
+ import type { OutputMeta } from "./output-meta";
42
+ import {
43
+ expandDelimitedPathEntries,
44
+ hasGlobPathChars,
45
+ isLineInRanges,
46
+ type LineRange,
47
+ parseLineRanges,
48
+ type ResolvedSearchTarget,
49
+ resolveReadPath,
50
+ resolveToolSearchScope,
51
+ selectorLineRanges,
52
+ splitInternalUrlSel,
53
+ splitPathAndSel,
54
+ } from "./path-utils";
55
+ import {
56
+ createCachedComponent,
57
+ formatCodeFrameLine,
58
+ formatCount,
59
+ formatEmptyMessage,
60
+ formatErrorMessage,
61
+ formatMoreItems,
62
+ PREVIEW_LIMITS,
63
+ replaceTabs,
64
+ } from "./render-utils";
65
+ import { ToolError } from "./tool-errors";
66
+ import { toolResult } from "./tool-result";
67
+
68
+ const searchPathEntrySchema = z
69
+ .string()
70
+ .describe(
71
+ 'file, directory, glob, internal URL, or "<file>:<lines>" selector (e.g. "src/foo.ts:50-100", "src/foo.ts:50+10", "src/foo.ts:50-100,200-300")',
72
+ );
73
+ const searchSchema = z
74
+ .object({
75
+ pattern: z.string().describe("regex pattern"),
76
+ paths: z
77
+ .union([searchPathEntrySchema, z.array(searchPathEntrySchema)])
78
+ .optional()
79
+ .describe(
80
+ 'file, directory, glob, internal URL, or array of those to search; append `:<lines>` to scope a file to specific line ranges. Omitted or empty -> searches the workspace root (".")',
81
+ ),
82
+ i: z.boolean().optional().describe("case-insensitive search"),
83
+ gitignore: z.boolean().optional().describe("respect gitignore"),
84
+ skip: z
85
+ .number()
86
+ .nullable()
87
+ .optional()
88
+ .describe("files to skip before collecting results — use to paginate when the prior call hit the file limit"),
89
+ })
90
+ .strict();
91
+
92
+ export type SearchToolInput = z.infer<typeof searchSchema>;
93
+ export function toPathList(input: string | string[] | undefined): string[] {
94
+ return typeof input === "string" ? [input] : (input ?? []);
95
+ }
96
+
97
+ /** Maximum number of distinct files surfaced in a single response. The
98
+ * agent paginates further pages via `skip`. */
99
+ export const DEFAULT_FILE_LIMIT = 20;
100
+ /** Per-file match cap for multi-file searches — keeps a single hot file
101
+ * from crowding out diverse hits. Applied in JS after grep returns. */
102
+ export const MULTI_FILE_PER_FILE_MATCHES = 20;
103
+ /** Per-file match cap for single-file searches — there's no diversity
104
+ * concern when the scope is one file. */
105
+ export const SINGLE_FILE_MATCHES = 200;
106
+ /** Hard safety ceiling on how many matches we fetch from native grep
107
+ * before JS-side grouping. Sized to comfortably cover the file window
108
+ * (DEFAULT_FILE_LIMIT files × MULTI_FILE_PER_FILE_MATCHES matches) plus
109
+ * pagination headroom so the caller can see total file count. */
110
+ const INTERNAL_TOTAL_CAP = 2000;
111
+ /** Mirrors `MAX_FILE_BYTES` in `crates/pi-natives/src/grep.rs`. Native grep
112
+ * silently returns no matches for files larger than this; surface a warning
113
+ * when the caller explicitly targeted such a file so they know to chunk it. */
114
+ const NATIVE_GREP_MAX_FILE_BYTES = 4 * 1024 * 1024;
115
+ /** Wall-clock budget for a single native grep invocation. Without it, an
116
+ * aborted or runaway search (huge tree, network mount) keeps burning CPU on
117
+ * the native thread pool after the JS promise is abandoned. */
118
+ const SEARCH_GREP_TIMEOUT_MS = 30_000;
119
+
120
+ /**
121
+ * Parsed `paths` entry — a path (possibly archive-shaped) plus an optional
122
+ * line-range selector peeled off the trailing `:N-M` (or `:N+K`, `:N,M`, …)
123
+ * chunk via {@link splitPathAndSel}.
124
+ */
125
+ interface SearchPathSpec {
126
+ original: string;
127
+ clean: string;
128
+ ranges?: [LineRange, ...LineRange[]];
129
+ }
130
+
131
+ function parsePathSpecs(rawEntries: readonly string[]): SearchPathSpec[] {
132
+ const specs: SearchPathSpec[] = [];
133
+ for (const entry of rawEntries) {
134
+ // Internal URLs (`artifact://`, `skill://`, …) use the URL-aware splitter,
135
+ // which peels selector-shaped tails only for selector-capable schemes and
136
+ // leaves opaque ones (`mcp://`) intact. Unlike filesystem paths, their
137
+ // verbatim/index display modes (`raw`, `conflicts`) carry no meaning for
138
+ // content search, so we accept them — searching the whole resource — and
139
+ // still honor any embedded line range as a match filter.
140
+ const internalSplit = splitInternalUrlSel(entry);
141
+ if (internalSplit.sel !== undefined) {
142
+ specs.push({
143
+ original: entry,
144
+ clean: internalSplit.path,
145
+ ranges: selectorLineRanges(internalSplit.sel),
146
+ });
147
+ continue;
148
+ }
149
+ const split = splitPathAndSel(entry);
150
+ let clean = entry;
151
+ let ranges: [LineRange, ...LineRange[]] | undefined;
152
+ if (split.sel) {
153
+ const parsed = parseLineRanges(split.sel);
154
+ if (!parsed) {
155
+ throw new ToolError(
156
+ `paths entry "${entry}" — only line-range selectors like ":50-100" are supported (no ":raw"/":conflicts")`,
157
+ );
158
+ }
159
+ if (hasGlobPathChars(split.path)) {
160
+ throw new ToolError(`Line-range selector requires a single file, not a glob: ${entry}`);
161
+ }
162
+ clean = split.path;
163
+ ranges = parsed;
164
+ }
165
+ specs.push({ original: entry, clean, ranges });
166
+ }
167
+ return specs;
168
+ }
169
+
170
+ function mergeRangesInto(map: Map<string, LineRange[]>, absKey: string, ranges: readonly LineRange[]): void {
171
+ // Concat-without-merge is correct: `isLineInRanges` scans linearly, so
172
+ // duplicates/overlaps only cost a few extra comparisons per match.
173
+ const existing = map.get(absKey);
174
+ if (existing) {
175
+ existing.push(...ranges);
176
+ } else {
177
+ map.set(absKey, [...ranges]);
178
+ }
179
+ }
180
+
181
+ function matchAbsolutePath(matchPath: string, searchPath: string): string {
182
+ if (matchPath === "") return searchPath;
183
+ if (path.isAbsolute(matchPath)) return matchPath;
184
+ return path.resolve(searchPath, matchPath);
185
+ }
186
+
187
+ /**
188
+ * Pre-resolve any `paths` entries that point at a member inside an archive
189
+ * (e.g. `bundle.zip:src/foo.ts`, `release.tar.gz:notes.md`). Native grep
190
+ * cannot read archive members, so we materialize each text member to a
191
+ * temp scratch file and substitute that path into the search inputs. After
192
+ * grep returns, callers remap `match.path` back to the original
193
+ * `archive:member` selector so it round-trips through the `read` tool.
194
+ *
195
+ * Returns the rewritten paths array (same length/order as input), a map
196
+ * from absolute scratch path → original selector, a list of entries we
197
+ * could not materialize (binary member, missing archive, etc.), and a
198
+ * cleanup hook the caller MUST invoke in a `finally`.
199
+ */
200
+ async function resolveArchiveSearchPaths(
201
+ paths: string[],
202
+ cwd: string,
203
+ ): Promise<{
204
+ resolvedPaths: string[];
205
+ displayMap: Map<string, string>;
206
+ displaySet: Set<string>;
207
+ unreadable: string[];
208
+ cleanup: () => Promise<void>;
209
+ }> {
210
+ const resolvedPaths = paths.slice();
211
+ const displayMap = new Map<string, string>();
212
+ const displaySet = new Set<string>();
213
+ const unreadable: string[] = [];
214
+ let tempDir: string | undefined;
215
+ const archiveCache = new Map<string, ArchiveReader>();
216
+
217
+ for (let idx = 0; idx < paths.length; idx++) {
218
+ const entry = paths[idx];
219
+ const candidates = parseArchivePathCandidates(entry);
220
+ // Longest archive prefix first; we want the one whose member portion is non-empty.
221
+ const member = candidates.find(c => c.subPath !== "" && c.archivePath !== entry);
222
+ if (!member) continue;
223
+
224
+ const archiveAbs = resolveReadPath(member.archivePath, cwd);
225
+ let archive = archiveCache.get(archiveAbs);
226
+ if (!archive) {
227
+ try {
228
+ archive = await openArchive(archiveAbs);
229
+ } catch (err) {
230
+ unreadable.push(`${entry} (cannot open archive: ${(err as Error).message})`);
231
+ continue;
232
+ }
233
+ archiveCache.set(archiveAbs, archive);
234
+ }
235
+
236
+ let extracted: ExtractedArchiveFile;
237
+ try {
238
+ extracted = await archive.readFile(member.subPath);
239
+ } catch (err) {
240
+ unreadable.push(`${entry} (${(err as Error).message})`);
241
+ continue;
242
+ }
243
+ // UTF-8 only — binary members would just produce noise through ripgrep.
244
+ if (extracted.bytes.some(byte => byte === 0)) {
245
+ unreadable.push(`${entry} (binary archive entry)`);
246
+ continue;
247
+ }
248
+ let text: string;
249
+ try {
250
+ text = new TextDecoder("utf-8", { fatal: true }).decode(extracted.bytes);
251
+ } catch {
252
+ unreadable.push(`${entry} (non-UTF-8 archive entry)`);
253
+ continue;
254
+ }
255
+
256
+ if (!tempDir) {
257
+ tempDir = await mkdtemp(path.join(tmpdir(), "omp-search-archive-"));
258
+ }
259
+ // Per-entry filename keeps the scratch path unique even when two selectors
260
+ // resolve to members with the same basename.
261
+ const safeBase = path.basename(member.subPath).replace(/[^\w.-]+/g, "_") || "entry";
262
+ const tempPath = path.join(tempDir, `${idx}-${safeBase}`);
263
+ await writeFile(tempPath, text);
264
+ resolvedPaths[idx] = tempPath;
265
+ displayMap.set(tempPath, entry);
266
+ displaySet.add(entry);
267
+ }
268
+
269
+ const cleanup = async () => {
270
+ if (tempDir) {
271
+ await rm(tempDir, { recursive: true, force: true }).catch(() => {});
272
+ }
273
+ };
274
+ return { resolvedPaths, displayMap, displaySet, unreadable, cleanup };
275
+ }
276
+
277
+ interface VirtualSearchResource {
278
+ path: string;
279
+ content: string;
280
+ ranges?: readonly LineRange[];
281
+ }
282
+
283
+ interface InternalSearchInputResolution {
284
+ paths: string[];
285
+ resolvedPathsByInput: string[];
286
+ virtualResources: VirtualSearchResource[];
287
+ virtualPathSet: Set<string>;
288
+ virtualInputIndexes: Set<number>;
289
+ immutableSourcePaths: Set<string>;
290
+ virtualScopePath?: string;
291
+ }
292
+
293
+ interface IndexedContentLines {
294
+ lines: string[];
295
+ starts: number[];
296
+ }
297
+
298
+ const OMP_ROOT_URL_RE = /^omp:\/\/(?:\/?|docs\/?)$/i;
299
+
300
+ function normalizeSearchLine(line: string): string {
301
+ return line.endsWith("\r") ? line.slice(0, -1) : line;
302
+ }
303
+
304
+ function splitSearchLines(content: string): string[] {
305
+ const lines = content.split("\n");
306
+ if (lines.length > 0 && lines[lines.length - 1] === "") {
307
+ lines.pop();
308
+ }
309
+ return lines.map(normalizeSearchLine);
310
+ }
311
+
312
+ function indexSearchLines(content: string): IndexedContentLines {
313
+ const rawLines = content.split("\n");
314
+ if (rawLines.length > 0 && rawLines[rawLines.length - 1] === "") {
315
+ rawLines.pop();
316
+ }
317
+ const lines: string[] = [];
318
+ const starts: number[] = [];
319
+ let offset = 0;
320
+ for (const rawLine of rawLines) {
321
+ starts.push(offset);
322
+ lines.push(normalizeSearchLine(rawLine));
323
+ offset += rawLine.length + 1;
324
+ }
325
+ return { lines, starts };
326
+ }
327
+
328
+ function findLineIndex(starts: readonly number[], offset: number): number {
329
+ if (starts.length === 0) return -1;
330
+ let low = 0;
331
+ let high = starts.length - 1;
332
+ while (low <= high) {
333
+ const mid = Math.floor((low + high) / 2);
334
+ if (starts[mid] <= offset) {
335
+ low = mid + 1;
336
+ } else {
337
+ high = mid - 1;
338
+ }
339
+ }
340
+ return Math.max(0, high);
341
+ }
342
+
343
+ function lineAllowed(lineNumber: number, ranges: readonly LineRange[] | undefined): boolean {
344
+ return !ranges || isLineInRanges(lineNumber, ranges);
345
+ }
346
+
347
+ function makeContextLine(lines: readonly string[], lineIndex: number): { lineNumber: number; line: string } {
348
+ const { text } = truncateLine(lines[lineIndex] ?? "", DEFAULT_MAX_COLUMN);
349
+ return { lineNumber: lineIndex + 1, line: text };
350
+ }
351
+
352
+ function makeVirtualMatch(
353
+ resource: VirtualSearchResource,
354
+ lines: readonly string[],
355
+ lineIndex: number,
356
+ contextBefore: number,
357
+ contextAfter: number,
358
+ lastEmittedLine: number,
359
+ nextMatchLine: number,
360
+ ): GrepMatch {
361
+ const lineNumber = lineIndex + 1;
362
+ const { text, wasTruncated } = truncateLine(lines[lineIndex] ?? "", DEFAULT_MAX_COLUMN);
363
+ const match: GrepMatch = {
364
+ path: resource.path,
365
+ lineNumber,
366
+ line: text,
367
+ };
368
+ if (wasTruncated) match.truncated = true;
369
+
370
+ if (contextBefore > 0) {
371
+ const before: NonNullable<GrepMatch["contextBefore"]> = [];
372
+ // Start after the previous match's last emitted line so adjacent matches
373
+ // never repeat or rewind context lines (mirrors native grep's sink).
374
+ const start = Math.max(0, lineIndex - contextBefore, lastEmittedLine);
375
+ for (let idx = start; idx < lineIndex; idx++) {
376
+ const contextLineNumber = idx + 1;
377
+ if (lineAllowed(contextLineNumber, resource.ranges)) {
378
+ before.push(makeContextLine(lines, idx));
379
+ }
380
+ }
381
+ if (before.length > 0) match.contextBefore = before;
382
+ }
383
+
384
+ if (contextAfter > 0) {
385
+ const after: NonNullable<GrepMatch["contextAfter"]> = [];
386
+ // Stop before the next match line; it is emitted as a match itself.
387
+ const end = Math.min(lines.length - 1, lineIndex + contextAfter, nextMatchLine - 2);
388
+ for (let idx = lineIndex + 1; idx <= end; idx++) {
389
+ const contextLineNumber = idx + 1;
390
+ if (lineAllowed(contextLineNumber, resource.ranges)) {
391
+ after.push(makeContextLine(lines, idx));
392
+ }
393
+ }
394
+ if (after.length > 0) match.contextAfter = after;
395
+ }
396
+
397
+ return match;
398
+ }
399
+
400
+ /** Build matches for ascending matched line indexes with forward-only,
401
+ * deduplicated context windows (line numbers never repeat or go backwards
402
+ * within one resource). */
403
+ function buildVirtualMatches(
404
+ resource: VirtualSearchResource,
405
+ lines: readonly string[],
406
+ matchedIndexes: readonly number[],
407
+ contextBefore: number,
408
+ contextAfter: number,
409
+ maxCount: number,
410
+ ): GrepMatch[] {
411
+ const matches: GrepMatch[] = [];
412
+ let lastEmittedLine = 0;
413
+ for (let i = 0; i < matchedIndexes.length && matches.length < maxCount; i++) {
414
+ const lineIndex = matchedIndexes[i];
415
+ const nextMatchLine = i + 1 < matchedIndexes.length ? matchedIndexes[i + 1] + 1 : Number.POSITIVE_INFINITY;
416
+ const match = makeVirtualMatch(
417
+ resource,
418
+ lines,
419
+ lineIndex,
420
+ contextBefore,
421
+ contextAfter,
422
+ lastEmittedLine,
423
+ nextMatchLine,
424
+ );
425
+ const after = match.contextAfter;
426
+ lastEmittedLine = after && after.length > 0 ? after[after.length - 1].lineNumber : match.lineNumber;
427
+ matches.push(match);
428
+ }
429
+ return matches;
430
+ }
431
+
432
+ function compileVirtualRegex(pattern: string, ignoreCase: boolean, multiline: boolean): RegExp {
433
+ const flags = `${ignoreCase ? "i" : ""}${multiline ? "gm" : ""}`;
434
+ try {
435
+ return new RegExp(pattern, flags);
436
+ } catch (err) {
437
+ const message = err instanceof Error ? err.message : String(err);
438
+ throw new ToolError(`Invalid regex: ${message.replace(/^Invalid regular expression:\s*/i, "")}`);
439
+ }
440
+ }
441
+
442
+ function searchVirtualResourceLines(
443
+ resource: VirtualSearchResource,
444
+ regex: RegExp,
445
+ contextBefore: number,
446
+ contextAfter: number,
447
+ maxCount: number,
448
+ ): { matches: GrepMatch[]; totalMatches: number; limitReached: boolean } {
449
+ const lines = splitSearchLines(resource.content);
450
+ const matchedIndexes: number[] = [];
451
+
452
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
453
+ const lineNumber = lineIndex + 1;
454
+ if (!lineAllowed(lineNumber, resource.ranges)) continue;
455
+ regex.lastIndex = 0;
456
+ if (!regex.test(lines[lineIndex] ?? "")) continue;
457
+ matchedIndexes.push(lineIndex);
458
+ }
459
+
460
+ const matches = buildVirtualMatches(resource, lines, matchedIndexes, contextBefore, contextAfter, maxCount);
461
+ return { matches, totalMatches: matchedIndexes.length, limitReached: matchedIndexes.length > matches.length };
462
+ }
463
+
464
+ function searchVirtualResourceMultiline(
465
+ resource: VirtualSearchResource,
466
+ regex: RegExp,
467
+ contextBefore: number,
468
+ contextAfter: number,
469
+ maxCount: number,
470
+ ): { matches: GrepMatch[]; totalMatches: number; limitReached: boolean } {
471
+ const indexed = indexSearchLines(resource.content);
472
+ const matchedLines = new Set<number>();
473
+ const matchedIndexes: number[] = [];
474
+
475
+ while (true) {
476
+ const match = regex.exec(resource.content);
477
+ if (match === null) break;
478
+ const lineIndex = findLineIndex(indexed.starts, match.index);
479
+ if (lineIndex >= 0) {
480
+ const lineNumber = lineIndex + 1;
481
+ if (!matchedLines.has(lineNumber) && lineAllowed(lineNumber, resource.ranges)) {
482
+ matchedLines.add(lineNumber);
483
+ matchedIndexes.push(lineIndex);
484
+ }
485
+ }
486
+ if (match[0].length === 0) {
487
+ regex.lastIndex++;
488
+ }
489
+ }
490
+
491
+ const matches = buildVirtualMatches(resource, indexed.lines, matchedIndexes, contextBefore, contextAfter, maxCount);
492
+ return { matches, totalMatches: matchedIndexes.length, limitReached: matchedIndexes.length > matches.length };
493
+ }
494
+
495
+ function searchVirtualResources(
496
+ resources: readonly VirtualSearchResource[],
497
+ pattern: string,
498
+ ignoreCase: boolean,
499
+ multiline: boolean,
500
+ contextBefore: number,
501
+ contextAfter: number,
502
+ maxCount: number,
503
+ ): GrepResult {
504
+ if (resources.length === 0) {
505
+ return { matches: [], totalMatches: 0, filesWithMatches: 0, filesSearched: 0, limitReached: false };
506
+ }
507
+ const regex = compileVirtualRegex(pattern, ignoreCase, multiline);
508
+ const matches: GrepMatch[] = [];
509
+ const filesWithMatches = new Set<string>();
510
+ let totalMatches = 0;
511
+ let limitReached = false;
512
+
513
+ for (const resource of resources) {
514
+ const remaining = Math.max(maxCount - matches.length, 0);
515
+ const resourceResult = multiline
516
+ ? searchVirtualResourceMultiline(resource, regex, contextBefore, contextAfter, remaining)
517
+ : searchVirtualResourceLines(resource, regex, contextBefore, contextAfter, remaining);
518
+ if (resourceResult.totalMatches > 0) {
519
+ filesWithMatches.add(resource.path);
520
+ }
521
+ totalMatches += resourceResult.totalMatches;
522
+ limitReached = limitReached || resourceResult.limitReached;
523
+ matches.push(...resourceResult.matches);
524
+ }
525
+
526
+ return {
527
+ matches,
528
+ totalMatches,
529
+ filesWithMatches: filesWithMatches.size,
530
+ filesSearched: resources.length,
531
+ limitReached,
532
+ };
533
+ }
534
+
535
+ function mergeGrepResults(left: GrepResult, right: GrepResult, maxCount: number): GrepResult {
536
+ if (left.matches.length === 0) return right;
537
+ if (right.matches.length === 0) return left;
538
+ const combinedMatches = [...left.matches, ...right.matches];
539
+ const matches = combinedMatches.length > maxCount ? combinedMatches.slice(0, maxCount) : combinedMatches;
540
+ return {
541
+ matches,
542
+ totalMatches: left.totalMatches + right.totalMatches,
543
+ filesWithMatches: new Set(matches.map(match => match.path)).size,
544
+ filesSearched: left.filesSearched + right.filesSearched,
545
+ limitReached: left.limitReached || right.limitReached || matches.length < combinedMatches.length,
546
+ };
547
+ }
548
+
549
+ async function expandVirtualInternalResource(
550
+ rawPath: string,
551
+ resource: InternalResource,
552
+ internalRouter: InternalUrlRouter,
553
+ context: ResolveContext,
554
+ ranges: readonly LineRange[] | undefined,
555
+ ): Promise<VirtualSearchResource[]> {
556
+ if (OMP_ROOT_URL_RE.test(rawPath)) {
557
+ const completions = await internalRouter.complete("omp", "");
558
+ if (completions && completions.length > 0) {
559
+ const resources: VirtualSearchResource[] = [];
560
+ const seen = new Set<string>();
561
+ for (const completion of completions) {
562
+ if (seen.has(completion.value)) continue;
563
+ seen.add(completion.value);
564
+ const docUrl = `omp://${completion.value}`;
565
+ const doc = await internalRouter.resolve(docUrl, context);
566
+ if (!doc.sourcePath) {
567
+ resources.push({ path: docUrl, content: doc.content, ranges });
568
+ }
569
+ }
570
+ if (resources.length > 0) return resources;
571
+ }
572
+ }
573
+
574
+ return [{ path: rawPath, content: resource.content, ranges }];
575
+ }
576
+
577
+ async function resolveInternalSearchInputs(opts: {
578
+ pathSpecs: readonly SearchPathSpec[];
579
+ resolvedPaths: string[];
580
+ cwd: string;
581
+ settings: unknown;
582
+ signal?: AbortSignal;
583
+ archiveDisplayMap: ReadonlyMap<string, string>;
584
+ localProtocolOptions?: LocalProtocolOptions;
585
+ }): Promise<InternalSearchInputResolution> {
586
+ const internalRouter = InternalUrlRouter.instance();
587
+ const paths = opts.resolvedPaths.slice();
588
+ const virtualResources: VirtualSearchResource[] = [];
589
+ const virtualPathSet = new Set<string>();
590
+ const virtualInputIndexes = new Set<number>();
591
+ const immutableSourcePaths = new Set<string>();
592
+ let virtualScopePath: string | undefined;
593
+ const context: ResolveContext = {
594
+ cwd: opts.cwd,
595
+ settings: opts.settings,
596
+ signal: opts.signal,
597
+ localProtocolOptions: opts.localProtocolOptions,
598
+ };
599
+
600
+ for (let idx = 0; idx < paths.length; idx++) {
601
+ const rawPath = paths[idx];
602
+ if (!rawPath || opts.archiveDisplayMap.has(rawPath) || !internalRouter.canHandle(rawPath)) {
603
+ continue;
604
+ }
605
+ if (hasGlobPathChars(rawPath)) {
606
+ throw new ToolError(`Glob patterns are not supported for internal URLs: ${rawPath}`);
607
+ }
608
+ const resource = await internalRouter.resolve(rawPath, context);
609
+ if (resource.sourcePath) {
610
+ paths[idx] = resource.sourcePath;
611
+ if (resource.immutable) {
612
+ immutableSourcePaths.add(path.resolve(resource.sourcePath));
613
+ }
614
+ continue;
615
+ }
616
+
617
+ const ranges = opts.pathSpecs[idx]?.ranges;
618
+ const expanded = await expandVirtualInternalResource(rawPath, resource, internalRouter, context, ranges);
619
+ virtualInputIndexes.add(idx);
620
+ for (const virtual of expanded) {
621
+ virtualResources.push(virtual);
622
+ virtualPathSet.add(virtual.path);
623
+ }
624
+ virtualScopePath = virtualScopePath ? `${virtualScopePath}, ${rawPath}` : rawPath;
625
+ }
626
+
627
+ return {
628
+ resolvedPathsByInput: paths,
629
+ paths: paths.filter((_, idx) => !virtualInputIndexes.has(idx)),
630
+ virtualResources,
631
+ virtualPathSet,
632
+ virtualInputIndexes,
633
+ immutableSourcePaths,
634
+ virtualScopePath,
635
+ };
636
+ }
637
+
638
+ export interface SearchToolDetails {
639
+ truncation?: TruncationResult;
640
+ fileLimitReached?: number;
641
+ perFileLimitReached?: number;
642
+ linesTruncated?: boolean;
643
+ meta?: OutputMeta;
644
+ scopePath?: string;
645
+ matchCount?: number;
646
+ fileCount?: number;
647
+ files?: string[];
648
+ fileMatches?: Array<{ path: string; count: number }>;
649
+ truncated?: boolean;
650
+ error?: string;
651
+ /** Pre-formatted text for the user-visible TUI render. Mirrors the model-facing
652
+ * `result.text` lines but uses a `│` gutter and `*` to mark match lines (vs space for
653
+ * context). The TUI uses this directly so it never parses model-facing hashline anchors. */
654
+ displayContent?: string;
655
+ /** Absolute base directory used during search. Used by the renderer to resolve
656
+ * display-relative paths to absolute paths for OSC 8 hyperlinks. */
657
+ searchPath?: string;
658
+ /** Session cwd at search time. The renderer resolves the display-relative
659
+ * (cwd-relative) header/match paths against this for OSC 8 hyperlinks;
660
+ * `searchPath` is the scope label target, not the display-path base. */
661
+ cwd?: string;
662
+ /** User-supplied paths whose base directory was missing on disk. The tool
663
+ * skipped these and continued with the surviving entries; surfaced as a
664
+ * non-fatal warning in the renderer and in the model-facing text. */
665
+ missingPaths?: string[];
666
+ }
667
+
668
+ type SearchParams = z.infer<typeof searchSchema>;
669
+
670
+ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDetails> {
671
+ readonly name = "search";
672
+ readonly approval = "read" as const;
673
+ readonly label = "Search";
674
+ readonly loadMode = "discoverable";
675
+ readonly summary = "Search file contents using ripgrep (fast text search)";
676
+ readonly description: string;
677
+ readonly parameters = searchSchema;
678
+ readonly strict = true;
679
+
680
+ constructor(private readonly session: ToolSession) {
681
+ const displayMode = resolveFileDisplayMode(session);
682
+ this.description = prompt.render(searchDescription, {
683
+ IS_HL_MODE: displayMode.hashLines,
684
+ IS_LINE_NUMBER_MODE: !displayMode.hashLines && displayMode.lineNumbers,
685
+ });
686
+ }
687
+
688
+ async execute(
689
+ _toolCallId: string,
690
+ params: SearchParams,
691
+ signal?: AbortSignal,
692
+ _onUpdate?: AgentToolUpdateCallback<SearchToolDetails>,
693
+ _toolContext?: AgentToolContext,
694
+ ): Promise<AgentToolResult<SearchToolDetails>> {
695
+ const { pattern, paths: rawPaths, i, gitignore, skip } = params;
696
+
697
+ return untilAborted(signal, async () => {
698
+ // Preserve the pattern verbatim — leading/trailing whitespace is
699
+ // meaningful in regexes (indentation anchors, trailing-space matches).
700
+ if (!pattern.trim()) {
701
+ throw new ToolError("Pattern must not be empty");
702
+ }
703
+ const normalizedPattern = pattern;
704
+
705
+ const normalizedSkip =
706
+ skip === undefined || skip === null ? 0 : Number.isFinite(skip) ? Math.floor(skip) : Number.NaN;
707
+ if (normalizedSkip < 0 || !Number.isFinite(normalizedSkip)) {
708
+ throw new ToolError("Skip must be a non-negative number");
709
+ }
710
+ const scopedPaths = toPathList(rawPaths);
711
+ const effectivePaths = scopedPaths.length > 0 ? scopedPaths : ["."];
712
+ const rawEntries = await expandDelimitedPathEntries(effectivePaths, this.session.cwd);
713
+ const pathSpecs = parsePathSpecs(rawEntries);
714
+ const paths = pathSpecs.map(spec => spec.clean);
715
+ const {
716
+ resolvedPaths,
717
+ displayMap: archiveDisplayMap,
718
+ displaySet: archiveDisplaySet,
719
+ unreadable: archiveUnreadable,
720
+ cleanup: cleanupArchiveScratch,
721
+ } = await resolveArchiveSearchPaths(paths, this.session.cwd);
722
+ try {
723
+ const internalResolution = await resolveInternalSearchInputs({
724
+ pathSpecs,
725
+ resolvedPaths,
726
+ cwd: this.session.cwd,
727
+ settings: this.session.settings,
728
+ signal,
729
+ archiveDisplayMap,
730
+ localProtocolOptions: this.session.localProtocolOptions,
731
+ });
732
+ const searchablePaths = internalResolution.paths;
733
+ const { virtualResources, virtualPathSet, virtualInputIndexes } = internalResolution;
734
+ // Build the per-file line-range filter (keyed by absolute path) now that
735
+ // archive entries have been materialized to scratch files. Plain entries
736
+ // resolve through `resolveReadPath`; archive entries are keyed by the
737
+ // scratch path that grep will actually report against.
738
+ const rangesByAbsPath = new Map<string, LineRange[]>();
739
+ for (let idx = 0; idx < pathSpecs.length; idx++) {
740
+ const spec = pathSpecs[idx];
741
+ if (!spec.ranges) continue;
742
+ if (virtualInputIndexes.has(idx)) continue;
743
+ const resolved = internalResolution.resolvedPathsByInput[idx];
744
+ if (!resolved) continue;
745
+ if (resolved === spec.clean && !archiveDisplayMap.has(resolved)) {
746
+ // Non-archive entry; ensure the cleaned path resolves to a regular file.
747
+ const absKey = path.resolve(resolveReadPath(resolved, this.session.cwd));
748
+ const stats = await stat(absKey).catch(() => null);
749
+ if (!stats) {
750
+ throw new ToolError(`Path not found for line-range selector: ${spec.original}`);
751
+ }
752
+ if (!stats.isFile()) {
753
+ throw new ToolError(`Line-range selector requires a single file: ${spec.original} is a directory`);
754
+ }
755
+ mergeRangesInto(rangesByAbsPath, absKey, spec.ranges);
756
+ } else {
757
+ // Archive entry — `resolveArchiveSearchPaths` substituted a scratch path.
758
+ const absKey = path.resolve(resolved);
759
+ mergeRangesInto(rangesByAbsPath, absKey, spec.ranges);
760
+ }
761
+ }
762
+
763
+ if (
764
+ archiveUnreadable.length > 0 &&
765
+ searchablePaths.length === archiveUnreadable.length &&
766
+ virtualResources.length === 0
767
+ ) {
768
+ // All inputs were archive selectors we couldn't materialize; surface the
769
+ // reason instead of a downstream "path not found" from the scope resolver.
770
+ throw new ToolError(
771
+ `Cannot search archive member(s): ${archiveUnreadable.join(", ")}. ` +
772
+ `Read the member with \`read <archive>:<member>\` and inspect the returned text, ` +
773
+ `or pass a UTF-8 text member.`,
774
+ );
775
+ }
776
+ const normalizedContextBefore = this.session.settings.get("search.contextBefore");
777
+ const normalizedContextAfter = this.session.settings.get("search.contextAfter");
778
+ const ignoreCase = i ?? false;
779
+ const useGitignore = gitignore ?? true;
780
+ const patternHasNewline = normalizedPattern.includes("\n") || normalizedPattern.includes("\\n");
781
+ const effectiveMultiline = patternHasNewline;
782
+
783
+ let searchPath: string;
784
+ let scopePath: string;
785
+ let globFilter: string | undefined;
786
+ let isDirectory: boolean;
787
+ let multiTargets: ResolvedSearchTarget[] | undefined;
788
+ let exactFilePaths: string[] | undefined;
789
+ let missingPaths: string[];
790
+ const immutableSourcePaths = new Set(internalResolution.immutableSourcePaths);
791
+ if (searchablePaths.length > 0) {
792
+ const scope = await resolveToolSearchScope({
793
+ rawPaths: searchablePaths,
794
+ cwd: this.session.cwd,
795
+ internalUrlAction: "search",
796
+ trackImmutableSources: true,
797
+ surfaceExactFilePaths: true,
798
+ multipathStatHint: " (`paths` entries must each exist relative to cwd)",
799
+ settings: this.session.settings,
800
+ signal,
801
+ localProtocolOptions: this.session.localProtocolOptions,
802
+ });
803
+ searchPath = scope.searchPath;
804
+ isDirectory = scope.isDirectory;
805
+ multiTargets = scope.multiTargets;
806
+ exactFilePaths = scope.exactFilePaths;
807
+ missingPaths = scope.missingPaths;
808
+ globFilter = scope.globFilter;
809
+ for (const immutablePath of scope.immutableSourcePaths) {
810
+ immutableSourcePaths.add(immutablePath);
811
+ }
812
+ // When the only input was an archive selector, surface that selector instead
813
+ // of the temp scratch path the resolver substituted in.
814
+ const physicalScopePath =
815
+ searchablePaths.length === 1 && archiveDisplayMap.get(searchPath)
816
+ ? (archiveDisplayMap.get(searchPath) as string)
817
+ : scope.scopePath;
818
+ scopePath = internalResolution.virtualScopePath
819
+ ? `${physicalScopePath}, ${internalResolution.virtualScopePath}`
820
+ : physicalScopePath;
821
+ } else {
822
+ searchPath = this.session.cwd;
823
+ scopePath = internalResolution.virtualScopePath ?? ".";
824
+ globFilter = undefined;
825
+ isDirectory = false;
826
+ multiTargets = undefined;
827
+ exactFilePaths = undefined;
828
+ missingPaths = [];
829
+ }
830
+ if (
831
+ missingPaths.length > 0 &&
832
+ missingPaths.length === searchablePaths.length &&
833
+ virtualResources.length === 0
834
+ ) {
835
+ const archiveHint =
836
+ archiveUnreadable.length > 0
837
+ ? ` (archive members were not searchable: ${archiveUnreadable.join(", ")})`
838
+ : "";
839
+ throw new ToolError(
840
+ `Path not found: ${missingPaths.join(", ")}; pass each path as its own array element${archiveHint}`,
841
+ );
842
+ }
843
+ const baseDisplayMode = resolveFileDisplayMode(this.session);
844
+
845
+ const effectiveOutputMode = GrepOutputMode.Content;
846
+ const isMultiScope =
847
+ isDirectory ||
848
+ Boolean(exactFilePaths) ||
849
+ Boolean(multiTargets) ||
850
+ (virtualResources.length > 0 && (virtualResources.length > 1 || searchablePaths.length > 0));
851
+ const perFileMatchCap = isMultiScope ? MULTI_FILE_PER_FILE_MATCHES : SINGLE_FILE_MATCHES;
852
+
853
+ // Run grep
854
+ let result: GrepResult = {
855
+ matches: [],
856
+ totalMatches: 0,
857
+ filesWithMatches: 0,
858
+ filesSearched: 0,
859
+ limitReached: false,
860
+ };
861
+ let skippedOversizedCount = 0;
862
+ try {
863
+ if (searchablePaths.length > 0) {
864
+ if (exactFilePaths || multiTargets) {
865
+ const matches: GrepMatch[] = [];
866
+ let limitReached = false;
867
+ let totalMatches = 0;
868
+ let filesSearched = 0;
869
+ const targets = exactFilePaths
870
+ ? exactFilePaths.map(filePath => ({
871
+ basePath: filePath,
872
+ glob: undefined as string | undefined,
873
+ }))
874
+ : (multiTargets ?? []);
875
+ for (const target of targets) {
876
+ const targetResult = await grep(
877
+ {
878
+ pattern: normalizedPattern,
879
+ path: target.basePath,
880
+ glob: target.glob,
881
+ ignoreCase,
882
+ multiline: effectiveMultiline,
883
+ hidden: true,
884
+ gitignore: useGitignore,
885
+ cache: false,
886
+ maxCount: INTERNAL_TOTAL_CAP,
887
+ contextBefore: normalizedContextBefore,
888
+ contextAfter: normalizedContextAfter,
889
+ maxColumns: DEFAULT_MAX_COLUMN,
890
+ mode: effectiveOutputMode,
891
+ maxCountPerFile: perFileMatchCap + 1,
892
+ signal,
893
+ timeoutMs: SEARCH_GREP_TIMEOUT_MS,
894
+ },
895
+ undefined,
896
+ );
897
+ skippedOversizedCount += targetResult.skippedOversized ?? 0;
898
+ limitReached = limitReached || Boolean(targetResult.limitReached);
899
+ totalMatches += targetResult.totalMatches;
900
+ filesSearched += targetResult.filesSearched;
901
+ for (const match of targetResult.matches) {
902
+ const absolute = path.resolve(target.basePath, match.path);
903
+ const rebased = path.relative(searchPath, absolute).replace(/\\/g, "/");
904
+ matches.push({ ...match, path: rebased });
905
+ }
906
+ }
907
+ result = {
908
+ matches,
909
+ totalMatches: exactFilePaths ? matches.length : totalMatches,
910
+ filesWithMatches: new Set(matches.map(match => match.path)).size,
911
+ filesSearched: exactFilePaths ? exactFilePaths.length : filesSearched,
912
+ limitReached,
913
+ };
914
+ } else {
915
+ result = await grep(
916
+ {
917
+ pattern: normalizedPattern,
918
+ path: searchPath,
919
+ glob: globFilter,
920
+ ignoreCase,
921
+ multiline: effectiveMultiline,
922
+ hidden: true,
923
+ gitignore: useGitignore,
924
+ cache: false,
925
+ maxCount: INTERNAL_TOTAL_CAP,
926
+ contextBefore: normalizedContextBefore,
927
+ contextAfter: normalizedContextAfter,
928
+ maxColumns: DEFAULT_MAX_COLUMN,
929
+ mode: effectiveOutputMode,
930
+ maxCountPerFile: perFileMatchCap + 1,
931
+ signal,
932
+ timeoutMs: SEARCH_GREP_TIMEOUT_MS,
933
+ },
934
+ undefined,
935
+ );
936
+ skippedOversizedCount = result.skippedOversized ?? 0;
937
+ }
938
+ }
939
+ } catch (err) {
940
+ if (err instanceof Error && /^regex(?: parse)? error/i.test(err.message)) {
941
+ throw new ToolError(err.message.replace(/^regex(?: parse)? error:?\s*/i, "Invalid regex: "));
942
+ }
943
+ if (err instanceof Error && err.message.includes("Aborted: Timeout")) {
944
+ throw new ToolError(
945
+ `Search timed out after ${SEARCH_GREP_TIMEOUT_MS / 1000}s; narrow paths or pattern, or scope with \`find\` first`,
946
+ );
947
+ }
948
+ throw err;
949
+ }
950
+ const virtualResult = searchVirtualResources(
951
+ virtualResources,
952
+ normalizedPattern,
953
+ ignoreCase,
954
+ effectiveMultiline,
955
+ normalizedContextBefore,
956
+ normalizedContextAfter,
957
+ INTERNAL_TOTAL_CAP,
958
+ );
959
+ result = mergeGrepResults(result, virtualResult, INTERNAL_TOTAL_CAP);
960
+ if (rangesByAbsPath.size > 0) {
961
+ const filteredMatches: GrepMatch[] = [];
962
+ for (const match of result.matches) {
963
+ const abs = matchAbsolutePath(match.path, searchPath);
964
+ const ranges = rangesByAbsPath.get(abs);
965
+ if (!ranges) {
966
+ // Path has no line-range constraint (e.g. a peer entry without `:N-M`).
967
+ filteredMatches.push(match);
968
+ continue;
969
+ }
970
+ if (!isLineInRanges(match.lineNumber, ranges)) continue;
971
+ // Drop context lines that fall outside the allowed ranges; they would
972
+ // otherwise leak content the caller explicitly excluded.
973
+ const trimBefore = match.contextBefore?.filter(c => isLineInRanges(c.lineNumber, ranges));
974
+ const trimAfter = match.contextAfter?.filter(c => isLineInRanges(c.lineNumber, ranges));
975
+ filteredMatches.push({
976
+ ...match,
977
+ contextBefore: trimBefore && trimBefore.length > 0 ? trimBefore : undefined,
978
+ contextAfter: trimAfter && trimAfter.length > 0 ? trimAfter : undefined,
979
+ });
980
+ }
981
+ result = {
982
+ matches: filteredMatches,
983
+ totalMatches: filteredMatches.length,
984
+ filesWithMatches: new Set(filteredMatches.map(match => match.path)).size,
985
+ filesSearched: result.filesSearched,
986
+ limitReached: result.limitReached,
987
+ };
988
+ }
989
+ if (archiveDisplayMap.size > 0) {
990
+ for (const match of result.matches) {
991
+ const abs = matchAbsolutePath(match.path, searchPath);
992
+ const display = archiveDisplayMap.get(abs);
993
+ if (display) match.path = display;
994
+ }
995
+ }
996
+
997
+ const formatPath = (filePath: string): string =>
998
+ archiveDisplaySet.has(filePath) || virtualPathSet.has(filePath)
999
+ ? filePath
1000
+ : formatResultPath(filePath, isDirectory, searchPath, this.session.cwd);
1001
+
1002
+ // Group matches by file in encounter order. Detect per-file overflow
1003
+ // BEFORE truncation so the renderer can surface that a hot file was
1004
+ // trimmed for diversity.
1005
+ const fileOrder: string[] = [];
1006
+ const matchesByPath = new Map<string, GrepMatch[]>();
1007
+ for (const match of result.matches) {
1008
+ if (!matchesByPath.has(match.path)) {
1009
+ fileOrder.push(match.path);
1010
+ matchesByPath.set(match.path, []);
1011
+ }
1012
+ matchesByPath.get(match.path)!.push(match);
1013
+ }
1014
+ let perFileLimitReached = false;
1015
+ for (const file of fileOrder) {
1016
+ const list = matchesByPath.get(file)!;
1017
+ if (list.length > perFileMatchCap) {
1018
+ perFileLimitReached = true;
1019
+ list.length = perFileMatchCap;
1020
+ }
1021
+ }
1022
+ const totalFiles = fileOrder.length;
1023
+ // When native grep stopped at its internal cap, files past the cap were
1024
+ // never surfaced — the file total is only a lower bound.
1025
+ const totalFilesLabel = result.limitReached ? `${totalFiles}+` : `${totalFiles}`;
1026
+ // Single-file scopes can't paginate — there is one file by definition.
1027
+ const canPaginate = isMultiScope;
1028
+ const skipFiles = canPaginate ? Math.min(normalizedSkip, totalFiles) : 0;
1029
+ const windowFiles = canPaginate ? fileOrder.slice(skipFiles, skipFiles + DEFAULT_FILE_LIMIT) : fileOrder;
1030
+ const fileLimitReached = canPaginate && totalFiles > skipFiles + DEFAULT_FILE_LIMIT;
1031
+ const selectedMatches: GrepMatch[] = [];
1032
+ if (windowFiles.length > 0) {
1033
+ const lists = windowFiles.map(file => matchesByPath.get(file) ?? []);
1034
+ const cursors = new Array<number>(lists.length).fill(0);
1035
+ let anyAdded = true;
1036
+ while (anyAdded) {
1037
+ anyAdded = false;
1038
+ for (let i = 0; i < lists.length; i++) {
1039
+ if (cursors[i] < lists[i].length) {
1040
+ selectedMatches.push(lists[i][cursors[i]++]);
1041
+ anyAdded = true;
1042
+ }
1043
+ }
1044
+ }
1045
+ }
1046
+ const nextSkip = skipFiles + windowFiles.length;
1047
+ const limitMessage = fileLimitReached
1048
+ ? `Showing files ${skipFiles + 1}-${nextSkip} of ${totalFilesLabel}. Use skip=${nextSkip} for the next page, or narrow paths/pattern.`
1049
+ : "";
1050
+ const { record: recordFile, list: fileList } = createFileRecorder();
1051
+ const fileMatchCounts = new Map<string, number>();
1052
+ // Detect explicit file targets that exceed the native grep size cap.
1053
+ // Native silently returns no matches above the cap; without this note the
1054
+ // caller sees "no matches" for a literal pattern that visibly exists.
1055
+ const oversizedNote = await (async (): Promise<string | undefined> => {
1056
+ const explicitFileTargets: string[] = [];
1057
+ if (exactFilePaths) {
1058
+ explicitFileTargets.push(...exactFilePaths);
1059
+ } else if (searchablePaths.length > 0 && !isDirectory && !multiTargets) {
1060
+ explicitFileTargets.push(searchPath);
1061
+ }
1062
+ if (explicitFileTargets.length === 0) return undefined;
1063
+ const oversized: string[] = [];
1064
+ await Promise.all(
1065
+ explicitFileTargets.map(async target => {
1066
+ try {
1067
+ const st = await stat(target);
1068
+ if (st.isFile() && st.size > NATIVE_GREP_MAX_FILE_BYTES) {
1069
+ oversized.push(path.relative(this.session.cwd, target) || target);
1070
+ }
1071
+ } catch {
1072
+ // Stat failures here are surfaced by other code paths.
1073
+ }
1074
+ }),
1075
+ );
1076
+ if (oversized.length === 0) return undefined;
1077
+ const limitMb = Math.floor(NATIVE_GREP_MAX_FILE_BYTES / (1024 * 1024));
1078
+ return `Skipped oversized files (>${limitMb}MB grep limit; split the file or narrow with \`read\`): ${oversized.join(", ")}`;
1079
+ })();
1080
+ // Directory/multi-target scopes: native grep counts oversized skips but
1081
+ // cannot name them; explicit-file scopes are covered (with names) above.
1082
+ const oversizedScanNote =
1083
+ !oversizedNote && skippedOversizedCount > 0
1084
+ ? `Skipped ${skippedOversizedCount} oversized file(s) (>${Math.floor(NATIVE_GREP_MAX_FILE_BYTES / (1024 * 1024))}MB grep limit); target them directly with \`read\``
1085
+ : undefined;
1086
+ const archiveNote =
1087
+ archiveUnreadable.length > 0
1088
+ ? `Skipped archive entries (search supports text members only): ${archiveUnreadable.join(", ")}`
1089
+ : undefined;
1090
+ // Suppress entries we already explained via archiveNote — they would otherwise
1091
+ // double up (the unreadable selector also failed the scope's existence check).
1092
+ const archiveUnreadablePaths = new Set(archiveUnreadable.map(s => s.replace(/ \(.*\)$/, "")));
1093
+ const missingPathsForNote = missingPaths.filter(p => !archiveUnreadablePaths.has(p));
1094
+ const missingPathsNote =
1095
+ missingPathsForNote.length > 0 ? `Skipped missing paths: ${missingPathsForNote.join(", ")}` : undefined;
1096
+ const warningNote =
1097
+ [missingPathsNote, archiveNote, oversizedNote, oversizedScanNote]
1098
+ .filter((s): s is string => Boolean(s))
1099
+ .join("\n") || undefined;
1100
+ if (selectedMatches.length === 0) {
1101
+ const details: SearchToolDetails = {
1102
+ scopePath,
1103
+ searchPath,
1104
+ cwd: this.session.cwd,
1105
+ matchCount: 0,
1106
+ fileCount: 0,
1107
+ files: [],
1108
+ truncated: false,
1109
+ missingPaths: missingPaths.length > 0 ? missingPaths : undefined,
1110
+ };
1111
+ const skipPastEnd = canPaginate && normalizedSkip > 0 && totalFiles > 0 && skipFiles >= totalFiles;
1112
+ const noMatchText = skipPastEnd
1113
+ ? `No more results (${totalFilesLabel} files total; skip=${normalizedSkip} is past the end)`
1114
+ : "No matches found";
1115
+ const text = warningNote ? `${noMatchText}\n${warningNote}` : noMatchText;
1116
+ return toolResult(details).text(text).done();
1117
+ }
1118
+ const outputLines: string[] = [];
1119
+ let linesTruncated = false;
1120
+ const matchesByFile = new Map<string, GrepMatch[]>();
1121
+ for (const match of selectedMatches) {
1122
+ const relativePath = formatPath(match.path);
1123
+ recordFile(relativePath);
1124
+ if (!matchesByFile.has(relativePath)) {
1125
+ matchesByFile.set(relativePath, []);
1126
+ }
1127
+ matchesByFile.get(relativePath)!.push(match);
1128
+ }
1129
+ const displayLines: string[] = [];
1130
+ const hashContexts = new Map<string, { tag: string }>();
1131
+ if (baseDisplayMode.hashLines) {
1132
+ for (const relativePath of fileList) {
1133
+ if (archiveDisplaySet.has(relativePath) || virtualPathSet.has(relativePath)) continue;
1134
+ const absoluteFilePath = path.resolve(this.session.cwd, relativePath);
1135
+ if (immutableSourcePaths.has(absoluteFilePath)) continue;
1136
+ // Mint a whole-file content tag so any anchor validates while the
1137
+ // file is unchanged; over-cap / unreadable files get no tag (and
1138
+ // therefore plain, non-editable line output).
1139
+ const tag = await recordFileSnapshot(this.session, absoluteFilePath);
1140
+ if (tag) hashContexts.set(relativePath, { tag });
1141
+ }
1142
+ }
1143
+ const renderMatchesForFile = (relativePath: string): { model: string[]; display: string[] } => {
1144
+ const modelOut: string[] = [];
1145
+ const displayOut: string[] = [];
1146
+ const fileMatches = matchesByFile.get(relativePath) ?? [];
1147
+ const hashContext = hashContexts.get(relativePath);
1148
+ const useHashLines = hashContext !== undefined;
1149
+ const lineNumberWidth = fileMatches.reduce((width, match) => {
1150
+ let nextWidth = Math.max(width, String(match.lineNumber).length);
1151
+ for (const ctx of match.contextBefore ?? []) {
1152
+ nextWidth = Math.max(nextWidth, String(ctx.lineNumber).length);
1153
+ }
1154
+ for (const ctx of match.contextAfter ?? []) {
1155
+ nextWidth = Math.max(nextWidth, String(ctx.lineNumber).length);
1156
+ }
1157
+ return nextWidth;
1158
+ }, 0);
1159
+ let lastEmittedLine: number | undefined;
1160
+ const gutterPad = " ".repeat(lineNumberWidth + 1);
1161
+ for (const match of fileMatches) {
1162
+ const pushLine = (lineNumber: number, line: string, isMatch: boolean) => {
1163
+ if (lastEmittedLine !== undefined && lineNumber > lastEmittedLine + 1) {
1164
+ modelOut.push("...");
1165
+ displayOut.push(`${gutterPad}│...`);
1166
+ }
1167
+ modelOut.push(formatMatchLine(lineNumber, line, isMatch, { useHashLines }));
1168
+ displayOut.push(formatCodeFrameLine(isMatch ? "*" : " ", lineNumber, line, lineNumberWidth));
1169
+ lastEmittedLine = lineNumber;
1170
+ };
1171
+ if (match.contextBefore) {
1172
+ for (const ctx of match.contextBefore) {
1173
+ pushLine(ctx.lineNumber, ctx.line, false);
1174
+ }
1175
+ }
1176
+ pushLine(match.lineNumber, match.line, true);
1177
+ if (match.truncated) {
1178
+ linesTruncated = true;
1179
+ }
1180
+ if (match.contextAfter) {
1181
+ for (const ctx of match.contextAfter) {
1182
+ pushLine(ctx.lineNumber, ctx.line, false);
1183
+ }
1184
+ }
1185
+ fileMatchCounts.set(relativePath, (fileMatchCounts.get(relativePath) ?? 0) + 1);
1186
+ }
1187
+ return { model: modelOut, display: displayOut };
1188
+ };
1189
+ const useGroupedOutput = isDirectory || isMultiScope;
1190
+ if (useGroupedOutput) {
1191
+ const grouped = formatGroupedFiles(fileList, relativePath => {
1192
+ const rendered = renderMatchesForFile(relativePath);
1193
+ const hashContext = hashContexts.get(relativePath);
1194
+ return {
1195
+ modelLines: rendered.model,
1196
+ displayLines: rendered.display,
1197
+ headerSuffix: hashContext?.tag ? `#${hashContext.tag}` : "",
1198
+ skip: rendered.model.length === 0,
1199
+ };
1200
+ });
1201
+ outputLines.push(...grouped.model);
1202
+ displayLines.push(...grouped.display);
1203
+ } else {
1204
+ for (const relativePath of fileList) {
1205
+ const rendered = renderMatchesForFile(relativePath);
1206
+ if (rendered.model.length === 0) continue;
1207
+ if (outputLines.length > 0) {
1208
+ outputLines.push("");
1209
+ displayLines.push("");
1210
+ }
1211
+ const hashContext = hashContexts.get(relativePath);
1212
+ if (hashContext?.tag) {
1213
+ outputLines.push(formatHashlineHeader(relativePath, hashContext.tag));
1214
+ }
1215
+ outputLines.push(...rendered.model);
1216
+ displayLines.push(...rendered.display);
1217
+ }
1218
+ }
1219
+ if (limitMessage) {
1220
+ outputLines.push("", limitMessage);
1221
+ }
1222
+ if (warningNote) {
1223
+ outputLines.push("", warningNote);
1224
+ }
1225
+ const rawOutput = outputLines.join("\n");
1226
+ const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });
1227
+ const output = truncation.content;
1228
+ const displayText = displayLines.join("\n");
1229
+ const truncated = Boolean(
1230
+ fileLimitReached || perFileLimitReached || result.limitReached || truncation.truncated || linesTruncated,
1231
+ );
1232
+ const details: SearchToolDetails = {
1233
+ scopePath,
1234
+ searchPath,
1235
+ cwd: this.session.cwd,
1236
+ matchCount: selectedMatches.length,
1237
+ fileCount: fileList.length,
1238
+ files: fileList,
1239
+ fileMatches: fileList.map(path => ({
1240
+ path,
1241
+ count: fileMatchCounts.get(path) ?? 0,
1242
+ })),
1243
+ truncated,
1244
+ fileLimitReached: fileLimitReached ? DEFAULT_FILE_LIMIT : undefined,
1245
+ perFileLimitReached: perFileLimitReached ? perFileMatchCap : undefined,
1246
+ displayContent: displayText,
1247
+ missingPaths: missingPaths.length > 0 ? missingPaths : undefined,
1248
+ };
1249
+ if (truncation.truncated) details.truncation = truncation;
1250
+ if (linesTruncated) details.linesTruncated = true;
1251
+ const resultBuilder = toolResult(details)
1252
+ .text(output)
1253
+ .limits({ columnMax: linesTruncated ? DEFAULT_MAX_COLUMN : undefined });
1254
+ if (truncation.truncated) {
1255
+ resultBuilder.truncation(truncation, { direction: "head" });
1256
+ }
1257
+ return resultBuilder.done();
1258
+ } finally {
1259
+ await cleanupArchiveScratch();
1260
+ }
1261
+ });
1262
+ }
1263
+ }
1264
+
1265
+ // =============================================================================
1266
+ // TUI Renderer
1267
+ // =============================================================================
1268
+
1269
+ interface SearchRenderArgs {
1270
+ pattern: string;
1271
+ paths?: string | string[];
1272
+ i?: boolean;
1273
+ gitignore?: boolean;
1274
+ skip?: number;
1275
+ }
1276
+
1277
+ const COLLAPSED_TEXT_LIMIT = PREVIEW_LIMITS.COLLAPSED_LINES * 2;
1278
+ /** Line budget for the expanded view. Larger than collapsed so expanding
1279
+ * reveals more matches with context, but still bounded so a single hot file
1280
+ * whose matches span the whole file can't dump its entire length. */
1281
+ const EXPANDED_TEXT_LIMIT = PREVIEW_LIMITS.EXPANDED_LINES * 2;
1282
+
1283
+ const SEARCH_CODE_FRAME_LINE_RE = /^\s*\*?(\d+)│/;
1284
+
1285
+ function searchScopeMeta(details: SearchToolDetails | undefined): string | undefined {
1286
+ if (!details?.scopePath) return undefined;
1287
+ const label = details.searchPath ? fileHyperlink(details.searchPath, details.scopePath) : details.scopePath;
1288
+ return `in ${label}`;
1289
+ }
1290
+
1291
+ function linkUrlLikeSearchHeader(raw: string, styled: string): { line: string; absPath?: string } {
1292
+ const resolvedPath = tryResolveInternalUrlSync(raw);
1293
+ if (resolvedPath) return { line: fileHyperlink(resolvedPath, styled), absPath: resolvedPath };
1294
+ return { line: uriHyperlink(raw, styled) };
1295
+ }
1296
+
1297
+ function parseSearchDisplayLineNumber(line: string): number | undefined {
1298
+ const match = SEARCH_CODE_FRAME_LINE_RE.exec(line);
1299
+ if (!match) return undefined;
1300
+ return Number.parseInt(match[1]!, 10);
1301
+ }
1302
+
1303
+ const SEARCH_MATCH_LINE_RE = /^\s*\*\d+(?:│|[:|])/;
1304
+
1305
+ interface RenderedSearchLine {
1306
+ raw: string;
1307
+ styled: string;
1308
+ }
1309
+
1310
+ function isSearchMatchLine(line: string): boolean {
1311
+ return SEARCH_MATCH_LINE_RE.test(line);
1312
+ }
1313
+
1314
+ function isSearchHeaderLine(line: string): boolean {
1315
+ return /^#+ /.test(line);
1316
+ }
1317
+
1318
+ const URL_HEADER_PREFIX_RE = /^#+\s+/;
1319
+
1320
+ function renderSearchDisplayLines(
1321
+ lines: readonly string[],
1322
+ headerBase: string | undefined,
1323
+ fileScope: string | undefined,
1324
+ uiTheme: Theme,
1325
+ ): RenderedSearchLine[] {
1326
+ const contexts = classifyGroupedLines(lines, headerBase, fileScope);
1327
+ // `classifyGroupedLines` can't resolve internal URLs (TUI-only), so track the
1328
+ // resolved URL target here and use it for the body lines that follow.
1329
+ let urlFile: string | undefined;
1330
+ return lines.map((line, index) => {
1331
+ const ctx = contexts[index]!;
1332
+ if (ctx.kind === "dir") {
1333
+ urlFile = undefined;
1334
+ const styled = uiTheme.fg("accent", line);
1335
+ return { raw: line, styled: ctx.headerPath ? fileHyperlink(ctx.headerPath, styled) : styled };
1336
+ }
1337
+ if (ctx.kind === "file") {
1338
+ if (ctx.isUrl) {
1339
+ const raw = line
1340
+ .replace(URL_HEADER_PREFIX_RE, "")
1341
+ .trimEnd()
1342
+ .replace(/\s+\([^)]*\)\s*$/, "");
1343
+ const linked = linkUrlLikeSearchHeader(raw, uiTheme.fg("accent", line));
1344
+ urlFile = linked.absPath;
1345
+ return { raw: line, styled: linked.line };
1346
+ }
1347
+ urlFile = undefined;
1348
+ // Root-level files keep the bright accent; nested file headers are dimmed.
1349
+ const styled = uiTheme.fg(ctx.depth === 1 ? "accent" : "dim", line);
1350
+ return { raw: line, styled: ctx.headerPath ? fileHyperlink(ctx.headerPath, styled) : styled };
1351
+ }
1352
+ const styled = uiTheme.fg("toolOutput", line);
1353
+ const lineNumber = parseSearchDisplayLineNumber(line);
1354
+ const filePath = ctx.filePath ?? urlFile;
1355
+ return {
1356
+ raw: line,
1357
+ styled: filePath && lineNumber !== undefined ? fileHyperlink(filePath, styled, { line: lineNumber }) : styled,
1358
+ };
1359
+ });
1360
+ }
1361
+
1362
+ function compactSearchPreviewGroup(group: RenderedSearchLine[]): RenderedSearchLine[] {
1363
+ const compact = group.filter(line => isSearchHeaderLine(line.raw) || isSearchMatchLine(line.raw));
1364
+ return compact.length > 0 ? compact : group;
1365
+ }
1366
+
1367
+ function countPreviewMatches(lines: readonly RenderedSearchLine[], hasMarkedMatches: boolean): number {
1368
+ if (hasMarkedMatches) return lines.reduce((count, line) => count + (isSearchMatchLine(line.raw) ? 1 : 0), 0);
1369
+ return lines.reduce((count, line) => count + (!isSearchHeaderLine(line.raw) && line.raw.length > 0 ? 1 : 0), 0);
1370
+ }
1371
+
1372
+ function renderBudgetedSearchGroups(
1373
+ groups: RenderedSearchLine[][],
1374
+ maxLines: number,
1375
+ matchCount: number,
1376
+ uiTheme: Theme,
1377
+ compact: boolean,
1378
+ ): string[] {
1379
+ if (maxLines <= 0) return [];
1380
+ const renderedGroups = groups
1381
+ .map(group => (compact ? compactSearchPreviewGroup(group) : group))
1382
+ .filter(group => group.length > 0);
1383
+ if (renderedGroups.length === 0) return [];
1384
+
1385
+ let totalLines = 0;
1386
+ let totalMarkedMatches = 0;
1387
+ let totalFallbackMatches = 0;
1388
+ for (const group of renderedGroups) {
1389
+ totalLines += group.length;
1390
+ totalMarkedMatches += countPreviewMatches(group, true);
1391
+ totalFallbackMatches += countPreviewMatches(group, false);
1392
+ }
1393
+ const hasMarkedMatches = totalMarkedMatches > 0;
1394
+ const needsSummary = totalLines > maxLines;
1395
+ const contentBudget = needsSummary ? Math.max(maxLines - 1, 0) : maxLines;
1396
+ const visibleGroups: RenderedSearchLine[][] = [];
1397
+ let visibleLineCount = 0;
1398
+ let visibleMatches = 0;
1399
+ for (const group of renderedGroups) {
1400
+ if (visibleLineCount >= contentBudget) break;
1401
+ const available = contentBudget - visibleLineCount;
1402
+ const take = Math.min(group.length, available);
1403
+ if (take <= 0) break;
1404
+ const visibleGroup = group.slice(0, take);
1405
+ visibleGroups.push(visibleGroup);
1406
+ visibleLineCount += visibleGroup.length;
1407
+ visibleMatches += countPreviewMatches(visibleGroup, hasMarkedMatches);
1408
+ }
1409
+
1410
+ const totalMatches = hasMarkedMatches ? totalMarkedMatches : Math.max(matchCount, totalFallbackMatches);
1411
+ const hiddenMatches = Math.max(totalMatches - visibleMatches, 0);
1412
+ const hiddenLines = Math.max(totalLines - visibleLineCount, 0);
1413
+ const hasSummary = needsSummary && (hiddenMatches > 0 || hiddenLines > 0);
1414
+ const lines: string[] = [];
1415
+ for (let i = 0; i < visibleGroups.length; i++) {
1416
+ const group = visibleGroups[i]!;
1417
+ const isLast = !hasSummary && i === visibleGroups.length - 1;
1418
+ const prefix = `${uiTheme.fg("dim", getTreeBranch(isLast, uiTheme))} `;
1419
+ const continuePrefix = uiTheme.fg("dim", getTreeContinuePrefix(isLast, uiTheme));
1420
+ lines.push(`${prefix}${replaceTabs(group[0]!.styled)}`);
1421
+ for (let j = 1; j < group.length; j++) {
1422
+ lines.push(`${continuePrefix}${replaceTabs(group[j]!.styled)}`);
1423
+ }
1424
+ }
1425
+ if (hasSummary) {
1426
+ const hiddenLabel =
1427
+ hiddenMatches > 0 ? formatMoreItems(hiddenMatches, "match") : formatMoreItems(hiddenLines, "line");
1428
+ lines.push(`${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg("muted", hiddenLabel)}`);
1429
+ }
1430
+ return lines;
1431
+ }
1432
+
1433
+ function searchStatusIcon(uiTheme: Theme): string {
1434
+ return uiTheme.fg("toolTitle", uiTheme.symbol("icon.search"));
1435
+ }
1436
+
1437
+ export const searchToolRenderer = {
1438
+ inline: true,
1439
+ renderCall(args: SearchRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
1440
+ const paths = toPathList(args.paths);
1441
+ const meta: string[] = [];
1442
+ if (paths.length) meta.push(`in ${paths.join(", ")}`);
1443
+ if (args.i) meta.push("case:insensitive");
1444
+ if (args.gitignore === false) meta.push("gitignore:false");
1445
+ if (args.skip !== undefined && args.skip > 0) meta.push(`skip:${args.skip}`);
1446
+
1447
+ const text = renderStatusLine(
1448
+ { icon: "pending", title: "Search", titleColor: "toolTitle", description: args.pattern || "?", meta },
1449
+ uiTheme,
1450
+ );
1451
+ return new Text(text, 1, 0);
1452
+ },
1453
+
1454
+ renderResult(
1455
+ result: { content: Array<{ type: string; text?: string }>; details?: SearchToolDetails; isError?: boolean },
1456
+ options: RenderResultOptions,
1457
+ uiTheme: Theme,
1458
+ args?: SearchRenderArgs,
1459
+ ): Component {
1460
+ const details = result.details;
1461
+
1462
+ if (result.isError || details?.error) {
1463
+ const errorText = details?.error || result.content?.find(c => c.type === "text")?.text || "Unknown error";
1464
+ return new Text(formatErrorMessage(errorText, uiTheme), 1, 0);
1465
+ }
1466
+
1467
+ const hasDetailedData = details?.matchCount !== undefined || details?.fileCount !== undefined;
1468
+
1469
+ if (!hasDetailedData) {
1470
+ const textContent = result.details?.displayContent ?? result.content?.find(c => c.type === "text")?.text;
1471
+ if (!textContent || textContent === "No matches found") {
1472
+ return new Text(formatEmptyMessage("No matches found", uiTheme), 1, 0);
1473
+ }
1474
+ const lines = textContent.split("\n").filter(line => line.trim() !== "");
1475
+ const description = args?.pattern ?? undefined;
1476
+ const header = renderStatusLine(
1477
+ {
1478
+ iconOverride: searchStatusIcon(uiTheme),
1479
+ title: "Search",
1480
+ titleColor: "toolTitle",
1481
+ description,
1482
+ meta: [formatCount("item", lines.length)],
1483
+ },
1484
+ uiTheme,
1485
+ );
1486
+ return createCachedComponent(
1487
+ () => options.expanded,
1488
+ width => {
1489
+ const listLines = renderTreeList(
1490
+ {
1491
+ items: lines,
1492
+ expanded: options.expanded,
1493
+ maxCollapsed: COLLAPSED_TEXT_LIMIT,
1494
+ maxCollapsedLines: COLLAPSED_TEXT_LIMIT,
1495
+ itemType: "item",
1496
+ renderItem: line => uiTheme.fg("toolOutput", line),
1497
+ },
1498
+ uiTheme,
1499
+ );
1500
+ return [header, ...listLines].map(l => truncateToWidth(l, width, Ellipsis.Omit));
1501
+ },
1502
+ { paddingX: 1 },
1503
+ );
1504
+ }
1505
+
1506
+ const matchCount = details?.matchCount ?? 0;
1507
+ const fileCount = details?.fileCount ?? 0;
1508
+ const truncation = details?.meta?.truncation;
1509
+ const limits = details?.meta?.limits;
1510
+ const truncated = Boolean(details?.truncated || truncation || limits?.columnTruncated);
1511
+
1512
+ const missingPathsList = details?.missingPaths ?? [];
1513
+ const missingNote =
1514
+ missingPathsList.length > 0
1515
+ ? uiTheme.fg("warning", `skipped missing: ${missingPathsList.join(", ")}`)
1516
+ : undefined;
1517
+
1518
+ if (matchCount === 0) {
1519
+ const meta = ["0 matches"];
1520
+ const scopeMeta = searchScopeMeta(details);
1521
+ if (scopeMeta) meta.push(scopeMeta);
1522
+ const header = renderStatusLine(
1523
+ { icon: "warning", title: "Search", titleColor: "toolTitle", description: args?.pattern, meta },
1524
+ uiTheme,
1525
+ );
1526
+ const lines = [header, formatEmptyMessage("No matches found", uiTheme)];
1527
+ if (missingNote) lines.push(missingNote);
1528
+ return new Text(lines.join("\n"), 1, 0);
1529
+ }
1530
+
1531
+ const summaryParts = [formatCount("match", matchCount), formatCount("file", fileCount)];
1532
+ const meta = [...summaryParts];
1533
+ const scopeMeta = searchScopeMeta(details);
1534
+ if (scopeMeta) meta.push(scopeMeta);
1535
+ if (truncated) meta.push(uiTheme.fg("warning", "truncated"));
1536
+ const description = args?.pattern ?? undefined;
1537
+ const header = renderStatusLine(
1538
+ {
1539
+ ...(truncated ? { icon: "warning" as const } : { iconOverride: searchStatusIcon(uiTheme) }),
1540
+ title: "Search",
1541
+ titleColor: "toolTitle",
1542
+ description,
1543
+ meta,
1544
+ },
1545
+ uiTheme,
1546
+ );
1547
+
1548
+ const textContent = result.details?.displayContent ?? result.content?.find(c => c.type === "text")?.text ?? "";
1549
+ const allLines = textContent.split("\n");
1550
+ // Resolve hyperlinks once over the whole output so a nested directory stack
1551
+ // reconstructs correctly across blank-line group boundaries.
1552
+ // Header/match display paths are cwd-relative, so resolve them against cwd
1553
+ // (falling back to searchPath for legacy results that predate `cwd`); the
1554
+ // scoped file's absolute path seeds body lines in single-file searches.
1555
+ const renderedLines = renderSearchDisplayLines(
1556
+ allLines,
1557
+ details?.cwd ?? details?.searchPath,
1558
+ details?.searchPath,
1559
+ uiTheme,
1560
+ );
1561
+ const matchGroups = groupLineIndicesByBlank(allLines).map(indices => indices.map(i => renderedLines[i]!));
1562
+
1563
+ const extraLines: string[] = [];
1564
+ if (missingNote) extraLines.push(missingNote);
1565
+
1566
+ return createCachedComponent(
1567
+ () => options.expanded,
1568
+ width => {
1569
+ const budget = Math.max(
1570
+ (options.expanded ? EXPANDED_TEXT_LIMIT : COLLAPSED_TEXT_LIMIT) - extraLines.length,
1571
+ 0,
1572
+ );
1573
+ const matchLines = renderBudgetedSearchGroups(matchGroups, budget, matchCount, uiTheme, !options.expanded);
1574
+ return [header, ...matchLines, ...extraLines].map(l => truncateToWidth(l, width, Ellipsis.Omit));
1575
+ },
1576
+ { paddingX: 1 },
1577
+ );
1578
+ },
1579
+ mergeCallAndResult: true,
1580
+ };