@livx.cc/agentx 0.99.32 → 0.99.34
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.js +170 -76
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +30 -2
- package/dist/index.js +174 -76
- package/dist/index.js.map +1 -1
- package/dist/tools.shell.js +191 -0
- package/dist/tools.shell.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -39,6 +39,12 @@ declare class NodeDiskFilesystem implements IFilesystem {
|
|
|
39
39
|
/** The REAL on-disk path behind a VFS path — an optional capability (not on IFilesystem) the Read
|
|
40
40
|
* tool duck-types so it can persist an image REFERENCE instead of megabytes of base64. */
|
|
41
41
|
realPath(path: string): string;
|
|
42
|
+
/** The CANONICAL on-disk path — symlinks resolved. `realPath` is path arithmetic (baseDir + vpath)
|
|
43
|
+
* and therefore reports a symlink and its target as different places, which is no use as an
|
|
44
|
+
* identity: a tree walk that dedupes on it still re-enters a loop (`a/b/loop -> a` re-walked 11
|
|
45
|
+
* times before a depth cap stopped it). Optional capability, duck-typed by the file-scanning
|
|
46
|
+
* tools; falls back to the arithmetic path for anything unresolvable (a broken/racing link). */
|
|
47
|
+
canonicalPath(path: string): string;
|
|
42
48
|
/** Read raw bytes (for binary files like images). Not on the base IFilesystem (which is utf8-only) — an
|
|
43
49
|
* optional capability the Read tool duck-types to return image blocks. Same symlink/jail guards as readFile. */
|
|
44
50
|
readFileBytes(path: string): Promise<Uint8Array>;
|
|
@@ -434,9 +440,31 @@ declare function fullAgentOptions(opts?: Partial<AgentOptions> & {
|
|
|
434
440
|
cwd?: string;
|
|
435
441
|
}): Promise<Partial<AgentOptions>>;
|
|
436
442
|
|
|
443
|
+
/**
|
|
444
|
+
* Directory names never worth walking. Pruned by NAME at every level (not by path), because the
|
|
445
|
+
* cost is where they nest: a real-FS root of a JS deployment is mostly `node_modules`, and on a
|
|
446
|
+
* measured 412k-file app root a single `Glob` spent 31s almost entirely inside it.
|
|
447
|
+
*/
|
|
448
|
+
declare const DEFAULT_SKIP_DIRS: string[];
|
|
449
|
+
/** Depth below a root at which the walk stops. Deep enough for any real source tree; a bound at all
|
|
450
|
+
* is what turns a symlink cycle from a hang into a truncated result. */
|
|
451
|
+
declare const DEFAULT_MAX_DEPTH = 24;
|
|
452
|
+
/** How the file-scanning tools (Glob/Grep) decide WHAT to walk. */
|
|
453
|
+
interface FileScanOptions {
|
|
454
|
+
/** Roots to walk when a call names no `path`. Default: `[fs.getCwd()]`, i.e. today's behaviour.
|
|
455
|
+
* A host with its data outside the cwd passes both, so a search can reach it at all. */
|
|
456
|
+
roots?: string[];
|
|
457
|
+
/** Directory names pruned during the walk. Default: {@link DEFAULT_SKIP_DIRS}. */
|
|
458
|
+
skipDirs?: string[];
|
|
459
|
+
/** Max depth below each root. Default: {@link DEFAULT_MAX_DEPTH}. */
|
|
460
|
+
maxDepth?: number;
|
|
461
|
+
}
|
|
437
462
|
/** List paths matching a glob, sorted — the structured alternative to `find`. */
|
|
438
|
-
declare
|
|
463
|
+
declare function makeGlobTool(opts?: FileScanOptions): AgentTool;
|
|
439
464
|
/** Search file contents by regex, returning typed `path:line:text` hits with optional context. */
|
|
465
|
+
declare function makeGrepTool(opts?: FileScanOptions): AgentTool;
|
|
466
|
+
/** Default instances — cwd-rooted, standard prunes. Hosts that need other roots build their own. */
|
|
467
|
+
declare const globTool: AgentTool;
|
|
440
468
|
declare const grepTool: AgentTool;
|
|
441
469
|
/**
|
|
442
470
|
* Compact map of a VFS — code signatures and/or doc outlines. Edge-safe (pure IFilesystem walk).
|
|
@@ -1285,4 +1313,4 @@ declare function exitWorktree(): {
|
|
|
1285
1313
|
reason?: string;
|
|
1286
1314
|
} | null;
|
|
1287
1315
|
|
|
1288
|
-
export { Agent, type AgentDef, AgentOptions, AgentTool, type AskOptions, type Attempt, BodDbFilesystem, ChatLike, ChatOptions, ChatResponse, type CommandInfo, ConsoleHostBridge, DEFAULT_DENY, DuplexAgent, DuplexAgentOptions, type DuplexTaskStatus, FakeAIClient, Hooks, HostBridge, JailOptions, JailedFilesystem, type LessonOptions, LessonOptionsDefaults, type LoadMemoryOpts, MEMORY_PROMPT, MessageContent, type Mount, MountFilesystem, NodeDiskFilesystem, OverlayFilesystem, type ReflectOptions, RunResult, SCRATCH_DIR, type ScheduledJob, type ScheduledJobSnapshot, Scheduler, type SchedulerOptions, Scratch, type ScratchOptions, ScriptedHostBridge, type SiblingResolver, type SkillInfo, type TaskRecord, type TaskToolOptions, ToolCall, type ToolSpec, type Trigger, type TriggerCron, type TriggerInterval, type TriggerOneOff, UserQuestion, VOICE_MEMORY_PROMPT, VOICE_SYSTEM_PROMPT, type WebFetchOptions, type WebSearchOptions, type WorkerTier, type WorktreeInfo, type WorktreeSession, applyEditsTool, askUserQuestionTool, checkpointTool, checkpointTools, cleanupWorktree, compileSynthesizedTool, cronMatches, decodeDdgUrl, diskAgentOptions, enterWorktree, exitWorktree, expandCommand, expandEntry, expandTemplate, findGitRoot, forComponent, fullAgentOptions, getOrCreateWorktree, getWorktreeSession, globTool, grepTool, htmlToText, idfWeights, lessonCapture, loadAgents, loadCommands, loadInstructions, loadMemory, loadSkills, makeAskTool, makeScheduleTools, makeTaskBatchTool, makeTaskTool, makeWebFetchTool, makeWebSearchAnthropicTool, makeWebSearchTool, mkdirp, multiEditTool, nextCronAfter, parseCron, parseDdgHtml, raceAttempts, reflectOnRun, relevanceScore, repoIndex, repoMapTool, rollbackTool, sandboxAgentOptions, scanCommands, scanSkills, slugify, tokenize, toolCall, topByRelevance, validateToolCode, validateWorktreeSlug, webFetchTool, webSearchAnthropicTool, webSearchTool, worktreeBranchName, writeFact, writeTool };
|
|
1316
|
+
export { Agent, type AgentDef, AgentOptions, AgentTool, type AskOptions, type Attempt, BodDbFilesystem, ChatLike, ChatOptions, ChatResponse, type CommandInfo, ConsoleHostBridge, DEFAULT_DENY, DEFAULT_MAX_DEPTH, DEFAULT_SKIP_DIRS, DuplexAgent, DuplexAgentOptions, type DuplexTaskStatus, FakeAIClient, type FileScanOptions, Hooks, HostBridge, JailOptions, JailedFilesystem, type LessonOptions, LessonOptionsDefaults, type LoadMemoryOpts, MEMORY_PROMPT, MessageContent, type Mount, MountFilesystem, NodeDiskFilesystem, OverlayFilesystem, type ReflectOptions, RunResult, SCRATCH_DIR, type ScheduledJob, type ScheduledJobSnapshot, Scheduler, type SchedulerOptions, Scratch, type ScratchOptions, ScriptedHostBridge, type SiblingResolver, type SkillInfo, type TaskRecord, type TaskToolOptions, ToolCall, type ToolSpec, type Trigger, type TriggerCron, type TriggerInterval, type TriggerOneOff, UserQuestion, VOICE_MEMORY_PROMPT, VOICE_SYSTEM_PROMPT, type WebFetchOptions, type WebSearchOptions, type WorkerTier, type WorktreeInfo, type WorktreeSession, applyEditsTool, askUserQuestionTool, checkpointTool, checkpointTools, cleanupWorktree, compileSynthesizedTool, cronMatches, decodeDdgUrl, diskAgentOptions, enterWorktree, exitWorktree, expandCommand, expandEntry, expandTemplate, findGitRoot, forComponent, fullAgentOptions, getOrCreateWorktree, getWorktreeSession, globTool, grepTool, htmlToText, idfWeights, lessonCapture, loadAgents, loadCommands, loadInstructions, loadMemory, loadSkills, makeAskTool, makeGlobTool, makeGrepTool, makeScheduleTools, makeTaskBatchTool, makeTaskTool, makeWebFetchTool, makeWebSearchAnthropicTool, makeWebSearchTool, mkdirp, multiEditTool, nextCronAfter, parseCron, parseDdgHtml, raceAttempts, reflectOnRun, relevanceScore, repoIndex, repoMapTool, rollbackTool, sandboxAgentOptions, scanCommands, scanSkills, slugify, tokenize, toolCall, topByRelevance, validateToolCode, validateWorktreeSlug, webFetchTool, webSearchAnthropicTool, webSearchTool, worktreeBranchName, writeFact, writeTool };
|
package/dist/index.js
CHANGED
|
@@ -304,7 +304,24 @@ var init_redact = __esm({
|
|
|
304
304
|
function ckAbort(signal) {
|
|
305
305
|
if (signal?.aborted) throw new Error("aborted");
|
|
306
306
|
}
|
|
307
|
-
|
|
307
|
+
function realOf(fs, p) {
|
|
308
|
+
const rp = fs.canonicalPath;
|
|
309
|
+
if (typeof rp !== "function") return p;
|
|
310
|
+
try {
|
|
311
|
+
return rp.call(fs, p) || p;
|
|
312
|
+
} catch {
|
|
313
|
+
return p;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
async function walkFiles(fs, dir, signal, out = [], st, depth = 0) {
|
|
317
|
+
const state = st ?? { skip: new Set(DEFAULT_SKIP_DIRS), maxDepth: DEFAULT_MAX_DEPTH, seen: /* @__PURE__ */ new Set(), prunedDirs: /* @__PURE__ */ new Set(), depthCuts: 0 };
|
|
318
|
+
if (depth > state.maxDepth) {
|
|
319
|
+
state.depthCuts++;
|
|
320
|
+
return out;
|
|
321
|
+
}
|
|
322
|
+
const real = realOf(fs, dir);
|
|
323
|
+
if (state.seen.has(real)) return out;
|
|
324
|
+
state.seen.add(real);
|
|
308
325
|
let entries;
|
|
309
326
|
try {
|
|
310
327
|
entries = await fs.readDir(dir);
|
|
@@ -313,12 +330,57 @@ async function walkFiles(fs, dir, signal, out = []) {
|
|
|
313
330
|
}
|
|
314
331
|
for (const name of entries.sort()) {
|
|
315
332
|
ckAbort(signal);
|
|
333
|
+
if (state.skip.has(name)) {
|
|
334
|
+
state.prunedDirs.add(name);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
316
337
|
const p = dir === "/" ? `/${name}` : `${dir}/${name}`;
|
|
317
|
-
if (await fs.isDirectory(p)) await walkFiles(fs, p, signal, out);
|
|
338
|
+
if (await fs.isDirectory(p)) await walkFiles(fs, p, signal, out, state, depth + 1);
|
|
318
339
|
else out.push(p);
|
|
319
340
|
}
|
|
320
341
|
return out;
|
|
321
342
|
}
|
|
343
|
+
function scanRoots(fs, path, opts) {
|
|
344
|
+
const asked = path == null ? [] : (Array.isArray(path) ? path : [path]).map((p) => String(p)).filter(Boolean);
|
|
345
|
+
const roots = (asked.length ? asked : opts?.roots?.length ? opts.roots : [fsCwd(fs)]).map((r) => r.length > 1 && r.endsWith("/") ? r.slice(0, -1) : r);
|
|
346
|
+
return [...new Set(roots)].filter((r, _i, all) => !all.some((other) => other !== r && r.startsWith(`${other}/`)));
|
|
347
|
+
}
|
|
348
|
+
function scanNote(states) {
|
|
349
|
+
const pruned = [...new Set(states.flatMap((st) => [...st.prunedDirs]))].sort();
|
|
350
|
+
const cuts = states.reduce((n, st) => n + st.depthCuts, 0);
|
|
351
|
+
const parts = [];
|
|
352
|
+
if (pruned.length) parts.push(`skipped ${pruned.join(", ")} (pass skipDirs to include them)`);
|
|
353
|
+
if (cuts) parts.push(`stopped at depth ${states[0]?.maxDepth} in ${cuts} place${cuts === 1 ? "" : "s"} (pass maxDepth to go deeper)`);
|
|
354
|
+
return parts.length ? `
|
|
355
|
+
[scan: ${parts.join("; ")}]` : "";
|
|
356
|
+
}
|
|
357
|
+
async function walkRoots(fs, roots, signal, opts, call) {
|
|
358
|
+
const skip = new Set(call?.skipDirs ?? opts?.skipDirs ?? DEFAULT_SKIP_DIRS);
|
|
359
|
+
const maxDepth = call?.maxDepth ?? opts?.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
360
|
+
const out = [];
|
|
361
|
+
const states = [];
|
|
362
|
+
for (const root of roots) {
|
|
363
|
+
const state = { skip, maxDepth, seen: /* @__PURE__ */ new Set(), prunedDirs: /* @__PURE__ */ new Set(), depthCuts: 0 };
|
|
364
|
+
states.push(state);
|
|
365
|
+
await walkFiles(fs, root, signal, out, state, 0);
|
|
366
|
+
}
|
|
367
|
+
return { files: out, note: scanNote(states) };
|
|
368
|
+
}
|
|
369
|
+
function callScan(args) {
|
|
370
|
+
const sd = args.skipDirs;
|
|
371
|
+
return {
|
|
372
|
+
skipDirs: Array.isArray(sd) ? sd.map(String) : typeof sd === "string" ? sd.split(",").map((x) => x.trim()).filter(Boolean) : void 0,
|
|
373
|
+
maxDepth: args.maxDepth == null ? void 0 : Number(args.maxDepth)
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function anchoredGlobAt(root, glob) {
|
|
377
|
+
if (glob.startsWith("/")) return globToRegExp(glob);
|
|
378
|
+
return globToRegExp(`${root === "/" ? "" : root}/${glob}`);
|
|
379
|
+
}
|
|
380
|
+
function globMatcher(roots, glob) {
|
|
381
|
+
const res = roots.map((r) => anchoredGlobAt(r, glob));
|
|
382
|
+
return (p) => res.some((re) => re.test(p));
|
|
383
|
+
}
|
|
322
384
|
function globToRegExp(glob, caseInsensitive = false) {
|
|
323
385
|
const g = glob.startsWith("/") ? glob : `/${glob}`;
|
|
324
386
|
let re = "";
|
|
@@ -340,6 +402,88 @@ function anchoredGlob(fs, glob) {
|
|
|
340
402
|
const base = cwd === "/" ? "" : cwd;
|
|
341
403
|
return globToRegExp(glob.startsWith("/") ? glob : `${base}/${glob}`);
|
|
342
404
|
}
|
|
405
|
+
function makeGlobTool(opts) {
|
|
406
|
+
return {
|
|
407
|
+
name: "Glob",
|
|
408
|
+
description: 'Find files by glob pattern (e.g. "**/*.ts", "src/**/*.test.ts"). Returns sorted paths, one per line. Space-separated patterns combine; `!`-prefix excludes (e.g. "**/*.ts !**/*.test.ts"). Optional `path` to search a specific directory. Prefer over `bash find` for locating files \u2014 one call, structured output.',
|
|
409
|
+
parameters: {
|
|
410
|
+
type: "object",
|
|
411
|
+
required: ["pattern"],
|
|
412
|
+
properties: {
|
|
413
|
+
pattern: { type: "string", description: "glob pattern(s); ** matches across directories; prefix a pattern with ! to exclude" },
|
|
414
|
+
...SCAN_PARAMS
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
async run({ pattern, path, skipDirs, maxDepth }, ctx) {
|
|
418
|
+
const roots = scanRoots(ctx.fs, path, opts);
|
|
419
|
+
const pats = String(pattern ?? "").trim().split(/\s+/).filter(Boolean);
|
|
420
|
+
const include = pats.filter((p) => !p.startsWith("!")).map((p) => globMatcher(roots, p));
|
|
421
|
+
const exclude = pats.filter((p) => p.startsWith("!")).map((p) => globMatcher(roots, p.slice(1)));
|
|
422
|
+
const includes = include.length ? include : [globMatcher(roots, "**")];
|
|
423
|
+
const { files, note } = await walkRoots(ctx.fs, roots, ctx.signal, opts, callScan({ skipDirs, maxDepth }));
|
|
424
|
+
const hits = files.filter((p) => includes.some((m) => m(p)) && !exclude.some((m) => m(p)));
|
|
425
|
+
return (hits.length ? [...new Set(hits)].sort().join("\n") : "(no matches)") + note;
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
function makeGrepTool(opts) {
|
|
430
|
+
return {
|
|
431
|
+
name: "Grep",
|
|
432
|
+
description: "Search file contents by regex. Returns `path:line: text` hits. Optional `glob` to scope files, `path` to search a specific directory, `context` for surrounding lines, `filesOnly` for matching paths only. Prefer over `bash grep` for file content search \u2014 structured results, no re-parse needed. Use `bash` instead for running commands, tests, or piped workflows.",
|
|
433
|
+
parameters: {
|
|
434
|
+
type: "object",
|
|
435
|
+
required: ["pattern"],
|
|
436
|
+
properties: {
|
|
437
|
+
pattern: { type: "string", description: "JS regular expression" },
|
|
438
|
+
glob: { type: "string", description: "optional file glob to restrict the search" },
|
|
439
|
+
...SCAN_PARAMS,
|
|
440
|
+
context: { type: "number", description: "lines of context before/after each hit" },
|
|
441
|
+
filesOnly: { type: "boolean", description: "only list matching file paths" }
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
async run({ pattern, glob, path, skipDirs, maxDepth, context, filesOnly }, ctx) {
|
|
445
|
+
let re;
|
|
446
|
+
try {
|
|
447
|
+
re = new RegExp(String(pattern ?? ""));
|
|
448
|
+
} catch (e) {
|
|
449
|
+
throw new Error(`invalid regex: ${String(e)}`);
|
|
450
|
+
}
|
|
451
|
+
const roots = scanRoots(ctx.fs, path, opts);
|
|
452
|
+
const scope = glob ? globMatcher(roots, String(glob)) : null;
|
|
453
|
+
const walked = await walkRoots(ctx.fs, roots, ctx.signal, opts, callScan({ skipDirs, maxDepth }));
|
|
454
|
+
const files = [...new Set(walked.files)].sort().filter((p) => !scope || scope(p));
|
|
455
|
+
const ctxN = Math.max(0, Number(context ?? 0));
|
|
456
|
+
const out = [];
|
|
457
|
+
const matched = [];
|
|
458
|
+
let skipped = 0;
|
|
459
|
+
for (const path2 of files) {
|
|
460
|
+
ckAbort(ctx.signal);
|
|
461
|
+
let content;
|
|
462
|
+
try {
|
|
463
|
+
content = await ctx.fs.readFile(path2);
|
|
464
|
+
} catch {
|
|
465
|
+
skipped++;
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const lines = content.split("\n");
|
|
469
|
+
const mask = CONFIG_FILE_RE.test(path2);
|
|
470
|
+
let fileHit = false;
|
|
471
|
+
for (let i = 0; i < lines.length; i++) {
|
|
472
|
+
if (!re.test(lines[i])) continue;
|
|
473
|
+
fileHit = true;
|
|
474
|
+
if (filesOnly) break;
|
|
475
|
+
const lo = Math.max(0, i - ctxN), hi = Math.min(lines.length - 1, i + ctxN);
|
|
476
|
+
for (let j = lo; j <= hi; j++) out.push(`${path2}:${j + 1}: ${mask ? redactSecrets(lines[j]) : lines[j]}`);
|
|
477
|
+
}
|
|
478
|
+
if (fileHit) matched.push(path2);
|
|
479
|
+
}
|
|
480
|
+
const note = (skipped ? `
|
|
481
|
+
[skipped ${skipped} unreadable file${skipped === 1 ? "" : "s"}]` : "") + walked.note;
|
|
482
|
+
if (filesOnly) return (matched.length ? matched.join("\n") : "(no matches)") + note;
|
|
483
|
+
return (out.length ? out.join("\n") : "(no matches)") + note;
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
}
|
|
343
487
|
function docOutlineOf(content, cap = 20) {
|
|
344
488
|
const out = [];
|
|
345
489
|
const lines = content.split("\n");
|
|
@@ -482,84 +626,21 @@ Reply with a short numbered list of concrete, actionable issues. If the changes
|
|
|
482
626
|
}
|
|
483
627
|
};
|
|
484
628
|
}
|
|
485
|
-
var fsCwd, globTool, grepTool, SIG_RE, isCode, isDoc, repoMapTool, writeTool, multiEditTool, applyEditsTool;
|
|
629
|
+
var DEFAULT_SKIP_DIRS, DEFAULT_MAX_DEPTH, SCAN_PARAMS, fsCwd, globTool, grepTool, SIG_RE, isCode, isDoc, repoMapTool, writeTool, multiEditTool, applyEditsTool;
|
|
486
630
|
var init_tools_structured = __esm({
|
|
487
631
|
"src/tools.structured.ts"() {
|
|
488
632
|
"use strict";
|
|
489
633
|
init_redact();
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
required: ["pattern"],
|
|
497
|
-
properties: { pattern: { type: "string", description: "glob pattern(s); ** matches across directories; prefix a pattern with ! to exclude" } }
|
|
498
|
-
},
|
|
499
|
-
async run({ pattern }, ctx) {
|
|
500
|
-
const pats = String(pattern ?? "").trim().split(/\s+/).filter(Boolean);
|
|
501
|
-
const include = pats.filter((p) => !p.startsWith("!")).map((p) => anchoredGlob(ctx.fs, p));
|
|
502
|
-
const exclude = pats.filter((p) => p.startsWith("!")).map((p) => anchoredGlob(ctx.fs, p.slice(1)));
|
|
503
|
-
const includes = include.length ? include : [anchoredGlob(ctx.fs, "**")];
|
|
504
|
-
const hits = (await walkFiles(ctx.fs, fsCwd(ctx.fs), ctx.signal)).filter(
|
|
505
|
-
(p) => includes.some((re) => re.test(p)) && !exclude.some((re) => re.test(p))
|
|
506
|
-
);
|
|
507
|
-
return hits.length ? hits.join("\n") : "(no matches)";
|
|
508
|
-
}
|
|
509
|
-
};
|
|
510
|
-
grepTool = {
|
|
511
|
-
name: "Grep",
|
|
512
|
-
description: "Search file contents by regex. Returns `path:line: text` hits. Optional `glob` to scope files, `context` for surrounding lines, `filesOnly` for matching paths only. Prefer over `bash grep` for file content search \u2014 structured results, no re-parse needed. Use `bash` instead for running commands, tests, or piped workflows.",
|
|
513
|
-
parameters: {
|
|
514
|
-
type: "object",
|
|
515
|
-
required: ["pattern"],
|
|
516
|
-
properties: {
|
|
517
|
-
pattern: { type: "string", description: "JS regular expression" },
|
|
518
|
-
glob: { type: "string", description: "optional file glob to restrict the search" },
|
|
519
|
-
context: { type: "number", description: "lines of context before/after each hit" },
|
|
520
|
-
filesOnly: { type: "boolean", description: "only list matching file paths" }
|
|
521
|
-
}
|
|
522
|
-
},
|
|
523
|
-
async run({ pattern, glob, context, filesOnly }, ctx) {
|
|
524
|
-
let re;
|
|
525
|
-
try {
|
|
526
|
-
re = new RegExp(String(pattern ?? ""));
|
|
527
|
-
} catch (e) {
|
|
528
|
-
throw new Error(`invalid regex: ${String(e)}`);
|
|
529
|
-
}
|
|
530
|
-
const scope = glob ? anchoredGlob(ctx.fs, String(glob)) : null;
|
|
531
|
-
const files = (await walkFiles(ctx.fs, fsCwd(ctx.fs), ctx.signal)).filter((p) => !scope || scope.test(p));
|
|
532
|
-
const ctxN = Math.max(0, Number(context ?? 0));
|
|
533
|
-
const out = [];
|
|
534
|
-
const matched = [];
|
|
535
|
-
let skipped = 0;
|
|
536
|
-
for (const path of files) {
|
|
537
|
-
ckAbort(ctx.signal);
|
|
538
|
-
let content;
|
|
539
|
-
try {
|
|
540
|
-
content = await ctx.fs.readFile(path);
|
|
541
|
-
} catch {
|
|
542
|
-
skipped++;
|
|
543
|
-
continue;
|
|
544
|
-
}
|
|
545
|
-
const lines = content.split("\n");
|
|
546
|
-
const mask = CONFIG_FILE_RE.test(path);
|
|
547
|
-
let fileHit = false;
|
|
548
|
-
for (let i = 0; i < lines.length; i++) {
|
|
549
|
-
if (!re.test(lines[i])) continue;
|
|
550
|
-
fileHit = true;
|
|
551
|
-
if (filesOnly) break;
|
|
552
|
-
const lo = Math.max(0, i - ctxN), hi = Math.min(lines.length - 1, i + ctxN);
|
|
553
|
-
for (let j = lo; j <= hi; j++) out.push(`${path}:${j + 1}: ${mask ? redactSecrets(lines[j]) : lines[j]}`);
|
|
554
|
-
}
|
|
555
|
-
if (fileHit) matched.push(path);
|
|
556
|
-
}
|
|
557
|
-
const note = skipped ? `
|
|
558
|
-
[skipped ${skipped} unreadable file${skipped === 1 ? "" : "s"}]` : "";
|
|
559
|
-
if (filesOnly) return (matched.length ? matched.join("\n") : "(no matches)") + note;
|
|
560
|
-
return (out.length ? out.join("\n") : "(no matches)") + note;
|
|
561
|
-
}
|
|
634
|
+
DEFAULT_SKIP_DIRS = ["node_modules", ".git", "dist", ".tmp"];
|
|
635
|
+
DEFAULT_MAX_DEPTH = 24;
|
|
636
|
+
SCAN_PARAMS = {
|
|
637
|
+
path: { type: "string", description: "optional directory (or list) to search instead of the default roots; absolute VFS path" },
|
|
638
|
+
skipDirs: { type: "string", description: `comma-separated directory names to prune (default: ${DEFAULT_SKIP_DIRS.join(", ")}); pass an empty string to prune nothing` },
|
|
639
|
+
maxDepth: { type: "number", description: `max directory depth below each root (default ${DEFAULT_MAX_DEPTH})` }
|
|
562
640
|
};
|
|
641
|
+
fsCwd = (fs) => fs.getCwd();
|
|
642
|
+
globTool = makeGlobTool();
|
|
643
|
+
grepTool = makeGrepTool();
|
|
563
644
|
SIG_RE = /^\s*(export\b|(?:export\s+)?(?:async\s+)?function\s+\*?\w|(?:export\s+)?(?:abstract\s+)?class\s+\w|(?:export\s+)?interface\s+\w|(?:export\s+)?type\s+\w|(?:export\s+)?enum\s+\w)/;
|
|
564
645
|
isCode = (p) => /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(p);
|
|
565
646
|
isDoc = (p) => /\.(md|mdx|txt)$/.test(p);
|
|
@@ -1423,7 +1504,7 @@ var NodeDiskFilesystem_exports = {};
|
|
|
1423
1504
|
__export(NodeDiskFilesystem_exports, {
|
|
1424
1505
|
NodeDiskFilesystem: () => NodeDiskFilesystem
|
|
1425
1506
|
});
|
|
1426
|
-
import { promises as fsp } from "fs";
|
|
1507
|
+
import { promises as fsp, realpathSync as realpathSync2 } from "fs";
|
|
1427
1508
|
import * as np from "path";
|
|
1428
1509
|
import { PathResolver } from "@livx.cc/wcli/core";
|
|
1429
1510
|
var NodeDiskFilesystem;
|
|
@@ -1502,6 +1583,19 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
1502
1583
|
realPath(path) {
|
|
1503
1584
|
return this.real(this.resolvePath(path));
|
|
1504
1585
|
}
|
|
1586
|
+
/** The CANONICAL on-disk path — symlinks resolved. `realPath` is path arithmetic (baseDir + vpath)
|
|
1587
|
+
* and therefore reports a symlink and its target as different places, which is no use as an
|
|
1588
|
+
* identity: a tree walk that dedupes on it still re-enters a loop (`a/b/loop -> a` re-walked 11
|
|
1589
|
+
* times before a depth cap stopped it). Optional capability, duck-typed by the file-scanning
|
|
1590
|
+
* tools; falls back to the arithmetic path for anything unresolvable (a broken/racing link). */
|
|
1591
|
+
canonicalPath(path) {
|
|
1592
|
+
const p = this.realPath(path);
|
|
1593
|
+
try {
|
|
1594
|
+
return realpathSync2(p);
|
|
1595
|
+
} catch {
|
|
1596
|
+
return p;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1505
1599
|
/** Read raw bytes (for binary files like images). Not on the base IFilesystem (which is utf8-only) — an
|
|
1506
1600
|
* optional capability the Read tool duck-types to return image blocks. Same symlink/jail guards as readFile. */
|
|
1507
1601
|
async readFileBytes(path) {
|
|
@@ -8401,7 +8495,9 @@ export {
|
|
|
8401
8495
|
CommandExecutor2 as CommandExecutor,
|
|
8402
8496
|
ConsoleHostBridge,
|
|
8403
8497
|
DEFAULT_DENY,
|
|
8498
|
+
DEFAULT_MAX_DEPTH,
|
|
8404
8499
|
DEFAULT_MUTATING,
|
|
8500
|
+
DEFAULT_SKIP_DIRS,
|
|
8405
8501
|
DuplexAgent,
|
|
8406
8502
|
DuplexAgentOptions,
|
|
8407
8503
|
FakeAIClient,
|
|
@@ -8483,6 +8579,8 @@ export {
|
|
|
8483
8579
|
log,
|
|
8484
8580
|
makeAskTool,
|
|
8485
8581
|
makeContext,
|
|
8582
|
+
makeGlobTool,
|
|
8583
|
+
makeGrepTool,
|
|
8486
8584
|
makeJobTools,
|
|
8487
8585
|
makeLazyMcpToolSearch,
|
|
8488
8586
|
makeMcpToolSearch,
|