@zqagent/core 0.1.30 → 0.2.5
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/CONTRIBUTING.md +65 -0
- package/LICENSE +190 -0
- package/README.md +102 -146
- package/README.oss.en.md +161 -0
- package/README.oss.md +165 -0
- package/README.oss.zh-TW.md +165 -0
- package/SECURITY.md +51 -0
- package/dist/{ToolRegistry-BmvPxgMr.d.cts → ToolRegistry-C3pTFiaJ.d.cts} +13 -4
- package/dist/{ToolRegistry-BmvPxgMr.d.ts → ToolRegistry-C3pTFiaJ.d.ts} +13 -4
- package/dist/cli/index.cjs +17799 -2062
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +16729 -985
- package/dist/cli/index.js.map +1 -1
- package/dist/{explore-OI6htHN1.d.cts → explore-D4lCKXbA.d.ts} +160 -19
- package/dist/{explore-Pmhyk4gv.d.ts → explore-Dj1j_GyN.d.cts} +160 -19
- package/dist/index.cjs +3626 -453
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +337 -27
- package/dist/index.d.ts +337 -27
- package/dist/index.js +3617 -452
- package/dist/index.js.map +1 -1
- package/dist/server/index.cjs +14029 -1482
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.js +14036 -1482
- package/dist/server/index.js.map +1 -1
- package/dist/tools/ask.cjs +14 -0
- package/dist/tools/ask.cjs.map +1 -1
- package/dist/tools/ask.d.cts +8 -2
- package/dist/tools/ask.d.ts +8 -2
- package/dist/tools/ask.js +13 -0
- package/dist/tools/ask.js.map +1 -1
- package/dist/tools/diff.cjs +59 -30
- package/dist/tools/diff.cjs.map +1 -1
- package/dist/tools/diff.d.cts +1 -1
- package/dist/tools/diff.d.ts +1 -1
- package/dist/tools/diff.js +59 -30
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/directories.d.cts +1 -1
- package/dist/tools/directories.d.ts +1 -1
- package/dist/tools/explore.cjs +1050 -309
- package/dist/tools/explore.cjs.map +1 -1
- package/dist/tools/explore.d.cts +2 -2
- package/dist/tools/explore.d.ts +2 -2
- package/dist/tools/explore.js +1057 -309
- package/dist/tools/explore.js.map +1 -1
- package/dist/tools/files.cjs +58 -43
- package/dist/tools/files.cjs.map +1 -1
- package/dist/tools/files.d.cts +10 -4
- package/dist/tools/files.d.ts +10 -4
- package/dist/tools/files.js +56 -42
- package/dist/tools/files.js.map +1 -1
- package/dist/tools/git.cjs +816 -63
- package/dist/tools/git.cjs.map +1 -1
- package/dist/tools/git.d.cts +200 -2
- package/dist/tools/git.d.ts +200 -2
- package/dist/tools/git.js +788 -62
- package/dist/tools/git.js.map +1 -1
- package/dist/tools/media.cjs +1191 -0
- package/dist/tools/media.cjs.map +1 -0
- package/dist/tools/media.d.cts +31 -0
- package/dist/tools/media.d.ts +31 -0
- package/dist/tools/media.js +1159 -0
- package/dist/tools/media.js.map +1 -0
- package/dist/tools/plan.d.cts +1 -1
- package/dist/tools/plan.d.ts +1 -1
- package/dist/tools/search.cjs +513 -33
- package/dist/tools/search.cjs.map +1 -1
- package/dist/tools/search.d.cts +12 -1
- package/dist/tools/search.d.ts +12 -1
- package/dist/tools/search.js +508 -33
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/shell.cjs +631 -41
- package/dist/tools/shell.cjs.map +1 -1
- package/dist/tools/shell.d.cts +37 -4
- package/dist/tools/shell.d.ts +37 -4
- package/dist/tools/shell.js +626 -40
- package/dist/tools/shell.js.map +1 -1
- package/dist/tools/todo.d.cts +1 -1
- package/dist/tools/todo.d.ts +1 -1
- package/dist/tools/web.cjs +51 -18
- package/dist/tools/web.cjs.map +1 -1
- package/dist/tools/web.d.cts +1 -1
- package/dist/tools/web.d.ts +1 -1
- package/dist/tools/web.js +51 -18
- package/dist/tools/web.js.map +1 -1
- package/package.json +43 -8
package/dist/tools/diff.js
CHANGED
|
@@ -5,17 +5,23 @@ import * as path2 from "path";
|
|
|
5
5
|
// src/tools/pathSafety.ts
|
|
6
6
|
import * as fs from "fs";
|
|
7
7
|
import * as path from "path";
|
|
8
|
-
function
|
|
8
|
+
function isInsideRelative(rel) {
|
|
9
|
+
return !path.isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${path.sep}`);
|
|
10
|
+
}
|
|
11
|
+
function isFilesystemRoot(p) {
|
|
12
|
+
return path.parse(p).root === p;
|
|
13
|
+
}
|
|
14
|
+
function resolveWithinRoot(workspace, target) {
|
|
15
|
+
const abs = path.resolve(workspace, target);
|
|
9
16
|
let rootReal;
|
|
10
17
|
try {
|
|
11
18
|
rootReal = fs.realpathSync(workspace);
|
|
12
19
|
} catch {
|
|
13
20
|
const rootAbs = path.resolve(workspace);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return !rel2.startsWith("..") && !path.isAbsolute(rel2);
|
|
21
|
+
if (!isInsideRelative(path.relative(rootAbs, abs))) return null;
|
|
22
|
+
return { root: rootAbs, real: abs };
|
|
17
23
|
}
|
|
18
|
-
|
|
24
|
+
if (isFilesystemRoot(rootReal)) return null;
|
|
19
25
|
let real;
|
|
20
26
|
try {
|
|
21
27
|
real = fs.realpathSync(abs);
|
|
@@ -29,17 +35,23 @@ function isWithinRoot(workspace, target) {
|
|
|
29
35
|
real = path.join(fs.realpathSync(probe), ...tail);
|
|
30
36
|
break;
|
|
31
37
|
} catch {
|
|
32
|
-
return
|
|
38
|
+
return null;
|
|
33
39
|
}
|
|
34
40
|
}
|
|
35
41
|
tail.unshift(path.basename(probe));
|
|
36
42
|
const parent = path.dirname(probe);
|
|
37
|
-
if (parent === probe) return
|
|
43
|
+
if (parent === probe) return null;
|
|
38
44
|
probe = parent;
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
|
-
|
|
42
|
-
return
|
|
47
|
+
if (!isInsideRelative(path.relative(rootReal, real))) return null;
|
|
48
|
+
return { root: rootReal, real };
|
|
49
|
+
}
|
|
50
|
+
function resolveEntryWithinRoot(workspace, target) {
|
|
51
|
+
const abs = path.resolve(workspace, target);
|
|
52
|
+
const parent = resolveWithinRoot(workspace, path.dirname(abs));
|
|
53
|
+
if (!parent) return null;
|
|
54
|
+
return { root: parent.root, real: path.join(parent.real, path.basename(abs)) };
|
|
43
55
|
}
|
|
44
56
|
|
|
45
57
|
// src/tools/diff.ts
|
|
@@ -48,7 +60,7 @@ function makeApplyPatch(workspace) {
|
|
|
48
60
|
return function apply_patch(args) {
|
|
49
61
|
const patch = String(args.patch ?? "");
|
|
50
62
|
if (!patch.trim()) return { error: "\u8865\u4E01\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A" };
|
|
51
|
-
const lines = patch.split("\n");
|
|
63
|
+
const lines = patch.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trimEnd().split("\n");
|
|
52
64
|
if (!lines[0]?.trim().startsWith("*** Begin Patch")) {
|
|
53
65
|
return { error: "\u8865\u4E01\u5FC5\u987B\u4EE5 '*** Begin Patch' \u5F00\u5934" };
|
|
54
66
|
}
|
|
@@ -68,10 +80,11 @@ function makeApplyPatch(workspace) {
|
|
|
68
80
|
}
|
|
69
81
|
if (line.startsWith("*** Add File: ")) {
|
|
70
82
|
const filePath = line.slice("*** Add File: ".length).trim();
|
|
71
|
-
const
|
|
72
|
-
if (!
|
|
83
|
+
const resolved = resolveWithinRoot(root, filePath);
|
|
84
|
+
if (!resolved) {
|
|
73
85
|
return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
|
|
74
86
|
}
|
|
87
|
+
const target = resolved.real;
|
|
75
88
|
i++;
|
|
76
89
|
const contentLines = [];
|
|
77
90
|
while (i < lines.length - 1 && !lines[i].trimStart().startsWith("*** ")) {
|
|
@@ -87,7 +100,7 @@ function makeApplyPatch(workspace) {
|
|
|
87
100
|
}
|
|
88
101
|
fs2.mkdirSync(path2.dirname(target), { recursive: true });
|
|
89
102
|
fs2.writeFileSync(target, contentLines.join("\n") + "\n", "utf-8");
|
|
90
|
-
const rel = path2.relative(root, target);
|
|
103
|
+
const rel = path2.relative(resolved.root, target);
|
|
91
104
|
changedFiles.push(rel);
|
|
92
105
|
addedFiles.push(rel);
|
|
93
106
|
fileCount++;
|
|
@@ -95,25 +108,27 @@ function makeApplyPatch(workspace) {
|
|
|
95
108
|
}
|
|
96
109
|
if (line.startsWith("*** Delete File: ")) {
|
|
97
110
|
const filePath = line.slice("*** Delete File: ".length).trim();
|
|
98
|
-
const
|
|
99
|
-
if (!
|
|
111
|
+
const entry = resolveEntryWithinRoot(root, filePath);
|
|
112
|
+
if (!entry) {
|
|
100
113
|
return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
|
|
101
114
|
}
|
|
115
|
+
const target = entry.real;
|
|
102
116
|
if (!fs2.existsSync(target)) {
|
|
103
117
|
return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
|
|
104
118
|
}
|
|
105
119
|
fs2.unlinkSync(target);
|
|
106
|
-
deletedFiles.push(path2.relative(root, target));
|
|
120
|
+
deletedFiles.push(path2.relative(entry.root, target));
|
|
107
121
|
fileCount++;
|
|
108
122
|
i++;
|
|
109
123
|
continue;
|
|
110
124
|
}
|
|
111
125
|
if (line.startsWith("*** Update File: ")) {
|
|
112
126
|
const filePath = line.slice("*** Update File: ".length).trim();
|
|
113
|
-
const
|
|
114
|
-
if (!
|
|
127
|
+
const resolved = resolveWithinRoot(root, filePath);
|
|
128
|
+
if (!resolved) {
|
|
115
129
|
return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
|
|
116
130
|
}
|
|
131
|
+
const target = resolved.real;
|
|
117
132
|
i++;
|
|
118
133
|
let moveTo = null;
|
|
119
134
|
if (i < lines.length - 1 && lines[i].trimStart().startsWith("*** Move to: ")) {
|
|
@@ -143,20 +158,29 @@ function makeApplyPatch(workspace) {
|
|
|
143
158
|
}
|
|
144
159
|
const oldLines = fs2.readFileSync(target, "utf-8").split("\n");
|
|
145
160
|
const newLines = applyPatchHunks(oldLines, chunkLines, filePath);
|
|
146
|
-
|
|
147
|
-
if (moveTo
|
|
148
|
-
|
|
161
|
+
let finalResolved = resolved;
|
|
162
|
+
if (moveTo) {
|
|
163
|
+
const moved = resolveWithinRoot(root, moveTo);
|
|
164
|
+
if (!moved) {
|
|
165
|
+
return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${moveTo}` };
|
|
166
|
+
}
|
|
167
|
+
finalResolved = moved;
|
|
149
168
|
}
|
|
169
|
+
const finalTarget = finalResolved.real;
|
|
150
170
|
if (moveTo && fs2.existsSync(finalTarget) && finalTarget !== target) {
|
|
151
171
|
return { error: `\u6587\u4EF6\u5DF2\u5B58\u5728: ${moveTo}` };
|
|
152
172
|
}
|
|
153
173
|
fs2.mkdirSync(path2.dirname(finalTarget), { recursive: true });
|
|
154
174
|
fs2.writeFileSync(finalTarget, newLines.join("\n"), "utf-8");
|
|
155
175
|
if (moveTo && finalTarget !== target) {
|
|
156
|
-
|
|
157
|
-
|
|
176
|
+
const entry = resolveEntryWithinRoot(root, filePath);
|
|
177
|
+
if (!entry) {
|
|
178
|
+
return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
|
|
179
|
+
}
|
|
180
|
+
fs2.unlinkSync(entry.real);
|
|
181
|
+
deletedFiles.push(path2.relative(resolved.root, target));
|
|
158
182
|
}
|
|
159
|
-
changedFiles.push(path2.relative(root, finalTarget));
|
|
183
|
+
changedFiles.push(path2.relative(finalResolved.root, finalTarget));
|
|
160
184
|
fileCount++;
|
|
161
185
|
continue;
|
|
162
186
|
}
|
|
@@ -198,7 +222,7 @@ function findBlockMatch(lines, block, start) {
|
|
|
198
222
|
function makeApplyUnifiedDiff(workspace) {
|
|
199
223
|
const root = path2.resolve(workspace);
|
|
200
224
|
return function apply_unified_diff(args) {
|
|
201
|
-
const diff = String(args.diff ?? "");
|
|
225
|
+
const diff = String(args.diff ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
202
226
|
if (!diff.trim()) return { error: "diff \u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A" };
|
|
203
227
|
const patches = parseUnifiedDiff(diff);
|
|
204
228
|
if (!patches.length) return { error: "\u672A\u627E\u5230 unified diff \u6587\u4EF6\u8865\u4E01" };
|
|
@@ -210,10 +234,11 @@ function makeApplyUnifiedDiff(workspace) {
|
|
|
210
234
|
const oldIsDevNull = patch.oldPath === "/dev/null";
|
|
211
235
|
const newIsDevNull = patch.newPath === "/dev/null";
|
|
212
236
|
const targetPath = newIsDevNull ? patch.oldPath : patch.newPath;
|
|
213
|
-
const
|
|
214
|
-
if (!
|
|
237
|
+
const resolved = resolveWithinRoot(root, targetPath);
|
|
238
|
+
if (!resolved) {
|
|
215
239
|
return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${targetPath}` };
|
|
216
240
|
}
|
|
241
|
+
const target = resolved.real;
|
|
217
242
|
const oldLines = oldIsDevNull ? [] : fs2.existsSync(target) ? fs2.readFileSync(target, "utf-8").split("\n") : [];
|
|
218
243
|
const newLines = applyDiffHunks(oldLines, patch.hunks, targetPath);
|
|
219
244
|
hunkCount += patch.hunks.length;
|
|
@@ -222,14 +247,18 @@ function makeApplyUnifiedDiff(workspace) {
|
|
|
222
247
|
if (!fs2.statSync(target).isFile()) {
|
|
223
248
|
return { error: `\u8DEF\u5F84\u4E0D\u662F\u6587\u4EF6: ${targetPath}` };
|
|
224
249
|
}
|
|
225
|
-
|
|
250
|
+
const entry = resolveEntryWithinRoot(root, targetPath);
|
|
251
|
+
if (!entry) {
|
|
252
|
+
return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${targetPath}` };
|
|
253
|
+
}
|
|
254
|
+
fs2.unlinkSync(entry.real);
|
|
226
255
|
}
|
|
227
|
-
deletedFiles.push(path2.relative(root, target));
|
|
256
|
+
deletedFiles.push(path2.relative(resolved.root, target));
|
|
228
257
|
continue;
|
|
229
258
|
}
|
|
230
259
|
fs2.mkdirSync(path2.dirname(target), { recursive: true });
|
|
231
260
|
fs2.writeFileSync(target, newLines.join("\n"), "utf-8");
|
|
232
|
-
const rel = path2.relative(root, target);
|
|
261
|
+
const rel = path2.relative(resolved.root, target);
|
|
233
262
|
changedFiles.push(rel);
|
|
234
263
|
if (oldIsDevNull) addedFiles.push(rel);
|
|
235
264
|
}
|
package/dist/tools/diff.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tools/diff.ts","../../src/tools/pathSafety.ts"],"sourcesContent":["import { ToolRegistry } from \"./ToolRegistry.js\";\n\n/**\n * apply_patch + apply_unified_diff\n * 对齐 Python 版 aisuite.toolkits.files.FileToolkit\n */\n\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport { isWithinRoot } from \"./pathSafety.js\";\n\n/** apply_patch — Codex 风格补丁 (对齐 Python 版) */\nfunction makeApplyPatch(workspace: string) {\n const root = path.resolve(workspace);\n\n return function apply_patch(args: Record<string, unknown>) {\n const patch = String(args.patch ?? \"\");\n if (!patch.trim()) return { error: \"补丁内容不能为空\" };\n\n const lines = patch.split(\"\\n\");\n if (!lines[0]?.trim().startsWith(\"*** Begin Patch\")) {\n return { error: \"补丁必须以 '*** Begin Patch' 开头\" };\n }\n if (!lines[lines.length - 1]?.trim().startsWith(\"*** End Patch\")) {\n return { error: \"补丁必须以 '*** End Patch' 结尾\" };\n }\n\n const changedFiles: string[] = [];\n const addedFiles: string[] = [];\n const deletedFiles: string[] = [];\n let fileCount = 0;\n\n let i = 1;\n while (i < lines.length - 1) {\n const line = lines[i].trim();\n if (!line) { i++; continue; }\n\n if (line.startsWith(\"*** Add File: \")) {\n const filePath = line.slice(\"*** Add File: \".length).trim();\n const target = path.resolve(root, filePath);\n if (!isWithinRoot(root, target)) {\n return { error: `路径在工作区之外: ${filePath}` };\n }\n i++;\n const contentLines: string[] = [];\n while (i < lines.length - 1 && !lines[i].trimStart().startsWith(\"*** \")) {\n const current = lines[i];\n if (!current.startsWith(\"+\")) {\n return { error: `无效的 add-file 补丁行: ${current}` };\n }\n contentLines.push(current.slice(1));\n i++;\n }\n if (fs.existsSync(target)) {\n return { error: `文件已存在: ${filePath}` };\n }\n fs.mkdirSync(path.dirname(target), { recursive: true });\n fs.writeFileSync(target, contentLines.join(\"\\n\") + \"\\n\", \"utf-8\");\n const rel = path.relative(root, target);\n changedFiles.push(rel);\n addedFiles.push(rel);\n fileCount++;\n continue;\n }\n\n if (line.startsWith(\"*** Delete File: \")) {\n const filePath = line.slice(\"*** Delete File: \".length).trim();\n const target = path.resolve(root, filePath);\n if (!isWithinRoot(root, target)) {\n return { error: `路径在工作区之外: ${filePath}` };\n }\n if (!fs.existsSync(target)) {\n return { error: `文件不存在: ${filePath}` };\n }\n fs.unlinkSync(target);\n deletedFiles.push(path.relative(root, target));\n fileCount++;\n i++;\n continue;\n }\n\n if (line.startsWith(\"*** Update File: \")) {\n const filePath = line.slice(\"*** Update File: \".length).trim();\n const target = path.resolve(root, filePath);\n if (!isWithinRoot(root, target)) {\n return { error: `路径在工作区之外: ${filePath}` };\n }\n i++;\n let moveTo: string | null = null;\n if (i < lines.length - 1 && lines[i].trimStart().startsWith(\"*** Move to: \")) {\n moveTo = lines[i].trim().slice(\"*** Move to: \".length).trim();\n i++;\n }\n\n const chunkLines: string[][] = [];\n let currentChunk: string[] = [];\n while (i < lines.length - 1 && !lines[i].trimStart().startsWith(\"*** \")) {\n const ln = lines[i];\n if (ln.trimStart().startsWith(\"@@\")) {\n if (currentChunk.length) {\n chunkLines.push(currentChunk);\n currentChunk = [];\n }\n } else if (ln && \" +-\".includes(ln[0]!)) {\n currentChunk.push(ln);\n }\n i++;\n }\n if (currentChunk.length) chunkLines.push(currentChunk);\n if (!chunkLines.length) {\n return { error: `补丁更新没有 hunks: ${filePath}` };\n }\n\n // Read original file\n if (!fs.existsSync(target)) {\n return { error: `文件不存在: ${filePath}` };\n }\n const oldLines = fs.readFileSync(target, \"utf-8\").split(\"\\n\");\n // Apply hunks\n const newLines = applyPatchHunks(oldLines, chunkLines, filePath);\n\n const finalTarget = moveTo ? path.resolve(root, moveTo) : target;\n if (moveTo && !isWithinRoot(root, finalTarget)) {\n return { error: `路径在工作区之外: ${moveTo}` };\n }\n if (moveTo && fs.existsSync(finalTarget) && finalTarget !== target) {\n return { error: `文件已存在: ${moveTo}` };\n }\n fs.mkdirSync(path.dirname(finalTarget), { recursive: true });\n fs.writeFileSync(finalTarget, newLines.join(\"\\n\"), \"utf-8\");\n if (moveTo && finalTarget !== target) {\n fs.unlinkSync(target);\n deletedFiles.push(path.relative(root, target));\n }\n changedFiles.push(path.relative(root, finalTarget));\n fileCount++;\n continue;\n }\n\n return { error: `未知的补丁操作: ${line}` };\n }\n\n if (fileCount === 0) {\n return { error: \"补丁不包含任何文件操作\" };\n }\n\n return { changed_files: changedFiles, added_files: addedFiles, deleted_files: deletedFiles, file_count: fileCount };\n };\n}\n\n/** 应用补丁 hunks 到文件行 */\nfunction applyPatchHunks(oldLines: string[], chunks: string[][], path: string): string[] {\n let newLines = [...oldLines];\n let cursor = 0;\n\n for (const chunk of chunks) {\n const oldBlock = chunk.filter((l) => l[0] === \" \" || l[0] === \"-\").map((l) => l.slice(1));\n const newBlock = chunk.filter((l) => l[0] === \" \" || l[0] === \"+\").map((l) => l.slice(1));\n\n if (!oldBlock.length) {\n throw new Error(`补丁 hunk 必须包含上下文或删除行: ${path}`);\n }\n\n // Find match for old block\n const matchStart = findBlockMatch(newLines, oldBlock, cursor);\n if (matchStart === -1) {\n throw new Error(`补丁上下文不匹配: ${path}`);\n }\n\n const matchEnd = matchStart + oldBlock.length;\n newLines.splice(matchStart, oldBlock.length, ...newBlock);\n cursor = matchStart + newBlock.length;\n }\n\n return newLines;\n}\n\n/** 在行数组中查找块匹配 */\nfunction findBlockMatch(lines: string[], block: string[], start: number): number {\n for (let i = start; i <= lines.length - block.length; i++) {\n if (lines.slice(i, i + block.length).every((l, j) => l === block[j]!)) {\n return i;\n }\n }\n return -1;\n}\n\n// ==================== Unified Diff ====================\n\n/** apply_unified_diff — 标准 unified diff (对齐 Python 版) */\nfunction makeApplyUnifiedDiff(workspace: string) {\n const root = path.resolve(workspace);\n\n return function apply_unified_diff(args: Record<string, unknown>) {\n const diff = String(args.diff ?? \"\");\n if (!diff.trim()) return { error: \"diff 内容不能为空\" };\n\n const patches = parseUnifiedDiff(diff);\n if (!patches.length) return { error: \"未找到 unified diff 文件补丁\" };\n\n const changedFiles: string[] = [];\n const addedFiles: string[] = [];\n const deletedFiles: string[] = [];\n let hunkCount = 0;\n\n for (const patch of patches) {\n const oldIsDevNull = patch.oldPath === \"/dev/null\";\n const newIsDevNull = patch.newPath === \"/dev/null\";\n const targetPath = newIsDevNull ? patch.oldPath : patch.newPath;\n const target = path.resolve(root, targetPath);\n if (!isWithinRoot(root, target)) {\n return { error: `路径在工作区之外: ${targetPath}` };\n }\n\n const oldLines = oldIsDevNull ? [] : (fs.existsSync(target) ? fs.readFileSync(target, \"utf-8\").split(\"\\n\") : []);\n const newLines = applyDiffHunks(oldLines, patch.hunks, targetPath);\n hunkCount += patch.hunks.length;\n\n if (newIsDevNull) {\n if (fs.existsSync(target)) {\n if (!fs.statSync(target).isFile()) {\n return { error: `路径不是文件: ${targetPath}` };\n }\n fs.unlinkSync(target);\n }\n deletedFiles.push(path.relative(root, target));\n continue;\n }\n\n fs.mkdirSync(path.dirname(target), { recursive: true });\n fs.writeFileSync(target, newLines.join(\"\\n\"), \"utf-8\");\n const rel = path.relative(root, target);\n changedFiles.push(rel);\n if (oldIsDevNull) addedFiles.push(rel);\n }\n\n return {\n changed_files: changedFiles,\n added_files: addedFiles,\n deleted_files: deletedFiles,\n file_count: patches.length,\n hunk_count: hunkCount,\n };\n };\n}\n\ninterface DiffHunk {\n oldStart: number;\n oldCount: number;\n newStart: number;\n newCount: number;\n lines: string[];\n}\n\ninterface FilePatch {\n oldPath: string;\n newPath: string;\n hunks: DiffHunk[];\n}\n\nfunction parseUnifiedDiff(diff: string): FilePatch[] {\n const lines = diff.split(\"\\n\");\n const patches: FilePatch[] = [];\n let i = 0;\n\n while (i < lines.length) {\n if (!lines[i]!.startsWith(\"--- \")) { i++; continue; }\n\n const oldPath = parseDiffFileLine(lines[i]!, \"---\");\n i++;\n if (i >= lines.length || !lines[i]!.startsWith(\"+++ \")) {\n throw new Error(\"Unified diff 缺少 +++ 文件头\");\n }\n const newPath = parseDiffFileLine(lines[i]!, \"+++\");\n i++;\n\n const hunks: DiffHunk[] = [];\n while (i < lines.length) {\n const current = lines[i]!;\n if (current.startsWith(\"--- \")) break;\n if (!current.startsWith(\"@@ \")) { i++; continue; }\n\n const [oldStart, oldCount, newStart, newCount] = parseHunkHeader(current);\n i++;\n const hunkLines: string[] = [];\n while (i < lines.length) {\n const ln = lines[i]!;\n if (ln.startsWith(\"@@ \") || ln.startsWith(\"--- \")) break;\n if (ln.startsWith(\"\\\\ No newline\")) { i++; continue; }\n if (ln && \" +-\".includes(ln[0]!)) {\n hunkLines.push(ln);\n i++;\n } else {\n break;\n }\n }\n hunks.push({ oldStart, oldCount, newStart, newCount, lines: hunkLines });\n }\n if (hunks.length) {\n patches.push({ oldPath, newPath, hunks });\n }\n }\n\n return patches;\n}\n\nfunction parseDiffFileLine(line: string, marker: string): string {\n let value = line.slice(marker.length).trim();\n if (value.includes(\"\\t\")) value = value.split(\"\\t\")[0]!;\n if (value.startsWith(\"a/\") || value.startsWith(\"b/\")) value = value.slice(2);\n return value;\n}\n\nfunction parseHunkHeader(line: string): [number, number, number, number] {\n const parts = line.split(\" \");\n if (parts.length < 3 || !parts[1]!.startsWith(\"-\") || !parts[2]!.startsWith(\"+\")) {\n throw new Error(`无效的 hunk header: ${line}`);\n }\n const [oldStart, oldCount] = parseRange(parts[1]!.slice(1));\n const [newStart, newCount] = parseRange(parts[2]!.slice(1));\n return [oldStart, oldCount, newStart, newCount];\n}\n\nfunction parseRange(value: string): [number, number] {\n if (!value.includes(\",\")) return [parseInt(value), 1];\n const [start, count] = value.split(\",\");\n return [parseInt(start!), parseInt(count!)];\n}\n\nfunction applyDiffHunks(oldLines: string[], hunks: DiffHunk[], filePath: string): string[] {\n const newLines: string[] = [];\n let cursor = 0;\n\n for (const hunk of hunks) {\n const start = Math.max(hunk.oldStart - 1, 0);\n if (start < cursor) {\n throw new Error(`Overlapping hunks: ${filePath}`);\n }\n newLines.push(...oldLines.slice(cursor, start));\n cursor = start;\n\n for (const line of hunk.lines) {\n const prefix = line[0]!;\n const content = line.slice(1);\n if (prefix === \" \") {\n if (cursor >= oldLines.length || oldLines[cursor] !== content) {\n throw new Error(`Diff context 不匹配: ${filePath}`);\n }\n newLines.push(oldLines[cursor]!);\n cursor++;\n } else if (prefix === \"-\") {\n if (cursor >= oldLines.length || oldLines[cursor] !== content) {\n throw new Error(`Diff removal 不匹配: ${filePath}`);\n }\n cursor++;\n } else if (prefix === \"+\") {\n newLines.push(content);\n }\n }\n }\n newLines.push(...oldLines.slice(cursor));\n return newLines;\n}\n\n/** 注册 diff 工具 */\nexport function registerDiffTools(registry: ToolRegistry, workspace: string): void {\n const applyPatchFunc = makeApplyPatch(workspace);\n registry.register(\n (args) => applyPatchFunc(args),\n { category: \"filesystem\", riskLevel: \"medium\", capabilities: [\"edit_file\"], requiresApproval: true },\n {\n type: \"function\",\n function: {\n name: \"apply_patch\",\n description: \"应用 Codex 风格的补丁信封(使用 *** Begin Patch / *** End Patch 段落)。支持 Add File、Delete File、Update File 操作。\",\n parameters: {\n type: \"object\",\n properties: {\n patch: { type: \"string\", description: \"补丁内容 (必须包含 *** Begin Patch 和 *** End Patch)\" },\n },\n required: [\"patch\"],\n },\n },\n },\n );\n\n const applyUnifiedDiffFunc = makeApplyUnifiedDiff(workspace);\n registry.register(\n (args) => applyUnifiedDiffFunc(args),\n { category: \"filesystem\", riskLevel: \"medium\", capabilities: [\"edit_file\"], requiresApproval: true },\n {\n type: \"function\",\n function: {\n name: \"apply_unified_diff\",\n description: \"应用标准 unified diff 补丁。支持 --- / +++ 文件头和 @@ hunk headers。\",\n parameters: {\n type: \"object\",\n properties: {\n diff: { type: \"string\", description: \"unified diff 格式的补丁内容\" },\n },\n required: [\"diff\"],\n },\n },\n },\n );\n}\n","/**\n * 路径安全辅助函数\n *\n * 判断目标路径是否真正位于工作区根目录之内。\n *\n * 仅做字符串比较(path.relative)无法防御符号链接逃逸:\n * 工作区内若存在指向外部的 symlink,`workspace/link/secret.txt`\n * 的字符串前缀合法,但实际读写发生在链接目标(工作区外)。\n *\n * 因此这里用 fs.realpathSync 解析符号链接后再与根目录的 realpath\n * 比较。目标尚不存在(如 write_file 新建文件)时,向上回溯到最近\n * 已存在的祖先目录解析,再拼回剩余路径段。\n */\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\n/**\n * 判断 target(相对 workspace 的路径)是否安全地位于 workspace 内。\n *\n * - 解析符号链接:路径中任何 symlink 组件都会先 realpath 再校验;\n * broken symlink(存在但无法解析)一律拒绝。\n * - 目标不存在时(写新文件):回溯到最近存在的祖先做 realpath,\n * 再拼接剩余段,仍能捕获祖先链中的 symlink 逃逸。\n */\nexport function isWithinRoot(workspace: string, target: string): boolean {\n let rootReal: string;\n try {\n rootReal = fs.realpathSync(workspace);\n } catch {\n // 根目录不存在(如权限引擎尚未初始化工作区):\n // 没有实际目录结构即无 symlink 逃逸面,退化为纯字符串路径比较。\n const rootAbs = path.resolve(workspace);\n const abs = path.resolve(workspace, target);\n const rel = path.relative(rootAbs, abs);\n return !rel.startsWith(\"..\") && !path.isAbsolute(rel);\n }\n\n const abs = path.resolve(workspace, target);\n let real: string;\n try {\n real = fs.realpathSync(abs);\n } catch {\n // abs 不存在:向上回溯找最近存在的祖先\n let probe = abs;\n const tail: string[] = [];\n for (;;) {\n // 组件存在(含 broken symlink)但 realpath 失败 → 无法解析,拒绝\n const exists = fs.existsSync(probe) || fs.lstatSync(probe, { throwIfNoEntry: false }) !== undefined;\n if (exists) {\n try {\n real = path.join(fs.realpathSync(probe), ...tail);\n break;\n } catch {\n return false;\n }\n }\n tail.unshift(path.basename(probe));\n const parent = path.dirname(probe);\n if (parent === probe) return false; // 已到文件系统根\n probe = parent;\n }\n }\n\n const rel = path.relative(rootReal, real);\n return !rel.startsWith(\"..\") && !path.isAbsolute(rel);\n}\n"],"mappings":"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","names":["fs","path","abs","rel","path"]}
|
|
1
|
+
{"version":3,"sources":["../../src/tools/diff.ts","../../src/tools/pathSafety.ts"],"sourcesContent":["import { ToolRegistry } from \"./ToolRegistry.js\";\n\n/**\n * apply_patch + apply_unified_diff\n * 对齐 Python 版 aisuite.toolkits.files.FileToolkit\n */\n\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport { resolveEntryWithinRoot, resolveWithinRoot } from \"./pathSafety.js\";\n\n/** apply_patch — Codex 风格补丁 (对齐 Python 版) */\nfunction makeApplyPatch(workspace: string) {\n const root = path.resolve(workspace);\n\n return function apply_patch(args: Record<string, unknown>) {\n const patch = String(args.patch ?? \"\");\n if (!patch.trim()) return { error: \"补丁内容不能为空\" };\n\n // 归一化 CRLF,并去掉尾部空白:否则 `*** End Patch\\n` 会因最后一项是空串\n // 而被误判为「补丁必须以 *** End Patch 结尾」。\n const lines = patch\n .replace(/\\r\\n/g, \"\\n\")\n .replace(/\\r/g, \"\\n\")\n .trimEnd()\n .split(\"\\n\");\n if (!lines[0]?.trim().startsWith(\"*** Begin Patch\")) {\n return { error: \"补丁必须以 '*** Begin Patch' 开头\" };\n }\n if (!lines[lines.length - 1]?.trim().startsWith(\"*** End Patch\")) {\n return { error: \"补丁必须以 '*** End Patch' 结尾\" };\n }\n\n const changedFiles: string[] = [];\n const addedFiles: string[] = [];\n const deletedFiles: string[] = [];\n let fileCount = 0;\n\n let i = 1;\n while (i < lines.length - 1) {\n const line = lines[i].trim();\n if (!line) { i++; continue; }\n\n if (line.startsWith(\"*** Add File: \")) {\n const filePath = line.slice(\"*** Add File: \".length).trim();\n const resolved = resolveWithinRoot(root, filePath);\n if (!resolved) {\n return { error: `路径在工作区之外: ${filePath}` };\n }\n const target = resolved.real;\n i++;\n const contentLines: string[] = [];\n while (i < lines.length - 1 && !lines[i].trimStart().startsWith(\"*** \")) {\n const current = lines[i];\n if (!current.startsWith(\"+\")) {\n return { error: `无效的 add-file 补丁行: ${current}` };\n }\n contentLines.push(current.slice(1));\n i++;\n }\n if (fs.existsSync(target)) {\n return { error: `文件已存在: ${filePath}` };\n }\n fs.mkdirSync(path.dirname(target), { recursive: true });\n fs.writeFileSync(target, contentLines.join(\"\\n\") + \"\\n\", \"utf-8\");\n const rel = path.relative(resolved.root, target);\n changedFiles.push(rel);\n addedFiles.push(rel);\n fileCount++;\n continue;\n }\n\n if (line.startsWith(\"*** Delete File: \")) {\n const filePath = line.slice(\"*** Delete File: \".length).trim();\n // 删除作用于条目本身:父目录取 realpath,最后一段不解析\n const entry = resolveEntryWithinRoot(root, filePath);\n if (!entry) {\n return { error: `路径在工作区之外: ${filePath}` };\n }\n const target = entry.real;\n if (!fs.existsSync(target)) {\n return { error: `文件不存在: ${filePath}` };\n }\n fs.unlinkSync(target);\n deletedFiles.push(path.relative(entry.root, target));\n fileCount++;\n i++;\n continue;\n }\n\n if (line.startsWith(\"*** Update File: \")) {\n const filePath = line.slice(\"*** Update File: \".length).trim();\n const resolved = resolveWithinRoot(root, filePath);\n if (!resolved) {\n return { error: `路径在工作区之外: ${filePath}` };\n }\n const target = resolved.real;\n i++;\n let moveTo: string | null = null;\n if (i < lines.length - 1 && lines[i].trimStart().startsWith(\"*** Move to: \")) {\n moveTo = lines[i].trim().slice(\"*** Move to: \".length).trim();\n i++;\n }\n\n const chunkLines: string[][] = [];\n let currentChunk: string[] = [];\n while (i < lines.length - 1 && !lines[i].trimStart().startsWith(\"*** \")) {\n const ln = lines[i];\n if (ln.trimStart().startsWith(\"@@\")) {\n if (currentChunk.length) {\n chunkLines.push(currentChunk);\n currentChunk = [];\n }\n } else if (ln && \" +-\".includes(ln[0]!)) {\n currentChunk.push(ln);\n }\n i++;\n }\n if (currentChunk.length) chunkLines.push(currentChunk);\n if (!chunkLines.length) {\n return { error: `补丁更新没有 hunks: ${filePath}` };\n }\n\n // Read original file\n if (!fs.existsSync(target)) {\n return { error: `文件不存在: ${filePath}` };\n }\n const oldLines = fs.readFileSync(target, \"utf-8\").split(\"\\n\");\n // Apply hunks\n const newLines = applyPatchHunks(oldLines, chunkLines, filePath);\n\n let finalResolved = resolved;\n if (moveTo) {\n const moved = resolveWithinRoot(root, moveTo);\n if (!moved) {\n return { error: `路径在工作区之外: ${moveTo}` };\n }\n finalResolved = moved;\n }\n const finalTarget = finalResolved.real;\n if (moveTo && fs.existsSync(finalTarget) && finalTarget !== target) {\n return { error: `文件已存在: ${moveTo}` };\n }\n fs.mkdirSync(path.dirname(finalTarget), { recursive: true });\n fs.writeFileSync(finalTarget, newLines.join(\"\\n\"), \"utf-8\");\n if (moveTo && finalTarget !== target) {\n // 删除源条目本身:父目录取 realpath,最后一段不解析(符号链接删链接)\n const entry = resolveEntryWithinRoot(root, filePath);\n if (!entry) {\n return { error: `路径在工作区之外: ${filePath}` };\n }\n fs.unlinkSync(entry.real);\n deletedFiles.push(path.relative(resolved.root, target));\n }\n changedFiles.push(path.relative(finalResolved.root, finalTarget));\n fileCount++;\n continue;\n }\n\n return { error: `未知的补丁操作: ${line}` };\n }\n\n if (fileCount === 0) {\n return { error: \"补丁不包含任何文件操作\" };\n }\n\n return { changed_files: changedFiles, added_files: addedFiles, deleted_files: deletedFiles, file_count: fileCount };\n };\n}\n\n/** 应用补丁 hunks 到文件行 */\nfunction applyPatchHunks(oldLines: string[], chunks: string[][], path: string): string[] {\n let newLines = [...oldLines];\n let cursor = 0;\n\n for (const chunk of chunks) {\n const oldBlock = chunk.filter((l) => l[0] === \" \" || l[0] === \"-\").map((l) => l.slice(1));\n const newBlock = chunk.filter((l) => l[0] === \" \" || l[0] === \"+\").map((l) => l.slice(1));\n\n if (!oldBlock.length) {\n throw new Error(`补丁 hunk 必须包含上下文或删除行: ${path}`);\n }\n\n // Find match for old block\n const matchStart = findBlockMatch(newLines, oldBlock, cursor);\n if (matchStart === -1) {\n throw new Error(`补丁上下文不匹配: ${path}`);\n }\n\n const matchEnd = matchStart + oldBlock.length;\n newLines.splice(matchStart, oldBlock.length, ...newBlock);\n cursor = matchStart + newBlock.length;\n }\n\n return newLines;\n}\n\n/** 在行数组中查找块匹配 */\nfunction findBlockMatch(lines: string[], block: string[], start: number): number {\n for (let i = start; i <= lines.length - block.length; i++) {\n if (lines.slice(i, i + block.length).every((l, j) => l === block[j]!)) {\n return i;\n }\n }\n return -1;\n}\n\n// ==================== Unified Diff ====================\n\n/** apply_unified_diff — 标准 unified diff (对齐 Python 版) */\nfunction makeApplyUnifiedDiff(workspace: string) {\n const root = path.resolve(workspace);\n\n return function apply_unified_diff(args: Record<string, unknown>) {\n const diff = String(args.diff ?? \"\").replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n if (!diff.trim()) return { error: \"diff 内容不能为空\" };\n\n const patches = parseUnifiedDiff(diff);\n if (!patches.length) return { error: \"未找到 unified diff 文件补丁\" };\n\n const changedFiles: string[] = [];\n const addedFiles: string[] = [];\n const deletedFiles: string[] = [];\n let hunkCount = 0;\n\n for (const patch of patches) {\n const oldIsDevNull = patch.oldPath === \"/dev/null\";\n const newIsDevNull = patch.newPath === \"/dev/null\";\n const targetPath = newIsDevNull ? patch.oldPath : patch.newPath;\n const resolved = resolveWithinRoot(root, targetPath);\n if (!resolved) {\n return { error: `路径在工作区之外: ${targetPath}` };\n }\n const target = resolved.real;\n\n const oldLines = oldIsDevNull ? [] : (fs.existsSync(target) ? fs.readFileSync(target, \"utf-8\").split(\"\\n\") : []);\n const newLines = applyDiffHunks(oldLines, patch.hunks, targetPath);\n hunkCount += patch.hunks.length;\n\n if (newIsDevNull) {\n if (fs.existsSync(target)) {\n if (!fs.statSync(target).isFile()) {\n return { error: `路径不是文件: ${targetPath}` };\n }\n // 删除源条目本身:父目录取 realpath,最后一段不解析\n const entry = resolveEntryWithinRoot(root, targetPath);\n if (!entry) {\n return { error: `路径在工作区之外: ${targetPath}` };\n }\n fs.unlinkSync(entry.real);\n }\n deletedFiles.push(path.relative(resolved.root, target));\n continue;\n }\n\n fs.mkdirSync(path.dirname(target), { recursive: true });\n fs.writeFileSync(target, newLines.join(\"\\n\"), \"utf-8\");\n const rel = path.relative(resolved.root, target);\n changedFiles.push(rel);\n if (oldIsDevNull) addedFiles.push(rel);\n }\n\n return {\n changed_files: changedFiles,\n added_files: addedFiles,\n deleted_files: deletedFiles,\n file_count: patches.length,\n hunk_count: hunkCount,\n };\n };\n}\n\ninterface DiffHunk {\n oldStart: number;\n oldCount: number;\n newStart: number;\n newCount: number;\n lines: string[];\n}\n\ninterface FilePatch {\n oldPath: string;\n newPath: string;\n hunks: DiffHunk[];\n}\n\nfunction parseUnifiedDiff(diff: string): FilePatch[] {\n const lines = diff.split(\"\\n\");\n const patches: FilePatch[] = [];\n let i = 0;\n\n while (i < lines.length) {\n if (!lines[i]!.startsWith(\"--- \")) { i++; continue; }\n\n const oldPath = parseDiffFileLine(lines[i]!, \"---\");\n i++;\n if (i >= lines.length || !lines[i]!.startsWith(\"+++ \")) {\n throw new Error(\"Unified diff 缺少 +++ 文件头\");\n }\n const newPath = parseDiffFileLine(lines[i]!, \"+++\");\n i++;\n\n const hunks: DiffHunk[] = [];\n while (i < lines.length) {\n const current = lines[i]!;\n if (current.startsWith(\"--- \")) break;\n if (!current.startsWith(\"@@ \")) { i++; continue; }\n\n const [oldStart, oldCount, newStart, newCount] = parseHunkHeader(current);\n i++;\n const hunkLines: string[] = [];\n while (i < lines.length) {\n const ln = lines[i]!;\n if (ln.startsWith(\"@@ \") || ln.startsWith(\"--- \")) break;\n if (ln.startsWith(\"\\\\ No newline\")) { i++; continue; }\n if (ln && \" +-\".includes(ln[0]!)) {\n hunkLines.push(ln);\n i++;\n } else {\n break;\n }\n }\n hunks.push({ oldStart, oldCount, newStart, newCount, lines: hunkLines });\n }\n if (hunks.length) {\n patches.push({ oldPath, newPath, hunks });\n }\n }\n\n return patches;\n}\n\nfunction parseDiffFileLine(line: string, marker: string): string {\n let value = line.slice(marker.length).trim();\n if (value.includes(\"\\t\")) value = value.split(\"\\t\")[0]!;\n if (value.startsWith(\"a/\") || value.startsWith(\"b/\")) value = value.slice(2);\n return value;\n}\n\nfunction parseHunkHeader(line: string): [number, number, number, number] {\n const parts = line.split(\" \");\n if (parts.length < 3 || !parts[1]!.startsWith(\"-\") || !parts[2]!.startsWith(\"+\")) {\n throw new Error(`无效的 hunk header: ${line}`);\n }\n const [oldStart, oldCount] = parseRange(parts[1]!.slice(1));\n const [newStart, newCount] = parseRange(parts[2]!.slice(1));\n return [oldStart, oldCount, newStart, newCount];\n}\n\nfunction parseRange(value: string): [number, number] {\n if (!value.includes(\",\")) return [parseInt(value), 1];\n const [start, count] = value.split(\",\");\n return [parseInt(start!), parseInt(count!)];\n}\n\nfunction applyDiffHunks(oldLines: string[], hunks: DiffHunk[], filePath: string): string[] {\n const newLines: string[] = [];\n let cursor = 0;\n\n for (const hunk of hunks) {\n const start = Math.max(hunk.oldStart - 1, 0);\n if (start < cursor) {\n throw new Error(`Overlapping hunks: ${filePath}`);\n }\n newLines.push(...oldLines.slice(cursor, start));\n cursor = start;\n\n for (const line of hunk.lines) {\n const prefix = line[0]!;\n const content = line.slice(1);\n if (prefix === \" \") {\n if (cursor >= oldLines.length || oldLines[cursor] !== content) {\n throw new Error(`Diff context 不匹配: ${filePath}`);\n }\n newLines.push(oldLines[cursor]!);\n cursor++;\n } else if (prefix === \"-\") {\n if (cursor >= oldLines.length || oldLines[cursor] !== content) {\n throw new Error(`Diff removal 不匹配: ${filePath}`);\n }\n cursor++;\n } else if (prefix === \"+\") {\n newLines.push(content);\n }\n }\n }\n newLines.push(...oldLines.slice(cursor));\n return newLines;\n}\n\n/** 注册 diff 工具 */\nexport function registerDiffTools(registry: ToolRegistry, workspace: string): void {\n const applyPatchFunc = makeApplyPatch(workspace);\n registry.register(\n (args) => applyPatchFunc(args),\n { category: \"filesystem\", riskLevel: \"medium\", capabilities: [\"edit_file\"], requiresApproval: true },\n {\n type: \"function\",\n function: {\n name: \"apply_patch\",\n description: \"应用 Codex 风格的补丁信封(使用 *** Begin Patch / *** End Patch 段落)。支持 Add File、Delete File、Update File 操作。\",\n parameters: {\n type: \"object\",\n properties: {\n patch: { type: \"string\", description: \"补丁内容 (必须包含 *** Begin Patch 和 *** End Patch)\" },\n },\n required: [\"patch\"],\n },\n },\n },\n );\n\n const applyUnifiedDiffFunc = makeApplyUnifiedDiff(workspace);\n registry.register(\n (args) => applyUnifiedDiffFunc(args),\n { category: \"filesystem\", riskLevel: \"medium\", capabilities: [\"edit_file\"], requiresApproval: true },\n {\n type: \"function\",\n function: {\n name: \"apply_unified_diff\",\n description: \"应用标准 unified diff 补丁。支持 --- / +++ 文件头和 @@ hunk headers。\",\n parameters: {\n type: \"object\",\n properties: {\n diff: { type: \"string\", description: \"unified diff 格式的补丁内容\" },\n },\n required: [\"diff\"],\n },\n },\n },\n );\n}\n","/**\n * 路径安全辅助函数\n *\n * 判断目标路径是否真正位于工作区根目录之内。\n *\n * 仅做字符串比较(path.relative)无法防御符号链接逃逸:\n * 工作区内若存在指向外部的 symlink,`workspace/link/secret.txt`\n * 的字符串前缀合法,但实际读写发生在链接目标(工作区外)。\n *\n * 因此这里用 fs.realpathSync 解析符号链接后再与根目录的 realpath\n * 比较。目标尚不存在(如 write_file 新建文件)时,向上回溯到最近\n * 已存在的祖先目录解析,再拼回剩余路径段。\n *\n * 校验通过时把解析后的真实路径一并返回:调用方必须用它(而非原始\n * 相对路径)做实际 fs 操作,否则「校验后、操作前」路径组件被替换为\n * 指向外部的 symlink 仍会逃逸(TOCTOU)。\n */\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\n/**\n * 判断 `path.relative(root, target)` 的结果是否落在 root 之内。\n *\n * 逃逸必然恰好是 `..` 或以 `..` + 分隔符开头,不能用 `startsWith(\"..\")`:\n * 工作区里名为 `..foo` 的合法条目会被误判为逃逸而拒绝。\n * 跨盘符时(Windows)path.relative 返回绝对路径,一并拒绝。\n */\nfunction isInsideRelative(rel: string): boolean {\n return (\n !path.isAbsolute(rel) && rel !== \"..\" && !rel.startsWith(`..${path.sep}`)\n );\n}\n\n/** 是否为文件系统根(\"/\"、\"C:\\\\\") */\nfunction isFilesystemRoot(p: string): boolean {\n return path.parse(p).root === p;\n}\n\n/** 校验通过的路径:root 为工作区根的真实路径,real 为目标的真实路径 */\nexport interface ResolvedPath {\n root: string;\n real: string;\n}\n\n/**\n * 校验 target(相对 workspace 的路径)是否位于 workspace 内,并返回\n * 解析符号链接后的真实路径;不在工作区内时返回 null。\n *\n * - 解析符号链接:路径中任何 symlink 组件都会先 realpath 再校验;\n * broken symlink(存在但无法解析)一律拒绝。\n * - 目标不存在时(写新文件):回溯到最近存在的祖先做 realpath,\n * 再拼接剩余段,仍能捕获祖先链中的 symlink 逃逸。\n * - 工作区根若就是文件系统根(`WORKSPACE=/`):此时任何绝对路径都在\n * 其内,等同没有沙箱,直接判为越界。\n */\nexport function resolveWithinRoot(\n workspace: string,\n target: string,\n): ResolvedPath | null {\n const abs = path.resolve(workspace, target);\n\n let rootReal: string;\n try {\n rootReal = fs.realpathSync(workspace);\n } catch {\n // 根目录不存在(如权限引擎尚未初始化工作区):\n // 没有实际目录结构即无 symlink 逃逸面,退化为纯字符串路径比较。\n const rootAbs = path.resolve(workspace);\n if (!isInsideRelative(path.relative(rootAbs, abs))) return null;\n return { root: rootAbs, real: abs };\n }\n if (isFilesystemRoot(rootReal)) return null;\n\n let real: string;\n try {\n real = fs.realpathSync(abs);\n } catch {\n // abs 不存在:向上回溯找最近存在的祖先\n let probe = abs;\n const tail: string[] = [];\n for (;;) {\n // 组件存在(含 broken symlink)但 realpath 失败 → 无法解析,拒绝\n const exists = fs.existsSync(probe) || fs.lstatSync(probe, { throwIfNoEntry: false }) !== undefined;\n if (exists) {\n try {\n real = path.join(fs.realpathSync(probe), ...tail);\n break;\n } catch {\n return null;\n }\n }\n tail.unshift(path.basename(probe));\n const parent = path.dirname(probe);\n if (parent === probe) return null; // 已到文件系统根\n probe = parent;\n }\n }\n\n if (!isInsideRelative(path.relative(rootReal, real))) return null;\n return { root: rootReal, real };\n}\n\n/**\n * 判断 target 是否安全地位于 workspace 内(不需要真实路径时用这个)。\n *\n * 注意:删除类操作不应据此改用 realpath 去 unlink —— 那会删掉符号链接\n * 指向的目标而非链接本身;这类操作请用 resolveEntryWithinRoot。\n */\nexport function isWithinRoot(workspace: string, target: string): boolean {\n return resolveWithinRoot(workspace, target) !== null;\n}\n\n/**\n * 解析「目录项本身」的路径:父目录取 realpath,最后一段保持原样。\n *\n * 供 unlink / rename 等作用于条目自身的操作用:父目录不可被 symlink\n * 替换,而最后一段若是符号链接,被操作的是链接本身,不会被跟随。\n */\nexport function resolveEntryWithinRoot(\n workspace: string,\n target: string,\n): ResolvedPath | null {\n const abs = path.resolve(workspace, target);\n const parent = resolveWithinRoot(workspace, path.dirname(abs));\n if (!parent) return null;\n return { root: parent.root, real: path.join(parent.real, path.basename(abs)) };\n}\n"],"mappings":"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","names":["fs","path","path"]}
|