jinzd-ai-cli 0.4.204 → 0.4.206

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 (26) hide show
  1. package/README.md +1 -1
  2. package/dist/{batch-ZP52GVVD.js → batch-6PORMJWT.js} +2 -2
  3. package/dist/{chunk-JLZ5QA2I.js → chunk-2WV6AGHM.js} +1 -1
  4. package/dist/{chunk-26YWVRLT.js → chunk-BQIV4ZQT.js} +1 -1
  5. package/dist/{chunk-T5RTPF2Q.js → chunk-KJHMRAJU.js} +1 -1
  6. package/dist/{chunk-PIZWD5JN.js → chunk-KOU4KX7J.js} +2 -2
  7. package/dist/{chunk-UZISJ3KZ.js → chunk-QMXC327F.js} +3 -3
  8. package/dist/{chunk-SCQOEDVL.js → chunk-QNUVZO5X.js} +1 -1
  9. package/dist/{chunk-B75A6AG4.js → chunk-QQYSZMET.js} +108 -64
  10. package/dist/{chunk-GYN2PTY2.js → chunk-SFCNFX42.js} +1 -1
  11. package/dist/{chunk-BENRC3OO.js → chunk-TW47X5AO.js} +1 -1
  12. package/dist/{chunk-SMFRJCXB.js → chunk-XPBEJB27.js} +8 -1
  13. package/dist/{ci-LWE2I5QW.js → ci-AYMV66P3.js} +9 -9
  14. package/dist/{constants-JRY7PDEY.js → constants-NHGTSHKT.js} +1 -1
  15. package/dist/{doctor-cli-KPUULUPS.js → doctor-cli-WKH7T4AW.js} +6 -6
  16. package/dist/electron-server.js +131 -82
  17. package/dist/{hub-4CDJPNIM.js → hub-6R3M3NTC.js} +1 -1
  18. package/dist/index.js +29 -27
  19. package/dist/{run-tests-RQWSG25U.js → run-tests-5F2OBWIK.js} +2 -2
  20. package/dist/{run-tests-6G2F7OSC.js → run-tests-NBCA4KEG.js} +1 -1
  21. package/dist/{server-XNB76DRR.js → server-37EXC4EB.js} +5 -5
  22. package/dist/{server-MF6UDMBZ.js → server-LDPQ3DLK.js} +23 -20
  23. package/dist/{task-orchestrator-PKZ2UTOK.js → task-orchestrator-2DTDVCMR.js} +5 -5
  24. package/dist/{usage-Z64KJASD.js → usage-SZWZFX3T.js} +2 -2
  25. package/dist/web/client/app.js +2 -2
  26. package/package.json +1 -1
package/README.md CHANGED
@@ -100,7 +100,7 @@ Use `@filepath` to reference files or images directly in your prompt.
100
100
  ### Web UI
101
101
 
102
102
  ```bash
103
- aicli web # Start on localhost:3456
103
+ aicli web # Start on localhost:3000
104
104
  aicli web --port 8080 # Custom port
105
105
  aicli web --host 0.0.0.0 # LAN access (shows QR-friendly URL)
106
106
  ```
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigManager
4
- } from "./chunk-BENRC3OO.js";
4
+ } from "./chunk-TW47X5AO.js";
5
5
  import "./chunk-TZQHYZKT.js";
6
- import "./chunk-SCQOEDVL.js";
6
+ import "./chunk-QNUVZO5X.js";
7
7
  import {
8
8
  atomicWriteFileSync
9
9
  } from "./chunk-IW3Q7AE5.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CONFIG_DIR_NAME
4
- } from "./chunk-SCQOEDVL.js";
4
+ } from "./chunk-QNUVZO5X.js";
5
5
  import {
6
6
  atomicWriteFileSync
7
7
  } from "./chunk-IW3Q7AE5.js";
@@ -6,7 +6,7 @@ import { platform } from "os";
6
6
  import chalk from "chalk";
7
7
 
8
8
  // src/core/constants.ts
9
- var VERSION = "0.4.204";
9
+ var VERSION = "0.4.206";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  CONFIG_DIR_NAME,
4
4
  VERSION
5
- } from "./chunk-SCQOEDVL.js";
5
+ } from "./chunk-QNUVZO5X.js";
6
6
 
7
7
  // src/diagnostics/crash-log.ts
8
8
  import {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  truncateForPersist
4
- } from "./chunk-B75A6AG4.js";
4
+ } from "./chunk-QQYSZMET.js";
5
5
  import {
6
6
  APP_NAME,
7
7
  CONFIG_DIR_NAME,
@@ -11,7 +11,7 @@ import {
11
11
  MCP_PROTOCOL_VERSION,
12
12
  MCP_TOOL_PREFIX,
13
13
  VERSION
14
- } from "./chunk-SCQOEDVL.js";
14
+ } from "./chunk-QNUVZO5X.js";
15
15
 
16
16
  // src/mcp/client.ts
17
17
  import { spawn } from "child_process";
@@ -3,7 +3,7 @@ import {
3
3
  detectsHallucinatedFileOp,
4
4
  repairToolCallArguments,
5
5
  schemaToJsonSchema
6
- } from "./chunk-SMFRJCXB.js";
6
+ } from "./chunk-XPBEJB27.js";
7
7
  import {
8
8
  AuthError,
9
9
  ProviderError,
@@ -31,8 +31,8 @@ var BaseProvider = class {
31
31
  * 将 Message[] 转换为 OpenAI API 格式的消息数组。
32
32
  * content 为 string 时直接传递;为 MessageContentPart[] 时保留数组格式(vision 请求)。
33
33
  *
34
- * 自动跳过 role='tool' 和带 toolCalls 的 assistant 消息——
35
- * 这些是 v0.4.60+ 持久化的工具历史,由 _extraMessages 机制单独注入。
34
+ * 基类只提供降级格式:跳过 role='tool' 和带 toolCalls 的 assistant 消息。
35
+ * 需要完整工具历史 roundtrip provider 应覆盖消息构造逻辑,例如 OpenAI-compatible provider。
36
36
  */
37
37
  normalizeMessages(messages) {
38
38
  return messages.filter((m) => m.role !== "tool" && !m.toolCalls).map((m) => ({ role: m.role, content: m.content }));
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.204";
4
+ var VERSION = "0.4.206";
5
5
  var APP_NAME = "ai-cli";
6
6
  var CONFIG_DIR_NAME = ".aicli";
7
7
  var CONFIG_FILE_NAME = "config.json";
@@ -5,15 +5,15 @@ import {
5
5
  } from "./chunk-T2NL5ZIA.js";
6
6
  import {
7
7
  runTestsTool
8
- } from "./chunk-GYN2PTY2.js";
8
+ } from "./chunk-SFCNFX42.js";
9
9
  import {
10
10
  runTool
11
- } from "./chunk-JLZ5QA2I.js";
11
+ } from "./chunk-2WV6AGHM.js";
12
12
  import {
13
13
  getDangerLevel,
14
14
  isFileWriteTool,
15
15
  runLeanAgentLoop
16
- } from "./chunk-SMFRJCXB.js";
16
+ } from "./chunk-XPBEJB27.js";
17
17
  import {
18
18
  EnvLoader,
19
19
  NetworkError,
@@ -26,7 +26,7 @@ import {
26
26
  SUBAGENT_ALLOWED_TOOLS,
27
27
  SUBAGENT_DEFAULT_MAX_ROUNDS,
28
28
  SUBAGENT_MAX_ROUNDS_LIMIT
29
- } from "./chunk-SCQOEDVL.js";
29
+ } from "./chunk-QNUVZO5X.js";
30
30
  import {
31
31
  fileCheckpoints
32
32
  } from "./chunk-4BKXL7SM.js";
@@ -40,11 +40,14 @@ import {
40
40
  import {
41
41
  loadIndex
42
42
  } from "./chunk-CKH4KQ4E.js";
43
+ import {
44
+ atomicWriteFileSync
45
+ } from "./chunk-IW3Q7AE5.js";
43
46
 
44
47
  // src/tools/builtin/bash.ts
45
48
  import { spawn } from "child_process";
46
49
  import { existsSync as existsSync2, readdirSync, statSync } from "fs";
47
- import { platform } from "os";
50
+ import { platform as platform2 } from "os";
48
51
  import { resolve } from "path";
49
52
 
50
53
  // src/tools/undo-stack.ts
@@ -196,9 +199,32 @@ function runWithSessionKey(sessionKey, fn) {
196
199
  return als.run({ sessionKey: key }, fn);
197
200
  }
198
201
 
199
- // src/tools/builtin/bash.ts
202
+ // src/tools/win-shell.ts
203
+ import { platform } from "os";
200
204
  var IS_WINDOWS = platform() === "win32";
201
- var SHELL = IS_WINDOWS ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
205
+ var WIN_UTF8_PREAMBLE = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; $OutputEncoding = [System.Text.Encoding]::UTF8; ";
206
+ function shellInvocation(command) {
207
+ if (IS_WINDOWS) {
208
+ return {
209
+ shell: "powershell.exe",
210
+ args: ["-NoProfile", "-NonInteractive", "-Command", WIN_UTF8_PREAMBLE + command]
211
+ };
212
+ }
213
+ return { shell: process.env["SHELL"] ?? "/bin/bash", args: ["-c", command] };
214
+ }
215
+ function decodeWindowsBuffer(buf) {
216
+ const utf8 = buf.toString("utf-8");
217
+ if (!utf8.includes("\uFFFD")) return utf8;
218
+ try {
219
+ return new TextDecoder("gbk").decode(buf);
220
+ } catch {
221
+ return utf8;
222
+ }
223
+ }
224
+
225
+ // src/tools/builtin/bash.ts
226
+ var IS_WINDOWS2 = platform2() === "win32";
227
+ var SHELL = IS_WINDOWS2 ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
202
228
  var cwdBySession = /* @__PURE__ */ new Map();
203
229
  function getCwd() {
204
230
  const key = getCurrentSessionKey();
@@ -215,7 +241,7 @@ function setCwd(next) {
215
241
  var bashTool = {
216
242
  definition: {
217
243
  name: "bash",
218
- description: IS_WINDOWS ? `Execute commands in PowerShell. Supports mkdir, ls, cat, python, etc.
244
+ description: IS_WINDOWS2 ? `Execute commands in PowerShell. Supports mkdir, ls, cat, python, etc.
219
245
  Important rules:
220
246
  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 mydir; cd mydir; New-Item file.txt".
221
247
  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.
@@ -231,7 +257,7 @@ Important rules:
231
257
  parameters: {
232
258
  command: {
233
259
  type: "string",
234
- description: IS_WINDOWS ? `PowerShell command to execute. Combine multiple commands with semicolons, e.g.: "mkdir mydir; Set-Content mydir/file.txt 'content'"` : `${SHELL} command to execute. Combine multiple commands with &&, e.g.: "mkdir -p mydir && echo 'content' > mydir/file.txt"`,
260
+ description: IS_WINDOWS2 ? `PowerShell command to execute. Combine multiple commands with semicolons, e.g.: "mkdir mydir; Set-Content mydir/file.txt 'content'"` : `${SHELL} command to execute. Combine multiple commands with &&, e.g.: "mkdir -p mydir && echo 'content' > mydir/file.txt"`,
235
261
  required: true
236
262
  },
237
263
  cwd: {
@@ -283,9 +309,9 @@ Important rules:
283
309
  setCwd(resolved);
284
310
  }
285
311
  let actualCommand;
286
- if (IS_WINDOWS) {
312
+ if (IS_WINDOWS2) {
287
313
  const fixedCommand = fixWindowsDeleteCommand(command);
288
- actualCommand = `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; $OutputEncoding = [System.Text.Encoding]::UTF8; ${fixedCommand}`;
314
+ actualCommand = WIN_UTF8_PREAMBLE + fixedCommand;
289
315
  } else {
290
316
  actualCommand = command;
291
317
  }
@@ -329,7 +355,7 @@ Important rules:
329
355
  }
330
356
  updateCwdFromCommand(command, effectiveCwd);
331
357
  pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
332
- const result = Buffer.isBuffer(stdout) ? IS_WINDOWS ? decodeWindowsBuffer(stdout) : stdout.toString("utf-8") : String(stdout ?? "");
358
+ const result = Buffer.isBuffer(stdout) ? IS_WINDOWS2 ? decodeWindowsBuffer(stdout) : stdout.toString("utf-8") : String(stdout ?? "");
333
359
  return result || "(command completed with no output)";
334
360
  } catch (err) {
335
361
  pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
@@ -354,8 +380,8 @@ How to recover (pick ONE \u2014 do NOT retry the same command):
354
380
  );
355
381
  }
356
382
  if ("status" in execErr && execErr.status !== void 0) {
357
- const stderr = IS_WINDOWS && Buffer.isBuffer(execErr.stderr) ? decodeWindowsBuffer(execErr.stderr).trim() : execErr.stderr?.toString().trim() ?? "";
358
- const stdout = IS_WINDOWS && Buffer.isBuffer(execErr.stdout) ? decodeWindowsBuffer(execErr.stdout).trim() : execErr.stdout?.toString().trim() ?? "";
383
+ const stderr = IS_WINDOWS2 && Buffer.isBuffer(execErr.stderr) ? decodeWindowsBuffer(execErr.stderr).trim() : execErr.stderr?.toString().trim() ?? "";
384
+ const stdout = IS_WINDOWS2 && Buffer.isBuffer(execErr.stdout) ? decodeWindowsBuffer(execErr.stdout).trim() : execErr.stdout?.toString().trim() ?? "";
359
385
  const combined = [stdout, stderr].filter(Boolean).join("\n");
360
386
  const hint = buildErrorHint(command, combined);
361
387
  throw new ToolError(
@@ -389,23 +415,14 @@ function fixWindowsDeleteCommand(command) {
389
415
  }
390
416
  );
391
417
  }
392
- function decodeWindowsBuffer(buf) {
393
- const utf8 = buf.toString("utf-8");
394
- if (!utf8.includes("\uFFFD")) return utf8;
395
- try {
396
- return new TextDecoder("gbk").decode(buf);
397
- } catch {
398
- return utf8;
399
- }
400
- }
401
418
  function buildErrorHint(command, stderr) {
402
419
  const hints = [];
403
- if (IS_WINDOWS && /\|\|/.test(command) && /(\|\||is not a valid argument|不是此版本中的有效|ParserError|Unexpected token)/i.test(stderr)) {
420
+ if (IS_WINDOWS2 && /\|\|/.test(command) && /(\|\||is not a valid argument|不是此版本中的有效|ParserError|Unexpected token)/i.test(stderr)) {
404
421
  hints.push(
405
422
  `Hint: PowerShell parses '||' as a pipeline operator (PS 5.x) or logical-or (PS 7+), it CANNOT be passed inline as SQL string concatenation. Workaround: write the SQL to a local .sql file with write_file, then 'scp' it to the remote host and run 'psql -f /tmp/x.sql'. Do NOT try to escape || or wrap the command differently \u2014 it will not work.`
406
423
  );
407
424
  }
408
- if (IS_WINDOWS && /\bpython3\b/.test(command)) {
425
+ if (IS_WINDOWS2 && /\bpython3\b/.test(command)) {
409
426
  const trivialStderr = stderr.trim().length === 0 || /^[·\s]*exit 1\b/i.test(stderr.trim());
410
427
  if (/(not recognized|is not recognized|无法将.*识别)/i.test(stderr) || trivialStderr) {
411
428
  hints.push(
@@ -413,27 +430,27 @@ function buildErrorHint(command, stderr) {
413
430
  );
414
431
  }
415
432
  }
416
- if (IS_WINDOWS && /^\s*ssh\b/.test(command) && /\\"/.test(command) && /(syntax error|ERROR:|ParserError|unexpected|parser|意外的|语法|不是此版本|所在位置\s+行)/i.test(stderr)) {
433
+ if (IS_WINDOWS2 && /^\s*ssh\b/.test(command) && /\\"/.test(command) && /(syntax error|ERROR:|ParserError|unexpected|parser|意外的|语法|不是此版本|所在位置\s+行)/i.test(stderr)) {
417
434
  hints.push(
418
435
  `Hint: SSH + nested quotes ("...\\"...\\"") is fragile across PS \u2192 ssh \u2192 remote-shell \u2192 psql layers. Use the file-based flow: (1) write_file locally to a .sql file, (2) 'scp x.sql root@host:/tmp/', (3) 'ssh root@host "sudo -u postgres psql -d <db> -f /tmp/x.sql"'. Or for remote-only SQL: 'ssh host "cat > /tmp/x.sql << \\'SQLEOF\\'\\n...\\nSQLEOF"' then run psql -f. Avoid inline psql -c with embedded quotes.`
419
436
  );
420
437
  }
421
- if (IS_WINDOWS && /\bmkdir\b/.test(command) && /\s-p\b/.test(command) && /(已存在|already exists|ItemExistsUnauthorizedAccessError|exists)/i.test(stderr)) {
438
+ if (IS_WINDOWS2 && /\bmkdir\b/.test(command) && /\s-p\b/.test(command) && /(已存在|already exists|ItemExistsUnauthorizedAccessError|exists)/i.test(stderr)) {
422
439
  hints.push(
423
440
  `Hint: PowerShell's mkdir does not support the '-p' flag. When the directory already exists it exits with code 1. Use instead: New-Item -ItemType Directory -Force -Path <dir> ('-Force' silently succeeds even if the directory already exists, mimicking 'mkdir -p').`
424
441
  );
425
442
  }
426
- if (IS_WINDOWS && /Remove-Item/i.test(command) && /cannot find path|no such file|exit 1/i.test(stderr || command)) {
443
+ if (IS_WINDOWS2 && /Remove-Item/i.test(command) && /cannot find path|no such file|exit 1/i.test(stderr || command)) {
427
444
  hints.push(
428
445
  `Hint: On Windows, "Remove-Item ... -ErrorAction SilentlyContinue" still makes the powershell process exit 1 when no files match (even though the error is silenced). Use Test-Path as a guard: @('tmp_a.sql','tmp_b.sql','tmp_c.csv') | Where-Object { Test-Path $_ } | Remove-Item -Force \u2014 Test-Path returns a bool without raising, so missing paths are filtered out and exit code is 0.`
429
446
  );
430
447
  }
431
- if (IS_WINDOWS && /-(?:EA|ErrorAction)\s+(?:SilentlyContinue|Ignore)\b/i.test(command) && /\b(Get-ChildItem|Get-Item|Get-Content|gci|gi|gc|dir|ls)\b/i.test(command) && !/Remove-Item/i.test(command) && !/Test-Path/i.test(command) && /cannot find path|no such file|does not exist|exit 1|不存在|找不到/i.test(stderr || command)) {
448
+ if (IS_WINDOWS2 && /-(?:EA|ErrorAction)\s+(?:SilentlyContinue|Ignore)\b/i.test(command) && /\b(Get-ChildItem|Get-Item|Get-Content|gci|gi|gc|dir|ls)\b/i.test(command) && !/Remove-Item/i.test(command) && !/Test-Path/i.test(command) && /cannot find path|no such file|does not exist|exit 1|不存在|找不到/i.test(stderr || command)) {
432
449
  hints.push(
433
450
  `Hint: On Windows, "-ErrorAction SilentlyContinue" silences the error MESSAGE but does NOT change the exit code \u2014 the process still exits 1 when the path doesn't exist (because powershell.exe -NonInteractive flips the exit code on any non-terminating error, silenced or not). If you're just probing for existence, use Test-Path instead \u2014 it returns $true/$false without raising: \`if (Test-Path ./mydir) { Get-ChildItem ./mydir }\` or unconditionally use \`New-Item -ItemType Directory -Force -Path ./mydir\` which is idempotent. Don't retry the same -ErrorAction SilentlyContinue probe expecting a different result.`
434
451
  );
435
452
  }
436
- if (IS_WINDOWS && /\bWrite-(?:Content|File)\b/i.test(command) && /(not recognized|无法将.*识别|不是.*cmdlet|CommandNotFoundException)/i.test(stderr)) {
453
+ if (IS_WINDOWS2 && /\bWrite-(?:Content|File)\b/i.test(command) && /(not recognized|无法将.*识别|不是.*cmdlet|CommandNotFoundException)/i.test(stderr)) {
437
454
  hints.push(
438
455
  `Hint: 'Write-Content' / 'Write-File' are NOT PowerShell cmdlets (common LLM hallucination). To write a file, prefer the built-in 'write_file' tool \u2014 it handles encoding/escaping cleanly and supports diff preview. If you must stay in bash: use 'Set-Content -Path <file> -Value <string> -Encoding utf8' (overwrites) or 'Add-Content' (appends) or 'Out-File -FilePath <file> -Encoding utf8'.`
439
456
  );
@@ -454,6 +471,18 @@ function detectBlockingCommand(command) {
454
471
  if (logcat && !/(?:^|\s)-(?:d|t|c|g)\b/.test(logcat[1] ?? "")) {
455
472
  return blockingMessage("adb logcat", "stream device logs");
456
473
  }
474
+ if (/\bStart-Process\b/i.test(command) && /\b(?:emulator|qemu[\w-]*)(?:\.exe)?\b/i.test(command)) {
475
+ return `This launches a long-running emulator/qemu process via Start-Process, which DETACHES a GUI child that aicli cannot track or stop with task_stop \u2014 and in practice that child often dies together with this bash call, leaving nothing running.
476
+
477
+ Use the 'task_create' tool instead so the process is tracked and stoppable:
478
+ task_create(command: "<sdk>\\emulator\\emulator.exe -avd <name> -no-boot-anim", description: "launch emulator")
479
+
480
+ Then poll readiness with SHORT bash calls (these DO exit):
481
+ adb devices # is the device listed?
482
+ adb shell getprop sys.boot_completed # prints "1" once fully booted
483
+
484
+ [Do NOT launch long-running GUI processes via Start-Process in bash \u2014 use task_create.]`;
485
+ }
457
486
  return null;
458
487
  }
459
488
  function blockingMessage(example, what) {
@@ -533,7 +562,7 @@ function pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd) {
533
562
  }
534
563
  function runShell(command, opts) {
535
564
  return new Promise((resolvePromise) => {
536
- const shellArgs = IS_WINDOWS ? ["-NoProfile", "-NonInteractive", "-Command", command] : ["-c", command];
565
+ const shellArgs = IS_WINDOWS2 ? ["-NoProfile", "-NonInteractive", "-Command", command] : ["-c", command];
537
566
  const child = spawn(SHELL, shellArgs, {
538
567
  cwd: opts.cwd,
539
568
  env: opts.env,
@@ -596,7 +625,7 @@ function runShell(command, opts) {
596
625
  }
597
626
  function killChild(child) {
598
627
  if (child.killed || child.exitCode !== null) return;
599
- if (IS_WINDOWS && child.pid) {
628
+ if (IS_WINDOWS2 && child.pid) {
600
629
  try {
601
630
  spawn("taskkill", ["/PID", String(child.pid), "/T", "/F"], {
602
631
  windowsHide: true,
@@ -1021,7 +1050,7 @@ ${content}`;
1021
1050
  };
1022
1051
 
1023
1052
  // src/tools/builtin/write-file.ts
1024
- import { writeFileSync as writeFileSync2, appendFileSync, mkdirSync } from "fs";
1053
+ import { appendFileSync, mkdirSync } from "fs";
1025
1054
  import { dirname as dirname2 } from "path";
1026
1055
 
1027
1056
  // src/tools/executor.ts
@@ -1496,8 +1525,8 @@ var ToolExecutor = class {
1496
1525
  /** confirm() 的取消回调,由 SIGINT handler 调用 */
1497
1526
  cancelConfirmFn = null;
1498
1527
  /**
1499
- * 会话级 auto-approve:跳过所有 write/destructive 确认(仅当前会话有效)。
1500
- * 通过 /yolo 命令切换。destructive 操作仍会显示警告但不阻塞。
1528
+ * 会话级 auto-approve:跳过 write 确认(仅当前会话有效)。
1529
+ * destructive 操作仍然必须逐次确认。
1501
1530
  */
1502
1531
  sessionAutoApprove = false;
1503
1532
  /**
@@ -1581,7 +1610,7 @@ var ToolExecutor = class {
1581
1610
  }
1582
1611
  }
1583
1612
  }
1584
- if (this.sessionAutoApprove && dangerLevel !== "safe") {
1613
+ if (this.sessionAutoApprove && dangerLevel === "write") {
1585
1614
  this.printToolCall(call);
1586
1615
  if (dangerLevel === "write") this.printDiffPreview(call);
1587
1616
  console.log(theme.warning(" \u26A1 Auto-approved (session /yolo mode)"));
@@ -2110,7 +2139,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
2110
2139
  if (appendMode) {
2111
2140
  appendFileSync(filePath, content, encoding);
2112
2141
  } else {
2113
- writeFileSync2(filePath, content, encoding);
2142
+ atomicWriteFileSync(filePath, content);
2114
2143
  }
2115
2144
  const lines = content.split("\n").length;
2116
2145
  const mode = appendMode ? "appended" : "written";
@@ -2126,7 +2155,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
2126
2155
  };
2127
2156
 
2128
2157
  // src/tools/builtin/edit-file.ts
2129
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
2158
+ import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
2130
2159
 
2131
2160
  // src/tools/builtin/patch-apply.ts
2132
2161
  function parseUnifiedDiff(patch) {
@@ -2525,7 +2554,7 @@ Note: Path can be absolute or relative to cwd.`,
2525
2554
  if (res.ok && res.appliedCount > 0 && res.content !== void 0) {
2526
2555
  undoStack.push(filePath, `edit_file (patch ${res.appliedCount}/${hunks.length}): ${filePath}`);
2527
2556
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
2528
- writeFileSync3(filePath, res.content, encoding);
2557
+ atomicWriteFileSync(filePath, res.content);
2529
2558
  }
2530
2559
  return lines.join("\n");
2531
2560
  }
@@ -2559,7 +2588,7 @@ Note: Path can be absolute or relative to cwd.`,
2559
2588
  if (writeChanges) {
2560
2589
  undoStack.push(filePath, `edit_file (batch ${appliedCount}/${edits.length}): ${filePath}`);
2561
2590
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
2562
- writeFileSync3(filePath, working, encoding);
2591
+ atomicWriteFileSync(filePath, working);
2563
2592
  }
2564
2593
  const lines = [];
2565
2594
  if (anyFailed && stopOnError) {
@@ -2597,7 +2626,7 @@ Please read the file first and use exact text.`;
2597
2626
  const label = res.info?.mode === "ignore_whitespace" ? "edit_file (ws-replace)" : res.info?.mode === "replace_all" ? "edit_file (replace_all)" : "edit_file (replace)";
2598
2627
  undoStack.push(filePath, `${label}: ${filePath}`);
2599
2628
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
2600
- writeFileSync3(filePath, res.content, encoding);
2629
+ atomicWriteFileSync(filePath, res.content);
2601
2630
  if (res.info?.mode === "replace_all") {
2602
2631
  return `Successfully edited ${filePath}${modeLabel}
2603
2632
  Replaced: ${res.info.replacedCount} occurrence(s) of ${truncatePreview(oldStr)}
@@ -2621,7 +2650,7 @@ Please read the file first and use exact text.`;
2621
2650
  undoStack.push(filePath, `edit_file (insert): ${filePath}`);
2622
2651
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
2623
2652
  lines.splice(afterLine, 0, content);
2624
- writeFileSync3(filePath, lines.join("\n"), encoding);
2653
+ atomicWriteFileSync(filePath, lines.join("\n"));
2625
2654
  return `Successfully inserted ${content.split("\n").length} line(s) after line ${afterLine} in ${filePath}`;
2626
2655
  }
2627
2656
  if (args["delete_from_line"] !== void 0) {
@@ -2637,7 +2666,7 @@ Please read the file first and use exact text.`;
2637
2666
  undoStack.push(filePath, `edit_file (delete): ${filePath}`);
2638
2667
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
2639
2668
  const deleted = lines.splice(fromLine - 1, toLine - fromLine + 1);
2640
- writeFileSync3(filePath, lines.join("\n"), encoding);
2669
+ atomicWriteFileSync(filePath, lines.join("\n"));
2641
2670
  return `Successfully deleted lines ${fromLine}-${toLine} (${deleted.length} lines) from ${filePath}`;
2642
2671
  }
2643
2672
  throw new ToolError(
@@ -3122,8 +3151,8 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
3122
3151
 
3123
3152
  // src/tools/builtin/run-interactive.ts
3124
3153
  import { spawn as spawn2 } from "child_process";
3125
- import { platform as platform2 } from "os";
3126
- var IS_WINDOWS2 = platform2() === "win32";
3154
+ import { platform as platform3 } from "os";
3155
+ var IS_WINDOWS3 = platform3() === "win32";
3127
3156
  var runInteractiveTool = {
3128
3157
  definition: {
3129
3158
  name: "run_interactive",
@@ -3203,7 +3232,7 @@ var runInteractiveTool = {
3203
3232
  let lineIdx = 0;
3204
3233
  const writeNextLine = () => {
3205
3234
  if (lineIdx < stdinLines.length && !child.stdin.destroyed) {
3206
- const line = stdinLines[lineIdx++] + (IS_WINDOWS2 ? "\r\n" : "\n");
3235
+ const line = stdinLines[lineIdx++] + (IS_WINDOWS3 ? "\r\n" : "\n");
3207
3236
  const canContinue = child.stdin.write(line);
3208
3237
  if (canContinue) {
3209
3238
  setTimeout(writeNextLine, 150);
@@ -3842,7 +3871,7 @@ ${preamble}`;
3842
3871
  }
3843
3872
 
3844
3873
  // src/tools/builtin/save-last-response.ts
3845
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, rmdirSync as rmdirSync2 } from "fs";
3874
+ import { mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, rmdirSync as rmdirSync2 } from "fs";
3846
3875
  import { dirname as dirname3 } from "path";
3847
3876
  var lastResponseStore = { content: "" };
3848
3877
  function cleanupRejectedTeeFile(filePath) {
@@ -3893,7 +3922,7 @@ Any of these triggers means use save_last_response, NOT write_file:
3893
3922
  }
3894
3923
  undoStack.push(filePath, `save_last_response: ${filePath}`);
3895
3924
  mkdirSync2(dirname3(filePath), { recursive: true });
3896
- writeFileSync4(filePath, content, "utf-8");
3925
+ atomicWriteFileSync(filePath, content);
3897
3926
  const lines = content.split("\n").length;
3898
3927
  return `File saved: ${filePath} (${lines} lines, ${content.length} bytes)`;
3899
3928
  }
@@ -4516,16 +4545,29 @@ var spawnAgentTool = {
4516
4545
  // src/tools/builtin/task-manager.ts
4517
4546
  import { spawn as spawn3 } from "child_process";
4518
4547
  import { randomUUID } from "crypto";
4519
- import { platform as platform3 } from "os";
4548
+ import { platform as platform4 } from "os";
4520
4549
  var MAX_OUTPUT_CHARS = 1e4;
4550
+ var MAX_OUTPUT_BYTES = 64 * 1024;
4521
4551
  var MAX_TASKS = 20;
4522
4552
  var tasks = /* @__PURE__ */ new Map();
4523
- function appendOutput(task, field, chunk) {
4524
- task[field] += chunk;
4525
- if (task[field].length > MAX_OUTPUT_CHARS) {
4526
- task[field] = "... [truncated] ...\n" + task[field].slice(-MAX_OUTPUT_CHARS);
4553
+ function capChunks(chunks) {
4554
+ let total = 0;
4555
+ for (const c of chunks) total += c.length;
4556
+ while (total > MAX_OUTPUT_BYTES && chunks.length > 1) {
4557
+ total -= chunks.shift().length;
4527
4558
  }
4528
4559
  }
4560
+ function decodeField(chunks) {
4561
+ const buf = Buffer.concat(chunks);
4562
+ const text = IS_WINDOWS ? decodeWindowsBuffer(buf) : buf.toString("utf-8");
4563
+ return text.length > MAX_OUTPUT_CHARS ? "... [truncated] ...\n" + text.slice(-MAX_OUTPUT_CHARS) : text;
4564
+ }
4565
+ function appendOutput(task, field, chunk) {
4566
+ const chunks = field === "stdout" ? task._stdoutChunks : task._stderrChunks;
4567
+ chunks.push(chunk);
4568
+ capChunks(chunks);
4569
+ task[field] = decodeField(chunks);
4570
+ }
4529
4571
  function createTask(command, description) {
4530
4572
  if (tasks.size >= MAX_TASKS) {
4531
4573
  for (const [id2, t] of tasks) {
@@ -4536,13 +4578,11 @@ function createTask(command, description) {
4536
4578
  }
4537
4579
  }
4538
4580
  const id = randomUUID().slice(0, 8);
4539
- const isWin = platform3() === "win32";
4540
- const shell = isWin ? "cmd" : "sh";
4541
- const shellFlag = isWin ? "/c" : "-c";
4542
- const proc = spawn3(shell, [shellFlag, command], {
4581
+ const { shell, args } = shellInvocation(command);
4582
+ const proc = spawn3(shell, args, {
4543
4583
  stdio: ["ignore", "pipe", "pipe"],
4544
4584
  detached: false,
4545
- env: { ...process.env, PYTHONUTF8: "1" }
4585
+ env: { ...process.env, PYTHONUTF8: "1", PYTHONIOENCODING: "utf-8" }
4546
4586
  });
4547
4587
  const task = {
4548
4588
  id,
@@ -4554,10 +4594,12 @@ function createTask(command, description) {
4554
4594
  status: "running",
4555
4595
  exitCode: null,
4556
4596
  stdout: "",
4557
- stderr: ""
4597
+ stderr: "",
4598
+ _stdoutChunks: [],
4599
+ _stderrChunks: []
4558
4600
  };
4559
- proc.stdout?.on("data", (chunk) => appendOutput(task, "stdout", chunk.toString("utf-8")));
4560
- proc.stderr?.on("data", (chunk) => appendOutput(task, "stderr", chunk.toString("utf-8")));
4601
+ proc.stdout?.on("data", (chunk) => appendOutput(task, "stdout", chunk));
4602
+ proc.stderr?.on("data", (chunk) => appendOutput(task, "stderr", chunk));
4561
4603
  proc.on("close", (code) => {
4562
4604
  task.endTime = Date.now();
4563
4605
  task.exitCode = code;
@@ -4573,7 +4615,9 @@ Process error: ${err.message}`;
4573
4615
  return task;
4574
4616
  }
4575
4617
  function listTasks() {
4576
- return [...tasks.values()].map(({ process: _p, ...rest }) => rest);
4618
+ return [...tasks.values()].map(
4619
+ ({ process: _p, _stdoutChunks: _o, _stderrChunks: _e, ...rest }) => rest
4620
+ );
4577
4621
  }
4578
4622
  function getTaskOutput(id) {
4579
4623
  const task = tasks.get(id);
@@ -4585,7 +4629,7 @@ function stopTask(id) {
4585
4629
  if (!task || task.status !== "running") return false;
4586
4630
  try {
4587
4631
  const proc = task.process;
4588
- if (platform3() === "win32" && proc.pid) {
4632
+ if (platform4() === "win32" && proc.pid) {
4589
4633
  try {
4590
4634
  spawn3("taskkill", ["/PID", String(proc.pid), "/T", "/F"], {
4591
4635
  windowsHide: true,
@@ -4620,7 +4664,7 @@ function stopTask(id) {
4620
4664
  var taskCreateTool = {
4621
4665
  definition: {
4622
4666
  name: "task_create",
4623
- description: `Start a command running in the background and return immediately with a task ID (monitor via task_list, stop via task_stop). Use this for ANY long-running or never-exiting command, including: dev servers (npm run dev, metro / react-native start), Android emulator launch (emulator -avd X), device log streaming (adb logcat), file watchers, and builds you want to monitor while continuing other work. Running such commands via 'bash' instead would block until timeout.`,
4667
+ description: `Start a command running in the background and return immediately with a task ID (monitor via task_list, stop via task_stop). Use this for ANY long-running or never-exiting command, including: dev servers (npm run dev, metro / react-native start), Android emulator launch (emulator -avd X), device log streaming (adb logcat), file watchers, and builds you want to monitor while continuing other work. Running such commands via 'bash' instead would block until timeout. The command runs in the SAME shell as the 'bash' tool (PowerShell on Windows, $SHELL on Unix), so a command you verified with 'bash' can be moved here verbatim \u2014 write it in PowerShell syntax on Windows (e.g. & "C:\\path\\app.exe" -flag), NOT cmd.exe syntax.`,
4624
4668
  parameters: {
4625
4669
  command: {
4626
4670
  type: "string",
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-SCQOEDVL.js";
4
+ } from "./chunk-QNUVZO5X.js";
5
5
 
6
6
  // src/tools/builtin/run-tests.ts
7
7
  import { execSync, spawnSync } from "child_process";
@@ -8,7 +8,7 @@ import {
8
8
  CONFIG_FILE_NAME,
9
9
  HISTORY_DIR_NAME,
10
10
  PLUGINS_DIR_NAME
11
- } from "./chunk-SCQOEDVL.js";
11
+ } from "./chunk-QNUVZO5X.js";
12
12
 
13
13
  // src/config/config-manager.ts
14
14
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -5,7 +5,14 @@ function isFileWriteTool(name) {
5
5
  return name === "write_file" || name === "edit_file" || name === "notebook_edit";
6
6
  }
7
7
  function getDangerLevel(toolName, args) {
8
- if (toolName.startsWith("mcp__")) return "safe";
8
+ if (toolName.startsWith("mcp__")) {
9
+ const rawName = toolName.split("__").pop() ?? "";
10
+ const normalized = rawName.toLowerCase().replace(/[^a-z0-9]+/g, "_");
11
+ if (/^(read|get|list|search|find|query|fetch|status|describe|inspect|show|lookup)(?:_|$)/.test(normalized)) {
12
+ return "safe";
13
+ }
14
+ return "write";
15
+ }
9
16
  if (toolName === "bash") {
10
17
  const cmd = String(args["command"] ?? "");
11
18
  if (/\brm\s+[^\n]*(?:-\w*[rRfF]\w*|--recursive|--force)\b/.test(cmd)) return "destructive";
@@ -6,18 +6,18 @@ import {
6
6
  } from "./chunk-HLWUDRBO.js";
7
7
  import {
8
8
  ProviderRegistry
9
- } from "./chunk-UZISJ3KZ.js";
10
- import "./chunk-SMFRJCXB.js";
9
+ } from "./chunk-QMXC327F.js";
10
+ import "./chunk-XPBEJB27.js";
11
11
  import {
12
12
  ConfigManager
13
- } from "./chunk-BENRC3OO.js";
13
+ } from "./chunk-TW47X5AO.js";
14
14
  import "./chunk-TZQHYZKT.js";
15
15
  import {
16
16
  VERSION
17
- } from "./chunk-SCQOEDVL.js";
17
+ } from "./chunk-QNUVZO5X.js";
18
18
 
19
19
  // src/cli/ci.ts
20
- import { execFileSync, execSync } from "child_process";
20
+ import { execFileSync } from "child_process";
21
21
  var CI_COMMENT_MARKER = "<!-- aicli-ci-review -->";
22
22
  function fetchDiff(opts) {
23
23
  if (opts.diffOverride != null) return opts.diffOverride;
@@ -34,7 +34,8 @@ function fetchDiff(opts) {
34
34
  }
35
35
  if (opts.base) {
36
36
  try {
37
- return execSync(`git diff ${shellQuote(opts.base)}...HEAD`, {
37
+ validateGitRef(opts.base);
38
+ return execFileSync("git", ["diff", `${opts.base}...HEAD`], {
38
39
  encoding: "utf-8",
39
40
  maxBuffer: 50 * 1024 * 1024,
40
41
  timeout: 3e4
@@ -45,18 +46,17 @@ function fetchDiff(opts) {
45
46
  }
46
47
  throw new Error("aicli ci: must supply --pr <num> or --base <ref> to determine the diff source");
47
48
  }
48
- function shellQuote(ref) {
49
+ function validateGitRef(ref) {
49
50
  if (!/^[A-Za-z0-9._/\-]+$/.test(ref)) {
50
51
  throw new Error(`unsafe ref: ${ref}`);
51
52
  }
52
- return ref;
53
53
  }
54
54
  function buildGitContextStr(opts) {
55
55
  const parts = [];
56
56
  if (opts.pr != null) parts.push(`PR: #${opts.pr}`);
57
57
  if (opts.base) parts.push(`Base: ${opts.base}`);
58
58
  try {
59
- const branch = execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8", timeout: 5e3 }).trim();
59
+ const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { encoding: "utf-8", timeout: 5e3 }).trim();
60
60
  if (branch) parts.push(`Branch: ${branch}`);
61
61
  } catch {
62
62
  }
@@ -36,7 +36,7 @@ import {
36
36
  TEST_TIMEOUT,
37
37
  VERSION,
38
38
  buildUserIdentityPrompt
39
- } from "./chunk-SCQOEDVL.js";
39
+ } from "./chunk-QNUVZO5X.js";
40
40
  export {
41
41
  AGENTIC_BEHAVIOR_GUIDELINE,
42
42
  APP_NAME,