@wrongstack/tools 0.275.1 → 0.276.3
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/{background-indexer-BoTUw0EM.d.ts → background-indexer-BeDBxfSh.d.ts} +6 -0
- package/dist/builtin.js +1056 -246
- package/dist/builtin.js.map +1 -1
- package/dist/codebase-index/index.d.ts +30 -2
- package/dist/codebase-index/index.js +201 -24
- package/dist/codebase-index/index.js.map +1 -1
- package/dist/codebase-index/worker.js +196 -23
- package/dist/codebase-index/worker.js.map +1 -1
- package/dist/document.js +2 -2
- package/dist/document.js.map +1 -1
- package/dist/edit.js +52 -15
- package/dist/edit.js.map +1 -1
- package/dist/fetch.js +89 -18
- package/dist/fetch.js.map +1 -1
- package/dist/glob.js +35 -1
- package/dist/glob.js.map +1 -1
- package/dist/grep.js +15 -4
- package/dist/grep.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1090 -255
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts +7 -0
- package/dist/install.js +6 -0
- package/dist/install.js.map +1 -1
- package/dist/json.d.ts +26 -1
- package/dist/json.js +453 -46
- package/dist/json.js.map +1 -1
- package/dist/memory.js +26 -4
- package/dist/memory.js.map +1 -1
- package/dist/outdated.js +2 -2
- package/dist/outdated.js.map +1 -1
- package/dist/pack.js +1056 -246
- package/dist/pack.js.map +1 -1
- package/dist/read.js +36 -6
- package/dist/read.js.map +1 -1
- package/dist/replace.js +27 -9
- package/dist/replace.js.map +1 -1
- package/dist/search.d.ts +5 -1
- package/dist/search.js +179 -62
- package/dist/search.js.map +1 -1
- package/dist/tool-help.js +2 -2
- package/dist/tool-help.js.map +1 -1
- package/dist/tool-search.js +2 -2
- package/dist/tool-search.js.map +1 -1
- package/dist/write.js +13 -3
- package/dist/write.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs7 from 'node:fs/promises';
|
|
2
2
|
import * as Core from '@wrongstack/core';
|
|
3
|
-
import { toErrorMessage, atomicWrite, unifiedDiff, detectNewlineStyle, normalizeToLf, toStyle, compileGlob, expectDefined, buildChildEnv, isPrivateIPv4, isPrivateIPv6, loadPlan, setPlanItemStatus, savePlan, loadTasks, saveTasks, mutatePlan, clearPlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, mutateTasks, formatTaskList, formatPlan, assessCommitSafety, recordPackageAction, detectPackageEcosystem, getDesignKitLoader, isDesignStack, loadActiveKit, applyTokenOverrides, setActiveKit, recordKitChoice, recordOverrides, setDesignOverrides, materializeTokens, runDesignVerify, computeTaskItemProgress, wstackGlobalRoot, resolveWstackPaths, truncate } from '@wrongstack/core';
|
|
3
|
+
import { ToolValidationError, FsError, toErrorMessage, atomicWrite, unifiedDiff, detectNewlineStyle, normalizeToLf, toStyle, compileGlob, expectDefined, buildChildEnv, ToolError, FetchError, isPrivateIPv4, isPrivateIPv6, loadPlan, setPlanItemStatus, savePlan, loadTasks, saveTasks, mutatePlan, clearPlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, mutateTasks, formatTaskList, formatPlan, assessCommitSafety, deepMerge, recordPackageAction, detectPackageEcosystem, getDesignKitLoader, isDesignStack, loadActiveKit, applyTokenOverrides, setActiveKit, recordKitChoice, recordOverrides, setDesignOverrides, materializeTokens, runDesignVerify, computeTaskItemProgress, wstackGlobalRoot, resolveWstackPaths, truncate } from '@wrongstack/core';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { resolve, sep, dirname, join } from 'node:path';
|
|
6
6
|
import { spawn, execFileSync } from 'node:child_process';
|
|
@@ -207,19 +207,49 @@ var readTool = {
|
|
|
207
207
|
required: ["path"]
|
|
208
208
|
},
|
|
209
209
|
async execute(input, ctx) {
|
|
210
|
-
if (!input?.path)
|
|
210
|
+
if (!input?.path) {
|
|
211
|
+
throw new ToolValidationError({
|
|
212
|
+
message: "read: path is required",
|
|
213
|
+
field: "path"
|
|
214
|
+
});
|
|
215
|
+
}
|
|
211
216
|
const absPath = await safeResolveReal(input.path, ctx);
|
|
212
217
|
let stat11;
|
|
213
218
|
try {
|
|
214
219
|
stat11 = await fs7.stat(absPath);
|
|
215
220
|
} catch (err) {
|
|
216
221
|
const code = err.code;
|
|
217
|
-
if (code === "ENOENT")
|
|
218
|
-
|
|
222
|
+
if (code === "ENOENT") {
|
|
223
|
+
throw new FsError({
|
|
224
|
+
message: `read: file not found "${input.path}"`,
|
|
225
|
+
code: "FS_READ_FAILED",
|
|
226
|
+
path: absPath,
|
|
227
|
+
context: { errno: "ENOENT" }
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
throw new FsError({
|
|
231
|
+
message: `read: failed to stat "${input.path}": ${toErrorMessage(err)}`,
|
|
232
|
+
code: "FS_READ_FAILED",
|
|
233
|
+
path: absPath,
|
|
234
|
+
context: { errno: code },
|
|
235
|
+
cause: err
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (!stat11.isFile()) {
|
|
239
|
+
throw new FsError({
|
|
240
|
+
message: `read: "${input.path}" is not a regular file`,
|
|
241
|
+
code: "FS_READ_FAILED",
|
|
242
|
+
path: absPath,
|
|
243
|
+
context: { reason: "not-a-regular-file" }
|
|
244
|
+
});
|
|
219
245
|
}
|
|
220
|
-
if (!stat11.isFile()) throw new Error(`read: "${input.path}" is not a regular file`);
|
|
221
246
|
if (stat11.size > MAX_BYTES) {
|
|
222
|
-
throw new
|
|
247
|
+
throw new FsError({
|
|
248
|
+
message: `read: file too large (${stat11.size} bytes, limit ${MAX_BYTES})`,
|
|
249
|
+
code: "FS_READ_FAILED",
|
|
250
|
+
path: absPath,
|
|
251
|
+
context: { size: stat11.size, limit: MAX_BYTES, reason: "too-large" }
|
|
252
|
+
});
|
|
223
253
|
}
|
|
224
254
|
const offset = Math.max(1, input.offset ?? 1);
|
|
225
255
|
const limit = Math.max(0, Math.min(input.limit ?? 2e3, 5e3));
|
|
@@ -364,8 +394,18 @@ var writeTool = {
|
|
|
364
394
|
required: ["path", "content"]
|
|
365
395
|
},
|
|
366
396
|
async execute(input, ctx) {
|
|
367
|
-
if (!input?.path)
|
|
368
|
-
|
|
397
|
+
if (!input?.path) {
|
|
398
|
+
throw new ToolValidationError({
|
|
399
|
+
message: "write: path is required",
|
|
400
|
+
field: "path"
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
if (input.content === void 0) {
|
|
404
|
+
throw new ToolValidationError({
|
|
405
|
+
message: "write: content is required",
|
|
406
|
+
field: "content"
|
|
407
|
+
});
|
|
408
|
+
}
|
|
369
409
|
const absPath = await safeResolveReal(input.path, ctx);
|
|
370
410
|
let existed = false;
|
|
371
411
|
let prev = "";
|
|
@@ -425,28 +465,62 @@ var editTool = {
|
|
|
425
465
|
required: ["path", "old_string", "new_string"]
|
|
426
466
|
},
|
|
427
467
|
async execute(input, ctx) {
|
|
428
|
-
if (!input?.path)
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
if (input.old_string ===
|
|
468
|
+
if (!input?.path) {
|
|
469
|
+
throw new ToolValidationError({ message: "edit: path is required", field: "path" });
|
|
470
|
+
}
|
|
471
|
+
if (input.old_string === void 0) {
|
|
472
|
+
throw new ToolValidationError({
|
|
473
|
+
message: "edit: old_string is required",
|
|
474
|
+
field: "old_string"
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
if (input.new_string === void 0) {
|
|
478
|
+
throw new ToolValidationError({
|
|
479
|
+
message: "edit: new_string is required",
|
|
480
|
+
field: "new_string"
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
if (input.old_string === "") {
|
|
484
|
+
throw new ToolValidationError({
|
|
485
|
+
message: "edit: old_string cannot be empty",
|
|
486
|
+
field: "old_string"
|
|
487
|
+
});
|
|
488
|
+
}
|
|
432
489
|
const absPath = await safeResolveReal(input.path, ctx);
|
|
433
490
|
const stat11 = await fs7.stat(absPath).catch((err) => {
|
|
434
491
|
if (err.code === "ENOENT") {
|
|
435
|
-
throw new
|
|
492
|
+
throw new ToolValidationError({
|
|
493
|
+
message: `edit: file "${input.path}" does not exist. Use \`write\` instead.`,
|
|
494
|
+
field: "path",
|
|
495
|
+
context: { exists: false }
|
|
496
|
+
});
|
|
436
497
|
}
|
|
437
498
|
throw err;
|
|
438
499
|
});
|
|
439
|
-
if (!stat11.isFile())
|
|
500
|
+
if (!stat11.isFile()) {
|
|
501
|
+
throw new ToolValidationError({
|
|
502
|
+
message: `edit: "${input.path}" is not a regular file`,
|
|
503
|
+
field: "path"
|
|
504
|
+
});
|
|
505
|
+
}
|
|
440
506
|
const autoRead = !ctx.hasRead(absPath);
|
|
441
507
|
const original = await fs7.readFile(absPath, "utf8");
|
|
442
508
|
const updated = await fs7.stat(absPath);
|
|
443
509
|
const mtimeTolerance = process.platform === "win32" ? 2e3 : 1;
|
|
444
510
|
const lastReadMtime = ctx.lastReadMtime(absPath);
|
|
445
511
|
if (lastReadMtime !== void 0 && updated.mtimeMs > lastReadMtime + mtimeTolerance) {
|
|
446
|
-
throw new
|
|
512
|
+
throw new ToolValidationError({
|
|
513
|
+
message: `edit: file "${input.path}" was modified externally. Re-read it first.`,
|
|
514
|
+
field: "path",
|
|
515
|
+
context: { reason: "external_modification" }
|
|
516
|
+
});
|
|
447
517
|
}
|
|
448
518
|
if (autoRead && updated.mtimeMs > stat11.mtimeMs + mtimeTolerance) {
|
|
449
|
-
throw new
|
|
519
|
+
throw new ToolValidationError({
|
|
520
|
+
message: `edit: file "${input.path}" changed while being auto-read. Retry the edit.`,
|
|
521
|
+
field: "path",
|
|
522
|
+
context: { reason: "auto_read_race" }
|
|
523
|
+
});
|
|
450
524
|
}
|
|
451
525
|
const autoReadNote = autoRead ? `No prior read was recorded for "${input.path}"; edit auto-read the current file and applied the replacement only after the ambiguity checks passed.` : void 0;
|
|
452
526
|
const style = detectNewlineStyle(original);
|
|
@@ -472,15 +546,18 @@ var editTool = {
|
|
|
472
546
|
}
|
|
473
547
|
if (count === 0) {
|
|
474
548
|
const hint = findSimilarity(fileLf, oldLf);
|
|
475
|
-
throw new
|
|
476
|
-
`edit: no match for old_string in "${input.path}".${hint ? ` Nearest match near line ${hint}.` : ""}
|
|
477
|
-
|
|
549
|
+
throw new ToolValidationError({
|
|
550
|
+
message: `edit: no match for old_string in "${input.path}".${hint ? ` Nearest match near line ${hint}.` : ""}`,
|
|
551
|
+
field: "old_string"
|
|
552
|
+
});
|
|
478
553
|
}
|
|
479
554
|
if (count > 1 && !input.replace_all) {
|
|
480
555
|
const lines = lineNumbersFor(fileLf, matches);
|
|
481
|
-
throw new
|
|
482
|
-
`edit: old_string matched ${count} times in "${input.path}" (lines: ${lines.join(", ")}). Add more context to make it unique, or set replace_all: true
|
|
483
|
-
|
|
556
|
+
throw new ToolValidationError({
|
|
557
|
+
message: `edit: old_string matched ${count} times in "${input.path}" (lines: ${lines.join(", ")}). Add more context to make it unique, or set replace_all: true.`,
|
|
558
|
+
field: "old_string",
|
|
559
|
+
context: { occurrences: count }
|
|
560
|
+
});
|
|
484
561
|
}
|
|
485
562
|
const newFileLf = input.replace_all ? fileLf.split(oldLf).join(newLf) : fileLf.replace(oldLf, newLf);
|
|
486
563
|
const newFile = toStyle(newFileLf, style);
|
|
@@ -580,8 +657,8 @@ var DEFAULT_IGNORE = ["node_modules", ".git", "dist", "build", ".next", "coverag
|
|
|
580
657
|
var replaceTool = {
|
|
581
658
|
name: "replace",
|
|
582
659
|
category: "Transform",
|
|
583
|
-
description: "Perform a search-and-replace across multiple files using a regex pattern. This is a powerful bulk transformation tool.
|
|
584
|
-
usageHint: "DANGEROUS IF USED CARELESSLY \u2014 review the diff output carefully.\n\nRecommended workflow:\n1.
|
|
660
|
+
description: "Perform a search-and-replace across multiple files using a regex pattern. This is a powerful bulk transformation tool. Dry-run is ON by default \u2014 set `dry_run: false` to apply changes.",
|
|
661
|
+
usageHint: "DANGEROUS IF USED CARELESSLY \u2014 review the diff output carefully.\n\nRecommended workflow:\n1. Run without `dry_run: false` first to see exactly what would change (dry-run is the default).\n2. Review the diff output, then re-run with `dry_run: false` to apply.\n3. Use a specific enough `pattern` (and `glob` / `files`) to avoid accidental broad changes.\n4. `replace_all` controls whether only the first match per file or all matches are replaced.\nThis tool is excellent for large-scale refactors (renaming, import updates, etc.) but must be used with caution.",
|
|
585
662
|
permission: "confirm",
|
|
586
663
|
mutating: true,
|
|
587
664
|
capabilities: ["fs.write"],
|
|
@@ -601,22 +678,40 @@ var replaceTool = {
|
|
|
601
678
|
type: "boolean",
|
|
602
679
|
description: "Replace all occurrences in each file (default: true)"
|
|
603
680
|
},
|
|
604
|
-
dry_run: { type: "boolean", description: "Preview changes without writing" }
|
|
681
|
+
dry_run: { type: "boolean", description: "Preview changes without writing (default: true)" }
|
|
605
682
|
},
|
|
606
683
|
required: ["pattern", "replacement", "files"]
|
|
607
684
|
},
|
|
608
685
|
async execute(input, ctx) {
|
|
609
|
-
if (!input?.pattern)
|
|
610
|
-
|
|
611
|
-
|
|
686
|
+
if (!input?.pattern) {
|
|
687
|
+
throw new ToolValidationError({
|
|
688
|
+
message: "replace: pattern is required",
|
|
689
|
+
field: "pattern"
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
if (input.replacement === void 0) {
|
|
693
|
+
throw new ToolValidationError({
|
|
694
|
+
message: "replace: replacement is required",
|
|
695
|
+
field: "replacement"
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
if (!input?.files) {
|
|
699
|
+
throw new ToolValidationError({
|
|
700
|
+
message: "replace: files is required",
|
|
701
|
+
field: "files"
|
|
702
|
+
});
|
|
703
|
+
}
|
|
612
704
|
const replaceAll = input.replace_all ?? true;
|
|
613
705
|
const compiled = compileUserRegex(input.pattern, "g");
|
|
614
706
|
if (!compiled.ok) {
|
|
615
|
-
throw new
|
|
707
|
+
throw new ToolValidationError({
|
|
708
|
+
message: `replace: ${compiled.reason}`,
|
|
709
|
+
field: "pattern"
|
|
710
|
+
});
|
|
616
711
|
}
|
|
617
712
|
const re = compiled.regex;
|
|
618
713
|
const globRe = input.glob ? compileGlob(input.glob) : null;
|
|
619
|
-
const dryRun = input.dry_run ??
|
|
714
|
+
const dryRun = input.dry_run ?? true;
|
|
620
715
|
const filesInput = Array.isArray(input.files) ? input.files.join(",") : input.files;
|
|
621
716
|
const fileList = await resolveFiles(filesInput, ctx, globRe);
|
|
622
717
|
const realRoot = await fs7.realpath(ctx.projectRoot).catch(() => ctx.projectRoot);
|
|
@@ -712,13 +807,13 @@ async function globFiles(pattern, base, extraGlob) {
|
|
|
712
807
|
return await globNative(pattern, base, extraGlob);
|
|
713
808
|
}
|
|
714
809
|
function checkRg() {
|
|
715
|
-
return new Promise((
|
|
810
|
+
return new Promise((resolve7) => {
|
|
716
811
|
try {
|
|
717
812
|
const p = spawn("rg", ["--version"], { env: buildChildEnv(), stdio: "ignore", windowsHide: true });
|
|
718
|
-
p.on("error", () =>
|
|
719
|
-
p.on("close", (code) =>
|
|
813
|
+
p.on("error", () => resolve7(false));
|
|
814
|
+
p.on("close", (code) => resolve7(code === 0));
|
|
720
815
|
} catch {
|
|
721
|
-
|
|
816
|
+
resolve7(false);
|
|
722
817
|
}
|
|
723
818
|
});
|
|
724
819
|
}
|
|
@@ -735,10 +830,10 @@ function spawnRgFind(pattern, base) {
|
|
|
735
830
|
buf += chunk.toString();
|
|
736
831
|
});
|
|
737
832
|
return {
|
|
738
|
-
promise: new Promise((
|
|
833
|
+
promise: new Promise((resolve7, reject) => {
|
|
739
834
|
child.on("error", reject);
|
|
740
835
|
child.on("close", () => {
|
|
741
|
-
|
|
836
|
+
resolve7(buf.split("\n").filter(Boolean));
|
|
742
837
|
});
|
|
743
838
|
})
|
|
744
839
|
};
|
|
@@ -830,7 +925,7 @@ var globTool = {
|
|
|
830
925
|
},
|
|
831
926
|
async execute(input, ctx) {
|
|
832
927
|
if (!input?.pattern) throw new Error("glob: pattern is required");
|
|
833
|
-
const base = input.path ?
|
|
928
|
+
const base = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
|
|
834
929
|
const limit = Math.max(1, Math.min(input.limit ?? 1e3, 5e3));
|
|
835
930
|
const ignored = await readGitignore(base);
|
|
836
931
|
const re = compileGlob(input.pattern);
|
|
@@ -885,8 +980,12 @@ var globTool = {
|
|
|
885
980
|
try {
|
|
886
981
|
const st = await fs7.stat(full);
|
|
887
982
|
if (st.isDirectory()) {
|
|
983
|
+
const real = await fs7.realpath(full);
|
|
984
|
+
await assertRealInsideRoot(real, ctx);
|
|
888
985
|
subdirs.push({ full, rel });
|
|
889
986
|
} else if (st.isFile()) {
|
|
987
|
+
const real = await fs7.realpath(full);
|
|
988
|
+
await assertRealInsideRoot(real, ctx);
|
|
890
989
|
re.lastIndex = 0;
|
|
891
990
|
const relMatch = re.test(rel);
|
|
892
991
|
re.lastIndex = 0;
|
|
@@ -977,13 +1076,21 @@ var grepTool = {
|
|
|
977
1076
|
return final;
|
|
978
1077
|
},
|
|
979
1078
|
async *executeStream(input, ctx, opts) {
|
|
980
|
-
if (!input?.pattern)
|
|
1079
|
+
if (!input?.pattern) {
|
|
1080
|
+
throw new ToolValidationError({
|
|
1081
|
+
message: "grep: pattern is required",
|
|
1082
|
+
field: "pattern"
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
981
1085
|
const base = input.path ? safeResolve(input.path, ctx) : ctx.cwd;
|
|
982
1086
|
const mode = input.output_mode ?? "content";
|
|
983
1087
|
const limit = Math.max(1, Math.min(input.limit ?? 200, 2e3));
|
|
984
1088
|
const validation = compileUserRegex(input.pattern, input.case_insensitive ? "i" : "");
|
|
985
1089
|
if (!validation.ok) {
|
|
986
|
-
throw new
|
|
1090
|
+
throw new ToolValidationError({
|
|
1091
|
+
message: `grep: ${validation.reason}`,
|
|
1092
|
+
field: "pattern"
|
|
1093
|
+
});
|
|
987
1094
|
}
|
|
988
1095
|
const rgAvailable = await detectRg(opts.signal);
|
|
989
1096
|
if (rgAvailable) {
|
|
@@ -999,13 +1106,13 @@ var grepTool = {
|
|
|
999
1106
|
}
|
|
1000
1107
|
};
|
|
1001
1108
|
async function detectRg(signal) {
|
|
1002
|
-
return new Promise((
|
|
1109
|
+
return new Promise((resolve7) => {
|
|
1003
1110
|
try {
|
|
1004
1111
|
const p = spawn("rg", ["--version"], { env: buildChildEnv(), stdio: "ignore", signal, windowsHide: true });
|
|
1005
|
-
p.on("error", () =>
|
|
1006
|
-
p.on("close", (code) =>
|
|
1112
|
+
p.on("error", () => resolve7(false));
|
|
1113
|
+
p.on("close", (code) => resolve7(code === 0));
|
|
1007
1114
|
} catch {
|
|
1008
|
-
|
|
1115
|
+
resolve7(false);
|
|
1009
1116
|
}
|
|
1010
1117
|
});
|
|
1011
1118
|
}
|
|
@@ -1139,7 +1246,10 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
1139
1246
|
const flags = input.case_insensitive ? "i" : "";
|
|
1140
1247
|
const compiled = compileUserRegex(input.pattern, flags);
|
|
1141
1248
|
if (!compiled.ok) {
|
|
1142
|
-
throw new
|
|
1249
|
+
throw new ToolValidationError({
|
|
1250
|
+
message: `grep: ${compiled.reason}`,
|
|
1251
|
+
field: "pattern"
|
|
1252
|
+
});
|
|
1143
1253
|
}
|
|
1144
1254
|
const re = compiled.regex;
|
|
1145
1255
|
const globRe = input.glob ? compileGlob(input.glob) : null;
|
|
@@ -2971,10 +3081,10 @@ var bashTool = {
|
|
|
2971
3081
|
queue.push(c);
|
|
2972
3082
|
}
|
|
2973
3083
|
};
|
|
2974
|
-
const next = () => new Promise((
|
|
3084
|
+
const next = () => new Promise((resolve7) => {
|
|
2975
3085
|
const c = queue.shift();
|
|
2976
|
-
if (c)
|
|
2977
|
-
else resolveNext =
|
|
3086
|
+
if (c) resolve7(c);
|
|
3087
|
+
else resolveNext = resolve7;
|
|
2978
3088
|
});
|
|
2979
3089
|
let lastFlush = Date.now();
|
|
2980
3090
|
const flush = () => {
|
|
@@ -3435,7 +3545,7 @@ var execTool = {
|
|
|
3435
3545
|
}
|
|
3436
3546
|
};
|
|
3437
3547
|
function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
3438
|
-
return new Promise((
|
|
3548
|
+
return new Promise((resolve7) => {
|
|
3439
3549
|
let stdout = "";
|
|
3440
3550
|
let stderr = "";
|
|
3441
3551
|
let killed = false;
|
|
@@ -3443,7 +3553,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
3443
3553
|
const finish = (result) => {
|
|
3444
3554
|
if (resolvedOnce.value) return;
|
|
3445
3555
|
resolvedOnce.value = true;
|
|
3446
|
-
|
|
3556
|
+
resolve7(result);
|
|
3447
3557
|
};
|
|
3448
3558
|
const startedAt = Date.now();
|
|
3449
3559
|
const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT2 });
|
|
@@ -3620,10 +3730,16 @@ async function guardedFetch(url, maxRedirects, signal, headers = {
|
|
|
3620
3730
|
for (; ; ) {
|
|
3621
3731
|
const parsed = new URL(currentUrl);
|
|
3622
3732
|
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
3623
|
-
throw new
|
|
3733
|
+
throw new ToolValidationError({
|
|
3734
|
+
message: `fetch: redirect to unsupported protocol "${parsed.protocol}"`,
|
|
3735
|
+
field: "url"
|
|
3736
|
+
});
|
|
3624
3737
|
}
|
|
3625
3738
|
if (parsed.protocol === "http:" && !ALLOW_PRIVATE) {
|
|
3626
|
-
throw new
|
|
3739
|
+
throw new ToolValidationError({
|
|
3740
|
+
message: "fetch: redirect to http:// blocked (HTTPS required by default)",
|
|
3741
|
+
field: "url"
|
|
3742
|
+
});
|
|
3627
3743
|
}
|
|
3628
3744
|
await assertNotPrivate(parsed.hostname);
|
|
3629
3745
|
const init = {
|
|
@@ -3638,11 +3754,19 @@ async function guardedFetch(url, maxRedirects, signal, headers = {
|
|
|
3638
3754
|
}
|
|
3639
3755
|
redirectCount++;
|
|
3640
3756
|
if (redirectCount > maxRedirects) {
|
|
3641
|
-
throw new
|
|
3757
|
+
throw new FetchError({
|
|
3758
|
+
message: `fetch: exceeded ${maxRedirects} redirects`,
|
|
3759
|
+
status: res.status,
|
|
3760
|
+
context: { url: currentUrl, maxRedirects, redirectCount }
|
|
3761
|
+
});
|
|
3642
3762
|
}
|
|
3643
3763
|
const location = res.headers.get("location");
|
|
3644
3764
|
if (!location) {
|
|
3645
|
-
throw new
|
|
3765
|
+
throw new FetchError({
|
|
3766
|
+
message: "fetch: redirect status with no location header",
|
|
3767
|
+
status: res.status,
|
|
3768
|
+
context: { url: currentUrl, redirectCount }
|
|
3769
|
+
});
|
|
3646
3770
|
}
|
|
3647
3771
|
currentUrl = new URL(location, currentUrl).toString();
|
|
3648
3772
|
}
|
|
@@ -3681,26 +3805,53 @@ var fetchTool = {
|
|
|
3681
3805
|
async execute(input, ctx, opts) {
|
|
3682
3806
|
let final;
|
|
3683
3807
|
const executeStream = fetchTool.executeStream;
|
|
3684
|
-
if (!executeStream)
|
|
3808
|
+
if (!executeStream) {
|
|
3809
|
+
throw new ToolError({
|
|
3810
|
+
message: "fetchTool: stream execution unavailable",
|
|
3811
|
+
code: "TOOL_EXECUTION_FAILED",
|
|
3812
|
+
toolName: "fetch"
|
|
3813
|
+
});
|
|
3814
|
+
}
|
|
3685
3815
|
for await (const ev of executeStream(input, ctx, opts)) {
|
|
3686
3816
|
if (ev.type === "final") final = ev.output;
|
|
3687
3817
|
}
|
|
3688
|
-
if (!final)
|
|
3818
|
+
if (!final) {
|
|
3819
|
+
throw new ToolError({
|
|
3820
|
+
message: "fetch: stream ended without final event",
|
|
3821
|
+
code: "TOOL_EXECUTION_FAILED",
|
|
3822
|
+
toolName: "fetch"
|
|
3823
|
+
});
|
|
3824
|
+
}
|
|
3689
3825
|
return final;
|
|
3690
3826
|
},
|
|
3691
3827
|
async *executeStream(input, ctx, opts) {
|
|
3692
|
-
if (!input?.url)
|
|
3828
|
+
if (!input?.url) {
|
|
3829
|
+
throw new ToolValidationError({
|
|
3830
|
+
message: "fetch: url is required",
|
|
3831
|
+
field: "url"
|
|
3832
|
+
});
|
|
3833
|
+
}
|
|
3693
3834
|
const u = new URL(input.url);
|
|
3694
3835
|
if (u.protocol !== "https:" && u.protocol !== "http:") {
|
|
3695
|
-
throw new
|
|
3836
|
+
throw new ToolValidationError({
|
|
3837
|
+
message: `fetch: unsupported protocol "${u.protocol}"`,
|
|
3838
|
+
field: "url"
|
|
3839
|
+
});
|
|
3696
3840
|
}
|
|
3697
3841
|
if (u.protocol === "http:" && !ALLOW_PRIVATE) {
|
|
3698
|
-
throw new
|
|
3842
|
+
throw new ToolValidationError({
|
|
3843
|
+
message: "fetch: http:// blocked (HTTPS required by default)",
|
|
3844
|
+
field: "url"
|
|
3845
|
+
});
|
|
3699
3846
|
}
|
|
3700
3847
|
await assertNotPrivate(u.hostname);
|
|
3701
3848
|
yield { type: "log", text: `GET ${input.url}` };
|
|
3702
3849
|
const ctrl = new AbortController();
|
|
3703
|
-
const timer = setTimeout(() => ctrl.abort(new
|
|
3850
|
+
const timer = setTimeout(() => ctrl.abort(new ToolError({
|
|
3851
|
+
message: "fetch timeout",
|
|
3852
|
+
code: "TOOL_TIMEOUT",
|
|
3853
|
+
toolName: "fetch"
|
|
3854
|
+
})), TIMEOUT_MS);
|
|
3704
3855
|
const combined = combineSignals([opts.signal, ctrl.signal]);
|
|
3705
3856
|
try {
|
|
3706
3857
|
let res;
|
|
@@ -3712,7 +3863,11 @@ var fetchTool = {
|
|
|
3712
3863
|
}
|
|
3713
3864
|
const ct = res.headers.get("content-type") ?? "application/octet-stream";
|
|
3714
3865
|
if (/^image\/|^audio\/|^video\/|application\/octet-stream/.test(ct)) {
|
|
3715
|
-
throw new
|
|
3866
|
+
throw new FetchError({
|
|
3867
|
+
message: `fetch: refusing to read binary content-type "${ct}"`,
|
|
3868
|
+
status: res.status,
|
|
3869
|
+
context: { url: res.url, contentType: ct }
|
|
3870
|
+
});
|
|
3716
3871
|
}
|
|
3717
3872
|
yield {
|
|
3718
3873
|
type: "log",
|
|
@@ -3777,16 +3932,25 @@ async function assertNotPrivate(hostname4) {
|
|
|
3777
3932
|
if (ALLOW_PRIVATE) return;
|
|
3778
3933
|
const host = hostname4.startsWith("[") && hostname4.endsWith("]") ? hostname4.slice(1, -1) : hostname4;
|
|
3779
3934
|
if (host === "localhost" || host.endsWith(".localhost")) {
|
|
3780
|
-
throw new
|
|
3935
|
+
throw new ToolValidationError({
|
|
3936
|
+
message: "fetch: blocked localhost target",
|
|
3937
|
+
field: "url"
|
|
3938
|
+
});
|
|
3781
3939
|
}
|
|
3782
3940
|
const ipVersion = net.isIP(host);
|
|
3783
3941
|
if (ipVersion === 4) {
|
|
3784
3942
|
if (isPrivateIPv4(host)) {
|
|
3785
|
-
throw new
|
|
3943
|
+
throw new ToolValidationError({
|
|
3944
|
+
message: `fetch: blocked private/loopback address "${host}"`,
|
|
3945
|
+
field: "url"
|
|
3946
|
+
});
|
|
3786
3947
|
}
|
|
3787
3948
|
} else if (ipVersion === 6) {
|
|
3788
3949
|
if (isPrivateIPv6(host)) {
|
|
3789
|
-
throw new
|
|
3950
|
+
throw new ToolValidationError({
|
|
3951
|
+
message: `fetch: blocked private/loopback address "${host}"`,
|
|
3952
|
+
field: "url"
|
|
3953
|
+
});
|
|
3790
3954
|
}
|
|
3791
3955
|
} else {
|
|
3792
3956
|
try {
|
|
@@ -3794,7 +3958,10 @@ async function assertNotPrivate(hostname4) {
|
|
|
3794
3958
|
for (const r of records) {
|
|
3795
3959
|
const bad = r.family === 4 ? isPrivateIPv4(r.address) : isPrivateIPv6(r.address);
|
|
3796
3960
|
if (bad) {
|
|
3797
|
-
throw new
|
|
3961
|
+
throw new ToolValidationError({
|
|
3962
|
+
message: `fetch: resolved to private address ${r.address}`,
|
|
3963
|
+
field: "url"
|
|
3964
|
+
});
|
|
3798
3965
|
}
|
|
3799
3966
|
}
|
|
3800
3967
|
} catch (err) {
|
|
@@ -3804,7 +3971,13 @@ async function assertNotPrivate(hostname4) {
|
|
|
3804
3971
|
}
|
|
3805
3972
|
function describeFetchError(err, url, timedOut) {
|
|
3806
3973
|
if (timedOut) {
|
|
3807
|
-
return new
|
|
3974
|
+
return new ToolError({
|
|
3975
|
+
message: `fetch: GET ${url} timed out after ${TIMEOUT_MS}ms`,
|
|
3976
|
+
code: "TOOL_TIMEOUT",
|
|
3977
|
+
toolName: "fetch",
|
|
3978
|
+
context: { url, timedOut: true, timeoutMs: TIMEOUT_MS },
|
|
3979
|
+
cause: err
|
|
3980
|
+
});
|
|
3808
3981
|
}
|
|
3809
3982
|
const parts = [];
|
|
3810
3983
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3817,7 +3990,15 @@ function describeFetchError(err, url, timedOut) {
|
|
|
3817
3990
|
cur = cur.cause;
|
|
3818
3991
|
}
|
|
3819
3992
|
const detail = parts.length > 0 ? parts.join(" \u2192 ") : "fetch failed";
|
|
3820
|
-
return new
|
|
3993
|
+
return new FetchError({
|
|
3994
|
+
message: `fetch: GET ${url} failed \u2014 ${detail}`,
|
|
3995
|
+
status: 502,
|
|
3996
|
+
context: { url, timedOut: false, transportErrors: parts },
|
|
3997
|
+
// Preserve the original undici / DNS / TLS chain so callers can inspect
|
|
3998
|
+
// it via `err.cause` and structured `instanceof` checks. The flattened
|
|
3999
|
+
// text version stays in the message for human readability.
|
|
4000
|
+
cause: err
|
|
4001
|
+
});
|
|
3821
4002
|
}
|
|
3822
4003
|
function prettyJson(s) {
|
|
3823
4004
|
try {
|
|
@@ -3829,11 +4010,13 @@ function prettyJson(s) {
|
|
|
3829
4010
|
var DEFAULT_NUM = 10;
|
|
3830
4011
|
var MAX_RESULTS = 50;
|
|
3831
4012
|
var TIMEOUT_MS2 = 15e3;
|
|
4013
|
+
var CACHE_TTL_MS = 3e5;
|
|
4014
|
+
var cache = /* @__PURE__ */ new Map();
|
|
3832
4015
|
var searchTool = {
|
|
3833
4016
|
name: "search",
|
|
3834
4017
|
category: "Search",
|
|
3835
|
-
description: "Perform a web search and return results with title, URL, and snippet. Use this when you need up-to-date external information that is not in the local codebase.",
|
|
3836
|
-
usageHint: "Good for: API documentation, error messages, library usage examples, current best practices.\n\n- Prefer specific queries over very broad ones.\n- Results go through the guarded fetch system (same protections as the `fetch` tool).\n- This is often better than the model trying to recall outdated knowledge.",
|
|
4018
|
+
description: "Perform a web search and return results with title, URL, and snippet. Use this when you need up-to-date external information that is not in the local codebase. Results are cached (5 min TTL) and deduplicated by URL.",
|
|
4019
|
+
usageHint: "Good for: API documentation, error messages, library usage examples, current best practices.\n\n- Prefer specific queries over very broad ones.\n- Results go through the guarded fetch system (same protections as the `fetch` tool).\n- Supports duckduckgo (default), google, and bing sources.\n- Set `skip_cache: true` to force a fresh search.\n- This is often better than the model trying to recall outdated knowledge.",
|
|
3837
4020
|
permission: "confirm",
|
|
3838
4021
|
mutating: false,
|
|
3839
4022
|
capabilities: ["net.outbound"],
|
|
@@ -3853,6 +4036,10 @@ var searchTool = {
|
|
|
3853
4036
|
type: "string",
|
|
3854
4037
|
enum: ["duckduckgo", "google", "bing"],
|
|
3855
4038
|
description: "Search engine to use (default: duckduckgo)"
|
|
4039
|
+
},
|
|
4040
|
+
skip_cache: {
|
|
4041
|
+
type: "boolean",
|
|
4042
|
+
description: "Skip the in-memory cache and force a fresh search (default: false)"
|
|
3856
4043
|
}
|
|
3857
4044
|
},
|
|
3858
4045
|
required: ["query"]
|
|
@@ -3868,57 +4055,135 @@ var searchTool = {
|
|
|
3868
4055
|
return final;
|
|
3869
4056
|
},
|
|
3870
4057
|
async *executeStream(input, _ctx, opts) {
|
|
3871
|
-
if (!input?.query
|
|
4058
|
+
if (!input?.query || input.query.trim() === "") {
|
|
4059
|
+
throw new ToolValidationError({
|
|
4060
|
+
message: "search: query is required and must be a non-empty string",
|
|
4061
|
+
field: "query"
|
|
4062
|
+
});
|
|
4063
|
+
}
|
|
3872
4064
|
const num = Math.max(1, Math.min(input.num_results ?? DEFAULT_NUM, MAX_RESULTS));
|
|
3873
4065
|
const source = input.source ?? "duckduckgo";
|
|
4066
|
+
const skipCache = input.skip_cache ?? false;
|
|
4067
|
+
const cacheKey = `${source}:${input.query}`;
|
|
4068
|
+
if (!skipCache) {
|
|
4069
|
+
const entry = cache.get(cacheKey);
|
|
4070
|
+
if (entry && Date.now() - entry.timestamp < CACHE_TTL_MS) {
|
|
4071
|
+
const results = entry.results.map((r) => ({
|
|
4072
|
+
title: r.title,
|
|
4073
|
+
url: r.url,
|
|
4074
|
+
snippet: r.snippet
|
|
4075
|
+
}));
|
|
4076
|
+
yield {
|
|
4077
|
+
type: "log",
|
|
4078
|
+
text: `Cache hit for "${input.query}" (${source})`,
|
|
4079
|
+
data: { source, query: input.query, cached: true }
|
|
4080
|
+
};
|
|
4081
|
+
yield {
|
|
4082
|
+
type: "partial_output",
|
|
4083
|
+
text: `${results.length} cached results from ${source}`,
|
|
4084
|
+
data: { count: results.length, cached: true }
|
|
4085
|
+
};
|
|
4086
|
+
yield {
|
|
4087
|
+
type: "final",
|
|
4088
|
+
output: {
|
|
4089
|
+
query: input.query,
|
|
4090
|
+
results: results.slice(0, num),
|
|
4091
|
+
source,
|
|
4092
|
+
truncated: results.length >= num,
|
|
4093
|
+
cached: true
|
|
4094
|
+
}
|
|
4095
|
+
};
|
|
4096
|
+
return;
|
|
4097
|
+
}
|
|
4098
|
+
}
|
|
3874
4099
|
yield {
|
|
3875
4100
|
type: "log",
|
|
3876
4101
|
text: `Querying ${source} for "${input.query}"\u2026`,
|
|
3877
|
-
data: { source, query: input.query }
|
|
4102
|
+
data: { source, query: input.query, cached: false }
|
|
3878
4103
|
};
|
|
3879
|
-
let
|
|
4104
|
+
let rawResults;
|
|
3880
4105
|
switch (source) {
|
|
3881
4106
|
case "duckduckgo":
|
|
3882
|
-
|
|
4107
|
+
rawResults = await duckduckgoSearch(input.query, num, opts.signal);
|
|
3883
4108
|
break;
|
|
3884
4109
|
case "google":
|
|
3885
|
-
|
|
4110
|
+
rawResults = await googleSearch(input.query, num, opts.signal);
|
|
3886
4111
|
break;
|
|
3887
4112
|
case "bing":
|
|
3888
|
-
|
|
4113
|
+
rawResults = await bingSearch(input.query, num, opts.signal);
|
|
3889
4114
|
break;
|
|
3890
4115
|
default:
|
|
3891
|
-
throw new
|
|
4116
|
+
throw new ToolValidationError({
|
|
4117
|
+
message: `search: unknown source "${source}"`,
|
|
4118
|
+
field: "source"
|
|
4119
|
+
});
|
|
3892
4120
|
}
|
|
4121
|
+
const seenUrls = /* @__PURE__ */ new Set();
|
|
4122
|
+
const deduped = [];
|
|
4123
|
+
for (const r of rawResults) {
|
|
4124
|
+
const noQuery = r.url.split("?")[0] ?? r.url;
|
|
4125
|
+
const normalized = noQuery.split("#")[0] ?? r.url;
|
|
4126
|
+
if (!seenUrls.has(normalized) && r.url.startsWith("http")) {
|
|
4127
|
+
seenUrls.add(normalized);
|
|
4128
|
+
deduped.push(r);
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
const ranked = scoreResults(deduped, input.query);
|
|
4132
|
+
const finalResults = ranked.slice(0, num);
|
|
4133
|
+
cache.set(cacheKey, { results: ranked, timestamp: Date.now() });
|
|
4134
|
+
pruneStaleCacheEntries();
|
|
3893
4135
|
yield {
|
|
3894
4136
|
type: "partial_output",
|
|
3895
|
-
text: `${
|
|
3896
|
-
data: { count:
|
|
4137
|
+
text: `${finalResults.length} results from ${source}`,
|
|
4138
|
+
data: { count: finalResults.length, cached: false }
|
|
4139
|
+
};
|
|
4140
|
+
yield {
|
|
4141
|
+
type: "final",
|
|
4142
|
+
output: {
|
|
4143
|
+
query: input.query,
|
|
4144
|
+
results: finalResults.map((r) => ({
|
|
4145
|
+
title: r.title,
|
|
4146
|
+
url: r.url,
|
|
4147
|
+
snippet: r.snippet
|
|
4148
|
+
})),
|
|
4149
|
+
source,
|
|
4150
|
+
truncated: finalResults.length >= num,
|
|
4151
|
+
cached: false
|
|
4152
|
+
}
|
|
3897
4153
|
};
|
|
3898
|
-
yield { type: "final", output };
|
|
3899
4154
|
}
|
|
3900
4155
|
};
|
|
3901
|
-
|
|
3902
|
-
const
|
|
4156
|
+
function pruneStaleCacheEntries() {
|
|
4157
|
+
const cutoff = Date.now() - CACHE_TTL_MS * 2;
|
|
4158
|
+
for (const [key, entry] of cache.entries()) {
|
|
4159
|
+
if (entry.timestamp < cutoff) cache.delete(key);
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4162
|
+
function scoreResults(results, query) {
|
|
4163
|
+
const terms = query.toLowerCase().split(/\s+/).filter((t) => t.length > 0);
|
|
4164
|
+
return results.map((r) => {
|
|
4165
|
+
const titleLower = r.title.toLowerCase();
|
|
4166
|
+
const snippetLower = r.snippet.toLowerCase();
|
|
4167
|
+
let score = r.score;
|
|
4168
|
+
for (const term of terms) {
|
|
4169
|
+
if (titleLower.includes(term)) score += 2;
|
|
4170
|
+
if (snippetLower.includes(term)) score += 1;
|
|
4171
|
+
}
|
|
4172
|
+
return { ...r, score };
|
|
4173
|
+
}).sort((a, b) => b.score - a.score);
|
|
4174
|
+
}
|
|
4175
|
+
async function duckduckgoSearch(query, num, signal) {
|
|
4176
|
+
const encoded = encodeURIComponent(query);
|
|
3903
4177
|
const url = `https://lite.duckduckgo.com/lite/?q=${encoded}&kd=-1&kl=wt-wt`;
|
|
3904
4178
|
try {
|
|
3905
4179
|
const response = await fetchWithTimeout(url, signal, TIMEOUT_MS2);
|
|
3906
4180
|
const html = await response.text();
|
|
3907
|
-
|
|
3908
|
-
return {
|
|
3909
|
-
query: query2,
|
|
3910
|
-
results,
|
|
3911
|
-
source: "duckduckgo",
|
|
3912
|
-
truncated: results.length >= num
|
|
3913
|
-
};
|
|
4181
|
+
return parseDuckDuckGo(html, num);
|
|
3914
4182
|
} catch (err) {
|
|
3915
|
-
console.log(
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
source: "duckduckgo",
|
|
3920
|
-
truncated: false
|
|
3921
|
-
};
|
|
4183
|
+
console.log(
|
|
4184
|
+
JSON.stringify({ level: "debug", event: "search_failed", query, error: toErrorMessage$2(err) })
|
|
4185
|
+
);
|
|
4186
|
+
return [{ title: "Search unavailable", url: "https://duckduckgo.com/unavailable", snippet: "Could not reach DuckDuckGo", score: 0 }];
|
|
3922
4187
|
}
|
|
3923
4188
|
}
|
|
3924
4189
|
function takeFrom(iter, max) {
|
|
@@ -3943,25 +4208,22 @@ function parseDuckDuckGo(html, num) {
|
|
|
3943
4208
|
);
|
|
3944
4209
|
for (let i = 0; i < linkMatches.length && i < num; i++) {
|
|
3945
4210
|
const entry = linkMatches[i];
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
4211
|
+
if (entry) {
|
|
4212
|
+
results.push({
|
|
4213
|
+
title: entry.title ?? "",
|
|
4214
|
+
url: entry.url ?? "",
|
|
4215
|
+
snippet: snippetMatches[i] ?? "",
|
|
4216
|
+
score: 1
|
|
4217
|
+
});
|
|
4218
|
+
}
|
|
3951
4219
|
}
|
|
3952
4220
|
return results;
|
|
3953
4221
|
}
|
|
3954
|
-
async function googleSearch(
|
|
3955
|
-
const encoded = encodeURIComponent(
|
|
4222
|
+
async function googleSearch(query, num, signal) {
|
|
4223
|
+
const encoded = encodeURIComponent(query);
|
|
3956
4224
|
const url = `https://www.google.com/search?q=${encoded}&hl=en`;
|
|
3957
4225
|
const html = await fetchWithTimeout(url, signal, TIMEOUT_MS2).then((r) => r.text()).catch(() => "");
|
|
3958
|
-
|
|
3959
|
-
return {
|
|
3960
|
-
query: query2,
|
|
3961
|
-
results,
|
|
3962
|
-
source: "google",
|
|
3963
|
-
truncated: results.length >= num
|
|
3964
|
-
};
|
|
4226
|
+
return parseGoogleResults(html, num);
|
|
3965
4227
|
}
|
|
3966
4228
|
function parseGoogleResults(html, num) {
|
|
3967
4229
|
const results = [];
|
|
@@ -3984,22 +4246,17 @@ function parseGoogleResults(html, num) {
|
|
|
3984
4246
|
results.push({
|
|
3985
4247
|
title: titles[i] ?? "",
|
|
3986
4248
|
url: urls[i] ?? "",
|
|
3987
|
-
snippet: snippets[i] ?? ""
|
|
4249
|
+
snippet: snippets[i] ?? "",
|
|
4250
|
+
score: 1
|
|
3988
4251
|
});
|
|
3989
4252
|
}
|
|
3990
4253
|
return results;
|
|
3991
4254
|
}
|
|
3992
|
-
async function bingSearch(
|
|
3993
|
-
const encoded = encodeURIComponent(
|
|
4255
|
+
async function bingSearch(query, num, signal) {
|
|
4256
|
+
const encoded = encodeURIComponent(query);
|
|
3994
4257
|
const url = `https://www.bing.com/search?q=${encoded}`;
|
|
3995
4258
|
const html = await fetchWithTimeout(url, signal, TIMEOUT_MS2).then((r) => r.text()).catch(() => "");
|
|
3996
|
-
|
|
3997
|
-
return {
|
|
3998
|
-
query: query2,
|
|
3999
|
-
results,
|
|
4000
|
-
source: "bing",
|
|
4001
|
-
truncated: results.length >= num
|
|
4002
|
-
};
|
|
4259
|
+
return parseBingResults(html, num);
|
|
4003
4260
|
}
|
|
4004
4261
|
function parseBingResults(html, num) {
|
|
4005
4262
|
const results = [];
|
|
@@ -4014,11 +4271,15 @@ function parseBingResults(html, num) {
|
|
|
4014
4271
|
num
|
|
4015
4272
|
);
|
|
4016
4273
|
for (let i = 0; i < entries.length; i++) {
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4274
|
+
const entry = entries[i];
|
|
4275
|
+
if (entry) {
|
|
4276
|
+
results.push({
|
|
4277
|
+
title: entry.title ?? "",
|
|
4278
|
+
url: entry.url ?? "",
|
|
4279
|
+
snippet: snippets[i] ?? "",
|
|
4280
|
+
score: 1
|
|
4281
|
+
});
|
|
4282
|
+
}
|
|
4022
4283
|
}
|
|
4023
4284
|
return results;
|
|
4024
4285
|
}
|
|
@@ -4034,7 +4295,15 @@ async function fetchWithTimeout(url, signal, timeoutMs) {
|
|
|
4034
4295
|
return res;
|
|
4035
4296
|
} catch (e) {
|
|
4036
4297
|
clearTimeout(timer);
|
|
4037
|
-
|
|
4298
|
+
if (e instanceof FetchError) {
|
|
4299
|
+
throw e;
|
|
4300
|
+
}
|
|
4301
|
+
throw new FetchError({
|
|
4302
|
+
message: `search: failed to fetch ${url}`,
|
|
4303
|
+
status: 0,
|
|
4304
|
+
context: { url },
|
|
4305
|
+
cause: e
|
|
4306
|
+
});
|
|
4038
4307
|
}
|
|
4039
4308
|
}
|
|
4040
4309
|
function anySignal(...signals) {
|
|
@@ -4647,7 +4916,7 @@ function buildArgs(input) {
|
|
|
4647
4916
|
}
|
|
4648
4917
|
}
|
|
4649
4918
|
function runGit(args, cwd, signal) {
|
|
4650
|
-
return new Promise((
|
|
4919
|
+
return new Promise((resolve7) => {
|
|
4651
4920
|
let stdout = "";
|
|
4652
4921
|
let stderr = "";
|
|
4653
4922
|
const child = spawn("git", args, {
|
|
@@ -4668,7 +4937,7 @@ function runGit(args, cwd, signal) {
|
|
|
4668
4937
|
}
|
|
4669
4938
|
});
|
|
4670
4939
|
child.on("error", (err) => {
|
|
4671
|
-
|
|
4940
|
+
resolve7({
|
|
4672
4941
|
command: args[0],
|
|
4673
4942
|
stdout: normalizeCommandOutput(stdout),
|
|
4674
4943
|
stderr: err.message,
|
|
@@ -4677,7 +4946,7 @@ function runGit(args, cwd, signal) {
|
|
|
4677
4946
|
});
|
|
4678
4947
|
});
|
|
4679
4948
|
child.on("close", (code) => {
|
|
4680
|
-
|
|
4949
|
+
resolve7({
|
|
4681
4950
|
command: args[0],
|
|
4682
4951
|
stdout: normalizeCommandOutput(stdout),
|
|
4683
4952
|
stderr: normalizeCommandOutput(stderr),
|
|
@@ -4777,7 +5046,7 @@ function stripPathComponents(p, strip) {
|
|
|
4777
5046
|
return parts.slice(strip).join("/");
|
|
4778
5047
|
}
|
|
4779
5048
|
function runPatch(args, cwd, signal) {
|
|
4780
|
-
return new Promise((
|
|
5049
|
+
return new Promise((resolve7) => {
|
|
4781
5050
|
let stdout = "";
|
|
4782
5051
|
let stderr = "";
|
|
4783
5052
|
const env = { ...buildChildEnv(), LANG: "C", LC_ALL: "C" };
|
|
@@ -4788,8 +5057,8 @@ function runPatch(args, cwd, signal) {
|
|
|
4788
5057
|
child.stderr?.on("data", (c) => {
|
|
4789
5058
|
stderr += c.toString();
|
|
4790
5059
|
});
|
|
4791
|
-
child.on("close", (code) =>
|
|
4792
|
-
child.on("error", (e) =>
|
|
5060
|
+
child.on("close", (code) => resolve7({ exitCode: code ?? 1, stdout, stderr }));
|
|
5061
|
+
child.on("error", (e) => resolve7({ exitCode: 1, stdout: "", stderr: e.message }));
|
|
4793
5062
|
});
|
|
4794
5063
|
}
|
|
4795
5064
|
function extractPatchedFiles(output) {
|
|
@@ -4803,8 +5072,8 @@ function extractPatchedFiles(output) {
|
|
|
4803
5072
|
var jsonTool = {
|
|
4804
5073
|
name: "json",
|
|
4805
5074
|
category: "Data",
|
|
4806
|
-
description: "Parse, pretty-print, query,
|
|
4807
|
-
usageHint:
|
|
5075
|
+
description: "Parse, pretty-print, query, validate, transform, and merge JSON/JSON5/YAML. Use `action` to select the operation: parse (default), query, validate, transform, or merge.",
|
|
5076
|
+
usageHint: 'VERY USEFUL FOR DATA INSPECTION:\n\n- `action: "parse"` (default): read/pretty-print/convert JSON, JSON5, or YAML from `file` or `data`.\n- `action: "query"`: JMESPath-like query (`a.b[0].c`, `items[*].name`, filters, functions).\n- `action: "validate"`: validate data against a JSON Schema (`schema` param).\n- `action: "transform"`: chain multiple JMESPath transforms (`transforms` param).\n- `action: "merge"`: deep merge `base` and `patch` objects (`conflictResolution` param).\nPrefer this over raw `read` + manual parsing when dealing with configuration or data files.',
|
|
4808
5077
|
permission: "auto",
|
|
4809
5078
|
mutating: false,
|
|
4810
5079
|
timeoutMs: 5e3,
|
|
@@ -4813,69 +5082,420 @@ var jsonTool = {
|
|
|
4813
5082
|
inputSchema: {
|
|
4814
5083
|
type: "object",
|
|
4815
5084
|
properties: {
|
|
4816
|
-
|
|
4817
|
-
data: { type: "string", description: "JSON/JSON5/YAML string (alternative to file)" },
|
|
4818
|
-
query: {
|
|
5085
|
+
action: {
|
|
4819
5086
|
type: "string",
|
|
4820
|
-
|
|
5087
|
+
enum: ["parse", "query", "validate", "transform", "merge"],
|
|
5088
|
+
description: "Operation (default: parse). parse=read/pretty-print, query=JMESPath, validate=schema, transform=chained queries, merge=deep merge."
|
|
4821
5089
|
},
|
|
5090
|
+
file: { type: "string", description: "Path to JSON/JSON5/YAML file (parse/query/validate)" },
|
|
5091
|
+
data: { type: "string", description: "JSON/JSON5/YAML string (parse/query/validate, alternative to file)" },
|
|
4822
5092
|
format: {
|
|
4823
5093
|
type: "string",
|
|
4824
5094
|
enum: ["json", "json5", "yaml"],
|
|
4825
|
-
description: "Output format (default: json)"
|
|
5095
|
+
description: "Output format for parse/query/transform (default: json)"
|
|
5096
|
+
},
|
|
5097
|
+
query: {
|
|
5098
|
+
type: "string",
|
|
5099
|
+
description: "JMESPath-like query expression (query action)"
|
|
5100
|
+
},
|
|
5101
|
+
transforms: {
|
|
5102
|
+
type: "array",
|
|
5103
|
+
items: { type: "string" },
|
|
5104
|
+
description: "Ordered JMESPath query strings (transform action)"
|
|
5105
|
+
},
|
|
5106
|
+
schema: {
|
|
5107
|
+
type: "object",
|
|
5108
|
+
description: "JSON Schema to validate against (validate action)"
|
|
5109
|
+
},
|
|
5110
|
+
base: { description: "Base JSON object (merge action)" },
|
|
5111
|
+
patch: { description: "Patch JSON object to merge in (merge action)" },
|
|
5112
|
+
conflictResolution: {
|
|
5113
|
+
type: "string",
|
|
5114
|
+
enum: ["prefer-base", "prefer-patch"],
|
|
5115
|
+
description: "Merge conflict resolution (default: prefer-patch)"
|
|
4826
5116
|
},
|
|
4827
5117
|
validate: {
|
|
4828
5118
|
type: "boolean",
|
|
4829
|
-
description: "Validate syntax only, no output (default: false)"
|
|
5119
|
+
description: "Validate syntax only, no output (parse action, default: false)"
|
|
4830
5120
|
}
|
|
4831
5121
|
}
|
|
4832
5122
|
},
|
|
4833
|
-
async execute(input) {
|
|
4834
|
-
const
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
return
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
5123
|
+
async execute(input, ctx) {
|
|
5124
|
+
const action = input.action ?? "parse";
|
|
5125
|
+
switch (action) {
|
|
5126
|
+
case "query":
|
|
5127
|
+
return executeQuery(input, ctx);
|
|
5128
|
+
case "validate":
|
|
5129
|
+
return executeValidate(input, ctx);
|
|
5130
|
+
case "transform":
|
|
5131
|
+
return executeTransform(input, ctx);
|
|
5132
|
+
case "merge":
|
|
5133
|
+
return executeMerge(input);
|
|
5134
|
+
case "parse":
|
|
5135
|
+
default:
|
|
5136
|
+
return executeParse(input, ctx);
|
|
4847
5137
|
}
|
|
5138
|
+
}
|
|
5139
|
+
};
|
|
5140
|
+
async function executeParse(input, ctx) {
|
|
5141
|
+
const format = input.format ?? "json";
|
|
5142
|
+
let parsed;
|
|
5143
|
+
let raw;
|
|
5144
|
+
if (input.file) {
|
|
4848
5145
|
try {
|
|
4849
|
-
|
|
4850
|
-
} catch
|
|
4851
|
-
return {
|
|
4852
|
-
data: null,
|
|
4853
|
-
formatted: "",
|
|
4854
|
-
type: "unknown",
|
|
4855
|
-
/* v8 ignore next -- JSON.parse only throws SyntaxError (an Error); the String(e) side is defensive. */
|
|
4856
|
-
error: `Parse failed: ${e instanceof Error ? e.message : String(e)}`
|
|
4857
|
-
};
|
|
4858
|
-
}
|
|
4859
|
-
if (input.validate) {
|
|
4860
|
-
return {
|
|
4861
|
-
data: parsed,
|
|
4862
|
-
formatted: "valid",
|
|
4863
|
-
type: Array.isArray(parsed) ? "array" : typeof parsed,
|
|
4864
|
-
keys: typeof parsed === "object" && parsed !== null ? Object.keys(parsed) : void 0
|
|
4865
|
-
};
|
|
5146
|
+
raw = await fs7.readFile(await safeResolveReal(input.file, ctx), "utf8");
|
|
5147
|
+
} catch {
|
|
5148
|
+
return { data: null, formatted: "", type: "unknown", action: "parse", error: "Could not read file" };
|
|
4866
5149
|
}
|
|
4867
|
-
|
|
4868
|
-
|
|
5150
|
+
} else if (input.data) {
|
|
5151
|
+
raw = input.data;
|
|
5152
|
+
} else {
|
|
5153
|
+
return { data: null, formatted: "", type: "unknown", action: "parse", error: "Provide file or data" };
|
|
5154
|
+
}
|
|
5155
|
+
try {
|
|
5156
|
+
parsed = JSON.parse(raw);
|
|
5157
|
+
} catch (e) {
|
|
5158
|
+
return {
|
|
5159
|
+
data: null,
|
|
5160
|
+
formatted: "",
|
|
5161
|
+
type: "unknown",
|
|
5162
|
+
action: "parse",
|
|
5163
|
+
/* v8 ignore next -- JSON.parse only throws SyntaxError (an Error); the String(e) side is defensive. */
|
|
5164
|
+
error: `Parse failed: ${e instanceof Error ? e.message : String(e)}`
|
|
5165
|
+
};
|
|
5166
|
+
}
|
|
5167
|
+
if (input.validate) {
|
|
4869
5168
|
return {
|
|
4870
5169
|
data: parsed,
|
|
4871
|
-
formatted,
|
|
5170
|
+
formatted: "valid",
|
|
4872
5171
|
type: Array.isArray(parsed) ? "array" : typeof parsed,
|
|
5172
|
+
action: "parse",
|
|
5173
|
+
keys: typeof parsed === "object" && parsed !== null ? Object.keys(parsed) : void 0
|
|
5174
|
+
};
|
|
5175
|
+
}
|
|
5176
|
+
if (input.query) {
|
|
5177
|
+
const queryResult = simpleQuery(parsed, input.query);
|
|
5178
|
+
const formatted2 = formatOutput(queryResult, format);
|
|
5179
|
+
return {
|
|
5180
|
+
data: parsed,
|
|
5181
|
+
formatted: formatted2,
|
|
5182
|
+
type: Array.isArray(parsed) ? "array" : typeof parsed,
|
|
5183
|
+
action: "parse",
|
|
4873
5184
|
keys: typeof parsed === "object" && parsed !== null ? Object.keys(parsed) : void 0,
|
|
4874
5185
|
query_result: queryResult
|
|
4875
5186
|
};
|
|
4876
5187
|
}
|
|
4877
|
-
|
|
4878
|
-
|
|
5188
|
+
const formatted = formatOutput(parsed, format);
|
|
5189
|
+
return {
|
|
5190
|
+
data: parsed,
|
|
5191
|
+
formatted,
|
|
5192
|
+
type: Array.isArray(parsed) ? "array" : typeof parsed,
|
|
5193
|
+
action: "parse",
|
|
5194
|
+
keys: typeof parsed === "object" && parsed !== null ? Object.keys(parsed) : void 0
|
|
5195
|
+
};
|
|
5196
|
+
}
|
|
5197
|
+
async function executeQuery(input, ctx) {
|
|
5198
|
+
if (!input.query) {
|
|
5199
|
+
return { data: null, formatted: "", type: "unknown", action: "query", error: "query is required for action: query" };
|
|
5200
|
+
}
|
|
5201
|
+
let parsed;
|
|
5202
|
+
if (input.file) {
|
|
5203
|
+
try {
|
|
5204
|
+
const raw = await fs7.readFile(await safeResolveReal(input.file, ctx), "utf8");
|
|
5205
|
+
parsed = JSON.parse(raw);
|
|
5206
|
+
} catch {
|
|
5207
|
+
return { data: null, formatted: "", type: "unknown", action: "query", error: "Could not read/parse file" };
|
|
5208
|
+
}
|
|
5209
|
+
} else if (input.data) {
|
|
5210
|
+
try {
|
|
5211
|
+
parsed = JSON.parse(input.data);
|
|
5212
|
+
} catch {
|
|
5213
|
+
return { data: null, formatted: "", type: "unknown", action: "query", error: "Could not parse data string" };
|
|
5214
|
+
}
|
|
5215
|
+
} else {
|
|
5216
|
+
return { data: null, formatted: "", type: "unknown", action: "query", error: "Provide file or data" };
|
|
5217
|
+
}
|
|
5218
|
+
try {
|
|
5219
|
+
const result = jmespathSearch(parsed, input.query);
|
|
5220
|
+
const format = input.format ?? "json";
|
|
5221
|
+
return {
|
|
5222
|
+
data: parsed,
|
|
5223
|
+
formatted: formatOutput(result, format),
|
|
5224
|
+
type: result === null ? "null" : Array.isArray(result) ? "array" : typeof result,
|
|
5225
|
+
action: "query",
|
|
5226
|
+
query_result: result
|
|
5227
|
+
};
|
|
5228
|
+
} catch (e) {
|
|
5229
|
+
return {
|
|
5230
|
+
data: null,
|
|
5231
|
+
formatted: "",
|
|
5232
|
+
type: "unknown",
|
|
5233
|
+
action: "query",
|
|
5234
|
+
/* v8 ignore next -- defensive String(e) */
|
|
5235
|
+
error: `Query failed: ${e instanceof Error ? e.message : String(e)}`
|
|
5236
|
+
};
|
|
5237
|
+
}
|
|
5238
|
+
}
|
|
5239
|
+
async function executeValidate(input, ctx) {
|
|
5240
|
+
if (!input.schema) {
|
|
5241
|
+
return { data: null, formatted: "", type: "unknown", action: "validate", error: "schema is required for action: validate" };
|
|
5242
|
+
}
|
|
5243
|
+
let parsed;
|
|
5244
|
+
if (input.file) {
|
|
5245
|
+
try {
|
|
5246
|
+
const raw = await fs7.readFile(await safeResolveReal(input.file, ctx), "utf8");
|
|
5247
|
+
parsed = JSON.parse(raw);
|
|
5248
|
+
} catch {
|
|
5249
|
+
return { data: null, formatted: "", type: "unknown", action: "validate", error: "Could not read/parse file" };
|
|
5250
|
+
}
|
|
5251
|
+
} else if (input.data) {
|
|
5252
|
+
try {
|
|
5253
|
+
parsed = JSON.parse(input.data);
|
|
5254
|
+
} catch {
|
|
5255
|
+
return { data: null, formatted: "", type: "unknown", action: "validate", error: "Could not parse data string" };
|
|
5256
|
+
}
|
|
5257
|
+
} else {
|
|
5258
|
+
return { data: null, formatted: "", type: "unknown", action: "validate", error: "Provide file or data" };
|
|
5259
|
+
}
|
|
5260
|
+
try {
|
|
5261
|
+
const { valid, errors } = validateJsonSchema(parsed, input.schema);
|
|
5262
|
+
return {
|
|
5263
|
+
data: parsed,
|
|
5264
|
+
formatted: valid ? "valid" : "invalid",
|
|
5265
|
+
type: Array.isArray(parsed) ? "array" : typeof parsed,
|
|
5266
|
+
action: "validate",
|
|
5267
|
+
valid,
|
|
5268
|
+
errors
|
|
5269
|
+
};
|
|
5270
|
+
} catch (e) {
|
|
5271
|
+
return {
|
|
5272
|
+
data: null,
|
|
5273
|
+
formatted: "",
|
|
5274
|
+
type: "unknown",
|
|
5275
|
+
action: "validate",
|
|
5276
|
+
/* v8 ignore next -- defensive String(e) */
|
|
5277
|
+
error: `Validation failed: ${e instanceof Error ? e.message : String(e)}`
|
|
5278
|
+
};
|
|
5279
|
+
}
|
|
5280
|
+
}
|
|
5281
|
+
async function executeTransform(input, ctx) {
|
|
5282
|
+
if (!input.transforms || input.transforms.length === 0) {
|
|
5283
|
+
return { data: null, formatted: "", type: "unknown", action: "transform", error: "transforms array is required for action: transform" };
|
|
5284
|
+
}
|
|
5285
|
+
let parsed;
|
|
5286
|
+
if (input.file) {
|
|
5287
|
+
try {
|
|
5288
|
+
const raw = await fs7.readFile(await safeResolveReal(input.file, ctx), "utf8");
|
|
5289
|
+
parsed = JSON.parse(raw);
|
|
5290
|
+
} catch {
|
|
5291
|
+
return { data: null, formatted: "", type: "unknown", action: "transform", error: "Could not read/parse file" };
|
|
5292
|
+
}
|
|
5293
|
+
} else if (input.data) {
|
|
5294
|
+
try {
|
|
5295
|
+
parsed = JSON.parse(input.data);
|
|
5296
|
+
} catch {
|
|
5297
|
+
return { data: null, formatted: "", type: "unknown", action: "transform", error: "Could not parse data string" };
|
|
5298
|
+
}
|
|
5299
|
+
} else {
|
|
5300
|
+
return { data: null, formatted: "", type: "unknown", action: "transform", error: "Provide file or data" };
|
|
5301
|
+
}
|
|
5302
|
+
try {
|
|
5303
|
+
let current = parsed;
|
|
5304
|
+
const steps = [];
|
|
5305
|
+
for (const t of input.transforms) {
|
|
5306
|
+
current = jmespathSearch(current, t);
|
|
5307
|
+
steps.push({ transform: t, result: current });
|
|
5308
|
+
}
|
|
5309
|
+
const format = input.format ?? "json";
|
|
5310
|
+
return {
|
|
5311
|
+
data: parsed,
|
|
5312
|
+
formatted: formatOutput(current, format),
|
|
5313
|
+
type: current === null ? "null" : Array.isArray(current) ? "array" : typeof current,
|
|
5314
|
+
action: "transform",
|
|
5315
|
+
result: current,
|
|
5316
|
+
steps
|
|
5317
|
+
};
|
|
5318
|
+
} catch (e) {
|
|
5319
|
+
return {
|
|
5320
|
+
data: null,
|
|
5321
|
+
formatted: "",
|
|
5322
|
+
type: "unknown",
|
|
5323
|
+
action: "transform",
|
|
5324
|
+
/* v8 ignore next -- defensive String(e) */
|
|
5325
|
+
error: `Transform failed: ${e instanceof Error ? e.message : String(e)}`
|
|
5326
|
+
};
|
|
5327
|
+
}
|
|
5328
|
+
}
|
|
5329
|
+
async function executeMerge(input) {
|
|
5330
|
+
if (input.base === void 0 || input.patch === void 0) {
|
|
5331
|
+
return { data: null, formatted: "", type: "unknown", action: "merge", error: "base and patch are required for action: merge" };
|
|
5332
|
+
}
|
|
5333
|
+
const conflictResolution = input.conflictResolution ?? "prefer-patch";
|
|
5334
|
+
try {
|
|
5335
|
+
const result = deepMerge(input.base, input.patch, { conflictResolution });
|
|
5336
|
+
const format = input.format ?? "json";
|
|
5337
|
+
return {
|
|
5338
|
+
data: result,
|
|
5339
|
+
formatted: formatOutput(result, format),
|
|
5340
|
+
type: result === null ? "null" : Array.isArray(result) ? "array" : typeof result,
|
|
5341
|
+
action: "merge",
|
|
5342
|
+
result
|
|
5343
|
+
};
|
|
5344
|
+
} catch (e) {
|
|
5345
|
+
return {
|
|
5346
|
+
data: null,
|
|
5347
|
+
formatted: "",
|
|
5348
|
+
type: "unknown",
|
|
5349
|
+
action: "merge",
|
|
5350
|
+
/* v8 ignore next -- defensive String(e) */
|
|
5351
|
+
error: `Merge failed: ${e instanceof Error ? e.message : String(e)}`
|
|
5352
|
+
};
|
|
5353
|
+
}
|
|
5354
|
+
}
|
|
5355
|
+
function jmespathSearch(data, query) {
|
|
5356
|
+
if (!query || query === "@") return data;
|
|
5357
|
+
if (query === "$") return data;
|
|
5358
|
+
const dotMatch = query.match(/^([a-zA-Z_][a-zA-Z0-9_]*)(?:\.(.+))?$/);
|
|
5359
|
+
if (dotMatch) {
|
|
5360
|
+
const key = dotMatch[1];
|
|
5361
|
+
const rest = dotMatch[2];
|
|
5362
|
+
const val = data?.[key];
|
|
5363
|
+
if (rest === void 0) return val;
|
|
5364
|
+
return jmespathSearch(val, rest);
|
|
5365
|
+
}
|
|
5366
|
+
const arrMatch = query.match(/^\[(\d+)\](?:\.(.+))?$/);
|
|
5367
|
+
if (arrMatch) {
|
|
5368
|
+
const idx = Number.parseInt(arrMatch[1], 10);
|
|
5369
|
+
const rest = arrMatch[2];
|
|
5370
|
+
const arr = data;
|
|
5371
|
+
const val = arr?.[idx];
|
|
5372
|
+
if (rest === void 0) return val;
|
|
5373
|
+
return jmespathSearch(val, rest);
|
|
5374
|
+
}
|
|
5375
|
+
if (query === "[*]") {
|
|
5376
|
+
if (Array.isArray(data)) {
|
|
5377
|
+
return data;
|
|
5378
|
+
}
|
|
5379
|
+
return data;
|
|
5380
|
+
}
|
|
5381
|
+
const multiMatch = query.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\[\*\](?:\.(.+))?$/);
|
|
5382
|
+
if (multiMatch) {
|
|
5383
|
+
const key = multiMatch[1];
|
|
5384
|
+
const rest = multiMatch[2];
|
|
5385
|
+
const arr = data?.[key];
|
|
5386
|
+
if (!Array.isArray(arr)) return [];
|
|
5387
|
+
if (rest === void 0) return arr;
|
|
5388
|
+
return arr.map((item) => jmespathSearch(item, rest));
|
|
5389
|
+
}
|
|
5390
|
+
const filterMatch = query.match(/^\[\\?([a-zA-Z_][a-zA-Z0-9_]*)(==|!=|<|>|<=|>=)(`[^`]+`|'[^']*')\](?:\.(.+))?$/);
|
|
5391
|
+
if (filterMatch) {
|
|
5392
|
+
const field = filterMatch[1];
|
|
5393
|
+
const op = filterMatch[2];
|
|
5394
|
+
const rawVal = filterMatch[3];
|
|
5395
|
+
const rest = filterMatch[4];
|
|
5396
|
+
const cmpVal = JSON.parse(rawVal.slice(1, -1));
|
|
5397
|
+
const arr = data;
|
|
5398
|
+
if (!Array.isArray(arr)) return [];
|
|
5399
|
+
const filtered = arr.filter((item) => {
|
|
5400
|
+
const itemVal = item[field];
|
|
5401
|
+
switch (op) {
|
|
5402
|
+
case "==":
|
|
5403
|
+
return itemVal === cmpVal;
|
|
5404
|
+
case "!=":
|
|
5405
|
+
return itemVal !== cmpVal;
|
|
5406
|
+
case ">":
|
|
5407
|
+
return Number(itemVal) > Number(cmpVal);
|
|
5408
|
+
case "<":
|
|
5409
|
+
return Number(itemVal) < Number(cmpVal);
|
|
5410
|
+
case ">=":
|
|
5411
|
+
return Number(itemVal) >= Number(cmpVal);
|
|
5412
|
+
case "<=":
|
|
5413
|
+
return Number(itemVal) <= Number(cmpVal);
|
|
5414
|
+
/* v8 ignore next -- op is constrained to the six operators by the filter regex; default is unreachable. */
|
|
5415
|
+
default:
|
|
5416
|
+
return true;
|
|
5417
|
+
}
|
|
5418
|
+
});
|
|
5419
|
+
if (rest === void 0) return filtered;
|
|
5420
|
+
return filtered.map((item) => jmespathSearch(item, rest));
|
|
5421
|
+
}
|
|
5422
|
+
const fnMatch = query.match(/^(length|keys|values|type)\(@\)$/);
|
|
5423
|
+
if (fnMatch) {
|
|
5424
|
+
const fn = fnMatch[1];
|
|
5425
|
+
switch (fn) {
|
|
5426
|
+
case "length":
|
|
5427
|
+
if (Array.isArray(data)) return data.length;
|
|
5428
|
+
if (typeof data === "string") return data.length;
|
|
5429
|
+
if (typeof data === "object" && data !== null) return Object.keys(data).length;
|
|
5430
|
+
return 0;
|
|
5431
|
+
case "keys":
|
|
5432
|
+
if (typeof data === "object" && data !== null && !Array.isArray(data)) return Object.keys(data);
|
|
5433
|
+
return [];
|
|
5434
|
+
case "values":
|
|
5435
|
+
if (typeof data === "object" && data !== null && !Array.isArray(data)) return Object.values(data);
|
|
5436
|
+
return [];
|
|
5437
|
+
case "type":
|
|
5438
|
+
if (data === null) return "null";
|
|
5439
|
+
if (Array.isArray(data)) return "array";
|
|
5440
|
+
return typeof data;
|
|
5441
|
+
/* v8 ignore next 2 -- fn is constrained to the four names by the function regex; default is unreachable. */
|
|
5442
|
+
default:
|
|
5443
|
+
return null;
|
|
5444
|
+
}
|
|
5445
|
+
}
|
|
5446
|
+
return null;
|
|
5447
|
+
}
|
|
5448
|
+
function validateJsonSchema(data, schema) {
|
|
5449
|
+
const errors = [];
|
|
5450
|
+
function check(value, s, path22) {
|
|
5451
|
+
if (s["type"]) {
|
|
5452
|
+
const expectedType = s["type"];
|
|
5453
|
+
const actualType = Array.isArray(value) ? "array" : value === null ? "null" : typeof value;
|
|
5454
|
+
if (expectedType === "integer") {
|
|
5455
|
+
if (!Number.isInteger(value)) errors.push(`${path22}: expected integer, got ${actualType}`);
|
|
5456
|
+
} else if (expectedType !== actualType) {
|
|
5457
|
+
errors.push(`${path22}: expected ${expectedType}, got ${actualType}`);
|
|
5458
|
+
}
|
|
5459
|
+
}
|
|
5460
|
+
if (typeof value === "string" && s["format"] === "uri" && value) {
|
|
5461
|
+
try {
|
|
5462
|
+
new URL(value);
|
|
5463
|
+
} catch {
|
|
5464
|
+
errors.push(`${path22}: not a valid URI`);
|
|
5465
|
+
}
|
|
5466
|
+
}
|
|
5467
|
+
if (typeof value === "string" && s["pattern"]) {
|
|
5468
|
+
const re = new RegExp(s["pattern"]);
|
|
5469
|
+
if (!re.test(value)) errors.push(`${path22}: does not match pattern ${s["pattern"]}`);
|
|
5470
|
+
}
|
|
5471
|
+
if (typeof value === "string" && s["minLength"] !== void 0 && value.length < s["minLength"]) {
|
|
5472
|
+
errors.push(`${path22}: string too short (min ${s["minLength"]})`);
|
|
5473
|
+
}
|
|
5474
|
+
if (typeof value === "string" && s["maxLength"] !== void 0 && value.length > s["maxLength"]) {
|
|
5475
|
+
errors.push(`${path22}: string too long (max ${s["maxLength"]})`);
|
|
5476
|
+
}
|
|
5477
|
+
if (typeof value === "number" && s["minimum"] !== void 0 && value < s["minimum"]) {
|
|
5478
|
+
errors.push(`${path22}: below minimum ${s["minimum"]}`);
|
|
5479
|
+
}
|
|
5480
|
+
if (typeof value === "number" && s["maximum"] !== void 0 && value > s["maximum"]) {
|
|
5481
|
+
errors.push(`${path22}: above maximum ${s["maximum"]}`);
|
|
5482
|
+
}
|
|
5483
|
+
if (Array.isArray(value) && s["items"] && Array.isArray(s["items"])) {
|
|
5484
|
+
for (let i = 0; i < value.length; i++) {
|
|
5485
|
+
check(value[i], s["items"], `${path22}[${i}]`);
|
|
5486
|
+
}
|
|
5487
|
+
}
|
|
5488
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value) && s["properties"]) {
|
|
5489
|
+
const props = s["properties"];
|
|
5490
|
+
for (const [k, propSchema] of Object.entries(props)) {
|
|
5491
|
+
check(value[k], propSchema, `${path22}.${k}`);
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
5494
|
+
}
|
|
5495
|
+
check(data, schema, "$");
|
|
5496
|
+
return { valid: errors.length === 0, errors };
|
|
5497
|
+
}
|
|
5498
|
+
function simpleQuery(data, path22) {
|
|
4879
5499
|
const parts = path22.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
|
|
4880
5500
|
let current = data;
|
|
4881
5501
|
for (const part of parts) {
|
|
@@ -5017,7 +5637,7 @@ function findGitDir2(cwd) {
|
|
|
5017
5637
|
return null;
|
|
5018
5638
|
}
|
|
5019
5639
|
function runGit2(args, cwd, signal) {
|
|
5020
|
-
return new Promise((
|
|
5640
|
+
return new Promise((resolve7) => {
|
|
5021
5641
|
let stdout = "";
|
|
5022
5642
|
let stderr = "";
|
|
5023
5643
|
const child = spawn("git", args, {
|
|
@@ -5033,8 +5653,8 @@ function runGit2(args, cwd, signal) {
|
|
|
5033
5653
|
child.stderr?.on("data", (c) => {
|
|
5034
5654
|
stderr += c.toString();
|
|
5035
5655
|
});
|
|
5036
|
-
child.on("close", (code) =>
|
|
5037
|
-
child.on("error", (e) =>
|
|
5656
|
+
child.on("close", (code) => resolve7({ stdout, stderr, exitCode: code ?? 0 }));
|
|
5657
|
+
child.on("error", (e) => resolve7({ stdout: "", stderr: e.message, exitCode: 1 }));
|
|
5038
5658
|
});
|
|
5039
5659
|
}
|
|
5040
5660
|
async function fileDiff(input, ctx, _signal) {
|
|
@@ -5355,8 +5975,8 @@ async function* spawnStream(opts) {
|
|
|
5355
5975
|
try {
|
|
5356
5976
|
for (; ; ) {
|
|
5357
5977
|
while (queue.length === 0) {
|
|
5358
|
-
await new Promise((
|
|
5359
|
-
waiter =
|
|
5978
|
+
await new Promise((resolve7) => {
|
|
5979
|
+
waiter = resolve7;
|
|
5360
5980
|
});
|
|
5361
5981
|
}
|
|
5362
5982
|
const chunk = queue.shift();
|
|
@@ -5889,6 +6509,10 @@ var installTool = {
|
|
|
5889
6509
|
global: {
|
|
5890
6510
|
type: "boolean",
|
|
5891
6511
|
description: "Whether to perform a global install (use with caution)."
|
|
6512
|
+
},
|
|
6513
|
+
lifecycleScripts: {
|
|
6514
|
+
type: "boolean",
|
|
6515
|
+
description: "Opt in to running package lifecycle scripts (preinstall / install / postinstall / prepare / \u2026). Default: false \u2014 installs pass --ignore-scripts so a malicious package cannot execute arbitrary code at install time. Set true to opt back in to the legacy npm/pnpm/yarn default."
|
|
5892
6516
|
}
|
|
5893
6517
|
}
|
|
5894
6518
|
},
|
|
@@ -5908,8 +6532,10 @@ var installTool = {
|
|
|
5908
6532
|
yield { type: "log", text: `Resolving with ${pkgManager}\u2026`, data: { phase: "resolve" } };
|
|
5909
6533
|
const save = input.save === "dev" ? "-D" : input.save === "optional" ? "-O" : "";
|
|
5910
6534
|
const globalFlag = input.global ? ["-g"] : [];
|
|
6535
|
+
const ignoreScripts = input.lifecycleScripts !== true;
|
|
5911
6536
|
const args = [];
|
|
5912
6537
|
if (input.dry_run) args.push("--dry-run");
|
|
6538
|
+
if (ignoreScripts) args.push("--ignore-scripts");
|
|
5913
6539
|
if (pkgManager === "pnpm") {
|
|
5914
6540
|
if (save) args.push(save);
|
|
5915
6541
|
args.push("add", ...globalFlag);
|
|
@@ -6114,8 +6740,8 @@ var outdatedTool = {
|
|
|
6114
6740
|
// read-only, but `outdated` makes outbound HTTP calls to the
|
|
6115
6741
|
// registry. The 'confirm' permission routes the call through the
|
|
6116
6742
|
// tool.confirm_needed flow on every invocation. M-1 originally
|
|
6117
|
-
// fixed four sibling tools (mcp_control, shellcheck,
|
|
6118
|
-
//
|
|
6743
|
+
// fixed four sibling tools (mcp_control, shellcheck, shellcheck (scan mode),
|
|
6744
|
+
// search) but missed this one; applying the same contract here.
|
|
6119
6745
|
mutating: true,
|
|
6120
6746
|
// Capability is outbound network — the tool only hits the package
|
|
6121
6747
|
// registry over HTTP, never touches the filesystem or runs shell.
|
|
@@ -6156,7 +6782,7 @@ var outdatedTool = {
|
|
|
6156
6782
|
}
|
|
6157
6783
|
};
|
|
6158
6784
|
function runOutdated(manager, args, cwd, signal) {
|
|
6159
|
-
return new Promise((
|
|
6785
|
+
return new Promise((resolve7) => {
|
|
6160
6786
|
let stdout = "";
|
|
6161
6787
|
let stderr = "";
|
|
6162
6788
|
const MAX = 1e5;
|
|
@@ -6173,10 +6799,10 @@ function runOutdated(manager, args, cwd, signal) {
|
|
|
6173
6799
|
});
|
|
6174
6800
|
child.on("close", (code) => {
|
|
6175
6801
|
const result = parseOutdatedOutput(stdout, code ?? 0);
|
|
6176
|
-
|
|
6802
|
+
resolve7(result);
|
|
6177
6803
|
});
|
|
6178
6804
|
child.on("error", (e) => {
|
|
6179
|
-
|
|
6805
|
+
resolve7({
|
|
6180
6806
|
exit_code: 1,
|
|
6181
6807
|
packages: [],
|
|
6182
6808
|
total: 0,
|
|
@@ -6302,7 +6928,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
6302
6928
|
};
|
|
6303
6929
|
}
|
|
6304
6930
|
args.push("--timestamps", service);
|
|
6305
|
-
return new Promise((
|
|
6931
|
+
return new Promise((resolve7) => {
|
|
6306
6932
|
let stdout = "";
|
|
6307
6933
|
let stderr = "";
|
|
6308
6934
|
const MAX = 2e5;
|
|
@@ -6318,7 +6944,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
6318
6944
|
if (settled) return;
|
|
6319
6945
|
settled = true;
|
|
6320
6946
|
clearTimeout(timer);
|
|
6321
|
-
|
|
6947
|
+
resolve7(result);
|
|
6322
6948
|
};
|
|
6323
6949
|
const child = spawn("docker", args, { cwd, signal, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
6324
6950
|
const timer = setTimeout(() => {
|
|
@@ -6428,8 +7054,8 @@ function parseLine(line) {
|
|
|
6428
7054
|
var documentTool = {
|
|
6429
7055
|
name: "document",
|
|
6430
7056
|
category: "Project",
|
|
6431
|
-
description: "
|
|
6432
|
-
usageHint: "
|
|
7057
|
+
description: "DEPRECATED \u2014 use the `auto_doc` tool with `dryRun: true` instead. This tool is a read-only preview stub that returns `skipped` candidates without generating real docstrings.",
|
|
7058
|
+
usageHint: "Deprecated: prefer `auto_doc` with `dryRun: true` for previewing, or `auto_doc` without dryRun for writing. This tool only lists undocumented symbols with placeholder comments \u2014 it does not generate real JSDoc/TSDoc.",
|
|
6433
7059
|
permission: "auto",
|
|
6434
7060
|
mutating: false,
|
|
6435
7061
|
timeoutMs: 3e4,
|
|
@@ -6912,7 +7538,14 @@ These win over kit tokens. Run \`design {action:"materialize"}\` to write them t
|
|
|
6912
7538
|
kitId: active.kit,
|
|
6913
7539
|
outPath: input.out
|
|
6914
7540
|
});
|
|
6915
|
-
const
|
|
7541
|
+
const root = path.resolve(ctx.projectRoot);
|
|
7542
|
+
const abs = path.resolve(path.join(ctx.projectRoot, result.path));
|
|
7543
|
+
const rel = path.relative(root, abs);
|
|
7544
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
7545
|
+
throw new Error(
|
|
7546
|
+
`design: materialize path "${result.path}" would escape the project root`
|
|
7547
|
+
);
|
|
7548
|
+
}
|
|
6916
7549
|
let exists = false;
|
|
6917
7550
|
try {
|
|
6918
7551
|
await fs7.access(abs);
|
|
@@ -6983,8 +7616,8 @@ Replace off-palette colors with kit tokens (or the materialized CSS vars / token
|
|
|
6983
7616
|
var toolSearchTool = {
|
|
6984
7617
|
name: "tool_search",
|
|
6985
7618
|
category: "Meta",
|
|
6986
|
-
description: "Search the catalog of available tools
|
|
6987
|
-
usageHint: "SELF-DISCOVERY TOOL:\n\n- Use when you need to find the right tool for a job.\n- `query` searches names and descriptions.\n- You can filter by `tags` (category), `permission`, or `mutating`.\nCall this before guessing tool names. It helps you discover the best tool for the current situation.",
|
|
7619
|
+
description: "Search the catalog of available tools by name or description. Use this to discover which tool to use for a task. For the full schema and usage details of a specific tool, use `tool_help` instead.",
|
|
7620
|
+
usageHint: "SELF-DISCOVERY TOOL:\n\n- Use when you need to find the right tool for a job.\n- `query` searches names and descriptions.\n- You can filter by `tags` (category), `permission`, or `mutating`.\n- Once you find the right tool name, use `tool_help` with that name for full schema details.\nCall this before guessing tool names. It helps you discover the best tool for the current situation.",
|
|
6988
7621
|
permission: "auto",
|
|
6989
7622
|
mutating: false,
|
|
6990
7623
|
timeoutMs: 1e3,
|
|
@@ -7022,9 +7655,9 @@ var toolSearchTool = {
|
|
|
7022
7655
|
async execute(input, ctx) {
|
|
7023
7656
|
const limit = Math.min(input.limit ?? 20, 100);
|
|
7024
7657
|
const tools = ctx.tools;
|
|
7025
|
-
const
|
|
7658
|
+
const query = input.query?.toLowerCase() ?? "";
|
|
7026
7659
|
const filtered = tools.filter((t) => {
|
|
7027
|
-
if (
|
|
7660
|
+
if (query && !t.name.toLowerCase().includes(query) && !t.description.toLowerCase().includes(query)) {
|
|
7028
7661
|
return false;
|
|
7029
7662
|
}
|
|
7030
7663
|
if (input.tags && input.tags.length > 0) {
|
|
@@ -7048,7 +7681,7 @@ var toolSearchTool = {
|
|
|
7048
7681
|
mutating: t.mutating
|
|
7049
7682
|
}));
|
|
7050
7683
|
const totalAvailable = tools.length;
|
|
7051
|
-
const hint = results.length === 0 &&
|
|
7684
|
+
const hint = results.length === 0 && query ? `No tools matched "${input.query}". Use tool-help (without arguments) to see all ${totalAvailable} available tools.` : void 0;
|
|
7052
7685
|
return {
|
|
7053
7686
|
tools: results,
|
|
7054
7687
|
total: filtered.length,
|
|
@@ -7240,8 +7873,8 @@ async function executeSingle(call, ctx, opts) {
|
|
|
7240
7873
|
var toolHelpTool = {
|
|
7241
7874
|
name: "tool_help",
|
|
7242
7875
|
category: "Meta",
|
|
7243
|
-
description: "Get detailed help for
|
|
7244
|
-
usageHint: "USE WHEN YOU NEED PRECISE TOOL INFORMATION:\n\n- Call with a specific `tool` name when you want the full schema and current usageHint.\n- Omit `tool`
|
|
7876
|
+
description: "Get detailed help for a specific tool, including its full input schema and usage guidance. If you do not know which tool to use, search with `tool_search` first, then call this with the tool name.",
|
|
7877
|
+
usageHint: "USE WHEN YOU NEED PRECISE TOOL INFORMATION:\n\n- Call with a specific `tool` name when you want the full schema and current usageHint.\n- Omit `tool` to get an overview of all available tools.\n- Different `format` options give you different levels of detail.\n- Tip: use `tool_search` to find the right tool name, then `tool_help` for the full schema.\nThis tool is extremely valuable for self-correction when you are unsure about a tool's interface.",
|
|
7245
7878
|
permission: "auto",
|
|
7246
7879
|
mutating: false,
|
|
7247
7880
|
timeoutMs: 5e3,
|
|
@@ -7359,8 +7992,6 @@ function formatAllToolsMarkdown(tools) {
|
|
|
7359
7992
|
}
|
|
7360
7993
|
return lines.join("\n");
|
|
7361
7994
|
}
|
|
7362
|
-
|
|
7363
|
-
// src/memory.ts
|
|
7364
7995
|
function rememberTool(memory) {
|
|
7365
7996
|
return {
|
|
7366
7997
|
name: "remember",
|
|
@@ -7403,7 +8034,12 @@ function rememberTool(memory) {
|
|
|
7403
8034
|
required: ["text"]
|
|
7404
8035
|
},
|
|
7405
8036
|
async execute(input) {
|
|
7406
|
-
if (!input?.text)
|
|
8037
|
+
if (!input?.text) {
|
|
8038
|
+
throw new ToolValidationError({
|
|
8039
|
+
message: "remember: text is required",
|
|
8040
|
+
field: "text"
|
|
8041
|
+
});
|
|
8042
|
+
}
|
|
7407
8043
|
const scope = input.scope ?? "project-memory";
|
|
7408
8044
|
await memory.remember(input.text, scope, {
|
|
7409
8045
|
type: input.type,
|
|
@@ -7433,7 +8069,12 @@ function forgetTool(memory) {
|
|
|
7433
8069
|
required: ["query"]
|
|
7434
8070
|
},
|
|
7435
8071
|
async execute(input) {
|
|
7436
|
-
if (!input?.query)
|
|
8072
|
+
if (!input?.query) {
|
|
8073
|
+
throw new ToolValidationError({
|
|
8074
|
+
message: "forget: query is required",
|
|
8075
|
+
field: "query"
|
|
8076
|
+
});
|
|
8077
|
+
}
|
|
7437
8078
|
const scope = input.scope ?? "project-memory";
|
|
7438
8079
|
const removed = await memory.forget(input.query, scope);
|
|
7439
8080
|
return { removed, scope };
|
|
@@ -7470,7 +8111,12 @@ function searchMemoryTool(memory) {
|
|
|
7470
8111
|
required: ["query"]
|
|
7471
8112
|
},
|
|
7472
8113
|
async execute(input) {
|
|
7473
|
-
if (!input?.query)
|
|
8114
|
+
if (!input?.query) {
|
|
8115
|
+
throw new ToolValidationError({
|
|
8116
|
+
message: "search_memory: query is required",
|
|
8117
|
+
field: "query"
|
|
8118
|
+
});
|
|
8119
|
+
}
|
|
7474
8120
|
const scope = input.scope ?? "project-memory";
|
|
7475
8121
|
const limit = Math.min(input.limit ?? 5, 20);
|
|
7476
8122
|
const entries = await memory.search(input.query, scope, limit);
|
|
@@ -7517,7 +8163,12 @@ function relatedMemoryTool(memory) {
|
|
|
7517
8163
|
required: ["text"]
|
|
7518
8164
|
},
|
|
7519
8165
|
async execute(input) {
|
|
7520
|
-
if (!input?.text)
|
|
8166
|
+
if (!input?.text) {
|
|
8167
|
+
throw new ToolValidationError({
|
|
8168
|
+
message: "find_related_memories: text is required",
|
|
8169
|
+
field: "text"
|
|
8170
|
+
});
|
|
8171
|
+
}
|
|
7521
8172
|
const scope = input.scope ?? "project-memory";
|
|
7522
8173
|
const limit = Math.min(input.limit ?? 5, 20);
|
|
7523
8174
|
const entries = memory.findRelated ? await memory.findRelated(input.text, scope, limit) : await memory.search(input.text, scope, limit);
|
|
@@ -7758,16 +8409,23 @@ var ProcessGuardian = class {
|
|
|
7758
8409
|
event: "process_guardian.uncaught_exception",
|
|
7759
8410
|
error: err.message,
|
|
7760
8411
|
stack: err.stack,
|
|
7761
|
-
instanceId: this.instanceId
|
|
8412
|
+
instanceId: this.instanceId,
|
|
8413
|
+
fatal: true
|
|
7762
8414
|
}));
|
|
8415
|
+
this.stop();
|
|
8416
|
+
process.exit(1);
|
|
7763
8417
|
});
|
|
7764
8418
|
process.on("unhandledRejection", (reason) => {
|
|
8419
|
+
const err = reason instanceof Error ? { message: reason.message, stack: reason.stack } : { value: String(reason) };
|
|
7765
8420
|
console.error(JSON.stringify({
|
|
7766
8421
|
level: "error",
|
|
7767
8422
|
event: "process_guardian.unhandled_rejection",
|
|
7768
|
-
|
|
7769
|
-
instanceId: this.instanceId
|
|
8423
|
+
...err,
|
|
8424
|
+
instanceId: this.instanceId,
|
|
8425
|
+
fatal: true
|
|
7770
8426
|
}));
|
|
8427
|
+
this.stop();
|
|
8428
|
+
process.exit(1);
|
|
7771
8429
|
});
|
|
7772
8430
|
process.on("SIGTERM", (origin) => {
|
|
7773
8431
|
console.log(JSON.stringify({
|
|
@@ -8308,8 +8966,8 @@ var Bm25Index = class {
|
|
|
8308
8966
|
df;
|
|
8309
8967
|
N;
|
|
8310
8968
|
safeAvgLen;
|
|
8311
|
-
score(
|
|
8312
|
-
const qTokens = tokenise(
|
|
8969
|
+
score(query, filter) {
|
|
8970
|
+
const qTokens = tokenise(query);
|
|
8313
8971
|
if (qTokens.length === 0) return [];
|
|
8314
8972
|
const results = [];
|
|
8315
8973
|
for (const doc of this.documents) {
|
|
@@ -8632,7 +9290,7 @@ var IndexStore = class {
|
|
|
8632
9290
|
);
|
|
8633
9291
|
}
|
|
8634
9292
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
8635
|
-
search(
|
|
9293
|
+
search(query, filter) {
|
|
8636
9294
|
const conditions = [];
|
|
8637
9295
|
const values = [];
|
|
8638
9296
|
let effectiveKind = filter?.kind;
|
|
@@ -8656,8 +9314,8 @@ var IndexStore = class {
|
|
|
8656
9314
|
conditions.push("file LIKE ?");
|
|
8657
9315
|
values.push(`%${filter.file}%`);
|
|
8658
9316
|
}
|
|
8659
|
-
if (
|
|
8660
|
-
const tokens =
|
|
9317
|
+
if (query.trim()) {
|
|
9318
|
+
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
8661
9319
|
const tokenConds = tokens.map(() => "text LIKE ?");
|
|
8662
9320
|
conditions.push(`(${tokenConds.join(" OR ")})`);
|
|
8663
9321
|
for (const t of tokens) values.push(`%${t}%`);
|
|
@@ -8691,10 +9349,10 @@ var IndexStore = class {
|
|
|
8691
9349
|
* `LIKE '%tok%'` recall for the common symbol-search shapes ("user" finds
|
|
8692
9350
|
* "users", camelCase-split text makes "complex" find "complexOperation").
|
|
8693
9351
|
*/
|
|
8694
|
-
searchRanked(
|
|
8695
|
-
const tokens = tokenise(
|
|
9352
|
+
searchRanked(query, filter, limit) {
|
|
9353
|
+
const tokens = tokenise(query);
|
|
8696
9354
|
if (tokens.length === 0 || !this.ftsAvailable) {
|
|
8697
|
-
return this.searchRankedFallback(
|
|
9355
|
+
return this.searchRankedFallback(query, filter, limit);
|
|
8698
9356
|
}
|
|
8699
9357
|
let effectiveKind = filter?.kind;
|
|
8700
9358
|
if (filter?.lspKind !== void 0) {
|
|
@@ -8751,19 +9409,19 @@ var IndexStore = class {
|
|
|
8751
9409
|
};
|
|
8752
9410
|
}
|
|
8753
9411
|
/** Legacy ranked path: LIKE candidates + in-process BM25 + JS snippets. */
|
|
8754
|
-
searchRankedFallback(
|
|
8755
|
-
const candidates = this.search(
|
|
9412
|
+
searchRankedFallback(query, filter, limit) {
|
|
9413
|
+
const candidates = this.search(query, filter);
|
|
8756
9414
|
if (candidates.length === 0) return { results: [], total: 0 };
|
|
8757
|
-
if (!
|
|
9415
|
+
if (!query.trim()) {
|
|
8758
9416
|
return { results: candidates.slice(0, limit), total: candidates.length };
|
|
8759
9417
|
}
|
|
8760
9418
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
8761
9419
|
const bm25 = buildBm25Index(
|
|
8762
9420
|
candidates.map((c) => ({ id: c.id, text: buildIndexableText(c.name, c.signature, c.docComment) }))
|
|
8763
9421
|
);
|
|
8764
|
-
const scored = bm25.score(
|
|
9422
|
+
const scored = bm25.score(query, (id) => candidateById.has(id));
|
|
8765
9423
|
scored.sort((a, b) => b.score - a.score);
|
|
8766
|
-
const qTokens = tokenise(
|
|
9424
|
+
const qTokens = tokenise(query);
|
|
8767
9425
|
const results = scored.slice(0, limit).map(({ id, score }) => {
|
|
8768
9426
|
const c = expectDefined(candidateById.get(id));
|
|
8769
9427
|
return { ...c, score, snippet: bm25.extractSnippet(id, qTokens) };
|
|
@@ -8874,6 +9532,104 @@ var IndexStore = class {
|
|
|
8874
9532
|
}
|
|
8875
9533
|
});
|
|
8876
9534
|
}
|
|
9535
|
+
/**
|
|
9536
|
+
* Commit a batch of file-level symbol/refs/upserts in a single transaction.
|
|
9537
|
+
*
|
|
9538
|
+
* Used by the indexer to amortize SQLite commit overhead across many files.
|
|
9539
|
+
* Before this, the indexer issued one transaction per file (BEGIN IMMEDIATE
|
|
9540
|
+
* for symbols, plus per-file deletes and an upsertFile call), so a 20-file
|
|
9541
|
+
* parallel batch cost ~5+ transactions × 20 files = 100+ commits. With
|
|
9542
|
+
* this entry point we do exactly one BEGIN/COMMIT per parallel batch.
|
|
9543
|
+
*
|
|
9544
|
+
* Each entry must already be a fully-parsed FileSymbols (symbols + refs).
|
|
9545
|
+
* The caller is responsible for the per-file prefix accounting
|
|
9546
|
+
* (refsByLine → flat list with `fromId` populated). `deleteForFiles` lets
|
|
9547
|
+
* the caller clear stale symbols/refs for any files being re-indexed before
|
|
9548
|
+
* the inserts run (required to keep refs → symbols FK invariants).
|
|
9549
|
+
*
|
|
9550
|
+
* Returns the symbols back with their assigned `id` (same shape as
|
|
9551
|
+
* {@link insertSymbols}) so callers can build final per-file results.
|
|
9552
|
+
*/
|
|
9553
|
+
commitBatch(entries, options = {}) {
|
|
9554
|
+
if (entries.length === 0 && (options.deleteForFiles?.length ?? 0) === 0) {
|
|
9555
|
+
return [];
|
|
9556
|
+
}
|
|
9557
|
+
return this.runWithRetry(() => {
|
|
9558
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
9559
|
+
try {
|
|
9560
|
+
if (options.deleteForFiles && options.deleteForFiles.length > 0) {
|
|
9561
|
+
const placeholders = options.deleteForFiles.map(() => "?").join(",");
|
|
9562
|
+
if (this.ftsAvailable) {
|
|
9563
|
+
this.db.prepare(
|
|
9564
|
+
`DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
9565
|
+
).run(...options.deleteForFiles);
|
|
9566
|
+
}
|
|
9567
|
+
this.db.prepare(
|
|
9568
|
+
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
9569
|
+
).run(...options.deleteForFiles);
|
|
9570
|
+
this.db.prepare(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
|
|
9571
|
+
}
|
|
9572
|
+
const maxRows = this.db.prepare("SELECT MAX(id) AS m FROM symbols").all();
|
|
9573
|
+
let nextId = (maxRows[0]?.m ?? 0) + 1;
|
|
9574
|
+
const symStmt = this.db.prepare(
|
|
9575
|
+
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
9576
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
9577
|
+
);
|
|
9578
|
+
const ftsStmt = this.ftsAvailable ? this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)") : null;
|
|
9579
|
+
const allInserted = [];
|
|
9580
|
+
const refsToInsert = [];
|
|
9581
|
+
for (const entry of entries) {
|
|
9582
|
+
for (const s of entry.symbols) {
|
|
9583
|
+
const id = nextId++;
|
|
9584
|
+
symStmt.run(
|
|
9585
|
+
id,
|
|
9586
|
+
s.lang,
|
|
9587
|
+
s.kind,
|
|
9588
|
+
s.name,
|
|
9589
|
+
s.file,
|
|
9590
|
+
s.line,
|
|
9591
|
+
s.col,
|
|
9592
|
+
s.signature,
|
|
9593
|
+
s.docComment,
|
|
9594
|
+
s.scope,
|
|
9595
|
+
s.text,
|
|
9596
|
+
s.file
|
|
9597
|
+
);
|
|
9598
|
+
ftsStmt?.run(id, buildIndexableText(s.name, s.signature, s.docComment));
|
|
9599
|
+
allInserted.push({ ...s, id });
|
|
9600
|
+
}
|
|
9601
|
+
for (const r of entry.refs) refsToInsert.push(r);
|
|
9602
|
+
}
|
|
9603
|
+
if (refsToInsert.length > 0) {
|
|
9604
|
+
const refStmt = this.db.prepare(
|
|
9605
|
+
`INSERT INTO refs(from_id, to_name, to_id, call_type, line)
|
|
9606
|
+
VALUES (?, ?, ?, ?, ?)`
|
|
9607
|
+
);
|
|
9608
|
+
for (const ref of refsToInsert) {
|
|
9609
|
+
refStmt.run(ref.fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
9610
|
+
}
|
|
9611
|
+
}
|
|
9612
|
+
const upsertStmt = this.db.prepare(
|
|
9613
|
+
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
9614
|
+
VALUES (?, ?, ?, ?, ?)
|
|
9615
|
+
ON CONFLICT(file) DO UPDATE SET
|
|
9616
|
+
lang = excluded.lang,
|
|
9617
|
+
mtime_ms = excluded.mtime_ms,
|
|
9618
|
+
symbol_count = excluded.symbol_count,
|
|
9619
|
+
last_indexed = excluded.last_indexed`
|
|
9620
|
+
);
|
|
9621
|
+
const now2 = Date.now();
|
|
9622
|
+
for (const entry of entries) {
|
|
9623
|
+
upsertStmt.run(entry.file, entry.lang, entry.mtimeMs, entry.symbolCount, now2);
|
|
9624
|
+
}
|
|
9625
|
+
this.db.exec("COMMIT");
|
|
9626
|
+
return allInserted;
|
|
9627
|
+
} catch (err) {
|
|
9628
|
+
this.db.exec("ROLLBACK");
|
|
9629
|
+
throw err;
|
|
9630
|
+
}
|
|
9631
|
+
});
|
|
9632
|
+
}
|
|
8877
9633
|
/**
|
|
8878
9634
|
* Delete all refs whose source symbols are in a given file.
|
|
8879
9635
|
* Used when re-indexing a file to clear stale refs.
|
|
@@ -9390,8 +10146,8 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
9390
10146
|
proc.stdin?.write(content);
|
|
9391
10147
|
proc.stdin?.end();
|
|
9392
10148
|
const { code } = await Promise.race([
|
|
9393
|
-
new Promise((
|
|
9394
|
-
proc.on("close", (c) =>
|
|
10149
|
+
new Promise((resolve7) => {
|
|
10150
|
+
proc.on("close", (c) => resolve7({ code: c }));
|
|
9395
10151
|
}),
|
|
9396
10152
|
new Promise(
|
|
9397
10153
|
(_, reject) => setTimeout(() => {
|
|
@@ -9653,8 +10409,8 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
9653
10409
|
stdout += chunk.toString();
|
|
9654
10410
|
});
|
|
9655
10411
|
const { code } = await Promise.race([
|
|
9656
|
-
new Promise((
|
|
9657
|
-
proc.on("close", (c) =>
|
|
10412
|
+
new Promise((resolve7) => {
|
|
10413
|
+
proc.on("close", (c) => resolve7({ code: c }));
|
|
9658
10414
|
}),
|
|
9659
10415
|
new Promise(
|
|
9660
10416
|
(_, reject) => setTimeout(() => {
|
|
@@ -9739,8 +10495,8 @@ async function tryNativeParse(file, content) {
|
|
|
9739
10495
|
stdout += chunk.toString();
|
|
9740
10496
|
});
|
|
9741
10497
|
const { code } = await Promise.race([
|
|
9742
|
-
new Promise((
|
|
9743
|
-
proc.on("close", (c) =>
|
|
10498
|
+
new Promise((resolve7) => {
|
|
10499
|
+
proc.on("close", (c) => resolve7({ code: c }));
|
|
9744
10500
|
}),
|
|
9745
10501
|
new Promise(
|
|
9746
10502
|
(_, reject) => setTimeout(() => {
|
|
@@ -10230,7 +10986,7 @@ async function loadGitignoreMatcher(projectRoot) {
|
|
|
10230
10986
|
var YIELD_EVERY_N = 50;
|
|
10231
10987
|
var PARALLEL_BATCH = 20;
|
|
10232
10988
|
function yieldEventLoop() {
|
|
10233
|
-
return new Promise((
|
|
10989
|
+
return new Promise((resolve7) => setImmediate(resolve7));
|
|
10234
10990
|
}
|
|
10235
10991
|
function throwIfAborted(signal) {
|
|
10236
10992
|
if (!signal?.aborted) return;
|
|
@@ -10405,6 +11161,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10405
11161
|
return { file, stat: stat11, lang, parsed, content };
|
|
10406
11162
|
})
|
|
10407
11163
|
);
|
|
11164
|
+
const batchEntries = [];
|
|
11165
|
+
const deleteForFiles = [];
|
|
10408
11166
|
for (let fi = 0; fi < statReadParse.length; fi++) {
|
|
10409
11167
|
const settled = statReadParse[fi];
|
|
10410
11168
|
const file = expectDefined(batchFiles[fi]);
|
|
@@ -10429,43 +11187,116 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10429
11187
|
}
|
|
10430
11188
|
if (!lang || !parsed) {
|
|
10431
11189
|
if (lang) {
|
|
10432
|
-
store.upsertFile({
|
|
11190
|
+
store.upsertFile({
|
|
11191
|
+
file,
|
|
11192
|
+
lang,
|
|
11193
|
+
mtimeMs: Math.floor(stat11.mtimeMs),
|
|
11194
|
+
symbolCount: 0,
|
|
11195
|
+
lastIndexed: Date.now()
|
|
11196
|
+
});
|
|
10433
11197
|
filesIndexed++;
|
|
10434
11198
|
}
|
|
10435
11199
|
continue;
|
|
10436
11200
|
}
|
|
10437
|
-
store.deleteRefsForFile(file);
|
|
10438
|
-
store.deleteSymbolsForFile(file);
|
|
10439
11201
|
if (parsed.symbols.length === 0) {
|
|
10440
|
-
store.upsertFile({
|
|
11202
|
+
store.upsertFile({
|
|
11203
|
+
file,
|
|
11204
|
+
lang,
|
|
11205
|
+
mtimeMs: Math.floor(stat11.mtimeMs),
|
|
11206
|
+
symbolCount: 0,
|
|
11207
|
+
lastIndexed: Date.now()
|
|
11208
|
+
});
|
|
10441
11209
|
filesIndexed++;
|
|
10442
11210
|
continue;
|
|
10443
11211
|
}
|
|
10444
|
-
const
|
|
10445
|
-
const count = symbolsWithIds.length;
|
|
10446
|
-
symbolsIndexed += count;
|
|
10447
|
-
langStats[lang] = (langStats[lang] ?? 0) + count;
|
|
11212
|
+
const refs = [];
|
|
10448
11213
|
if (parsed.refs && parsed.refs.length > 0) {
|
|
10449
|
-
const
|
|
10450
|
-
|
|
10451
|
-
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
|
|
11214
|
+
for (const r of parsed.refs) refs.push({ ...r, fromId: 0 });
|
|
11215
|
+
}
|
|
11216
|
+
batchEntries.push({
|
|
11217
|
+
file,
|
|
11218
|
+
lang,
|
|
11219
|
+
symbols: parsed.symbols,
|
|
11220
|
+
refs,
|
|
11221
|
+
mtimeMs: Math.floor(stat11.mtimeMs),
|
|
11222
|
+
symbolCount: parsed.symbols.length
|
|
11223
|
+
});
|
|
11224
|
+
deleteForFiles.push(file);
|
|
11225
|
+
}
|
|
11226
|
+
if (batchEntries.length > 0) {
|
|
11227
|
+
try {
|
|
11228
|
+
const inserted = store.commitBatch(batchEntries, { deleteForFiles });
|
|
11229
|
+
let cursor = 0;
|
|
11230
|
+
for (const entry of batchEntries) {
|
|
11231
|
+
const count = entry.symbols.length;
|
|
11232
|
+
const symbolsWithIds = inserted.slice(cursor, cursor + count);
|
|
11233
|
+
cursor += count;
|
|
11234
|
+
symbolsIndexed += count;
|
|
11235
|
+
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + count;
|
|
11236
|
+
filesIndexed++;
|
|
11237
|
+
if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
|
|
11238
|
+
const refsByLine = /* @__PURE__ */ new Map();
|
|
11239
|
+
for (let i = 0; i < symbolsWithIds.length; i++) {
|
|
11240
|
+
const sym = symbolsWithIds[i];
|
|
11241
|
+
let arr = refsByLine.get(sym.line);
|
|
11242
|
+
if (!arr) {
|
|
11243
|
+
arr = [];
|
|
11244
|
+
refsByLine.set(sym.line, arr);
|
|
11245
|
+
}
|
|
11246
|
+
arr.push(i);
|
|
11247
|
+
}
|
|
11248
|
+
for (const ref of entry.refs) {
|
|
11249
|
+
const indices = refsByLine.get(ref.line);
|
|
11250
|
+
if (indices && indices.length > 0) {
|
|
11251
|
+
const idx = indices.shift();
|
|
11252
|
+
ref.fromId = symbolsWithIds[idx].id;
|
|
11253
|
+
}
|
|
11254
|
+
}
|
|
10455
11255
|
}
|
|
10456
|
-
arr.push(r);
|
|
10457
11256
|
}
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
11257
|
+
} catch (err) {
|
|
11258
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11259
|
+
errors.push(`commitBatch failed: ${message} \u2014 falling back to per-file writes`);
|
|
11260
|
+
for (const entry of batchEntries) {
|
|
11261
|
+
try {
|
|
11262
|
+
store.deleteRefsForFile(entry.file);
|
|
11263
|
+
store.deleteSymbolsForFile(entry.file);
|
|
11264
|
+
const symbolsWithIds = store.insertSymbols(entry.symbols);
|
|
11265
|
+
symbolsIndexed += symbolsWithIds.length;
|
|
11266
|
+
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
|
|
11267
|
+
filesIndexed++;
|
|
11268
|
+
if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
|
|
11269
|
+
const refsByLine = /* @__PURE__ */ new Map();
|
|
11270
|
+
for (const sym of symbolsWithIds) {
|
|
11271
|
+
let arr = refsByLine.get(sym.line);
|
|
11272
|
+
if (!arr) {
|
|
11273
|
+
arr = [];
|
|
11274
|
+
refsByLine.set(sym.line, arr);
|
|
11275
|
+
}
|
|
11276
|
+
arr.push(sym);
|
|
11277
|
+
}
|
|
11278
|
+
const fallbackBatch = [];
|
|
11279
|
+
for (const ref of entry.refs) {
|
|
11280
|
+
const syms = refsByLine.get(ref.line);
|
|
11281
|
+
if (syms && syms.length > 0) {
|
|
11282
|
+
const sym = syms.shift();
|
|
11283
|
+
fallbackBatch.push({ ...ref, fromId: sym.id });
|
|
11284
|
+
}
|
|
11285
|
+
}
|
|
11286
|
+
if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
|
|
11287
|
+
}
|
|
11288
|
+
store.upsertFile({
|
|
11289
|
+
file: entry.file,
|
|
11290
|
+
lang: entry.lang,
|
|
11291
|
+
mtimeMs: entry.mtimeMs,
|
|
11292
|
+
symbolCount: entry.symbolCount,
|
|
11293
|
+
lastIndexed: Date.now()
|
|
11294
|
+
});
|
|
11295
|
+
} catch (innerErr) {
|
|
11296
|
+
errors.push(`fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`);
|
|
10463
11297
|
}
|
|
10464
11298
|
}
|
|
10465
|
-
if (batch.length > 0) store.insertRefsBatch(batch);
|
|
10466
11299
|
}
|
|
10467
|
-
store.upsertFile({ file, lang, mtimeMs: Math.floor(stat11.mtimeMs), symbolCount: count, lastIndexed: Date.now() });
|
|
10468
|
-
filesIndexed++;
|
|
10469
11300
|
}
|
|
10470
11301
|
}
|
|
10471
11302
|
if (discoveredFiles) {
|
|
@@ -10648,7 +11479,7 @@ function shutdownCodebaseIndexHost() {
|
|
|
10648
11479
|
function callIndexOp(op, args, opts) {
|
|
10649
11480
|
const w = ensureWorker();
|
|
10650
11481
|
if (!w) return callInline(op, args, opts);
|
|
10651
|
-
return new Promise((
|
|
11482
|
+
return new Promise((resolve7, reject) => {
|
|
10652
11483
|
const id = nextRpcId++;
|
|
10653
11484
|
const timer = setTimeout(() => {
|
|
10654
11485
|
pending.delete(id);
|
|
@@ -10671,7 +11502,7 @@ function callIndexOp(op, args, opts) {
|
|
|
10671
11502
|
pending.set(id, {
|
|
10672
11503
|
resolve: (v) => {
|
|
10673
11504
|
cleanup();
|
|
10674
|
-
|
|
11505
|
+
resolve7(v);
|
|
10675
11506
|
},
|
|
10676
11507
|
reject: (e) => {
|
|
10677
11508
|
cleanup();
|
|
@@ -10907,7 +11738,7 @@ var codebaseSearchTool = {
|
|
|
10907
11738
|
name: "codebase-search",
|
|
10908
11739
|
category: "Project",
|
|
10909
11740
|
icon: "index",
|
|
10910
|
-
description: "
|
|
11741
|
+
description: "Search code symbols using a fast SQLite+BM25 index, with optional LSP fallback. Much more powerful and structured than raw `grep` for finding code by name or concept. Set `preferLsp: true` for live precision when the LSP plugin is active (supersedes codebase-lsp-search).",
|
|
10911
11742
|
usageHint: "PREFERRED FOR CODE UNDERSTANDING:\n\n- Use when you need to find where something is defined or used by name.\n- `kind` filter is very useful (e.g. only functions or only interfaces).\n- Combine with `file` filter to scope to a specific directory or module.\nThis is generally better than `grep` when you are looking for symbols rather than arbitrary text patterns.",
|
|
10912
11743
|
permission: "auto",
|
|
10913
11744
|
mutating: false,
|
|
@@ -10941,6 +11772,10 @@ var codebaseSearchTool = {
|
|
|
10941
11772
|
description: "Maximum results to return (default 20, max 100)",
|
|
10942
11773
|
minimum: 1,
|
|
10943
11774
|
maximum: 100
|
|
11775
|
+
},
|
|
11776
|
+
preferLsp: {
|
|
11777
|
+
type: "boolean",
|
|
11778
|
+
description: "Prefer live LSP results over the index. Index-only when the LSP plugin is not active. When the LSP plugin is active and this is true, results come from live workspaceSymbol queries."
|
|
10944
11779
|
}
|
|
10945
11780
|
},
|
|
10946
11781
|
required: ["query"]
|
|
@@ -11103,15 +11938,15 @@ var setWorkingDirTool = {
|
|
|
11103
11938
|
};
|
|
11104
11939
|
}
|
|
11105
11940
|
};
|
|
11106
|
-
function findTaskIndex(tasks,
|
|
11107
|
-
const asNum = Number.parseInt(
|
|
11941
|
+
function findTaskIndex(tasks, query) {
|
|
11942
|
+
const asNum = Number.parseInt(query, 10);
|
|
11108
11943
|
if (!Number.isNaN(asNum)) {
|
|
11109
11944
|
const idx = asNum - 1;
|
|
11110
11945
|
if (tasks[idx]) return idx;
|
|
11111
11946
|
}
|
|
11112
|
-
const byId = tasks.findIndex((t) => t.id ===
|
|
11947
|
+
const byId = tasks.findIndex((t) => t.id === query);
|
|
11113
11948
|
if (byId >= 0) return byId;
|
|
11114
|
-
const lower =
|
|
11949
|
+
const lower = query.toLowerCase();
|
|
11115
11950
|
return tasks.findIndex((t) => t.title.toLowerCase().includes(lower));
|
|
11116
11951
|
}
|
|
11117
11952
|
var taskTool = {
|