@zqagent/core 0.2.20 → 0.3.6

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.
Files changed (238) hide show
  1. package/README.md +1 -1
  2. package/README.oss.en.md +3 -3
  3. package/README.oss.md +8 -3
  4. package/README.oss.zh-TW.md +3 -3
  5. package/dist/McpClient-CEQBUPuC.d.cts +145 -0
  6. package/dist/McpClient-CEQBUPuC.d.ts +145 -0
  7. package/dist/{ToolRegistry-C3pTFiaJ.d.cts → ToolRegistry-Cy0fQxrb.d.ts} +2 -111
  8. package/dist/{ToolRegistry-C3pTFiaJ.d.ts → ToolRegistry-DouDvn9E.d.cts} +2 -111
  9. package/dist/{explore-RtZtb-V6.d.ts → TurnEngine-BPKhWuSI.d.cts} +108 -73
  10. package/dist/{explore-BgRhj6SP.d.cts → TurnEngine-DtGEuVLT.d.ts} +108 -73
  11. package/dist/conflictGuard-BBOOIxxK.d.cts +42 -0
  12. package/dist/conflictGuard-BBOOIxxK.d.ts +42 -0
  13. package/dist/index.cjs +5418 -1096
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +577 -195
  16. package/dist/index.d.ts +577 -195
  17. package/dist/index.js +5299 -1011
  18. package/dist/index.js.map +1 -1
  19. package/dist/internal/crypto/secrets.cjs +25 -0
  20. package/dist/internal/crypto/secrets.cjs.map +1 -0
  21. package/dist/internal/crypto/secrets.d.cts +2 -0
  22. package/dist/internal/crypto/secrets.d.ts +2 -0
  23. package/dist/internal/crypto/secrets.js +3 -0
  24. package/dist/internal/crypto/secrets.js.map +1 -0
  25. package/dist/internal/engine/FileLogHook.cjs +179 -0
  26. package/dist/internal/engine/FileLogHook.cjs.map +1 -0
  27. package/dist/internal/engine/FileLogHook.d.cts +67 -0
  28. package/dist/internal/engine/FileLogHook.d.ts +67 -0
  29. package/dist/internal/engine/FileLogHook.js +144 -0
  30. package/dist/internal/engine/FileLogHook.js.map +1 -0
  31. package/dist/internal/engine/approvalView.cjs +25 -0
  32. package/dist/internal/engine/approvalView.cjs.map +1 -0
  33. package/dist/internal/engine/approvalView.d.cts +1 -0
  34. package/dist/internal/engine/approvalView.d.ts +1 -0
  35. package/dist/internal/engine/approvalView.js +3 -0
  36. package/dist/internal/engine/approvalView.js.map +1 -0
  37. package/dist/internal/engine/limits.cjs +59 -0
  38. package/dist/internal/engine/limits.cjs.map +1 -0
  39. package/dist/internal/engine/limits.d.cts +32 -0
  40. package/dist/internal/engine/limits.d.ts +32 -0
  41. package/dist/internal/engine/limits.js +31 -0
  42. package/dist/internal/engine/limits.js.map +1 -0
  43. package/dist/internal/engine/sessionNaming.cjs +47 -0
  44. package/dist/internal/engine/sessionNaming.cjs.map +1 -0
  45. package/dist/internal/engine/sessionNaming.d.cts +25 -0
  46. package/dist/internal/engine/sessionNaming.d.ts +25 -0
  47. package/dist/internal/engine/sessionNaming.js +20 -0
  48. package/dist/internal/engine/sessionNaming.js.map +1 -0
  49. package/dist/internal/llmConfig.cjs +561 -0
  50. package/dist/internal/llmConfig.cjs.map +1 -0
  51. package/dist/internal/llmConfig.d.cts +75 -0
  52. package/dist/internal/llmConfig.d.ts +75 -0
  53. package/dist/internal/llmConfig.js +535 -0
  54. package/dist/internal/llmConfig.js.map +1 -0
  55. package/dist/internal/mcp/builtin/index.cjs +685 -0
  56. package/dist/internal/mcp/builtin/index.cjs.map +1 -0
  57. package/dist/internal/mcp/builtin/index.d.cts +21 -0
  58. package/dist/internal/mcp/builtin/index.d.ts +21 -0
  59. package/dist/internal/mcp/builtin/index.js +652 -0
  60. package/dist/internal/mcp/builtin/index.js.map +1 -0
  61. package/dist/internal/mcp/config.cjs +278 -0
  62. package/dist/internal/mcp/config.cjs.map +1 -0
  63. package/dist/internal/mcp/config.d.cts +61 -0
  64. package/dist/internal/mcp/config.d.ts +61 -0
  65. package/dist/internal/mcp/config.js +235 -0
  66. package/dist/internal/mcp/config.js.map +1 -0
  67. package/dist/internal/mcp/presets.cjs +184 -0
  68. package/dist/internal/mcp/presets.cjs.map +1 -0
  69. package/dist/internal/mcp/presets.d.cts +39 -0
  70. package/dist/internal/mcp/presets.d.ts +39 -0
  71. package/dist/internal/mcp/presets.js +158 -0
  72. package/dist/internal/mcp/presets.js.map +1 -0
  73. package/dist/internal/node-name.cjs +45 -0
  74. package/dist/internal/node-name.cjs.map +1 -0
  75. package/dist/internal/node-name.d.cts +3 -0
  76. package/dist/internal/node-name.d.ts +3 -0
  77. package/dist/internal/node-name.js +10 -0
  78. package/dist/internal/node-name.js.map +1 -0
  79. package/dist/internal/paths.cjs +63 -0
  80. package/dist/internal/paths.cjs.map +1 -0
  81. package/dist/internal/paths.d.cts +17 -0
  82. package/dist/internal/paths.d.ts +17 -0
  83. package/dist/internal/paths.js +25 -0
  84. package/dist/internal/paths.js.map +1 -0
  85. package/dist/internal/providers/diagnostics.cjs +288 -0
  86. package/dist/internal/providers/diagnostics.cjs.map +1 -0
  87. package/dist/internal/providers/diagnostics.d.cts +103 -0
  88. package/dist/internal/providers/diagnostics.d.ts +103 -0
  89. package/dist/internal/providers/diagnostics.js +250 -0
  90. package/dist/internal/providers/diagnostics.js.map +1 -0
  91. package/dist/internal/providers/models.cjs +699 -0
  92. package/dist/internal/providers/models.cjs.map +1 -0
  93. package/dist/internal/providers/models.d.cts +136 -0
  94. package/dist/internal/providers/models.d.ts +136 -0
  95. package/dist/internal/providers/models.js +654 -0
  96. package/dist/internal/providers/models.js.map +1 -0
  97. package/dist/internal/terminal.cjs +161 -0
  98. package/dist/internal/terminal.cjs.map +1 -0
  99. package/dist/internal/terminal.d.cts +58 -0
  100. package/dist/internal/terminal.d.ts +58 -0
  101. package/dist/internal/terminal.js +128 -0
  102. package/dist/internal/terminal.js.map +1 -0
  103. package/dist/internal/tools/execAsync.cjs +187 -0
  104. package/dist/internal/tools/execAsync.cjs.map +1 -0
  105. package/dist/internal/tools/execAsync.d.cts +30 -0
  106. package/dist/internal/tools/execAsync.d.ts +30 -0
  107. package/dist/internal/tools/execAsync.js +162 -0
  108. package/dist/internal/tools/execAsync.js.map +1 -0
  109. package/dist/internal/tools/git.cjs +935 -0
  110. package/dist/internal/tools/git.cjs.map +1 -0
  111. package/dist/internal/tools/git.d.cts +206 -0
  112. package/dist/internal/tools/git.d.ts +206 -0
  113. package/dist/internal/tools/git.js +883 -0
  114. package/dist/internal/tools/git.js.map +1 -0
  115. package/dist/internal/tools/pathSafety.cjs +99 -0
  116. package/dist/internal/tools/pathSafety.cjs.map +1 -0
  117. package/dist/internal/tools/pathSafety.d.cts +44 -0
  118. package/dist/internal/tools/pathSafety.d.ts +44 -0
  119. package/dist/internal/tools/pathSafety.js +61 -0
  120. package/dist/internal/tools/pathSafety.js.map +1 -0
  121. package/dist/internal/tools/plan.cjs +146 -0
  122. package/dist/internal/tools/plan.cjs.map +1 -0
  123. package/dist/internal/tools/plan.d.cts +48 -0
  124. package/dist/internal/tools/plan.d.ts +48 -0
  125. package/dist/internal/tools/plan.js +117 -0
  126. package/dist/internal/tools/plan.js.map +1 -0
  127. package/dist/internal/tools/ripgrep.cjs +118 -0
  128. package/dist/internal/tools/ripgrep.cjs.map +1 -0
  129. package/dist/internal/tools/ripgrep.d.cts +6 -0
  130. package/dist/internal/tools/ripgrep.d.ts +6 -0
  131. package/dist/internal/tools/ripgrep.js +89 -0
  132. package/dist/internal/tools/ripgrep.js.map +1 -0
  133. package/dist/internal/tools/sandbox.cjs +253 -0
  134. package/dist/internal/tools/sandbox.cjs.map +1 -0
  135. package/dist/internal/tools/sandbox.d.cts +67 -0
  136. package/dist/internal/tools/sandbox.d.ts +67 -0
  137. package/dist/internal/tools/sandbox.js +213 -0
  138. package/dist/internal/tools/sandbox.js.map +1 -0
  139. package/dist/internal/tools/shell.cjs +771 -0
  140. package/dist/internal/tools/shell.cjs.map +1 -0
  141. package/dist/internal/tools/shell.d.cts +47 -0
  142. package/dist/internal/tools/shell.d.ts +47 -0
  143. package/dist/internal/tools/shell.js +731 -0
  144. package/dist/internal/tools/shell.js.map +1 -0
  145. package/dist/internal/tools/shellRuntime.cjs +220 -0
  146. package/dist/internal/tools/shellRuntime.cjs.map +1 -0
  147. package/dist/internal/tools/shellRuntime.d.cts +67 -0
  148. package/dist/internal/tools/shellRuntime.d.ts +67 -0
  149. package/dist/internal/tools/shellRuntime.js +181 -0
  150. package/dist/internal/tools/shellRuntime.js.map +1 -0
  151. package/dist/internal/update/controller.cjs +1035 -0
  152. package/dist/internal/update/controller.cjs.map +1 -0
  153. package/dist/internal/update/controller.d.cts +150 -0
  154. package/dist/internal/update/controller.d.ts +150 -0
  155. package/dist/internal/update/controller.js +991 -0
  156. package/dist/internal/update/controller.js.map +1 -0
  157. package/dist/internal/update/platform.cjs +234 -0
  158. package/dist/internal/update/platform.cjs.map +1 -0
  159. package/dist/internal/update/platform.d.cts +77 -0
  160. package/dist/internal/update/platform.d.ts +77 -0
  161. package/dist/internal/update/platform.js +184 -0
  162. package/dist/internal/update/platform.js.map +1 -0
  163. package/dist/tools/ask.d.cts +3 -1
  164. package/dist/tools/ask.d.ts +3 -1
  165. package/dist/tools/diff.cjs +17 -6
  166. package/dist/tools/diff.cjs.map +1 -1
  167. package/dist/tools/diff.d.cts +5 -2
  168. package/dist/tools/diff.d.ts +5 -2
  169. package/dist/tools/diff.js +17 -6
  170. package/dist/tools/diff.js.map +1 -1
  171. package/dist/tools/directories.d.cts +3 -1
  172. package/dist/tools/directories.d.ts +3 -1
  173. package/dist/tools/explore.cjs +1778 -204
  174. package/dist/tools/explore.cjs.map +1 -1
  175. package/dist/tools/explore.d.cts +82 -2
  176. package/dist/tools/explore.d.ts +82 -2
  177. package/dist/tools/explore.js +1780 -204
  178. package/dist/tools/explore.js.map +1 -1
  179. package/dist/tools/files.cjs +56 -21
  180. package/dist/tools/files.cjs.map +1 -1
  181. package/dist/tools/files.d.cts +6 -3
  182. package/dist/tools/files.d.ts +6 -3
  183. package/dist/tools/files.js +56 -21
  184. package/dist/tools/files.js.map +1 -1
  185. package/dist/tools/git.cjs +34 -25
  186. package/dist/tools/git.cjs.map +1 -1
  187. package/dist/tools/git.d.cts +4 -204
  188. package/dist/tools/git.d.ts +4 -204
  189. package/dist/tools/git.js +34 -25
  190. package/dist/tools/git.js.map +1 -1
  191. package/dist/tools/media.cjs +280 -3
  192. package/dist/tools/media.cjs.map +1 -1
  193. package/dist/tools/media.d.cts +3 -1
  194. package/dist/tools/media.d.ts +3 -1
  195. package/dist/tools/media.js +278 -1
  196. package/dist/tools/media.js.map +1 -1
  197. package/dist/tools/plan.cjs +62 -0
  198. package/dist/tools/plan.cjs.map +1 -1
  199. package/dist/tools/plan.d.cts +4 -17
  200. package/dist/tools/plan.d.ts +4 -17
  201. package/dist/tools/plan.js +59 -0
  202. package/dist/tools/plan.js.map +1 -1
  203. package/dist/tools/search.cjs +3 -0
  204. package/dist/tools/search.cjs.map +1 -1
  205. package/dist/tools/search.d.cts +3 -1
  206. package/dist/tools/search.d.ts +3 -1
  207. package/dist/tools/search.js +3 -0
  208. package/dist/tools/search.js.map +1 -1
  209. package/dist/tools/shell.cjs +76 -11
  210. package/dist/tools/shell.cjs.map +1 -1
  211. package/dist/tools/shell.d.cts +5 -39
  212. package/dist/tools/shell.d.ts +5 -39
  213. package/dist/tools/shell.js +75 -11
  214. package/dist/tools/shell.js.map +1 -1
  215. package/dist/tools/todo.d.cts +3 -1
  216. package/dist/tools/todo.d.ts +3 -1
  217. package/dist/tools/web.cjs +279 -5
  218. package/dist/tools/web.cjs.map +1 -1
  219. package/dist/tools/web.d.cts +3 -1
  220. package/dist/tools/web.d.ts +3 -1
  221. package/dist/tools/web.js +275 -3
  222. package/dist/tools/web.js.map +1 -1
  223. package/dist/types-BYyXWWPW.d.cts +237 -0
  224. package/dist/types-BYyXWWPW.d.ts +237 -0
  225. package/package.json +26 -37
  226. package/bin/zqagent +0 -2
  227. package/dist/cli/index.cjs +0 -25218
  228. package/dist/cli/index.cjs.map +0 -1
  229. package/dist/cli/index.d.cts +0 -2
  230. package/dist/cli/index.d.ts +0 -2
  231. package/dist/cli/index.js +0 -25201
  232. package/dist/cli/index.js.map +0 -1
  233. package/dist/server/index.cjs +0 -20510
  234. package/dist/server/index.cjs.map +0 -1
  235. package/dist/server/index.d.cts +0 -2
  236. package/dist/server/index.d.ts +0 -2
  237. package/dist/server/index.js +0 -20493
  238. package/dist/server/index.js.map +0 -1
@@ -0,0 +1,184 @@
1
+ // src/update/platform.ts
2
+ import * as fs from "fs";
3
+ import * as os from "os";
4
+ import * as path from "path";
5
+ import { spawnSync } from "child_process";
6
+ import { createRequire } from "module";
7
+ function currentVersion() {
8
+ const injected = "0.3.6"?.trim();
9
+ if (injected) return injected;
10
+ try {
11
+ const req = createRequire(import.meta.url);
12
+ const pkg = req("../../package.json");
13
+ if (pkg?.version) return pkg.version;
14
+ } catch {
15
+ }
16
+ return "dev";
17
+ }
18
+ function isSeaRuntime() {
19
+ if (process.env.ZQAGENT_SEA === "1") return true;
20
+ const base = path.basename(process.execPath).toLowerCase().replace(/\.exe$/, "");
21
+ return base !== "node" && base !== "nodejs";
22
+ }
23
+ var DEFAULT_SERVICE_NAME = "zqagent";
24
+ var DEFAULT_SERVICE_LABEL = "com.zqagent.node";
25
+ function serviceManagerFromCgroup(cgroup, serviceName = DEFAULT_SERVICE_NAME) {
26
+ const unitName = `${serviceName}.service`;
27
+ for (const line of cgroup.split("\n")) {
28
+ const idx = line.lastIndexOf(":");
29
+ if (idx < 0) continue;
30
+ const unitPath = line.slice(idx + 1).trim();
31
+ if (unitPath.split("/").includes(unitName)) return "systemd";
32
+ }
33
+ return null;
34
+ }
35
+ function launchdDomain() {
36
+ const uid = typeof process.getuid === "function" ? process.getuid() : -1;
37
+ return uid === 0 ? "system" : `gui/${uid}`;
38
+ }
39
+ function launchdLabel() {
40
+ return process.env.ZQAGENT_SERVICE_LABEL?.trim() || DEFAULT_SERVICE_LABEL;
41
+ }
42
+ function launchdPlistPath() {
43
+ const label = launchdLabel();
44
+ const uid = typeof process.getuid === "function" ? process.getuid() : -1;
45
+ if (uid === 0) return `/Library/LaunchDaemons/${label}.plist`;
46
+ return path.join(os.homedir(), "Library", "LaunchAgents", `${label}.plist`);
47
+ }
48
+ function serviceManagerFromLaunchctl(output, pid, execPath, sea = true) {
49
+ const pidMatch = /^\s*pid = (\d+)\s*$/m.exec(output);
50
+ if (pidMatch && Number(pidMatch[1]) === pid) return "launchd";
51
+ const program = /^\s*program = (.+?)\s*$/m.exec(output);
52
+ if (program) {
53
+ const shown = program[1].replace(/^"|"$/g, "");
54
+ if (path.resolve(shown) === path.resolve(execPath)) return "launchd";
55
+ }
56
+ if (!sea && /^\s*state = /m.test(output)) return "launchd";
57
+ return null;
58
+ }
59
+ function parseLaunchdEnvironment(xml) {
60
+ const out = {};
61
+ const marker = xml.indexOf("<key>EnvironmentVariables</key>");
62
+ if (marker < 0) return out;
63
+ const open = xml.indexOf("<dict>", marker);
64
+ if (open < 0) return out;
65
+ const close = xml.indexOf("</dict>", open);
66
+ if (close < 0) return out;
67
+ const body = xml.slice(open + "<dict>".length, close);
68
+ for (const m of body.matchAll(/<key>([^<]+)<\/key>\s*<string>([^<]*)<\/string>/g)) {
69
+ out[m[1]] = m[2];
70
+ }
71
+ return out;
72
+ }
73
+ function detectServiceManager() {
74
+ if (process.platform === "darwin") return detectLaunchd();
75
+ if (process.platform !== "linux") return null;
76
+ const serviceName = process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;
77
+ try {
78
+ return serviceManagerFromCgroup(
79
+ fs.readFileSync("/proc/self/cgroup", "utf8"),
80
+ serviceName
81
+ );
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+ function detectLaunchd() {
87
+ try {
88
+ const res = spawnSync(
89
+ "launchctl",
90
+ ["print", `${launchdDomain()}/${launchdLabel()}`],
91
+ { encoding: "utf8", timeout: 3e3 }
92
+ );
93
+ if (res.status !== 0 || !res.stdout) return null;
94
+ return serviceManagerFromLaunchctl(
95
+ res.stdout,
96
+ process.pid,
97
+ process.execPath,
98
+ isSeaRuntime()
99
+ );
100
+ } catch {
101
+ return null;
102
+ }
103
+ }
104
+ function serviceManager() {
105
+ const v = process.env.ZQAGENT_SERVICE_MANAGER?.trim().toLowerCase();
106
+ if (v === "systemd" || v === "launchd") return v;
107
+ return detectServiceManager();
108
+ }
109
+ function autoUpdateDisabled() {
110
+ return process.env.ZQAGENT_AUTO_UPDATE?.trim() === "0";
111
+ }
112
+ function parseServiceEnvironment(raw) {
113
+ const out = {};
114
+ for (const token of raw.trim().split(/\s+/)) {
115
+ const idx = token.indexOf("=");
116
+ if (idx > 0) out[token.slice(0, idx)] = token.slice(idx + 1);
117
+ }
118
+ return out;
119
+ }
120
+ var cachedServiceEnv = null;
121
+ function readServiceEnvironment() {
122
+ const serviceName = process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;
123
+ try {
124
+ const res = spawnSync(
125
+ "systemctl",
126
+ ["show", serviceName, "--property=Environment", "--value", "--no-pager"],
127
+ { encoding: "utf8", timeout: 3e3 }
128
+ );
129
+ if (res.status !== 0 || !res.stdout) return {};
130
+ return parseServiceEnvironment(res.stdout);
131
+ } catch {
132
+ return {};
133
+ }
134
+ }
135
+ function serviceEnvValue(key) {
136
+ if (process.platform === "darwin") {
137
+ if (serviceManager() !== "launchd") return void 0;
138
+ cachedLaunchdEnv ??= readLaunchdEnvironment();
139
+ return cachedLaunchdEnv[key];
140
+ }
141
+ if (process.platform !== "linux") return void 0;
142
+ if (serviceManager() !== "systemd") return void 0;
143
+ cachedServiceEnv ??= readServiceEnvironment();
144
+ return cachedServiceEnv[key];
145
+ }
146
+ var cachedLaunchdEnv = null;
147
+ function readLaunchdEnvironment() {
148
+ try {
149
+ return parseLaunchdEnvironment(fs.readFileSync(launchdPlistPath(), "utf8"));
150
+ } catch {
151
+ return {};
152
+ }
153
+ }
154
+ function selfUpdateCapability(hasPublicKey) {
155
+ if (!isSeaRuntime()) {
156
+ return {
157
+ ok: false,
158
+ reason: "\u5F53\u524D\u4E0D\u662F\u5355\u6587\u4EF6\uFF08SEA\uFF09\u5F62\u6001\uFF1A\u8BF7\u7528 npm \u5347\u7EA7\uFF08npm i -g @zqagent/cli@latest\uFF09"
159
+ };
160
+ }
161
+ if (!serviceManager()) {
162
+ return {
163
+ ok: false,
164
+ reason: "\u672A\u88AB systemd/launchd \u6258\u7BA1\uFF1A\u8BF7\u7528 deploy/service/install.sh \u5B89\u88C5\uFF0C\u6216\u624B\u52A8\u5347\u7EA7"
165
+ };
166
+ }
167
+ if (!hasPublicKey) {
168
+ return { ok: false, reason: "\u672A\u914D\u7F6E ZQAGENT_UPDATE_PUBKEY\uFF0C\u65E0\u6CD5\u9A8C\u7B7E\uFF0C\u5DF2\u7981\u7528\u81EA\u52A8\u66F4\u65B0" };
169
+ }
170
+ return { ok: true };
171
+ }
172
+ export {
173
+ autoUpdateDisabled,
174
+ currentVersion,
175
+ isSeaRuntime,
176
+ parseLaunchdEnvironment,
177
+ parseServiceEnvironment,
178
+ selfUpdateCapability,
179
+ serviceEnvValue,
180
+ serviceManager,
181
+ serviceManagerFromCgroup,
182
+ serviceManagerFromLaunchctl
183
+ };
184
+ //# sourceMappingURL=platform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/update/platform.ts"],"sourcesContent":["/**\n * 运行形态与自更新能力判定。\n *\n * 只有「SEA 单文件 + 由 systemd/launchd 托管 + 配置了公钥」三者同时满足,\n * 才允许自动替换可执行文件并重启——否则退出后没人把它拉起来。\n */\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\n\n/**\n * 当前版本:构建期注入(SEA/tsup define)优先,其次读包内 package.json,\n * 最后回退 \"dev\"。后两条让源码运行(tsx)也能显示真实版本。\n */\nexport function currentVersion(): string {\n const injected = process.env.ZQAGENT_VERSION?.trim();\n if (injected) return injected;\n try {\n const req = createRequire(import.meta.url);\n const pkg = req(\"../../package.json\") as { version?: string };\n if (pkg?.version) return pkg.version;\n } catch {\n // SEA 内取不到包信息,忽略\n }\n return \"dev\";\n}\n\n/**\n * 是否为 SEA 单文件运行。\n *\n * 判据优先取 execPath 的文件名:SEA 产物是 `zqagent`,经 node 运行的则是 `node`。\n * 构建期还会注入 `ZQAGENT_SEA=1`(见 tsup.sea.config.ts)作为权威标记。\n */\nexport function isSeaRuntime(): boolean {\n if (process.env.ZQAGENT_SEA === \"1\") return true;\n const base = path.basename(process.execPath).toLowerCase().replace(/\\.exe$/, \"\");\n return base !== \"node\" && base !== \"nodejs\";\n}\n\nexport type ServiceManager = \"systemd\" | \"launchd\";\n\n/** 未指定 ZQAGENT_SERVICE_NAME 时的默认 systemd unit 名(与 install.sh 一致) */\nconst DEFAULT_SERVICE_NAME = \"zqagent\";\n\n/** 未指定 ZQAGENT_SERVICE_LABEL 时的默认 launchd 作业名(与 install.sh 一致) */\nconst DEFAULT_SERVICE_LABEL = \"com.zqagent.node\";\n\n/**\n * 从 `/proc/self/cgroup` 的内容判定进程是否属于某个 systemd unit。\n *\n * 抽取为纯函数以便测试。兼容 cgroup v1(`1:name=systemd:/system.slice/x.service`)\n * 与 v2(`0::/system.slice/x.service`)两种行格式。\n *\n * 只认路径中**独立成段**的 `<serviceName>.service`:`my-zqagent.service`、\n * `zqagent.service.bak` 这类名字相近的 unit 不会被误判成 `zqagent.service`。\n */\nexport function serviceManagerFromCgroup(\n cgroup: string,\n serviceName: string = DEFAULT_SERVICE_NAME,\n): ServiceManager | null {\n const unitName = `${serviceName}.service`;\n for (const line of cgroup.split(\"\\n\")) {\n // 路径在最后一个冒号之后;v1 的 `name=systemd` 不会干扰\n const idx = line.lastIndexOf(\":\");\n if (idx < 0) continue;\n const unitPath = line.slice(idx + 1).trim();\n if (unitPath.split(\"/\").includes(unitName)) return \"systemd\";\n }\n return null;\n}\n\n/** launchd 作业所在域:root 装的是 LaunchDaemon(system),普通用户是 LaunchAgent(gui/<uid>) */\nfunction launchdDomain(): string {\n const uid = typeof process.getuid === \"function\" ? process.getuid() : -1;\n return uid === 0 ? \"system\" : `gui/${uid}`;\n}\n\nfunction launchdLabel(): string {\n return process.env.ZQAGENT_SERVICE_LABEL?.trim() || DEFAULT_SERVICE_LABEL;\n}\n\n/** 与 install.sh 写入的位置保持一致 */\nfunction launchdPlistPath(): string {\n const label = launchdLabel();\n const uid = typeof process.getuid === \"function\" ? process.getuid() : -1;\n if (uid === 0) return `/Library/LaunchDaemons/${label}.plist`;\n return path.join(os.homedir(), \"Library\", \"LaunchAgents\", `${label}.plist`);\n}\n\n/**\n * 从 `launchctl print <domain>/<label>` 的输出判定是否为 launchd 托管。\n *\n * macOS 没有 cgroup 可用,改用作业描述里的两处信息:\n * - `pid = N` 与当前进程一致 → 本进程就是该作业拉起的;\n * - `program = /path` 与 execPath 相同 → 服务运行的正是这个可执行文件,\n * 在交互 shell 里执行 `zqagent update` 时靠这条判定(Linux 靠 cgroup 归属)。\n *\n * `sea` 为 false(npm/source 形态)时作业存在即算托管:这种形态本来就不允许\n * 自替换二进制(见 selfUpdateCapability),这里只影响 `update --status` 的显示。\n */\nexport function serviceManagerFromLaunchctl(\n output: string,\n pid: number,\n execPath: string,\n sea = true,\n): ServiceManager | null {\n const pidMatch = /^\\s*pid = (\\d+)\\s*$/m.exec(output);\n if (pidMatch && Number(pidMatch[1]) === pid) return \"launchd\";\n\n const program = /^\\s*program = (.+?)\\s*$/m.exec(output);\n if (program) {\n const shown = program[1].replace(/^\"|\"$/g, \"\");\n if (path.resolve(shown) === path.resolve(execPath)) return \"launchd\";\n }\n\n if (!sea && /^\\s*state = /m.test(output)) return \"launchd\";\n return null;\n}\n\n/**\n * 解析 launchd plist 中 `EnvironmentVariables` 字典的键值。\n *\n * 只处理本脚本会写出的 `<key>K</key><string>V</string>` 形式;`<true/>` 之类的\n * 非字符串值忽略(更新配置用不到)。\n */\nexport function parseLaunchdEnvironment(xml: string): Record<string, string> {\n const out: Record<string, string> = {};\n const marker = xml.indexOf(\"<key>EnvironmentVariables</key>\");\n if (marker < 0) return out;\n const open = xml.indexOf(\"<dict>\", marker);\n if (open < 0) return out;\n const close = xml.indexOf(\"</dict>\", open);\n if (close < 0) return out;\n const body = xml.slice(open + \"<dict>\".length, close);\n for (const m of body.matchAll(/<key>([^<]+)<\\/key>\\s*<string>([^<]*)<\\/string>/g)) {\n out[m[1]] = m[2];\n }\n return out;\n}\n\n/**\n * 未注入 ZQAGENT_SERVICE_MANAGER 时的兜底检测。\n *\n * 服务进程的环境变量由 unit 注入,但运维在交互 shell 里手动执行\n * `zqagent update --status/--check/--now` 时没有这些变量——此前会被判成\n * 「未托管」,导致诊断结论错误、`--now` 直接拒绝执行。这里用 cgroup 归属\n * 兜底:Linux 上进程落在 `/<service>.service` 里,说明 systemd 确实会拉起它。\n *\n * 刻意不使用「/run/systemd/system 存在即视为托管」这类判据:那样任何在\n * systemd 机器上裸跑的进程都会通过自检,替换二进制后 `exit(70)` 却没人拉起,\n * 节点会直接下线。\n */\nfunction detectServiceManager(): ServiceManager | null {\n if (process.platform === \"darwin\") return detectLaunchd();\n if (process.platform !== \"linux\") return null;\n const serviceName =\n process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;\n try {\n return serviceManagerFromCgroup(\n fs.readFileSync(\"/proc/self/cgroup\", \"utf8\"),\n serviceName,\n );\n } catch {\n return null;\n }\n}\n\n/**\n * macOS 兜底:读取 launchd 作业描述,按 pid / program 判定(见\n * `serviceManagerFromLaunchctl`)。作业不存在、launchctl 不可用时返回 null。\n */\nfunction detectLaunchd(): ServiceManager | null {\n try {\n const res = spawnSync(\n \"launchctl\",\n [\"print\", `${launchdDomain()}/${launchdLabel()}`],\n { encoding: \"utf8\", timeout: 3000 },\n );\n if (res.status !== 0 || !res.stdout) return null;\n return serviceManagerFromLaunchctl(\n res.stdout,\n process.pid,\n process.execPath,\n isSeaRuntime(),\n );\n } catch {\n return null;\n }\n}\n\n/**\n * 进程由哪个服务管理器托管;无托管返回 null。\n *\n * 优先采信 `deploy/service/install.sh` 注入的环境变量,缺失时在 Linux 上\n * 按 cgroup 归属兜底检测(见 `detectServiceManager`)。\n */\nexport function serviceManager(): ServiceManager | null {\n const v = process.env.ZQAGENT_SERVICE_MANAGER?.trim().toLowerCase();\n if (v === \"systemd\" || v === \"launchd\") return v;\n return detectServiceManager();\n}\n\nexport function autoUpdateDisabled(): boolean {\n return process.env.ZQAGENT_AUTO_UPDATE?.trim() === \"0\";\n}\n\n/**\n * 解析 `systemctl show -p Environment --value` 的输出为键值表。\n *\n * 输出形如 `NODE_ENV=production PATH=/usr/local/bin:/usr/bin KEY=abc`。\n * 值里若含空格会被拆开——本函数只用于取公钥/通道/清单地址这类不含空格的\n * 变量,够用且无需处理 systemd 的引号规则。\n */\nexport function parseServiceEnvironment(raw: string): Record<string, string> {\n const out: Record<string, string> = {};\n for (const token of raw.trim().split(/\\s+/)) {\n const idx = token.indexOf(\"=\");\n if (idx > 0) out[token.slice(0, idx)] = token.slice(idx + 1);\n }\n return out;\n}\n\nlet cachedServiceEnv: Record<string, string> | null = null;\n\nfunction readServiceEnvironment(): Record<string, string> {\n const serviceName =\n process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;\n try {\n const res = spawnSync(\n \"systemctl\",\n [\"show\", serviceName, \"--property=Environment\", \"--value\", \"--no-pager\"],\n { encoding: \"utf8\", timeout: 3000 },\n );\n if (res.status !== 0 || !res.stdout) return {};\n return parseServiceEnvironment(res.stdout);\n } catch {\n return {};\n }\n}\n\n/**\n * 读取 systemd unit 注入的环境变量。\n *\n * 服务进程拿得到这些变量,但运维在交互 shell 里手动执行 `zqagent update`\n * 时拿不到——公钥缺失会让自更新被判为不可用。这里按需从 unit 补齐,\n * 取不到(非 systemd、无 systemctl、超时)时返回 undefined。\n */\nexport function serviceEnvValue(key: string): string | undefined {\n if (process.platform === \"darwin\") {\n if (serviceManager() !== \"launchd\") return undefined;\n cachedLaunchdEnv ??= readLaunchdEnvironment();\n return cachedLaunchdEnv[key];\n }\n if (process.platform !== \"linux\") return undefined;\n if (serviceManager() !== \"systemd\") return undefined;\n cachedServiceEnv ??= readServiceEnvironment();\n return cachedServiceEnv[key];\n}\n\nlet cachedLaunchdEnv: Record<string, string> | null = null;\n\n/**\n * 读取 launchd plist 注入的环境变量。\n *\n * 与 systemd 的 `systemctl show -p Environment` 对应:干净 shell 里执行\n * `zqagent update --status` 时,公钥/通道/清单地址只能从服务定义里取回。\n */\nfunction readLaunchdEnvironment(): Record<string, string> {\n try {\n return parseLaunchdEnvironment(fs.readFileSync(launchdPlistPath(), \"utf8\"));\n } catch {\n return {};\n }\n}\n\nexport interface Capability {\n ok: boolean;\n /** 不满足时的可读原因(用于日志/`update --status`) */\n reason?: string;\n}\n\n/**\n * 自更新能力判定。`hasPublicKey` 由调用方传入(公钥只在验签时解析一次)。\n */\nexport function selfUpdateCapability(hasPublicKey: boolean): Capability {\n if (!isSeaRuntime()) {\n return {\n ok: false,\n reason: \"当前不是单文件(SEA)形态:请用 npm 升级(npm i -g @zqagent/cli@latest)\",\n };\n }\n if (!serviceManager()) {\n return {\n ok: false,\n reason: \"未被 systemd/launchd 托管:请用 deploy/service/install.sh 安装,或手动升级\",\n };\n }\n if (!hasPublicKey) {\n return { ok: false, reason: \"未配置 ZQAGENT_UPDATE_PUBKEY,无法验签,已禁用自动更新\" };\n }\n return { ok: true };\n}\n"],"mappings":"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","names":[]}
@@ -1,4 +1,6 @@
1
- import { T as ToolRegistry } from '../ToolRegistry-C3pTFiaJ.cjs';
1
+ import { T as ToolRegistry } from '../ToolRegistry-DouDvn9E.cjs';
2
+ import '../types-BYyXWWPW.cjs';
3
+ import '@zqagent/shared/model-capabilities';
2
4
 
3
5
  /**
4
6
  * ask_user — 人机交互问答
@@ -1,4 +1,6 @@
1
- import { T as ToolRegistry } from '../ToolRegistry-C3pTFiaJ.js';
1
+ import { T as ToolRegistry } from '../ToolRegistry-Cy0fQxrb.js';
2
+ import '../types-BYyXWWPW.js';
3
+ import '@zqagent/shared/model-capabilities';
2
4
 
3
5
  /**
4
6
  * ask_user — 人机交互问答
@@ -89,7 +89,7 @@ function resolveEntryWithinRoot(workspace, target) {
89
89
  }
90
90
 
91
91
  // src/tools/diff.ts
92
- function makeApplyPatch(workspace) {
92
+ function makeApplyPatch(workspace, guard) {
93
93
  const root = path2.resolve(workspace);
94
94
  return function apply_patch(args) {
95
95
  const patch = String(args.patch ?? "");
@@ -150,6 +150,8 @@ function makeApplyPatch(workspace) {
150
150
  if (!fs2.existsSync(target)) {
151
151
  return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
152
152
  }
153
+ const delConflict = guard?.checkWrite(target, filePath);
154
+ if (delConflict) return { error: delConflict.message };
153
155
  fs2.unlinkSync(target);
154
156
  deletedFiles.push(path2.relative(entry.root, target));
155
157
  fileCount++;
@@ -191,6 +193,8 @@ function makeApplyPatch(workspace) {
191
193
  return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
192
194
  }
193
195
  const oldLines = fs2.readFileSync(target, "utf-8").split("\n");
196
+ const conflict = guard?.checkWrite(target, filePath);
197
+ if (conflict) return { error: conflict.message };
194
198
  const newLines = applyPatchHunks(oldLines, chunkLines, filePath);
195
199
  let finalResolved = resolved;
196
200
  if (moveTo) {
@@ -206,6 +210,7 @@ function makeApplyPatch(workspace) {
206
210
  }
207
211
  fs2.mkdirSync(path2.dirname(finalTarget), { recursive: true });
208
212
  fs2.writeFileSync(finalTarget, newLines.join("\n"), "utf-8");
213
+ guard?.noteWrite(finalTarget, newLines.join("\n"));
209
214
  if (moveTo && finalTarget !== target) {
210
215
  const entry = resolveEntryWithinRoot(root, filePath);
211
216
  if (!entry) {
@@ -253,7 +258,7 @@ function findBlockMatch(lines, block, start) {
253
258
  }
254
259
  return -1;
255
260
  }
256
- function makeApplyUnifiedDiff(workspace) {
261
+ function makeApplyUnifiedDiff(workspace, guard) {
257
262
  const root = path2.resolve(workspace);
258
263
  return function apply_unified_diff(args) {
259
264
  const diff = String(args.diff ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
@@ -274,6 +279,10 @@ function makeApplyUnifiedDiff(workspace) {
274
279
  }
275
280
  const target = resolved.real;
276
281
  const oldLines = oldIsDevNull ? [] : fs2.existsSync(target) ? fs2.readFileSync(target, "utf-8").split("\n") : [];
282
+ if (!oldIsDevNull) {
283
+ const conflict = guard?.checkWrite(target, targetPath);
284
+ if (conflict) return { error: conflict.message };
285
+ }
277
286
  const newLines = applyDiffHunks(oldLines, patch.hunks, targetPath);
278
287
  hunkCount += patch.hunks.length;
279
288
  if (newIsDevNull) {
@@ -291,7 +300,9 @@ function makeApplyUnifiedDiff(workspace) {
291
300
  continue;
292
301
  }
293
302
  fs2.mkdirSync(path2.dirname(target), { recursive: true });
294
- fs2.writeFileSync(target, newLines.join("\n"), "utf-8");
303
+ const written = newLines.join("\n");
304
+ fs2.writeFileSync(target, written, "utf-8");
305
+ guard?.noteWrite(target, written);
295
306
  const rel = path2.relative(resolved.root, target);
296
307
  changedFiles.push(rel);
297
308
  if (oldIsDevNull) addedFiles.push(rel);
@@ -406,8 +417,8 @@ function applyDiffHunks(oldLines, hunks, filePath) {
406
417
  newLines.push(...oldLines.slice(cursor));
407
418
  return newLines;
408
419
  }
409
- function registerDiffTools(registry, workspace) {
410
- const applyPatchFunc = makeApplyPatch(workspace);
420
+ function registerDiffTools(registry, workspace, guard) {
421
+ const applyPatchFunc = makeApplyPatch(workspace, guard);
411
422
  registry.register(
412
423
  (args) => applyPatchFunc(args),
413
424
  { category: "filesystem", riskLevel: "medium", capabilities: ["edit_file"], requiresApproval: true },
@@ -426,7 +437,7 @@ function registerDiffTools(registry, workspace) {
426
437
  }
427
438
  }
428
439
  );
429
- const applyUnifiedDiffFunc = makeApplyUnifiedDiff(workspace);
440
+ const applyUnifiedDiffFunc = makeApplyUnifiedDiff(workspace, guard);
430
441
  registry.register(
431
442
  (args) => applyUnifiedDiffFunc(args),
432
443
  { category: "filesystem", riskLevel: "medium", capabilities: ["edit_file"], requiresApproval: true },
@@ -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 { 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"]}
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\";\nimport type { ConflictGuard } from \"./conflictGuard.js\";\n\n/** apply_patch — Codex 风格补丁 (对齐 Python 版) */\nfunction makeApplyPatch(workspace: string, guard?: ConflictGuard) {\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 // 删除同样是破坏性操作:本会话读过之后被别人改过 → 先让人确认\n const delConflict = guard?.checkWrite(target, filePath);\n if (delConflict) return { error: delConflict.message };\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 // 补丁是「基于读到的版本」生成的:先确认这个版本没被别人改过\n const conflict = guard?.checkWrite(target, filePath);\n if (conflict) return { error: conflict.message };\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 guard?.noteWrite(finalTarget, newLines.join(\"\\n\"));\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, guard?: ConflictGuard) {\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 // diff 基于某个版本生成:不校验就应用会把别人的改动覆盖掉\n if (!oldIsDevNull) {\n const conflict = guard?.checkWrite(target, targetPath);\n if (conflict) return { error: conflict.message };\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 const written = newLines.join(\"\\n\");\n fs.writeFileSync(target, written, \"utf-8\");\n guard?.noteWrite(target, written);\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(\n registry: ToolRegistry,\n workspace: string,\n guard?: ConflictGuard,\n): void {\n const applyPatchFunc = makeApplyPatch(workspace, guard);\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, guard);\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 *\n * 导出供自行做 `path.relative` 校验的调用方复用(如目录浏览新建目录、\n * git 面板的 repo / path 参数),避免各处重复写 `startsWith(\"..\")`。\n */\nexport function 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"]}
@@ -1,6 +1,9 @@
1
- import { T as ToolRegistry } from '../ToolRegistry-C3pTFiaJ.cjs';
1
+ import { T as ToolRegistry } from '../ToolRegistry-DouDvn9E.cjs';
2
+ import { C as ConflictGuard } from '../conflictGuard-BBOOIxxK.cjs';
3
+ import '../types-BYyXWWPW.cjs';
4
+ import '@zqagent/shared/model-capabilities';
2
5
 
3
6
  /** 注册 diff 工具 */
4
- declare function registerDiffTools(registry: ToolRegistry, workspace: string): void;
7
+ declare function registerDiffTools(registry: ToolRegistry, workspace: string, guard?: ConflictGuard): void;
5
8
 
6
9
  export { registerDiffTools };
@@ -1,6 +1,9 @@
1
- import { T as ToolRegistry } from '../ToolRegistry-C3pTFiaJ.js';
1
+ import { T as ToolRegistry } from '../ToolRegistry-Cy0fQxrb.js';
2
+ import { C as ConflictGuard } from '../conflictGuard-BBOOIxxK.js';
3
+ import '../types-BYyXWWPW.js';
4
+ import '@zqagent/shared/model-capabilities';
2
5
 
3
6
  /** 注册 diff 工具 */
4
- declare function registerDiffTools(registry: ToolRegistry, workspace: string): void;
7
+ declare function registerDiffTools(registry: ToolRegistry, workspace: string, guard?: ConflictGuard): void;
5
8
 
6
9
  export { registerDiffTools };
@@ -55,7 +55,7 @@ function resolveEntryWithinRoot(workspace, target) {
55
55
  }
56
56
 
57
57
  // src/tools/diff.ts
58
- function makeApplyPatch(workspace) {
58
+ function makeApplyPatch(workspace, guard) {
59
59
  const root = path2.resolve(workspace);
60
60
  return function apply_patch(args) {
61
61
  const patch = String(args.patch ?? "");
@@ -116,6 +116,8 @@ function makeApplyPatch(workspace) {
116
116
  if (!fs2.existsSync(target)) {
117
117
  return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
118
118
  }
119
+ const delConflict = guard?.checkWrite(target, filePath);
120
+ if (delConflict) return { error: delConflict.message };
119
121
  fs2.unlinkSync(target);
120
122
  deletedFiles.push(path2.relative(entry.root, target));
121
123
  fileCount++;
@@ -157,6 +159,8 @@ function makeApplyPatch(workspace) {
157
159
  return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
158
160
  }
159
161
  const oldLines = fs2.readFileSync(target, "utf-8").split("\n");
162
+ const conflict = guard?.checkWrite(target, filePath);
163
+ if (conflict) return { error: conflict.message };
160
164
  const newLines = applyPatchHunks(oldLines, chunkLines, filePath);
161
165
  let finalResolved = resolved;
162
166
  if (moveTo) {
@@ -172,6 +176,7 @@ function makeApplyPatch(workspace) {
172
176
  }
173
177
  fs2.mkdirSync(path2.dirname(finalTarget), { recursive: true });
174
178
  fs2.writeFileSync(finalTarget, newLines.join("\n"), "utf-8");
179
+ guard?.noteWrite(finalTarget, newLines.join("\n"));
175
180
  if (moveTo && finalTarget !== target) {
176
181
  const entry = resolveEntryWithinRoot(root, filePath);
177
182
  if (!entry) {
@@ -219,7 +224,7 @@ function findBlockMatch(lines, block, start) {
219
224
  }
220
225
  return -1;
221
226
  }
222
- function makeApplyUnifiedDiff(workspace) {
227
+ function makeApplyUnifiedDiff(workspace, guard) {
223
228
  const root = path2.resolve(workspace);
224
229
  return function apply_unified_diff(args) {
225
230
  const diff = String(args.diff ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
@@ -240,6 +245,10 @@ function makeApplyUnifiedDiff(workspace) {
240
245
  }
241
246
  const target = resolved.real;
242
247
  const oldLines = oldIsDevNull ? [] : fs2.existsSync(target) ? fs2.readFileSync(target, "utf-8").split("\n") : [];
248
+ if (!oldIsDevNull) {
249
+ const conflict = guard?.checkWrite(target, targetPath);
250
+ if (conflict) return { error: conflict.message };
251
+ }
243
252
  const newLines = applyDiffHunks(oldLines, patch.hunks, targetPath);
244
253
  hunkCount += patch.hunks.length;
245
254
  if (newIsDevNull) {
@@ -257,7 +266,9 @@ function makeApplyUnifiedDiff(workspace) {
257
266
  continue;
258
267
  }
259
268
  fs2.mkdirSync(path2.dirname(target), { recursive: true });
260
- fs2.writeFileSync(target, newLines.join("\n"), "utf-8");
269
+ const written = newLines.join("\n");
270
+ fs2.writeFileSync(target, written, "utf-8");
271
+ guard?.noteWrite(target, written);
261
272
  const rel = path2.relative(resolved.root, target);
262
273
  changedFiles.push(rel);
263
274
  if (oldIsDevNull) addedFiles.push(rel);
@@ -372,8 +383,8 @@ function applyDiffHunks(oldLines, hunks, filePath) {
372
383
  newLines.push(...oldLines.slice(cursor));
373
384
  return newLines;
374
385
  }
375
- function registerDiffTools(registry, workspace) {
376
- const applyPatchFunc = makeApplyPatch(workspace);
386
+ function registerDiffTools(registry, workspace, guard) {
387
+ const applyPatchFunc = makeApplyPatch(workspace, guard);
377
388
  registry.register(
378
389
  (args) => applyPatchFunc(args),
379
390
  { category: "filesystem", riskLevel: "medium", capabilities: ["edit_file"], requiresApproval: true },
@@ -392,7 +403,7 @@ function registerDiffTools(registry, workspace) {
392
403
  }
393
404
  }
394
405
  );
395
- const applyUnifiedDiffFunc = makeApplyUnifiedDiff(workspace);
406
+ const applyUnifiedDiffFunc = makeApplyUnifiedDiff(workspace, guard);
396
407
  registry.register(
397
408
  (args) => applyUnifiedDiffFunc(args),
398
409
  { category: "filesystem", riskLevel: "medium", capabilities: ["edit_file"], requiresApproval: true },