@astralyn/pi 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +77 -0
- package/dist/bundle/chunks/anthropic-messages-MSB6JBS2.js +6 -0
- package/dist/bundle/chunks/{azure-openai-responses-CH74HGP4.js → azure-openai-responses-ATZ757I2.js} +1 -1
- package/dist/bundle/chunks/bedrock-converse-stream.js +14 -11
- package/dist/bundle/chunks/{chunk-H35KWBT5.js → chunk-4XZYHGVN.js} +1 -1
- package/dist/bundle/chunks/{chunk-MAC4N3BW.js → chunk-7UBIBLPN.js} +1 -1
- package/dist/bundle/chunks/{chunk-KAC2YPTW.js → chunk-J75WV3FL.js} +1 -1
- package/dist/bundle/chunks/{chunk-EJEOEBSJ.js → chunk-KSCGI7JW.js} +2 -2
- package/dist/bundle/chunks/chunk-LUYFHZ6W.js +11 -0
- package/dist/bundle/chunks/{chunk-TEAAYBTS.js → chunk-NM2KGRFS.js} +1 -1
- package/dist/bundle/chunks/chunk-Q74S7ILH.js +2 -0
- package/dist/bundle/chunks/{chunk-QLU3LESZ.js → chunk-QDDSFJ35.js} +1 -1
- package/dist/bundle/chunks/{chunk-D3XW5X3Z.js → chunk-R2KES7LS.js} +175 -244
- package/dist/bundle/chunks/{chunk-5B3S5DVL.js → chunk-SA2AQYEI.js} +5 -2
- package/dist/bundle/chunks/{chunk-C2ARIVS3.js → chunk-YHRHHXFG.js} +1 -1
- package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-XO45ZT3V.js → cloudflare-workers-ai-system-one-HGOVFZDC.js} +1 -1
- package/dist/bundle/chunks/codemode-worker.js +17 -9
- package/dist/bundle/chunks/github-copilot.js +1 -1
- package/dist/bundle/chunks/{google-generative-ai-VVO64ZRQ.js → google-generative-ai-QRLQ4MCJ.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-LKS5NUHA.js → google-vertex-L3T6EGTL.js} +1 -1
- package/dist/bundle/chunks/mistral-conversations-6E6SS3WF.js +5 -0
- package/dist/bundle/chunks/openai-codex-responses-4GUO76XJ.js +10 -0
- package/dist/bundle/chunks/{openai-completions-7HTTU6FH.js → openai-completions-O4TKZCIM.js} +1 -1
- package/dist/bundle/chunks/{openai-decisions-776H4VDZ.js → openai-decisions-TVCDIPZX.js} +1 -1
- package/dist/bundle/chunks/openai-responses-VBQKJFNO.js +3 -0
- package/dist/bundle/chunks/{pi-messages-ZXNERWJG.js → pi-messages-3POWZ43C.js} +1 -1
- package/dist/bundle/chunks/{typesafe-system-one-2DQLG4R2.js → typesafe-system-one-LWPJYTJP.js} +1 -1
- package/dist/bundle/chunks/{virtual-modules-2NHGQRS6.js → virtual-modules-TG53AOL4.js} +1 -1
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli/credential-print.d.ts.map +1 -1
- package/dist/cli/credential-print.js +4 -4
- package/dist/cli/credential-print.js.map +1 -1
- package/dist/core/agent-session.d.ts +18 -3
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +98 -62
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +20 -13
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bug-report.d.ts +9 -1
- package/dist/core/bug-report.d.ts.map +1 -1
- package/dist/core/compaction/utils.d.ts +6 -0
- package/dist/core/compaction/utils.d.ts.map +1 -1
- package/dist/core/compaction/utils.js +7 -8
- package/dist/core/compaction/utils.js.map +1 -1
- package/dist/core/export-html/template.js +21 -16
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +4 -2
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +2 -3
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +31 -7
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/key-id-schema.d.ts +6 -0
- package/dist/core/key-id-schema.d.ts.map +1 -0
- package/dist/core/key-id-schema.js +16 -0
- package/dist/core/key-id-schema.js.map +1 -0
- package/dist/core/keybinding-registry.d.ts +4 -3
- package/dist/core/keybinding-registry.d.ts.map +1 -1
- package/dist/core/keybinding-registry.js +4 -3
- package/dist/core/keybinding-registry.js.map +1 -1
- package/dist/core/keybindings-schema.d.ts +4 -0
- package/dist/core/keybindings-schema.d.ts.map +1 -0
- package/dist/core/keybindings-schema.js +16 -0
- package/dist/core/keybindings-schema.js.map +1 -0
- package/dist/core/keybindings.d.ts +258 -81
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +27 -24
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-config.d.ts +729 -180
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +18 -179
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-resolver.d.ts +56 -43
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +247 -352
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +11 -6
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +3 -3
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/project-trust.d.ts.map +1 -1
- package/dist/core/project-trust.js +2 -1
- package/dist/core/project-trust.js.map +1 -1
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +7 -2
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts +12 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +82 -24
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +7 -2
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-defaults.d.ts +66 -0
- package/dist/core/settings-defaults.d.ts.map +1 -0
- package/dist/core/settings-defaults.js +67 -0
- package/dist/core/settings-defaults.js.map +1 -0
- package/dist/core/settings-manager.d.ts +6 -149
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +59 -44
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-schema.d.ts +233 -0
- package/dist/core/settings-schema.d.ts.map +1 -0
- package/dist/core/settings-schema.js +298 -0
- package/dist/core/settings-schema.js.map +1 -0
- package/dist/core/slash-commands.js +1 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +20 -0
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +67 -0
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/renderers/bash.d.ts.map +1 -1
- package/dist/core/tools/renderers/bash.js +21 -50
- package/dist/core/tools/renderers/bash.js.map +1 -1
- package/dist/core/tools/renderers/read.d.ts.map +1 -1
- package/dist/core/tools/renderers/read.js +7 -11
- package/dist/core/tools/renderers/read.js.map +1 -1
- package/dist/extensions/btw/keybindings.d.ts +2 -2
- package/dist/extensions/btw/keybindings.d.ts.map +1 -1
- package/dist/extensions/btw/keybindings.js.map +1 -1
- package/dist/extensions/codemode/execute.d.ts.map +1 -1
- package/dist/extensions/codemode/execute.js +76 -45
- package/dist/extensions/codemode/execute.js.map +1 -1
- package/dist/extensions/codemode/index.d.ts.map +1 -1
- package/dist/extensions/codemode/index.js +3 -7
- package/dist/extensions/codemode/index.js.map +1 -1
- package/dist/extensions/codemode/renderer.d.ts +2 -2
- package/dist/extensions/codemode/renderer.d.ts.map +1 -1
- package/dist/extensions/codemode/renderer.js +9 -3
- package/dist/extensions/codemode/renderer.js.map +1 -1
- package/dist/extensions/codemode/tool.d.ts +6 -12
- package/dist/extensions/codemode/tool.d.ts.map +1 -1
- package/dist/extensions/codemode/tool.js +44 -32
- package/dist/extensions/codemode/tool.js.map +1 -1
- package/dist/extensions/list-keybindings.d.ts +2 -2
- package/dist/extensions/list-keybindings.d.ts.map +1 -1
- package/dist/extensions/list-keybindings.js.map +1 -1
- package/dist/extensions/mcp/cli.js +5 -8
- package/dist/extensions/mcp/cli.js.map +1 -1
- package/dist/extensions/mcp/index.d.ts.map +1 -1
- package/dist/extensions/mcp/index.js +25 -34
- package/dist/extensions/mcp/index.js.map +1 -1
- package/dist/extensions/mcp/resources.d.ts +0 -6
- package/dist/extensions/mcp/resources.d.ts.map +1 -1
- package/dist/extensions/mcp/resources.js +1 -1
- package/dist/extensions/mcp/resources.js.map +1 -1
- package/dist/extensions/mcp/runtime.d.ts +3 -6
- package/dist/extensions/mcp/runtime.d.ts.map +1 -1
- package/dist/extensions/mcp/runtime.js +4 -39
- package/dist/extensions/mcp/runtime.js.map +1 -1
- package/dist/extensions/mcp/tools.d.ts +2 -3
- package/dist/extensions/mcp/tools.d.ts.map +1 -1
- package/dist/extensions/mcp/tools.js +62 -6
- package/dist/extensions/mcp/tools.js.map +1 -1
- package/dist/extensions/provider/keybindings.d.ts +2 -2
- package/dist/extensions/provider/keybindings.d.ts.map +1 -1
- package/dist/extensions/provider/keybindings.js.map +1 -1
- package/dist/extensions/search/code-search.d.ts +0 -2
- package/dist/extensions/search/code-search.d.ts.map +1 -1
- package/dist/extensions/search/code-search.js +2 -2
- package/dist/extensions/search/code-search.js.map +1 -1
- package/dist/extensions/search/command.js +2 -2
- package/dist/extensions/search/command.js.map +1 -1
- package/dist/extensions/search/index.d.ts.map +1 -1
- package/dist/extensions/search/index.js +14 -11
- package/dist/extensions/search/index.js.map +1 -1
- package/dist/extensions/search/workspace.d.ts +11 -10
- package/dist/extensions/search/workspace.d.ts.map +1 -1
- package/dist/extensions/search/workspace.js +259 -141
- package/dist/extensions/search/workspace.js.map +1 -1
- package/dist/extensions/tool-search/index.d.ts.map +1 -1
- package/dist/extensions/tool-search/index.js +1 -6
- package/dist/extensions/tool-search/index.js.map +1 -1
- package/dist/extensions/tool-search/tool.d.ts +0 -2
- package/dist/extensions/tool-search/tool.d.ts.map +1 -1
- package/dist/extensions/tool-search/tool.js +2 -4
- package/dist/extensions/tool-search/tool.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +4 -3
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +13 -2
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/status-indicator.d.ts +1 -1
- package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
- package/dist/modes/interactive/components/status-indicator.js +2 -2
- package/dist/modes/interactive/components/status-indicator.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +2 -9
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +20 -8
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/session-share.d.ts.map +1 -1
- package/dist/modes/interactive/session-share.js +9 -2
- package/dist/modes/interactive/session-share.js.map +1 -1
- package/dist/modes/interactive/tasks/layout.d.ts +2 -0
- package/dist/modes/interactive/tasks/layout.d.ts.map +1 -1
- package/dist/modes/interactive/tasks/layout.js +32 -6
- package/dist/modes/interactive/tasks/layout.js.map +1 -1
- package/dist/modes/interactive/tasks/manager.d.ts.map +1 -1
- package/dist/modes/interactive/tasks/manager.js +20 -2
- package/dist/modes/interactive/tasks/manager.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +1 -1
- package/dist/modes/interactive/theme/light.json +1 -1
- package/dist/modes/interactive/theme/theme-schema.d.ts +28 -0
- package/dist/modes/interactive/theme/theme-schema.d.ts.map +1 -0
- package/dist/modes/interactive/theme/theme-schema.js +105 -0
- package/dist/modes/interactive/theme/theme-schema.js.map +1 -0
- package/dist/modes/interactive/theme/theme-tokens.d.ts +257 -0
- package/dist/modes/interactive/theme/theme-tokens.d.ts.map +1 -0
- package/dist/modes/interactive/theme/theme-tokens.js +94 -0
- package/dist/modes/interactive/theme/theme-tokens.js.map +1 -0
- package/dist/modes/interactive/theme/theme.d.ts +13 -16
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +23 -31
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +4 -0
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +6 -1
- package/dist/utils/clipboard.js.map +1 -1
- package/docs/bundled/extensions/search.md +2 -1
- package/docs/bundled/tasks.md +1 -1
- package/docs/codemode.md +2 -1
- package/docs/compaction.md +2 -2
- package/docs/development.md +0 -13
- package/docs/docs.json +12 -0
- package/docs/environment-variables.md +2 -3
- package/docs/extensions.md +11 -5
- package/docs/keybindings.md +1 -0
- package/docs/llama-cpp.md +1 -1
- package/docs/mcp.md +1 -1
- package/docs/models.md +1 -0
- package/docs/nvidia-rtx-llama-cpp.md +96 -0
- package/docs/providers.md +17 -1
- package/docs/sdk.md +0 -27
- package/docs/sessions.md +1 -1
- package/docs/settings.md +4 -3
- package/docs/themes.md +4 -2
- package/docs/tui.md +1 -1
- package/docs/usage.md +7 -5
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/dynamic-resources/dynamic.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/minimal-mode.ts +4 -5
- package/examples/extensions/overlay-test.ts +2 -2
- package/examples/extensions/plan-mode/index.ts +6 -2
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +151 -199
- package/package.json +8 -20
- package/schemas/keybindings.schema.json +453 -0
- package/schemas/models.schema.json +1157 -0
- package/schemas/settings.schema.json +800 -0
- package/schemas/theme.schema.json +365 -0
- package/dist/bundle/chunks/anthropic-messages-3K3DWVY6.js +0 -6
- package/dist/bundle/chunks/chunk-3MMB5I7J.js +0 -11
- package/dist/bundle/chunks/mistral-conversations-NJUOG4ET.js +0 -5
- package/dist/bundle/chunks/openai-codex-responses-TZWBTTYM.js +0 -10
- package/dist/bundle/chunks/openai-responses-7A6BAQ5X.js +0 -3
- package/dist/modes/interactive/theme/theme-json.d.ts +0 -85
- package/dist/modes/interactive/theme/theme-json.d.ts.map +0 -1
- package/dist/modes/interactive/theme/theme-json.js +0 -132
- package/dist/modes/interactive/theme/theme-json.js.map +0 -1
- package/dist/modes/interactive/theme/theme-schema.json +0 -361
- package/examples/plugins/pi-example-plugin/README.md +0 -38
- package/examples/plugins/pi-example-plugin/package.json +0 -10
- package/examples/plugins/pi-example-plugin/src/contract.ts +0 -13
- package/examples/plugins/pi-example-plugin/src/session.ts +0 -24
- package/examples/plugins/pi-example-plugin/src/tui.ts +0 -31
|
@@ -3,45 +3,104 @@
|
|
|
3
3
|
* `/codebase`; this module executes them read-only, confined to one directory.
|
|
4
4
|
*
|
|
5
5
|
* Security boundary: every model-supplied path must resolve inside the root, both lexically and
|
|
6
|
-
* after following symlinks
|
|
6
|
+
* after following symlinks, and must be a file or directory that `rg --files` lists, so ignored
|
|
7
|
+
* files are neither searched, listed, nor read. Only `rg`, `readfile`, and `tree` exist; there is
|
|
8
|
+
* no shell and no write. Model-supplied rg values only ever fill `--regexp` and `--glob`.
|
|
7
9
|
*/
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
12
|
+
import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
13
|
+
import { createInterface } from "node:readline";
|
|
14
|
+
import { ensureTool } from "../../utils/tools-manager.js";
|
|
11
15
|
export const VIRTUAL_ROOT = "/codebase";
|
|
12
16
|
/** Per-command result cap. Results are sent back to Devin, whose request limit is ~320KB. */
|
|
13
17
|
const RESULT_MAX_LINES = 40;
|
|
14
18
|
const LINE_MAX_CHARS = 200;
|
|
19
|
+
/** Matches read from rg before it is stopped; enough to count which files an over-broad pattern hits. */
|
|
20
|
+
const RG_MAX_MATCHES = 2000;
|
|
15
21
|
/** The repo map's share of that limit; deeper trees fall back to shallower ones until they fit. */
|
|
16
22
|
const MAP_MAX_BYTES = 64 * 1024;
|
|
17
23
|
const MAP_MAX_DEPTH = 3;
|
|
18
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Directories skipped at any depth on top of the ignore files, for a tree that has none: `.git`,
|
|
26
|
+
* which `--hidden` would otherwise add, plus dependency installs and tool caches. Names that are
|
|
27
|
+
* sometimes source or committed code (`build`, `dist`, `vendor`, ...) are left to the ignore files.
|
|
28
|
+
*/
|
|
29
|
+
const SKIPPED_DIRS = [
|
|
19
30
|
".git",
|
|
20
31
|
"node_modules",
|
|
21
|
-
"
|
|
22
|
-
"build",
|
|
23
|
-
"out",
|
|
24
|
-
"target",
|
|
25
|
-
"coverage",
|
|
32
|
+
"bower_components",
|
|
26
33
|
".venv",
|
|
27
34
|
"venv",
|
|
28
35
|
"__pycache__",
|
|
29
|
-
".
|
|
36
|
+
".tox",
|
|
37
|
+
".mypy_cache",
|
|
38
|
+
".pytest_cache",
|
|
39
|
+
".ruff_cache",
|
|
40
|
+
".gradle",
|
|
30
41
|
".next",
|
|
31
42
|
".nuxt",
|
|
43
|
+
".svelte-kit",
|
|
32
44
|
".turbo",
|
|
33
|
-
".
|
|
34
|
-
"
|
|
35
|
-
"third_party",
|
|
45
|
+
".parcel-cache",
|
|
46
|
+
".cache",
|
|
36
47
|
];
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
/**
|
|
49
|
+
* What every rg call sees: hidden files, ignore files (`.gitignore`, `.ignore`, `.rgignore`) even
|
|
50
|
+
* outside a git repository, and none of `SKIPPED_DIRS`.
|
|
51
|
+
*/
|
|
52
|
+
const RG_SCOPE = ["--hidden", "--no-require-git", ...SKIPPED_DIRS.flatMap((dir) => ["--glob", `!${dir}/`])];
|
|
53
|
+
/**
|
|
54
|
+
* Run Pi's ripgrep in `root` on `target`, passing each output line to `onLine` until it returns
|
|
55
|
+
* false. Planner globs in `args` come before `RG_SCOPE`, since rg lets a later glob win. Resolves
|
|
56
|
+
* whether rg ran to the end.
|
|
57
|
+
*/
|
|
58
|
+
async function runRg(root, args, target, onLine, signal) {
|
|
59
|
+
const rg = await ensureTool("rg");
|
|
60
|
+
if (!rg)
|
|
61
|
+
throw new Error("ripgrep (rg) is not available");
|
|
62
|
+
return new Promise((resolvePromise, reject) => {
|
|
63
|
+
const child = spawn(rg, [...args, ...RG_SCOPE, "--", target], { cwd: root, stdio: ["ignore", "pipe", "pipe"] });
|
|
64
|
+
let stderr = "";
|
|
65
|
+
let lines = 0;
|
|
66
|
+
let stopped = false;
|
|
67
|
+
const stop = () => {
|
|
68
|
+
stopped = true;
|
|
69
|
+
child.kill();
|
|
70
|
+
};
|
|
71
|
+
signal?.addEventListener("abort", stop, { once: true });
|
|
72
|
+
child.stderr.on("data", (chunk) => {
|
|
73
|
+
stderr += chunk;
|
|
74
|
+
});
|
|
75
|
+
createInterface({ input: child.stdout }).on("line", (line) => {
|
|
76
|
+
if (stopped)
|
|
77
|
+
return;
|
|
78
|
+
lines++;
|
|
79
|
+
if (!onLine(line))
|
|
80
|
+
stop();
|
|
81
|
+
});
|
|
82
|
+
child.on("error", reject);
|
|
83
|
+
child.on("close", (code) => {
|
|
84
|
+
signal?.removeEventListener("abort", stop);
|
|
85
|
+
if (signal?.aborted)
|
|
86
|
+
return reject(new Error("Operation aborted"));
|
|
87
|
+
// 1 is "no matches"; 2 with output means some files were unreadable, which is not worth failing over.
|
|
88
|
+
if (!stopped && code !== 0 && code !== 1 && !lines) {
|
|
89
|
+
// A regex error spans several lines: the message, the pattern, a caret, and the cause.
|
|
90
|
+
const message = stderr.trim().split(/\r?\n/).slice(0, 6).join("\n");
|
|
91
|
+
return reject(new Error(message || `rg exited with code ${code}`));
|
|
92
|
+
}
|
|
93
|
+
resolvePromise(!stopped);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/** An rg-reported path relative to the root, with forward slashes. */
|
|
98
|
+
function rgPath(path) {
|
|
99
|
+
return path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
100
|
+
}
|
|
101
|
+
/** A planner glob as rg takes it: relative to the root, which is where rg runs. */
|
|
102
|
+
function rgGlob(glob) {
|
|
103
|
+
return glob.replace(/\\/g, "/").replace(new RegExp(`^${VIRTUAL_ROOT}/`), "");
|
|
45
104
|
}
|
|
46
105
|
function within(base, path) {
|
|
47
106
|
const rel = relative(base, path);
|
|
@@ -70,20 +129,25 @@ export function isInside(root, path) {
|
|
|
70
129
|
const abs = resolve(path);
|
|
71
130
|
return within(resolve(root), abs) && within(realpathOf(root), realpathOf(abs));
|
|
72
131
|
}
|
|
132
|
+
function strings(value) {
|
|
133
|
+
return Array.isArray(value) ? value.filter((v) => typeof v === "string" && v.length > 0) : [];
|
|
134
|
+
}
|
|
135
|
+
function clip(line) {
|
|
136
|
+
return line.length > LINE_MAX_CHARS ? line.slice(0, LINE_MAX_CHARS) : line;
|
|
137
|
+
}
|
|
73
138
|
function capLines(text) {
|
|
74
139
|
const lines = text.split("\n");
|
|
75
|
-
const kept = lines
|
|
76
|
-
.slice(0, RESULT_MAX_LINES)
|
|
77
|
-
.map((l) => (l.length > LINE_MAX_CHARS ? l.slice(0, LINE_MAX_CHARS) : l));
|
|
140
|
+
const kept = lines.slice(0, RESULT_MAX_LINES).map(clip);
|
|
78
141
|
if (lines.length > RESULT_MAX_LINES)
|
|
79
142
|
kept.push(`... (${lines.length - RESULT_MAX_LINES} more lines)`);
|
|
80
143
|
return kept.join("\n");
|
|
81
144
|
}
|
|
145
|
+
function virtualOf(rel) {
|
|
146
|
+
return rel ? `${VIRTUAL_ROOT}/${rel}` : VIRTUAL_ROOT;
|
|
147
|
+
}
|
|
82
148
|
export class Workspace {
|
|
83
|
-
constructor(root
|
|
149
|
+
constructor(root) {
|
|
84
150
|
this.root = resolve(root);
|
|
85
|
-
this.grep = grep;
|
|
86
|
-
this.noise = new Set([...NOISE_DIRS, ...gitignoredNames(this.root)]);
|
|
87
151
|
}
|
|
88
152
|
contains(path) {
|
|
89
153
|
return isInside(this.root, path);
|
|
@@ -108,138 +172,192 @@ export class Workspace {
|
|
|
108
172
|
return this.contains(candidate) ? candidate : null;
|
|
109
173
|
}
|
|
110
174
|
toVirtual(real) {
|
|
175
|
+
return virtualOf(relative(this.root, real).split(sep).join("/"));
|
|
176
|
+
}
|
|
177
|
+
/** Every file rg would search, listed once per search; tree, readfile, and rg targets all check against it. */
|
|
178
|
+
files(signal) {
|
|
179
|
+
this.index ??= (async () => {
|
|
180
|
+
const files = new Set();
|
|
181
|
+
await runRg(this.root, ["--files"], ".", (line) => {
|
|
182
|
+
files.add(rgPath(line));
|
|
183
|
+
return true;
|
|
184
|
+
}, signal);
|
|
185
|
+
const entries = new Map([["", new Set()]]);
|
|
186
|
+
for (const file of files) {
|
|
187
|
+
const parts = file.split("/");
|
|
188
|
+
for (let i = 0; i < parts.length; i++) {
|
|
189
|
+
const dir = parts.slice(0, i).join("/");
|
|
190
|
+
if (!entries.has(dir))
|
|
191
|
+
entries.set(dir, new Set());
|
|
192
|
+
entries.get(dir).add(i < parts.length - 1 ? `${parts[i]}/` : parts[i]);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return { files, dirs: new Map([...entries].map(([dir, names]) => [dir, [...names].sort()])) };
|
|
196
|
+
})();
|
|
197
|
+
return this.index;
|
|
198
|
+
}
|
|
199
|
+
/** A model-named path that rg would see, or the error string to return in its place. */
|
|
200
|
+
async find(value, signal) {
|
|
201
|
+
const real = this.toReal(value);
|
|
202
|
+
if (!real)
|
|
203
|
+
return { error: `Error: path outside project root: ${String(value)}` };
|
|
111
204
|
const rel = relative(this.root, real).split(sep).join("/");
|
|
112
|
-
|
|
205
|
+
const index = await this.files(signal);
|
|
206
|
+
if (index.files.has(rel))
|
|
207
|
+
return { rel, real, kind: "file" };
|
|
208
|
+
if (index.dirs.has(rel))
|
|
209
|
+
return { rel, real, kind: "dir" };
|
|
210
|
+
return {
|
|
211
|
+
error: existsSync(real)
|
|
212
|
+
? `Error: not searchable (ignored, a dependency or cache directory, or empty): ${String(value)}`
|
|
213
|
+
: `Error: path does not exist: ${String(value)}`,
|
|
214
|
+
};
|
|
113
215
|
}
|
|
114
216
|
/** The deepest tree of the root, up to three levels, that fits the map budget. */
|
|
115
|
-
repoMap() {
|
|
217
|
+
async repoMap(signal) {
|
|
218
|
+
const index = await this.files(signal);
|
|
116
219
|
for (let depth = MAP_MAX_DEPTH; depth > 1; depth--) {
|
|
117
|
-
const tree =
|
|
220
|
+
const tree = renderTree(index, "", depth);
|
|
118
221
|
if (Buffer.byteLength(tree) <= MAP_MAX_BYTES)
|
|
119
222
|
return { depth, tree };
|
|
120
223
|
}
|
|
121
|
-
return { depth: 1, tree:
|
|
224
|
+
return { depth: 1, tree: renderTree(index, "", 1).slice(0, MAP_MAX_BYTES / 4) };
|
|
122
225
|
}
|
|
123
226
|
async run(command, signal) {
|
|
124
|
-
switch (command?.type) {
|
|
125
|
-
case "rg":
|
|
126
|
-
return this.rg(command, signal);
|
|
127
|
-
case "readfile":
|
|
128
|
-
return this.readfile(command);
|
|
129
|
-
case "tree":
|
|
130
|
-
return this.tree(command);
|
|
131
|
-
default:
|
|
132
|
-
return `Error: unknown command type '${String(command?.type)}'; use rg, readfile, or tree`;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
async rg(command, signal) {
|
|
136
|
-
if (typeof command.pattern !== "string" || !command.pattern)
|
|
137
|
-
return "Error: missing pattern";
|
|
138
|
-
const real = this.toReal(command.path ?? VIRTUAL_ROOT);
|
|
139
|
-
if (!real)
|
|
140
|
-
return `Error: path outside project root: ${String(command.path)}`;
|
|
141
|
-
if (!existsSync(real))
|
|
142
|
-
return `Error: path does not exist: ${String(command.path)}`;
|
|
143
|
-
const include = Array.isArray(command.include) && typeof command.include[0] === "string" ? command.include[0] : undefined;
|
|
144
|
-
let raw;
|
|
145
227
|
try {
|
|
146
|
-
|
|
228
|
+
switch (command?.type) {
|
|
229
|
+
case "rg":
|
|
230
|
+
return await this.rg(command, signal);
|
|
231
|
+
case "readfile":
|
|
232
|
+
return await this.readfile(command, signal);
|
|
233
|
+
case "tree":
|
|
234
|
+
return await this.tree(command, signal);
|
|
235
|
+
default:
|
|
236
|
+
return `Error: unknown command type '${String(command?.type)}'; use rg, readfile, or tree`;
|
|
237
|
+
}
|
|
147
238
|
}
|
|
148
239
|
catch (e) {
|
|
149
240
|
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
150
241
|
}
|
|
151
|
-
// Pi's grep prints paths relative to the searched directory, or the bare file name for a file.
|
|
152
|
-
const isFile = statSync(real).isFile();
|
|
153
|
-
const base = this.toVirtual(real);
|
|
154
|
-
const remapped = raw
|
|
155
|
-
.split("\n")
|
|
156
|
-
.map((line) => {
|
|
157
|
-
const m = line.match(/^(.+?):(\d+):(.*)$/);
|
|
158
|
-
if (!m)
|
|
159
|
-
return line;
|
|
160
|
-
return `${isFile ? base : `${base}/${m[1].replace(/\\/g, "/")}`}:${m[2]}:${m[3]}`;
|
|
161
|
-
})
|
|
162
|
-
.join("\n");
|
|
163
|
-
return capLines(remapped.trim() || "(no matches)");
|
|
164
242
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
243
|
+
async rg(command, signal) {
|
|
244
|
+
if (typeof command.pattern !== "string" || !command.pattern)
|
|
245
|
+
return "Error: missing pattern";
|
|
246
|
+
// rg searches a path it is handed even when ignored, so the target must pass the same check as readfile.
|
|
247
|
+
const target = await this.find(command.path ?? VIRTUAL_ROOT, signal);
|
|
248
|
+
if ("error" in target)
|
|
249
|
+
return target.error;
|
|
250
|
+
const args = ["--null", "--line-number", "--with-filename", "--no-heading", "--color=never", "--smart-case"];
|
|
251
|
+
args.push("--max-columns", String(LINE_MAX_CHARS), "--max-columns-preview");
|
|
252
|
+
for (const glob of strings(command.include))
|
|
253
|
+
args.push("--glob", rgGlob(glob));
|
|
254
|
+
for (const glob of strings(command.exclude))
|
|
255
|
+
args.push("--glob", `!${rgGlob(glob).replace(/^!+/, "")}`);
|
|
256
|
+
args.push("--regexp", command.pattern);
|
|
257
|
+
const matches = [];
|
|
258
|
+
const complete = await runRg(this.root, args, target.rel || ".", (line) => {
|
|
259
|
+
const nul = line.indexOf("\0");
|
|
260
|
+
const m = line.slice(nul + 1).match(/^(\d+):(.*)$/);
|
|
261
|
+
if (nul >= 0 && m) {
|
|
262
|
+
matches.push({ path: rgPath(line.slice(0, nul)), line: Number(m[1]), text: m[2].replace(/\r$/, "") });
|
|
263
|
+
}
|
|
264
|
+
return matches.length < RG_MAX_MATCHES;
|
|
265
|
+
}, signal);
|
|
266
|
+
if (!matches.length)
|
|
267
|
+
return "(no matches)";
|
|
268
|
+
matches.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : a.line - b.line));
|
|
269
|
+
const lines = matches.map((m) => `${VIRTUAL_ROOT}/${m.path}:${m.line}:${m.text}`);
|
|
270
|
+
if (lines.length <= RESULT_MAX_LINES)
|
|
271
|
+
return capLines(lines.join("\n"));
|
|
272
|
+
return capLines([...matchSummary(matches, complete), ...lines].join("\n"));
|
|
273
|
+
}
|
|
274
|
+
/** A numbered slice of a text file, headed by its length and ending with where to continue. */
|
|
275
|
+
async readfile(command, signal) {
|
|
276
|
+
const file = await this.find(command.file, signal);
|
|
277
|
+
if ("error" in file)
|
|
278
|
+
return file.error;
|
|
279
|
+
if (file.kind !== "file")
|
|
280
|
+
return `Error: not a file: ${String(command.file)}`;
|
|
281
|
+
const name = virtualOf(file.rel);
|
|
282
|
+
const buffer = readFileSync(file.real);
|
|
283
|
+
if (buffer.subarray(0, 8192).includes(0))
|
|
284
|
+
return `Error: binary file: ${name}`;
|
|
285
|
+
const lines = buffer.toString("utf-8").split(/\r?\n/);
|
|
286
|
+
if (lines.at(-1) === "")
|
|
287
|
+
lines.pop();
|
|
288
|
+
const total = lines.length;
|
|
289
|
+
if (!total)
|
|
290
|
+
return `${name} (empty)`;
|
|
179
291
|
const start = Math.max(1, Number(command.start_line) || 1);
|
|
180
|
-
const end = Math.min(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
292
|
+
const end = Math.min(total, Number(command.end_line) || total);
|
|
293
|
+
if (start > total)
|
|
294
|
+
return `Error: start_line ${start} is past the end of ${name} (${total} lines)`;
|
|
295
|
+
if (end < start)
|
|
296
|
+
return `Error: end_line ${end} is before start_line ${start}`;
|
|
297
|
+
const last = Math.min(end, start + RESULT_MAX_LINES - 1);
|
|
298
|
+
const out = [`${name} (${total} lines)`];
|
|
299
|
+
for (let n = start; n <= last; n++)
|
|
300
|
+
out.push(clip(`${n}:${lines[n - 1]}`));
|
|
301
|
+
if (last < end)
|
|
302
|
+
out.push(`... (lines ${last + 1}-${end} not shown; continue with start_line=${last + 1})`);
|
|
303
|
+
return out.join("\n");
|
|
185
304
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
catch {
|
|
195
|
-
return `Error: directory not found: ${String(command.path)}`;
|
|
196
|
-
}
|
|
305
|
+
/** The requested depth of a directory, or the deepest one that fits the result cap. */
|
|
306
|
+
async tree(command, signal) {
|
|
307
|
+
const dir = await this.find(command.path ?? VIRTUAL_ROOT, signal);
|
|
308
|
+
if ("error" in dir)
|
|
309
|
+
return dir.error;
|
|
310
|
+
if (dir.kind !== "dir")
|
|
311
|
+
return `Error: not a directory: ${String(command.path)}`;
|
|
312
|
+
const index = await this.files(signal);
|
|
197
313
|
const levels = Math.min(Math.max(Number(command.levels) || 2, 1), 4);
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (depth >= maxDepth)
|
|
204
|
-
return;
|
|
205
|
-
let entries;
|
|
206
|
-
try {
|
|
207
|
-
entries = readdirSync(current)
|
|
208
|
-
.filter((name) => !this.noise.has(name))
|
|
209
|
-
.sort();
|
|
210
|
-
}
|
|
211
|
-
catch {
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
entries.forEach((name, i) => {
|
|
215
|
-
const last = i === entries.length - 1;
|
|
216
|
-
lines.push(`${prefix}${last ? "└── " : "├── "}${name}`);
|
|
217
|
-
const path = join(current, name);
|
|
218
|
-
let isDir = false;
|
|
219
|
-
try {
|
|
220
|
-
isDir = statSync(path).isDirectory() && this.contains(path);
|
|
221
|
-
}
|
|
222
|
-
catch { }
|
|
223
|
-
if (isDir)
|
|
224
|
-
walk(path, depth + 1, `${prefix}${last ? " " : "│ "}`);
|
|
225
|
-
});
|
|
226
|
-
};
|
|
227
|
-
walk(dir, 0, "");
|
|
228
|
-
return lines.join("\n");
|
|
314
|
+
let depth = levels;
|
|
315
|
+
let tree = renderTree(index, dir.rel, depth);
|
|
316
|
+
while (depth > 1 && tree.split("\n").length > RESULT_MAX_LINES)
|
|
317
|
+
tree = renderTree(index, dir.rel, --depth);
|
|
318
|
+
return depth < levels ? `${capLines(tree)}\n(levels reduced to ${depth} to fit)` : capLines(tree);
|
|
229
319
|
}
|
|
230
320
|
}
|
|
231
|
-
/**
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
.
|
|
321
|
+
/**
|
|
322
|
+
* What an over-broad pattern hit, per file, ahead of the first matches; rg's arbitrary first 40
|
|
323
|
+
* lines would say nothing about where to narrow next.
|
|
324
|
+
*/
|
|
325
|
+
function matchSummary(matches, complete) {
|
|
326
|
+
const counts = new Map();
|
|
327
|
+
for (const m of matches)
|
|
328
|
+
counts.set(m.path, (counts.get(m.path) ?? 0) + 1);
|
|
329
|
+
const total = complete ? `${matches.length} matches` : `More than ${RG_MAX_MATCHES} matches`;
|
|
330
|
+
const files = complete ? `${counts.size} files` : `at least ${counts.size} files`;
|
|
331
|
+
// Densest files first: they are where a narrower search most likely belongs.
|
|
332
|
+
const rows = [...counts]
|
|
333
|
+
.sort((a, b) => b[1] - a[1])
|
|
334
|
+
.slice(0, RESULT_MAX_LINES / 2)
|
|
335
|
+
.map(([path, n]) => `${VIRTUAL_ROOT}/${path}: ${n}`);
|
|
336
|
+
if (counts.size > rows.length)
|
|
337
|
+
rows.push(`... (${counts.size - rows.length} more files)`);
|
|
338
|
+
return [
|
|
339
|
+
`${total} in ${files}; narrow with path, include, exclude, or a more specific pattern. Matches per file:`,
|
|
340
|
+
...rows,
|
|
341
|
+
"First matches:",
|
|
342
|
+
];
|
|
343
|
+
}
|
|
344
|
+
/** `dir` and its entries to `maxDepth` levels; a trailing slash marks a directory. */
|
|
345
|
+
function renderTree(index, dir, maxDepth) {
|
|
346
|
+
const lines = [virtualOf(dir)];
|
|
347
|
+
const walk = (current, depth, prefix) => {
|
|
348
|
+
if (depth >= maxDepth)
|
|
349
|
+
return;
|
|
350
|
+
const entries = index.dirs.get(current) ?? [];
|
|
351
|
+
entries.forEach((name, i) => {
|
|
352
|
+
const last = i === entries.length - 1;
|
|
353
|
+
lines.push(`${prefix}${last ? "└── " : "├── "}${name}`);
|
|
354
|
+
if (name.endsWith("/")) {
|
|
355
|
+
const child = current ? `${current}/${name.slice(0, -1)}` : name.slice(0, -1);
|
|
356
|
+
walk(child, depth + 1, `${prefix}${last ? " " : "│ "}`);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
};
|
|
360
|
+
walk(dir, 0, "");
|
|
361
|
+
return lines.join("\n");
|
|
244
362
|
}
|
|
245
363
|
//# sourceMappingURL=workspace.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../../src/extensions/search/workspace.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The local side of code_search: the planner runs remotely and names commands against a virtual\n * `/codebase`; this module executes them read-only, confined to one directory.\n *\n * Security boundary: every model-supplied path must resolve inside the root, both lexically and\n * after following symlinks. Only `rg`, `readfile`, and `tree` exist; there is no shell and no write.\n */\nimport { existsSync, readdirSync, readFileSync, realpathSync, statSync } from \"node:fs\";\nimport { basename, dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport { createGrepToolDefinition } from \"../../core/tools/grep.ts\";\n\nexport const VIRTUAL_ROOT = \"/codebase\";\n\n/** Per-command result cap. Results are sent back to Devin, whose request limit is ~320KB. */\nconst RESULT_MAX_LINES = 40;\nconst LINE_MAX_CHARS = 200;\n/** The repo map's share of that limit; deeper trees fall back to shallower ones until they fit. */\nconst MAP_MAX_BYTES = 64 * 1024;\nconst MAP_MAX_DEPTH = 3;\n\nconst NOISE_DIRS = [\n\t\".git\",\n\t\"node_modules\",\n\t\"dist\",\n\t\"build\",\n\t\"out\",\n\t\"target\",\n\t\"coverage\",\n\t\".venv\",\n\t\"venv\",\n\t\"__pycache__\",\n\t\".cache\",\n\t\".next\",\n\t\".nuxt\",\n\t\".turbo\",\n\t\".idea\",\n\t\"vendor\",\n\t\"third_party\",\n];\n\n/** `rg` backend: pattern, directory or file, optional glob → `path:line: text` lines. */\nexport type Grep = (pattern: string, path: string, glob: string | undefined, signal?: AbortSignal) => Promise<string>;\n\n/** Pi's own grep tool: gitignore-aware, with its bundled ripgrep. It takes one glob, so only the first include applies. */\nexport function piGrep(root: string): Grep {\n\tconst tool = createGrepToolDefinition(root);\n\tconst execute = tool.execute as unknown as (\n\t\tid: string,\n\t\tparams: { pattern: string; path: string; glob?: string; limit: number },\n\t\tsignal?: AbortSignal,\n\t) => Promise<{ content?: Array<{ type?: string; text?: string }> }>;\n\treturn async (pattern, path, glob, signal) => {\n\t\tconst result = await execute(\"code_search\", { pattern, path, glob, limit: RESULT_MAX_LINES }, signal);\n\t\treturn result.content?.find((c) => c.type === \"text\")?.text ?? \"\";\n\t};\n}\n\nfunction within(base: string, path: string): boolean {\n\tconst rel = relative(base, path);\n\treturn rel === \"\" || (!rel.startsWith(`..${sep}`) && rel !== \"..\" && !isAbsolute(rel));\n}\n\n/** realpath of the longest existing prefix, so a not-yet-existing path still resolves through symlinks. */\nfunction realpathOf(path: string): string {\n\tlet current = resolve(path);\n\tconst tail: string[] = [];\n\twhile (!existsSync(current)) {\n\t\tconst parent = dirname(current);\n\t\tif (parent === current) return resolve(path);\n\t\ttail.unshift(basename(current));\n\t\tcurrent = parent;\n\t}\n\ttry {\n\t\treturn resolve(realpathSync(current), ...tail);\n\t} catch {\n\t\treturn resolve(path);\n\t}\n}\n\n/** Whether `path` lies inside `root`, both as written and after following symlinks. */\nexport function isInside(root: string, path: string): boolean {\n\tconst abs = resolve(path);\n\treturn within(resolve(root), abs) && within(realpathOf(root), realpathOf(abs));\n}\n\nfunction capLines(text: string): string {\n\tconst lines = text.split(\"\\n\");\n\tconst kept = lines\n\t\t.slice(0, RESULT_MAX_LINES)\n\t\t.map((l) => (l.length > LINE_MAX_CHARS ? l.slice(0, LINE_MAX_CHARS) : l));\n\tif (lines.length > RESULT_MAX_LINES) kept.push(`... (${lines.length - RESULT_MAX_LINES} more lines)`);\n\treturn kept.join(\"\\n\");\n}\n\nexport interface Command {\n\ttype?: unknown;\n\tpattern?: unknown;\n\tpath?: unknown;\n\tinclude?: unknown;\n\tfile?: unknown;\n\tstart_line?: unknown;\n\tend_line?: unknown;\n\tlevels?: unknown;\n}\n\nexport class Workspace {\n\treadonly root: string;\n\tprivate readonly grep: Grep;\n\tprivate readonly noise: Set<string>;\n\n\tconstructor(root: string, grep: Grep = piGrep(root)) {\n\t\tthis.root = resolve(root);\n\t\tthis.grep = grep;\n\t\tthis.noise = new Set([...NOISE_DIRS, ...gitignoredNames(this.root)]);\n\t}\n\n\tcontains(path: string): boolean {\n\t\treturn isInside(this.root, path);\n\t}\n\n\t/** `/codebase/src/a.ts` or `src/a.ts` → an absolute path inside the root, or null. */\n\ttoReal(virtual: unknown): string | null {\n\t\tif (typeof virtual !== \"string\" || !virtual) return null;\n\t\tconst normalized = virtual.replace(/\\\\/g, \"/\");\n\t\tlet rel: string;\n\t\tif (normalized === VIRTUAL_ROOT || normalized.startsWith(`${VIRTUAL_ROOT}/`)) {\n\t\t\trel = normalized.slice(VIRTUAL_ROOT.length);\n\t\t} else if (isAbsolute(virtual) || normalized.startsWith(\"/\") || /^[a-zA-Z]:/.test(normalized)) {\n\t\t\t// A drive-letter path is another drive on Windows and a stray filename elsewhere; refuse it everywhere.\n\t\t\treturn null;\n\t\t} else {\n\t\t\trel = normalized;\n\t\t}\n\t\tconst candidate = resolve(this.root, rel.replace(/^\\/+/, \"\"));\n\t\treturn this.contains(candidate) ? candidate : null;\n\t}\n\n\ttoVirtual(real: string): string {\n\t\tconst rel = relative(this.root, real).split(sep).join(\"/\");\n\t\treturn rel ? `${VIRTUAL_ROOT}/${rel}` : VIRTUAL_ROOT;\n\t}\n\n\t/** The deepest tree of the root, up to three levels, that fits the map budget. */\n\trepoMap(): { depth: number; tree: string } {\n\t\tfor (let depth = MAP_MAX_DEPTH; depth > 1; depth--) {\n\t\t\tconst tree = this.renderTree(this.root, depth);\n\t\t\tif (Buffer.byteLength(tree) <= MAP_MAX_BYTES) return { depth, tree };\n\t\t}\n\t\treturn { depth: 1, tree: this.renderTree(this.root, 1).slice(0, MAP_MAX_BYTES / 4) };\n\t}\n\n\tasync run(command: Command, signal?: AbortSignal): Promise<string> {\n\t\tswitch (command?.type) {\n\t\t\tcase \"rg\":\n\t\t\t\treturn this.rg(command, signal);\n\t\t\tcase \"readfile\":\n\t\t\t\treturn this.readfile(command);\n\t\t\tcase \"tree\":\n\t\t\t\treturn this.tree(command);\n\t\t\tdefault:\n\t\t\t\treturn `Error: unknown command type '${String(command?.type)}'; use rg, readfile, or tree`;\n\t\t}\n\t}\n\n\tprivate async rg(command: Command, signal?: AbortSignal): Promise<string> {\n\t\tif (typeof command.pattern !== \"string\" || !command.pattern) return \"Error: missing pattern\";\n\t\tconst real = this.toReal(command.path ?? VIRTUAL_ROOT);\n\t\tif (!real) return `Error: path outside project root: ${String(command.path)}`;\n\t\tif (!existsSync(real)) return `Error: path does not exist: ${String(command.path)}`;\n\t\tconst include =\n\t\t\tArray.isArray(command.include) && typeof command.include[0] === \"string\" ? command.include[0] : undefined;\n\t\tlet raw: string;\n\t\ttry {\n\t\t\traw = await this.grep(command.pattern, real, include, signal);\n\t\t} catch (e) {\n\t\t\treturn `Error: ${e instanceof Error ? e.message : String(e)}`;\n\t\t}\n\t\t// Pi's grep prints paths relative to the searched directory, or the bare file name for a file.\n\t\tconst isFile = statSync(real).isFile();\n\t\tconst base = this.toVirtual(real);\n\t\tconst remapped = raw\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => {\n\t\t\t\tconst m = line.match(/^(.+?):(\\d+):(.*)$/);\n\t\t\t\tif (!m) return line;\n\t\t\t\treturn `${isFile ? base : `${base}/${m[1]!.replace(/\\\\/g, \"/\")}`}:${m[2]}:${m[3]}`;\n\t\t\t})\n\t\t\t.join(\"\\n\");\n\t\treturn capLines(remapped.trim() || \"(no matches)\");\n\t}\n\n\tprivate readfile(command: Command): string {\n\t\tconst real = this.toReal(command.file);\n\t\tif (!real) return `Error: path outside project root: ${String(command.file)}`;\n\t\tlet content: string;\n\t\ttry {\n\t\t\tif (!statSync(real).isFile()) return `Error: not a file: ${String(command.file)}`;\n\t\t\tcontent = readFileSync(real, \"utf-8\");\n\t\t} catch {\n\t\t\treturn `Error: file not found: ${String(command.file)}`;\n\t\t}\n\t\tconst lines = content.split(\"\\n\");\n\t\tconst start = Math.max(1, Number(command.start_line) || 1);\n\t\tconst end = Math.min(lines.length, Number(command.end_line) || lines.length);\n\t\treturn capLines(\n\t\t\tlines\n\t\t\t\t.slice(start - 1, end)\n\t\t\t\t.map((line, i) => `${start + i}:${line}`)\n\t\t\t\t.join(\"\\n\"),\n\t\t);\n\t}\n\n\tprivate tree(command: Command): string {\n\t\tconst real = this.toReal(command.path ?? VIRTUAL_ROOT);\n\t\tif (!real) return `Error: path outside project root: ${String(command.path)}`;\n\t\ttry {\n\t\t\tif (!statSync(real).isDirectory()) return `Error: not a directory: ${String(command.path)}`;\n\t\t} catch {\n\t\t\treturn `Error: directory not found: ${String(command.path)}`;\n\t\t}\n\t\tconst levels = Math.min(Math.max(Number(command.levels) || 2, 1), 4);\n\t\treturn capLines(this.renderTree(real, levels));\n\t}\n\n\tprivate renderTree(dir: string, maxDepth: number): string {\n\t\tconst lines = [this.toVirtual(dir)];\n\t\tconst walk = (current: string, depth: number, prefix: string): void => {\n\t\t\tif (depth >= maxDepth) return;\n\t\t\tlet entries: string[];\n\t\t\ttry {\n\t\t\t\tentries = readdirSync(current)\n\t\t\t\t\t.filter((name) => !this.noise.has(name))\n\t\t\t\t\t.sort();\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tentries.forEach((name, i) => {\n\t\t\t\tconst last = i === entries.length - 1;\n\t\t\t\tlines.push(`${prefix}${last ? \"└── \" : \"├── \"}${name}`);\n\t\t\t\tconst path = join(current, name);\n\t\t\t\tlet isDir = false;\n\t\t\t\ttry {\n\t\t\t\t\tisDir = statSync(path).isDirectory() && this.contains(path);\n\t\t\t\t} catch {}\n\t\t\t\tif (isDir) walk(path, depth + 1, `${prefix}${last ? \" \" : \"│ \"}`);\n\t\t\t});\n\t\t};\n\t\twalk(dir, 0, \"\");\n\t\treturn lines.join(\"\\n\");\n\t}\n}\n\n/** Plain directory names from `.gitignore`; patterns with slashes or globs are left to grep. */\nfunction gitignoredNames(root: string): string[] {\n\tlet text: string;\n\ttry {\n\t\ttext = readFileSync(join(root, \".gitignore\"), \"utf-8\");\n\t} catch {\n\t\treturn [];\n\t}\n\treturn text\n\t\t.split(\"\\n\")\n\t\t.map((line) => line.trim().replace(/^\\/+|\\/+$/g, \"\"))\n\t\t.filter((name) => name && !name.startsWith(\"#\") && !name.startsWith(\"!\") && !/[/*?[\\]]/.test(name));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../../src/extensions/search/workspace.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The local side of code_search: the planner runs remotely and names commands against a virtual\n * `/codebase`; this module executes them read-only, confined to one directory.\n *\n * Security boundary: every model-supplied path must resolve inside the root, both lexically and\n * after following symlinks, and must be a file or directory that `rg --files` lists, so ignored\n * files are neither searched, listed, nor read. Only `rg`, `readfile`, and `tree` exist; there is\n * no shell and no write. Model-supplied rg values only ever fill `--regexp` and `--glob`.\n */\nimport { spawn } from \"node:child_process\";\nimport { existsSync, readFileSync, realpathSync } from \"node:fs\";\nimport { basename, dirname, isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { ensureTool } from \"../../utils/tools-manager.ts\";\n\nexport const VIRTUAL_ROOT = \"/codebase\";\n\n/** Per-command result cap. Results are sent back to Devin, whose request limit is ~320KB. */\nconst RESULT_MAX_LINES = 40;\nconst LINE_MAX_CHARS = 200;\n/** Matches read from rg before it is stopped; enough to count which files an over-broad pattern hits. */\nconst RG_MAX_MATCHES = 2000;\n/** The repo map's share of that limit; deeper trees fall back to shallower ones until they fit. */\nconst MAP_MAX_BYTES = 64 * 1024;\nconst MAP_MAX_DEPTH = 3;\n\n/**\n * Directories skipped at any depth on top of the ignore files, for a tree that has none: `.git`,\n * which `--hidden` would otherwise add, plus dependency installs and tool caches. Names that are\n * sometimes source or committed code (`build`, `dist`, `vendor`, ...) are left to the ignore files.\n */\nconst SKIPPED_DIRS = [\n\t\".git\",\n\t\"node_modules\",\n\t\"bower_components\",\n\t\".venv\",\n\t\"venv\",\n\t\"__pycache__\",\n\t\".tox\",\n\t\".mypy_cache\",\n\t\".pytest_cache\",\n\t\".ruff_cache\",\n\t\".gradle\",\n\t\".next\",\n\t\".nuxt\",\n\t\".svelte-kit\",\n\t\".turbo\",\n\t\".parcel-cache\",\n\t\".cache\",\n];\n\n/**\n * What every rg call sees: hidden files, ignore files (`.gitignore`, `.ignore`, `.rgignore`) even\n * outside a git repository, and none of `SKIPPED_DIRS`.\n */\nconst RG_SCOPE = [\"--hidden\", \"--no-require-git\", ...SKIPPED_DIRS.flatMap((dir) => [\"--glob\", `!${dir}/`])];\n\ninterface Match {\n\t/** Relative to the root, with forward slashes. */\n\tpath: string;\n\tline: number;\n\ttext: string;\n}\n\n/** The files rg would search, and the directories that hold them. */\ninterface Index {\n\t/** Relative to the root, with forward slashes. */\n\tfiles: Set<string>;\n\t/** Directory (\"\" for the root) → its sorted entries, subdirectories with a trailing slash. */\n\tdirs: Map<string, string[]>;\n}\n\n/**\n * Run Pi's ripgrep in `root` on `target`, passing each output line to `onLine` until it returns\n * false. Planner globs in `args` come before `RG_SCOPE`, since rg lets a later glob win. Resolves\n * whether rg ran to the end.\n */\nasync function runRg(\n\troot: string,\n\targs: string[],\n\ttarget: string,\n\tonLine: (line: string) => boolean,\n\tsignal?: AbortSignal,\n): Promise<boolean> {\n\tconst rg = await ensureTool(\"rg\");\n\tif (!rg) throw new Error(\"ripgrep (rg) is not available\");\n\treturn new Promise((resolvePromise, reject) => {\n\t\tconst child = spawn(rg, [...args, ...RG_SCOPE, \"--\", target], { cwd: root, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\tlet stderr = \"\";\n\t\tlet lines = 0;\n\t\tlet stopped = false;\n\t\tconst stop = () => {\n\t\t\tstopped = true;\n\t\t\tchild.kill();\n\t\t};\n\t\tsignal?.addEventListener(\"abort\", stop, { once: true });\n\t\tchild.stderr.on(\"data\", (chunk) => {\n\t\t\tstderr += chunk;\n\t\t});\n\t\tcreateInterface({ input: child.stdout }).on(\"line\", (line) => {\n\t\t\tif (stopped) return;\n\t\t\tlines++;\n\t\t\tif (!onLine(line)) stop();\n\t\t});\n\t\tchild.on(\"error\", reject);\n\t\tchild.on(\"close\", (code) => {\n\t\t\tsignal?.removeEventListener(\"abort\", stop);\n\t\t\tif (signal?.aborted) return reject(new Error(\"Operation aborted\"));\n\t\t\t// 1 is \"no matches\"; 2 with output means some files were unreadable, which is not worth failing over.\n\t\t\tif (!stopped && code !== 0 && code !== 1 && !lines) {\n\t\t\t\t// A regex error spans several lines: the message, the pattern, a caret, and the cause.\n\t\t\t\tconst message = stderr.trim().split(/\\r?\\n/).slice(0, 6).join(\"\\n\");\n\t\t\t\treturn reject(new Error(message || `rg exited with code ${code}`));\n\t\t\t}\n\t\t\tresolvePromise(!stopped);\n\t\t});\n\t});\n}\n\n/** An rg-reported path relative to the root, with forward slashes. */\nfunction rgPath(path: string): string {\n\treturn path.replace(/\\\\/g, \"/\").replace(/^\\.\\//, \"\");\n}\n\n/** A planner glob as rg takes it: relative to the root, which is where rg runs. */\nfunction rgGlob(glob: string): string {\n\treturn glob.replace(/\\\\/g, \"/\").replace(new RegExp(`^${VIRTUAL_ROOT}/`), \"\");\n}\n\nfunction within(base: string, path: string): boolean {\n\tconst rel = relative(base, path);\n\treturn rel === \"\" || (!rel.startsWith(`..${sep}`) && rel !== \"..\" && !isAbsolute(rel));\n}\n\n/** realpath of the longest existing prefix, so a not-yet-existing path still resolves through symlinks. */\nfunction realpathOf(path: string): string {\n\tlet current = resolve(path);\n\tconst tail: string[] = [];\n\twhile (!existsSync(current)) {\n\t\tconst parent = dirname(current);\n\t\tif (parent === current) return resolve(path);\n\t\ttail.unshift(basename(current));\n\t\tcurrent = parent;\n\t}\n\ttry {\n\t\treturn resolve(realpathSync(current), ...tail);\n\t} catch {\n\t\treturn resolve(path);\n\t}\n}\n\n/** Whether `path` lies inside `root`, both as written and after following symlinks. */\nexport function isInside(root: string, path: string): boolean {\n\tconst abs = resolve(path);\n\treturn within(resolve(root), abs) && within(realpathOf(root), realpathOf(abs));\n}\n\nfunction strings(value: unknown): string[] {\n\treturn Array.isArray(value) ? value.filter((v): v is string => typeof v === \"string\" && v.length > 0) : [];\n}\n\nfunction clip(line: string): string {\n\treturn line.length > LINE_MAX_CHARS ? line.slice(0, LINE_MAX_CHARS) : line;\n}\n\nfunction capLines(text: string): string {\n\tconst lines = text.split(\"\\n\");\n\tconst kept = lines.slice(0, RESULT_MAX_LINES).map(clip);\n\tif (lines.length > RESULT_MAX_LINES) kept.push(`... (${lines.length - RESULT_MAX_LINES} more lines)`);\n\treturn kept.join(\"\\n\");\n}\n\nfunction virtualOf(rel: string): string {\n\treturn rel ? `${VIRTUAL_ROOT}/${rel}` : VIRTUAL_ROOT;\n}\n\nexport interface Command {\n\ttype?: unknown;\n\tpattern?: unknown;\n\tpath?: unknown;\n\tinclude?: unknown;\n\texclude?: unknown;\n\tfile?: unknown;\n\tstart_line?: unknown;\n\tend_line?: unknown;\n\tlevels?: unknown;\n}\n\nexport class Workspace {\n\treadonly root: string;\n\tprivate index?: Promise<Index>;\n\n\tconstructor(root: string) {\n\t\tthis.root = resolve(root);\n\t}\n\n\tcontains(path: string): boolean {\n\t\treturn isInside(this.root, path);\n\t}\n\n\t/** `/codebase/src/a.ts` or `src/a.ts` → an absolute path inside the root, or null. */\n\ttoReal(virtual: unknown): string | null {\n\t\tif (typeof virtual !== \"string\" || !virtual) return null;\n\t\tconst normalized = virtual.replace(/\\\\/g, \"/\");\n\t\tlet rel: string;\n\t\tif (normalized === VIRTUAL_ROOT || normalized.startsWith(`${VIRTUAL_ROOT}/`)) {\n\t\t\trel = normalized.slice(VIRTUAL_ROOT.length);\n\t\t} else if (isAbsolute(virtual) || normalized.startsWith(\"/\") || /^[a-zA-Z]:/.test(normalized)) {\n\t\t\t// A drive-letter path is another drive on Windows and a stray filename elsewhere; refuse it everywhere.\n\t\t\treturn null;\n\t\t} else {\n\t\t\trel = normalized;\n\t\t}\n\t\tconst candidate = resolve(this.root, rel.replace(/^\\/+/, \"\"));\n\t\treturn this.contains(candidate) ? candidate : null;\n\t}\n\n\ttoVirtual(real: string): string {\n\t\treturn virtualOf(relative(this.root, real).split(sep).join(\"/\"));\n\t}\n\n\t/** Every file rg would search, listed once per search; tree, readfile, and rg targets all check against it. */\n\tprivate files(signal?: AbortSignal): Promise<Index> {\n\t\tthis.index ??= (async () => {\n\t\t\tconst files = new Set<string>();\n\t\t\tawait runRg(\n\t\t\t\tthis.root,\n\t\t\t\t[\"--files\"],\n\t\t\t\t\".\",\n\t\t\t\t(line) => {\n\t\t\t\t\tfiles.add(rgPath(line));\n\t\t\t\t\treturn true;\n\t\t\t\t},\n\t\t\t\tsignal,\n\t\t\t);\n\t\t\tconst entries = new Map<string, Set<string>>([[\"\", new Set()]]);\n\t\t\tfor (const file of files) {\n\t\t\t\tconst parts = file.split(\"/\");\n\t\t\t\tfor (let i = 0; i < parts.length; i++) {\n\t\t\t\t\tconst dir = parts.slice(0, i).join(\"/\");\n\t\t\t\t\tif (!entries.has(dir)) entries.set(dir, new Set());\n\t\t\t\t\tentries.get(dir)!.add(i < parts.length - 1 ? `${parts[i]}/` : parts[i]!);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { files, dirs: new Map([...entries].map(([dir, names]) => [dir, [...names].sort()])) };\n\t\t})();\n\t\treturn this.index;\n\t}\n\n\t/** A model-named path that rg would see, or the error string to return in its place. */\n\tprivate async find(\n\t\tvalue: unknown,\n\t\tsignal?: AbortSignal,\n\t): Promise<{ rel: string; real: string; kind: \"file\" | \"dir\" } | { error: string }> {\n\t\tconst real = this.toReal(value);\n\t\tif (!real) return { error: `Error: path outside project root: ${String(value)}` };\n\t\tconst rel = relative(this.root, real).split(sep).join(\"/\");\n\t\tconst index = await this.files(signal);\n\t\tif (index.files.has(rel)) return { rel, real, kind: \"file\" };\n\t\tif (index.dirs.has(rel)) return { rel, real, kind: \"dir\" };\n\t\treturn {\n\t\t\terror: existsSync(real)\n\t\t\t\t? `Error: not searchable (ignored, a dependency or cache directory, or empty): ${String(value)}`\n\t\t\t\t: `Error: path does not exist: ${String(value)}`,\n\t\t};\n\t}\n\n\t/** The deepest tree of the root, up to three levels, that fits the map budget. */\n\tasync repoMap(signal?: AbortSignal): Promise<{ depth: number; tree: string }> {\n\t\tconst index = await this.files(signal);\n\t\tfor (let depth = MAP_MAX_DEPTH; depth > 1; depth--) {\n\t\t\tconst tree = renderTree(index, \"\", depth);\n\t\t\tif (Buffer.byteLength(tree) <= MAP_MAX_BYTES) return { depth, tree };\n\t\t}\n\t\treturn { depth: 1, tree: renderTree(index, \"\", 1).slice(0, MAP_MAX_BYTES / 4) };\n\t}\n\n\tasync run(command: Command, signal?: AbortSignal): Promise<string> {\n\t\ttry {\n\t\t\tswitch (command?.type) {\n\t\t\t\tcase \"rg\":\n\t\t\t\t\treturn await this.rg(command, signal);\n\t\t\t\tcase \"readfile\":\n\t\t\t\t\treturn await this.readfile(command, signal);\n\t\t\t\tcase \"tree\":\n\t\t\t\t\treturn await this.tree(command, signal);\n\t\t\t\tdefault:\n\t\t\t\t\treturn `Error: unknown command type '${String(command?.type)}'; use rg, readfile, or tree`;\n\t\t\t}\n\t\t} catch (e) {\n\t\t\treturn `Error: ${e instanceof Error ? e.message : String(e)}`;\n\t\t}\n\t}\n\n\tprivate async rg(command: Command, signal?: AbortSignal): Promise<string> {\n\t\tif (typeof command.pattern !== \"string\" || !command.pattern) return \"Error: missing pattern\";\n\t\t// rg searches a path it is handed even when ignored, so the target must pass the same check as readfile.\n\t\tconst target = await this.find(command.path ?? VIRTUAL_ROOT, signal);\n\t\tif (\"error\" in target) return target.error;\n\t\tconst args = [\"--null\", \"--line-number\", \"--with-filename\", \"--no-heading\", \"--color=never\", \"--smart-case\"];\n\t\targs.push(\"--max-columns\", String(LINE_MAX_CHARS), \"--max-columns-preview\");\n\t\tfor (const glob of strings(command.include)) args.push(\"--glob\", rgGlob(glob));\n\t\tfor (const glob of strings(command.exclude)) args.push(\"--glob\", `!${rgGlob(glob).replace(/^!+/, \"\")}`);\n\t\targs.push(\"--regexp\", command.pattern);\n\n\t\tconst matches: Match[] = [];\n\t\tconst complete = await runRg(\n\t\t\tthis.root,\n\t\t\targs,\n\t\t\ttarget.rel || \".\",\n\t\t\t(line) => {\n\t\t\t\tconst nul = line.indexOf(\"\\0\");\n\t\t\t\tconst m = line.slice(nul + 1).match(/^(\\d+):(.*)$/);\n\t\t\t\tif (nul >= 0 && m) {\n\t\t\t\t\tmatches.push({ path: rgPath(line.slice(0, nul)), line: Number(m[1]), text: m[2]!.replace(/\\r$/, \"\") });\n\t\t\t\t}\n\t\t\t\treturn matches.length < RG_MAX_MATCHES;\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tif (!matches.length) return \"(no matches)\";\n\t\tmatches.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : a.line - b.line));\n\t\tconst lines = matches.map((m) => `${VIRTUAL_ROOT}/${m.path}:${m.line}:${m.text}`);\n\t\tif (lines.length <= RESULT_MAX_LINES) return capLines(lines.join(\"\\n\"));\n\t\treturn capLines([...matchSummary(matches, complete), ...lines].join(\"\\n\"));\n\t}\n\n\t/** A numbered slice of a text file, headed by its length and ending with where to continue. */\n\tprivate async readfile(command: Command, signal?: AbortSignal): Promise<string> {\n\t\tconst file = await this.find(command.file, signal);\n\t\tif (\"error\" in file) return file.error;\n\t\tif (file.kind !== \"file\") return `Error: not a file: ${String(command.file)}`;\n\t\tconst name = virtualOf(file.rel);\n\t\tconst buffer = readFileSync(file.real);\n\t\tif (buffer.subarray(0, 8192).includes(0)) return `Error: binary file: ${name}`;\n\t\tconst lines = buffer.toString(\"utf-8\").split(/\\r?\\n/);\n\t\tif (lines.at(-1) === \"\") lines.pop();\n\t\tconst total = lines.length;\n\t\tif (!total) return `${name} (empty)`;\n\n\t\tconst start = Math.max(1, Number(command.start_line) || 1);\n\t\tconst end = Math.min(total, Number(command.end_line) || total);\n\t\tif (start > total) return `Error: start_line ${start} is past the end of ${name} (${total} lines)`;\n\t\tif (end < start) return `Error: end_line ${end} is before start_line ${start}`;\n\t\tconst last = Math.min(end, start + RESULT_MAX_LINES - 1);\n\t\tconst out = [`${name} (${total} lines)`];\n\t\tfor (let n = start; n <= last; n++) out.push(clip(`${n}:${lines[n - 1]}`));\n\t\tif (last < end) out.push(`... (lines ${last + 1}-${end} not shown; continue with start_line=${last + 1})`);\n\t\treturn out.join(\"\\n\");\n\t}\n\n\t/** The requested depth of a directory, or the deepest one that fits the result cap. */\n\tprivate async tree(command: Command, signal?: AbortSignal): Promise<string> {\n\t\tconst dir = await this.find(command.path ?? VIRTUAL_ROOT, signal);\n\t\tif (\"error\" in dir) return dir.error;\n\t\tif (dir.kind !== \"dir\") return `Error: not a directory: ${String(command.path)}`;\n\t\tconst index = await this.files(signal);\n\t\tconst levels = Math.min(Math.max(Number(command.levels) || 2, 1), 4);\n\t\tlet depth = levels;\n\t\tlet tree = renderTree(index, dir.rel, depth);\n\t\twhile (depth > 1 && tree.split(\"\\n\").length > RESULT_MAX_LINES) tree = renderTree(index, dir.rel, --depth);\n\t\treturn depth < levels ? `${capLines(tree)}\\n(levels reduced to ${depth} to fit)` : capLines(tree);\n\t}\n}\n\n/**\n * What an over-broad pattern hit, per file, ahead of the first matches; rg's arbitrary first 40\n * lines would say nothing about where to narrow next.\n */\nfunction matchSummary(matches: Match[], complete: boolean): string[] {\n\tconst counts = new Map<string, number>();\n\tfor (const m of matches) counts.set(m.path, (counts.get(m.path) ?? 0) + 1);\n\tconst total = complete ? `${matches.length} matches` : `More than ${RG_MAX_MATCHES} matches`;\n\tconst files = complete ? `${counts.size} files` : `at least ${counts.size} files`;\n\t// Densest files first: they are where a narrower search most likely belongs.\n\tconst rows = [...counts]\n\t\t.sort((a, b) => b[1] - a[1])\n\t\t.slice(0, RESULT_MAX_LINES / 2)\n\t\t.map(([path, n]) => `${VIRTUAL_ROOT}/${path}: ${n}`);\n\tif (counts.size > rows.length) rows.push(`... (${counts.size - rows.length} more files)`);\n\treturn [\n\t\t`${total} in ${files}; narrow with path, include, exclude, or a more specific pattern. Matches per file:`,\n\t\t...rows,\n\t\t\"First matches:\",\n\t];\n}\n\n/** `dir` and its entries to `maxDepth` levels; a trailing slash marks a directory. */\nfunction renderTree(index: Index, dir: string, maxDepth: number): string {\n\tconst lines = [virtualOf(dir)];\n\tconst walk = (current: string, depth: number, prefix: string): void => {\n\t\tif (depth >= maxDepth) return;\n\t\tconst entries = index.dirs.get(current) ?? [];\n\t\tentries.forEach((name, i) => {\n\t\t\tconst last = i === entries.length - 1;\n\t\t\tlines.push(`${prefix}${last ? \"└── \" : \"├── \"}${name}`);\n\t\t\tif (name.endsWith(\"/\")) {\n\t\t\t\tconst child = current ? `${current}/${name.slice(0, -1)}` : name.slice(0, -1);\n\t\t\t\twalk(child, depth + 1, `${prefix}${last ? \" \" : \"│ \"}`);\n\t\t\t}\n\t\t});\n\t};\n\twalk(dir, 0, \"\");\n\treturn lines.join(\"\\n\");\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/tool-search/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAGvE,wBAAgB,yBAAyB,IAAI,gBAAgB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/tool-search/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAGvE,wBAAgB,yBAAyB,IAAI,gBAAgB,CAI5D;;AAED,wBAA2C"}
|
|
@@ -5,14 +5,9 @@
|
|
|
5
5
|
* `tool_search` is registered inactive. Activate it with `--tools`, the `defaultTools` setting, or
|
|
6
6
|
* `setActiveTools()`.
|
|
7
7
|
*/
|
|
8
|
-
import { createToolSearchToolDefinition
|
|
8
|
+
import { createToolSearchToolDefinition } from "./tool.js";
|
|
9
9
|
export function createToolSearchExtension() {
|
|
10
10
|
return (pi) => {
|
|
11
|
-
pi.events.on("pi:discover-tool-orchestrators", (candidates) => {
|
|
12
|
-
if (candidates instanceof Map && candidates.get(TOOL_SEARCH_TOOL_NAME) === toolSearchSchema) {
|
|
13
|
-
candidates.set(TOOL_SEARCH_TOOL_NAME, true);
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
11
|
pi.registerTool({ ...createToolSearchToolDefinition({ tools: pi }), defaultActive: false });
|
|
17
12
|
};
|
|
18
13
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/tool-search/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,8BAA8B,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/tool-search/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,8BAA8B,EAAE,MAAM,WAAW,CAAC;AAE3D,MAAM,UAAU,yBAAyB;IACxC,OAAO,CAAC,EAAE,EAAE,EAAE;QACb,EAAE,CAAC,YAAY,CAAC,EAAE,GAAG,8BAA8B,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7F,CAAC,CAAC;AACH,CAAC;AAED,eAAe,yBAAyB,EAAE,CAAC","sourcesContent":["/**\n * The `tool_search` tool as an extension. The CLI loads it as a built-in extension; SDK users add\n * `createToolSearchExtension()` to their extension factories.\n *\n * `tool_search` is registered inactive. Activate it with `--tools`, the `defaultTools` setting, or\n * `setActiveTools()`.\n */\n\nimport type { ExtensionFactory } from \"../../core/extensions/types.ts\";\nimport { createToolSearchToolDefinition } from \"./tool.ts\";\n\nexport function createToolSearchExtension(): ExtensionFactory {\n\treturn (pi) => {\n\t\tpi.registerTool({ ...createToolSearchToolDefinition({ tools: pi }), defaultActive: false });\n\t};\n}\n\nexport default createToolSearchExtension();\n"]}
|
|
@@ -47,8 +47,6 @@ export declare const toolSearchSchema: Type.TObject<{
|
|
|
47
47
|
limit: Type.TOptional<Type.TNumber>;
|
|
48
48
|
}>;
|
|
49
49
|
export type ToolSearchInput = Static<typeof toolSearchSchema>;
|
|
50
|
-
/** Whether the tool is this `tool_search`, not another extension's tool of the same name. */
|
|
51
|
-
export declare function isToolSearchTool(tool: Pick<ToolInfo, "name" | "parameters">): boolean;
|
|
52
50
|
export interface ToolSearchResultTool {
|
|
53
51
|
name: string;
|
|
54
52
|
description: string;
|