jinzd-ai-cli 0.4.306 → 0.4.308

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 (95) hide show
  1. package/README.md +1 -0
  2. package/THIRD_PARTY_NOTICES.md +3 -0
  3. package/dist/{agent-client-6GX6QQDU.js → agent-client-TIEAYZXV.js} +4 -1
  4. package/dist/{auth-PE3Z5OHS.js → auth-QTQNZ6IX.js} +2 -2
  5. package/dist/{batch-JLGWQ7HU.js → batch-S7XLEEOH.js} +3 -3
  6. package/dist/{chat-index-2D5GK2DB.js → chat-index-7CLR4CBG.js} +4 -3
  7. package/dist/{chat-index-DGUOUUQW.js → chat-index-RLVLQRRC.js} +3 -2
  8. package/dist/{chunk-JDT6UG6R.js → chunk-2DYQDLLF.js} +1 -1
  9. package/dist/{chunk-T6WQEQI4.js → chunk-2MNUMLN4.js} +1 -1
  10. package/dist/chunk-2XMUCC42.js +67 -0
  11. package/dist/{chunk-7SSCSSC6.js → chunk-3537CQ5M.js} +2 -2
  12. package/dist/{chunk-OAXLMF3U.js → chunk-3YVNDYOY.js} +1 -1
  13. package/dist/{chunk-RYQBBEMM.js → chunk-4PR5AVJ5.js} +115 -19
  14. package/dist/{chunk-GHWZVPUV.js → chunk-6JHACXB3.js} +7 -5
  15. package/dist/{chunk-WZWRA7R2.js → chunk-7OHXFTZO.js} +2 -2
  16. package/dist/{chunk-IUVDZVZ4.js → chunk-AXTLYOA7.js} +24 -17
  17. package/dist/{chunk-XJOK5353.js → chunk-B3MPYI4E.js} +2 -2
  18. package/dist/{chunk-3RDJEYF7.js → chunk-BGZN4K7Q.js} +4 -4
  19. package/dist/chunk-BMLQPSHP.js +69 -0
  20. package/dist/{chunk-NRSYBKWA.js → chunk-C7CKDFJS.js} +1 -1
  21. package/dist/{chunk-JFNBJHR6.js → chunk-E2PNXNKI.js} +4 -4
  22. package/dist/chunk-FVXNEZEW.js +161 -0
  23. package/dist/{chunk-YKJO3SI4.js → chunk-G6DVGQ4I.js} +1 -1
  24. package/dist/{chunk-AYLK4EGA.js → chunk-HC33C4U3.js} +18 -18
  25. package/dist/chunk-JJFJWD35.js +162 -0
  26. package/dist/{chunk-W6PLV3EY.js → chunk-JJNUASMJ.js} +263 -55
  27. package/dist/{chunk-NYNSYLFY.js → chunk-JL44HGZC.js} +1 -1
  28. package/dist/{chunk-NUJTNH76.js → chunk-LBWKMTT3.js} +1 -1
  29. package/dist/{chunk-LAP7TPPK.js → chunk-LR3TGZWQ.js} +2 -2
  30. package/dist/{chunk-WQ3OMEC4.js → chunk-M6HLYG33.js} +1 -1
  31. package/dist/{chunk-I4GQRMFV.js → chunk-M6YV5KSS.js} +6 -4
  32. package/dist/{chunk-K5SDDTN7.js → chunk-NLK45GUS.js} +1 -1
  33. package/dist/{chunk-KBDKKHXB.js → chunk-O6CGOVD4.js} +2 -2
  34. package/dist/chunk-QMFMST2T.js +39 -0
  35. package/dist/{chunk-MI4SFZGX.js → chunk-RMEJR3AW.js} +5 -2
  36. package/dist/{chunk-UFW4S3RU.js → chunk-RVMCY6DP.js} +1 -1
  37. package/dist/{chunk-LF67HWWR.js → chunk-RWRZVQCB.js} +1 -1
  38. package/dist/chunk-SXIJLSVF.js +41 -0
  39. package/dist/chunk-TDQW2YHK.js +65 -0
  40. package/dist/{chunk-VLDB2SVN.js → chunk-TPTFV3Y6.js} +383 -513
  41. package/dist/{chunk-45UVJXFQ.js → chunk-W2UOVGRC.js} +1 -1
  42. package/dist/{chunk-VXEF2SWG.js → chunk-WT252ZDV.js} +2 -2
  43. package/dist/{chunk-NLB7JZSO.js → chunk-WVEIUSXE.js} +246 -55
  44. package/dist/{chunk-OC2XHG5L.js → chunk-YBZM7PHI.js} +4 -4
  45. package/dist/{ci-6PHEL3C7.js → ci-5HONSUAS.js} +7 -7
  46. package/dist/{ci-format-AZYUP6BL.js → ci-format-URUH3ZZ5.js} +2 -2
  47. package/dist/{constants-CPNWIUGZ.js → constants-BLDBI7IG.js} +1 -1
  48. package/dist/{constants-AXBD2EEB.js → constants-FG27DQMS.js} +1 -1
  49. package/dist/doctor-cli-QQLMNJN7.js +18 -0
  50. package/dist/electron-server.js +1003 -829
  51. package/dist/file-checkpoint-64A2AXHE.js +7 -0
  52. package/dist/{file-checkpoint-NKBHGC7L.js → file-checkpoint-VVPUJNCI.js} +2 -1
  53. package/dist/{hub-DDYAWLF2.js → hub-GGFW3DCC.js} +3 -3
  54. package/dist/{hub-server-CBKCP5VD.js → hub-server-JLC7CNP7.js} +55 -17
  55. package/dist/index.js +42 -40
  56. package/dist/{indexer-P3FPRTPJ.js → indexer-GKIWSSRP.js} +3 -3
  57. package/dist/indexer-VRXDZ74A.js +10 -0
  58. package/dist/{persist-S77WRVLD.js → persist-ZZXRWFBX.js} +4 -4
  59. package/dist/{persistent-memory-XZISXRWT.js → persistent-memory-UNLCXUWH.js} +3 -3
  60. package/dist/{persistent-memory-6XYYOMSC.js → persistent-memory-YYWIN6BG.js} +3 -3
  61. package/dist/{pr-UY4ZCZNI.js → pr-YCFGPFI7.js} +7 -7
  62. package/dist/{project-trust-MUG325AW.js → project-trust-6CJB5ZMX.js} +1 -1
  63. package/dist/{project-trust-NKYHL3VZ.js → project-trust-KGG2BWKC.js} +1 -1
  64. package/dist/run-tests-GHY4BYDE.js +13 -0
  65. package/dist/run-tests-KRNMFXAN.js +12 -0
  66. package/dist/{semantic-7GM5M4MU.js → semantic-N227CLSC.js} +4 -4
  67. package/dist/{semantic-QOBAXEQ4.js → semantic-TRQAZSRY.js} +4 -4
  68. package/dist/{server-6IC33CJK.js → server-73UBHSFV.js} +16 -14
  69. package/dist/{server-DBHLVQVP.js → server-QRAWSG4Z.js} +231 -103
  70. package/dist/{store-SIKHCUYJ.js → store-2N6PPPBP.js} +2 -2
  71. package/dist/{store-CMSSWZF7.js → store-76VVFDXM.js} +2 -2
  72. package/dist/{task-orchestrator-RDDETDW2.js → task-orchestrator-GI3RGLZI.js} +15 -14
  73. package/dist/{usage-D473ZVIP.js → usage-GWTIGZF5.js} +4 -4
  74. package/dist/{vector-store-JAXWDZYW.js → vector-store-T2DVUMY4.js} +2 -2
  75. package/dist/{vector-store-PWIOQFTP.js → vector-store-V6UGEYWK.js} +2 -2
  76. package/dist/web/client/app.js +7 -0
  77. package/dist/web/client/app.js.br +0 -0
  78. package/dist/web/client/app.js.gz +0 -0
  79. package/dist/web/client/util.js +14 -23
  80. package/dist/web/client/util.js.br +0 -0
  81. package/dist/web/client/util.js.gz +0 -0
  82. package/dist/web/client/vendor/DOMPurify-LICENSE.txt +202 -0
  83. package/dist/web/client/vendor/purify.es.mjs +2722 -0
  84. package/dist/web/client/vendor/purify.es.mjs.br +0 -0
  85. package/dist/web/client/vendor/purify.es.mjs.gz +0 -0
  86. package/package.json +4 -2
  87. package/dist/chunk-4BKXL7SM.js +0 -98
  88. package/dist/chunk-6BUTA5VW.js +0 -38
  89. package/dist/chunk-IW3Q7AE5.js +0 -40
  90. package/dist/chunk-SKET65WZ.js +0 -96
  91. package/dist/doctor-cli-DDHHDMMJ.js +0 -18
  92. package/dist/file-checkpoint-CGH6OJVI.js +0 -6
  93. package/dist/indexer-FPKY7LCT.js +0 -10
  94. package/dist/run-tests-BAMLVLO6.js +0 -10
  95. package/dist/run-tests-UEE3OMLS.js +0 -9
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  loadChatIndex,
4
4
  searchChatMemory
5
- } from "./chunk-GHWZVPUV.js";
5
+ } from "./chunk-6JHACXB3.js";
6
6
  import {
7
7
  addMemoryEntry,
8
8
  findSimilarMemory,
@@ -11,17 +11,17 @@ import {
11
11
  searchPersistentMemories,
12
12
  touchMemoryReferences,
13
13
  updateMemoryEntry
14
- } from "./chunk-LAP7TPPK.js";
14
+ } from "./chunk-LR3TGZWQ.js";
15
15
  import {
16
16
  indexProject
17
- } from "./chunk-LF67HWWR.js";
17
+ } from "./chunk-RWRZVQCB.js";
18
18
  import {
19
19
  hasSemanticIndex,
20
20
  semanticSearch
21
- } from "./chunk-XJOK5353.js";
21
+ } from "./chunk-B3MPYI4E.js";
22
22
  import {
23
23
  loadIndex
24
- } from "./chunk-WQ3OMEC4.js";
24
+ } from "./chunk-M6HLYG33.js";
25
25
  import {
26
26
  theme
27
27
  } from "./chunk-5C76XDDF.js";
@@ -30,13 +30,13 @@ import {
30
30
  runHook,
31
31
  runLifecycleHooks,
32
32
  runTool
33
- } from "./chunk-7SSCSSC6.js";
33
+ } from "./chunk-3537CQ5M.js";
34
34
  import {
35
35
  buildTruncatedArgumentsError,
36
36
  getDangerLevel,
37
37
  isFileWriteTool,
38
38
  runLeanAgentLoop
39
- } from "./chunk-MI4SFZGX.js";
39
+ } from "./chunk-RMEJR3AW.js";
40
40
  import {
41
41
  EnvLoader,
42
42
  NetworkError,
@@ -44,13 +44,26 @@ import {
44
44
  } from "./chunk-TU3L3PW7.js";
45
45
  import {
46
46
  atomicWriteFileSync
47
- } from "./chunk-IW3Q7AE5.js";
47
+ } from "./chunk-BMLQPSHP.js";
48
48
  import {
49
- fileCheckpoints
50
- } from "./chunk-4BKXL7SM.js";
49
+ MAX_SNAPSHOT_TOTAL_BYTES,
50
+ assertReadablePath,
51
+ canonicalPath,
52
+ fileCheckpoints,
53
+ isCredentialVault,
54
+ readSnapshot
55
+ } from "./chunk-JJFJWD35.js";
51
56
  import {
52
- runTestsTool
53
- } from "./chunk-W6PLV3EY.js";
57
+ IS_WINDOWS,
58
+ WIN_UTF8_PREAMBLE,
59
+ currentAbortSignal,
60
+ decodeWindowsBuffer,
61
+ killProcessTree,
62
+ looksLikeGbkMojibake,
63
+ recoverGbkMojibake,
64
+ runTestsTool,
65
+ shellInvocation
66
+ } from "./chunk-JJNUASMJ.js";
54
67
  import {
55
68
  CONFIG_DIR_NAME,
56
69
  DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
@@ -59,170 +72,25 @@ import {
59
72
  SUBAGENT_ALLOWED_TOOLS,
60
73
  SUBAGENT_DEFAULT_MAX_ROUNDS,
61
74
  SUBAGENT_MAX_ROUNDS_LIMIT
62
- } from "./chunk-T6WQEQI4.js";
75
+ } from "./chunk-2MNUMLN4.js";
76
+ import {
77
+ getCurrentSessionKey,
78
+ getExecutionConfigDir,
79
+ runWithSessionKey,
80
+ scopedState
81
+ } from "./chunk-SXIJLSVF.js";
63
82
 
64
83
  // src/tools/shell-pool.ts
65
- import { spawn as spawn2 } from "child_process";
66
- import { platform as platform2 } from "os";
67
-
68
- // src/tools/win-shell.ts
69
- import { platform } from "os";
70
84
  import { spawn } from "child_process";
71
- var IS_WINDOWS = platform() === "win32";
72
- var WIN_UTF8_PREAMBLE = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; $OutputEncoding = [System.Text.Encoding]::UTF8; ";
73
- function shellInvocation(command) {
74
- if (IS_WINDOWS) {
75
- return {
76
- shell: "powershell.exe",
77
- args: ["-NoProfile", "-NonInteractive", "-Command", WIN_UTF8_PREAMBLE + command]
78
- };
79
- }
80
- return { shell: process.env["SHELL"] ?? "/bin/bash", args: ["-c", command] };
81
- }
82
- function decodeWindowsBuffer(buf) {
83
- const utf8 = buf.toString("utf-8");
84
- if (!utf8.includes("\uFFFD")) return utf8;
85
- try {
86
- return new TextDecoder("gbk").decode(buf);
87
- } catch {
88
- return utf8;
89
- }
90
- }
91
- var GBK_MOJIBAKE_FINGERPRINTS = [
92
- "\uFFFD",
93
- "\u9215",
94
- "\u951B",
95
- "\u9983",
96
- "\u9241",
97
- "\u6D93",
98
- "\u935B",
99
- "\u9428",
100
- "\u7EFE",
101
- "\u95AB",
102
- "\u9593",
103
- "\u95BF",
104
- "\u68E3",
105
- "\u95B4",
106
- "\u5A11",
107
- "\u95B8",
108
- "\u95BB",
109
- "\u7F01",
110
- "\u95C1"
111
- ];
112
- var gbkReverse = null;
113
- function gbkReverseMap() {
114
- if (gbkReverse) return gbkReverse;
115
- const map = /* @__PURE__ */ new Map();
116
- try {
117
- const dec = new TextDecoder("gbk");
118
- for (let hi = 129; hi <= 254; hi++) {
119
- for (let lo = 64; lo <= 254; lo++) {
120
- if (lo === 127) continue;
121
- const ch = dec.decode(new Uint8Array([hi, lo]));
122
- if (ch.length === 1 && ch !== "\uFFFD" && !map.has(ch)) map.set(ch, [hi, lo]);
123
- }
124
- }
125
- } catch {
126
- }
127
- gbkReverse = map;
128
- return map;
129
- }
130
- function decodeRunAsChinese(bytes) {
131
- for (let start = 0; start <= 2 && start < bytes.length; start++) {
132
- for (let end = 0; end <= 2 && bytes.length - end > start; end++) {
133
- const slice = bytes.slice(start, bytes.length - end);
134
- if (slice.length < 6) continue;
135
- try {
136
- const out = new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(slice));
137
- if (/[一-鿿]/.test(out)) return out;
138
- } catch {
139
- }
140
- }
141
- }
142
- return null;
143
- }
144
- function recoverGbkMojibake(text) {
145
- if (!text) return null;
146
- const map = gbkReverseMap();
147
- if (map.size === 0) return null;
148
- let run = [];
149
- let best = null;
150
- const flush = () => {
151
- if (run.length >= 4) {
152
- const bytes = [];
153
- for (const p of run) bytes.push(p[0], p[1]);
154
- const out = decodeRunAsChinese(bytes);
155
- if (out && (!best || out.length > best.length)) best = out;
156
- }
157
- run = [];
158
- };
159
- for (const ch of text) {
160
- const pair = ch.codePointAt(0) < 128 ? void 0 : map.get(ch);
161
- if (pair) run.push(pair);
162
- else flush();
163
- }
164
- flush();
165
- return best;
166
- }
167
- function looksLikeGbkMojibake(text) {
168
- if (recoverGbkMojibake(text) !== null) return true;
169
- let hits = 0;
170
- for (const ch of GBK_MOJIBAKE_FINGERPRINTS) {
171
- if (text.includes(ch) && ++hits >= 2) return true;
172
- }
173
- return false;
174
- }
175
- function killProcessTree(proc) {
176
- if (proc.killed || proc.exitCode !== null) return;
177
- if (IS_WINDOWS && proc.pid) {
178
- try {
179
- const tk = spawn("taskkill", ["/PID", String(proc.pid), "/T", "/F"], {
180
- windowsHide: true,
181
- stdio: "ignore"
182
- });
183
- tk.on("exit", () => {
184
- try {
185
- proc.kill();
186
- } catch {
187
- }
188
- });
189
- tk.on("error", () => {
190
- try {
191
- proc.kill();
192
- } catch {
193
- }
194
- });
195
- } catch {
196
- try {
197
- proc.kill();
198
- } catch {
199
- }
200
- }
201
- return;
202
- }
203
- try {
204
- proc.kill("SIGTERM");
205
- } catch {
206
- }
207
- setTimeout(() => {
208
- if (!proc.killed && proc.exitCode === null) {
209
- try {
210
- proc.kill("SIGKILL");
211
- } catch {
212
- }
213
- }
214
- }, 500).unref();
215
- }
216
-
217
- // src/tools/shell-pool.ts
218
- var IS_WINDOWS2 = platform2() === "win32";
85
+ import { platform } from "os";
86
+ var IS_WINDOWS2 = platform() === "win32";
219
87
  var SHELL = IS_WINDOWS2 ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
220
88
  var pool = /* @__PURE__ */ new Map();
221
89
  var IDLE_EVICT_MS = 5 * 6e4;
222
90
  var evictionTimer = null;
223
91
  function spawnShell(cwd) {
224
92
  const shellArgs = IS_WINDOWS2 ? ["-NoProfile", "-NonInteractive", "-Command", "-"] : ["-s"];
225
- const proc = spawn2(SHELL, shellArgs, {
93
+ const proc = spawn(SHELL, shellArgs, {
226
94
  cwd,
227
95
  windowsHide: true,
228
96
  stdio: ["pipe", "pipe", "pipe"]
@@ -433,16 +301,22 @@ function isToolCapableProvider(provider) {
433
301
  }
434
302
 
435
303
  // src/tools/builtin/bash.ts
436
- import { spawn as spawn3 } from "child_process";
304
+ import { spawn as spawn2 } from "child_process";
437
305
  import { existsSync as existsSync2, readdirSync, statSync } from "fs";
438
- import { platform as platform3 } from "os";
306
+ import { platform as platform2 } from "os";
439
307
  import { resolve } from "path";
440
308
 
441
309
  // src/tools/undo-stack.ts
442
- import { readFileSync, writeFileSync, unlinkSync, rmdirSync, existsSync } from "fs";
310
+ import { writeFileSync, unlinkSync, rmdirSync, existsSync } from "fs";
443
311
  var MAX_UNDO_DEPTH = 20;
444
312
  var UndoStack = class {
445
- stack = [];
313
+ stateKey = /* @__PURE__ */ Symbol("stack");
314
+ get stack() {
315
+ return scopedState(this.stateKey, () => ({ items: [] })).items;
316
+ }
317
+ set stack(items) {
318
+ scopedState(this.stateKey, () => ({ items: [] })).items = items;
319
+ }
446
320
  /**
447
321
  * 在执行文件写入操作之前调用,保存当前状态。
448
322
  * @param filePath 将要被写入的文件路径
@@ -451,11 +325,7 @@ var UndoStack = class {
451
325
  push(filePath, description) {
452
326
  let previousContent = null;
453
327
  if (existsSync(filePath)) {
454
- try {
455
- previousContent = readFileSync(filePath, "utf-8");
456
- } catch {
457
- return;
458
- }
328
+ previousContent = readSnapshot(filePath);
459
329
  }
460
330
  this.pushEntry({
461
331
  filePath,
@@ -492,7 +362,7 @@ var UndoStack = class {
492
362
  /** 内部统一入栈方法,含溢出裁剪 */
493
363
  pushEntry(entry) {
494
364
  this.stack.push(entry);
495
- if (this.stack.length > MAX_UNDO_DEPTH) {
365
+ while (this.stack.length > MAX_UNDO_DEPTH || this.stack.reduce((n, e) => n + Buffer.byteLength(e.previousContent ?? ""), 0) > MAX_SNAPSHOT_TOTAL_BYTES) {
496
366
  this.stack.shift();
497
367
  }
498
368
  }
@@ -553,42 +423,8 @@ var UndoStack = class {
553
423
  };
554
424
  var undoStack = new UndoStack();
555
425
 
556
- // src/core/interrupt.ts
557
- var controller = new AbortController();
558
- var interrupted = false;
559
- function currentAbortSignal() {
560
- return controller.signal;
561
- }
562
- function isInterrupted() {
563
- return interrupted;
564
- }
565
- function requestInterrupt() {
566
- interrupted = true;
567
- if (!controller.signal.aborted) {
568
- controller.abort(new DOMException("User interrupted (Ctrl+C)", "AbortError"));
569
- }
570
- }
571
- function resetInterrupt() {
572
- controller = new AbortController();
573
- interrupted = false;
574
- }
575
-
576
- // src/tools/session-context.ts
577
- import { AsyncLocalStorage } from "async_hooks";
578
- var als = new AsyncLocalStorage();
579
- var DEFAULT_SESSION_KEY = "__default__";
580
- function getCurrentSessionKey() {
581
- const ctx = als.getStore();
582
- if (!ctx || !ctx.sessionKey) return DEFAULT_SESSION_KEY;
583
- return ctx.sessionKey;
584
- }
585
- function runWithSessionKey(sessionKey, fn) {
586
- const key = sessionKey && sessionKey.length > 0 ? sessionKey : DEFAULT_SESSION_KEY;
587
- return als.run({ sessionKey: key }, fn);
588
- }
589
-
590
426
  // src/tools/builtin/bash.ts
591
- var IS_WINDOWS3 = platform3() === "win32";
427
+ var IS_WINDOWS3 = platform2() === "win32";
592
428
  var SHELL2 = IS_WINDOWS3 ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
593
429
  var cwdBySession = /* @__PURE__ */ new Map();
594
430
  function getCwd() {
@@ -618,7 +454,8 @@ Important rules:
618
454
  4. To delete directories, use Remove-Item -Recurse (the system will automatically optimize to a more reliable method).
619
455
  5. IMPORTANT: On Windows, "curl" is an alias for Invoke-WebRequest and does NOT support curl flags like -s, -X, -H. Use Invoke-RestMethod instead for HTTP requests. Example: Invoke-RestMethod -Uri "http://localhost:3000/api/health" -Method Get
620
456
  6. NEVER run long-running / never-exiting commands through bash \u2014 they block until timeout. This includes starting an Android emulator (emulator -avd X), streaming logs (adb logcat), and dev servers (node server.js, npm run dev, metro / react-native start). Use the 'task_create' tool to run these in the background, then poll status with short bash calls (e.g. adb shell getprop sys.boot_completed).
621
- 7. CRITICAL \u2014 this is PowerShell, NOT bash: \\ is NOT an escape character. Writing \\" inside a double-quoted string CLOSES the string and silently splits your command into fragments \u2014 it often still exits 0 with no output, so it looks like it worked when it did not. To escape a quote use \`" or "". For anything with nested quoting (especially ssh + psql -c "SELECT ..."), do NOT try to escape your way out: write the payload to a file with the 'write_file' tool, 'scp' it to the host, and run it with a single level of quoting (e.g. ssh host "psql -d db -f /tmp/x.sql").` : `Execute commands in ${SHELL2}.
457
+ 7. CRITICAL \u2014 this is PowerShell, NOT bash: \\ is NOT an escape character. Writing \\" inside a double-quoted string CLOSES the string and silently splits your command into fragments \u2014 it often still exits 0 with no output, so it looks like it worked when it did not. To escape a quote use \`" or "". For anything with nested quoting (especially ssh + psql -c "SELECT ..."), do NOT try to escape your way out: write the payload to a file with the 'write_file' tool, 'scp' it to the host, and run it with a single level of quoting (e.g. ssh host "psql -d db -f /tmp/x.sql").
458
+ 8. CRITICAL \u2014 running a program by its full path: a quoted string at the START of a statement is parsed as a string EXPRESSION, not a command. "C:\\path\\python.exe" -c "print(1)" is a SYNTAX ERROR. Put the call operator '&' in front: & "C:\\path\\python.exe" -c "print(1)" \u2014 or $py = "C:\\path\\python.exe"; & $py -c "print(1)". This applies to any quoted path used as a command, anywhere a statement starts (including after ';').` : `Execute commands in ${SHELL2}.
622
459
  Important rules:
623
460
  1. Each bash call runs in an independent subprocess; cd commands do not persist. To run in a specific directory, use the cwd parameter, or combine commands: e.g. "cd mydir && ls" or "mkdir -p mydir && touch mydir/file.txt".
624
461
  2. If a command fails (returns an error or non-zero exit code), stop immediately, report the error to the user, and do not retry the same or similar commands.
@@ -873,6 +710,116 @@ function detectPowerShellBackslashQuote(command) {
873
710
  }
874
711
  return false;
875
712
  }
713
+ var PS_OPERATOR_WORDS = /* @__PURE__ */ new Set([
714
+ "eq",
715
+ "ne",
716
+ "gt",
717
+ "ge",
718
+ "lt",
719
+ "le",
720
+ "ceq",
721
+ "cne",
722
+ "cgt",
723
+ "cge",
724
+ "clt",
725
+ "cle",
726
+ "ieq",
727
+ "ine",
728
+ "igt",
729
+ "ige",
730
+ "ilt",
731
+ "ile",
732
+ "like",
733
+ "notlike",
734
+ "match",
735
+ "notmatch",
736
+ "clike",
737
+ "cnotlike",
738
+ "cmatch",
739
+ "cnotmatch",
740
+ "ilike",
741
+ "inotlike",
742
+ "imatch",
743
+ "inotmatch",
744
+ "contains",
745
+ "notcontains",
746
+ "ccontains",
747
+ "cnotcontains",
748
+ "icontains",
749
+ "inotcontains",
750
+ "in",
751
+ "notin",
752
+ "cin",
753
+ "cnotin",
754
+ "iin",
755
+ "inotin",
756
+ "is",
757
+ "isnot",
758
+ "as",
759
+ "and",
760
+ "or",
761
+ "not",
762
+ "xor",
763
+ "band",
764
+ "bor",
765
+ "bnot",
766
+ "bxor",
767
+ "shl",
768
+ "shr",
769
+ "join",
770
+ "split",
771
+ "replace",
772
+ "csplit",
773
+ "creplace",
774
+ "isplit",
775
+ "ireplace",
776
+ "f"
777
+ ]);
778
+ function detectMissingCallOperator(command) {
779
+ if (!IS_WINDOWS3) return false;
780
+ let atStatementStart = true;
781
+ let afterCallOperator = false;
782
+ for (let i = 0; i < command.length; i++) {
783
+ const ch = command[i];
784
+ if (ch === " " || ch === " ") continue;
785
+ if (ch === ";" || ch === "\n" || ch === "{" || ch === "(" || ch === "|" || ch === "&") {
786
+ if (ch === "&") {
787
+ const next = command.slice(i + 1).replace(/^[ \t]+/, "");
788
+ afterCallOperator = !next.startsWith("&");
789
+ } else {
790
+ afterCallOperator = false;
791
+ }
792
+ atStatementStart = true;
793
+ continue;
794
+ }
795
+ if (ch === "." && atStatementStart) {
796
+ afterCallOperator = true;
797
+ continue;
798
+ }
799
+ if ((ch === '"' || ch === "'") && atStatementStart && !afterCallOperator) {
800
+ const quote = ch;
801
+ let j = i + 1;
802
+ while (j < command.length && command[j] !== quote) j++;
803
+ if (j >= command.length) return false;
804
+ const rest = command.slice(j + 1);
805
+ const m = /^[ \t]+(\S+)/.exec(rest);
806
+ if (m) {
807
+ const token = m[1];
808
+ if (/^[-]/.test(token)) {
809
+ const word = token.slice(1).toLowerCase();
810
+ if (PS_OPERATOR_WORDS.has(word)) return false;
811
+ return true;
812
+ }
813
+ if (/^[+*%=<>|;)}\]]/.test(token)) return false;
814
+ return true;
815
+ }
816
+ return false;
817
+ }
818
+ atStatementStart = false;
819
+ afterCallOperator = false;
820
+ }
821
+ return false;
822
+ }
876
823
  var BACKSLASH_QUOTE_SILENT_WARNING = `
877
824
 
878
825
  \u26A0 WARNING: this command contains \\" inside a double-quoted string, but PowerShell does NOT treat \\ as an escape \u2014 that quote CLOSED the string and the command was split into fragments. It exited 0 with no output because the outer program (e.g. ssh) still ran, but it very likely did NOT run what you intended. Do NOT assume this succeeded.
@@ -953,6 +900,11 @@ function buildErrorHint(command, stderr) {
953
900
  `Hint: PowerShell does NOT treat \\ as an escape character \u2014 the \\" in this command closed the double-quoted string and split the command into fragments. Escape quotes with \`" or "" instead. Better: avoid nested quoting entirely \u2014 write the payload to a file with 'write_file', 'scp' it over, and run it with one level of quoting (e.g. ssh host "psql -d <db> -f /tmp/x.sql").`
954
901
  );
955
902
  }
903
+ if (detectMissingCallOperator(command) && /(ParserError|UnexpectedToken|Unexpected token|意外的标记|运算符后面提供|必须在)/i.test(stderr)) {
904
+ hints.push(
905
+ `Hint: in PowerShell a quoted string at the START of a statement is parsed as a string EXPRESSION, not as a command \u2014 so "C:\\path\\python.exe" -c "..." is a syntax error, not a quoting problem. Do NOT retry by removing the quotes, dropping the argument, or re-quoting it differently; all of those fail the same way. Fix: put the call operator '&' in front \u2014 \`& "C:\\path\\python.exe" -c "..."\` \u2014 or assign the path to a variable first (\`$py = "C:\\path\\python.exe"; & $py -c "..."\`).`
906
+ );
907
+ }
956
908
  return hints.length > 0 ? hints.map((h) => `\u{1F4A1} ${h}`).join("\n\n") : null;
957
909
  }
958
910
  function detectBlockingCommand(command) {
@@ -1055,7 +1007,7 @@ function pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd) {
1055
1007
  function runShell(command, opts) {
1056
1008
  return new Promise((resolvePromise) => {
1057
1009
  const shellArgs = IS_WINDOWS3 ? ["-NoProfile", "-NonInteractive", "-Command", WIN_UTF8_PREAMBLE + command] : ["-c", command];
1058
- const child = spawn3(SHELL2, shellArgs, {
1010
+ const child = spawn2(SHELL2, shellArgs, {
1059
1011
  cwd: opts.cwd,
1060
1012
  env: opts.env,
1061
1013
  windowsHide: true,
@@ -1134,16 +1086,16 @@ function updateCwdFromCommand(command, baseCwd) {
1134
1086
  }
1135
1087
 
1136
1088
  // src/tools/builtin/read-file.ts
1137
- import { readFileSync as readFileSync2, existsSync as existsSync3, statSync as statSync2, readdirSync as readdirSync2 } from "fs";
1089
+ import { readFileSync, existsSync as existsSync3, statSync as statSync2, readdirSync as readdirSync2 } from "fs";
1138
1090
  import { execFileSync } from "child_process";
1139
1091
  import { extname, resolve as resolve2, basename, sep, dirname } from "path";
1140
1092
  import { homedir } from "os";
1141
1093
 
1142
1094
  // src/tools/builtin/notebook-utils.ts
1143
- function cellSourceToString(source) {
1144
- if (!source) return "";
1145
- if (Array.isArray(source)) return source.join("");
1146
- return source;
1095
+ function cellSourceToString(source2) {
1096
+ if (!source2) return "";
1097
+ if (Array.isArray(source2)) return source2.join("");
1098
+ return source2;
1147
1099
  }
1148
1100
  function stringToCellSource(content) {
1149
1101
  if (content.length === 0) return [];
@@ -1198,16 +1150,16 @@ function renderNotebookAsText(nb, filePath) {
1198
1150
  nb.cells.forEach((cell, idx) => {
1199
1151
  const cellNum = idx + 1;
1200
1152
  const type = cell.cell_type;
1201
- const source = cellSourceToString(cell.source);
1153
+ const source2 = cellSourceToString(cell.source);
1202
1154
  if (type === "markdown") {
1203
1155
  lines.push(`## Cell ${cellNum} [markdown]`);
1204
- lines.push(source);
1156
+ lines.push(source2);
1205
1157
  lines.push("");
1206
1158
  } else if (type === "code") {
1207
1159
  const execLabel = cell.execution_count != null ? ` (execution #${cell.execution_count})` : "";
1208
1160
  lines.push(`## Cell ${cellNum} [code${execLabel}]`);
1209
1161
  lines.push("```" + language);
1210
- lines.push(source);
1162
+ lines.push(source2);
1211
1163
  lines.push("```");
1212
1164
  const outputText = extractOutputText(cell.outputs);
1213
1165
  if (outputText) {
@@ -1219,7 +1171,7 @@ function renderNotebookAsText(nb, filePath) {
1219
1171
  lines.push("");
1220
1172
  } else if (type === "raw") {
1221
1173
  lines.push(`## Cell ${cellNum} [raw]`);
1222
- lines.push(source);
1174
+ lines.push(source2);
1223
1175
  lines.push("");
1224
1176
  }
1225
1177
  });
@@ -1244,6 +1196,7 @@ function createCell(cellType, content) {
1244
1196
  // src/tools/builtin/read-file.ts
1245
1197
  var MAX_FILE_BYTES = 10 * 1024 * 1024;
1246
1198
  function getHardRefusal(normalizedPath) {
1199
+ if (isCredentialVault(normalizedPath)) return "[Refused] Credential vault files cannot be read by tools.";
1247
1200
  const home2 = homedir();
1248
1201
  const p = normalizedPath.toLowerCase();
1249
1202
  const base = basename(normalizedPath).toLowerCase();
@@ -1418,7 +1371,7 @@ var readFileTool = {
1418
1371
  const offsetLine = typeof rawOffset === "number" && Number.isFinite(rawOffset) && rawOffset > 0 ? Math.floor(rawOffset) : void 0;
1419
1372
  const limitLines = typeof rawLimit === "number" && Number.isFinite(rawLimit) && rawLimit > 0 ? Math.floor(rawLimit) : void 0;
1420
1373
  if (!filePath) throw new ToolError("read_file", "path is required");
1421
- const normalizedPath = resolve2(filePath);
1374
+ const normalizedPath = canonicalPath(filePath);
1422
1375
  const refusal = getHardRefusal(normalizedPath);
1423
1376
  if (refusal) return refusal;
1424
1377
  if (!existsSync3(normalizedPath)) {
@@ -1485,7 +1438,7 @@ Suggestion: read existing text versions (.md / .txt) in the project, or install
1485
1438
  }
1486
1439
  if (ext === ".ipynb") {
1487
1440
  try {
1488
- const raw = readFileSync2(normalizedPath, "utf-8");
1441
+ const raw = readFileSync(normalizedPath, "utf-8");
1489
1442
  const nb = parseNotebook(raw);
1490
1443
  return renderNotebookAsText(nb, filePath);
1491
1444
  } catch (err) {
@@ -1503,8 +1456,8 @@ Do NOT claim you inspected this image \u2014 you have not seen it.`;
1503
1456
  return `[Binary file: ${filePath} (${ext})]
1504
1457
  This is a binary file and cannot be read as text. If there is a text version (.md / .txt) in the project, please read that instead.`;
1505
1458
  }
1506
- const { readFile: readFile2 } = await import("fs/promises");
1507
- const buf = size > 1048576 ? await readFile2(normalizedPath) : readFileSync2(normalizedPath);
1459
+ const { readFile } = await import("fs/promises");
1460
+ const buf = size > 1048576 ? await readFile(normalizedPath) : readFileSync(normalizedPath);
1508
1461
  if (encoding === "base64") {
1509
1462
  return `[File: ${filePath} | base64]
1510
1463
 
@@ -1542,12 +1495,12 @@ ${content}`;
1542
1495
  };
1543
1496
 
1544
1497
  // src/tools/builtin/write-file.ts
1545
- import { appendFileSync, mkdirSync, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
1498
+ import { appendFileSync, mkdirSync, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
1546
1499
  import { dirname as dirname3 } from "path";
1547
1500
 
1548
1501
  // src/tools/executor.ts
1549
1502
  import chalk2 from "chalk";
1550
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
1503
+ import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
1551
1504
  import { tmpdir } from "os";
1552
1505
 
1553
1506
  // src/core/readline-internal.ts
@@ -1890,7 +1843,8 @@ var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
1890
1843
  "recall_memory"
1891
1844
  ]);
1892
1845
  function normalizePathForPermission(value) {
1893
- return value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
1846
+ const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
1847
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
1894
1848
  }
1895
1849
  function canonicalizeWithNearestParent(value) {
1896
1850
  const missing = [];
@@ -1978,7 +1932,7 @@ function profileHardDecision(profileName, toolName, args, dangerLevel, ctx) {
1978
1932
  return { action: "deny", reason: "file write target is outside workspace/temp roots", profileName, hardBoundary: true };
1979
1933
  }
1980
1934
  }
1981
- if (toolName === "bash" || toolName === "run_interactive" || toolName === "task_create") {
1935
+ if (toolName === "bash" || toolName === "run_interactive" || toolName === "task_create" || toolName === "run_tests") {
1982
1936
  return {
1983
1937
  action: "confirm",
1984
1938
  reason: "shell/process writes cannot be proven to stay inside workspace/temp roots",
@@ -2080,9 +2034,9 @@ function checkNetworkPolicy(toolName, args, policy) {
2080
2034
  if (!policy?.enabled) return null;
2081
2035
  let action = networkToolAction(toolName, policy);
2082
2036
  let reason = "network access requires confirmation";
2083
- if (toolName === "bash") {
2037
+ if (["bash", "run_tests", "run_interactive", "task_create"].includes(toolName)) {
2084
2038
  const command = String(args["command"] ?? "");
2085
- if (!isShellNetworkCommand(command)) return null;
2039
+ if (toolName === "bash" && !isShellNetworkCommand(command) && dangerIsReadOnly(command)) return null;
2086
2040
  action = policy.tools?.shell ?? policy.defaultAction ?? "confirm";
2087
2041
  reason = "shell command may access the network";
2088
2042
  }
@@ -2118,6 +2072,9 @@ function checkNetworkPolicy(toolName, args, policy) {
2118
2072
  if (!permissionAction) return null;
2119
2073
  return { action: permissionAction, reason, profileName: "networkPolicy" };
2120
2074
  }
2075
+ function dangerIsReadOnly(command) {
2076
+ return /^(?:pwd|echo [\w .-]+|git status(?: --short)?)$/.test(command.trim());
2077
+ }
2121
2078
 
2122
2079
  // src/tools/truncate.ts
2123
2080
  var DEFAULT_MAX_TOOL_OUTPUT_CHARS = 12e3;
@@ -2227,8 +2184,7 @@ function truncateOutput(content, toolName, maxChars) {
2227
2184
  }
2228
2185
 
2229
2186
  // src/tools/action-classifier.ts
2230
- import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
2231
- import { isAbsolute as isAbsolute2, join, resolve as resolve4 } from "path";
2187
+ import { isAbsolute as isAbsolute2, resolve as resolve4 } from "path";
2232
2188
  var RECENT_DENIED_LIMIT = 50;
2233
2189
  var recentlyDenied = [];
2234
2190
  function recordRecentlyDeniedAutoAction(call, classification) {
@@ -2248,7 +2204,8 @@ function clearRecentlyDeniedAutoActions() {
2248
2204
  recentlyDenied.splice(0, recentlyDenied.length);
2249
2205
  }
2250
2206
  function normalizePath(value) {
2251
- return value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
2207
+ const normalized = canonicalPath(value).replace(/\\/g, "/").replace(/\/+$/, "");
2208
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
2252
2209
  }
2253
2210
  function isInside(root, target) {
2254
2211
  const absoluteRoot = resolve4(root);
@@ -2261,56 +2218,6 @@ function getPathArg(call) {
2261
2218
  const raw = call.arguments.path ?? call.arguments.filePath ?? call.arguments.outputPath ?? call.arguments.file;
2262
2219
  return typeof raw === "string" && raw.trim() ? raw.trim() : null;
2263
2220
  }
2264
- function splitShellWords(command) {
2265
- const out = [];
2266
- const re = /"([^"]*)"|'([^']*)'|([^\s]+)/g;
2267
- let m;
2268
- while (m = re.exec(command)) out.push(m[1] ?? m[2] ?? m[3] ?? "");
2269
- return out.filter(Boolean);
2270
- }
2271
- function readPackageManifest(root) {
2272
- const p = join(root, "package.json");
2273
- if (!existsSync5(p)) return null;
2274
- try {
2275
- return JSON.parse(readFileSync3(p, "utf-8"));
2276
- } catch {
2277
- return null;
2278
- }
2279
- }
2280
- function manifestHasDependency(root, name) {
2281
- const manifest = readPackageManifest(root);
2282
- if (!manifest) return false;
2283
- for (const key of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
2284
- const deps = manifest[key];
2285
- if (deps && typeof deps === "object" && Object.prototype.hasOwnProperty.call(deps, name)) return true;
2286
- }
2287
- return false;
2288
- }
2289
- function lockfileMentions(root, name) {
2290
- for (const file of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]) {
2291
- const p = join(root, file);
2292
- if (!existsSync5(p)) continue;
2293
- try {
2294
- if (readFileSync3(p, "utf-8").includes(name)) return true;
2295
- } catch {
2296
- }
2297
- }
2298
- return false;
2299
- }
2300
- function getInstallPackages(command) {
2301
- const words = splitShellWords(command);
2302
- const cmd = words[0]?.toLowerCase();
2303
- const sub = words[1]?.toLowerCase();
2304
- if (!cmd || !sub) return [];
2305
- const isInstall = cmd === "npm" && (sub === "install" || sub === "i" || sub === "add") || (cmd === "pnpm" || cmd === "yarn") && (sub === "add" || sub === "install");
2306
- if (!isInstall) return [];
2307
- return words.slice(2).filter((w) => !w.startsWith("-")).filter((w) => w !== "." && w !== "./").map((w) => w.replace(/^@([^/]+)\/([^@]+)(?:@.+)?$/, "@$1/$2").replace(/^([^@]+)@.+$/, "$1"));
2308
- }
2309
- function isPlainGitPush(command) {
2310
- if (!/^\s*git\s+push\b/i.test(command)) return false;
2311
- if (/--force|-f\b|\+[^\s]+/i.test(command)) return false;
2312
- return !/[;&|`$<>\n\r]/.test(command);
2313
- }
2314
2221
  function hasSecretExfiltrationShape(command) {
2315
2222
  return /(?:\.env|config\.json|id_rsa|id_ed25519|AWS_SECRET|AICLI_API_KEY|OPENAI_API_KEY|ANTHROPIC_API_KEY)/i.test(command) && /\b(curl|wget|scp|rsync|nc|netcat|Invoke-WebRequest|Invoke-RestMethod)\b/i.test(command);
2316
2223
  }
@@ -2332,7 +2239,7 @@ var RuleBasedActionClassifier = class {
2332
2239
  }
2333
2240
  return { decision: "confirm", reason: "destructive actions require confirmation", ruleId: "auto.destructive-confirm", confidence: "high" };
2334
2241
  }
2335
- classifyShell(call, dangerLevel, ctx) {
2242
+ classifyShell(call, dangerLevel, _ctx) {
2336
2243
  const command = String(call.arguments.command ?? "");
2337
2244
  if (/\bcurl\b[^\n|;&]*\|\s*(?:sudo\s+)?(?:bash|sh|zsh)\b/i.test(command)) {
2338
2245
  return { decision: "deny", reason: "curl pipe shell execution is blocked in auto mode", ruleId: "deny.curl-pipe-shell", confidence: "high" };
@@ -2358,18 +2265,7 @@ var RuleBasedActionClassifier = class {
2358
2265
  if (/\b(npx|pnpm\s+dlx|yarn\s+dlx)\s+(claude|codex|aider|cursor-agent|gemini)\b/i.test(command)) {
2359
2266
  return { decision: "confirm", reason: "third-party agent loop requires confirmation", ruleId: "confirm.third-party-agent", confidence: "high" };
2360
2267
  }
2361
- if (isPlainGitPush(command)) {
2362
- return { decision: "auto-approve", reason: "plain git push without force", ruleId: "auto.git-push", confidence: "high" };
2363
- }
2364
- const pkgs = getInstallPackages(command);
2365
- if (pkgs.length > 0) {
2366
- const allDeclared = pkgs.every((p) => manifestHasDependency(ctx.workspaceRoot, p) || lockfileMentions(ctx.workspaceRoot, p));
2367
- return allDeclared ? { decision: "auto-approve", reason: "installing dependencies already declared in manifest/lockfile", ruleId: "auto.declared-dependency-install", confidence: "medium" } : { decision: "confirm", reason: "dependency install includes undeclared packages", ruleId: "confirm.undeclared-dependency-install", confidence: "medium" };
2368
- }
2369
- if (/\b(curl|wget|Invoke-WebRequest|Invoke-RestMethod)\b/i.test(command) && !/[>|;&`$]/.test(command)) {
2370
- return { decision: "auto-approve", reason: "read-only HTTP shell command", ruleId: "auto.readonly-http-shell", confidence: "medium" };
2371
- }
2372
- if (dangerLevel === "safe") {
2268
+ if (dangerLevel === "safe" && getDangerLevel(call.name, call.arguments) === "safe") {
2373
2269
  return { decision: "auto-approve", reason: "safe shell action", ruleId: "auto.safe-shell", confidence: "medium" };
2374
2270
  }
2375
2271
  return { decision: "confirm", reason: "shell action is not proven low-risk", ruleId: "confirm.shell-default", confidence: "medium" };
@@ -2838,10 +2734,10 @@ var ToolExecutor = class {
2838
2734
  if (call.name === "write_file") {
2839
2735
  const { path: filePath, content: chunk, append } = resolveWriteFileArgs(call.arguments);
2840
2736
  if (!filePath) return;
2841
- if (existsSync6(filePath)) {
2737
+ if (existsSync5(filePath)) {
2842
2738
  let oldContent;
2843
2739
  try {
2844
- oldContent = readFileSync4(filePath, "utf-8");
2740
+ oldContent = readFileSync2(filePath, "utf-8");
2845
2741
  } catch {
2846
2742
  return;
2847
2743
  }
@@ -2865,7 +2761,7 @@ var ToolExecutor = class {
2865
2761
  }
2866
2762
  } else if (call.name === "edit_file") {
2867
2763
  const filePath = String(call.arguments["path"] ?? "");
2868
- if (!filePath || !existsSync6(filePath)) return;
2764
+ if (!filePath || !existsSync5(filePath)) return;
2869
2765
  const oldStr = call.arguments["old_str"];
2870
2766
  const newStr = call.arguments["new_str"];
2871
2767
  if (oldStr !== void 0) {
@@ -2892,7 +2788,7 @@ var ToolExecutor = class {
2892
2788
  const to = Number(call.arguments["delete_to_line"] ?? from);
2893
2789
  let fileContent;
2894
2790
  try {
2895
- fileContent = readFileSync4(filePath, "utf-8");
2791
+ fileContent = readFileSync2(filePath, "utf-8");
2896
2792
  } catch {
2897
2793
  return;
2898
2794
  }
@@ -2988,7 +2884,12 @@ import { resolve as resolve5, sep as sep2, basename as basename3 } from "path";
2988
2884
  import { homedir as homedir2 } from "os";
2989
2885
  var home = homedir2();
2990
2886
  function norm(p) {
2991
- const abs = resolve5(p);
2887
+ let abs;
2888
+ try {
2889
+ abs = canonicalPath(p);
2890
+ } catch {
2891
+ abs = resolve5(p);
2892
+ }
2992
2893
  return process.platform === "win32" ? abs.toLowerCase() : abs;
2993
2894
  }
2994
2895
  function homeRel(p) {
@@ -3002,6 +2903,7 @@ function homeRel(p) {
3002
2903
  }
3003
2904
  function classifyWritePath(path3) {
3004
2905
  if (!path3) return { sensitive: false };
2906
+ if (isCredentialVault(path3)) return { sensitive: true, reason: "credential vault" };
3005
2907
  const abs = norm(path3);
3006
2908
  const base = basename3(abs);
3007
2909
  const rel = homeRel(path3);
@@ -3084,7 +2986,7 @@ function countTextLines(text) {
3084
2986
 
3085
2987
  // src/tools/builtin/write-file.ts
3086
2988
  function countFileLines(filePath) {
3087
- return countTextLines(readFileSync5(filePath, "utf-8"));
2989
+ return countTextLines(readFileSync3(filePath, "utf-8"));
3088
2990
  }
3089
2991
  var writeFileTool = {
3090
2992
  definition: {
@@ -3148,7 +3050,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
3148
3050
  const mode = appendMode ? "appended" : "written";
3149
3051
  void (async () => {
3150
3052
  try {
3151
- const { updateFile } = await import("./indexer-P3FPRTPJ.js");
3053
+ const { updateFile } = await import("./indexer-GKIWSSRP.js");
3152
3054
  await updateFile(process.cwd(), filePath);
3153
3055
  } catch {
3154
3056
  }
@@ -3167,7 +3069,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
3167
3069
  };
3168
3070
 
3169
3071
  // src/tools/builtin/edit-file.ts
3170
- import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
3072
+ import { readFileSync as readFileSync4, existsSync as existsSync6 } from "fs";
3171
3073
 
3172
3074
  // src/tools/builtin/patch-apply.ts
3173
3075
  function parseUnifiedDiff(patch) {
@@ -3535,7 +3437,7 @@ Note: Path can be absolute or relative to cwd.`,
3535
3437
  const filePath = String(args["path"] ?? args["file"] ?? "");
3536
3438
  const encoding = args["encoding"] ?? "utf-8";
3537
3439
  if (!filePath) throw new ToolError("edit_file", "path is required");
3538
- if (!existsSync7(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
3440
+ if (!existsSync6(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
3539
3441
  const verdict = classifyWritePath(filePath);
3540
3442
  if (verdict.sensitive && subAgentGuard.active) {
3541
3443
  throw new ToolError(
@@ -3543,7 +3445,7 @@ Note: Path can be absolute or relative to cwd.`,
3543
3445
  `Refused: sub-agents cannot edit sensitive paths \u2014 ${verdict.reason}. If this is genuinely needed, ask the parent agent to perform the edit so the user can approve it.`
3544
3446
  );
3545
3447
  }
3546
- const original = readFileSync6(filePath, encoding);
3448
+ const original = readFileSync4(filePath, encoding);
3547
3449
  if (args["patch"] !== void 0) {
3548
3450
  const patchText = String(args["patch"] ?? "");
3549
3451
  const stopOnError = args["stop_on_error"] !== false;
@@ -3720,8 +3622,8 @@ function truncatePreview(str, maxLen = 80) {
3720
3622
  }
3721
3623
 
3722
3624
  // src/tools/builtin/list-dir.ts
3723
- import { readdirSync as readdirSync3, statSync as statSync4, existsSync as existsSync8 } from "fs";
3724
- import { join as join2, basename as basename4 } from "path";
3625
+ import { readdirSync as readdirSync3, statSync as statSync4, existsSync as existsSync7 } from "fs";
3626
+ import { join, basename as basename4 } from "path";
3725
3627
  var listDirTool = {
3726
3628
  definition: {
3727
3629
  name: "list_dir",
@@ -3743,7 +3645,7 @@ var listDirTool = {
3743
3645
  async execute(args) {
3744
3646
  const dirPath = String(args["path"] ?? process.cwd());
3745
3647
  const recursive = Boolean(args["recursive"] ?? false);
3746
- if (!existsSync8(dirPath)) {
3648
+ if (!existsSync7(dirPath)) {
3747
3649
  const targetName = basename4(dirPath).toLowerCase();
3748
3650
  const cwd = process.cwd();
3749
3651
  const suggestions = [];
@@ -3801,11 +3703,11 @@ function listRecursive(basePath, indent, recursive, lines) {
3801
3703
  if (entry.isDirectory()) {
3802
3704
  lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
3803
3705
  if (recursive) {
3804
- listRecursive(join2(basePath, entry.name), indent + " ", true, lines);
3706
+ listRecursive(join(basePath, entry.name), indent + " ", true, lines);
3805
3707
  }
3806
3708
  } else {
3807
3709
  try {
3808
- const stat = statSync4(join2(basePath, entry.name));
3710
+ const stat = statSync4(join(basePath, entry.name));
3809
3711
  const size = formatSize(stat.size);
3810
3712
  lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
3811
3713
  } catch {
@@ -3820,10 +3722,77 @@ function formatSize(bytes) {
3820
3722
  return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
3821
3723
  }
3822
3724
 
3725
+ // src/tools/regex-search.ts
3726
+ import { Worker } from "worker_threads";
3727
+ var source = String.raw`
3728
+ const { parentPort, workerData: d } = require('node:worker_threads');
3729
+ const fs = require('node:fs');
3730
+ let re;
3731
+ try { re = new RegExp(d.pattern, d.ignoreCase ? 'i' : ''); }
3732
+ catch { re = new RegExp(d.pattern.replace(/[.*+?^$()|[\]\\]/g, '\\$&'), d.ignoreCase ? 'i' : ''); }
3733
+ const results = [];
3734
+ for (const f of d.files) {
3735
+ if (results.length >= d.maxResults) break;
3736
+ let fd;
3737
+ try {
3738
+ fd = fs.openSync(f.fullPath, 'r');
3739
+ const buf = Buffer.alloc(1000001);
3740
+ let size = 0;
3741
+ while (size < buf.length) { const n = fs.readSync(fd, buf, size, buf.length - size, null); if (!n) break; size += n; }
3742
+ if (size > 1000000 || buf.subarray(0, Math.min(size,512)).includes(0)) continue;
3743
+ const lines = buf.subarray(0,size).toString('utf8').split('\n');
3744
+ for (let i=0; i<lines.length && results.length<d.maxResults; i++) {
3745
+ if (!re.test(lines[i])) continue;
3746
+ const match = { file:f.relPath.replace(/\\/g,'/'), lineNumber:i+1, lineText:lines[i].slice(0,10000) };
3747
+ if (d.context) {
3748
+ match.contextBefore=[]; match.contextAfter=[];
3749
+ for(let j=Math.max(0,i-d.context);j<i;j++) match.contextBefore.push([j+1,lines[j].slice(0,10000)]);
3750
+ for(let j=i+1;j<=Math.min(lines.length-1,i+d.context);j++) match.contextAfter.push([j+1,lines[j].slice(0,10000)]);
3751
+ }
3752
+ results.push(match);
3753
+ }
3754
+ } catch {} finally { if (fd !== undefined) fs.closeSync(fd); }
3755
+ }
3756
+ parentPort.postMessage(results);
3757
+ `;
3758
+ var active = 0;
3759
+ async function searchInWorker(files, pattern, ignoreCase, context, maxResults) {
3760
+ const signal = currentAbortSignal();
3761
+ if (signal.aborted) throw new Error("Search cancelled");
3762
+ if (active >= 4) throw new Error("Search capacity reached; wait for another search to finish");
3763
+ active++;
3764
+ let worker;
3765
+ try {
3766
+ return await new Promise((resolve7, reject) => {
3767
+ worker = new Worker(source, { eval: true, workerData: { files, pattern, ignoreCase, context, maxResults }, resourceLimits: { maxOldGenerationSizeMb: 64 } });
3768
+ let settled = false;
3769
+ const finish = (error, results) => {
3770
+ if (settled) return;
3771
+ settled = true;
3772
+ clearTimeout(timer);
3773
+ signal.removeEventListener("abort", abort);
3774
+ if (error) reject(error);
3775
+ else resolve7(results ?? []);
3776
+ };
3777
+ const timer = setTimeout(() => finish(new Error("Search exceeded 2 second CPU/time budget; simplify the pattern or narrow the path")), 2e3);
3778
+ const abort = () => finish(new Error("Search cancelled"));
3779
+ signal.addEventListener("abort", abort, { once: true });
3780
+ if (signal.aborted) abort();
3781
+ worker.once("message", (results) => finish(void 0, results));
3782
+ worker.once("error", (error) => finish(error));
3783
+ worker.once("exit", (code) => {
3784
+ if (!settled) finish(new Error(`Search worker exited (${code})`));
3785
+ });
3786
+ });
3787
+ } finally {
3788
+ if (worker) await worker.terminate();
3789
+ active--;
3790
+ }
3791
+ }
3792
+
3823
3793
  // src/tools/builtin/grep-files.ts
3824
- import { readdirSync as readdirSync4, readFileSync as readFileSync7, statSync as statSync5, existsSync as existsSync9 } from "fs";
3825
- import { readFile } from "fs/promises";
3826
- import { join as join3, relative } from "path";
3794
+ import { readdirSync as readdirSync4, statSync as statSync5, existsSync as existsSync8 } from "fs";
3795
+ import { join as join2, relative } from "path";
3827
3796
  var grepFilesTool = {
3828
3797
  definition: {
3829
3798
  name: "grep_files",
@@ -3871,43 +3840,24 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
3871
3840
  const rootPath = String(args["path"] ?? process.cwd());
3872
3841
  const filePattern = args["file_pattern"] ? String(args["file_pattern"]) : void 0;
3873
3842
  const ignoreCase = Boolean(args["ignore_case"] ?? false);
3874
- const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
3875
- const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
3843
+ const contextLines = Math.min(10, Math.max(0, Number(args["context_lines"]) || 0));
3844
+ const maxResults = Math.min(200, Math.max(1, Number(args["max_results"]) || 50));
3876
3845
  if (!pattern) throw new ToolError("grep_files", "pattern is required");
3877
- if (!existsSync9(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
3846
+ if (!existsSync8(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
3878
3847
  const MAX_PATTERN_LENGTH = 1e3;
3879
- if (pattern.length > MAX_PATTERN_LENGTH) {
3848
+ if (pattern.length > MAX_PATTERN_LENGTH || (filePattern?.length ?? 0) > MAX_PATTERN_LENGTH) {
3880
3849
  throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
3881
3850
  }
3882
- let regex;
3883
- try {
3884
- regex = new RegExp(pattern, ignoreCase ? "gi" : "g");
3885
- } catch {
3886
- const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3887
- regex = new RegExp(escaped, ignoreCase ? "gi" : "g");
3888
- }
3889
- const results = [];
3890
- const stat = statSync5(rootPath);
3891
- if (stat.isFile()) {
3892
- searchInFile(rootPath, rootPath, regex, contextLines, maxResults, results);
3893
- } else {
3894
- const filePaths = collectFilePaths(rootPath, filePattern, maxResults);
3895
- const BATCH_SIZE = 16;
3896
- for (let i = 0; i < filePaths.length && results.length < maxResults; i += BATCH_SIZE) {
3897
- const batch = filePaths.slice(i, i + BATCH_SIZE);
3898
- const batchResults = await Promise.all(
3899
- batch.map(
3900
- ({ fullPath, relPath }) => searchInFileAsync(fullPath, relPath, regex, contextLines, maxResults - results.length)
3901
- )
3902
- );
3903
- for (const br of batchResults) {
3904
- for (const r of br) {
3905
- if (results.length >= maxResults) break;
3906
- results.push(r);
3907
- }
3908
- }
3851
+ assertReadablePath(rootPath);
3852
+ const paths = statSync5(rootPath).isFile() ? [{ fullPath: rootPath, relPath: rootPath }] : collectFilePaths(rootPath, filePattern, maxResults);
3853
+ const files = paths.filter((f) => {
3854
+ try {
3855
+ return !isCredentialVault(f.fullPath);
3856
+ } catch {
3857
+ return false;
3909
3858
  }
3910
- }
3859
+ });
3860
+ const results = await searchInWorker(files, pattern, ignoreCase, contextLines, maxResults);
3911
3861
  if (results.length === 0) {
3912
3862
  return `No matches found for pattern: ${pattern}
3913
3863
  Searched in: ${rootPath}${filePattern ? `
@@ -3947,8 +3897,23 @@ File filter: ${filePattern}` : ""}`;
3947
3897
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", ".next", "__pycache__", ".cache", "coverage", ".nyc_output"]);
3948
3898
  var BINARY_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".ico", ".woff", ".woff2", ".ttf", ".eot", ".otf", ".mp3", ".mp4", ".wav", ".zip", ".tar", ".gz", ".rar", ".7z", ".exe", ".dll", ".so", ".dylib", ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx"]);
3949
3899
  function matchesFilePattern(filename, pattern) {
3950
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
3951
- return new RegExp(`^${escaped}$`, "i").test(filename);
3900
+ filename = filename.toLowerCase();
3901
+ pattern = pattern.toLowerCase();
3902
+ let i = 0, j = 0, star = -1, restart = 0;
3903
+ while (i < filename.length) {
3904
+ if (pattern[j] === filename[i]) {
3905
+ i++;
3906
+ j++;
3907
+ } else if (pattern[j] === "*") {
3908
+ star = j++;
3909
+ restart = i;
3910
+ } else if (star >= 0) {
3911
+ j = star + 1;
3912
+ i = ++restart;
3913
+ } else return false;
3914
+ }
3915
+ while (pattern[j] === "*") j++;
3916
+ return j === pattern.length;
3952
3917
  }
3953
3918
  function isBinary(filename) {
3954
3919
  const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
@@ -3956,8 +3921,9 @@ function isBinary(filename) {
3956
3921
  }
3957
3922
  function collectFilePaths(rootPath, filePattern, maxFiles) {
3958
3923
  const paths = [];
3959
- function walk(dirPath) {
3960
- if (paths.length >= maxFiles) return;
3924
+ let visited = 0;
3925
+ function walk(dirPath, depth = 0) {
3926
+ if (paths.length >= maxFiles || depth > 20 || visited >= 2e4) return;
3961
3927
  let entries;
3962
3928
  try {
3963
3929
  entries = readdirSync4(dirPath, { withFileTypes: true });
@@ -3965,14 +3931,14 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
3965
3931
  return;
3966
3932
  }
3967
3933
  for (const entry of entries) {
3968
- if (paths.length >= maxFiles) return;
3934
+ if (paths.length >= maxFiles || ++visited > 2e4) return;
3969
3935
  if (entry.isDirectory()) {
3970
3936
  if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
3971
- walk(join3(dirPath, entry.name));
3937
+ walk(join2(dirPath, entry.name), depth + 1);
3972
3938
  } else if (entry.isFile()) {
3973
3939
  if (isBinary(entry.name)) continue;
3974
3940
  if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
3975
- const fullPath = join3(dirPath, entry.name);
3941
+ const fullPath = join2(dirPath, entry.name);
3976
3942
  try {
3977
3943
  if (statSync5(fullPath).size > 1e6) continue;
3978
3944
  } catch {
@@ -3985,81 +3951,10 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
3985
3951
  walk(rootPath);
3986
3952
  return paths;
3987
3953
  }
3988
- async function searchInFileAsync(fullPath, displayPath, regex, contextLines, maxResults) {
3989
- let content;
3990
- try {
3991
- content = await readFile(fullPath, "utf-8");
3992
- } catch {
3993
- return [];
3994
- }
3995
- const results = [];
3996
- const lines = content.split("\n");
3997
- regex.lastIndex = 0;
3998
- for (let i = 0; i < lines.length && results.length < maxResults; i++) {
3999
- regex.lastIndex = 0;
4000
- if (regex.test(lines[i])) {
4001
- const match = {
4002
- file: displayPath.replace(/\\/g, "/"),
4003
- lineNumber: i + 1,
4004
- lineText: lines[i]
4005
- };
4006
- if (contextLines > 0) {
4007
- match.contextBefore = [];
4008
- for (let j = Math.max(0, i - contextLines); j < i; j++) {
4009
- match.contextBefore.push([j + 1, lines[j]]);
4010
- }
4011
- match.contextAfter = [];
4012
- for (let j = i + 1; j <= Math.min(lines.length - 1, i + contextLines); j++) {
4013
- match.contextAfter.push([j + 1, lines[j]]);
4014
- }
4015
- }
4016
- results.push(match);
4017
- }
4018
- }
4019
- return results;
4020
- }
4021
- function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, results) {
4022
- try {
4023
- const stat = statSync5(fullPath);
4024
- if (stat.size > 1e6) return;
4025
- } catch {
4026
- return;
4027
- }
4028
- let content;
4029
- try {
4030
- content = readFileSync7(fullPath, "utf-8");
4031
- } catch {
4032
- return;
4033
- }
4034
- const lines = content.split("\n");
4035
- regex.lastIndex = 0;
4036
- for (let i = 0; i < lines.length; i++) {
4037
- if (results.length >= maxResults) return;
4038
- regex.lastIndex = 0;
4039
- if (regex.test(lines[i])) {
4040
- const result = {
4041
- file: displayPath,
4042
- lineNumber: i + 1,
4043
- lineText: lines[i].trimEnd()
4044
- };
4045
- if (contextLines > 0) {
4046
- result.contextBefore = [];
4047
- result.contextAfter = [];
4048
- for (let c = Math.max(0, i - contextLines); c < i; c++) {
4049
- result.contextBefore.push([c + 1, lines[c].trimEnd()]);
4050
- }
4051
- for (let c = i + 1; c <= Math.min(lines.length - 1, i + contextLines); c++) {
4052
- result.contextAfter.push([c + 1, lines[c].trimEnd()]);
4053
- }
4054
- }
4055
- results.push(result);
4056
- }
4057
- }
4058
- }
4059
3954
 
4060
3955
  // src/tools/builtin/glob-files.ts
4061
- import { readdirSync as readdirSync5, statSync as statSync6, existsSync as existsSync10 } from "fs";
4062
- import { join as join4, relative as relative2, basename as basename5 } from "path";
3956
+ import { readdirSync as readdirSync5, statSync as statSync6, existsSync as existsSync9 } from "fs";
3957
+ import { join as join3, relative as relative2, basename as basename5 } from "path";
4063
3958
  var globFilesTool = {
4064
3959
  definition: {
4065
3960
  name: "glob_files",
@@ -4092,7 +3987,7 @@ Results sorted by most recent modification time. Automatically skips node_module
4092
3987
  const rootPath = String(args["path"] ?? process.cwd());
4093
3988
  const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
4094
3989
  if (!pattern) throw new ToolError("glob_files", "pattern is required");
4095
- if (!existsSync10(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
3990
+ if (!existsSync9(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
4096
3991
  const regex = globToRegex(pattern);
4097
3992
  const matches = [];
4098
3993
  collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
@@ -4169,7 +4064,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
4169
4064
  }
4170
4065
  for (const entry of entries) {
4171
4066
  if (results.length >= maxResults) break;
4172
- const fullPath = join4(dirPath, entry.name);
4067
+ const fullPath = join3(dirPath, entry.name);
4173
4068
  if (entry.isDirectory()) {
4174
4069
  if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
4175
4070
  collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
@@ -4188,9 +4083,9 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
4188
4083
  }
4189
4084
 
4190
4085
  // src/tools/builtin/run-interactive.ts
4191
- import { spawn as spawn4 } from "child_process";
4192
- import { platform as platform4 } from "os";
4193
- var IS_WINDOWS4 = platform4() === "win32";
4086
+ import { spawn as spawn3 } from "child_process";
4087
+ import { platform as platform3 } from "os";
4088
+ var IS_WINDOWS4 = platform3() === "win32";
4194
4089
  var runInteractiveTool = {
4195
4090
  definition: {
4196
4091
  name: "run_interactive",
@@ -4252,7 +4147,7 @@ var runInteractiveTool = {
4252
4147
  };
4253
4148
  const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
4254
4149
  return new Promise((resolve7) => {
4255
- const child = spawn4(executable, cmdArgs.map(String), {
4150
+ const child = spawn3(executable, cmdArgs.map(String), {
4256
4151
  cwd: process.cwd(),
4257
4152
  env,
4258
4153
  stdio: ["pipe", "pipe", "pipe"]
@@ -4522,8 +4417,8 @@ var webFetchTool = {
4522
4417
  if (e.message.startsWith("Blocked:")) throw e;
4523
4418
  throw new NetworkError(`Invalid URL: "${url}"`);
4524
4419
  }
4525
- const controller2 = new AbortController();
4526
- const timeoutId = setTimeout(() => controller2.abort(), 2e4);
4420
+ const controller = new AbortController();
4421
+ const timeoutId = setTimeout(() => controller.abort(), 2e4);
4527
4422
  let rawHtml;
4528
4423
  let finalUrl;
4529
4424
  let contentType;
@@ -4544,7 +4439,7 @@ var webFetchTool = {
4544
4439
  }
4545
4440
  await resolveAndCheck(parsedHop.hostname);
4546
4441
  const r = await fetch(currentUrl, {
4547
- signal: controller2.signal,
4442
+ signal: controller.signal,
4548
4443
  headers: FETCH_HEADERS,
4549
4444
  redirect: "manual",
4550
4445
  // 手动控制重定向
@@ -4907,8 +4802,8 @@ function parseGoogleResults(html, maxResults) {
4907
4802
  return results;
4908
4803
  }
4909
4804
  async function fetchHtml(url) {
4910
- const controller2 = new AbortController();
4911
- const timer = setTimeout(() => controller2.abort(), REQUEST_TIMEOUT_MS);
4805
+ const controller = new AbortController();
4806
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
4912
4807
  try {
4913
4808
  const res = await fetch(url, {
4914
4809
  method: "GET",
@@ -4917,7 +4812,7 @@ async function fetchHtml(url) {
4917
4812
  "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9",
4918
4813
  "Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8"
4919
4814
  },
4920
- signal: controller2.signal
4815
+ signal: controller.signal
4921
4816
  });
4922
4817
  if (!res.ok) {
4923
4818
  throw new NetworkError(`HTTP ${res.status} ${res.statusText}`, res.status);
@@ -5011,10 +4906,8 @@ Any of these triggers means use save_last_response, NOT write_file:
5011
4906
  };
5012
4907
 
5013
4908
  // src/tools/builtin/save-memory.ts
5014
- import { join as join5 } from "path";
5015
- import { homedir as homedir3 } from "os";
5016
4909
  function getConfigDir() {
5017
- return join5(homedir3(), CONFIG_DIR_NAME);
4910
+ return getExecutionConfigDir();
5018
4911
  }
5019
4912
  function parseSensitivity(value) {
5020
4913
  return value === "low" || value === "medium" || value === "high" ? value : void 0;
@@ -5278,15 +5171,15 @@ var googleSearchTool = {
5278
5171
  url.searchParams.set("cx", cx);
5279
5172
  url.searchParams.set("q", query);
5280
5173
  url.searchParams.set("num", String(numResults));
5281
- const controller2 = new AbortController();
5282
- const timeout = setTimeout(() => controller2.abort(), REQUEST_TIMEOUT_MS2);
5174
+ const controller = new AbortController();
5175
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
5283
5176
  try {
5284
5177
  const response = await fetch(url.toString(), {
5285
5178
  method: "GET",
5286
5179
  headers: {
5287
5180
  "Accept": "application/json"
5288
5181
  },
5289
- signal: controller2.signal
5182
+ signal: controller.signal
5290
5183
  });
5291
5184
  if (!response.ok) {
5292
5185
  const errorBody = await response.text().catch(() => "");
@@ -5363,9 +5256,9 @@ function formatResults2(query, data, _requested) {
5363
5256
  }
5364
5257
 
5365
5258
  // src/agents/agent-config.ts
5366
- import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync8 } from "fs";
5367
- import { join as join6 } from "path";
5368
- import { homedir as homedir4 } from "os";
5259
+ import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync5 } from "fs";
5260
+ import { join as join4 } from "path";
5261
+ import { homedir as homedir3 } from "os";
5369
5262
  var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
5370
5263
  "read_file",
5371
5264
  "list_dir",
@@ -5436,13 +5329,13 @@ function normalizeName(name) {
5436
5329
  function isStringArray(value) {
5437
5330
  return Array.isArray(value) && value.every((item) => typeof item === "string");
5438
5331
  }
5439
- function parseAgentConfig(raw, source, path3) {
5332
+ function parseAgentConfig(raw, source2, path3) {
5440
5333
  if (!raw || typeof raw !== "object") return null;
5441
5334
  const obj = raw;
5442
5335
  if (typeof obj.name !== "string" || obj.name.trim() === "") return null;
5443
5336
  const cfg = {
5444
5337
  name: obj.name.trim(),
5445
- source,
5338
+ source: source2,
5446
5339
  path: path3
5447
5340
  };
5448
5341
  if (typeof obj.description === "string") cfg.description = obj.description;
@@ -5458,15 +5351,15 @@ function parseAgentConfig(raw, source, path3) {
5458
5351
  if (typeof obj.contextPolicy === "string") cfg.contextPolicy = obj.contextPolicy;
5459
5352
  return cfg;
5460
5353
  }
5461
- function loadAgentDir(dir, source) {
5462
- if (!existsSync11(dir)) return [];
5354
+ function loadAgentDir(dir, source2) {
5355
+ if (!existsSync10(dir)) return [];
5463
5356
  const out = [];
5464
5357
  for (const file of readdirSync6(dir)) {
5465
5358
  if (!file.endsWith(".json")) continue;
5466
- const path3 = join6(dir, file);
5359
+ const path3 = join4(dir, file);
5467
5360
  try {
5468
- const parsed = JSON.parse(readFileSync8(path3, "utf-8"));
5469
- const cfg = parseAgentConfig(parsed, source, path3);
5361
+ const parsed = JSON.parse(readFileSync5(path3, "utf-8"));
5362
+ const cfg = parseAgentConfig(parsed, source2, path3);
5470
5363
  if (cfg) out.push(cfg);
5471
5364
  } catch {
5472
5365
  }
@@ -5475,8 +5368,8 @@ function loadAgentDir(dir, source) {
5475
5368
  }
5476
5369
  function getAgentDirs(configDir, cwd = process.cwd()) {
5477
5370
  return {
5478
- user: join6(configDir ?? join6(homedir4(), CONFIG_DIR_NAME), "agents"),
5479
- project: join6(cwd, CONFIG_DIR_NAME, "agents")
5371
+ user: join4(configDir ?? join4(homedir3(), CONFIG_DIR_NAME), "agents"),
5372
+ project: join4(cwd, CONFIG_DIR_NAME, "agents")
5480
5373
  };
5481
5374
  }
5482
5375
  function listAgentConfigs(configDir, cwd = process.cwd()) {
@@ -5979,9 +5872,8 @@ var spawnAgentTool = {
5979
5872
  };
5980
5873
 
5981
5874
  // src/tools/builtin/task-manager.ts
5982
- import { spawn as spawn5 } from "child_process";
5875
+ import { spawn as spawn4 } from "child_process";
5983
5876
  import { randomUUID } from "crypto";
5984
- import { platform as platform5 } from "os";
5985
5877
  var MAX_OUTPUT_CHARS = 1e4;
5986
5878
  var MAX_OUTPUT_BYTES = 64 * 1024;
5987
5879
  var MAX_TASKS = 20;
@@ -6000,14 +5892,16 @@ function decodeField(chunks) {
6000
5892
  }
6001
5893
  function appendOutput(task, field, chunk) {
6002
5894
  const chunks = field === "stdout" ? task._stdoutChunks : task._stderrChunks;
6003
- chunks.push(chunk);
5895
+ chunks.push(chunk.length > MAX_OUTPUT_BYTES ? chunk.subarray(-MAX_OUTPUT_BYTES) : chunk);
6004
5896
  capChunks(chunks);
6005
5897
  task[field] = decodeField(chunks);
6006
5898
  }
6007
5899
  function createTask(command, description) {
5900
+ if ([...tasks.values()].filter((t) => t._active && t.owner === getExecutionConfigDir()).length >= 10) throw new Error("Background task limit reached (10 running per user).");
5901
+ if ([...tasks.values()].filter((t) => t._active).length >= MAX_TASKS) throw new Error("Background task limit reached (20 running). Stop a task before creating another.");
6008
5902
  if (tasks.size >= MAX_TASKS) {
6009
5903
  for (const [id2, t] of tasks) {
6010
- if (t.status !== "running") {
5904
+ if (!t._active) {
6011
5905
  tasks.delete(id2);
6012
5906
  break;
6013
5907
  }
@@ -6015,16 +5909,19 @@ function createTask(command, description) {
6015
5909
  }
6016
5910
  const id = randomUUID().slice(0, 8);
6017
5911
  const { shell, args } = shellInvocation(command);
6018
- const proc = spawn5(shell, args, {
5912
+ const proc = spawn4(shell, args, {
6019
5913
  stdio: ["ignore", "pipe", "pipe"],
6020
- detached: false,
5914
+ detached: process.platform !== "win32",
5915
+ windowsHide: true,
6021
5916
  env: { ...process.env, PYTHONUTF8: "1", PYTHONIOENCODING: "utf-8" }
6022
5917
  });
6023
5918
  const task = {
6024
5919
  id,
5920
+ owner: getExecutionConfigDir(),
6025
5921
  command,
6026
5922
  description,
6027
5923
  process: proc,
5924
+ _active: true,
6028
5925
  startTime: Date.now(),
6029
5926
  endTime: null,
6030
5927
  status: "running",
@@ -6037,11 +5934,13 @@ function createTask(command, description) {
6037
5934
  proc.stdout?.on("data", (chunk) => appendOutput(task, "stdout", chunk));
6038
5935
  proc.stderr?.on("data", (chunk) => appendOutput(task, "stderr", chunk));
6039
5936
  proc.on("close", (code) => {
5937
+ task._active = false;
6040
5938
  task.endTime = Date.now();
6041
5939
  task.exitCode = code;
6042
- task.status = code === 0 ? "completed" : "failed";
5940
+ if (task.status !== "stopped") task.status = code === 0 ? "completed" : "failed";
6043
5941
  });
6044
5942
  proc.on("error", (err) => {
5943
+ task._active = false;
6045
5944
  task.endTime = Date.now();
6046
5945
  task.status = "failed";
6047
5946
  task.stderr += `
@@ -6051,43 +5950,20 @@ Process error: ${err.message}`;
6051
5950
  return task;
6052
5951
  }
6053
5952
  function listTasks() {
6054
- return [...tasks.values()].map(
6055
- ({ process: _p, _stdoutChunks: _o, _stderrChunks: _e, ...rest }) => rest
5953
+ return [...tasks.values()].filter((t) => t.owner === getExecutionConfigDir()).map(
5954
+ ({ process: _p, _stdoutChunks: _o, _stderrChunks: _e, _active: _a, ...rest }) => rest
6056
5955
  );
6057
5956
  }
6058
5957
  function getTaskOutput(id) {
6059
5958
  const task = tasks.get(id);
6060
- if (!task) return null;
5959
+ if (!task || task.owner !== getExecutionConfigDir()) return null;
6061
5960
  return { stdout: task.stdout, stderr: task.stderr, status: task.status };
6062
5961
  }
6063
5962
  function stopTask(id) {
6064
5963
  const task = tasks.get(id);
6065
- if (!task || task.status !== "running") return false;
5964
+ if (!task || task.owner !== getExecutionConfigDir() || task.status !== "running") return false;
6066
5965
  try {
6067
- const proc = task.process;
6068
- if (platform5() === "win32" && proc.pid) {
6069
- try {
6070
- spawn5("taskkill", ["/PID", String(proc.pid), "/T", "/F"], {
6071
- windowsHide: true,
6072
- stdio: "ignore"
6073
- });
6074
- } catch {
6075
- }
6076
- try {
6077
- proc.kill();
6078
- } catch {
6079
- }
6080
- } else {
6081
- proc.kill("SIGTERM");
6082
- setTimeout(() => {
6083
- if (proc.exitCode === null && proc.signalCode === null) {
6084
- try {
6085
- proc.kill("SIGKILL");
6086
- } catch {
6087
- }
6088
- }
6089
- }, 3e3).unref();
6090
- }
5966
+ killProcessTree(task.process, true);
6091
5967
  task.status = "stopped";
6092
5968
  task.endTime = Date.now();
6093
5969
  return true;
@@ -6195,14 +6071,14 @@ var taskStopTool = {
6195
6071
 
6196
6072
  // src/tools/builtin/git-tools.ts
6197
6073
  import { execFileSync as execFileSync2 } from "child_process";
6198
- import { existsSync as existsSync12 } from "fs";
6199
- import { join as join7 } from "path";
6074
+ import { existsSync as existsSync11 } from "fs";
6075
+ import { join as join5 } from "path";
6200
6076
  function assertGitRepo(cwd) {
6201
6077
  let dir = cwd;
6202
6078
  const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
6203
6079
  while (dir && dir !== root) {
6204
- if (existsSync12(join7(dir, ".git"))) return;
6205
- const parent = join7(dir, "..");
6080
+ if (existsSync11(join5(dir, ".git"))) return;
6081
+ const parent = join5(dir, "..");
6206
6082
  if (parent === dir) break;
6207
6083
  dir = parent;
6208
6084
  }
@@ -6467,7 +6343,7 @@ ${commitOutput.trim()}`;
6467
6343
  };
6468
6344
 
6469
6345
  // src/tools/builtin/notebook-edit.ts
6470
- import { readFileSync as readFileSync9, existsSync as existsSync13 } from "fs";
6346
+ import { readFileSync as readFileSync6, existsSync as existsSync12 } from "fs";
6471
6347
  import { writeFile } from "fs/promises";
6472
6348
  import { resolve as resolve6, extname as extname2 } from "path";
6473
6349
  var notebookEditTool = {
@@ -6522,10 +6398,10 @@ var notebookEditTool = {
6522
6398
  if (extname2(absPath).toLowerCase() !== ".ipynb") {
6523
6399
  throw new ToolError("notebook_edit", "path must point to a .ipynb file");
6524
6400
  }
6525
- if (!existsSync13(absPath)) {
6401
+ if (!existsSync12(absPath)) {
6526
6402
  throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
6527
6403
  }
6528
- const raw = readFileSync9(absPath, "utf-8");
6404
+ const raw = readFileSync6(absPath, "utf-8");
6529
6405
  const nb = parseNotebook(raw);
6530
6406
  const cellIdx0 = cellIndexRaw - 1;
6531
6407
  undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
@@ -6851,8 +6727,6 @@ ${lines.join("\n")}`;
6851
6727
  };
6852
6728
 
6853
6729
  // src/tools/builtin/recall-memory.ts
6854
- import { join as join8 } from "path";
6855
- import { homedir as homedir5 } from "os";
6856
6730
  function formatHit(h, i) {
6857
6731
  const ts = h.chunk.timestamp.slice(0, 16).replace("T", " ");
6858
6732
  const title = h.chunk.sessionTitle ? ` \xB7 ${h.chunk.sessionTitle}` : "";
@@ -6902,7 +6776,7 @@ var recallMemoryTool = {
6902
6776
  const excludeCurrent = Boolean(args["excludeCurrentSession"]);
6903
6777
  const currentId = args["currentSessionId"] ? String(args["currentSessionId"]) : void 0;
6904
6778
  const minScore = args["minScore"] !== void 0 ? Number(args["minScore"]) : 0.25;
6905
- const configDir = join8(homedir5(), CONFIG_DIR_NAME);
6779
+ const configDir = getExecutionConfigDir();
6906
6780
  let persistentSection = "";
6907
6781
  try {
6908
6782
  const persistentHits = searchPersistentMemories(configDir, query, process.cwd(), 3);
@@ -6956,8 +6830,8 @@ function estimateToolDefinitionTokens(def) {
6956
6830
 
6957
6831
  // src/tools/registry.ts
6958
6832
  import { pathToFileURL } from "url";
6959
- import { existsSync as existsSync14, mkdirSync as mkdirSync3, readdirSync as readdirSync7 } from "fs";
6960
- import { join as join9 } from "path";
6833
+ import { existsSync as existsSync13, mkdirSync as mkdirSync3, readdirSync as readdirSync7 } from "fs";
6834
+ import { join as join6 } from "path";
6961
6835
  var ToolRegistry = class {
6962
6836
  tools = /* @__PURE__ */ new Map();
6963
6837
  pluginToolNames = /* @__PURE__ */ new Set();
@@ -7118,7 +6992,7 @@ var ToolRegistry = class {
7118
6992
  * Returns the number of successfully loaded plugins.
7119
6993
  */
7120
6994
  async loadPlugins(pluginsDir, allowPlugins = false) {
7121
- if (!existsSync14(pluginsDir)) {
6995
+ if (!existsSync13(pluginsDir)) {
7122
6996
  try {
7123
6997
  mkdirSync3(pluginsDir, { recursive: true });
7124
6998
  } catch {
@@ -7144,12 +7018,12 @@ var ToolRegistry = class {
7144
7018
  process.stderr.write(
7145
7019
  `
7146
7020
  [plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
7147
- ` + files.map((f) => ` + ${join9(pluginsDir, f)}`).join("\n") + "\n\n"
7021
+ ` + files.map((f) => ` + ${join6(pluginsDir, f)}`).join("\n") + "\n\n"
7148
7022
  );
7149
7023
  let loaded = 0;
7150
7024
  for (const file of files) {
7151
7025
  try {
7152
- const fileUrl = pathToFileURL(join9(pluginsDir, file)).href;
7026
+ const fileUrl = pathToFileURL(join6(pluginsDir, file)).href;
7153
7027
  const mod = await import(fileUrl);
7154
7028
  const tool = mod.tool ?? mod.default?.tool ?? mod.default;
7155
7029
  if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
@@ -7190,10 +7064,6 @@ export {
7190
7064
  requestAgentStop,
7191
7065
  setPreferredAgentName,
7192
7066
  getPreferredAgentName,
7193
- isInterrupted,
7194
- requestInterrupt,
7195
- resetInterrupt,
7196
- runWithSessionKey,
7197
7067
  clearBashSession,
7198
7068
  rlInternal,
7199
7069
  groupCallsByPhase,