feat(fs-search): spawn the packaged ripgrep binary through the subprocess seam

glob/grep now run the @vscode/ripgrep binary via ctx.subprocess with a plain
argv vector: no system rg install, no shell layer, unconditional registration.
The load-time command -v rg probe and the bash-seam coupling are removed;
timeouts ride the cooperative exec.signal plus the seam's terminate
escalation. The fs-glob-sampling ACP snapshot executes the real packaged
binary against an mtime-pinned fixture. Adds the packaged-ripgrep-search
Agent Note, updates the roster-note facts and both shipped-composition e2es,
and regenerates the doc catalogs and third-party notices (surfacing
pre-existing manifest drift plus the new @vscode/ripgrep row; the notices
generator also learns pnpm 11's truncated virtual-store names).
This commit is contained in:
Huanqi Cao
2026-08-01 18:04:38 +08:00
parent de520bee24
commit 18700f428d
32 changed files with 923 additions and 699 deletions

View File

@@ -1,6 +1,6 @@
import { fileURLToPath } from 'node:url'
import { readFileSync } from 'node:fs'
import { mkdir, writeFile } from 'node:fs/promises'
import { mkdir, utimes, writeFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { homedir } from 'node:os'
import { expect, it } from 'vitest'
@@ -44,7 +44,6 @@ const SESSION_TITLE_CONFIG = fileURLToPath(new URL('../session-title.cordis.yml'
const LSP_CONFIG = fileURLToPath(new URL('./lsp.cordis.yml', import.meta.url))
const WEB_CONFIG = fileURLToPath(new URL('../web.cordis.yml', import.meta.url))
const FS_SEARCH_CONFIG = fileURLToPath(new URL('./fs-search.cordis.yml', import.meta.url))
const FS_SEARCH_BIN = fileURLToPath(new URL('./fixtures/fs-search-bin', import.meta.url))
const SNAPSHOTS_DIR = join(dirname(fileURLToPath(import.meta.url)), 'snapshots')
const PACKED_CHUNKS_SOURCE = 'hook-cc-pretool-deny'
@@ -57,6 +56,33 @@ async function prepareDelimiterPathWorkspace(cwd: string): Promise<void> {
])
}
/**
* Seed the over-cap glob fixture: eight files under `tree/` with fixed mtimes,
* so the packaged ripgrep's `--sort=modified` order is deterministic — three
* files under `archive/`, one each under `docs/`, `src/`, and `test/`, plus
* two flat files (six top-level entries). Scoping the search to `tree/` keeps
* the harness's own session artifacts out of the listing.
*/
async function prepareFsSearchWorkspace(cwd: string): Promise<void> {
const tree = join(cwd, 'tree')
const files: Array<[relative: string, mtime: Date]> = [
[join('archive', 'a.ts'), new Date(2000, 0, 1, 0, 0, 0, 1)],
[join('archive', 'b.ts'), new Date(2000, 0, 1, 0, 0, 0, 2)],
[join('archive', 'c.ts'), new Date(2000, 0, 1, 0, 0, 0, 3)],
[join('docs', 'guide.md'), new Date(2000, 0, 1, 0, 0, 0, 4)],
[join('src', 'index.ts'), new Date(2000, 0, 1, 0, 0, 0, 5)],
[join('test', 'spec.ts'), new Date(2000, 0, 1, 0, 0, 0, 6)],
['top.txt', new Date(2000, 0, 1, 0, 0, 0, 7)],
['notes.md', new Date(2000, 0, 1, 0, 0, 0, 8)],
]
for (const [relative, mtime] of files) {
const target = join(tree, relative)
await mkdir(dirname(target), { recursive: true })
await writeFile(target, 'fixture\n')
await utimes(target, mtime, mtime)
}
}
// FIXME: Migrate backend-oriented scenarios to the headless stream-json suite;
// this ACP suite should eventually retain only automation-protocol contracts.
@@ -150,9 +176,12 @@ const SCENARIOS: Scenario[] = [
hasModelTurn: true,
recorded: true,
},
// The real Loader/app/bash path executes a deterministic rg stand-in at the
// external-process seam, pinning over-cap glob sampling without depending on
// a host-installed ripgrep binary.
// The real Loader/app/subprocess path executes the PACKAGED ripgrep binary
// against a prepared workspace whose fixed mtimes pin the
// `--sort=modified` order, pinning over-cap glob sampling without depending
// on a host-installed ripgrep binary or a PATH stand-in. POSIX-only because
// the displayed paths carry `/` separators the session-log comparison
// cannot normalize.
{
name: 'fs-glob-sampling',
hasModelTurn: true,
@@ -160,7 +189,7 @@ const SCENARIOS: Scenario[] = [
pinsHeader: true,
headerClass: 'fs-search',
configPath: FS_SEARCH_CONFIG,
env: { PATH: `${FS_SEARCH_BIN}:${process.env.PATH ?? ''}` },
prepareWorkspace: prepareFsSearchWorkspace,
posixOnly: true,
},
{ name: 'fs-read', hasModelTurn: true, recorded: true },

View File

@@ -1,10 +0,0 @@
#!/bin/sh
printf '%s\n' \
'archive/a.ts' \
'archive/b.ts' \
'archive/c.ts' \
'old\one' \
'old\two' \
'src/index.ts' \
'docs/guide.md' \
'test/spec.ts'

View File

@@ -2,6 +2,6 @@
"steps": [
{ "op": "initialize" },
{ "op": "newSession" },
{ "op": "prompt", "text": "Call glob exactly once with pattern * and no path. Then reply with exactly GLOB_SAMPLED and nothing else." }
{ "op": "prompt", "text": "Call glob exactly once with pattern * and path tree. Then reply with exactly GLOB_SAMPLED and nothing else." }
]
}

View File

@@ -1,18 +1,18 @@
{"type":"session","version":0,"id":"f5a99d52-3eaa-4ce7-858d-61d4fd77df2a","createdAt":1785218400000,"cwd":"{{cwd}}","delegationDepth":0}
{"type":"turn/start","seq":0,"time":1785218400001,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
{"type":"user/message","seq":1,"time":1785218400002,"data":{"content":[{"type":"text","text":"Call glob exactly once with pattern * and no path. Then reply with exactly GLOB_SAMPLED and nothing else."}],"source":{"kind":"user"},"role":"user","id":"6790985f-1de2-42f8-a7f1-24e46d6439c7"},"surfaceOp":"append"}
{"type":"user/message","seq":1,"time":1785218400002,"data":{"content":[{"type":"text","text":"Call glob exactly once with pattern * and path tree. Then reply with exactly GLOB_SAMPLED and nothing else."}],"source":{"kind":"user"},"role":"user","id":"6790985f-1de2-42f8-a7f1-24e46d6439c7"},"surfaceOp":"append"}
{"type":"session/title","seq":2,"time":1785218400003,"data":{"title":"Call glob exactly once with","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"step/start","seq":3,"time":1785218400004,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":4,"time":1785218400005,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-pro"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","seq":5,"time":1785483397569,"data":{"provider":"deepseek","model":"deepseek-v4-pro"}}
{"type":"assistant/chunk","seq":6,"time":1785218400007,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":7,"time":1785218400008,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"glob-sampling-call","name":"glob","argumentsDelta":"{\"pattern\":\"*\"}"}}}
{"type":"assistant/chunk","seq":8,"time":1785218400009,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"glob-sampling-call","name":"glob","arguments":"{\"pattern\":\"*\"}"}}}}
{"type":"assistant/chunk","seq":7,"time":1785218400008,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"glob-sampling-call","name":"glob","argumentsDelta":"{\"pattern\":\"*\",\"path\":\"tree\"}"}}}
{"type":"assistant/chunk","seq":8,"time":1785218400009,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"glob-sampling-call","name":"glob","arguments":"{\"pattern\":\"*\",\"path\":\"tree\"}"}}}}
{"type":"assistant/chunk","seq":9,"time":1785218400010,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":1,"outputTokens":1}}}}
{"type":"assistant/chunk","seq":10,"time":1785483397579,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":11,"time":1785483397579,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"glob-sampling-call","name":"glob","arguments":"{\"pattern\":\"*\"}"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-pro"},"id":"a127cfe5-39fb-462c-8e5a-a8c79bd0e52b"},"usage":{"inputTokens":1,"outputTokens":1}},"sourceEventSeqs":[6,7,8,9,10],"surfaceOp":"append"}
{"type":"tool/call","seq":12,"time":1785483397579,"data":{"turn":1,"step":1,"callId":"glob-sampling-call","name":"glob","arguments":"{\"pattern\":\"*\"}"}}
{"type":"tool/result","seq":13,"time":1785483398062,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"glob-sampling-call"},"content":[{"type":"tool-result","toolCallId":"glob-sampling-call","content":[{"type":"text","text":"archive/a.ts\nold\\one\nold\\two\nsrc/index.ts\n\n(Showing 4 of 8 paths, sampled across 4 of the 6 top-level entries this pattern matched instead of taken in modification-time order. Narrow path to inspect a specific subtree. The complete result could not be saved; narrow pattern or path to see more.)"}],"isError":false}],"role":"user","id":"2beecb2e-627d-43dc-a936-03e1dc874093"},"meta":{"shape":"paths","paths":["archive/a.ts","old\\one","old\\two","src/index.ts"],"truncated":true,"total":8}},"sourceEventSeqs":[12],"surfaceOp":"append"}
{"type":"assistant/message","seq":11,"time":1785483397579,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"glob-sampling-call","name":"glob","arguments":"{\"pattern\":\"*\",\"path\":\"tree\"}"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-pro"},"id":"a127cfe5-39fb-462c-8e5a-a8c79bd0e52b"},"usage":{"inputTokens":1,"outputTokens":1}},"sourceEventSeqs":[6,7,8,9,10],"surfaceOp":"append"}
{"type":"tool/call","seq":12,"time":1785483397579,"data":{"turn":1,"step":1,"callId":"glob-sampling-call","name":"glob","arguments":"{\"pattern\":\"*\",\"path\":\"tree\"}"}}
{"type":"tool/result","seq":13,"time":1785483398062,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"glob-sampling-call"},"content":[{"type":"tool-result","toolCallId":"glob-sampling-call","content":[{"type":"text","text":"tree/archive/a.ts\ntree/docs/guide.md\ntree/src/index.ts\ntree/test/spec.ts\n\n(Showing 4 of 8 paths, sampled across 4 of the 6 top-level entries this pattern matched instead of taken in modification-time order. Narrow path to inspect a specific subtree. The complete result could not be saved; narrow pattern or path to see more.)"}],"isError":false}],"role":"user","id":"2beecb2e-627d-43dc-a936-03e1dc874093"},"meta":{"shape":"paths","paths":["tree/archive/a.ts","tree/docs/guide.md","tree/src/index.ts","tree/test/spec.ts"],"truncated":true,"total":8}},"sourceEventSeqs":[12],"surfaceOp":"append"}
{"type":"step/end","seq":14,"time":1785483398062,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":15,"time":1785483398072,"data":{"turn":1,"step":2}}
{"type":"assistant/chunk","seq":16,"time":1785218400017,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}