@j0hanz/filesystem-mcp 2.7.6 → 2.8.1
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/dist/cli.d.ts +1 -9
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/core/cursor.d.ts +8 -0
- package/dist/core/cursor.d.ts.map +1 -1
- package/dist/core/cursor.js +14 -0
- package/dist/core/cursor.js.map +1 -1
- package/dist/core/errors.d.ts +4 -3
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +5 -16
- package/dist/core/errors.js.map +1 -1
- package/dist/core/fs.d.ts +11 -11
- package/dist/core/fs.d.ts.map +1 -1
- package/dist/core/fs.js +172 -83
- package/dist/core/fs.js.map +1 -1
- package/dist/core/glob.d.ts +8 -0
- package/dist/core/glob.d.ts.map +1 -1
- package/dist/core/glob.js +192 -29
- package/dist/core/glob.js.map +1 -1
- package/dist/core/input-required.d.ts +19 -0
- package/dist/core/input-required.d.ts.map +1 -1
- package/dist/core/input-required.js +20 -2
- package/dist/core/input-required.js.map +1 -1
- package/dist/core/observability.d.ts +2 -0
- package/dist/core/observability.d.ts.map +1 -1
- package/dist/core/observability.js +7 -0
- package/dist/core/observability.js.map +1 -1
- package/dist/core/path-completer.d.ts.map +1 -1
- package/dist/core/path-completer.js +11 -30
- package/dist/core/path-completer.js.map +1 -1
- package/dist/core/path-utils.d.ts +9 -6
- package/dist/core/path-utils.d.ts.map +1 -1
- package/dist/core/path-utils.js +13 -19
- package/dist/core/path-utils.js.map +1 -1
- package/dist/core/path.d.ts +1 -20
- package/dist/core/path.d.ts.map +1 -1
- package/dist/core/path.js +17 -69
- package/dist/core/path.js.map +1 -1
- package/dist/core/read.d.ts +7 -3
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +32 -50
- package/dist/core/read.js.map +1 -1
- package/dist/core/schema.d.ts +6 -1
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +17 -3
- package/dist/core/schema.js.map +1 -1
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +104 -51
- package/dist/core/search.js.map +1 -1
- package/dist/core/sensitive.d.ts.map +1 -1
- package/dist/core/sensitive.js +7 -14
- package/dist/core/sensitive.js.map +1 -1
- package/dist/core/store.d.ts +36 -1
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +71 -6
- package/dist/core/store.js.map +1 -1
- package/dist/core/util.d.ts +40 -5
- package/dist/core/util.d.ts.map +1 -1
- package/dist/core/util.js +75 -5
- package/dist/core/util.js.map +1 -1
- package/dist/index.js +5 -16
- package/dist/index.js.map +1 -1
- package/dist/prompts.d.ts +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +1 -1
- package/dist/prompts.js.map +1 -1
- package/dist/resources.d.ts +6 -3
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +46 -25
- package/dist/resources.js.map +1 -1
- package/dist/server.d.ts +9 -6
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +8 -9
- package/dist/server.js.map +1 -1
- package/dist/tools/create.d.ts.map +1 -1
- package/dist/tools/create.js +13 -25
- package/dist/tools/create.js.map +1 -1
- package/dist/tools/define.d.ts +8 -7
- package/dist/tools/define.d.ts.map +1 -1
- package/dist/tools/define.js +48 -16
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/delete.d.ts.map +1 -1
- package/dist/tools/delete.js +46 -27
- package/dist/tools/delete.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +24 -39
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/find-files.d.ts.map +1 -1
- package/dist/tools/find-files.js +45 -62
- package/dist/tools/find-files.js.map +1 -1
- package/dist/tools/index.d.ts +3 -12
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -10
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/list.js +81 -74
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/move.d.ts.map +1 -1
- package/dist/tools/move.js +17 -14
- package/dist/tools/move.js.map +1 -1
- package/dist/tools/patch.d.ts.map +1 -1
- package/dist/tools/patch.js +3 -6
- package/dist/tools/patch.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +31 -40
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/replace-text.d.ts.map +1 -1
- package/dist/tools/replace-text.js +24 -33
- package/dist/tools/replace-text.js.map +1 -1
- package/dist/tools/search-text.d.ts.map +1 -1
- package/dist/tools/search-text.js +42 -69
- package/dist/tools/search-text.js.map +1 -1
- package/dist/transport/http-policy.d.ts +3 -2
- package/dist/transport/http-policy.d.ts.map +1 -1
- package/dist/transport/http-policy.js +3 -2
- package/dist/transport/http-policy.js.map +1 -1
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +2 -3
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/shared.d.ts +7 -5
- package/dist/transport/shared.d.ts.map +1 -1
- package/dist/transport/shared.js +8 -2
- package/dist/transport/shared.js.map +1 -1
- package/dist/transport/stdio.d.ts +4 -1
- package/dist/transport/stdio.d.ts.map +1 -1
- package/dist/transport/stdio.js +45 -12
- package/dist/transport/stdio.js.map +1 -1
- package/package.json +3 -2
package/dist/core/search.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"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","sourcesContent":["import { stat as fsStat, readFile } from 'node:fs/promises';\nimport { basename } from 'node:path';\n\nimport type { RE2ExecArray } from '@adguard/re2-wasm';\nimport { RE2 } from '@adguard/re2-wasm';\n\nimport { processEntriesConcurrently, resolveStopReason } from './concurrency.ts';\nimport { globEntries, type GlobEntry } from './glob.ts';\nimport type { PathGuard } from './path.ts';\nimport { escapeRegExp, getMaxTextFileSize, MIB, PARALLEL_CONCURRENCY } from './util.ts';\n\n/**\n * A fresh, flat copy of `s`. V8 keeps a substring of a long string as a slice\n * that references its parent, so a kept line would otherwise pin the whole\n * file's text for as long as the match list lives (60 s in the page store).\n */\nconst own = (s: string): string => Buffer.from(s, 'utf8').toString('utf8');\n\ninterface SearchResult {\n file: string;\n line: number;\n /** 0-indexed column of the first occurrence on the line. */\n column: number;\n content: string;\n matchCount: number;\n /** Up to `context` lines either side of the match; absent when context is 0. */\n before?: string[];\n after?: string[];\n}\n\nexport type Regex = RE2;\n\n/** Occurrence-counting bound, so one pathological line cannot spin forever. */\nconst MAX_MATCHES_PER_LINE = 100_000;\n\n/**\n * re2-wasm copies the whole input on every exec, so draining a dense pattern is\n * O(matches x input) and fully synchronous: the loop pins and the tool's\n * AbortSignal.timeout (a timer) can never fire. The returned `tick` yields a\n * macrotask every ~10ms and reports whether `signal` has aborted.\n */\nexport function createYielder(signal: AbortSignal | undefined): () => Promise<boolean> {\n let last = Date.now();\n return async () => {\n if (Date.now() - last > 10) {\n await new Promise((resolve) => {\n setImmediate(resolve);\n });\n last = Date.now();\n }\n return signal?.aborted ?? false;\n };\n}\n\n/**\n * Largest input one RE2 call may scan as a single string. The input is copied\n * into the same fixed 16 MB wasm heap as the patterns; about 10 MB of input\n * aborts the module. 4 MiB leaves room for the copy and the pattern set.\n */\nexport const RE2_MAX_INPUT_BYTES = 4 * MIB;\n\nconst BUFFER_ANCHOR_RE = /\\\\[AzZ]/u;\n/** `(?i)`, `(?-m)`, `(?s:…)`: inline flags can undo the `m` the prefilter relies on. */\nconst INLINE_FLAGS_RE = /\\(\\?[a-zA-Z-]+[:)]/u;\nconst CR = 13;\n\n/**\n * Compile a pattern on RE2 rather than on V8's irregexp.\n *\n * Patterns arrive from the MCP client, so a backtracking engine would let one\n * request pin the event loop with no way out: an abort signal cannot preempt a\n * synchronous `exec`, and on stdio that wedges the whole server. RE2 matches in\n * time linear in the input and cannot backtrack at all, so the hazard is gone\n * rather than bounded.\n *\n * RE2 rejects the constructs the tool schema documents as unsupported —\n * lookahead, lookbehind, backreferences — with its own {@link SyntaxError},\n * which the tool layer turns into a normal tool error. It always matches in\n * Unicode mode and requires the `u` flag to say so.\n *\n * Every compiled pattern owns memory in re2-wasm's fixed 16 MB heap, which\n * `ALLOW_MEMORY_GROWTH` is off for. re2-wasm never frees it and a\n * FinalizationRegistry does not keep up (V8 sees no pressure from the wasm\n * heap), so exhaustion is an emscripten `abort()` that kills regex search for\n * the rest of the process. Every caller MUST pass the result to\n * {@link freeRegex} when it is done with it.\n */\nexport function compileRegex(\n pattern: string,\n options: { caseSensitive?: boolean; multiline?: boolean } = {},\n): Regex {\n const flags = `g${options.multiline ? 'm' : ''}${options.caseSensitive ? '' : 'i'}u`;\n try {\n return new RE2(pattern, flags);\n } catch (error) {\n if (!(error instanceof SyntaxError)) throw error;\n throw new SyntaxError(\n `${error.message} — lookahead, lookbehind, and backreferences are not supported.`,\n { cause: error },\n );\n }\n}\n\n/**\n * Release a compiled pattern's wasm memory. re2-wasm exposes no disposal of its\n * own, so this reaches the embind handle it holds privately; a version that\n * renames the field degrades to the pre-existing leak rather than throwing.\n * Idempotent — a second call on an already-freed handle is swallowed. Using a\n * {@link Regex} after freeing it is undefined behaviour in the wasm heap, so\n * free only in a `finally` that owns the compile.\n */\nexport function freeRegex(regex: Regex | undefined): void {\n const handle = (regex as unknown as { wrapper?: { delete?: () => void } } | undefined)?.wrapper;\n try {\n handle?.delete?.();\n } catch {\n // already deleted, or a re2-wasm build without embind disposal\n }\n}\n\nexport interface RegexMatch {\n /** The matched text. */\n text: string;\n /** Capture groups, 1-indexed in the pattern, 0-indexed here. */\n groups: (string | undefined)[];\n /** Named capture groups, when the pattern declares any. */\n named: Record<string, string> | undefined;\n /** Start offset in UTF-16 code units — usable with `String.prototype.slice`. */\n start: number;\n /** End offset in UTF-16 code units. */\n end: number;\n}\n\n/** Code-point length of `text`, i.e. its UTF-16 length minus its surrogate pairs. */\nconst codePointLength = (text: string): number =>\n // eslint-disable-next-line @typescript-eslint/no-misused-spread -- code points are the intended unit (re2-wasm offsets count code points); grapheme segmentation would change the count, not preserve it.\n [...text].length;\n\n/**\n * Iterate a global pattern's non-overlapping matches with offsets JS can use.\n *\n * re2-wasm reports `index` and `lastIndex` in **code points**, while every JS\n * string operation indexes in UTF-16 code units. The two agree only until the\n * first astral character (most emoji), past which every offset is short by one\n * per surrogate pair — slicing on the raw index cuts into the middle of\n * neighbouring text. Its `lastIndex` bookkeeping is worse still: it adds the\n * match's UTF-16 length to a code-point index, so an astral character inside a\n * match can skip the next one. This is the single place that corrects both; no\n * caller should read `match.index` or `regex.lastIndex` directly.\n *\n * Zero-length matches (e.g. `a*`) advance by one code point so they cannot loop\n * forever. The regex is global and shared across calls, so `lastIndex` is reset\n * on entry.\n */\nexport function* execMatches(regex: Regex, text: string): Generator<RegexMatch, undefined> {\n regex.lastIndex = 0;\n // Cursor into `text`, carried forward across matches: RE2 reports them in\n // ascending order, so the walk is O(text) in total rather than per match.\n let cursorCp = 0;\n let cursorU16 = 0;\n let match: RE2ExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n while (cursorCp < match.index && cursorU16 < text.length) {\n const isPair =\n (text.charCodeAt(cursorU16) & 0xfc00) === 0xd800 &&\n (text.charCodeAt(cursorU16 + 1) & 0xfc00) === 0xdc00;\n cursorU16 += isPair ? 2 : 1;\n cursorCp++;\n }\n const matched = match[0] ?? '';\n const start = cursorU16;\n yield {\n text: matched,\n groups: match.slice(1),\n named: match.groups,\n start,\n end: start + matched.length,\n };\n // Own the advance rather than trusting the wrapper's mixed-unit arithmetic.\n regex.lastIndex = match.index + (matched.length === 0 ? 1 : codePointLength(matched));\n }\n}\n\n/**\n * Find non-overlapping occurrences of a global regex in a single line, reporting\n * the first match's column alongside the count.\n */\nasync function findLineMatches(\n regex: Regex,\n line: string,\n tick: () => Promise<boolean>,\n): Promise<{ count: number; column: number } | undefined> {\n let count = 0;\n let column = -1;\n // Awaiting mid-drain is safe with a shared regex: execMatches re-writes\n // lastIndex from its own match before every exec, with no await inside.\n for (const match of execMatches(regex, line)) {\n if (column === -1) column = match.start;\n count++;\n if (count >= MAX_MATCHES_PER_LINE || (await tick())) break;\n }\n return count > 0 ? { count, column } : undefined;\n}\n\nexport interface SearchContentOptions {\n caseSensitive?: boolean;\n isRegex?: boolean;\n maxResults?: number;\n filePattern?: string;\n skipIgnored?: boolean;\n includeHidden?: boolean;\n maxDepth?: number;\n /**\n * Search exactly this file (absolute, already guard-validated) instead of\n * walking `directory` with `filePattern`. Set when the caller named a file:\n * its name must not be interpreted as a glob.\n */\n explicitFile?: string;\n /** Lines of context to carry either side of each match; 0 (default) carries none. */\n context?: number;\n signal?: AbortSignal;\n}\n\nexport interface SearchContentOutcome {\n basePath: string;\n matches: SearchResult[];\n summary: {\n /**\n * Matching *lines*, one per entry in `matches` — not pattern occurrences.\n * A line with three occurrences counts once here and reports 3 in its own\n * `SearchResult.matchCount`.\n */\n matchingLines: number;\n filesScanned: number;\n filesMatched: number;\n truncated: boolean;\n /** Files the guard rejected or that could not be stat'd. */\n skippedInaccessible: number;\n /** Files skipped unread because they exceed maxFileSize. */\n skippedTooLarge: number;\n /** Files skipped because they contain a NUL byte (binary). */\n skippedBinary: number;\n /**\n * `StoppedReason` narrowed to the stops these scans can produce: both\n * resolve the stop through `resolveStopReason` (concurrency.ts) — never\n * `maxFiles`, which belongs to `replace_text`'s per-file cap.\n */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}\n\ntype FileScan =\n | { kind: 'inaccessible' }\n | { kind: 'tooLarge' }\n | { kind: 'binary' }\n | { kind: 'scanned'; matches: SearchResult[] };\n\ninterface ScanContext {\n pathGuard: PathGuard;\n signal: AbortSignal | undefined;\n maxFileSize: number;\n maxResults: number;\n regex: Regex;\n prefilter: Regex;\n source: string;\n context: number;\n}\n\nasync function scanFile(entryPath: string, scanCtx: ScanContext): Promise<FileScan> {\n try {\n await scanCtx.pathGuard.validateExistingPath(entryPath);\n } catch {\n return { kind: 'inaccessible' };\n }\n\n try {\n const stats = await fsStat(entryPath);\n if (stats.size > scanCtx.maxFileSize) return { kind: 'tooLarge' };\n } catch {\n return { kind: 'inaccessible' };\n }\n\n let buffer: Buffer;\n try {\n buffer = await readFile(entryPath, { signal: scanCtx.signal });\n } catch (error) {\n if (scanCtx.signal?.aborted) throw error;\n return { kind: 'inaccessible' };\n }\n\n // A NUL byte marks a binary file, as it does for `read` and `grep -I`.\n // Non-UTF-8 text is still searched: a lossy decode only affects the\n // bytes a pattern could not have matched anyway.\n if (buffer.includes(0)) return { kind: 'binary' };\n\n const content = buffer.toString('utf-8');\n if (canPrefilter(scanCtx.source, buffer)) {\n scanCtx.prefilter.lastIndex = 0;\n if (scanCtx.prefilter.exec(content) === null) return { kind: 'scanned', matches: [] };\n }\n\n const lines = content.split(/\\r?\\n/u);\n // A trailing newline splits into a phantom empty last element, and an\n // empty file splits into one empty element: neither is a line the\n // file has, for matching or for context.\n const lineCount =\n content.length === 0 ? 0 : content.endsWith('\\n') ? lines.length - 1 : lines.length;\n // A single line past RE2_MAX_INPUT_BYTES OOMs the wasm module for the rest of\n // the process; report the file tooLarge instead. Only a buffer over the bound\n // can hold such a line, so ordinary files skip the per-line measure.\n const checkLineSize = buffer.length > RE2_MAX_INPUT_BYTES;\n const matches: SearchResult[] = [];\n const tick = createYielder(scanCtx.signal);\n for (let i = 0; i < lineCount; i++) {\n const line = lines[i];\n if (line === undefined) continue;\n if (checkLineSize && Buffer.byteLength(line, 'utf8') > RE2_MAX_INPUT_BYTES) {\n return { kind: 'tooLarge' };\n }\n // Yield between lines too: millions of short lines never yield per-match.\n if (await tick()) break;\n // One scan per line: the helper resets lastIndex itself, so it\n // doubles as the \"does this line match\" test.\n const found = await findLineMatches(scanCtx.regex, line, tick);\n if (found) {\n matches.push({\n file: entryPath,\n line: i + 1,\n column: found.column,\n content: own(line),\n matchCount: found.count,\n ...(scanCtx.context > 0\n ? {\n before: lines.slice(Math.max(0, i - scanCtx.context), i).map(own),\n after: lines.slice(i + 1, Math.min(lineCount, i + 1 + scanCtx.context)).map(own),\n }\n : {}),\n });\n if (matches.length >= scanCtx.maxResults) break;\n }\n }\n return { kind: 'scanned', matches };\n}\n\nexport async function searchContent(\n directory: string,\n pattern: string,\n options: SearchContentOptions,\n pathGuard: PathGuard,\n): Promise<SearchContentOutcome> {\n const source = options.isRegex ? pattern || '' : escapeRegExp(pattern || '');\n // Compiles live inside the try so a failing second compile does not leak the\n // first's wasm memory; freeRegex is null-safe.\n let regex: Regex | undefined;\n let prefilter: Regex | undefined;\n try {\n regex = compileRegex(source, {\n caseSensitive: Boolean(options.caseSensitive),\n });\n // Whole-buffer test: `m` makes ^/$ line-relative so a per-line hit is always a\n // buffer hit (the converse can be false — a false positive just falls through\n // to the per-line scan). \\A/\\z/\\Z are buffer-relative even under `m`, inline\n // flag groups can undo `m`, and $ cannot see past a \\r, so those inputs skip\n // the prefilter (see canPrefilter).\n prefilter = compileRegex(source, {\n caseSensitive: Boolean(options.caseSensitive),\n multiline: true,\n });\n const matches: SearchResult[] = [];\n const maxResults = options.maxResults ?? 100;\n const maxFileSize = getMaxTextFileSize();\n const context = options.context ?? 0;\n const ceiling = pathGuard.allowedRootContaining(directory);\n\n const entries = options.explicitFile\n ? [{ path: options.explicitFile }]\n : globEntries({\n cwd: directory,\n pattern: options.filePattern ?? '**/*',\n // Same rule as replace_text, so a search with the same glob previews the\n // files a replace would touch: a slash-free glob matches at any depth.\n baseNameMatch: true,\n includeHidden: Boolean(options.includeHidden),\n skipIgnored: Boolean(options.skipIgnored),\n ...(ceiling !== undefined ? { ignoreCeiling: ceiling } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n ...(options.maxDepth !== undefined ? { maxDepth: options.maxDepth } : {}),\n suppressErrors: true,\n });\n\n let filesScanned = 0;\n let filesMatched = 0;\n let matchingLines = 0;\n let skippedTooLarge = 0;\n let skippedBinary = 0;\n const scanCtx: ScanContext = {\n pathGuard,\n signal: options.signal,\n maxFileSize,\n maxResults,\n regex,\n prefilter,\n source,\n context,\n };\n const perFile = new Map<number, FileScan>();\n let dispatched = 0;\n let collected = 0;\n const stopped = await processEntriesConcurrently(entries, {\n signal: options.signal,\n concurrency: PARALLEL_CONCURRENCY,\n shouldStop: () => collected >= maxResults,\n onEntry: () => {\n dispatched++;\n },\n onError: () => undefined,\n runEntry: async (entryPath) => {\n const index = dispatched - 1;\n const scan = await scanFile(entryPath, scanCtx);\n perFile.set(index, scan);\n if (scan.kind === 'scanned') collected += scan.matches.length;\n },\n });\n\n let skippedInaccessible = 0;\n for (let i = 0; i < dispatched; i++) {\n const scan = perFile.get(i);\n if (!scan) continue;\n switch (scan.kind) {\n case 'inaccessible':\n skippedInaccessible++;\n break;\n case 'tooLarge':\n skippedTooLarge++;\n break;\n case 'binary':\n filesScanned++;\n skippedBinary++;\n break;\n case 'scanned':\n filesScanned++;\n if (scan.matches.length > 0) filesMatched++;\n for (const match of scan.matches) {\n if (matches.length >= maxResults) break;\n matches.push(match);\n matchingLines++;\n }\n break;\n }\n }\n\n const stoppedReason = resolveStopReason(\n matches.length >= maxResults,\n stopped === 'timeout' || Boolean(options.signal?.aborted),\n );\n\n return {\n basePath: directory,\n matches,\n summary: {\n matchingLines,\n filesScanned,\n filesMatched,\n truncated: stoppedReason !== undefined,\n skippedInaccessible,\n skippedTooLarge,\n skippedBinary,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n } finally {\n freeRegex(prefilter);\n freeRegex(regex);\n }\n}\n\n/** Whether one whole-buffer RE2 test is a sound \"cannot match\" check for this file. */\nfunction canPrefilter(source: string, buffer: Buffer): boolean {\n if (buffer.length > RE2_MAX_INPUT_BYTES) return false;\n if (BUFFER_ANCHOR_RE.test(source)) return false;\n if (INLINE_FLAGS_RE.test(source)) return false;\n if (source.includes('$') && buffer.includes(CR)) return false;\n return true;\n}\n\nasync function* guardedEntries(\n entries: AsyncIterable<GlobEntry>,\n pathGuard: PathGuard,\n signal: AbortSignal | undefined,\n counters: { skippedInaccessible: number; stoppedByAbort: boolean },\n): AsyncGenerator<GlobEntry> {\n // The signal carries both client cancellation and the tool's search timeout,\n // so an abort means \"return what we have, marked incomplete\" rather than\n // throw — but it must never be reported as a finished scan. The walk itself\n // throws when the signal stops it before a single entry is yielded.\n try {\n for await (const entry of entries) {\n if (signal?.aborted) {\n counters.stoppedByAbort = true;\n return;\n }\n try {\n await pathGuard.validateExistingPath(entry.path);\n } catch {\n counters.skippedInaccessible++;\n continue;\n }\n yield entry;\n }\n } catch (error) {\n if (!signal?.aborted) throw error;\n counters.stoppedByAbort = true;\n }\n}\n\nexport async function searchFiles(\n directory: string,\n pattern: string,\n options: {\n maxResults?: number;\n includeHidden?: boolean;\n sortBy?: 'name' | 'path';\n skipIgnored?: boolean;\n maxDepth?: number;\n signal?: AbortSignal;\n },\n pathGuard: PathGuard,\n): Promise<{\n basePath: string;\n results: { path: string }[];\n summary: {\n matched: number;\n filesScanned: number;\n truncated: boolean;\n skippedInaccessible: number;\n /** Narrowed like SearchContentOutcome's — scans never `maxFiles`. */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}> {\n const maxResults = options.maxResults ?? 100;\n const ceiling = pathGuard.allowedRootContaining(directory);\n const entries = globEntries({\n cwd: directory,\n pattern,\n baseNameMatch: true,\n includeHidden: Boolean(options.includeHidden),\n skipIgnored: Boolean(options.skipIgnored),\n ...(ceiling !== undefined ? { ignoreCeiling: ceiling } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n ...(options.maxDepth !== undefined ? { maxDepth: options.maxDepth } : {}),\n suppressErrors: true,\n });\n const results: { path: string }[] = [];\n let filesScanned = 0;\n const counters = { skippedInaccessible: 0, stoppedByAbort: false };\n\n for await (const entry of guardedEntries(entries, pathGuard, options.signal, counters)) {\n if (results.length >= maxResults) break;\n filesScanned++;\n results.push({ path: entry.path });\n }\n\n // Sorting — only name / path are supported; size / modified were removed\n // (the glob never collected stats, so they were always undefined).\n if (options.sortBy === 'name') {\n results.sort((a, b) => basename(a.path).localeCompare(basename(b.path)));\n } else {\n results.sort((a, b) => a.path.localeCompare(b.path));\n }\n\n // Same precedence as above: the result cap wins over a same-iteration abort.\n const stoppedReason = resolveStopReason(results.length >= maxResults, counters.stoppedByAbort);\n\n return {\n basePath: directory,\n results,\n summary: {\n matched: results.length,\n filesScanned,\n truncated: stoppedReason !== undefined,\n skippedInaccessible: counters.skippedInaccessible,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"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","sourcesContent":["import { basename } from 'node:path';\n\nimport type { RE2ExecArray } from '@adguard/re2-wasm';\nimport { RE2 } from '@adguard/re2-wasm';\n\nimport { processEntriesConcurrently, resolveStopReason } from './concurrency.ts';\nimport { GuardedFileSystem } from './fs.ts';\nimport { globEntries } from './glob.ts';\nimport type { PathGuard } from './path.ts';\nimport { escapeRegExp, getMaxTextFileSize, MIB, PARALLEL_CONCURRENCY } from './util.ts';\n\n/**\n * A fresh, flat copy of `s`. V8 keeps a substring of a long string as a slice\n * that references its parent, so a kept line would otherwise pin the whole\n * file's text for as long as the match list lives (60 s in the page store).\n */\nconst own = (s: string): string => Buffer.from(s, 'utf8').toString('utf8');\n\ninterface SearchResult {\n file: string;\n line: number;\n /** 0-indexed column of the first occurrence on the line. */\n column: number;\n content: string;\n matchCount: number;\n /** Up to `context` lines either side of the match; absent when context is 0. */\n before?: string[];\n after?: string[];\n}\n\nexport type Regex = RE2;\n\n/** Occurrence-counting bound, so one pathological line cannot spin forever. */\nconst MAX_MATCHES_PER_LINE = 100_000;\n\n/**\n * re2-wasm copies the whole input on every exec, so draining a dense pattern is\n * O(matches x input) and fully synchronous: the loop pins and the tool's\n * AbortSignal.timeout (a timer) can never fire. The returned `tick` yields a\n * macrotask every ~10ms and reports whether `signal` has aborted.\n */\nexport function createYielder(signal: AbortSignal | undefined): () => Promise<boolean> {\n let last = Date.now();\n return async () => {\n if (Date.now() - last > 10) {\n await new Promise((resolve) => {\n setImmediate(resolve);\n });\n last = Date.now();\n }\n return signal?.aborted ?? false;\n };\n}\n\n/**\n * Largest input one RE2 call may scan as a single string. The input is copied\n * into the same fixed 16 MB wasm heap as the patterns; about 10 MB of input\n * aborts the module. 4 MiB leaves room for the copy and the pattern set.\n */\nexport const RE2_MAX_INPUT_BYTES = 4 * MIB;\n\nconst BUFFER_ANCHOR_RE = /\\\\[AzZ]/u;\n/** `(?i)`, `(?-m)`, `(?s:…)`: inline flags can undo the `m` the prefilter relies on. */\nconst INLINE_FLAGS_RE = /\\(\\?[a-zA-Z-]+[:)]/u;\nconst CR = 13;\n\n/**\n * Compile a pattern on RE2 rather than on V8's irregexp.\n *\n * Patterns arrive from the MCP client, so a backtracking engine would let one\n * request pin the event loop with no way out: an abort signal cannot preempt a\n * synchronous `exec`, and on stdio that wedges the whole server. RE2 matches in\n * time linear in the input and cannot backtrack at all, so the hazard is gone\n * rather than bounded.\n *\n * RE2 rejects the constructs the tool schema documents as unsupported —\n * lookahead, lookbehind, backreferences — with its own {@link SyntaxError},\n * which the tool layer turns into a normal tool error. It always matches in\n * Unicode mode and requires the `u` flag to say so.\n *\n * Every compiled pattern owns memory in re2-wasm's fixed 16 MB heap, which\n * `ALLOW_MEMORY_GROWTH` is off for. re2-wasm never frees it and a\n * FinalizationRegistry does not keep up (V8 sees no pressure from the wasm\n * heap), so exhaustion is an emscripten `abort()` that kills regex search for\n * the rest of the process. Every caller MUST pass the result to\n * {@link freeRegex} when it is done with it.\n */\nexport function compileRegex(\n pattern: string,\n options: { caseSensitive?: boolean; multiline?: boolean } = {},\n): Regex {\n const flags = `g${options.multiline ? 'm' : ''}${options.caseSensitive ? '' : 'i'}u`;\n try {\n return new RE2(pattern, flags);\n } catch (error) {\n if (!(error instanceof SyntaxError)) throw error;\n throw new SyntaxError(\n `${error.message} — lookahead, lookbehind, and backreferences are not supported.`,\n { cause: error },\n );\n }\n}\n\n/**\n * Release a compiled pattern's wasm memory. re2-wasm exposes no disposal of its\n * own, so this reaches the embind handle it holds privately; a version that\n * renames the field degrades to the pre-existing leak rather than throwing.\n * Idempotent — a second call on an already-freed handle is swallowed. Using a\n * {@link Regex} after freeing it is undefined behaviour in the wasm heap, so\n * free only in a `finally` that owns the compile.\n */\nexport function freeRegex(regex: Regex | undefined): void {\n const handle = (regex as unknown as { wrapper?: { delete?: () => void } } | undefined)?.wrapper;\n try {\n handle?.delete?.();\n } catch {\n // already deleted, or a re2-wasm build without embind disposal\n }\n}\n\nexport interface RegexMatch {\n /** The matched text. */\n text: string;\n /** Capture groups, 1-indexed in the pattern, 0-indexed here. */\n groups: (string | undefined)[];\n /** Named capture groups, when the pattern declares any. */\n named: Record<string, string> | undefined;\n /** Start offset in UTF-16 code units — usable with `String.prototype.slice`. */\n start: number;\n /** End offset in UTF-16 code units. */\n end: number;\n}\n\n/** Code-point length of `text`, i.e. its UTF-16 length minus its surrogate pairs. */\nconst codePointLength = (text: string): number =>\n // eslint-disable-next-line @typescript-eslint/no-misused-spread -- code points are the intended unit (re2-wasm offsets count code points); grapheme segmentation would change the count, not preserve it.\n [...text].length;\n\n/**\n * Iterate a global pattern's non-overlapping matches with offsets JS can use.\n *\n * re2-wasm reports `index` and `lastIndex` in **code points**, while every JS\n * string operation indexes in UTF-16 code units. The two agree only until the\n * first astral character (most emoji), past which every offset is short by one\n * per surrogate pair — slicing on the raw index cuts into the middle of\n * neighbouring text. Its `lastIndex` bookkeeping is worse still: it adds the\n * match's UTF-16 length to a code-point index, so an astral character inside a\n * match can skip the next one. This is the single place that corrects both; no\n * caller should read `match.index` or `regex.lastIndex` directly.\n *\n * Zero-length matches (e.g. `a*`) advance by one code point so they cannot loop\n * forever. The regex is global and shared across calls, so `lastIndex` is reset\n * on entry.\n */\nexport function* execMatches(regex: Regex, text: string): Generator<RegexMatch, undefined> {\n regex.lastIndex = 0;\n // Cursor into `text`, carried forward across matches: RE2 reports them in\n // ascending order, so the walk is O(text) in total rather than per match.\n let cursorCp = 0;\n let cursorU16 = 0;\n let match: RE2ExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n while (cursorCp < match.index && cursorU16 < text.length) {\n const isPair =\n (text.charCodeAt(cursorU16) & 0xfc00) === 0xd800 &&\n (text.charCodeAt(cursorU16 + 1) & 0xfc00) === 0xdc00;\n cursorU16 += isPair ? 2 : 1;\n cursorCp++;\n }\n const matched = match[0] ?? '';\n const start = cursorU16;\n yield {\n text: matched,\n groups: match.slice(1),\n named: match.groups,\n start,\n end: start + matched.length,\n };\n // Own the advance rather than trusting the wrapper's mixed-unit arithmetic.\n regex.lastIndex = match.index + (matched.length === 0 ? 1 : codePointLength(matched));\n }\n}\n\n/**\n * Find non-overlapping occurrences of a global regex in a single line, reporting\n * the first match's column alongside the count.\n */\nasync function findLineMatches(\n regex: Regex,\n line: string,\n tick: () => Promise<boolean>,\n): Promise<{ count: number; column: number } | undefined> {\n let count = 0;\n let column = -1;\n // Awaiting mid-drain is safe with a shared regex: execMatches re-writes\n // lastIndex from its own match before every exec, with no await inside.\n for (const match of execMatches(regex, line)) {\n if (column === -1) column = match.start;\n count++;\n if (count >= MAX_MATCHES_PER_LINE || (await tick())) break;\n }\n return count > 0 ? { count, column } : undefined;\n}\n\nexport interface SearchContentOptions {\n caseSensitive?: boolean;\n isRegex?: boolean;\n maxResults?: number;\n filePattern?: string;\n skipIgnored?: boolean;\n includeHidden?: boolean;\n maxDepth?: number;\n /**\n * Search exactly this file (absolute, already guard-validated) instead of\n * walking `directory` with `filePattern`. Set when the caller named a file:\n * its name must not be interpreted as a glob.\n */\n explicitFile?: string;\n /** Lines of context to carry either side of each match; 0 (default) carries none. */\n context?: number;\n signal?: AbortSignal;\n}\n\nexport interface SearchContentOutcome {\n basePath: string;\n matches: SearchResult[];\n summary: {\n /**\n * Matching *lines*, one per entry in `matches` — not pattern occurrences.\n * A line with three occurrences counts once here and reports 3 in its own\n * `SearchResult.matchCount`.\n */\n matchingLines: number;\n filesScanned: number;\n filesMatched: number;\n truncated: boolean;\n /** Files the guard rejected or that could not be stat'd. */\n skippedInaccessible: number;\n /** Files skipped unread because they exceed maxFileSize. */\n skippedTooLarge: number;\n /** Files skipped because they contain a NUL byte (binary). */\n skippedBinary: number;\n /**\n * `StoppedReason` narrowed to the stops these scans can produce: both\n * resolve the stop through `resolveStopReason` (concurrency.ts) — never\n * `maxFiles`, which belongs to `replace_text`'s per-file cap.\n */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}\n\ntype FileScan =\n | { kind: 'inaccessible' }\n | { kind: 'tooLarge' }\n | { kind: 'binary' }\n | { kind: 'scanned'; matches: SearchResult[] };\n\ninterface ScanContext {\n fs: GuardedFileSystem;\n signal: AbortSignal | undefined;\n maxFileSize: number;\n maxResults: number;\n regex: Regex;\n prefilter: Regex;\n source: string;\n context: number;\n}\n\nasync function scanFile(entryPath: string, scanCtx: ScanContext): Promise<FileScan> {\n let buffer: Buffer;\n try {\n // open() validates, refuses a swapped final symlink (O_NOFOLLOW) and\n // re-checks the descriptor's real path (H4), so these bytes are the\n // validated file's, not whatever the name points at by now.\n await using handle = await scanCtx.fs.open(entryPath);\n const stats = await handle.stat();\n if (stats.size > scanCtx.maxFileSize) return { kind: 'tooLarge' };\n buffer = await handle.readFile({ signal: scanCtx.signal });\n } catch (error) {\n if (scanCtx.signal?.aborted) throw error;\n return { kind: 'inaccessible' };\n }\n\n // A NUL byte marks a binary file, as it does for `read` and `grep -I`.\n // Non-UTF-8 text is still searched: a lossy decode only affects the\n // bytes a pattern could not have matched anyway.\n if (buffer.includes(0)) return { kind: 'binary' };\n\n const content = buffer.toString('utf-8');\n if (canPrefilter(scanCtx.source, buffer)) {\n scanCtx.prefilter.lastIndex = 0;\n if (scanCtx.prefilter.exec(content) === null) return { kind: 'scanned', matches: [] };\n }\n\n const lines = content.split(/\\r?\\n/u);\n // A trailing newline splits into a phantom empty last element, and an\n // empty file splits into one empty element: neither is a line the\n // file has, for matching or for context.\n const lineCount =\n content.length === 0 ? 0 : content.endsWith('\\n') ? lines.length - 1 : lines.length;\n // A single line past RE2_MAX_INPUT_BYTES OOMs the wasm module for the rest of\n // the process; report the file tooLarge instead. Only a buffer over the bound\n // can hold such a line, so ordinary files skip the per-line measure.\n const checkLineSize = buffer.length > RE2_MAX_INPUT_BYTES;\n const matches: SearchResult[] = [];\n const tick = createYielder(scanCtx.signal);\n for (let i = 0; i < lineCount; i++) {\n const line = lines[i];\n if (line === undefined) continue;\n if (checkLineSize && Buffer.byteLength(line, 'utf8') > RE2_MAX_INPUT_BYTES) {\n return { kind: 'tooLarge' };\n }\n // Yield between lines too: millions of short lines never yield per-match.\n if (await tick()) break;\n // One scan per line: the helper resets lastIndex itself, so it\n // doubles as the \"does this line match\" test.\n const found = await findLineMatches(scanCtx.regex, line, tick);\n if (found) {\n matches.push({\n file: entryPath,\n line: i + 1,\n column: found.column,\n content: own(line),\n matchCount: found.count,\n ...(scanCtx.context > 0\n ? {\n before: lines.slice(Math.max(0, i - scanCtx.context), i).map(own),\n after: lines.slice(i + 1, Math.min(lineCount, i + 1 + scanCtx.context)).map(own),\n }\n : {}),\n });\n if (matches.length >= scanCtx.maxResults) break;\n }\n }\n return { kind: 'scanned', matches };\n}\n\nexport async function searchContent(\n directory: string,\n pattern: string,\n options: SearchContentOptions,\n pathGuard: PathGuard,\n): Promise<SearchContentOutcome> {\n const source = options.isRegex ? pattern || '' : escapeRegExp(pattern || '');\n // Compiles live inside the try so a failing second compile does not leak the\n // first's wasm memory; freeRegex is null-safe.\n let regex: Regex | undefined;\n let prefilter: Regex | undefined;\n try {\n regex = compileRegex(source, {\n caseSensitive: Boolean(options.caseSensitive),\n });\n // Whole-buffer test: `m` makes ^/$ line-relative so a per-line hit is always a\n // buffer hit (the converse can be false — a false positive just falls through\n // to the per-line scan). \\A/\\z/\\Z are buffer-relative even under `m`, inline\n // flag groups can undo `m`, and $ cannot see past a \\r, so those inputs skip\n // the prefilter (see canPrefilter).\n prefilter = compileRegex(source, {\n caseSensitive: Boolean(options.caseSensitive),\n multiline: true,\n });\n const matches: SearchResult[] = [];\n const maxResults = options.maxResults ?? 100;\n const maxFileSize = getMaxTextFileSize();\n const context = options.context ?? 0;\n const ceiling = pathGuard.allowedRootContaining(directory);\n\n const entries = options.explicitFile\n ? [{ path: options.explicitFile }]\n : globEntries({\n cwd: directory,\n pattern: options.filePattern ?? '**/*',\n // Same rule as replace_text, so a search with the same glob previews the\n // files a replace would touch: a slash-free glob matches at any depth.\n baseNameMatch: true,\n includeHidden: Boolean(options.includeHidden),\n skipIgnored: Boolean(options.skipIgnored),\n ...(ceiling !== undefined ? { ignoreCeiling: ceiling } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n ...(options.maxDepth !== undefined ? { maxDepth: options.maxDepth } : {}),\n suppressErrors: true,\n });\n\n let filesScanned = 0;\n let filesMatched = 0;\n let matchingLines = 0;\n let skippedTooLarge = 0;\n let skippedBinary = 0;\n const scanCtx: ScanContext = {\n fs: new GuardedFileSystem(pathGuard),\n signal: options.signal,\n maxFileSize,\n maxResults,\n regex,\n prefilter,\n source,\n context,\n };\n const perFile = new Map<number, FileScan>();\n let dispatched = 0;\n let collected = 0;\n const stopped = await processEntriesConcurrently(entries, {\n signal: options.signal,\n concurrency: PARALLEL_CONCURRENCY,\n shouldStop: () => collected >= maxResults,\n onEntry: () => {\n dispatched++;\n },\n onError: () => undefined,\n runEntry: async (entryPath) => {\n const index = dispatched - 1;\n const scan = await scanFile(entryPath, scanCtx);\n perFile.set(index, scan);\n if (scan.kind === 'scanned') collected += scan.matches.length;\n },\n });\n\n let skippedInaccessible = 0;\n for (let i = 0; i < dispatched; i++) {\n const scan = perFile.get(i);\n if (!scan) continue;\n switch (scan.kind) {\n case 'inaccessible':\n skippedInaccessible++;\n break;\n case 'tooLarge':\n skippedTooLarge++;\n break;\n case 'binary':\n filesScanned++;\n skippedBinary++;\n break;\n case 'scanned':\n filesScanned++;\n if (scan.matches.length > 0) filesMatched++;\n for (const match of scan.matches) {\n if (matches.length >= maxResults) break;\n matches.push(match);\n matchingLines++;\n }\n break;\n }\n }\n\n const stoppedReason = resolveStopReason(\n matches.length >= maxResults,\n stopped === 'timeout' || Boolean(options.signal?.aborted),\n );\n\n return {\n basePath: directory,\n matches,\n summary: {\n matchingLines,\n filesScanned,\n filesMatched,\n truncated: stoppedReason !== undefined,\n skippedInaccessible,\n skippedTooLarge,\n skippedBinary,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n } finally {\n freeRegex(prefilter);\n freeRegex(regex);\n }\n}\n\n/** Whether one whole-buffer RE2 test is a sound \"cannot match\" check for this file. */\nfunction canPrefilter(source: string, buffer: Buffer): boolean {\n if (buffer.length > RE2_MAX_INPUT_BYTES) return false;\n if (BUFFER_ANCHOR_RE.test(source)) return false;\n if (INLINE_FLAGS_RE.test(source)) return false;\n if (source.includes('$') && buffer.includes(CR)) return false;\n return true;\n}\n\nexport async function searchFiles(\n directory: string,\n pattern: string,\n options: {\n maxResults?: number;\n includeHidden?: boolean;\n sortBy?: 'name' | 'path';\n skipIgnored?: boolean;\n maxDepth?: number;\n signal?: AbortSignal;\n },\n pathGuard: PathGuard,\n): Promise<{\n basePath: string;\n results: { path: string }[];\n summary: {\n matched: number;\n filesScanned: number;\n truncated: boolean;\n skippedInaccessible: number;\n /** Narrowed like SearchContentOutcome's — scans never `maxFiles`. */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}> {\n const maxResults = options.maxResults ?? 100;\n const ceiling = pathGuard.allowedRootContaining(directory);\n const entries = globEntries({\n cwd: directory,\n pattern,\n baseNameMatch: true,\n includeHidden: Boolean(options.includeHidden),\n skipIgnored: Boolean(options.skipIgnored),\n ...(ceiling !== undefined ? { ignoreCeiling: ceiling } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n ...(options.maxDepth !== undefined ? { maxDepth: options.maxDepth } : {}),\n suppressErrors: true,\n });\n const results: { path: string }[] = [];\n let filesScanned = 0;\n let skippedInaccessible = 0;\n let stoppedByAbort = false;\n\n // The signal carries both client cancellation and the tool's search timeout,\n // so an abort means \"return what we have, marked incomplete\" rather than\n // throw — but it must never be reported as a finished scan. The walk itself\n // throws when the signal stops it before a single entry is yielded.\n try {\n for await (const entry of entries) {\n if (options.signal?.aborted) {\n stoppedByAbort = true;\n break;\n }\n try {\n await pathGuard.validateExistingPath(entry.path);\n } catch {\n skippedInaccessible++;\n continue;\n }\n if (results.length >= maxResults) break;\n filesScanned++;\n results.push({ path: entry.path });\n }\n } catch (error) {\n if (!options.signal?.aborted) throw error;\n stoppedByAbort = true;\n }\n\n // Sorting — only name / path are supported; size / modified were removed\n // (the glob never collected stats, so they were always undefined).\n if (options.sortBy === 'name') {\n results.sort((a, b) => basename(a.path).localeCompare(basename(b.path)));\n } else {\n results.sort((a, b) => a.path.localeCompare(b.path));\n }\n\n // Same precedence as above: the result cap wins over a same-iteration abort.\n const stoppedReason = resolveStopReason(results.length >= maxResults, stoppedByAbort);\n\n return {\n basePath: directory,\n results,\n summary: {\n matched: results.length,\n filesScanned,\n truncated: stoppedReason !== undefined,\n skippedInaccessible,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sensitive.d.ts","sourceRoot":"","sources":["../../src/core/sensitive.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sensitive.d.ts","sourceRoot":"","sources":["../../src/core/sensitive.ts"],"names":[],"mappings":"AAwUA,iHAAiH;AACjH,wBAAgB,uBAAuB,IAAI;IACzC,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B,CAQA;AAID,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;gBAK/B,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE;IAanE,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;CAcvC"}
|
package/dist/core/sensitive.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
// Sensitive-file denylist policy: the compiled pattern set, the path
|
|
2
2
|
// normalization that feeds it, and the NTFS alternate-data-stream stripping
|
|
3
3
|
// Windows needs. Split out of path.ts so the denylist has its own home — it
|
|
4
|
-
// shares only
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// shares only character-level primitives from path-utils.ts, not the
|
|
5
|
+
// allowed-directory assembly. path.ts and path-completer.ts reach it through
|
|
6
|
+
// PathGuard.isSensitive, which delegates here.
|
|
7
7
|
import { normalize, posix, sep } from 'node:path';
|
|
8
8
|
import { cli } from "./config.js";
|
|
9
|
-
import { IS_WINDOWS, isAlpha,
|
|
10
|
-
import { escapeRegExp } from "./util.js";
|
|
11
|
-
const CHAR_COLON = 58;
|
|
9
|
+
import { CHAR_COLON, IS_WINDOWS, isAlpha, toPosixPath, trimTrailingDotsAndSpaces, } from "./path-utils.js";
|
|
10
|
+
import { escapeRegExp, parseTrueEnvFlag } from "./util.js";
|
|
12
11
|
function normalizeForMatch(input) {
|
|
13
12
|
// Always lowercase for case-insensitive denylist matching on all platforms.
|
|
14
13
|
// normalize() preserves the double leading slash of UNC paths
|
|
@@ -207,10 +206,8 @@ function matchesAnyGlob(globs, candidate) {
|
|
|
207
206
|
// Strip NTFS alternate-data-stream suffixes (the ":stream" after a segment)
|
|
208
207
|
// before denylist matching, so `.env:secret` is caught as `.env`. Preserves the
|
|
209
208
|
// drive-letter colon in the first segment of an absolute path. Also trims
|
|
210
|
-
// trailing dots/spaces
|
|
211
|
-
//
|
|
212
|
-
// exact-name patterns (`.env`, `.npmrc`) are bypassed on Windows. Mirrors the
|
|
213
|
-
// precedent in getReservedDeviceName (path.ts) which trims for the same reason.
|
|
209
|
+
// trailing dots/spaces (see trimTrailingDotsAndSpaces in path-utils.ts), the
|
|
210
|
+
// same trim getReservedDeviceName applies.
|
|
214
211
|
function stripAlternateDataStreams(filePath) {
|
|
215
212
|
const parts = filePath.split(/[\\/]/);
|
|
216
213
|
const stripped = parts.map((segment, i) => {
|
|
@@ -222,14 +219,10 @@ function stripAlternateDataStreams(filePath) {
|
|
|
222
219
|
}
|
|
223
220
|
const colonIdx = segment.indexOf(':');
|
|
224
221
|
const withoutStream = colonIdx !== -1 ? segment.slice(0, colonIdx) : segment;
|
|
225
|
-
// Win32 strips trailing dots and spaces at the syscall boundary, so a
|
|
226
|
-
// request for ".env " creates ".env". Strip them before denylist matching
|
|
227
|
-
// or the exact-name patterns (".env", ".npmrc") are bypassed on Windows.
|
|
228
222
|
return trimTrailingDotsAndSpaces(withoutStream);
|
|
229
223
|
});
|
|
230
224
|
return stripped.join(sep);
|
|
231
225
|
}
|
|
232
|
-
const trimTrailingDotsAndSpaces = (segment) => segment.replace(/[. ]+$/, '');
|
|
233
226
|
const DEFAULT_SENSITIVE_PATTERNS = [
|
|
234
227
|
'.env',
|
|
235
228
|
'.env.*',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sensitive.js","sourceRoot":"","sources":["../../src/core/sensitive.ts"],"names":[],"mappings":"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","sourcesContent":["// Sensitive-file denylist policy: the compiled pattern set, the path\n// normalization that feeds it, and the NTFS alternate-data-stream stripping\n// Windows needs. Split out of path.ts so the denylist has its own home — it\n// shares only isAlpha / toPosixPath / IS_WINDOWS with the primitives in\n// path-utils.ts, not the allowed-directory assembly. path.ts and\n// path-completer.ts reach it through PathGuard.isSensitive, which delegates here.\nimport { normalize, posix, sep } from 'node:path';\n\nimport { cli } from './config.ts';\nimport { IS_WINDOWS, isAlpha, parseTrueEnvFlag, toPosixPath } from './path-utils.ts';\nimport { escapeRegExp } from './util.ts';\n\nconst CHAR_COLON = 58;\n\nfunction normalizeForMatch(input: string): string {\n // Always lowercase for case-insensitive denylist matching on all platforms.\n // normalize() preserves the double leading slash of UNC paths\n // (//server/share/...) on Windows; collapse it to one so the compiled\n // patterns — which carry no leading slash — still match their segments.\n return toPosixPath(normalize(input)).toLowerCase().replace(/^\\/+/u, '/');\n}\n\ninterface CompiledPatternSet {\n pathGlobs: readonly RegExp[];\n nameGlobs: readonly RegExp[];\n}\n\n// posix.matchesGlob runs with dot:false semantics and does not honor a dot\n// option at all (verified on Node 24): '*' and '**' never match dot-leading\n// path segments, so a deny of `secrets/**` silently let `secrets/.env`\n// through — fail-open against exactly the hidden files this list exists to\n// guard. Compile our own matcher instead. Supported metacharacters: '*'\n// (any run within a segment), '**' (any run of whole segments), '?' (one\n// char), '[...]' classes, and '{a,b}' alternation; everything else is\n// literal, and dot-leading segments match like any other.\n\n// Character classes can carry '{', '}' and ',' as members, so every scanner\n// below must skip a class's interior. classEnd returns the index of the ']'\n// closing the class opened at openIdx, or -1 when unclosed (then '[' is a\n// literal, matching globToRegExp's fallback). A ']' right after '[' or the\n// '!' negation is a literal member, per glob syntax.\nfunction classEnd(glob: string, openIdx: number): number {\n let i = openIdx + 1;\n if (glob[i] === '!') i++;\n if (glob[i] === ']') i++;\n while (i < glob.length && glob[i] !== ']') i++;\n return i < glob.length ? i : -1;\n}\n\nfunction expandBraces(glob: string): string[] {\n // First '{' outside any [...] class.\n let open = -1;\n for (let i = 0; i < glob.length; i++) {\n const char = glob[i] ?? '';\n if (char === '[') {\n const end = classEnd(glob, i);\n if (end !== -1) i = end;\n } else if (char === '{') {\n open = i;\n break;\n }\n }\n if (open === -1) return [glob];\n // Pair the outer '{' with ITS matching '}' — indexOf would grab the inner\n // group's closer and produce garbage like 'data/x}/secret'. Then split the\n // body on depth-0 commas so nested groups stay intact for the recursion.\n let depth = 0;\n let close = -1;\n for (let i = open; i < glob.length; i++) {\n const char = glob[i] ?? '';\n if (char === '[') {\n const end = classEnd(glob, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') {\n depth--;\n if (depth === 0) {\n close = i;\n break;\n }\n }\n }\n if (close === -1) return [glob];\n const prefix = glob.slice(0, open);\n const suffix = glob.slice(close + 1);\n const body = glob.slice(open + 1, close);\n const parts: string[] = [];\n let start = 0;\n depth = 0;\n for (let i = 0; i < body.length; i++) {\n const char = body[i] ?? '';\n if (char === '[') {\n const end = classEnd(body, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') depth--;\n else if (char === ',' && depth === 0) {\n parts.push(body.slice(start, i));\n start = i + 1;\n }\n }\n parts.push(body.slice(start));\n return parts.flatMap((part) => expandBraces(`${prefix}${part}${suffix}`));\n}\n\nfunction globToRegExp(glob: string): RegExp {\n const segments = glob.split('/');\n let source = '';\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i] ?? '';\n const last = i === segments.length - 1;\n if (segment === '**') {\n source += last ? '(?:.*)?' : '(?:[^/]+/)*';\n continue;\n }\n for (let j = 0; j < segment.length; j++) {\n const char = segment[j] ?? '';\n if (char === '*') {\n while (segment[j + 1] === '*') j++;\n source += '[^/]*';\n } else if (char === '?') {\n source += '[^/]';\n } else if (char === '[') {\n // Same classEnd rule the scanners use: a ']' right after '[' (or\n // after the '!' negation) is a literal member, not the closer.\n const close = classEnd(segment, j);\n if (close !== -1) {\n // Pass the class through, flipping glob '!' negation to '^' and\n // escaping backslashes so no body can make new RegExp throw. A\n // leading ']' must be escaped or the 'u' flag parses '[]a]' as an\n // empty class followed by stray literals; a leading '^' is a\n // literal member (glob negation is '!' only), unescaped it would\n // negate the class and relieve more than the operator named.\n let body = segment.slice(j + 1, close).replace(/\\\\/gu, '\\\\\\\\');\n const negated = body.startsWith('!');\n if (negated) body = body.slice(1);\n if (body.startsWith(']') || body.startsWith('^')) body = `\\\\${body}`;\n source += `[${negated ? '^' : ''}${body}]`;\n j = close;\n } else {\n source += escapeRegExp(char);\n }\n } else {\n source += escapeRegExp(char);\n }\n }\n if (!last) source += '/';\n }\n // Candidates are absolute paths but patterns carry no leading '/', so the\n // anchor optionally eats the root slash — `**/` then reaches from any\n // parent, exactly as matchesGlob's globstar did. '$' is strict in\n // JavaScript (it matches end-of-input only, unlike PCRE's, which also\n // matches before a final newline), so an exact-name pattern can never\n // match a '.env\\n' lookalike. The 's' flag makes the terminal-`**` `.`\n // match newlines too: filenames can contain them, and a deny ending in\n // '/**' must not fail open on 'secrets/plan\\nnotes'.\n return new RegExp(`^/?${source}$`, 'su');\n}\n\nfunction compilePatternGlobs(normalizedPattern: string): readonly string[] {\n // A leading '/' (POSIX-absolute) is rooted like a Windows drive path:\n // aliasing '/abs/secret' as '**/abs/secret' would also match\n // 'other/abs/secret', and on the allow side that is over-relief. The\n // drive-letter regex is safe on [a-z] because normalizeForMatch lowercased.\n const isRooted =\n normalizedPattern.startsWith('/') ||\n normalizedPattern.startsWith('**/') ||\n /^[a-z]:\\//u.test(normalizedPattern);\n const withoutRoot = isRooted ? '' : normalizedPattern.replace(/^\\/+/, '');\n return withoutRoot ? [normalizedPattern, `**/${withoutRoot}`] : [normalizedPattern];\n}\n\nfunction toPatternSet(patterns: readonly string[]): CompiledPatternSet {\n const pathGlobs = new Set<RegExp>();\n const nameGlobs = new Set<RegExp>();\n\n const deduped = Array.from(new Set(patterns.map((p) => p.trim()).filter((p) => p.length > 0)));\n for (const pattern of deduped) {\n const normalized = normalizeForMatch(pattern);\n const matchesPath = normalized.includes('/');\n const target = matchesPath ? pathGlobs : nameGlobs;\n const globs = matchesPath ? compilePatternGlobs(normalized) : [normalized];\n for (const glob of globs) {\n for (const expanded of expandBraces(glob)) {\n target.add(globToRegExp(expanded));\n }\n }\n }\n\n return {\n pathGlobs: [...pathGlobs],\n nameGlobs: [...nameGlobs],\n };\n}\n\nfunction matchesAnyGlob(globs: readonly RegExp[], candidate: string): boolean {\n return globs.some((glob) => glob.test(candidate));\n}\n\n// Strip NTFS alternate-data-stream suffixes (the \":stream\" after a segment)\n// before denylist matching, so `.env:secret` is caught as `.env`. Preserves the\n// drive-letter colon in the first segment of an absolute path. Also trims\n// trailing dots/spaces: Win32 strips them at the syscall boundary (a request\n// to create `.env ` actually creates `.env`), so without trimming them here the\n// exact-name patterns (`.env`, `.npmrc`) are bypassed on Windows. Mirrors the\n// precedent in getReservedDeviceName (path.ts) which trims for the same reason.\nfunction stripAlternateDataStreams(filePath: string): string {\n const parts = filePath.split(/[\\\\/]/);\n const stripped = parts.map((segment, i) => {\n if (\n i === 0 &&\n segment.length === 2 &&\n isAlpha(segment.charCodeAt(0)) &&\n segment.charCodeAt(1) === CHAR_COLON\n ) {\n return segment;\n }\n const colonIdx = segment.indexOf(':');\n const withoutStream = colonIdx !== -1 ? segment.slice(0, colonIdx) : segment;\n // Win32 strips trailing dots and spaces at the syscall boundary, so a\n // request for \".env \" creates \".env\". Strip them before denylist matching\n // or the exact-name patterns (\".env\", \".npmrc\") are bypassed on Windows.\n return trimTrailingDotsAndSpaces(withoutStream);\n });\n return stripped.join(sep);\n}\n\nconst trimTrailingDotsAndSpaces = (segment: string): string => segment.replace(/[. ]+$/, '');\n\nconst DEFAULT_SENSITIVE_PATTERNS = [\n '.env',\n '.env.*',\n '.npmrc',\n '.pypirc',\n '.netrc',\n '_netrc',\n '.git-credentials',\n '.pgpass',\n '.aws/credentials',\n '.aws/config',\n '.docker/config.json',\n '.kube/config',\n 'application_default_credentials.json',\n '.mcpregistry_*_token',\n '*.pem',\n '*.key',\n '*.p12',\n '*.pfx',\n '*.crt',\n '*.cer',\n '*id_rsa*',\n '*id_dsa*',\n '*id_ecdsa*',\n '*id_ed25519*',\n] as const;\n\n// Built-ins and operator-supplied deny entries live in separate tiers because\n// they answer to different relief switches: an allow entry (FS_ALLOWLIST /\n// --allow) can lift a built-in hit, but never an explicit deny.\n\n// Split an env pattern list on commas and newlines at brace depth 0 only and\n// outside any [...] class, so documented syntax like '*.{pem,key}' or\n// 'x[1,2].env' survives as one pattern instead of tearing apart.\nfunction splitPatternList(value: string): string[] {\n const naive = (): string[] =>\n value\n .split(/,|\\n/u)\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n const parts: string[] = [];\n let depth = 0;\n let start = 0;\n let unbalanced = false;\n for (let i = 0; i < value.length; i++) {\n const char = value[i] ?? '';\n if (char === '[') {\n const end = classEnd(value, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') {\n if (depth === 0) unbalanced = true;\n else depth--;\n } else if (depth === 0 && (char === ',' || char === '\\n')) {\n parts.push(value.slice(start, i));\n start = i + 1;\n }\n }\n // An unbalanced brace would swallow every separator behind it into one\n // bogus literal pattern and silently drop the real entries — '{bad,\n // secrets/**' must not lose 'secrets/**'. A typo'd brace costs the brace\n // pattern only: fall back to the naive split.\n if (unbalanced || depth !== 0) return naive();\n parts.push(value.slice(start));\n return parts.map((t) => t.trim()).filter((t) => t.length > 0);\n}\n\nfunction buildDenyTiers(): { builtin: readonly string[]; operator: readonly string[] } {\n const allowSensitive =\n cli.allowSensitive ?? parseTrueEnvFlag(process.env['FS_ALLOW_SENSITIVE'], 'FS_ALLOW_SENSITIVE');\n const envDenylist = process.env['FS_DENYLIST']\n ? splitPatternList(process.env['FS_DENYLIST'])\n : [];\n const flagDenylist = cli.denyPatterns ?? [];\n // FS_ALLOW_SENSITIVE suppresses built-ins only; deny entries (env and --deny)\n // always apply. Set-dedupe so a pattern in both sources matches once.\n return {\n builtin: allowSensitive ? [] : DEFAULT_SENSITIVE_PATTERNS,\n operator: [...new Set([...envDenylist, ...flagDenylist])],\n };\n}\n\nfunction buildAllowPatterns(): readonly string[] {\n const envAllowlist = process.env['FS_ALLOWLIST']\n ? splitPatternList(process.env['FS_ALLOWLIST'])\n : [];\n const flagAllowlist = cli.allowPatterns ?? [];\n return [...new Set([...envAllowlist, ...flagAllowlist])];\n}\n\n/** The three pattern tiers as configured, for `--print-config`. Read-only view; the matcher compiles its own. */\nexport function describeSensitivePolicy(): {\n allowSensitive: boolean;\n builtinDeny: readonly string[];\n operatorDeny: readonly string[];\n allow: readonly string[];\n} {\n const tiers = buildDenyTiers();\n return {\n allowSensitive: tiers.builtin.length === 0,\n builtinDeny: tiers.builtin,\n operatorDeny: tiers.operator,\n allow: buildAllowPatterns(),\n };\n}\n\nconst EMPTY_PATTERN_SET: CompiledPatternSet = { pathGlobs: [], nameGlobs: [] };\n\nexport class SensitiveMatcher {\n private readonly builtin: CompiledPatternSet;\n private readonly operator: CompiledPatternSet;\n private readonly allow: CompiledPatternSet;\n\n // An explicit pattern list (tests, custom guards) is one relievable tier;\n // the default construction splits built-ins from operator-supplied\n // FS_DENYLIST/--deny entries, which allow entries can never lift.\n constructor(patterns?: readonly string[], allow?: readonly string[]) {\n if (patterns !== undefined || allow !== undefined) {\n this.builtin = toPatternSet(patterns ?? []);\n this.operator = EMPTY_PATTERN_SET;\n this.allow = toPatternSet(allow ?? []);\n } else {\n const tiers = buildDenyTiers();\n this.builtin = toPatternSet(tiers.builtin);\n this.operator = toPatternSet(tiers.operator);\n this.allow = toPatternSet(buildAllowPatterns());\n }\n }\n\n isSensitive(filePath: string): boolean {\n const pathToCheck = IS_WINDOWS ? stripAlternateDataStreams(filePath) : filePath;\n const normalizedPath = normalizeForMatch(pathToCheck);\n const name = posix.basename(normalizedPath);\n const matches = (set: CompiledPatternSet): boolean =>\n matchesAnyGlob(set.pathGlobs, normalizedPath) || matchesAnyGlob(set.nameGlobs, name);\n\n // Explicit deny entries (FS_DENYLIST/--deny) always apply.\n if (matches(this.operator)) return true;\n if (!matches(this.builtin)) return false;\n // A built-in hit is lifted only by an allow entry that matches the same\n // normalized candidate — a pattern that matches nothing relieves nothing.\n return !matches(this.allow);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sensitive.js","sourceRoot":"","sources":["../../src/core/sensitive.ts"],"names":[],"mappings":"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","sourcesContent":["// Sensitive-file denylist policy: the compiled pattern set, the path\n// normalization that feeds it, and the NTFS alternate-data-stream stripping\n// Windows needs. Split out of path.ts so the denylist has its own home — it\n// shares only character-level primitives from path-utils.ts, not the\n// allowed-directory assembly. path.ts and path-completer.ts reach it through\n// PathGuard.isSensitive, which delegates here.\nimport { normalize, posix, sep } from 'node:path';\n\nimport { cli } from './config.ts';\nimport {\n CHAR_COLON,\n IS_WINDOWS,\n isAlpha,\n toPosixPath,\n trimTrailingDotsAndSpaces,\n} from './path-utils.ts';\nimport { escapeRegExp, parseTrueEnvFlag } from './util.ts';\n\nfunction normalizeForMatch(input: string): string {\n // Always lowercase for case-insensitive denylist matching on all platforms.\n // normalize() preserves the double leading slash of UNC paths\n // (//server/share/...) on Windows; collapse it to one so the compiled\n // patterns — which carry no leading slash — still match their segments.\n return toPosixPath(normalize(input)).toLowerCase().replace(/^\\/+/u, '/');\n}\n\ninterface CompiledPatternSet {\n pathGlobs: readonly RegExp[];\n nameGlobs: readonly RegExp[];\n}\n\n// posix.matchesGlob runs with dot:false semantics and does not honor a dot\n// option at all (verified on Node 24): '*' and '**' never match dot-leading\n// path segments, so a deny of `secrets/**` silently let `secrets/.env`\n// through — fail-open against exactly the hidden files this list exists to\n// guard. Compile our own matcher instead. Supported metacharacters: '*'\n// (any run within a segment), '**' (any run of whole segments), '?' (one\n// char), '[...]' classes, and '{a,b}' alternation; everything else is\n// literal, and dot-leading segments match like any other.\n\n// Character classes can carry '{', '}' and ',' as members, so every scanner\n// below must skip a class's interior. classEnd returns the index of the ']'\n// closing the class opened at openIdx, or -1 when unclosed (then '[' is a\n// literal, matching globToRegExp's fallback). A ']' right after '[' or the\n// '!' negation is a literal member, per glob syntax.\nfunction classEnd(glob: string, openIdx: number): number {\n let i = openIdx + 1;\n if (glob[i] === '!') i++;\n if (glob[i] === ']') i++;\n while (i < glob.length && glob[i] !== ']') i++;\n return i < glob.length ? i : -1;\n}\n\nfunction expandBraces(glob: string): string[] {\n // First '{' outside any [...] class.\n let open = -1;\n for (let i = 0; i < glob.length; i++) {\n const char = glob[i] ?? '';\n if (char === '[') {\n const end = classEnd(glob, i);\n if (end !== -1) i = end;\n } else if (char === '{') {\n open = i;\n break;\n }\n }\n if (open === -1) return [glob];\n // Pair the outer '{' with ITS matching '}' — indexOf would grab the inner\n // group's closer and produce garbage like 'data/x}/secret'. Then split the\n // body on depth-0 commas so nested groups stay intact for the recursion.\n let depth = 0;\n let close = -1;\n for (let i = open; i < glob.length; i++) {\n const char = glob[i] ?? '';\n if (char === '[') {\n const end = classEnd(glob, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') {\n depth--;\n if (depth === 0) {\n close = i;\n break;\n }\n }\n }\n if (close === -1) return [glob];\n const prefix = glob.slice(0, open);\n const suffix = glob.slice(close + 1);\n const body = glob.slice(open + 1, close);\n const parts: string[] = [];\n let start = 0;\n depth = 0;\n for (let i = 0; i < body.length; i++) {\n const char = body[i] ?? '';\n if (char === '[') {\n const end = classEnd(body, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') depth--;\n else if (char === ',' && depth === 0) {\n parts.push(body.slice(start, i));\n start = i + 1;\n }\n }\n parts.push(body.slice(start));\n return parts.flatMap((part) => expandBraces(`${prefix}${part}${suffix}`));\n}\n\nfunction globToRegExp(glob: string): RegExp {\n const segments = glob.split('/');\n let source = '';\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i] ?? '';\n const last = i === segments.length - 1;\n if (segment === '**') {\n source += last ? '(?:.*)?' : '(?:[^/]+/)*';\n continue;\n }\n for (let j = 0; j < segment.length; j++) {\n const char = segment[j] ?? '';\n if (char === '*') {\n while (segment[j + 1] === '*') j++;\n source += '[^/]*';\n } else if (char === '?') {\n source += '[^/]';\n } else if (char === '[') {\n // Same classEnd rule the scanners use: a ']' right after '[' (or\n // after the '!' negation) is a literal member, not the closer.\n const close = classEnd(segment, j);\n if (close !== -1) {\n // Pass the class through, flipping glob '!' negation to '^' and\n // escaping backslashes so no body can make new RegExp throw. A\n // leading ']' must be escaped or the 'u' flag parses '[]a]' as an\n // empty class followed by stray literals; a leading '^' is a\n // literal member (glob negation is '!' only), unescaped it would\n // negate the class and relieve more than the operator named.\n let body = segment.slice(j + 1, close).replace(/\\\\/gu, '\\\\\\\\');\n const negated = body.startsWith('!');\n if (negated) body = body.slice(1);\n if (body.startsWith(']') || body.startsWith('^')) body = `\\\\${body}`;\n source += `[${negated ? '^' : ''}${body}]`;\n j = close;\n } else {\n source += escapeRegExp(char);\n }\n } else {\n source += escapeRegExp(char);\n }\n }\n if (!last) source += '/';\n }\n // Candidates are absolute paths but patterns carry no leading '/', so the\n // anchor optionally eats the root slash — `**/` then reaches from any\n // parent, exactly as matchesGlob's globstar did. '$' is strict in\n // JavaScript (it matches end-of-input only, unlike PCRE's, which also\n // matches before a final newline), so an exact-name pattern can never\n // match a '.env\\n' lookalike. The 's' flag makes the terminal-`**` `.`\n // match newlines too: filenames can contain them, and a deny ending in\n // '/**' must not fail open on 'secrets/plan\\nnotes'.\n return new RegExp(`^/?${source}$`, 'su');\n}\n\nfunction compilePatternGlobs(normalizedPattern: string): readonly string[] {\n // A leading '/' (POSIX-absolute) is rooted like a Windows drive path:\n // aliasing '/abs/secret' as '**/abs/secret' would also match\n // 'other/abs/secret', and on the allow side that is over-relief. The\n // drive-letter regex is safe on [a-z] because normalizeForMatch lowercased.\n const isRooted =\n normalizedPattern.startsWith('/') ||\n normalizedPattern.startsWith('**/') ||\n /^[a-z]:\\//u.test(normalizedPattern);\n const withoutRoot = isRooted ? '' : normalizedPattern.replace(/^\\/+/, '');\n return withoutRoot ? [normalizedPattern, `**/${withoutRoot}`] : [normalizedPattern];\n}\n\nfunction toPatternSet(patterns: readonly string[]): CompiledPatternSet {\n const pathGlobs = new Set<RegExp>();\n const nameGlobs = new Set<RegExp>();\n\n const deduped = Array.from(new Set(patterns.map((p) => p.trim()).filter((p) => p.length > 0)));\n for (const pattern of deduped) {\n const normalized = normalizeForMatch(pattern);\n const matchesPath = normalized.includes('/');\n const target = matchesPath ? pathGlobs : nameGlobs;\n const globs = matchesPath ? compilePatternGlobs(normalized) : [normalized];\n for (const glob of globs) {\n for (const expanded of expandBraces(glob)) {\n target.add(globToRegExp(expanded));\n }\n }\n }\n\n return {\n pathGlobs: [...pathGlobs],\n nameGlobs: [...nameGlobs],\n };\n}\n\nfunction matchesAnyGlob(globs: readonly RegExp[], candidate: string): boolean {\n return globs.some((glob) => glob.test(candidate));\n}\n\n// Strip NTFS alternate-data-stream suffixes (the \":stream\" after a segment)\n// before denylist matching, so `.env:secret` is caught as `.env`. Preserves the\n// drive-letter colon in the first segment of an absolute path. Also trims\n// trailing dots/spaces (see trimTrailingDotsAndSpaces in path-utils.ts), the\n// same trim getReservedDeviceName applies.\nfunction stripAlternateDataStreams(filePath: string): string {\n const parts = filePath.split(/[\\\\/]/);\n const stripped = parts.map((segment, i) => {\n if (\n i === 0 &&\n segment.length === 2 &&\n isAlpha(segment.charCodeAt(0)) &&\n segment.charCodeAt(1) === CHAR_COLON\n ) {\n return segment;\n }\n const colonIdx = segment.indexOf(':');\n const withoutStream = colonIdx !== -1 ? segment.slice(0, colonIdx) : segment;\n return trimTrailingDotsAndSpaces(withoutStream);\n });\n return stripped.join(sep);\n}\n\nconst DEFAULT_SENSITIVE_PATTERNS = [\n '.env',\n '.env.*',\n '.npmrc',\n '.pypirc',\n '.netrc',\n '_netrc',\n '.git-credentials',\n '.pgpass',\n '.aws/credentials',\n '.aws/config',\n '.docker/config.json',\n '.kube/config',\n 'application_default_credentials.json',\n '.mcpregistry_*_token',\n '*.pem',\n '*.key',\n '*.p12',\n '*.pfx',\n '*.crt',\n '*.cer',\n '*id_rsa*',\n '*id_dsa*',\n '*id_ecdsa*',\n '*id_ed25519*',\n] as const;\n\n// Built-ins and operator-supplied deny entries live in separate tiers because\n// they answer to different relief switches: an allow entry (FS_ALLOWLIST /\n// --allow) can lift a built-in hit, but never an explicit deny.\n\n// Split an env pattern list on commas and newlines at brace depth 0 only and\n// outside any [...] class, so documented syntax like '*.{pem,key}' or\n// 'x[1,2].env' survives as one pattern instead of tearing apart.\nfunction splitPatternList(value: string): string[] {\n const naive = (): string[] =>\n value\n .split(/,|\\n/u)\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n const parts: string[] = [];\n let depth = 0;\n let start = 0;\n let unbalanced = false;\n for (let i = 0; i < value.length; i++) {\n const char = value[i] ?? '';\n if (char === '[') {\n const end = classEnd(value, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') {\n if (depth === 0) unbalanced = true;\n else depth--;\n } else if (depth === 0 && (char === ',' || char === '\\n')) {\n parts.push(value.slice(start, i));\n start = i + 1;\n }\n }\n // An unbalanced brace would swallow every separator behind it into one\n // bogus literal pattern and silently drop the real entries — '{bad,\n // secrets/**' must not lose 'secrets/**'. A typo'd brace costs the brace\n // pattern only: fall back to the naive split.\n if (unbalanced || depth !== 0) return naive();\n parts.push(value.slice(start));\n return parts.map((t) => t.trim()).filter((t) => t.length > 0);\n}\n\nfunction buildDenyTiers(): { builtin: readonly string[]; operator: readonly string[] } {\n const allowSensitive =\n cli.allowSensitive ?? parseTrueEnvFlag(process.env['FS_ALLOW_SENSITIVE'], 'FS_ALLOW_SENSITIVE');\n const envDenylist = process.env['FS_DENYLIST']\n ? splitPatternList(process.env['FS_DENYLIST'])\n : [];\n const flagDenylist = cli.denyPatterns ?? [];\n // FS_ALLOW_SENSITIVE suppresses built-ins only; deny entries (env and --deny)\n // always apply. Set-dedupe so a pattern in both sources matches once.\n return {\n builtin: allowSensitive ? [] : DEFAULT_SENSITIVE_PATTERNS,\n operator: [...new Set([...envDenylist, ...flagDenylist])],\n };\n}\n\nfunction buildAllowPatterns(): readonly string[] {\n const envAllowlist = process.env['FS_ALLOWLIST']\n ? splitPatternList(process.env['FS_ALLOWLIST'])\n : [];\n const flagAllowlist = cli.allowPatterns ?? [];\n return [...new Set([...envAllowlist, ...flagAllowlist])];\n}\n\n/** The three pattern tiers as configured, for `--print-config`. Read-only view; the matcher compiles its own. */\nexport function describeSensitivePolicy(): {\n allowSensitive: boolean;\n builtinDeny: readonly string[];\n operatorDeny: readonly string[];\n allow: readonly string[];\n} {\n const tiers = buildDenyTiers();\n return {\n allowSensitive: tiers.builtin.length === 0,\n builtinDeny: tiers.builtin,\n operatorDeny: tiers.operator,\n allow: buildAllowPatterns(),\n };\n}\n\nconst EMPTY_PATTERN_SET: CompiledPatternSet = { pathGlobs: [], nameGlobs: [] };\n\nexport class SensitiveMatcher {\n private readonly builtin: CompiledPatternSet;\n private readonly operator: CompiledPatternSet;\n private readonly allow: CompiledPatternSet;\n\n // An explicit pattern list (tests, custom guards) is one relievable tier;\n // the default construction splits built-ins from operator-supplied\n // FS_DENYLIST/--deny entries, which allow entries can never lift.\n constructor(patterns?: readonly string[], allow?: readonly string[]) {\n if (patterns !== undefined || allow !== undefined) {\n this.builtin = toPatternSet(patterns ?? []);\n this.operator = EMPTY_PATTERN_SET;\n this.allow = toPatternSet(allow ?? []);\n } else {\n const tiers = buildDenyTiers();\n this.builtin = toPatternSet(tiers.builtin);\n this.operator = toPatternSet(tiers.operator);\n this.allow = toPatternSet(buildAllowPatterns());\n }\n }\n\n isSensitive(filePath: string): boolean {\n const pathToCheck = IS_WINDOWS ? stripAlternateDataStreams(filePath) : filePath;\n const normalizedPath = normalizeForMatch(pathToCheck);\n const name = posix.basename(normalizedPath);\n const matches = (set: CompiledPatternSet): boolean =>\n matchesAnyGlob(set.pathGlobs, normalizedPath) || matchesAnyGlob(set.nameGlobs, name);\n\n // Explicit deny entries (FS_DENYLIST/--deny) always apply.\n if (matches(this.operator)) return true;\n if (!matches(this.builtin)) return false;\n // A built-in hit is lifted only by an allow entry that matches the same\n // normalized candidate — a pattern that matches nothing relieves nothing.\n return !matches(this.allow);\n }\n}\n"]}
|
package/dist/core/store.d.ts
CHANGED
|
@@ -9,9 +9,37 @@ interface ResourceEntry {
|
|
|
9
9
|
expiresAt: string;
|
|
10
10
|
text: string;
|
|
11
11
|
}
|
|
12
|
+
/** The `resources/read` result that serves `entry`, and what the store measures it by. */
|
|
13
|
+
export declare function entryReadResult(entry: ResourceEntry): {
|
|
14
|
+
contents: [{
|
|
15
|
+
uri: string;
|
|
16
|
+
mimeType: string;
|
|
17
|
+
text: string;
|
|
18
|
+
}];
|
|
19
|
+
};
|
|
20
|
+
interface ResourceStoreOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Refuse an entry whose `resources/read` result encodes past this many
|
|
23
|
+
* bytes. stdio sets it: the entry is re-encoded there as one JSON string,
|
|
24
|
+
* where escaping can outgrow the bytes stored, and a link the connection
|
|
25
|
+
* can never carry back is worth no more than an entry over the cap.
|
|
26
|
+
*/
|
|
27
|
+
readonly maxReadResultBytes?: number;
|
|
28
|
+
}
|
|
12
29
|
export declare class ResourceStore {
|
|
13
30
|
#private;
|
|
14
|
-
constructor(onListChanged?: () => void);
|
|
31
|
+
constructor(onListChanged?: () => void, options?: ResourceStoreOptions);
|
|
32
|
+
/**
|
|
33
|
+
* A view sharing this store's entries that also remembers the URIs put
|
|
34
|
+
* through it, so {@link discard} can take one tool call's entries back.
|
|
35
|
+
*/
|
|
36
|
+
forCall(): ResourceStore;
|
|
37
|
+
/**
|
|
38
|
+
* Drop what was put through this view and is still cached. The put already
|
|
39
|
+
* announced the list change (and may have pruned or evicted other entries),
|
|
40
|
+
* so taking the entry back is announced too.
|
|
41
|
+
*/
|
|
42
|
+
discard(): void;
|
|
15
43
|
getEntry(uri: string): ResourceEntry;
|
|
16
44
|
putText(params: {
|
|
17
45
|
name: string;
|
|
@@ -64,6 +92,13 @@ export declare function invalidCursor(): FsError;
|
|
|
64
92
|
*/
|
|
65
93
|
export declare class PageSnapshotStore {
|
|
66
94
|
#private;
|
|
95
|
+
/**
|
|
96
|
+
* A view sharing this store's snapshots that also remembers the ones
|
|
97
|
+
* created through it, so {@link discard} can take one tool call's back.
|
|
98
|
+
*/
|
|
99
|
+
forCall(): PageSnapshotStore;
|
|
100
|
+
/** Drop the snapshots created through this view. */
|
|
101
|
+
discard(): void;
|
|
67
102
|
create(params: {
|
|
68
103
|
queryKey: string;
|
|
69
104
|
items: readonly unknown[];
|
package/dist/core/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAIjE,OAAO,EAAa,OAAO,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAIjE,OAAO,EAAa,OAAO,EAAE,MAAM,aAAa,CAAC;AAqFjD,UAAU,aAAa;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAOD,0FAA0F;AAC1F,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG;IACrD,QAAQ,EAAE,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7D,CAEA;AAED,UAAU,oBAAoB;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACtC;AAED,qBAAa,aAAa;;gBAYZ,aAAa,CAAC,EAAE,MAAM,IAAI,EAAE,OAAO,GAAE,oBAAyB;IAK1E;;;OAGG;IACH,OAAO,IAAI,aAAa;IAOxB;;;;OAIG;IACH,OAAO,IAAI,IAAI;IAQf,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa;IAYpC,OAAO,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,aAAa;IAkCjF;;;;OAIG;IACH,IAAI,IAAI,MAAM,EAAE;CAGjB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,IAAI,EAAE,YAAY,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,MAAM,EAIZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,OAAO,EAAE,GAClD,kBAAkB,CAwBpB;AAID,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO;IACpD,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAKvC;AAID;;;;;;;;;;GAUG;AACH,qBAAa,iBAAiB;;IAS5B;;;OAGG;IACH,OAAO,IAAI,iBAAiB;IAO5B,oDAAoD;IACpD,OAAO,IAAI,IAAI;IAIf,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,SAAS,OAAO,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,MAAM;IAW3F,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC;IAMhF,KAAK,IAAI,IAAI;CAGd"}
|
package/dist/core/store.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { ErrorCode, FsError } from "./errors.js";
|
|
3
|
-
import { MIB } from "./util.js";
|
|
3
|
+
import { jsonBytesOver, MIB } from "./util.js";
|
|
4
4
|
// ─── TTL + LRU map ───────────────────────────────────────────────────────────
|
|
5
5
|
/**
|
|
6
6
|
* A bounded, expiring map. `Map` keeps insertion order and a read re-inserts,
|
|
@@ -55,12 +55,14 @@ class TtlLru {
|
|
|
55
55
|
this.delete(oldest.value);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
/** True when `key` was present. */
|
|
58
59
|
delete(key) {
|
|
59
60
|
const entry = this.#byKey.get(key);
|
|
60
61
|
if (!entry)
|
|
61
|
-
return;
|
|
62
|
+
return false;
|
|
62
63
|
this.#weight -= entry.weight;
|
|
63
64
|
this.#byKey.delete(key);
|
|
65
|
+
return true;
|
|
64
66
|
}
|
|
65
67
|
/** Live keys, least recently used first. Prunes expired entries first. */
|
|
66
68
|
keys() {
|
|
@@ -76,15 +78,48 @@ const MAX_ENTRIES = 64;
|
|
|
76
78
|
const MAX_TOTAL_BYTES = 25 * MIB;
|
|
77
79
|
const MAX_ENTRY_BYTES = 10 * MIB;
|
|
78
80
|
const ENTRY_TTL_MS = 60 * 1000;
|
|
81
|
+
/** The `resources/read` result that serves `entry`, and what the store measures it by. */
|
|
82
|
+
export function entryReadResult(entry) {
|
|
83
|
+
return { contents: [{ uri: entry.uri, mimeType: entry.mimeType, text: entry.text }] };
|
|
84
|
+
}
|
|
79
85
|
export class ResourceStore {
|
|
86
|
+
// Not readonly: a call view (`forCall`) shares its store's entries.
|
|
80
87
|
#entries = new TtlLru({
|
|
81
88
|
maxEntries: MAX_ENTRIES,
|
|
82
89
|
maxWeight: MAX_TOTAL_BYTES,
|
|
83
90
|
ttlMs: ENTRY_TTL_MS,
|
|
84
91
|
});
|
|
85
92
|
#onListChanged;
|
|
86
|
-
|
|
93
|
+
#options;
|
|
94
|
+
/** A call view's own puts; `undefined` on the store itself. */
|
|
95
|
+
#putUris;
|
|
96
|
+
constructor(onListChanged, options = {}) {
|
|
87
97
|
this.#onListChanged = onListChanged;
|
|
98
|
+
this.#options = options;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* A view sharing this store's entries that also remembers the URIs put
|
|
102
|
+
* through it, so {@link discard} can take one tool call's entries back.
|
|
103
|
+
*/
|
|
104
|
+
forCall() {
|
|
105
|
+
const view = new ResourceStore(this.#onListChanged, this.#options);
|
|
106
|
+
view.#entries = this.#entries;
|
|
107
|
+
view.#putUris = [];
|
|
108
|
+
return view;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Drop what was put through this view and is still cached. The put already
|
|
112
|
+
* announced the list change (and may have pruned or evicted other entries),
|
|
113
|
+
* so taking the entry back is announced too.
|
|
114
|
+
*/
|
|
115
|
+
discard() {
|
|
116
|
+
let dropped = false;
|
|
117
|
+
for (const uri of this.#putUris?.splice(0) ?? []) {
|
|
118
|
+
if (this.#entries.delete(uri))
|
|
119
|
+
dropped = true;
|
|
120
|
+
}
|
|
121
|
+
if (dropped)
|
|
122
|
+
this.#onListChanged?.();
|
|
88
123
|
}
|
|
89
124
|
getEntry(uri) {
|
|
90
125
|
const entry = this.#entries.get(uri);
|
|
@@ -99,9 +134,7 @@ export class ResourceStore {
|
|
|
99
134
|
putText(params) {
|
|
100
135
|
const entryBytes = Buffer.byteLength(params.text, 'utf8');
|
|
101
136
|
if (entryBytes > MAX_ENTRY_BYTES) {
|
|
102
|
-
|
|
103
|
-
this.#onListChanged?.();
|
|
104
|
-
throw new FsError(ErrorCode.TOO_LARGE, `Resource too large to cache (${entryBytes} bytes).`);
|
|
137
|
+
throw this.#refuse(`Resource too large to cache (${entryBytes} bytes).`);
|
|
105
138
|
}
|
|
106
139
|
const now = Date.now();
|
|
107
140
|
const entry = {
|
|
@@ -113,10 +146,23 @@ export class ResourceStore {
|
|
|
113
146
|
expiresAt: new Date(now + ENTRY_TTL_MS).toISOString(),
|
|
114
147
|
text: params.text,
|
|
115
148
|
};
|
|
149
|
+
const { maxReadResultBytes } = this.#options;
|
|
150
|
+
const readBytes = maxReadResultBytes === undefined
|
|
151
|
+
? undefined
|
|
152
|
+
: jsonBytesOver(entryReadResult(entry), maxReadResultBytes);
|
|
153
|
+
if (readBytes !== undefined) {
|
|
154
|
+
throw this.#refuse(`Resource too large to read back (${readBytes} bytes once JSON-encoded).`);
|
|
155
|
+
}
|
|
116
156
|
this.#entries.set(entry.uri, entry, entryBytes);
|
|
157
|
+
this.#putUris?.push(entry.uri);
|
|
117
158
|
this.#onListChanged?.();
|
|
118
159
|
return entry;
|
|
119
160
|
}
|
|
161
|
+
#refuse(message) {
|
|
162
|
+
if (this.#entries.prune())
|
|
163
|
+
this.#onListChanged?.();
|
|
164
|
+
return new FsError(ErrorCode.TOO_LARGE, message);
|
|
165
|
+
}
|
|
120
166
|
/**
|
|
121
167
|
* Live URIs, oldest first. Deliberately silent about the prune it runs:
|
|
122
168
|
* this is the read path `resources/list` runs through, and the caller is
|
|
@@ -181,10 +227,28 @@ const MAX_SNAPSHOTS = 32;
|
|
|
181
227
|
* the way `ResourceStore` does.
|
|
182
228
|
*/
|
|
183
229
|
export class PageSnapshotStore {
|
|
230
|
+
// Not readonly: a call view (`forCall`) shares its store's snapshots.
|
|
184
231
|
#byId = new TtlLru({
|
|
185
232
|
maxEntries: MAX_SNAPSHOTS,
|
|
186
233
|
ttlMs: ENTRY_TTL_MS,
|
|
187
234
|
});
|
|
235
|
+
/** A call view's own snapshots; `undefined` on the store itself. */
|
|
236
|
+
#createdIds;
|
|
237
|
+
/**
|
|
238
|
+
* A view sharing this store's snapshots that also remembers the ones
|
|
239
|
+
* created through it, so {@link discard} can take one tool call's back.
|
|
240
|
+
*/
|
|
241
|
+
forCall() {
|
|
242
|
+
const view = new PageSnapshotStore();
|
|
243
|
+
view.#byId = this.#byId;
|
|
244
|
+
view.#createdIds = [];
|
|
245
|
+
return view;
|
|
246
|
+
}
|
|
247
|
+
/** Drop the snapshots created through this view. */
|
|
248
|
+
discard() {
|
|
249
|
+
for (const id of this.#createdIds?.splice(0) ?? [])
|
|
250
|
+
this.#byId.delete(id);
|
|
251
|
+
}
|
|
188
252
|
create(params) {
|
|
189
253
|
const snapshotId = randomUUID();
|
|
190
254
|
this.#byId.set(snapshotId, {
|
|
@@ -192,6 +256,7 @@ export class PageSnapshotStore {
|
|
|
192
256
|
items: params.items,
|
|
193
257
|
metadata: params.metadata,
|
|
194
258
|
});
|
|
259
|
+
this.#createdIds?.push(snapshotId);
|
|
195
260
|
return snapshotId;
|
|
196
261
|
}
|
|
197
262
|
read(snapshotId, queryKey) {
|