@livx.cc/agentx 0.99.32 → 0.99.33

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/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 const globTool: AgentTool;
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
- async function walkFiles(fs, dir, signal, out = []) {
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
- fsCwd = (fs) => fs.getCwd();
491
- globTool = {
492
- name: "Glob",
493
- 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"). Prefer over `bash find` for locating files \u2014 one call, structured output.',
494
- parameters: {
495
- type: "object",
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,