pi-agent-zh 17.1.8

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