@visorcraft/idlehands 1.1.17 → 1.2.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/agent/formatting.js +30 -13
- package/dist/agent/formatting.js.map +1 -1
- package/dist/agent/review-artifact.js +12 -8
- package/dist/agent/review-artifact.js.map +1 -1
- package/dist/agent/tool-calls.js +57 -20
- package/dist/agent/tool-calls.js.map +1 -1
- package/dist/agent/tool-loop-detection.js +310 -0
- package/dist/agent/tool-loop-detection.js.map +1 -0
- package/dist/agent/tool-loop-guard.js +251 -0
- package/dist/agent/tool-loop-guard.js.map +1 -0
- package/dist/agent.js +460 -144
- package/dist/agent.js.map +1 -1
- package/dist/anton/controller.js +46 -30
- package/dist/anton/controller.js.map +1 -1
- package/dist/anton/lock.js +5 -1
- package/dist/anton/lock.js.map +1 -1
- package/dist/anton/parser.js +18 -19
- package/dist/anton/parser.js.map +1 -1
- package/dist/anton/prompt.js +42 -11
- package/dist/anton/prompt.js.map +1 -1
- package/dist/anton/reporter.js.map +1 -1
- package/dist/anton/session.js.map +1 -1
- package/dist/anton/verifier.js +3 -5
- package/dist/anton/verifier.js.map +1 -1
- package/dist/bench/compare.js +53 -20
- package/dist/bench/compare.js.map +1 -1
- package/dist/bench/openclaw.js +4 -4
- package/dist/bench/openclaw.js.map +1 -1
- package/dist/bench/report.js +11 -3
- package/dist/bench/report.js.map +1 -1
- package/dist/bench/runner.js +20 -14
- package/dist/bench/runner.js.map +1 -1
- package/dist/bot/commands.js +65 -31
- package/dist/bot/commands.js.map +1 -1
- package/dist/bot/confirm-discord.js +32 -9
- package/dist/bot/confirm-discord.js.map +1 -1
- package/dist/bot/confirm-telegram.js +26 -10
- package/dist/bot/confirm-telegram.js.map +1 -1
- package/dist/bot/dir-guard.js +18 -3
- package/dist/bot/dir-guard.js.map +1 -1
- package/dist/bot/discord-routing.js +28 -4
- package/dist/bot/discord-routing.js.map +1 -1
- package/dist/bot/discord-streaming.js +3 -3
- package/dist/bot/discord-streaming.js.map +1 -1
- package/dist/bot/discord.js +82 -37
- package/dist/bot/discord.js.map +1 -1
- package/dist/bot/escalation.js +124 -0
- package/dist/bot/escalation.js.map +1 -0
- package/dist/bot/format.js +2 -5
- package/dist/bot/format.js.map +1 -1
- package/dist/bot/session-manager.js +17 -6
- package/dist/bot/session-manager.js.map +1 -1
- package/dist/bot/telegram.js +88 -28
- package/dist/bot/telegram.js.map +1 -1
- package/dist/cli/agent-turn.js +10 -4
- package/dist/cli/agent-turn.js.map +1 -1
- package/dist/cli/args.js +51 -9
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/bot.js +19 -9
- package/dist/cli/bot.js.map +1 -1
- package/dist/cli/build-repl-context.js +60 -26
- package/dist/cli/build-repl-context.js.map +1 -1
- package/dist/cli/command-registry.js.map +1 -1
- package/dist/cli/commands/anton.js +5 -3
- package/dist/cli/commands/anton.js.map +1 -1
- package/dist/cli/commands/editing.js +27 -12
- package/dist/cli/commands/editing.js.map +1 -1
- package/dist/cli/commands/model.js +16 -7
- package/dist/cli/commands/model.js.map +1 -1
- package/dist/cli/commands/project.js +52 -17
- package/dist/cli/commands/project.js.map +1 -1
- package/dist/cli/commands/runtime.js +1 -1
- package/dist/cli/commands/runtime.js.map +1 -1
- package/dist/cli/commands/secrets.js +279 -0
- package/dist/cli/commands/secrets.js.map +1 -0
- package/dist/cli/commands/session.js +49 -1
- package/dist/cli/commands/session.js.map +1 -1
- package/dist/cli/commands/tools.js +3 -1
- package/dist/cli/commands/tools.js.map +1 -1
- package/dist/cli/commands/trifecta.js +1 -1
- package/dist/cli/commands/trifecta.js.map +1 -1
- package/dist/cli/commands/tui.js.map +1 -1
- package/dist/cli/init.js +50 -16
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/input.js +25 -7
- package/dist/cli/input.js.map +1 -1
- package/dist/cli/oneshot.js +31 -19
- package/dist/cli/oneshot.js.map +1 -1
- package/dist/cli/repl-dispatch.js +10 -6
- package/dist/cli/repl-dispatch.js.map +1 -1
- package/dist/cli/runtime-cmds.js +110 -46
- package/dist/cli/runtime-cmds.js.map +1 -1
- package/dist/cli/service.js +3 -3
- package/dist/cli/service.js.map +1 -1
- package/dist/cli/session-state.js +12 -5
- package/dist/cli/session-state.js.map +1 -1
- package/dist/cli/setup.js +86 -33
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/shell.js +4 -4
- package/dist/cli/shell.js.map +1 -1
- package/dist/cli/status.js +56 -12
- package/dist/cli/status.js.map +1 -1
- package/dist/client.js +40 -21
- package/dist/client.js.map +1 -1
- package/dist/commands.js +1 -1
- package/dist/commands.js.map +1 -1
- package/dist/config.js +171 -15
- package/dist/config.js.map +1 -1
- package/dist/confirm/auto.js.map +1 -1
- package/dist/confirm/headless.js +13 -2
- package/dist/confirm/headless.js.map +1 -1
- package/dist/confirm/terminal.js +1 -5
- package/dist/confirm/terminal.js.map +1 -1
- package/dist/context.js +9 -3
- package/dist/context.js.map +1 -1
- package/dist/git.js +56 -61
- package/dist/git.js.map +1 -1
- package/dist/harnesses.js +137 -37
- package/dist/harnesses.js.map +1 -1
- package/dist/history.js +12 -4
- package/dist/history.js.map +1 -1
- package/dist/hooks/index.js +2 -2
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/loader.js +6 -5
- package/dist/hooks/loader.js.map +1 -1
- package/dist/hooks/manager.js.map +1 -1
- package/dist/hooks/plugins/example-console.js.map +1 -1
- package/dist/hooks/scaffold.js +8 -6
- package/dist/hooks/scaffold.js.map +1 -1
- package/dist/index.js +120 -66
- package/dist/index.js.map +1 -1
- package/dist/indexer.js +6 -18
- package/dist/indexer.js.map +1 -1
- package/dist/jsonrpc.js.map +1 -1
- package/dist/lens.js +38 -16
- package/dist/lens.js.map +1 -1
- package/dist/lsp.js +60 -24
- package/dist/lsp.js.map +1 -1
- package/dist/markdown.js +6 -6
- package/dist/markdown.js.map +1 -1
- package/dist/mcp.js +15 -6
- package/dist/mcp.js.map +1 -1
- package/dist/model-customization.js +7 -3
- package/dist/model-customization.js.map +1 -1
- package/dist/progress/message-edit-scheduler.js +15 -3
- package/dist/progress/message-edit-scheduler.js.map +1 -1
- package/dist/progress/progress-message-renderer.js.map +1 -1
- package/dist/progress/progress-presenter.js +3 -3
- package/dist/progress/progress-presenter.js.map +1 -1
- package/dist/progress/serialize-telegram.js.map +1 -1
- package/dist/progress/tool-summary.js +3 -1
- package/dist/progress/tool-summary.js.map +1 -1
- package/dist/progress/turn-progress.js +3 -1
- package/dist/progress/turn-progress.js.map +1 -1
- package/dist/recovery.js +11 -3
- package/dist/recovery.js.map +1 -1
- package/dist/replay.js +9 -3
- package/dist/replay.js.map +1 -1
- package/dist/replay_cli.js +5 -3
- package/dist/replay_cli.js.map +1 -1
- package/dist/runtime/executor.js +66 -20
- package/dist/runtime/executor.js.map +1 -1
- package/dist/runtime/health.js.map +1 -1
- package/dist/runtime/host-runner.js +103 -0
- package/dist/runtime/host-runner.js.map +1 -0
- package/dist/runtime/planner.js +3 -1
- package/dist/runtime/planner.js.map +1 -1
- package/dist/runtime/secrets.js +102 -0
- package/dist/runtime/secrets.js.map +1 -0
- package/dist/runtime/store.js +95 -19
- package/dist/runtime/store.js.map +1 -1
- package/dist/safety.js +38 -21
- package/dist/safety.js.map +1 -1
- package/dist/spinner.js +7 -8
- package/dist/spinner.js.map +1 -1
- package/dist/sys/context.js +3 -3
- package/dist/sys/context.js.map +1 -1
- package/dist/term.js +1 -1
- package/dist/term.js.map +1 -1
- package/dist/themes.js +11 -5
- package/dist/themes.js.map +1 -1
- package/dist/tools/tool-error.js +2 -5
- package/dist/tools/tool-error.js.map +1 -1
- package/dist/tools.js +69 -34
- package/dist/tools.js.map +1 -1
- package/dist/tui/branch-picker.js +9 -3
- package/dist/tui/branch-picker.js.map +1 -1
- package/dist/tui/command-handler.js +88 -36
- package/dist/tui/command-handler.js.map +1 -1
- package/dist/tui/confirm.js.map +1 -1
- package/dist/tui/controller.js +234 -117
- package/dist/tui/controller.js.map +1 -1
- package/dist/tui/event-bridge.js.map +1 -1
- package/dist/tui/keymap.js +93 -71
- package/dist/tui/keymap.js.map +1 -1
- package/dist/tui/layout.js +9 -1
- package/dist/tui/layout.js.map +1 -1
- package/dist/tui/render.js +17 -5
- package/dist/tui/render.js.map +1 -1
- package/dist/tui/screen.js.map +1 -1
- package/dist/tui/state.js +129 -63
- package/dist/tui/state.js.map +1 -1
- package/dist/tui/theme.js +12 -3
- package/dist/tui/theme.js.map +1 -1
- package/dist/upgrade.js +28 -15
- package/dist/upgrade.js.map +1 -1
- package/dist/utils.js +8 -5
- package/dist/utils.js.map +1 -1
- package/dist/vault.js +48 -12
- package/dist/vault.js.map +1 -1
- package/dist/vim.js.map +1 -1
- package/package.json +11 -2
package/dist/tools.js
CHANGED
|
@@ -279,8 +279,10 @@ export async function undo_path(ctx, args) {
|
|
|
279
279
|
const p = directPath ? resolvePath(ctx, directPath) : ctx.lastEditedPath;
|
|
280
280
|
if (!p)
|
|
281
281
|
throw new Error('undo: missing path');
|
|
282
|
+
const absCwd = path.resolve(ctx.cwd);
|
|
283
|
+
const redactedPath = redactPath(p, absCwd);
|
|
282
284
|
if (!ctx.noConfirm && ctx.confirm) {
|
|
283
|
-
const ok = await ctx.confirm(`Restore latest backup for:\n ${
|
|
285
|
+
const ok = await ctx.confirm(`Restore latest backup for:\n ${redactedPath}\nThis will overwrite the current file. Proceed? (y/N) `);
|
|
284
286
|
if (!ok)
|
|
285
287
|
return 'undo: cancelled';
|
|
286
288
|
}
|
|
@@ -288,11 +290,13 @@ export async function undo_path(ctx, args) {
|
|
|
288
290
|
throw new Error('undo: confirmation required (run with --no-confirm/--yolo or in interactive mode)');
|
|
289
291
|
}
|
|
290
292
|
if (ctx.dryRun)
|
|
291
|
-
return `dry-run: would restore latest backup for ${
|
|
293
|
+
return `dry-run: would restore latest backup for ${redactedPath}`;
|
|
292
294
|
return await restoreLatestBackup(p, ctx);
|
|
293
295
|
}
|
|
294
296
|
export async function read_file(ctx, args) {
|
|
295
297
|
const p = resolvePath(ctx, args?.path);
|
|
298
|
+
const absCwd = path.resolve(ctx.cwd);
|
|
299
|
+
const redactedPath = redactPath(p, absCwd);
|
|
296
300
|
const offset = args?.offset != null ? Number(args.offset) : undefined;
|
|
297
301
|
const rawLimit = args?.limit != null ? Number(args.limit) : undefined;
|
|
298
302
|
const limit = Number.isFinite(rawLimit) && rawLimit > 0
|
|
@@ -317,7 +321,7 @@ export async function read_file(ctx, args) {
|
|
|
317
321
|
try {
|
|
318
322
|
const stat = await fs.stat(p);
|
|
319
323
|
if (stat.isDirectory()) {
|
|
320
|
-
return `read_file: "${
|
|
324
|
+
return `read_file: "${redactedPath}" is a directory, not a file. Use list_dir to see its contents, or search_files to find specific code.`;
|
|
321
325
|
}
|
|
322
326
|
}
|
|
323
327
|
catch (e) {
|
|
@@ -419,6 +423,8 @@ export async function read_files(ctx, args) {
|
|
|
419
423
|
}
|
|
420
424
|
export async function write_file(ctx, args) {
|
|
421
425
|
const p = resolvePath(ctx, args?.path);
|
|
426
|
+
const absCwd = path.resolve(ctx.cwd);
|
|
427
|
+
const redactedPath = redactPath(p, absCwd);
|
|
422
428
|
// Content may arrive as a string (normal) or as a parsed JSON object
|
|
423
429
|
// (when llama-server's XML parser auto-parses JSON content values).
|
|
424
430
|
const raw = args?.content;
|
|
@@ -444,7 +450,7 @@ export async function write_file(ctx, args) {
|
|
|
444
450
|
}
|
|
445
451
|
if (pathVerdict.tier === 'cautious' && !ctx.noConfirm) {
|
|
446
452
|
if (ctx.confirm) {
|
|
447
|
-
const ok = await ctx.confirm(pathVerdict.prompt || `Write to ${
|
|
453
|
+
const ok = await ctx.confirm(pathVerdict.prompt || `Write to ${redactedPath}?`, { tool: 'write_file', args: { path: p } });
|
|
448
454
|
if (!ok)
|
|
449
455
|
throw new Error(`write_file: cancelled by user (${pathVerdict.reason})`);
|
|
450
456
|
}
|
|
@@ -454,7 +460,7 @@ export async function write_file(ctx, args) {
|
|
|
454
460
|
}
|
|
455
461
|
const existingStat = await fs.stat(p).catch(() => null);
|
|
456
462
|
if (existingStat?.isFile() && existingStat.size > 0 && !overwrite) {
|
|
457
|
-
throw new Error(`write_file: refusing to overwrite existing non-empty file ${
|
|
463
|
+
throw new Error(`write_file: refusing to overwrite existing non-empty file ${redactedPath} without explicit overwrite=true (or force=true). ` +
|
|
458
464
|
`Use edit_range/apply_patch for surgical edits, or set overwrite=true for intentional full-file replacement.`);
|
|
459
465
|
}
|
|
460
466
|
if (ctx.dryRun) {
|
|
@@ -471,10 +477,12 @@ export async function write_file(ctx, args) {
|
|
|
471
477
|
ctx.onMutation?.(p);
|
|
472
478
|
const afterBuf = Buffer.from(content, 'utf8');
|
|
473
479
|
const replayNote = await checkpointReplay(ctx, { op: 'write_file', filePath: p, before: beforeBuf, after: afterBuf });
|
|
474
|
-
return `wrote ${
|
|
480
|
+
return `wrote ${redactedPath} (${Buffer.byteLength(content, 'utf8')} bytes)${replayNote}${cwdWarning}`;
|
|
475
481
|
}
|
|
476
482
|
export async function insert_file(ctx, args) {
|
|
477
483
|
const p = resolvePath(ctx, args?.path);
|
|
484
|
+
const absCwd = path.resolve(ctx.cwd);
|
|
485
|
+
const redactedPath = redactPath(p, absCwd);
|
|
478
486
|
const line = Number(args?.line);
|
|
479
487
|
const rawText = args?.text;
|
|
480
488
|
const text = typeof rawText === 'string' ? rawText
|
|
@@ -493,7 +501,7 @@ export async function insert_file(ctx, args) {
|
|
|
493
501
|
}
|
|
494
502
|
if (pathVerdict.tier === 'cautious' && !ctx.noConfirm) {
|
|
495
503
|
if (ctx.confirm) {
|
|
496
|
-
const ok = await ctx.confirm(pathVerdict.prompt || `Insert into ${
|
|
504
|
+
const ok = await ctx.confirm(pathVerdict.prompt || `Insert into ${redactedPath}?`, { tool: 'insert_file', args: { path: p } });
|
|
497
505
|
if (!ok)
|
|
498
506
|
throw new Error(`insert_file: cancelled by user (${pathVerdict.reason})`);
|
|
499
507
|
}
|
|
@@ -502,7 +510,7 @@ export async function insert_file(ctx, args) {
|
|
|
502
510
|
}
|
|
503
511
|
}
|
|
504
512
|
if (ctx.dryRun)
|
|
505
|
-
return `dry-run: would insert into ${
|
|
513
|
+
return `dry-run: would insert into ${redactedPath} at line=${line} (${Buffer.byteLength(text, 'utf8')} bytes)`;
|
|
506
514
|
// Phase 9d: snapshot /etc/ files before editing
|
|
507
515
|
if (ctx.mode === 'sys' && ctx.vault) {
|
|
508
516
|
await snapshotBeforeEdit(ctx.vault, p).catch(() => { });
|
|
@@ -523,7 +531,7 @@ export async function insert_file(ctx, args) {
|
|
|
523
531
|
after: Buffer.from(out, 'utf8')
|
|
524
532
|
});
|
|
525
533
|
const cwdWarning = checkCwdWarning('insert_file', p, ctx);
|
|
526
|
-
return `inserted into ${
|
|
534
|
+
return `inserted into ${redactedPath} at 0${replayNote}${cwdWarning}`;
|
|
527
535
|
}
|
|
528
536
|
const lines = beforeText.split(/\r?\n/);
|
|
529
537
|
let idx;
|
|
@@ -552,10 +560,12 @@ export async function insert_file(ctx, args) {
|
|
|
552
560
|
after: Buffer.from(out, 'utf8')
|
|
553
561
|
});
|
|
554
562
|
const cwdWarning = checkCwdWarning('insert_file', p, ctx);
|
|
555
|
-
return `inserted into ${
|
|
563
|
+
return `inserted into ${redactedPath} at ${idx}${replayNote}${cwdWarning}`;
|
|
556
564
|
}
|
|
557
565
|
export async function edit_file(ctx, args) {
|
|
558
566
|
const p = resolvePath(ctx, args?.path);
|
|
567
|
+
const absCwd = path.resolve(ctx.cwd);
|
|
568
|
+
const redactedPath = redactPath(p, absCwd);
|
|
559
569
|
const rawOld = args?.old_text;
|
|
560
570
|
const oldText = typeof rawOld === 'string' ? rawOld
|
|
561
571
|
: (rawOld != null && typeof rawOld === 'object' ? JSON.stringify(rawOld, null, 2) : undefined);
|
|
@@ -577,7 +587,7 @@ export async function edit_file(ctx, args) {
|
|
|
577
587
|
}
|
|
578
588
|
if (pathVerdict.tier === 'cautious' && !ctx.noConfirm) {
|
|
579
589
|
if (ctx.confirm) {
|
|
580
|
-
const ok = await ctx.confirm(pathVerdict.prompt || `Edit ${
|
|
590
|
+
const ok = await ctx.confirm(pathVerdict.prompt || `Edit ${redactedPath}?`, { tool: 'edit_file', args: { path: p, old_text: oldText, new_text: newText } });
|
|
581
591
|
if (!ok)
|
|
582
592
|
throw new Error(`edit_file: cancelled by user (${pathVerdict.reason})`);
|
|
583
593
|
}
|
|
@@ -590,7 +600,7 @@ export async function edit_file(ctx, args) {
|
|
|
590
600
|
await snapshotBeforeEdit(ctx.vault, p).catch(() => { });
|
|
591
601
|
}
|
|
592
602
|
const cur = await fs.readFile(p, 'utf8').catch((e) => {
|
|
593
|
-
throw new Error(`edit_file: cannot read ${
|
|
603
|
+
throw new Error(`edit_file: cannot read ${redactedPath}: ${e?.message ?? String(e)}`);
|
|
594
604
|
});
|
|
595
605
|
const idx = cur.indexOf(oldText);
|
|
596
606
|
if (idx === -1) {
|
|
@@ -626,11 +636,11 @@ export async function edit_file(ctx, args) {
|
|
|
626
636
|
else {
|
|
627
637
|
hint = `\nFile head (first 400 chars):\n${cur.slice(0, 400)}`;
|
|
628
638
|
}
|
|
629
|
-
throw new Error(`edit_file: old_text not found in ${
|
|
639
|
+
throw new Error(`edit_file: old_text not found in ${redactedPath}. Re-read the file and retry with exact text.${hint}`);
|
|
630
640
|
}
|
|
631
641
|
const next = replaceAll ? cur.split(oldText).join(newText) : cur.slice(0, idx) + newText + cur.slice(idx + oldText.length);
|
|
632
642
|
if (ctx.dryRun)
|
|
633
|
-
return `dry-run: would edit ${
|
|
643
|
+
return `dry-run: would edit ${redactedPath} (replace_all=${replaceAll})`;
|
|
634
644
|
await backupFile(p, ctx);
|
|
635
645
|
await atomicWrite(p, next);
|
|
636
646
|
ctx.onMutation?.(p);
|
|
@@ -641,7 +651,7 @@ export async function edit_file(ctx, args) {
|
|
|
641
651
|
after: Buffer.from(next, 'utf8')
|
|
642
652
|
});
|
|
643
653
|
const cwdWarning = checkCwdWarning('edit_file', p, ctx);
|
|
644
|
-
return `edited ${
|
|
654
|
+
return `edited ${redactedPath} (replace_all=${replaceAll})${replayNote}${cwdWarning}`;
|
|
645
655
|
}
|
|
646
656
|
function normalizePatchPath(p) {
|
|
647
657
|
let s = String(p ?? '').trim();
|
|
@@ -891,7 +901,8 @@ export async function apply_patch(ctx, args) {
|
|
|
891
901
|
if (chk.rc !== 0)
|
|
892
902
|
throw new Error(`apply_patch: patch --dry-run failed:\n${chk.err || chk.out}`);
|
|
893
903
|
}
|
|
894
|
-
|
|
904
|
+
const redactedPaths = touched.paths.map((rel) => redactPath(resolvePath(ctx, rel), path.resolve(ctx.cwd)));
|
|
905
|
+
return `dry-run: patch would apply cleanly (${touched.paths.length} files): ${redactedPaths.join(', ')}`;
|
|
895
906
|
}
|
|
896
907
|
// Snapshot + backup before applying
|
|
897
908
|
const beforeMap = new Map();
|
|
@@ -937,7 +948,8 @@ export async function apply_patch(ctx, args) {
|
|
|
937
948
|
replayNotes += replayNote;
|
|
938
949
|
cwdWarnings += checkCwdWarning('apply_patch', abs, ctx);
|
|
939
950
|
}
|
|
940
|
-
|
|
951
|
+
const redactedPaths = touched.paths.map((rel) => redactPath(resolvePath(ctx, rel), path.resolve(ctx.cwd)));
|
|
952
|
+
return `applied patch (${touched.paths.length} files): ${redactedPaths.join(', ')}${replayNotes}${cwdWarnings}`;
|
|
941
953
|
}
|
|
942
954
|
export async function list_dir(ctx, args) {
|
|
943
955
|
const p = resolvePath(ctx, args?.path ?? '.');
|
|
@@ -945,6 +957,7 @@ export async function list_dir(ctx, args) {
|
|
|
945
957
|
const maxEntries = Math.min(args?.max_entries ? Number(args.max_entries) : 200, 500);
|
|
946
958
|
if (!p)
|
|
947
959
|
throw new Error('list_dir: missing path');
|
|
960
|
+
const absCwd = path.resolve(ctx.cwd);
|
|
948
961
|
const lines = [];
|
|
949
962
|
let count = 0;
|
|
950
963
|
async function walk(dir, depth) {
|
|
@@ -959,7 +972,7 @@ export async function list_dir(ctx, args) {
|
|
|
959
972
|
const full = path.join(dir, ent.name);
|
|
960
973
|
const st = await fs.lstat(full).catch(() => null);
|
|
961
974
|
const kind = ent.isDirectory() ? 'dir' : ent.isSymbolicLink() ? 'link' : 'file';
|
|
962
|
-
lines.push(`${kind}\t${st?.size ?? 0}\t${full}`);
|
|
975
|
+
lines.push(`${kind}\t${st?.size ?? 0}\t${redactPath(full, absCwd)}`);
|
|
963
976
|
count++;
|
|
964
977
|
if (recursive && ent.isDirectory() && depth < 3) {
|
|
965
978
|
await walk(full, depth + 1);
|
|
@@ -970,7 +983,7 @@ export async function list_dir(ctx, args) {
|
|
|
970
983
|
if (count >= maxEntries)
|
|
971
984
|
lines.push(`[truncated after ${maxEntries} entries]`);
|
|
972
985
|
if (!lines.length)
|
|
973
|
-
return `[empty directory: ${p}]`;
|
|
986
|
+
return `[empty directory: ${redactPath(p, absCwd)}]`;
|
|
974
987
|
return lines.join('\n');
|
|
975
988
|
}
|
|
976
989
|
export async function search_files(ctx, args) {
|
|
@@ -982,6 +995,7 @@ export async function search_files(ctx, args) {
|
|
|
982
995
|
throw new Error('search_files: missing path');
|
|
983
996
|
if (!pattern)
|
|
984
997
|
throw new Error('search_files: missing pattern');
|
|
998
|
+
const absCwd = path.resolve(ctx.cwd);
|
|
985
999
|
// Prefer rg if available (fast, bounded output)
|
|
986
1000
|
if (await hasRg()) {
|
|
987
1001
|
const cmd = ['rg', '-n', '--no-heading', '--color', 'never', pattern, root];
|
|
@@ -992,7 +1006,7 @@ export async function search_files(ctx, args) {
|
|
|
992
1006
|
const parsed = JSON.parse(rawJson);
|
|
993
1007
|
// rg exits 1 when no matches found (not an error), 2+ for real errors.
|
|
994
1008
|
if (parsed.rc === 1 && !parsed.out?.trim()) {
|
|
995
|
-
return `No matches for pattern "${pattern}" in ${root}. STOP — do NOT read files individually to search. Try a broader regex pattern, different keywords, or use exec: grep -rn "keyword" ${root}`;
|
|
1009
|
+
return `No matches for pattern \"${pattern}\" in ${root}. STOP — do NOT read files individually to search. Try a broader regex pattern, different keywords, or use exec: grep -rn \"keyword\" ${root}`;
|
|
996
1010
|
}
|
|
997
1011
|
if (parsed.rc >= 2) {
|
|
998
1012
|
// Real rg error — fall through to regex fallback below
|
|
@@ -1003,7 +1017,16 @@ export async function search_files(ctx, args) {
|
|
|
1003
1017
|
const lines = rgOutput.split(/\r?\n/).filter(Boolean).slice(0, maxResults);
|
|
1004
1018
|
if (lines.length >= maxResults)
|
|
1005
1019
|
lines.push(`[truncated after ${maxResults} results]`);
|
|
1006
|
-
|
|
1020
|
+
// Redact paths in rg output
|
|
1021
|
+
const redactedLines = lines.map(line => {
|
|
1022
|
+
const colonIdx = line.indexOf(':');
|
|
1023
|
+
if (colonIdx === -1)
|
|
1024
|
+
return line;
|
|
1025
|
+
const filePath = line.substring(0, colonIdx);
|
|
1026
|
+
const rest = line.substring(colonIdx + 1);
|
|
1027
|
+
return redactPath(filePath, absCwd) + ':' + rest;
|
|
1028
|
+
});
|
|
1029
|
+
return redactedLines.join('\n');
|
|
1007
1030
|
}
|
|
1008
1031
|
}
|
|
1009
1032
|
}
|
|
@@ -1017,7 +1040,7 @@ export async function search_files(ctx, args) {
|
|
|
1017
1040
|
re = new RegExp(pattern);
|
|
1018
1041
|
}
|
|
1019
1042
|
catch (e) {
|
|
1020
|
-
throw new ToolError('invalid_args', `search_files: invalid regex pattern: ${e?.message ?? String(e)}`, false, 'Escape regex metacharacters (
|
|
1043
|
+
throw new ToolError('invalid_args', `search_files: invalid regex pattern: ${e?.message ?? String(e)}`, false, 'Escape regex metacharacters (\\\\, [, ], (, ), +, *, ?). If you intended literal text, use an escaped/literal pattern.');
|
|
1021
1044
|
}
|
|
1022
1045
|
const out = [];
|
|
1023
1046
|
async function walk(dir, depth) {
|
|
@@ -1056,7 +1079,7 @@ export async function search_files(ctx, args) {
|
|
|
1056
1079
|
const lines = buf.split(/\r?\n/);
|
|
1057
1080
|
for (let i = 0; i < lines.length; i++) {
|
|
1058
1081
|
if (re.test(lines[i])) {
|
|
1059
|
-
out.push(`${full}:${i + 1}:${lines[i]}`);
|
|
1082
|
+
out.push(`${redactPath(full, absCwd)}:${i + 1}:${lines[i]}`);
|
|
1060
1083
|
if (out.length >= maxResults)
|
|
1061
1084
|
return;
|
|
1062
1085
|
}
|
|
@@ -1066,10 +1089,9 @@ export async function search_files(ctx, args) {
|
|
|
1066
1089
|
await walk(root, 0);
|
|
1067
1090
|
if (out.length >= maxResults)
|
|
1068
1091
|
out.push(`[truncated after ${maxResults} results]`);
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
return result;
|
|
1092
|
+
if (!out.length)
|
|
1093
|
+
return `No matches for pattern \"${pattern}\" in ${redactPath(root, absCwd)}.`;
|
|
1094
|
+
return out.join('\n');
|
|
1073
1095
|
}
|
|
1074
1096
|
function stripSimpleQuotedSegments(s) {
|
|
1075
1097
|
// Best-effort quote stripping for lightweight shell pattern checks.
|
|
@@ -1404,7 +1426,7 @@ export async function exec(ctx, args) {
|
|
|
1404
1426
|
}
|
|
1405
1427
|
async function execWithPty(args) {
|
|
1406
1428
|
const { pty, command, cwd, timeout, maxBytes, captureLimit, signal, execCwdWarning } = args;
|
|
1407
|
-
const proc = pty.spawn(BASH_PATH, ['-
|
|
1429
|
+
const proc = pty.spawn(BASH_PATH, ['-c', command], {
|
|
1408
1430
|
name: 'xterm-color',
|
|
1409
1431
|
cwd,
|
|
1410
1432
|
cols: 120,
|
|
@@ -1526,11 +1548,6 @@ export async function vault_search(ctx, args) {
|
|
|
1526
1548
|
export async function sys_context(ctx, args) {
|
|
1527
1549
|
return sysContextTool(ctx, args);
|
|
1528
1550
|
}
|
|
1529
|
-
function resolvePath(ctx, p) {
|
|
1530
|
-
if (typeof p !== 'string' || !p.trim())
|
|
1531
|
-
throw new Error('missing path');
|
|
1532
|
-
return path.resolve(ctx.cwd, p);
|
|
1533
|
-
}
|
|
1534
1551
|
/**
|
|
1535
1552
|
* Check if a target path is within a directory.
|
|
1536
1553
|
* Handles the classic root directory edge case: when dir is `/`, every absolute path is valid.
|
|
@@ -1540,6 +1557,24 @@ function isWithinDir(target, dir) {
|
|
|
1540
1557
|
return target.startsWith('/');
|
|
1541
1558
|
return target === dir || target.startsWith(dir + path.sep);
|
|
1542
1559
|
}
|
|
1560
|
+
function resolvePath(ctx, p) {
|
|
1561
|
+
if (typeof p !== 'string' || !p.trim())
|
|
1562
|
+
throw new Error('missing path');
|
|
1563
|
+
return path.resolve(ctx.cwd, p);
|
|
1564
|
+
}
|
|
1565
|
+
/**
|
|
1566
|
+
* Redact a path for safe output.
|
|
1567
|
+
* - Paths within cwd are shown as relative paths
|
|
1568
|
+
* - Paths outside cwd are redacted as [outside-cwd]/basename
|
|
1569
|
+
*/
|
|
1570
|
+
function redactPath(filePath, absCwd) {
|
|
1571
|
+
const resolved = path.resolve(filePath);
|
|
1572
|
+
if (isWithinDir(resolved, absCwd)) {
|
|
1573
|
+
return path.relative(absCwd, resolved);
|
|
1574
|
+
}
|
|
1575
|
+
const basename = path.basename(resolved);
|
|
1576
|
+
return `[outside-cwd]/${basename}`;
|
|
1577
|
+
}
|
|
1543
1578
|
/**
|
|
1544
1579
|
* Check if a resolved path is outside the working directory.
|
|
1545
1580
|
* Returns a model-visible warning string if so, empty string otherwise.
|
|
@@ -1600,7 +1635,7 @@ async function hasRg() {
|
|
|
1600
1635
|
catch {
|
|
1601
1636
|
// try PATH
|
|
1602
1637
|
return await new Promise((resolve) => {
|
|
1603
|
-
const c = spawn(BASH_PATH, ['-
|
|
1638
|
+
const c = spawn(BASH_PATH, ['-c', 'command -v rg >/dev/null 2>&1'], { stdio: 'ignore' });
|
|
1604
1639
|
c.on('error', () => resolve(false));
|
|
1605
1640
|
c.on('close', (code) => resolve(code === 0));
|
|
1606
1641
|
});
|