@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/ask.cjs
CHANGED
|
@@ -20,10 +20,23 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/tools/ask.ts
|
|
21
21
|
var ask_exports = {};
|
|
22
22
|
__export(ask_exports, {
|
|
23
|
+
parsePicks: () => parsePicks,
|
|
23
24
|
registerAskTool: () => registerAskTool,
|
|
24
25
|
setQuestionHandler: () => setQuestionHandler
|
|
25
26
|
});
|
|
26
27
|
module.exports = __toCommonJS(ask_exports);
|
|
28
|
+
function parsePicks(input, max) {
|
|
29
|
+
const parts = input.split(/[\s,,、;;]+/).filter(Boolean);
|
|
30
|
+
if (parts.length === 0) return null;
|
|
31
|
+
const nums = [];
|
|
32
|
+
for (const p of parts) {
|
|
33
|
+
if (!/^\d+$/.test(p)) return null;
|
|
34
|
+
const n = Number(p);
|
|
35
|
+
if (n < 1 || n > max) return null;
|
|
36
|
+
nums.push(n);
|
|
37
|
+
}
|
|
38
|
+
return nums;
|
|
39
|
+
}
|
|
27
40
|
var _questionHandler = null;
|
|
28
41
|
function setQuestionHandler(handler) {
|
|
29
42
|
_questionHandler = handler;
|
|
@@ -75,6 +88,7 @@ function registerAskTool(registry) {
|
|
|
75
88
|
}
|
|
76
89
|
// Annotate the CommonJS export names for ESM import in node:
|
|
77
90
|
0 && (module.exports = {
|
|
91
|
+
parsePicks,
|
|
78
92
|
registerAskTool,
|
|
79
93
|
setQuestionHandler
|
|
80
94
|
});
|
package/dist/tools/ask.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tools/ask.ts"],"sourcesContent":["import { ToolRegistry } from \"./ToolRegistry.js\";\n\n/**\n * ask_user — 人机交互问答\n * 对齐 Python 版 tools/ask.py\n * CLI 模式下通过 readline 内联问答实现\n */\n\nexport type QuestionHandler = (question: string, options: string[], allowText: boolean, multi: boolean) => Promise<string>;\n\nlet _questionHandler: QuestionHandler | null = null;\n\n/** 注入 CLI 问答处理器 */\nexport function setQuestionHandler(handler: QuestionHandler): void {\n _questionHandler = handler;\n}\n\nfunction makeAskUser() {\n return async function ask_user(args: Record<string, unknown>) {\n const question = String(args.question ?? \"\");\n const options = (args.options as string[]) ?? [];\n const allowText = args.allow_text !== false;\n const multi = args.multi === true;\n\n if (!question.trim()) {\n return { answer: \"\", error: \"问题不能为空\" };\n }\n\n if (!_questionHandler) {\n return { answer: \"\", error: \"当前环境不支持用户问答\" };\n }\n\n try {\n const answer = await _questionHandler(question, options, allowText, multi);\n return { answer };\n } catch {\n return { answer: \"\", error: \"用户未回答\" };\n }\n };\n}\n\n/** 注册 ask_user 工具 */\nexport function registerAskTool(registry: ToolRegistry): void {\n const askFunc = makeAskUser();\n\n registry.register(\n (args) => askFunc(args),\n { category: \"interaction\", riskLevel: \"low\", capabilities: [\"ask_user\"], requiresApproval: false },\n {\n type: \"function\",\n function: {\n name: \"ask_user\",\n description: \"向用户提问并等待回答——当你真正需要人工决策或你无法推断的信息时使用(一个偏好、一个缺失的事实、在几个真实选项之间的选择)。优先使用此方法而不是猜测或拖延。\\n\\n- question:完整问题,用平实的语言。\\n- options:可选的快速回复选项。当答案是几个离散的选项之一时提供;留空则为开放式问题。\\n- allow_text:即使提供了选项也保留自由文本回答的入口(默认开启)。\\n- multi:允许用户选择多个选项。\\n- header:简短标签(可选)。\\n\\n返回 {\\\"answer\\\": \\\"...\\\"} — 选择的选项或输入的文本。不要问你自己可以合理决定的事情;将此工具保留给真正需要用户来做出的选择。\",\n parameters: {\n type: \"object\",\n properties: {\n question: { type: \"string\", description: \"完整的问题文本\" },\n options: { type: \"array\", items: { type: \"string\" }, description: \"可选的快速回复选项(留空为开放式问题)\" },\n allow_text: { type: \"boolean\", description: \"即使有选项也保留自由文本入口(默认 true)\" },\n multi: { type: \"boolean\", description: \"允许多选(默认 false)\" },\n header: { type: \"string\", description: \"简短标签(可选,≤12字符)\" },\n },\n required: [\"question\"],\n },\n },\n },\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
1
|
+
{"version":3,"sources":["../../src/tools/ask.ts"],"sourcesContent":["import { ToolRegistry } from \"./ToolRegistry.js\";\n\n/**\n * ask_user — 人机交互问答\n * 对齐 Python 版 tools/ask.py\n * CLI 模式下通过 readline 内联问答实现\n */\n\nexport type QuestionHandler = (question: string, options: string[], allowText: boolean, multi: boolean) => Promise<string>;\n\n/**\n * 解析用户输入的选项序号,支持单选 \"3\" 与多选 \"1,2,4\" / \"1 2 4\"\n * (含中英文逗号 / 顿号 / 分号 / 空格分隔)。\n * 返回 1-based 序号数组;输入含非数字或越界时返回 null。\n */\nexport function parsePicks(input: string, max: number): number[] | null {\n const parts = input.split(/[\\s,,、;;]+/).filter(Boolean);\n if (parts.length === 0) return null;\n const nums: number[] = [];\n for (const p of parts) {\n if (!/^\\d+$/.test(p)) return null;\n const n = Number(p);\n if (n < 1 || n > max) return null;\n nums.push(n);\n }\n return nums;\n}\n\nlet _questionHandler: QuestionHandler | null = null;\n\n/** 注入 CLI 问答处理器 */\nexport function setQuestionHandler(handler: QuestionHandler): void {\n _questionHandler = handler;\n}\n\nfunction makeAskUser() {\n return async function ask_user(args: Record<string, unknown>) {\n const question = String(args.question ?? \"\");\n const options = (args.options as string[]) ?? [];\n const allowText = args.allow_text !== false;\n const multi = args.multi === true;\n\n if (!question.trim()) {\n return { answer: \"\", error: \"问题不能为空\" };\n }\n\n if (!_questionHandler) {\n return { answer: \"\", error: \"当前环境不支持用户问答\" };\n }\n\n try {\n const answer = await _questionHandler(question, options, allowText, multi);\n return { answer };\n } catch {\n return { answer: \"\", error: \"用户未回答\" };\n }\n };\n}\n\n/** 注册 ask_user 工具 */\nexport function registerAskTool(registry: ToolRegistry): void {\n const askFunc = makeAskUser();\n\n registry.register(\n (args) => askFunc(args),\n { category: \"interaction\", riskLevel: \"low\", capabilities: [\"ask_user\"], requiresApproval: false },\n {\n type: \"function\",\n function: {\n name: \"ask_user\",\n description: \"向用户提问并等待回答——当你真正需要人工决策或你无法推断的信息时使用(一个偏好、一个缺失的事实、在几个真实选项之间的选择)。优先使用此方法而不是猜测或拖延。\\n\\n- question:完整问题,用平实的语言。\\n- options:可选的快速回复选项。当答案是几个离散的选项之一时提供;留空则为开放式问题。\\n- allow_text:即使提供了选项也保留自由文本回答的入口(默认开启)。\\n- multi:允许用户选择多个选项。\\n- header:简短标签(可选)。\\n\\n返回 {\\\"answer\\\": \\\"...\\\"} — 选择的选项或输入的文本。不要问你自己可以合理决定的事情;将此工具保留给真正需要用户来做出的选择。\",\n parameters: {\n type: \"object\",\n properties: {\n question: { type: \"string\", description: \"完整的问题文本\" },\n options: { type: \"array\", items: { type: \"string\" }, description: \"可选的快速回复选项(留空为开放式问题)\" },\n allow_text: { type: \"boolean\", description: \"即使有选项也保留自由文本入口(默认 true)\" },\n multi: { type: \"boolean\", description: \"允许多选(默认 false)\" },\n header: { type: \"string\", description: \"简短标签(可选,≤12字符)\" },\n },\n required: [\"question\"],\n },\n },\n },\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAeO,SAAS,WAAW,OAAe,KAA8B;AACtE,QAAM,QAAQ,MAAM,MAAM,YAAY,EAAE,OAAO,OAAO;AACtD,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,OAAiB,CAAC;AACxB,aAAW,KAAK,OAAO;AACrB,QAAI,CAAC,QAAQ,KAAK,CAAC,EAAG,QAAO;AAC7B,UAAM,IAAI,OAAO,CAAC;AAClB,QAAI,IAAI,KAAK,IAAI,IAAK,QAAO;AAC7B,SAAK,KAAK,CAAC;AAAA,EACb;AACA,SAAO;AACT;AAEA,IAAI,mBAA2C;AAGxC,SAAS,mBAAmB,SAAgC;AACjE,qBAAmB;AACrB;AAEA,SAAS,cAAc;AACrB,SAAO,eAAe,SAAS,MAA+B;AAC5D,UAAM,WAAW,OAAO,KAAK,YAAY,EAAE;AAC3C,UAAM,UAAW,KAAK,WAAwB,CAAC;AAC/C,UAAM,YAAY,KAAK,eAAe;AACtC,UAAM,QAAQ,KAAK,UAAU;AAE7B,QAAI,CAAC,SAAS,KAAK,GAAG;AACpB,aAAO,EAAE,QAAQ,IAAI,OAAO,uCAAS;AAAA,IACvC;AAEA,QAAI,CAAC,kBAAkB;AACrB,aAAO,EAAE,QAAQ,IAAI,OAAO,qEAAc;AAAA,IAC5C;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,iBAAiB,UAAU,SAAS,WAAW,KAAK;AACzE,aAAO,EAAE,OAAO;AAAA,IAClB,QAAQ;AACN,aAAO,EAAE,QAAQ,IAAI,OAAO,iCAAQ;AAAA,IACtC;AAAA,EACF;AACF;AAGO,SAAS,gBAAgB,UAA8B;AAC5D,QAAM,UAAU,YAAY;AAE5B,WAAS;AAAA,IACP,CAAC,SAAS,QAAQ,IAAI;AAAA,IACtB,EAAE,UAAU,eAAe,WAAW,OAAO,cAAc,CAAC,UAAU,GAAG,kBAAkB,MAAM;AAAA,IACjG;AAAA,MACE,MAAM;AAAA,MACN,UAAU;AAAA,QACR,MAAM;AAAA,QACN,aAAa;AAAA,QACb,YAAY;AAAA,UACV,MAAM;AAAA,UACN,YAAY;AAAA,YACV,UAAU,EAAE,MAAM,UAAU,aAAa,6CAAU;AAAA,YACnD,SAAS,EAAE,MAAM,SAAS,OAAO,EAAE,MAAM,SAAS,GAAG,aAAa,qHAAsB;AAAA,YACxF,YAAY,EAAE,MAAM,WAAW,aAAa,oHAA0B;AAAA,YACtE,OAAO,EAAE,MAAM,WAAW,aAAa,yDAAiB;AAAA,YACxD,QAAQ,EAAE,MAAM,UAAU,aAAa,6EAAiB;AAAA,UAC1D;AAAA,UACA,UAAU,CAAC,UAAU;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/dist/tools/ask.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { T as ToolRegistry } from '../ToolRegistry-
|
|
1
|
+
import { T as ToolRegistry } from '../ToolRegistry-C3pTFiaJ.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* ask_user — 人机交互问答
|
|
@@ -6,9 +6,15 @@ import { T as ToolRegistry } from '../ToolRegistry-BmvPxgMr.cjs';
|
|
|
6
6
|
* CLI 模式下通过 readline 内联问答实现
|
|
7
7
|
*/
|
|
8
8
|
type QuestionHandler = (question: string, options: string[], allowText: boolean, multi: boolean) => Promise<string>;
|
|
9
|
+
/**
|
|
10
|
+
* 解析用户输入的选项序号,支持单选 "3" 与多选 "1,2,4" / "1 2 4"
|
|
11
|
+
* (含中英文逗号 / 顿号 / 分号 / 空格分隔)。
|
|
12
|
+
* 返回 1-based 序号数组;输入含非数字或越界时返回 null。
|
|
13
|
+
*/
|
|
14
|
+
declare function parsePicks(input: string, max: number): number[] | null;
|
|
9
15
|
/** 注入 CLI 问答处理器 */
|
|
10
16
|
declare function setQuestionHandler(handler: QuestionHandler): void;
|
|
11
17
|
/** 注册 ask_user 工具 */
|
|
12
18
|
declare function registerAskTool(registry: ToolRegistry): void;
|
|
13
19
|
|
|
14
|
-
export { type QuestionHandler, registerAskTool, setQuestionHandler };
|
|
20
|
+
export { type QuestionHandler, parsePicks, registerAskTool, setQuestionHandler };
|
package/dist/tools/ask.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { T as ToolRegistry } from '../ToolRegistry-
|
|
1
|
+
import { T as ToolRegistry } from '../ToolRegistry-C3pTFiaJ.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* ask_user — 人机交互问答
|
|
@@ -6,9 +6,15 @@ import { T as ToolRegistry } from '../ToolRegistry-BmvPxgMr.js';
|
|
|
6
6
|
* CLI 模式下通过 readline 内联问答实现
|
|
7
7
|
*/
|
|
8
8
|
type QuestionHandler = (question: string, options: string[], allowText: boolean, multi: boolean) => Promise<string>;
|
|
9
|
+
/**
|
|
10
|
+
* 解析用户输入的选项序号,支持单选 "3" 与多选 "1,2,4" / "1 2 4"
|
|
11
|
+
* (含中英文逗号 / 顿号 / 分号 / 空格分隔)。
|
|
12
|
+
* 返回 1-based 序号数组;输入含非数字或越界时返回 null。
|
|
13
|
+
*/
|
|
14
|
+
declare function parsePicks(input: string, max: number): number[] | null;
|
|
9
15
|
/** 注入 CLI 问答处理器 */
|
|
10
16
|
declare function setQuestionHandler(handler: QuestionHandler): void;
|
|
11
17
|
/** 注册 ask_user 工具 */
|
|
12
18
|
declare function registerAskTool(registry: ToolRegistry): void;
|
|
13
19
|
|
|
14
|
-
export { type QuestionHandler, registerAskTool, setQuestionHandler };
|
|
20
|
+
export { type QuestionHandler, parsePicks, registerAskTool, setQuestionHandler };
|
package/dist/tools/ask.js
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
// src/tools/ask.ts
|
|
2
|
+
function parsePicks(input, max) {
|
|
3
|
+
const parts = input.split(/[\s,,、;;]+/).filter(Boolean);
|
|
4
|
+
if (parts.length === 0) return null;
|
|
5
|
+
const nums = [];
|
|
6
|
+
for (const p of parts) {
|
|
7
|
+
if (!/^\d+$/.test(p)) return null;
|
|
8
|
+
const n = Number(p);
|
|
9
|
+
if (n < 1 || n > max) return null;
|
|
10
|
+
nums.push(n);
|
|
11
|
+
}
|
|
12
|
+
return nums;
|
|
13
|
+
}
|
|
2
14
|
var _questionHandler = null;
|
|
3
15
|
function setQuestionHandler(handler) {
|
|
4
16
|
_questionHandler = handler;
|
|
@@ -49,6 +61,7 @@ function registerAskTool(registry) {
|
|
|
49
61
|
);
|
|
50
62
|
}
|
|
51
63
|
export {
|
|
64
|
+
parsePicks,
|
|
52
65
|
registerAskTool,
|
|
53
66
|
setQuestionHandler
|
|
54
67
|
};
|
package/dist/tools/ask.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tools/ask.ts"],"sourcesContent":["import { ToolRegistry } from \"./ToolRegistry.js\";\n\n/**\n * ask_user — 人机交互问答\n * 对齐 Python 版 tools/ask.py\n * CLI 模式下通过 readline 内联问答实现\n */\n\nexport type QuestionHandler = (question: string, options: string[], allowText: boolean, multi: boolean) => Promise<string>;\n\nlet _questionHandler: QuestionHandler | null = null;\n\n/** 注入 CLI 问答处理器 */\nexport function setQuestionHandler(handler: QuestionHandler): void {\n _questionHandler = handler;\n}\n\nfunction makeAskUser() {\n return async function ask_user(args: Record<string, unknown>) {\n const question = String(args.question ?? \"\");\n const options = (args.options as string[]) ?? [];\n const allowText = args.allow_text !== false;\n const multi = args.multi === true;\n\n if (!question.trim()) {\n return { answer: \"\", error: \"问题不能为空\" };\n }\n\n if (!_questionHandler) {\n return { answer: \"\", error: \"当前环境不支持用户问答\" };\n }\n\n try {\n const answer = await _questionHandler(question, options, allowText, multi);\n return { answer };\n } catch {\n return { answer: \"\", error: \"用户未回答\" };\n }\n };\n}\n\n/** 注册 ask_user 工具 */\nexport function registerAskTool(registry: ToolRegistry): void {\n const askFunc = makeAskUser();\n\n registry.register(\n (args) => askFunc(args),\n { category: \"interaction\", riskLevel: \"low\", capabilities: [\"ask_user\"], requiresApproval: false },\n {\n type: \"function\",\n function: {\n name: \"ask_user\",\n description: \"向用户提问并等待回答——当你真正需要人工决策或你无法推断的信息时使用(一个偏好、一个缺失的事实、在几个真实选项之间的选择)。优先使用此方法而不是猜测或拖延。\\n\\n- question:完整问题,用平实的语言。\\n- options:可选的快速回复选项。当答案是几个离散的选项之一时提供;留空则为开放式问题。\\n- allow_text:即使提供了选项也保留自由文本回答的入口(默认开启)。\\n- multi:允许用户选择多个选项。\\n- header:简短标签(可选)。\\n\\n返回 {\\\"answer\\\": \\\"...\\\"} — 选择的选项或输入的文本。不要问你自己可以合理决定的事情;将此工具保留给真正需要用户来做出的选择。\",\n parameters: {\n type: \"object\",\n properties: {\n question: { type: \"string\", description: \"完整的问题文本\" },\n options: { type: \"array\", items: { type: \"string\" }, description: \"可选的快速回复选项(留空为开放式问题)\" },\n allow_text: { type: \"boolean\", description: \"即使有选项也保留自由文本入口(默认 true)\" },\n multi: { type: \"boolean\", description: \"允许多选(默认 false)\" },\n header: { type: \"string\", description: \"简短标签(可选,≤12字符)\" },\n },\n required: [\"question\"],\n },\n },\n },\n );\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../src/tools/ask.ts"],"sourcesContent":["import { ToolRegistry } from \"./ToolRegistry.js\";\n\n/**\n * ask_user — 人机交互问答\n * 对齐 Python 版 tools/ask.py\n * CLI 模式下通过 readline 内联问答实现\n */\n\nexport type QuestionHandler = (question: string, options: string[], allowText: boolean, multi: boolean) => Promise<string>;\n\n/**\n * 解析用户输入的选项序号,支持单选 \"3\" 与多选 \"1,2,4\" / \"1 2 4\"\n * (含中英文逗号 / 顿号 / 分号 / 空格分隔)。\n * 返回 1-based 序号数组;输入含非数字或越界时返回 null。\n */\nexport function parsePicks(input: string, max: number): number[] | null {\n const parts = input.split(/[\\s,,、;;]+/).filter(Boolean);\n if (parts.length === 0) return null;\n const nums: number[] = [];\n for (const p of parts) {\n if (!/^\\d+$/.test(p)) return null;\n const n = Number(p);\n if (n < 1 || n > max) return null;\n nums.push(n);\n }\n return nums;\n}\n\nlet _questionHandler: QuestionHandler | null = null;\n\n/** 注入 CLI 问答处理器 */\nexport function setQuestionHandler(handler: QuestionHandler): void {\n _questionHandler = handler;\n}\n\nfunction makeAskUser() {\n return async function ask_user(args: Record<string, unknown>) {\n const question = String(args.question ?? \"\");\n const options = (args.options as string[]) ?? [];\n const allowText = args.allow_text !== false;\n const multi = args.multi === true;\n\n if (!question.trim()) {\n return { answer: \"\", error: \"问题不能为空\" };\n }\n\n if (!_questionHandler) {\n return { answer: \"\", error: \"当前环境不支持用户问答\" };\n }\n\n try {\n const answer = await _questionHandler(question, options, allowText, multi);\n return { answer };\n } catch {\n return { answer: \"\", error: \"用户未回答\" };\n }\n };\n}\n\n/** 注册 ask_user 工具 */\nexport function registerAskTool(registry: ToolRegistry): void {\n const askFunc = makeAskUser();\n\n registry.register(\n (args) => askFunc(args),\n { category: \"interaction\", riskLevel: \"low\", capabilities: [\"ask_user\"], requiresApproval: false },\n {\n type: \"function\",\n function: {\n name: \"ask_user\",\n description: \"向用户提问并等待回答——当你真正需要人工决策或你无法推断的信息时使用(一个偏好、一个缺失的事实、在几个真实选项之间的选择)。优先使用此方法而不是猜测或拖延。\\n\\n- question:完整问题,用平实的语言。\\n- options:可选的快速回复选项。当答案是几个离散的选项之一时提供;留空则为开放式问题。\\n- allow_text:即使提供了选项也保留自由文本回答的入口(默认开启)。\\n- multi:允许用户选择多个选项。\\n- header:简短标签(可选)。\\n\\n返回 {\\\"answer\\\": \\\"...\\\"} — 选择的选项或输入的文本。不要问你自己可以合理决定的事情;将此工具保留给真正需要用户来做出的选择。\",\n parameters: {\n type: \"object\",\n properties: {\n question: { type: \"string\", description: \"完整的问题文本\" },\n options: { type: \"array\", items: { type: \"string\" }, description: \"可选的快速回复选项(留空为开放式问题)\" },\n allow_text: { type: \"boolean\", description: \"即使有选项也保留自由文本入口(默认 true)\" },\n multi: { type: \"boolean\", description: \"允许多选(默认 false)\" },\n header: { type: \"string\", description: \"简短标签(可选,≤12字符)\" },\n },\n required: [\"question\"],\n },\n },\n },\n );\n}\n"],"mappings":";AAeO,SAAS,WAAW,OAAe,KAA8B;AACtE,QAAM,QAAQ,MAAM,MAAM,YAAY,EAAE,OAAO,OAAO;AACtD,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,OAAiB,CAAC;AACxB,aAAW,KAAK,OAAO;AACrB,QAAI,CAAC,QAAQ,KAAK,CAAC,EAAG,QAAO;AAC7B,UAAM,IAAI,OAAO,CAAC;AAClB,QAAI,IAAI,KAAK,IAAI,IAAK,QAAO;AAC7B,SAAK,KAAK,CAAC;AAAA,EACb;AACA,SAAO;AACT;AAEA,IAAI,mBAA2C;AAGxC,SAAS,mBAAmB,SAAgC;AACjE,qBAAmB;AACrB;AAEA,SAAS,cAAc;AACrB,SAAO,eAAe,SAAS,MAA+B;AAC5D,UAAM,WAAW,OAAO,KAAK,YAAY,EAAE;AAC3C,UAAM,UAAW,KAAK,WAAwB,CAAC;AAC/C,UAAM,YAAY,KAAK,eAAe;AACtC,UAAM,QAAQ,KAAK,UAAU;AAE7B,QAAI,CAAC,SAAS,KAAK,GAAG;AACpB,aAAO,EAAE,QAAQ,IAAI,OAAO,uCAAS;AAAA,IACvC;AAEA,QAAI,CAAC,kBAAkB;AACrB,aAAO,EAAE,QAAQ,IAAI,OAAO,qEAAc;AAAA,IAC5C;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,iBAAiB,UAAU,SAAS,WAAW,KAAK;AACzE,aAAO,EAAE,OAAO;AAAA,IAClB,QAAQ;AACN,aAAO,EAAE,QAAQ,IAAI,OAAO,iCAAQ;AAAA,IACtC;AAAA,EACF;AACF;AAGO,SAAS,gBAAgB,UAA8B;AAC5D,QAAM,UAAU,YAAY;AAE5B,WAAS;AAAA,IACP,CAAC,SAAS,QAAQ,IAAI;AAAA,IACtB,EAAE,UAAU,eAAe,WAAW,OAAO,cAAc,CAAC,UAAU,GAAG,kBAAkB,MAAM;AAAA,IACjG;AAAA,MACE,MAAM;AAAA,MACN,UAAU;AAAA,QACR,MAAM;AAAA,QACN,aAAa;AAAA,QACb,YAAY;AAAA,UACV,MAAM;AAAA,UACN,YAAY;AAAA,YACV,UAAU,EAAE,MAAM,UAAU,aAAa,6CAAU;AAAA,YACnD,SAAS,EAAE,MAAM,SAAS,OAAO,EAAE,MAAM,SAAS,GAAG,aAAa,qHAAsB;AAAA,YACxF,YAAY,EAAE,MAAM,WAAW,aAAa,oHAA0B;AAAA,YACtE,OAAO,EAAE,MAAM,WAAW,aAAa,yDAAiB;AAAA,YACxD,QAAQ,EAAE,MAAM,UAAU,aAAa,6EAAiB;AAAA,UAC1D;AAAA,UACA,UAAU,CAAC,UAAU;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/dist/tools/diff.cjs
CHANGED
|
@@ -39,17 +39,23 @@ var path2 = __toESM(require("path"), 1);
|
|
|
39
39
|
// src/tools/pathSafety.ts
|
|
40
40
|
var fs = __toESM(require("fs"), 1);
|
|
41
41
|
var path = __toESM(require("path"), 1);
|
|
42
|
-
function
|
|
42
|
+
function isInsideRelative(rel) {
|
|
43
|
+
return !path.isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${path.sep}`);
|
|
44
|
+
}
|
|
45
|
+
function isFilesystemRoot(p) {
|
|
46
|
+
return path.parse(p).root === p;
|
|
47
|
+
}
|
|
48
|
+
function resolveWithinRoot(workspace, target) {
|
|
49
|
+
const abs = path.resolve(workspace, target);
|
|
43
50
|
let rootReal;
|
|
44
51
|
try {
|
|
45
52
|
rootReal = fs.realpathSync(workspace);
|
|
46
53
|
} catch {
|
|
47
54
|
const rootAbs = path.resolve(workspace);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return !rel2.startsWith("..") && !path.isAbsolute(rel2);
|
|
55
|
+
if (!isInsideRelative(path.relative(rootAbs, abs))) return null;
|
|
56
|
+
return { root: rootAbs, real: abs };
|
|
51
57
|
}
|
|
52
|
-
|
|
58
|
+
if (isFilesystemRoot(rootReal)) return null;
|
|
53
59
|
let real;
|
|
54
60
|
try {
|
|
55
61
|
real = fs.realpathSync(abs);
|
|
@@ -63,17 +69,23 @@ function isWithinRoot(workspace, target) {
|
|
|
63
69
|
real = path.join(fs.realpathSync(probe), ...tail);
|
|
64
70
|
break;
|
|
65
71
|
} catch {
|
|
66
|
-
return
|
|
72
|
+
return null;
|
|
67
73
|
}
|
|
68
74
|
}
|
|
69
75
|
tail.unshift(path.basename(probe));
|
|
70
76
|
const parent = path.dirname(probe);
|
|
71
|
-
if (parent === probe) return
|
|
77
|
+
if (parent === probe) return null;
|
|
72
78
|
probe = parent;
|
|
73
79
|
}
|
|
74
80
|
}
|
|
75
|
-
|
|
76
|
-
return
|
|
81
|
+
if (!isInsideRelative(path.relative(rootReal, real))) return null;
|
|
82
|
+
return { root: rootReal, real };
|
|
83
|
+
}
|
|
84
|
+
function resolveEntryWithinRoot(workspace, target) {
|
|
85
|
+
const abs = path.resolve(workspace, target);
|
|
86
|
+
const parent = resolveWithinRoot(workspace, path.dirname(abs));
|
|
87
|
+
if (!parent) return null;
|
|
88
|
+
return { root: parent.root, real: path.join(parent.real, path.basename(abs)) };
|
|
77
89
|
}
|
|
78
90
|
|
|
79
91
|
// src/tools/diff.ts
|
|
@@ -82,7 +94,7 @@ function makeApplyPatch(workspace) {
|
|
|
82
94
|
return function apply_patch(args) {
|
|
83
95
|
const patch = String(args.patch ?? "");
|
|
84
96
|
if (!patch.trim()) return { error: "\u8865\u4E01\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A" };
|
|
85
|
-
const lines = patch.split("\n");
|
|
97
|
+
const lines = patch.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trimEnd().split("\n");
|
|
86
98
|
if (!lines[0]?.trim().startsWith("*** Begin Patch")) {
|
|
87
99
|
return { error: "\u8865\u4E01\u5FC5\u987B\u4EE5 '*** Begin Patch' \u5F00\u5934" };
|
|
88
100
|
}
|
|
@@ -102,10 +114,11 @@ function makeApplyPatch(workspace) {
|
|
|
102
114
|
}
|
|
103
115
|
if (line.startsWith("*** Add File: ")) {
|
|
104
116
|
const filePath = line.slice("*** Add File: ".length).trim();
|
|
105
|
-
const
|
|
106
|
-
if (!
|
|
117
|
+
const resolved = resolveWithinRoot(root, filePath);
|
|
118
|
+
if (!resolved) {
|
|
107
119
|
return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
|
|
108
120
|
}
|
|
121
|
+
const target = resolved.real;
|
|
109
122
|
i++;
|
|
110
123
|
const contentLines = [];
|
|
111
124
|
while (i < lines.length - 1 && !lines[i].trimStart().startsWith("*** ")) {
|
|
@@ -121,7 +134,7 @@ function makeApplyPatch(workspace) {
|
|
|
121
134
|
}
|
|
122
135
|
fs2.mkdirSync(path2.dirname(target), { recursive: true });
|
|
123
136
|
fs2.writeFileSync(target, contentLines.join("\n") + "\n", "utf-8");
|
|
124
|
-
const rel = path2.relative(root, target);
|
|
137
|
+
const rel = path2.relative(resolved.root, target);
|
|
125
138
|
changedFiles.push(rel);
|
|
126
139
|
addedFiles.push(rel);
|
|
127
140
|
fileCount++;
|
|
@@ -129,25 +142,27 @@ function makeApplyPatch(workspace) {
|
|
|
129
142
|
}
|
|
130
143
|
if (line.startsWith("*** Delete File: ")) {
|
|
131
144
|
const filePath = line.slice("*** Delete File: ".length).trim();
|
|
132
|
-
const
|
|
133
|
-
if (!
|
|
145
|
+
const entry = resolveEntryWithinRoot(root, filePath);
|
|
146
|
+
if (!entry) {
|
|
134
147
|
return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
|
|
135
148
|
}
|
|
149
|
+
const target = entry.real;
|
|
136
150
|
if (!fs2.existsSync(target)) {
|
|
137
151
|
return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
|
|
138
152
|
}
|
|
139
153
|
fs2.unlinkSync(target);
|
|
140
|
-
deletedFiles.push(path2.relative(root, target));
|
|
154
|
+
deletedFiles.push(path2.relative(entry.root, target));
|
|
141
155
|
fileCount++;
|
|
142
156
|
i++;
|
|
143
157
|
continue;
|
|
144
158
|
}
|
|
145
159
|
if (line.startsWith("*** Update File: ")) {
|
|
146
160
|
const filePath = line.slice("*** Update File: ".length).trim();
|
|
147
|
-
const
|
|
148
|
-
if (!
|
|
161
|
+
const resolved = resolveWithinRoot(root, filePath);
|
|
162
|
+
if (!resolved) {
|
|
149
163
|
return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
|
|
150
164
|
}
|
|
165
|
+
const target = resolved.real;
|
|
151
166
|
i++;
|
|
152
167
|
let moveTo = null;
|
|
153
168
|
if (i < lines.length - 1 && lines[i].trimStart().startsWith("*** Move to: ")) {
|
|
@@ -177,20 +192,29 @@ function makeApplyPatch(workspace) {
|
|
|
177
192
|
}
|
|
178
193
|
const oldLines = fs2.readFileSync(target, "utf-8").split("\n");
|
|
179
194
|
const newLines = applyPatchHunks(oldLines, chunkLines, filePath);
|
|
180
|
-
|
|
181
|
-
if (moveTo
|
|
182
|
-
|
|
195
|
+
let finalResolved = resolved;
|
|
196
|
+
if (moveTo) {
|
|
197
|
+
const moved = resolveWithinRoot(root, moveTo);
|
|
198
|
+
if (!moved) {
|
|
199
|
+
return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${moveTo}` };
|
|
200
|
+
}
|
|
201
|
+
finalResolved = moved;
|
|
183
202
|
}
|
|
203
|
+
const finalTarget = finalResolved.real;
|
|
184
204
|
if (moveTo && fs2.existsSync(finalTarget) && finalTarget !== target) {
|
|
185
205
|
return { error: `\u6587\u4EF6\u5DF2\u5B58\u5728: ${moveTo}` };
|
|
186
206
|
}
|
|
187
207
|
fs2.mkdirSync(path2.dirname(finalTarget), { recursive: true });
|
|
188
208
|
fs2.writeFileSync(finalTarget, newLines.join("\n"), "utf-8");
|
|
189
209
|
if (moveTo && finalTarget !== target) {
|
|
190
|
-
|
|
191
|
-
|
|
210
|
+
const entry = resolveEntryWithinRoot(root, filePath);
|
|
211
|
+
if (!entry) {
|
|
212
|
+
return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
|
|
213
|
+
}
|
|
214
|
+
fs2.unlinkSync(entry.real);
|
|
215
|
+
deletedFiles.push(path2.relative(resolved.root, target));
|
|
192
216
|
}
|
|
193
|
-
changedFiles.push(path2.relative(root, finalTarget));
|
|
217
|
+
changedFiles.push(path2.relative(finalResolved.root, finalTarget));
|
|
194
218
|
fileCount++;
|
|
195
219
|
continue;
|
|
196
220
|
}
|
|
@@ -232,7 +256,7 @@ function findBlockMatch(lines, block, start) {
|
|
|
232
256
|
function makeApplyUnifiedDiff(workspace) {
|
|
233
257
|
const root = path2.resolve(workspace);
|
|
234
258
|
return function apply_unified_diff(args) {
|
|
235
|
-
const diff = String(args.diff ?? "");
|
|
259
|
+
const diff = String(args.diff ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
236
260
|
if (!diff.trim()) return { error: "diff \u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A" };
|
|
237
261
|
const patches = parseUnifiedDiff(diff);
|
|
238
262
|
if (!patches.length) return { error: "\u672A\u627E\u5230 unified diff \u6587\u4EF6\u8865\u4E01" };
|
|
@@ -244,10 +268,11 @@ function makeApplyUnifiedDiff(workspace) {
|
|
|
244
268
|
const oldIsDevNull = patch.oldPath === "/dev/null";
|
|
245
269
|
const newIsDevNull = patch.newPath === "/dev/null";
|
|
246
270
|
const targetPath = newIsDevNull ? patch.oldPath : patch.newPath;
|
|
247
|
-
const
|
|
248
|
-
if (!
|
|
271
|
+
const resolved = resolveWithinRoot(root, targetPath);
|
|
272
|
+
if (!resolved) {
|
|
249
273
|
return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${targetPath}` };
|
|
250
274
|
}
|
|
275
|
+
const target = resolved.real;
|
|
251
276
|
const oldLines = oldIsDevNull ? [] : fs2.existsSync(target) ? fs2.readFileSync(target, "utf-8").split("\n") : [];
|
|
252
277
|
const newLines = applyDiffHunks(oldLines, patch.hunks, targetPath);
|
|
253
278
|
hunkCount += patch.hunks.length;
|
|
@@ -256,14 +281,18 @@ function makeApplyUnifiedDiff(workspace) {
|
|
|
256
281
|
if (!fs2.statSync(target).isFile()) {
|
|
257
282
|
return { error: `\u8DEF\u5F84\u4E0D\u662F\u6587\u4EF6: ${targetPath}` };
|
|
258
283
|
}
|
|
259
|
-
|
|
284
|
+
const entry = resolveEntryWithinRoot(root, targetPath);
|
|
285
|
+
if (!entry) {
|
|
286
|
+
return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${targetPath}` };
|
|
287
|
+
}
|
|
288
|
+
fs2.unlinkSync(entry.real);
|
|
260
289
|
}
|
|
261
|
-
deletedFiles.push(path2.relative(root, target));
|
|
290
|
+
deletedFiles.push(path2.relative(resolved.root, target));
|
|
262
291
|
continue;
|
|
263
292
|
}
|
|
264
293
|
fs2.mkdirSync(path2.dirname(target), { recursive: true });
|
|
265
294
|
fs2.writeFileSync(target, newLines.join("\n"), "utf-8");
|
|
266
|
-
const rel = path2.relative(root, target);
|
|
295
|
+
const rel = path2.relative(resolved.root, target);
|
|
267
296
|
changedFiles.push(rel);
|
|
268
297
|
if (oldIsDevNull) addedFiles.push(rel);
|
|
269
298
|
}
|
package/dist/tools/diff.cjs.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"]}
|
package/dist/tools/diff.d.cts
CHANGED
package/dist/tools/diff.d.ts
CHANGED