minimal-agent 0.1.6 → 0.1.7
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/main.js +569 -134
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/main.tsx
|
|
4
4
|
import { render } from "ink";
|
|
5
|
-
import { existsSync as
|
|
5
|
+
import { existsSync as existsSync9, mkdirSync } from "fs";
|
|
6
6
|
import { createRequire } from "module";
|
|
7
7
|
import { resolve as resolve8 } from "path";
|
|
8
8
|
|
|
@@ -462,6 +462,141 @@ function toToolParameters(schema) {
|
|
|
462
462
|
return rest;
|
|
463
463
|
}
|
|
464
464
|
|
|
465
|
+
// src/tools/bash/semantics.ts
|
|
466
|
+
var DEFAULT_SEMANTIC = (exitCode) => ({
|
|
467
|
+
isError: exitCode !== 0,
|
|
468
|
+
message: exitCode !== 0 ? `Command failed with exit code ${exitCode}` : void 0
|
|
469
|
+
});
|
|
470
|
+
var COMMAND_SEMANTICS = /* @__PURE__ */ new Map([
|
|
471
|
+
// grep: 0=找到匹配, 1=无匹配(非错误), 2+=真错误
|
|
472
|
+
["grep", (exitCode) => ({
|
|
473
|
+
isError: exitCode >= 2,
|
|
474
|
+
message: exitCode === 1 ? "No matches found" : void 0
|
|
475
|
+
})],
|
|
476
|
+
// ripgrep 与 grep 同义
|
|
477
|
+
["rg", (exitCode) => ({
|
|
478
|
+
isError: exitCode >= 2,
|
|
479
|
+
message: exitCode === 1 ? "No matches found" : void 0
|
|
480
|
+
})],
|
|
481
|
+
// find: 1=部分目录不可达(仍有结果,非致命), 2+=错误
|
|
482
|
+
["find", (exitCode) => ({
|
|
483
|
+
isError: exitCode >= 2,
|
|
484
|
+
message: exitCode === 1 ? "Some directories were inaccessible" : void 0
|
|
485
|
+
})],
|
|
486
|
+
// diff: 0=相同, 1=有差异(非错误), 2+=错误
|
|
487
|
+
["diff", (exitCode) => ({
|
|
488
|
+
isError: exitCode >= 2,
|
|
489
|
+
message: exitCode === 1 ? "Files differ" : void 0
|
|
490
|
+
})],
|
|
491
|
+
// test: 0=真, 1=假(非错误), 2+=错误
|
|
492
|
+
["test", (exitCode) => ({
|
|
493
|
+
isError: exitCode >= 2,
|
|
494
|
+
message: exitCode === 1 ? "Condition is false" : void 0
|
|
495
|
+
})],
|
|
496
|
+
// [ 是 test 的别名
|
|
497
|
+
["[", (exitCode) => ({
|
|
498
|
+
isError: exitCode >= 2,
|
|
499
|
+
message: exitCode === 1 ? "Condition is false" : void 0
|
|
500
|
+
})]
|
|
501
|
+
]);
|
|
502
|
+
function extractPrimaryCommand(command) {
|
|
503
|
+
let cmd = command.trim();
|
|
504
|
+
const wrapMatch = cmd.match(/^(?:bash|sh|zsh|dash)\s+-c\s+(['"])(.+)\1\s*$/s);
|
|
505
|
+
if (wrapMatch) cmd = wrapMatch[2].trim();
|
|
506
|
+
const segments = cmd.split(/\s*(?:\|\||&&|;|\|)\s*/).filter((s) => s.length > 0);
|
|
507
|
+
let last = segments[segments.length - 1] ?? cmd;
|
|
508
|
+
last = last.trim();
|
|
509
|
+
const tokens = last.split(/\s+/);
|
|
510
|
+
let i = 0;
|
|
511
|
+
if (tokens[i] === "env") i++;
|
|
512
|
+
while (i < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[i])) i++;
|
|
513
|
+
return tokens[i] ?? "";
|
|
514
|
+
}
|
|
515
|
+
function interpretCommandResult(command, exitCode, stdout, stderr) {
|
|
516
|
+
const base = extractPrimaryCommand(command);
|
|
517
|
+
const fn = COMMAND_SEMANTICS.get(base) ?? DEFAULT_SEMANTIC;
|
|
518
|
+
return fn(exitCode, stdout, stderr);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// src/tools/bash/warnings.ts
|
|
522
|
+
var DESTRUCTIVE_PATTERNS = [
|
|
523
|
+
// Git —— 数据丢失 / 难回退
|
|
524
|
+
{
|
|
525
|
+
pattern: /\bgit\s+reset\s+--hard\b/,
|
|
526
|
+
warning: "Note: may discard uncommitted changes"
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
pattern: /\bgit\s+push\b[^;&|\n]*[ \t](--force|--force-with-lease|-f)\b/,
|
|
530
|
+
warning: "Note: may overwrite remote history"
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
pattern: /\bgit\s+clean\b(?![^;&|\n]*(?:-[a-zA-Z]*n|--dry-run))[^;&|\n]*-[a-zA-Z]*f/,
|
|
534
|
+
warning: "Note: may permanently delete untracked files"
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
pattern: /\bgit\s+checkout\s+(--\s+)?\.[ \t]*($|[;&|\n])/,
|
|
538
|
+
warning: "Note: may discard all working tree changes"
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
pattern: /\bgit\s+restore\s+(--\s+)?\.[ \t]*($|[;&|\n])/,
|
|
542
|
+
warning: "Note: may discard all working tree changes"
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
pattern: /\bgit\s+stash[ \t]+(drop|clear)\b/,
|
|
546
|
+
warning: "Note: may permanently remove stashed changes"
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
pattern: /\bgit\s+branch\s+(-D[ \t]|--delete\s+--force|--force\s+--delete)\b/,
|
|
550
|
+
warning: "Note: may force-delete a branch"
|
|
551
|
+
},
|
|
552
|
+
// Git —— 安全绕过
|
|
553
|
+
{
|
|
554
|
+
pattern: /\bgit\s+(commit|push|merge)\b[^;&|\n]*--no-verify\b/,
|
|
555
|
+
warning: "Note: may skip safety hooks"
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
pattern: /\bgit\s+commit\b[^;&|\n]*--amend\b/,
|
|
559
|
+
warning: "Note: may rewrite the last commit"
|
|
560
|
+
},
|
|
561
|
+
// 文件删除(rm -rf / 之类的致命形式由 bash.ts 黑名单处理;这里只做"未到致命"的提醒)
|
|
562
|
+
{
|
|
563
|
+
pattern: /(^|[;&|\n]\s*)rm\s+-[a-zA-Z]*[rR][a-zA-Z]*f|(^|[;&|\n]\s*)rm\s+-[a-zA-Z]*f[a-zA-Z]*[rR]/,
|
|
564
|
+
warning: "Note: may recursively force-remove files"
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
pattern: /(^|[;&|\n]\s*)rm\s+-[a-zA-Z]*[rR]/,
|
|
568
|
+
warning: "Note: may recursively remove files"
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
pattern: /(^|[;&|\n]\s*)rm\s+-[a-zA-Z]*f/,
|
|
572
|
+
warning: "Note: may force-remove files"
|
|
573
|
+
},
|
|
574
|
+
// 数据库
|
|
575
|
+
{
|
|
576
|
+
pattern: /\b(DROP|TRUNCATE)\s+(TABLE|DATABASE|SCHEMA)\b/i,
|
|
577
|
+
warning: "Note: may drop or truncate database objects"
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
pattern: /\bDELETE\s+FROM\s+\w+[ \t]*(;|"|'|\n|$)/i,
|
|
581
|
+
warning: "Note: may delete all rows from a database table"
|
|
582
|
+
},
|
|
583
|
+
// 基础设施
|
|
584
|
+
{
|
|
585
|
+
pattern: /\bkubectl\s+delete\b/,
|
|
586
|
+
warning: "Note: may delete Kubernetes resources"
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
pattern: /\bterraform\s+destroy\b/,
|
|
590
|
+
warning: "Note: may destroy Terraform infrastructure"
|
|
591
|
+
}
|
|
592
|
+
];
|
|
593
|
+
function scanDestructiveCommand(command) {
|
|
594
|
+
for (const { pattern, warning } of DESTRUCTIVE_PATTERNS) {
|
|
595
|
+
if (pattern.test(command)) return warning;
|
|
596
|
+
}
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
|
|
465
600
|
// src/tools/bash/bash.ts
|
|
466
601
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
467
602
|
var MAX_TIMEOUT_MS = 6e5;
|
|
@@ -599,6 +734,7 @@ async function call(input, signal) {
|
|
|
599
734
|
\u547D\u4EE4\uFF1A${command}`
|
|
600
735
|
};
|
|
601
736
|
}
|
|
737
|
+
const destructiveWarning = scanDestructiveCommand(command);
|
|
602
738
|
let stdout = "";
|
|
603
739
|
let stderr = "";
|
|
604
740
|
let exitCode = null;
|
|
@@ -669,16 +805,33 @@ ${stderr.replace(/\s+$/, "")}
|
|
|
669
805
|
}
|
|
670
806
|
parts.push(`
|
|
671
807
|
[exit code: ${exitCode === null ? "killed" : exitCode}]`);
|
|
672
|
-
let
|
|
673
|
-
if (
|
|
674
|
-
|
|
808
|
+
let combinedOutput = parts.join("\n");
|
|
809
|
+
if (combinedOutput.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
|
|
810
|
+
combinedOutput = combinedOutput.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS) + `
|
|
675
811
|
|
|
676
812
|
... (\u8F93\u51FA\u8D85\u8FC7 ${DEFAULT_MAX_RESULT_SIZE_CHARS} \u5B57\u7B26\uFF0C\u5DF2\u622A\u65AD)`;
|
|
677
813
|
}
|
|
678
|
-
if (timedOut ||
|
|
679
|
-
return { ok: false, error:
|
|
814
|
+
if (timedOut || killedBySignal) {
|
|
815
|
+
return { ok: false, error: combinedOutput };
|
|
680
816
|
}
|
|
681
|
-
|
|
817
|
+
const semantic = interpretCommandResult(
|
|
818
|
+
command,
|
|
819
|
+
exitCode ?? 0,
|
|
820
|
+
stdout,
|
|
821
|
+
stderr
|
|
822
|
+
);
|
|
823
|
+
const finalContent = destructiveWarning ? `\u26A0\uFE0F \u8B66\u544A: ${destructiveWarning}
|
|
824
|
+
|
|
825
|
+
${combinedOutput}` : combinedOutput;
|
|
826
|
+
if (semantic.isError) {
|
|
827
|
+
return {
|
|
828
|
+
ok: false,
|
|
829
|
+
error: `\u547D\u4EE4\u5931\u8D25 (exit ${exitCode}): ${stderr || stdout || semantic.message || ""}`.trim() + `
|
|
830
|
+
|
|
831
|
+
${finalContent}`
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
return { ok: true, content: finalContent };
|
|
682
835
|
}
|
|
683
836
|
var bashTool = {
|
|
684
837
|
name: "Bash",
|
|
@@ -694,14 +847,14 @@ var bashTool = {
|
|
|
694
847
|
|
|
695
848
|
// src/tools/edit/edit.ts
|
|
696
849
|
import { readFile as readFile6, writeFile as writeFile3, mkdir as mkdir4 } from "fs/promises";
|
|
697
|
-
import { existsSync as
|
|
850
|
+
import { existsSync as existsSync3 } from "fs";
|
|
698
851
|
import { dirname as dirname5 } from "path";
|
|
699
852
|
import { z as z2 } from "zod";
|
|
700
853
|
|
|
701
854
|
// src/tools/shared/fileUtils.ts
|
|
702
|
-
import { readFile as readFile5 } from "fs/promises";
|
|
855
|
+
import { open, readFile as readFile5 } from "fs/promises";
|
|
703
856
|
import { homedir as homedir4 } from "os";
|
|
704
|
-
import { resolve as resolve4
|
|
857
|
+
import { extname, resolve as resolve4 } from "path";
|
|
705
858
|
var BLOCKED_DEVICE_PATHS = /* @__PURE__ */ new Set([
|
|
706
859
|
"/dev/zero",
|
|
707
860
|
"/dev/random",
|
|
@@ -718,12 +871,12 @@ var BLOCKED_DEVICE_PATHS = /* @__PURE__ */ new Set([
|
|
|
718
871
|
]);
|
|
719
872
|
var WINDOWS_BLOCKED_NAMES = /* @__PURE__ */ new Set(["NUL", "CON", "PRN", "AUX", "COM1", "COM2", "LPT1"]);
|
|
720
873
|
function isBlockedDevicePath(filePath) {
|
|
721
|
-
const
|
|
722
|
-
if (BLOCKED_DEVICE_PATHS.has(
|
|
723
|
-
if (
|
|
874
|
+
const slashed = filePath.replaceAll("\\", "/");
|
|
875
|
+
if (BLOCKED_DEVICE_PATHS.has(slashed)) return true;
|
|
876
|
+
if (slashed.startsWith("/proc/") && (slashed.endsWith("/fd/0") || slashed.endsWith("/fd/1") || slashed.endsWith("/fd/2"))) {
|
|
724
877
|
return true;
|
|
725
878
|
}
|
|
726
|
-
const baseName =
|
|
879
|
+
const baseName = slashed.split("/").pop() ?? "";
|
|
727
880
|
if (WINDOWS_BLOCKED_NAMES.has(baseName.toUpperCase())) {
|
|
728
881
|
return true;
|
|
729
882
|
}
|
|
@@ -733,6 +886,9 @@ function validateAndResolvePath(rawPath, workingDir) {
|
|
|
733
886
|
if (rawPath.includes("\0")) {
|
|
734
887
|
return { ok: false, error: "\u8DEF\u5F84\u5305\u542B\u975E\u6CD5\u5B57\u7B26\uFF08null byte\uFF09" };
|
|
735
888
|
}
|
|
889
|
+
if (isBlockedDevicePath(rawPath)) {
|
|
890
|
+
return { ok: false, error: `\u4E0D\u5141\u8BB8\u8BFB\u53D6\u8BBE\u5907\u6587\u4EF6\uFF1A${rawPath}\u3002\u8BE5\u8DEF\u5F84\u53EF\u80FD\u4EA7\u751F\u65E0\u9650\u8F93\u51FA\u6216\u963B\u585E\u8FDB\u7A0B\u3002` };
|
|
891
|
+
}
|
|
736
892
|
const expanded = expandPath(rawPath);
|
|
737
893
|
const resolved = resolve4(workingDir, expanded);
|
|
738
894
|
if (isBlockedDevicePath(resolved)) {
|
|
@@ -778,6 +934,88 @@ function applyLineEnding(content, ending) {
|
|
|
778
934
|
}
|
|
779
935
|
return content;
|
|
780
936
|
}
|
|
937
|
+
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
938
|
+
".png",
|
|
939
|
+
".jpg",
|
|
940
|
+
".jpeg",
|
|
941
|
+
".gif",
|
|
942
|
+
".webp",
|
|
943
|
+
".bmp",
|
|
944
|
+
".ico",
|
|
945
|
+
".tiff",
|
|
946
|
+
".tif",
|
|
947
|
+
".pdf",
|
|
948
|
+
".doc",
|
|
949
|
+
".docx",
|
|
950
|
+
".xls",
|
|
951
|
+
".xlsx",
|
|
952
|
+
".ppt",
|
|
953
|
+
".pptx",
|
|
954
|
+
".exe",
|
|
955
|
+
".dll",
|
|
956
|
+
".so",
|
|
957
|
+
".dylib",
|
|
958
|
+
".o",
|
|
959
|
+
".a",
|
|
960
|
+
".pyc",
|
|
961
|
+
".pyo",
|
|
962
|
+
".class",
|
|
963
|
+
".jar",
|
|
964
|
+
".zip",
|
|
965
|
+
".tar",
|
|
966
|
+
".gz",
|
|
967
|
+
".bz2",
|
|
968
|
+
".7z",
|
|
969
|
+
".rar",
|
|
970
|
+
".iso",
|
|
971
|
+
".mp3",
|
|
972
|
+
".mp4",
|
|
973
|
+
".mov",
|
|
974
|
+
".avi",
|
|
975
|
+
".mkv",
|
|
976
|
+
".wav",
|
|
977
|
+
".flac",
|
|
978
|
+
".ogg",
|
|
979
|
+
".ttf",
|
|
980
|
+
".otf",
|
|
981
|
+
".woff",
|
|
982
|
+
".woff2",
|
|
983
|
+
".sqlite",
|
|
984
|
+
".sqlite3",
|
|
985
|
+
".db",
|
|
986
|
+
".psd",
|
|
987
|
+
".ai",
|
|
988
|
+
".bin",
|
|
989
|
+
".wasm"
|
|
990
|
+
]);
|
|
991
|
+
function hasBinaryExtension(filePath) {
|
|
992
|
+
const ext = extname(filePath).toLowerCase();
|
|
993
|
+
return BINARY_EXTENSIONS.has(ext);
|
|
994
|
+
}
|
|
995
|
+
async function detectFileBomEncoding(filePath) {
|
|
996
|
+
let fh = null;
|
|
997
|
+
try {
|
|
998
|
+
fh = await open(filePath, "r");
|
|
999
|
+
const buf = Buffer.alloc(3);
|
|
1000
|
+
const { bytesRead } = await fh.read(buf, 0, 3, 0);
|
|
1001
|
+
if (bytesRead >= 3 && buf[0] === 239 && buf[1] === 187 && buf[2] === 191) {
|
|
1002
|
+
return "utf8-bom";
|
|
1003
|
+
}
|
|
1004
|
+
if (bytesRead >= 2 && buf[0] === 255 && buf[1] === 254) {
|
|
1005
|
+
return "utf16le";
|
|
1006
|
+
}
|
|
1007
|
+
return "utf8";
|
|
1008
|
+
} catch {
|
|
1009
|
+
return "utf8";
|
|
1010
|
+
} finally {
|
|
1011
|
+
if (fh) {
|
|
1012
|
+
try {
|
|
1013
|
+
await fh.close();
|
|
1014
|
+
} catch {
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
781
1019
|
var LEFT_SINGLE_CURLY_QUOTE = "\u2018";
|
|
782
1020
|
var RIGHT_SINGLE_CURLY_QUOTE = "\u2019";
|
|
783
1021
|
var LEFT_DOUBLE_CURLY_QUOTE = "\u201C";
|
|
@@ -857,6 +1095,44 @@ function applyCurlySingleQuotes(str) {
|
|
|
857
1095
|
return result.join("");
|
|
858
1096
|
}
|
|
859
1097
|
|
|
1098
|
+
// src/tools/shared/fileState.ts
|
|
1099
|
+
import { existsSync as existsSync2, statSync } from "fs";
|
|
1100
|
+
var fileState = /* @__PURE__ */ new Map();
|
|
1101
|
+
function recordRead(absPath) {
|
|
1102
|
+
try {
|
|
1103
|
+
const st = statSync(absPath);
|
|
1104
|
+
fileState.set(absPath, { timestamp: st.mtimeMs, size: st.size });
|
|
1105
|
+
} catch {
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
function assertFresh(absPath) {
|
|
1109
|
+
const entry = fileState.get(absPath);
|
|
1110
|
+
if (!entry) {
|
|
1111
|
+
if (existsSync2(absPath)) {
|
|
1112
|
+
return {
|
|
1113
|
+
ok: false,
|
|
1114
|
+
error: `\u6587\u4EF6 ${absPath} \u5DF2\u5B58\u5728\u4F46\u672A\u5728\u672C\u4F1A\u8BDD Read \u8FC7\u3002\u8BF7\u5148\u7528 Read \u5DE5\u5177\u8BFB\u53D6\uFF0C\u786E\u8BA4\u5185\u5BB9\u540E\u518D\u4FEE\u6539\u3002`
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
return { ok: true };
|
|
1118
|
+
}
|
|
1119
|
+
try {
|
|
1120
|
+
const st = statSync(absPath);
|
|
1121
|
+
if (st.mtimeMs > entry.timestamp) {
|
|
1122
|
+
return {
|
|
1123
|
+
ok: false,
|
|
1124
|
+
error: `${absPath} \u5728 Read \u540E\u88AB\u5916\u90E8\u4FEE\u6539\uFF08mtime \u6F02\u79FB\uFF09\u3002\u8BF7\u91CD\u65B0\u7528 Read \u5DE5\u5177\u8BFB\u53D6\u6700\u65B0\u5185\u5BB9\u3002`
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
} catch {
|
|
1128
|
+
return { ok: true };
|
|
1129
|
+
}
|
|
1130
|
+
return { ok: true };
|
|
1131
|
+
}
|
|
1132
|
+
function clearFileState() {
|
|
1133
|
+
fileState.clear();
|
|
1134
|
+
}
|
|
1135
|
+
|
|
860
1136
|
// src/tools/edit/edit.ts
|
|
861
1137
|
var MAX_EDIT_FILE_SIZE_BYTES = 1024 * 1024 * 1024;
|
|
862
1138
|
var inputSchema2 = z2.object({
|
|
@@ -885,10 +1161,14 @@ async function call2(input) {
|
|
|
885
1161
|
const pathResult = validateAndResolvePath(input.file_path, getWorkingDir());
|
|
886
1162
|
if (!pathResult.ok) return pathResult;
|
|
887
1163
|
const filePath = pathResult.resolvedPath;
|
|
1164
|
+
const freshness = assertFresh(filePath);
|
|
1165
|
+
if (!freshness.ok) {
|
|
1166
|
+
return { ok: false, error: freshness.error };
|
|
1167
|
+
}
|
|
888
1168
|
if (old_string === new_string) {
|
|
889
1169
|
return { ok: false, error: "old_string \u4E0E new_string \u5B8C\u5168\u76F8\u540C\uFF0C\u6CA1\u6709\u53EF\u6539\u7684\u5185\u5BB9\u3002" };
|
|
890
1170
|
}
|
|
891
|
-
if (old_string === "" && !
|
|
1171
|
+
if (old_string === "" && !existsSync3(filePath)) {
|
|
892
1172
|
try {
|
|
893
1173
|
await mkdir4(dirname5(filePath), { recursive: true });
|
|
894
1174
|
await writeFile3(filePath, new_string, "utf8");
|
|
@@ -900,7 +1180,7 @@ async function call2(input) {
|
|
|
900
1180
|
return { ok: false, error: `\u521B\u5EFA\u6587\u4EF6\u5931\u8D25\uFF1A${e.message}` };
|
|
901
1181
|
}
|
|
902
1182
|
}
|
|
903
|
-
if (!
|
|
1183
|
+
if (!existsSync3(filePath)) {
|
|
904
1184
|
return {
|
|
905
1185
|
ok: false,
|
|
906
1186
|
error: `\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${filePath}
|
|
@@ -1053,22 +1333,146 @@ var editTool = {
|
|
|
1053
1333
|
call: call2
|
|
1054
1334
|
};
|
|
1055
1335
|
|
|
1336
|
+
// src/tools/edit/multi-edit.ts
|
|
1337
|
+
import { readFile as readFile7, writeFile as writeFile4 } from "fs/promises";
|
|
1338
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1339
|
+
import { z as z3 } from "zod";
|
|
1340
|
+
var editItemSchema = z3.object({
|
|
1341
|
+
old_string: z3.string().min(1).describe("\u8981\u66FF\u6362\u7684\u539F\u6587\u672C\uFF08\u4E0D\u5141\u8BB8\u4E3A\u7A7A \u2014\u2014 \u521B\u5EFA\u65B0\u6587\u4EF6\u8BF7\u7528 Edit \u5DE5\u5177\uFF09"),
|
|
1342
|
+
new_string: z3.string().describe("\u66FF\u6362\u4E3A\u7684\u65B0\u6587\u672C"),
|
|
1343
|
+
replace_all: z3.boolean().optional().describe("\u662F\u5426\u66FF\u6362\u6240\u6709\u51FA\u73B0\u4F4D\u7F6E\uFF08\u9ED8\u8BA4 false\uFF0C\u8981\u6C42 old_string \u5728\u5F53\u524D\u5185\u5BB9\u4E2D\u552F\u4E00\uFF09")
|
|
1344
|
+
});
|
|
1345
|
+
var inputSchema3 = z3.object({
|
|
1346
|
+
file_path: z3.string().min(1).describe("\u8981\u7F16\u8F91\u7684\u6587\u4EF6\u8DEF\u5F84"),
|
|
1347
|
+
edits: z3.array(editItemSchema).min(1).max(50).describe("\u6309\u987A\u5E8F\u5E94\u7528\u7684 edit \u5217\u8868\uFF081-50 \u6761\uFF09\uFF0C\u539F\u5B50\u5316\u6267\u884C\uFF1A\u5168\u90E8\u6210\u529F\u624D\u843D\u76D8")
|
|
1348
|
+
});
|
|
1349
|
+
var parameters3 = toToolParameters(inputSchema3);
|
|
1350
|
+
var description3 = `Performs multiple exact string replacements in a single file, applied atomically (all-or-nothing).
|
|
1351
|
+
|
|
1352
|
+
Usage:
|
|
1353
|
+
- You MUST use your \`Read\` tool to read the current content of the file BEFORE calling MultiEdit.
|
|
1354
|
+
- Provide a list of \`edits\`, each with \`old_string\`, \`new_string\`, and optional \`replace_all\`.
|
|
1355
|
+
- All edits are applied sequentially in memory; if ANY edit fails (string not found, ambiguous match, or dependency conflict), the file on disk is UNTOUCHED.
|
|
1356
|
+
- Order matters: later edits operate on the result of earlier edits. If a later edit's \`old_string\` is a substring of an earlier edit's \`new_string\`, MultiEdit refuses (reorder or merge instead).
|
|
1357
|
+
- Each \`old_string\` must be unique in the current content (after prior edits) unless \`replace_all=true\`.
|
|
1358
|
+
- Empty \`old_string\` is NOT allowed in MultiEdit. To create a new file, use the \`Edit\` tool with a single empty-old_string call.
|
|
1359
|
+
- Preserve exact indentation (tabs/spaces).`;
|
|
1360
|
+
function countOccurrences2(haystack, needle) {
|
|
1361
|
+
if (needle.length === 0) return 0;
|
|
1362
|
+
let count = 0;
|
|
1363
|
+
let pos = 0;
|
|
1364
|
+
while ((pos = haystack.indexOf(needle, pos)) !== -1) {
|
|
1365
|
+
count++;
|
|
1366
|
+
pos += needle.length;
|
|
1367
|
+
}
|
|
1368
|
+
return count;
|
|
1369
|
+
}
|
|
1370
|
+
function splitReplaceAll2(haystack, needle, replacement) {
|
|
1371
|
+
return haystack.split(needle).join(replacement);
|
|
1372
|
+
}
|
|
1373
|
+
async function call3(input) {
|
|
1374
|
+
const pathResult = validateAndResolvePath(input.file_path, getWorkingDir());
|
|
1375
|
+
if (!pathResult.ok) return pathResult;
|
|
1376
|
+
const filePath = pathResult.resolvedPath;
|
|
1377
|
+
const freshness = assertFresh(filePath);
|
|
1378
|
+
if (!freshness.ok) {
|
|
1379
|
+
return { ok: false, error: freshness.error };
|
|
1380
|
+
}
|
|
1381
|
+
if (!existsSync4(filePath)) {
|
|
1382
|
+
return {
|
|
1383
|
+
ok: false,
|
|
1384
|
+
error: `\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${filePath}
|
|
1385
|
+
\uFF08MultiEdit \u4E0D\u652F\u6301\u521B\u5EFA\u65B0\u6587\u4EF6\uFF0C\u8BF7\u6539\u7528 Edit \u5DE5\u5177\uFF09`
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
const edits = input.edits;
|
|
1389
|
+
for (let i = 0; i < edits.length; i++) {
|
|
1390
|
+
for (let j = 0; j < i; j++) {
|
|
1391
|
+
if (edits[j].new_string.includes(edits[i].old_string)) {
|
|
1392
|
+
return {
|
|
1393
|
+
ok: false,
|
|
1394
|
+
error: `edits[${i}].old_string \u662F edits[${j}].new_string \u7684\u5B50\u4E32\uFF0C\u4F1A\u5BFC\u81F4\u540E\u7EED edit \u547D\u4E2D\u524D\u5E8F\u4EA7\u7269\uFF0C\u8BF7\u91CD\u65B0\u6392\u5E8F\u6216\u5408\u5E76`
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
let originalContent;
|
|
1400
|
+
try {
|
|
1401
|
+
originalContent = await readFile7(filePath, "utf8");
|
|
1402
|
+
} catch (e) {
|
|
1403
|
+
return { ok: false, error: `\u8BFB\u53D6\u5931\u8D25\uFF1A${e.message}` };
|
|
1404
|
+
}
|
|
1405
|
+
let currentContent = originalContent;
|
|
1406
|
+
for (let i = 0; i < edits.length; i++) {
|
|
1407
|
+
const edit = edits[i];
|
|
1408
|
+
const replaceAll = edit.replace_all ?? false;
|
|
1409
|
+
let searchTarget = edit.old_string;
|
|
1410
|
+
let processedNewString = edit.new_string;
|
|
1411
|
+
const actualOld = findActualString(currentContent, edit.old_string);
|
|
1412
|
+
if (actualOld === null) {
|
|
1413
|
+
return {
|
|
1414
|
+
ok: false,
|
|
1415
|
+
error: `edits[${i}] \u672A\u5339\u914D\u5230 old_string\uFF08\u5728\u5DF2\u5E94\u7528\u524D\u5E8F ${i} \u5904\u4FEE\u6539\u540E\u7684\u5185\u5BB9\u4E2D\u627E\u4E0D\u5230\uFF09\u3002\u8BF7\u5148\u7528 Read \u5DE5\u5177\u6838\u5BF9\u5F53\u524D\u5185\u5BB9\uFF08\u6CE8\u610F\u7A7A\u683C/\u7F29\u8FDB/\u6362\u884C\uFF09\uFF0C\u5E76\u68C0\u67E5 edit \u987A\u5E8F\u3002`
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
if (actualOld !== edit.old_string) {
|
|
1419
|
+
searchTarget = actualOld;
|
|
1420
|
+
processedNewString = preserveQuoteStyle(edit.old_string, actualOld, edit.new_string);
|
|
1421
|
+
}
|
|
1422
|
+
const occurrences = countOccurrences2(currentContent, searchTarget);
|
|
1423
|
+
if (occurrences === 0) {
|
|
1424
|
+
return {
|
|
1425
|
+
ok: false,
|
|
1426
|
+
error: `edits[${i}] \u672A\u5339\u914D\u5230 old_string\uFF08\u5DF2\u5E94\u7528\u524D\u5E8F ${i} \u5904\u4FEE\u6539\u540E\u5185\u5BB9\u4E2D\u51FA\u73B0 0 \u6B21\uFF09\u3002`
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
if (occurrences > 1 && !replaceAll) {
|
|
1430
|
+
return {
|
|
1431
|
+
ok: false,
|
|
1432
|
+
error: `edits[${i}].old_string \u5728\u5F53\u524D\u5185\u5BB9\u4E2D\u51FA\u73B0 ${occurrences} \u6B21\uFF0C\u4E0D\u552F\u4E00\u3002\u8BF7\u6269\u5927 old_string \u5305\u542B\u66F4\u591A\u4E0A\u4E0B\u6587\uFF0C\u6216\u663E\u5F0F\u4F20 replace_all=true\u3002`
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
currentContent = replaceAll ? splitReplaceAll2(currentContent, searchTarget, processedNewString) : currentContent.replace(searchTarget, processedNewString);
|
|
1436
|
+
}
|
|
1437
|
+
const lineEnding = await detectFileLineEndings(filePath);
|
|
1438
|
+
const finalContent = applyLineEnding(currentContent, lineEnding);
|
|
1439
|
+
try {
|
|
1440
|
+
await writeFile4(filePath, finalContent, "utf8");
|
|
1441
|
+
} catch (e) {
|
|
1442
|
+
return { ok: false, error: `\u5199\u5165\u5931\u8D25\uFF1A${e.message}` };
|
|
1443
|
+
}
|
|
1444
|
+
return {
|
|
1445
|
+
ok: true,
|
|
1446
|
+
content: `\u5DF2\u5BF9 ${filePath} \u5E94\u7528 ${edits.length} \u5904\u4FEE\u6539`
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
var multiEditTool = {
|
|
1450
|
+
name: "MultiEdit",
|
|
1451
|
+
description: description3,
|
|
1452
|
+
inputSchema: inputSchema3,
|
|
1453
|
+
parameters: parameters3,
|
|
1454
|
+
isReadOnly: false,
|
|
1455
|
+
isConcurrencySafe: false,
|
|
1456
|
+
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
1457
|
+
call: call3
|
|
1458
|
+
};
|
|
1459
|
+
|
|
1056
1460
|
// src/tools/glob/glob.ts
|
|
1057
1461
|
import { stat as stat2 } from "fs/promises";
|
|
1058
1462
|
import { isAbsolute, resolve as resolve5 } from "path";
|
|
1059
1463
|
import fg from "fast-glob";
|
|
1060
|
-
import { z as
|
|
1061
|
-
var
|
|
1062
|
-
pattern:
|
|
1063
|
-
path:
|
|
1464
|
+
import { z as z4 } from "zod";
|
|
1465
|
+
var inputSchema4 = z4.object({
|
|
1466
|
+
pattern: z4.string().min(1).describe('glob \u6A21\u5F0F\uFF0C\u4F8B\u5982 "**/*.ts" \u6216 "src/components/**/*.tsx"'),
|
|
1467
|
+
path: z4.string().optional().describe('\u641C\u7D22\u7684\u6839\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09\uFF1B\u7701\u7565\u65F6\u4E0D\u8981\u4F20 "undefined" \u5B57\u7B26\u4E32')
|
|
1064
1468
|
});
|
|
1065
|
-
var
|
|
1066
|
-
var
|
|
1469
|
+
var parameters4 = toToolParameters(inputSchema4);
|
|
1470
|
+
var description4 = `- Fast file pattern matching tool that works with any codebase size
|
|
1067
1471
|
- Supports glob patterns like "**/*.js" or "src/**/*.ts"
|
|
1068
1472
|
- Returns matching file paths sorted by modification time (oldest first)
|
|
1069
1473
|
- Use this tool when you need to find files by name patterns
|
|
1070
1474
|
- When you need to do an open ended search that may require multiple rounds, prefer the Grep tool for content search`;
|
|
1071
|
-
async function
|
|
1475
|
+
async function call4(input) {
|
|
1072
1476
|
const cwd = input.path ? resolve5(input.path) : getWorkingDir();
|
|
1073
1477
|
const pattern = input.pattern.replace(/\\/g, "/");
|
|
1074
1478
|
let matches;
|
|
@@ -1115,23 +1519,23 @@ async function call3(input) {
|
|
|
1115
1519
|
}
|
|
1116
1520
|
var globTool = {
|
|
1117
1521
|
name: "Glob",
|
|
1118
|
-
description:
|
|
1119
|
-
inputSchema:
|
|
1120
|
-
parameters:
|
|
1522
|
+
description: description4,
|
|
1523
|
+
inputSchema: inputSchema4,
|
|
1524
|
+
parameters: parameters4,
|
|
1121
1525
|
isReadOnly: true,
|
|
1122
1526
|
isConcurrencySafe: true,
|
|
1123
1527
|
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
1124
|
-
call:
|
|
1528
|
+
call: call4
|
|
1125
1529
|
};
|
|
1126
1530
|
|
|
1127
1531
|
// src/tools/grep/grep.ts
|
|
1128
1532
|
import { spawn as spawn3 } from "child_process";
|
|
1129
1533
|
import { resolve as resolve7 } from "path";
|
|
1130
|
-
import { z as
|
|
1534
|
+
import { z as z5 } from "zod";
|
|
1131
1535
|
|
|
1132
1536
|
// src/tools/grep/rgPath.ts
|
|
1133
1537
|
import { spawn as spawn2 } from "child_process";
|
|
1134
|
-
import { chmodSync, existsSync as
|
|
1538
|
+
import { chmodSync, existsSync as existsSync5 } from "fs";
|
|
1135
1539
|
import { resolve as resolve6 } from "path";
|
|
1136
1540
|
var cached;
|
|
1137
1541
|
async function resolveRgPath() {
|
|
@@ -1141,15 +1545,15 @@ async function resolveRgPath() {
|
|
|
1141
1545
|
}
|
|
1142
1546
|
async function detect() {
|
|
1143
1547
|
const fromEnv = process.env.MINIMAL_AGENT_RIPGREP_PATH;
|
|
1144
|
-
if (fromEnv &&
|
|
1548
|
+
if (fromEnv && existsSync5(fromEnv)) return fromEnv;
|
|
1145
1549
|
const vendored = vendoredRgPath();
|
|
1146
|
-
if (vendored &&
|
|
1550
|
+
if (vendored && existsSync5(vendored)) {
|
|
1147
1551
|
ensureExecutable(vendored);
|
|
1148
1552
|
return vendored;
|
|
1149
1553
|
}
|
|
1150
1554
|
if (await trySpawn("rg")) return "rg";
|
|
1151
1555
|
for (const candidate of claudeCodeCandidates()) {
|
|
1152
|
-
if (
|
|
1556
|
+
if (existsSync5(candidate)) {
|
|
1153
1557
|
ensureExecutable(candidate);
|
|
1154
1558
|
return candidate;
|
|
1155
1559
|
}
|
|
@@ -1242,21 +1646,21 @@ function claudeCodeCandidates() {
|
|
|
1242
1646
|
}
|
|
1243
1647
|
|
|
1244
1648
|
// src/tools/grep/grep.ts
|
|
1245
|
-
var
|
|
1246
|
-
pattern:
|
|
1247
|
-
path:
|
|
1248
|
-
glob:
|
|
1249
|
-
type:
|
|
1250
|
-
output_mode:
|
|
1251
|
-
"-i":
|
|
1252
|
-
"-n":
|
|
1253
|
-
"-A":
|
|
1254
|
-
"-B":
|
|
1255
|
-
"-C":
|
|
1256
|
-
head_limit:
|
|
1649
|
+
var inputSchema5 = z5.object({
|
|
1650
|
+
pattern: z5.string().min(1).describe("\u6B63\u5219\u8868\u8FBE\u5F0F\uFF08ripgrep \u517C\u5BB9\u8BED\u6CD5\uFF09"),
|
|
1651
|
+
path: z5.string().optional().describe("\u641C\u7D22\u7684\u6839\u76EE\u5F55\u6216\u6587\u4EF6\uFF08\u9ED8\u8BA4\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09"),
|
|
1652
|
+
glob: z5.string().optional().describe('\u6587\u4EF6\u540D glob \u8FC7\u6EE4\uFF0C\u5982 "*.ts"'),
|
|
1653
|
+
type: z5.string().optional().describe('rg \u7684\u6587\u4EF6\u7C7B\u578B\u5FEB\u6377\u540D\uFF0C\u5982 "py"\u3001"rust"\u3001"js"'),
|
|
1654
|
+
output_mode: z5.enum(["content", "files_with_matches", "count"]).optional().describe("\u8F93\u51FA\u6A21\u5F0F\uFF1Acontent=\u5339\u914D\u884C\uFF1Bfiles_with_matches=\u53EA\u5217\u6587\u4EF6\uFF1Bcount=\u6BCF\u6587\u4EF6\u8BA1\u6570"),
|
|
1655
|
+
"-i": z5.boolean().optional().describe("\u5FFD\u7565\u5927\u5C0F\u5199"),
|
|
1656
|
+
"-n": z5.boolean().optional().describe("\u663E\u793A\u884C\u53F7\uFF08\u4EC5 content \u6A21\u5F0F\uFF09"),
|
|
1657
|
+
"-A": z5.number().int().min(0).optional().describe("\u5339\u914D\u540E\u5C55\u793A\u51E0\u884C\u4E0A\u4E0B\u6587"),
|
|
1658
|
+
"-B": z5.number().int().min(0).optional().describe("\u5339\u914D\u524D\u5C55\u793A\u51E0\u884C\u4E0A\u4E0B\u6587"),
|
|
1659
|
+
"-C": z5.number().int().min(0).optional().describe("\u5339\u914D\u524D\u540E\u5404\u5C55\u793A\u51E0\u884C\uFF08\u8986\u76D6 -A/-B\uFF09"),
|
|
1660
|
+
head_limit: z5.number().int().positive().optional().describe("\u8F93\u51FA\u6700\u591A\u4FDD\u7559\u524D N \u884C\uFF08\u9632\u6B62\u7ED3\u679C\u8FC7\u5927\uFF09")
|
|
1257
1661
|
});
|
|
1258
|
-
var
|
|
1259
|
-
var
|
|
1662
|
+
var parameters5 = toToolParameters(inputSchema5);
|
|
1663
|
+
var description5 = `A powerful search tool built on ripgrep.
|
|
1260
1664
|
|
|
1261
1665
|
Usage:
|
|
1262
1666
|
- ALWAYS use Grep for content search tasks. Do NOT invoke \`grep\` or \`rg\` directly via Bash.
|
|
@@ -1264,7 +1668,7 @@ Usage:
|
|
|
1264
1668
|
- Filter files with glob parameter (e.g., "*.js", "**/*.tsx") or type parameter (e.g., "js", "py", "rust")
|
|
1265
1669
|
- Output modes: "content" shows matching lines, "files_with_matches" shows only file paths (default), "count" shows match counts
|
|
1266
1670
|
- Pattern syntax: Uses ripgrep (not classic grep)`;
|
|
1267
|
-
async function
|
|
1671
|
+
async function call5(input, signal) {
|
|
1268
1672
|
const args = [];
|
|
1269
1673
|
const mode = input.output_mode ?? "files_with_matches";
|
|
1270
1674
|
if (mode === "files_with_matches") args.push("-l");
|
|
@@ -1341,27 +1745,27 @@ async function call4(input, signal) {
|
|
|
1341
1745
|
}
|
|
1342
1746
|
var grepTool = {
|
|
1343
1747
|
name: "Grep",
|
|
1344
|
-
description:
|
|
1345
|
-
inputSchema:
|
|
1346
|
-
parameters:
|
|
1748
|
+
description: description5,
|
|
1749
|
+
inputSchema: inputSchema5,
|
|
1750
|
+
parameters: parameters5,
|
|
1347
1751
|
isReadOnly: true,
|
|
1348
1752
|
isConcurrencySafe: true,
|
|
1349
1753
|
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
1350
|
-
call:
|
|
1754
|
+
call: call5
|
|
1351
1755
|
};
|
|
1352
1756
|
|
|
1353
1757
|
// src/tools/read/read.ts
|
|
1354
1758
|
import { createReadStream } from "fs";
|
|
1355
|
-
import { readFile as
|
|
1759
|
+
import { readFile as readFile8, stat as stat3 } from "fs/promises";
|
|
1356
1760
|
import { createInterface } from "readline";
|
|
1357
|
-
import { z as
|
|
1358
|
-
var
|
|
1359
|
-
file_path:
|
|
1360
|
-
offset:
|
|
1361
|
-
limit:
|
|
1761
|
+
import { z as z6 } from "zod";
|
|
1762
|
+
var inputSchema6 = z6.object({
|
|
1763
|
+
file_path: z6.string().min(1, "\u5FC5\u987B\u63D0\u4F9B file_path").describe("\u8981\u8BFB\u53D6\u7684\u6587\u4EF6\u8DEF\u5F84\uFF0C\u7EDD\u5BF9\u8DEF\u5F84\u4F18\u5148"),
|
|
1764
|
+
offset: z6.number().int().positive().optional().describe("\u8D77\u59CB\u884C\u53F7\uFF081-indexed\uFF09\uFF1B\u4E0D\u586B\u5219\u4ECE\u6587\u4EF6\u5F00\u5934\u8BFB"),
|
|
1765
|
+
limit: z6.number().int().positive().optional().describe(`\u6700\u591A\u8BFB\u591A\u5C11\u884C\uFF1B\u4E0D\u586B\u5219\u7528\u9ED8\u8BA4\u503C ${MAX_LINES_TO_READ}`)
|
|
1362
1766
|
});
|
|
1363
|
-
var
|
|
1364
|
-
var
|
|
1767
|
+
var parameters6 = toToolParameters(inputSchema6);
|
|
1768
|
+
var description6 = `Reads a file from the local filesystem. You can access any file directly by using this tool.
|
|
1365
1769
|
Assume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.
|
|
1366
1770
|
|
|
1367
1771
|
Usage:
|
|
@@ -1372,7 +1776,7 @@ Usage:
|
|
|
1372
1776
|
- This tool can only read text files, not directories. To read a directory, use the Glob tool.
|
|
1373
1777
|
- If you read a file that exists but has empty contents you will receive a warning in place of file contents.`;
|
|
1374
1778
|
var STREAM_THRESHOLD = 1024 * 1024;
|
|
1375
|
-
async function
|
|
1779
|
+
async function call6(input) {
|
|
1376
1780
|
const offset = input.offset ?? 1;
|
|
1377
1781
|
const limit = input.limit ?? MAX_LINES_TO_READ;
|
|
1378
1782
|
const pathResult = validateAndResolvePath(input.file_path, getWorkingDir());
|
|
@@ -1383,6 +1787,12 @@ async function call5(input) {
|
|
|
1383
1787
|
if (isBlockedDevicePath(filePath)) {
|
|
1384
1788
|
return { ok: false, error: `\u4E0D\u5141\u8BB8\u8BFB\u53D6\u8BBE\u5907\u6587\u4EF6\uFF1A${filePath}\u3002\u8BE5\u8DEF\u5F84\u53EF\u80FD\u4EA7\u751F\u65E0\u9650\u8F93\u51FA\u6216\u963B\u585E\u8FDB\u7A0B\u3002` };
|
|
1385
1789
|
}
|
|
1790
|
+
if (hasBinaryExtension(filePath)) {
|
|
1791
|
+
return {
|
|
1792
|
+
ok: false,
|
|
1793
|
+
error: `\u4E0D\u652F\u6301\u8BFB\u53D6\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF1A${filePath}\uFF08\u6269\u5C55\u540D\u547D\u4E2D\u4E8C\u8FDB\u5236\u9ED1\u540D\u5355\uFF09\u3002\u82E5\u8BE5\u6587\u4EF6\u5B9E\u9645\u4E3A\u6587\u672C\uFF0C\u53EF\u6539\u540E\u7F00\u6216\u7528 Bash cat \u65C1\u8DEF\u3002`
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1386
1796
|
let st;
|
|
1387
1797
|
try {
|
|
1388
1798
|
st = await stat3(filePath);
|
|
@@ -1408,6 +1818,7 @@ async function call5(input) {
|
|
|
1408
1818
|
numbered = result.numbered;
|
|
1409
1819
|
totalLines = result.totalLines;
|
|
1410
1820
|
if (result.isEmpty) {
|
|
1821
|
+
recordRead(filePath);
|
|
1411
1822
|
return { ok: true, content: "<file is empty>" };
|
|
1412
1823
|
}
|
|
1413
1824
|
} else {
|
|
@@ -1416,6 +1827,7 @@ async function call5(input) {
|
|
|
1416
1827
|
totalLines = result.totalLines;
|
|
1417
1828
|
}
|
|
1418
1829
|
if (totalLines === 0 || !numbered) {
|
|
1830
|
+
recordRead(filePath);
|
|
1419
1831
|
return { ok: true, content: "<file is empty>" };
|
|
1420
1832
|
}
|
|
1421
1833
|
let content = numbered;
|
|
@@ -1438,10 +1850,11 @@ async function call5(input) {
|
|
|
1438
1850
|
|
|
1439
1851
|
\u26A0\uFE0F \u6CE8\u610F\uFF1A\u8FD9\u662F\u4E00\u4E2A\u5927\u6587\u4EF6\uFF08${(st.size / 1024).toFixed(1)} KB\uFF09\u3002\u5EFA\u8BAE\u7528 offset/limit \u5206\u6BB5\u8BFB\u53D6\uFF0C\u4F8B\u5982\u5148\u8BFB\u5173\u952E\u90E8\u5206\uFF08imports\u3001exports\u3001\u51FD\u6570\u7B7E\u540D\uFF09\u3002`;
|
|
1440
1852
|
}
|
|
1853
|
+
recordRead(filePath);
|
|
1441
1854
|
return { ok: true, content };
|
|
1442
1855
|
}
|
|
1443
1856
|
async function readSmallFile(filePath, offset, limit) {
|
|
1444
|
-
const raw = await
|
|
1857
|
+
const raw = await readFile8(filePath, "utf8");
|
|
1445
1858
|
if (raw.length === 0) {
|
|
1446
1859
|
return { numbered: "", totalLines: 0, isEmpty: true };
|
|
1447
1860
|
}
|
|
@@ -1491,17 +1904,17 @@ async function readLargeFileStream(filePath, offset, limit) {
|
|
|
1491
1904
|
}
|
|
1492
1905
|
var readTool = {
|
|
1493
1906
|
name: "Read",
|
|
1494
|
-
description:
|
|
1495
|
-
inputSchema:
|
|
1496
|
-
parameters:
|
|
1907
|
+
description: description6,
|
|
1908
|
+
inputSchema: inputSchema6,
|
|
1909
|
+
parameters: parameters6,
|
|
1497
1910
|
isReadOnly: true,
|
|
1498
1911
|
isConcurrencySafe: true,
|
|
1499
1912
|
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
1500
|
-
call:
|
|
1913
|
+
call: call6
|
|
1501
1914
|
};
|
|
1502
1915
|
|
|
1503
1916
|
// src/tools/webfetch/webfetch.ts
|
|
1504
|
-
import { z as
|
|
1917
|
+
import { z as z7 } from "zod";
|
|
1505
1918
|
|
|
1506
1919
|
// src/tools/webfetch/preapproved.ts
|
|
1507
1920
|
var PREAPPROVED_HOSTS = /* @__PURE__ */ new Set([
|
|
@@ -1786,12 +2199,12 @@ function cleanCache() {
|
|
|
1786
2199
|
}
|
|
1787
2200
|
}
|
|
1788
2201
|
}
|
|
1789
|
-
var
|
|
1790
|
-
url:
|
|
1791
|
-
prompt:
|
|
2202
|
+
var inputSchema7 = z7.object({
|
|
2203
|
+
url: z7.string().describe("\u8981\u83B7\u53D6\u5185\u5BB9\u7684 URL"),
|
|
2204
|
+
prompt: z7.string().describe("\u5BF9\u5185\u5BB9\u8FDB\u884C\u5904\u7406\u7684\u6307\u4EE4\uFF0C\u63CF\u8FF0\u4F60\u60F3\u4ECE\u9875\u9762\u63D0\u53D6\u4EC0\u4E48\u4FE1\u606F")
|
|
1792
2205
|
});
|
|
1793
|
-
var
|
|
1794
|
-
var
|
|
2206
|
+
var parameters7 = toToolParameters(inputSchema7);
|
|
2207
|
+
var description7 = `- Fetches content from a specified URL and processes it using an AI model.
|
|
1795
2208
|
- Takes a URL and a prompt as input.
|
|
1796
2209
|
- Fetches the URL content, converts HTML to markdown.
|
|
1797
2210
|
- Processes the content with the prompt (e.g., extract summary, find specific info).
|
|
@@ -1900,7 +2313,7 @@ async function htmlToMarkdown(html) {
|
|
|
1900
2313
|
const td = new TurndownService();
|
|
1901
2314
|
return td.turndown(html);
|
|
1902
2315
|
}
|
|
1903
|
-
async function
|
|
2316
|
+
async function call7(input, signal) {
|
|
1904
2317
|
const { url } = input;
|
|
1905
2318
|
const start = Date.now();
|
|
1906
2319
|
const cacheKey = url;
|
|
@@ -1998,17 +2411,17 @@ ${content}`;
|
|
|
1998
2411
|
}
|
|
1999
2412
|
var webfetchTool = {
|
|
2000
2413
|
name: "WebFetch",
|
|
2001
|
-
description:
|
|
2002
|
-
inputSchema:
|
|
2003
|
-
parameters:
|
|
2414
|
+
description: description7,
|
|
2415
|
+
inputSchema: inputSchema7,
|
|
2416
|
+
parameters: parameters7,
|
|
2004
2417
|
isReadOnly: true,
|
|
2005
2418
|
isConcurrencySafe: true,
|
|
2006
2419
|
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
2007
|
-
call:
|
|
2420
|
+
call: call7
|
|
2008
2421
|
};
|
|
2009
2422
|
|
|
2010
2423
|
// src/tools/webbrowser/webbrowser.ts
|
|
2011
|
-
import { z as
|
|
2424
|
+
import { z as z8 } from "zod";
|
|
2012
2425
|
|
|
2013
2426
|
// src/tools/webbrowser/browser.ts
|
|
2014
2427
|
import os from "os";
|
|
@@ -2044,15 +2457,15 @@ function screenshotPath(prefix = "browser") {
|
|
|
2044
2457
|
}
|
|
2045
2458
|
|
|
2046
2459
|
// src/tools/webbrowser/webbrowser.ts
|
|
2047
|
-
var
|
|
2048
|
-
action:
|
|
2049
|
-
url:
|
|
2050
|
-
selector:
|
|
2051
|
-
value:
|
|
2052
|
-
timeout:
|
|
2460
|
+
var inputSchema8 = z8.object({
|
|
2461
|
+
action: z8.enum(["navigate", "screenshot", "getContent", "click", "fill", "submit"]).describe("Browser action to perform"),
|
|
2462
|
+
url: z8.string().url().optional().describe("URL to navigate to (required for navigate action)"),
|
|
2463
|
+
selector: z8.string().optional().describe("CSS selector for click/fill/submit actions"),
|
|
2464
|
+
value: z8.string().optional().describe("Value to fill in input fields"),
|
|
2465
|
+
timeout: z8.number().int().positive().optional().describe("Timeout in milliseconds (default: 30000)")
|
|
2053
2466
|
});
|
|
2054
|
-
var
|
|
2055
|
-
var
|
|
2467
|
+
var parameters8 = toToolParameters(inputSchema8);
|
|
2468
|
+
var description8 = `Control a headless web browser. Navigate to URLs, take screenshots, and interact with web pages.
|
|
2056
2469
|
|
|
2057
2470
|
When to use WebBrowser vs WebSearch:
|
|
2058
2471
|
- WebSearch: When you need to find information or discover URLs through search
|
|
@@ -2083,7 +2496,7 @@ Example actions:
|
|
|
2083
2496
|
- Take screenshot: { action: "screenshot" }
|
|
2084
2497
|
- Click element: { action: "click", selector: "#submit-btn" }
|
|
2085
2498
|
- Fill form field: { action: "fill", selector: "input[name='email']", value: "user@example.com" }`;
|
|
2086
|
-
async function
|
|
2499
|
+
async function call8(input, signal) {
|
|
2087
2500
|
const { action, url, selector, value, timeout = 3e4 } = input;
|
|
2088
2501
|
let page;
|
|
2089
2502
|
try {
|
|
@@ -2187,30 +2600,30 @@ Current URL: ${page.url()}`
|
|
|
2187
2600
|
}
|
|
2188
2601
|
var webbrowserTool = {
|
|
2189
2602
|
name: "WebBrowser",
|
|
2190
|
-
description:
|
|
2191
|
-
inputSchema:
|
|
2192
|
-
parameters:
|
|
2603
|
+
description: description8,
|
|
2604
|
+
inputSchema: inputSchema8,
|
|
2605
|
+
parameters: parameters8,
|
|
2193
2606
|
isReadOnly: false,
|
|
2194
2607
|
isConcurrencySafe: false,
|
|
2195
2608
|
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
2196
|
-
call:
|
|
2609
|
+
call: call8
|
|
2197
2610
|
};
|
|
2198
2611
|
|
|
2199
2612
|
// src/tools/websearch/websearch.ts
|
|
2200
|
-
import { z as
|
|
2201
|
-
var
|
|
2202
|
-
query:
|
|
2203
|
-
max_results:
|
|
2204
|
-
search_depth:
|
|
2205
|
-
topic:
|
|
2613
|
+
import { z as z9 } from "zod";
|
|
2614
|
+
var inputSchema9 = z9.object({
|
|
2615
|
+
query: z9.string().min(1, "\u5FC5\u987B\u63D0\u4F9B\u641C\u7D22\u5173\u952E\u8BCD").max(400, "\u641C\u7D22\u5173\u952E\u8BCD\u592A\u957F\uFF08>400 \u5B57\uFF09").describe("\u641C\u7D22\u5173\u952E\u8BCD\uFF0C\u5EFA\u8BAE\u81EA\u7136\u8BED\u8A00\u63CF\u8FF0\u9700\u8981\u67E5\u7684\u4FE1\u606F"),
|
|
2616
|
+
max_results: z9.number().int().min(1).max(20).optional().describe("\u8FD4\u56DE\u7ED3\u679C\u6570\u91CF\uFF0C1-20\uFF0C\u9ED8\u8BA4 5"),
|
|
2617
|
+
search_depth: z9.enum(["basic", "advanced"]).optional().describe("basic \u5FEB\u4F46\u6D45\uFF1Badvanced \u6162\u4F46\u6DF1\uFF08\u542B answer \u6458\u8981\uFF09\uFF0C\u9ED8\u8BA4 basic"),
|
|
2618
|
+
topic: z9.enum(["general", "news"]).optional().describe("general=\u901A\u7528\u7F51\u9875\uFF1Bnews=\u504F\u65B0\u95FB\u6E90\uFF1B\u9ED8\u8BA4 general")
|
|
2206
2619
|
});
|
|
2207
|
-
var
|
|
2208
|
-
var
|
|
2620
|
+
var parameters9 = toToolParameters(inputSchema9);
|
|
2621
|
+
var description9 = `- Searches the public web via the Tavily Search API and returns structured results.
|
|
2209
2622
|
- Use this when you need up-to-date information that is not in your training data, or when the user asks for recent news / docs / API references.
|
|
2210
2623
|
- Returns the top N results, each with a title, URL, and content snippet. With \`search_depth: "advanced"\` Tavily also returns a synthesized answer at the top.
|
|
2211
2624
|
- Prefer specific natural-language queries over keyword soup (e.g. "how does Bun handle .env files in version 1.1").
|
|
2212
2625
|
- Requires the TAVILY_API_KEY environment variable to be set; if missing the tool returns a friendly error.`;
|
|
2213
|
-
async function
|
|
2626
|
+
async function call9(input, signal) {
|
|
2214
2627
|
const apiKey = process.env.TAVILY_API_KEY;
|
|
2215
2628
|
if (!apiKey) {
|
|
2216
2629
|
return {
|
|
@@ -2292,37 +2705,41 @@ ${(r.content ?? "").trim()}`
|
|
|
2292
2705
|
}
|
|
2293
2706
|
var webSearchTool = {
|
|
2294
2707
|
name: "WebSearch",
|
|
2295
|
-
description:
|
|
2296
|
-
inputSchema:
|
|
2297
|
-
parameters:
|
|
2708
|
+
description: description9,
|
|
2709
|
+
inputSchema: inputSchema9,
|
|
2710
|
+
parameters: parameters9,
|
|
2298
2711
|
isReadOnly: true,
|
|
2299
2712
|
isConcurrencySafe: true,
|
|
2300
2713
|
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
2301
|
-
call:
|
|
2714
|
+
call: call9
|
|
2302
2715
|
};
|
|
2303
2716
|
|
|
2304
2717
|
// src/tools/write/write.ts
|
|
2305
|
-
import { existsSync as
|
|
2306
|
-
import { mkdir as mkdir5, stat as stat4, writeFile as
|
|
2718
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2719
|
+
import { mkdir as mkdir5, stat as stat4, writeFile as writeFile5 } from "fs/promises";
|
|
2307
2720
|
import { dirname as dirname6 } from "path";
|
|
2308
|
-
import { z as
|
|
2721
|
+
import { z as z10 } from "zod";
|
|
2309
2722
|
var MAX_WRITE_SIZE_BYTES = 1024 * 1024 * 1024;
|
|
2310
|
-
var
|
|
2311
|
-
file_path:
|
|
2312
|
-
content:
|
|
2723
|
+
var inputSchema10 = z10.object({
|
|
2724
|
+
file_path: z10.string().min(1).describe("\u8981\u5199\u5165\u7684\u6587\u4EF6\u8DEF\u5F84"),
|
|
2725
|
+
content: z10.string().describe("\u6587\u4EF6\u5B8C\u6574\u5185\u5BB9\uFF08\u4F1A\u8986\u76D6\u65E2\u6709\u5185\u5BB9\uFF09")
|
|
2313
2726
|
});
|
|
2314
|
-
var
|
|
2315
|
-
var
|
|
2727
|
+
var parameters10 = toToolParameters(inputSchema10);
|
|
2728
|
+
var description10 = `Writes a file to the local filesystem.
|
|
2316
2729
|
|
|
2317
2730
|
Usage:
|
|
2318
2731
|
- This tool will overwrite the existing file if there is one at the provided path.
|
|
2319
2732
|
- If the parent directory does not exist, it will be created recursively.
|
|
2320
2733
|
- ALWAYS prefer editing existing files in the codebase via the Edit tool. NEVER write new files unless explicitly required.
|
|
2321
2734
|
- NEVER create documentation files (*.md) or README files unless explicitly requested by the User.`;
|
|
2322
|
-
async function
|
|
2735
|
+
async function call10(input) {
|
|
2323
2736
|
const pathResult = validateAndResolvePath(input.file_path, getWorkingDir());
|
|
2324
2737
|
if (!pathResult.ok) return pathResult;
|
|
2325
2738
|
const filePath = pathResult.resolvedPath;
|
|
2739
|
+
const freshness = assertFresh(filePath);
|
|
2740
|
+
if (!freshness.ok) {
|
|
2741
|
+
return { ok: false, error: freshness.error };
|
|
2742
|
+
}
|
|
2326
2743
|
const contentSize = Buffer.byteLength(input.content, "utf8");
|
|
2327
2744
|
if (contentSize > MAX_WRITE_SIZE_BYTES) {
|
|
2328
2745
|
return {
|
|
@@ -2333,7 +2750,7 @@ async function call9(input) {
|
|
|
2333
2750
|
try {
|
|
2334
2751
|
await mkdir5(dirname6(filePath), { recursive: true });
|
|
2335
2752
|
let originalSize = 0;
|
|
2336
|
-
const fileExisted =
|
|
2753
|
+
const fileExisted = existsSync6(filePath);
|
|
2337
2754
|
if (fileExisted) {
|
|
2338
2755
|
try {
|
|
2339
2756
|
const st = await stat4(filePath);
|
|
@@ -2342,16 +2759,32 @@ async function call9(input) {
|
|
|
2342
2759
|
}
|
|
2343
2760
|
}
|
|
2344
2761
|
let contentToWrite = input.content;
|
|
2762
|
+
let bomEncoding = "utf8";
|
|
2345
2763
|
if (fileExisted) {
|
|
2764
|
+
const detected = await detectFileBomEncoding(filePath);
|
|
2765
|
+
if (detected === "utf16le") {
|
|
2766
|
+
return {
|
|
2767
|
+
ok: false,
|
|
2768
|
+
error: "\u6682\u4E0D\u652F\u6301\u6539\u5199 UTF-16 LE \u6587\u4EF6\uFF08BOM=FF FE\uFF09\u3002\u8BF7\u6539\u7528 UTF-8 \u7F16\u7801\u3002"
|
|
2769
|
+
};
|
|
2770
|
+
}
|
|
2771
|
+
bomEncoding = detected;
|
|
2346
2772
|
const lineEnding = await detectFileLineEndings(filePath);
|
|
2347
2773
|
contentToWrite = applyLineEnding(input.content, lineEnding);
|
|
2348
2774
|
}
|
|
2349
|
-
|
|
2775
|
+
if (bomEncoding === "utf8-bom") {
|
|
2776
|
+
const bomBytes = Buffer.from([239, 187, 191]);
|
|
2777
|
+
const bodyBytes = Buffer.from(contentToWrite, "utf8");
|
|
2778
|
+
await writeFile5(filePath, Buffer.concat([bomBytes, bodyBytes]));
|
|
2779
|
+
} else {
|
|
2780
|
+
await writeFile5(filePath, contentToWrite, "utf8");
|
|
2781
|
+
}
|
|
2350
2782
|
const action = fileExisted ? "\u5DF2\u8986\u76D6" : "\u5DF2\u521B\u5EFA\u65B0\u6587\u4EF6";
|
|
2351
2783
|
const sizeInfo = fileExisted ? `\uFF08\u539F\u6587\u4EF6 ${originalSize} \u5B57\u7B26 \u2192 \u65B0\u5185\u5BB9 ${contentToWrite.length} \u5B57\u7B26\uFF09` : `\uFF08${contentToWrite.length} \u5B57\u7B26\uFF09`;
|
|
2784
|
+
const bomInfo = bomEncoding === "utf8-bom" ? "\uFF0C\u5DF2\u4FDD\u7559 UTF-8 BOM" : "";
|
|
2352
2785
|
return {
|
|
2353
2786
|
ok: true,
|
|
2354
|
-
content: `${action} ${filePath}${sizeInfo}`
|
|
2787
|
+
content: `${action} ${filePath}${sizeInfo}${bomInfo}`
|
|
2355
2788
|
};
|
|
2356
2789
|
} catch (e) {
|
|
2357
2790
|
return { ok: false, error: `\u5199\u5165\u5931\u8D25\uFF1A${e.message}` };
|
|
@@ -2359,19 +2792,20 @@ async function call9(input) {
|
|
|
2359
2792
|
}
|
|
2360
2793
|
var writeTool = {
|
|
2361
2794
|
name: "Write",
|
|
2362
|
-
description:
|
|
2363
|
-
inputSchema:
|
|
2364
|
-
parameters:
|
|
2795
|
+
description: description10,
|
|
2796
|
+
inputSchema: inputSchema10,
|
|
2797
|
+
parameters: parameters10,
|
|
2365
2798
|
isReadOnly: false,
|
|
2366
2799
|
isConcurrencySafe: false,
|
|
2367
2800
|
maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
|
|
2368
|
-
call:
|
|
2801
|
+
call: call10
|
|
2369
2802
|
};
|
|
2370
2803
|
|
|
2371
2804
|
// src/tools/index.ts
|
|
2372
2805
|
var ALL_TOOLS = [
|
|
2373
2806
|
readTool,
|
|
2374
2807
|
editTool,
|
|
2808
|
+
multiEditTool,
|
|
2375
2809
|
writeTool,
|
|
2376
2810
|
globTool,
|
|
2377
2811
|
grepTool,
|
|
@@ -3780,7 +4214,7 @@ async function reactiveCompactIfApplicable(messages, provider, error, state = de
|
|
|
3780
4214
|
}
|
|
3781
4215
|
|
|
3782
4216
|
// src/plugins/commandRouter.ts
|
|
3783
|
-
import { readFile as
|
|
4217
|
+
import { readFile as readFile9, readdir as readdir3 } from "fs/promises";
|
|
3784
4218
|
import { join as join6 } from "path";
|
|
3785
4219
|
var PLUGINS_DIR = join6(findPackageRoot(import.meta.url), "plugins");
|
|
3786
4220
|
var pluginCache = /* @__PURE__ */ new Map();
|
|
@@ -3812,7 +4246,7 @@ async function loadPlugin(pluginDirPath) {
|
|
|
3812
4246
|
let manifestVersion;
|
|
3813
4247
|
let manifestDesc;
|
|
3814
4248
|
try {
|
|
3815
|
-
const raw = await
|
|
4249
|
+
const raw = await readFile9(manifestPath, "utf8");
|
|
3816
4250
|
const parsed = JSON.parse(raw);
|
|
3817
4251
|
manifestName = parsed.name ?? dirName;
|
|
3818
4252
|
manifestVersion = parsed.version;
|
|
@@ -3827,7 +4261,7 @@ async function loadPlugin(pluginDirPath) {
|
|
|
3827
4261
|
if (!entry.name.endsWith(".md")) continue;
|
|
3828
4262
|
const cmdPath = join6(commandsDir, entry.name);
|
|
3829
4263
|
try {
|
|
3830
|
-
const content = await
|
|
4264
|
+
const content = await readFile9(cmdPath, "utf8");
|
|
3831
4265
|
const fm = parseMarkdownFrontmatter(content);
|
|
3832
4266
|
const sep2 = content.indexOf("\n---", 4);
|
|
3833
4267
|
const body = sep2 >= 0 ? content.slice(sep2 + 4).trim() : content.trim();
|
|
@@ -3847,7 +4281,7 @@ async function loadPlugin(pluginDirPath) {
|
|
|
3847
4281
|
const hooksJsonPath = join6(pluginDirPath, "hooks", "hooks.json");
|
|
3848
4282
|
let hasStopHook = false;
|
|
3849
4283
|
try {
|
|
3850
|
-
const hooksRaw = await
|
|
4284
|
+
const hooksRaw = await readFile9(hooksJsonPath, "utf8");
|
|
3851
4285
|
const hooksParsed = JSON.parse(hooksRaw);
|
|
3852
4286
|
const stopHooks = hooksParsed?.hooks?.Stop;
|
|
3853
4287
|
if (Array.isArray(stopHooks) && stopHooks.length > 0) {
|
|
@@ -3940,13 +4374,13 @@ function getActiveStopHookPlugins() {
|
|
|
3940
4374
|
}
|
|
3941
4375
|
|
|
3942
4376
|
// src/plugins/stopHook.ts
|
|
3943
|
-
import { readFile as
|
|
4377
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
3944
4378
|
import { join as join7 } from "path";
|
|
3945
4379
|
import { spawn as spawn4 } from "child_process";
|
|
3946
4380
|
async function loadStopHookConfig(pluginRoot) {
|
|
3947
4381
|
const hooksJsonPath = join7(pluginRoot, "hooks", "hooks.json");
|
|
3948
4382
|
try {
|
|
3949
|
-
const raw = await
|
|
4383
|
+
const raw = await readFile10(hooksJsonPath, "utf8");
|
|
3950
4384
|
const parsed = JSON.parse(raw);
|
|
3951
4385
|
const stopEntries = parsed?.hooks?.Stop;
|
|
3952
4386
|
if (!Array.isArray(stopEntries)) return [];
|
|
@@ -4028,7 +4462,7 @@ function runSingleStopHook(hookConfig, pluginRoot, transcriptText) {
|
|
|
4028
4462
|
}
|
|
4029
4463
|
|
|
4030
4464
|
// src/plugins/verificationGate.ts
|
|
4031
|
-
import { existsSync as
|
|
4465
|
+
import { existsSync as existsSync7, readFileSync } from "fs";
|
|
4032
4466
|
import { spawn as spawn5 } from "child_process";
|
|
4033
4467
|
function parseVerifyArg(arg) {
|
|
4034
4468
|
const colonIdx = arg.indexOf(":");
|
|
@@ -4105,7 +4539,7 @@ async function verifyShell(command, timeout) {
|
|
|
4105
4539
|
};
|
|
4106
4540
|
}
|
|
4107
4541
|
function verifyFileExists(file) {
|
|
4108
|
-
const exists =
|
|
4542
|
+
const exists = existsSync7(file);
|
|
4109
4543
|
return {
|
|
4110
4544
|
check: { type: "file_exists", file },
|
|
4111
4545
|
passed: exists,
|
|
@@ -4205,8 +4639,8 @@ function formatCheckName(check) {
|
|
|
4205
4639
|
}
|
|
4206
4640
|
|
|
4207
4641
|
// src/plugins/goalState.ts
|
|
4208
|
-
import { mkdir as mkdir6, appendFile, writeFile as
|
|
4209
|
-
import { existsSync as
|
|
4642
|
+
import { mkdir as mkdir6, appendFile, writeFile as writeFile6, unlink as unlink2, rmdir as rmdir2 } from "fs/promises";
|
|
4643
|
+
import { existsSync as existsSync8, readFileSync as readFileSync2 } from "fs";
|
|
4210
4644
|
import { join as join8 } from "path";
|
|
4211
4645
|
var Phase = /* @__PURE__ */ ((Phase2) => {
|
|
4212
4646
|
Phase2["PLAN"] = "plan";
|
|
@@ -4275,8 +4709,8 @@ ${goal}
|
|
|
4275
4709
|
};
|
|
4276
4710
|
for (const [name, content] of Object.entries(files)) {
|
|
4277
4711
|
const path2 = join8(this.dir, `${name}.md`);
|
|
4278
|
-
if (!
|
|
4279
|
-
await
|
|
4712
|
+
if (!existsSync8(path2)) {
|
|
4713
|
+
await writeFile6(path2, content);
|
|
4280
4714
|
}
|
|
4281
4715
|
}
|
|
4282
4716
|
}
|
|
@@ -4320,14 +4754,14 @@ ${goal}
|
|
|
4320
4754
|
`\u975E\u6CD5\u9636\u6BB5\u5207\u6362: ${current} \u2192 ${phase}\u3002\u8BE5\u76EE\u6807\u9636\u6BB5\u4E0D\u5728 PHASE_TRANSITIONS[${current}] \u7684\u53EF\u8FBE\u96C6\u5408\u5185\uFF0C\u9700\u8981\u8D70 forceSetPhase\u3002`
|
|
4321
4755
|
);
|
|
4322
4756
|
}
|
|
4323
|
-
await
|
|
4757
|
+
await writeFile6(join8(this.dir, "phase.md"), phase);
|
|
4324
4758
|
await this.appendProgress(`PHASE \u2192 ${phase}: ${reason}`);
|
|
4325
4759
|
}
|
|
4326
4760
|
async forceSetPhase(phase, reason) {
|
|
4327
4761
|
if (!VALID_PHASES.has(phase)) {
|
|
4328
4762
|
throw new Error(`Invalid phase: ${phase}`);
|
|
4329
4763
|
}
|
|
4330
|
-
await
|
|
4764
|
+
await writeFile6(join8(this.dir, "phase.md"), phase);
|
|
4331
4765
|
await this.appendProgress(`PHASE \u2192 ${phase}: ${reason}`);
|
|
4332
4766
|
}
|
|
4333
4767
|
async appendProgress(line) {
|
|
@@ -4945,6 +5379,7 @@ function useChat(args) {
|
|
|
4945
5379
|
setCompacting(false);
|
|
4946
5380
|
bump();
|
|
4947
5381
|
await clearContext();
|
|
5382
|
+
clearFileState();
|
|
4948
5383
|
}, [bump, isLoading]);
|
|
4949
5384
|
const compactNow = useCallback5(async () => {
|
|
4950
5385
|
if (isLoading) return;
|
|
@@ -5298,7 +5733,7 @@ async function main() {
|
|
|
5298
5733
|
const dirArg = extractCwdArg(args);
|
|
5299
5734
|
if (dirArg) {
|
|
5300
5735
|
const abs = resolve8(dirArg);
|
|
5301
|
-
if (!
|
|
5736
|
+
if (!existsSync9(abs)) {
|
|
5302
5737
|
mkdirSync(abs, { recursive: true });
|
|
5303
5738
|
}
|
|
5304
5739
|
process.chdir(abs);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minimal-agent",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "最小化 Agent 系统 —— 单对话 +
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"description": "最小化 Agent 系统 —— 单对话 + 10 工具 + MultiEdit + Pre-read Guard + 自动压缩 + OpenAI 兼容 + Ink TUI",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Bill Wang <leiwang0359@gmail.com>",
|
|
7
7
|
"repository": {
|