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
@@ -2,26 +2,26 @@
2
2
  import {
3
3
  getConfigDirUsage,
4
4
  listRecentCrashes
5
- } from "./chunk-T5RTPF2Q.js";
5
+ } from "./chunk-KJHMRAJU.js";
6
6
  import {
7
7
  ProviderRegistry
8
- } from "./chunk-UZISJ3KZ.js";
8
+ } from "./chunk-QMXC327F.js";
9
9
  import {
10
10
  getStatsSnapshot,
11
11
  getTopFailingTools,
12
12
  getTopUsedTools,
13
13
  resetStats
14
- } from "./chunk-JLZ5QA2I.js";
15
- import "./chunk-SMFRJCXB.js";
14
+ } from "./chunk-2WV6AGHM.js";
15
+ import "./chunk-XPBEJB27.js";
16
16
  import {
17
17
  ConfigManager
18
- } from "./chunk-BENRC3OO.js";
18
+ } from "./chunk-TW47X5AO.js";
19
19
  import "./chunk-TZQHYZKT.js";
20
20
  import {
21
21
  DEV_STATE_FILE_NAME,
22
22
  MEMORY_FILE_NAME,
23
23
  VERSION
24
- } from "./chunk-SCQOEDVL.js";
24
+ } from "./chunk-QNUVZO5X.js";
25
25
  import "./chunk-IW3Q7AE5.js";
26
26
 
27
27
  // src/diagnostics/doctor-cli.ts
@@ -36,7 +36,7 @@ import {
36
36
  VERSION,
37
37
  buildUserIdentityPrompt,
38
38
  runTestsTool
39
- } from "./chunk-26YWVRLT.js";
39
+ } from "./chunk-BQIV4ZQT.js";
40
40
  import {
41
41
  hasSemanticIndex,
42
42
  semanticSearch
@@ -623,8 +623,8 @@ var BaseProvider = class {
623
623
  * 将 Message[] 转换为 OpenAI API 格式的消息数组。
624
624
  * content 为 string 时直接传递;为 MessageContentPart[] 时保留数组格式(vision 请求)。
625
625
  *
626
- * 自动跳过 role='tool' 和带 toolCalls 的 assistant 消息——
627
- * 这些是 v0.4.60+ 持久化的工具历史,由 _extraMessages 机制单独注入。
626
+ * 基类只提供降级格式:跳过 role='tool' 和带 toolCalls 的 assistant 消息。
627
+ * 需要完整工具历史 roundtrip provider 应覆盖消息构造逻辑,例如 OpenAI-compatible provider。
628
628
  */
629
629
  normalizeMessages(messages) {
630
630
  return messages.filter((m) => m.role !== "tool" && !m.toolCalls).map((m) => ({ role: m.role, content: m.content }));
@@ -636,7 +636,14 @@ function isFileWriteTool(name) {
636
636
  return name === "write_file" || name === "edit_file" || name === "notebook_edit";
637
637
  }
638
638
  function getDangerLevel(toolName, args) {
639
- if (toolName.startsWith("mcp__")) return "safe";
639
+ if (toolName.startsWith("mcp__")) {
640
+ const rawName = toolName.split("__").pop() ?? "";
641
+ const normalized = rawName.toLowerCase().replace(/[^a-z0-9]+/g, "_");
642
+ if (/^(read|get|list|search|find|query|fetch|status|describe|inspect|show|lookup)(?:_|$)/.test(normalized)) {
643
+ return "safe";
644
+ }
645
+ return "write";
646
+ }
640
647
  if (toolName === "bash") {
641
648
  const cmd = String(args["command"] ?? "");
642
649
  if (/\brm\s+[^\n]*(?:-\w*[rRfF]\w*|--recursive|--force)\b/.test(cmd)) return "destructive";
@@ -4725,7 +4732,7 @@ var SessionManager = class {
4725
4732
  // src/tools/builtin/bash.ts
4726
4733
  import { spawn } from "child_process";
4727
4734
  import { existsSync as existsSync4, readdirSync as readdirSync2, statSync } from "fs";
4728
- import { platform } from "os";
4735
+ import { platform as platform2 } from "os";
4729
4736
  import { resolve } from "path";
4730
4737
 
4731
4738
  // src/tools/undo-stack.ts
@@ -4863,9 +4870,32 @@ function runWithSessionKey(sessionKey, fn) {
4863
4870
  return als.run({ sessionKey: key }, fn);
4864
4871
  }
4865
4872
 
4866
- // src/tools/builtin/bash.ts
4873
+ // src/tools/win-shell.ts
4874
+ import { platform } from "os";
4867
4875
  var IS_WINDOWS = platform() === "win32";
4868
- var SHELL = IS_WINDOWS ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
4876
+ var WIN_UTF8_PREAMBLE = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; $OutputEncoding = [System.Text.Encoding]::UTF8; ";
4877
+ function shellInvocation(command) {
4878
+ if (IS_WINDOWS) {
4879
+ return {
4880
+ shell: "powershell.exe",
4881
+ args: ["-NoProfile", "-NonInteractive", "-Command", WIN_UTF8_PREAMBLE + command]
4882
+ };
4883
+ }
4884
+ return { shell: process.env["SHELL"] ?? "/bin/bash", args: ["-c", command] };
4885
+ }
4886
+ function decodeWindowsBuffer(buf) {
4887
+ const utf8 = buf.toString("utf-8");
4888
+ if (!utf8.includes("\uFFFD")) return utf8;
4889
+ try {
4890
+ return new TextDecoder("gbk").decode(buf);
4891
+ } catch {
4892
+ return utf8;
4893
+ }
4894
+ }
4895
+
4896
+ // src/tools/builtin/bash.ts
4897
+ var IS_WINDOWS2 = platform2() === "win32";
4898
+ var SHELL = IS_WINDOWS2 ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
4869
4899
  var cwdBySession = /* @__PURE__ */ new Map();
4870
4900
  function getCwd() {
4871
4901
  const key = getCurrentSessionKey();
@@ -4882,7 +4912,7 @@ function setCwd(next) {
4882
4912
  var bashTool = {
4883
4913
  definition: {
4884
4914
  name: "bash",
4885
- description: IS_WINDOWS ? `Execute commands in PowerShell. Supports mkdir, ls, cat, python, etc.
4915
+ description: IS_WINDOWS2 ? `Execute commands in PowerShell. Supports mkdir, ls, cat, python, etc.
4886
4916
  Important rules:
4887
4917
  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".
4888
4918
  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.
@@ -4898,7 +4928,7 @@ Important rules:
4898
4928
  parameters: {
4899
4929
  command: {
4900
4930
  type: "string",
4901
- 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"`,
4931
+ 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"`,
4902
4932
  required: true
4903
4933
  },
4904
4934
  cwd: {
@@ -4950,9 +4980,9 @@ Important rules:
4950
4980
  setCwd(resolved);
4951
4981
  }
4952
4982
  let actualCommand;
4953
- if (IS_WINDOWS) {
4983
+ if (IS_WINDOWS2) {
4954
4984
  const fixedCommand = fixWindowsDeleteCommand(command);
4955
- actualCommand = `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; $OutputEncoding = [System.Text.Encoding]::UTF8; ${fixedCommand}`;
4985
+ actualCommand = WIN_UTF8_PREAMBLE + fixedCommand;
4956
4986
  } else {
4957
4987
  actualCommand = command;
4958
4988
  }
@@ -4996,7 +5026,7 @@ Important rules:
4996
5026
  }
4997
5027
  updateCwdFromCommand(command, effectiveCwd);
4998
5028
  pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
4999
- const result = Buffer.isBuffer(stdout) ? IS_WINDOWS ? decodeWindowsBuffer(stdout) : stdout.toString("utf-8") : String(stdout ?? "");
5029
+ const result = Buffer.isBuffer(stdout) ? IS_WINDOWS2 ? decodeWindowsBuffer(stdout) : stdout.toString("utf-8") : String(stdout ?? "");
5000
5030
  return result || "(command completed with no output)";
5001
5031
  } catch (err) {
5002
5032
  pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
@@ -5021,8 +5051,8 @@ How to recover (pick ONE \u2014 do NOT retry the same command):
5021
5051
  );
5022
5052
  }
5023
5053
  if ("status" in execErr && execErr.status !== void 0) {
5024
- const stderr = IS_WINDOWS && Buffer.isBuffer(execErr.stderr) ? decodeWindowsBuffer(execErr.stderr).trim() : execErr.stderr?.toString().trim() ?? "";
5025
- const stdout = IS_WINDOWS && Buffer.isBuffer(execErr.stdout) ? decodeWindowsBuffer(execErr.stdout).trim() : execErr.stdout?.toString().trim() ?? "";
5054
+ const stderr = IS_WINDOWS2 && Buffer.isBuffer(execErr.stderr) ? decodeWindowsBuffer(execErr.stderr).trim() : execErr.stderr?.toString().trim() ?? "";
5055
+ const stdout = IS_WINDOWS2 && Buffer.isBuffer(execErr.stdout) ? decodeWindowsBuffer(execErr.stdout).trim() : execErr.stdout?.toString().trim() ?? "";
5026
5056
  const combined = [stdout, stderr].filter(Boolean).join("\n");
5027
5057
  const hint = buildErrorHint(command, combined);
5028
5058
  throw new ToolError(
@@ -5056,23 +5086,14 @@ function fixWindowsDeleteCommand(command) {
5056
5086
  }
5057
5087
  );
5058
5088
  }
5059
- function decodeWindowsBuffer(buf) {
5060
- const utf8 = buf.toString("utf-8");
5061
- if (!utf8.includes("\uFFFD")) return utf8;
5062
- try {
5063
- return new TextDecoder("gbk").decode(buf);
5064
- } catch {
5065
- return utf8;
5066
- }
5067
- }
5068
5089
  function buildErrorHint(command, stderr) {
5069
5090
  const hints = [];
5070
- if (IS_WINDOWS && /\|\|/.test(command) && /(\|\||is not a valid argument|不是此版本中的有效|ParserError|Unexpected token)/i.test(stderr)) {
5091
+ if (IS_WINDOWS2 && /\|\|/.test(command) && /(\|\||is not a valid argument|不是此版本中的有效|ParserError|Unexpected token)/i.test(stderr)) {
5071
5092
  hints.push(
5072
5093
  `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.`
5073
5094
  );
5074
5095
  }
5075
- if (IS_WINDOWS && /\bpython3\b/.test(command)) {
5096
+ if (IS_WINDOWS2 && /\bpython3\b/.test(command)) {
5076
5097
  const trivialStderr = stderr.trim().length === 0 || /^[·\s]*exit 1\b/i.test(stderr.trim());
5077
5098
  if (/(not recognized|is not recognized|无法将.*识别)/i.test(stderr) || trivialStderr) {
5078
5099
  hints.push(
@@ -5080,27 +5101,27 @@ function buildErrorHint(command, stderr) {
5080
5101
  );
5081
5102
  }
5082
5103
  }
5083
- if (IS_WINDOWS && /^\s*ssh\b/.test(command) && /\\"/.test(command) && /(syntax error|ERROR:|ParserError|unexpected|parser|意外的|语法|不是此版本|所在位置\s+行)/i.test(stderr)) {
5104
+ if (IS_WINDOWS2 && /^\s*ssh\b/.test(command) && /\\"/.test(command) && /(syntax error|ERROR:|ParserError|unexpected|parser|意外的|语法|不是此版本|所在位置\s+行)/i.test(stderr)) {
5084
5105
  hints.push(
5085
5106
  `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.`
5086
5107
  );
5087
5108
  }
5088
- if (IS_WINDOWS && /\bmkdir\b/.test(command) && /\s-p\b/.test(command) && /(已存在|already exists|ItemExistsUnauthorizedAccessError|exists)/i.test(stderr)) {
5109
+ if (IS_WINDOWS2 && /\bmkdir\b/.test(command) && /\s-p\b/.test(command) && /(已存在|already exists|ItemExistsUnauthorizedAccessError|exists)/i.test(stderr)) {
5089
5110
  hints.push(
5090
5111
  `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').`
5091
5112
  );
5092
5113
  }
5093
- if (IS_WINDOWS && /Remove-Item/i.test(command) && /cannot find path|no such file|exit 1/i.test(stderr || command)) {
5114
+ if (IS_WINDOWS2 && /Remove-Item/i.test(command) && /cannot find path|no such file|exit 1/i.test(stderr || command)) {
5094
5115
  hints.push(
5095
5116
  `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.`
5096
5117
  );
5097
5118
  }
5098
- 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)) {
5119
+ 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)) {
5099
5120
  hints.push(
5100
5121
  `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.`
5101
5122
  );
5102
5123
  }
5103
- if (IS_WINDOWS && /\bWrite-(?:Content|File)\b/i.test(command) && /(not recognized|无法将.*识别|不是.*cmdlet|CommandNotFoundException)/i.test(stderr)) {
5124
+ if (IS_WINDOWS2 && /\bWrite-(?:Content|File)\b/i.test(command) && /(not recognized|无法将.*识别|不是.*cmdlet|CommandNotFoundException)/i.test(stderr)) {
5104
5125
  hints.push(
5105
5126
  `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'.`
5106
5127
  );
@@ -5121,6 +5142,18 @@ function detectBlockingCommand(command) {
5121
5142
  if (logcat && !/(?:^|\s)-(?:d|t|c|g)\b/.test(logcat[1] ?? "")) {
5122
5143
  return blockingMessage("adb logcat", "stream device logs");
5123
5144
  }
5145
+ if (/\bStart-Process\b/i.test(command) && /\b(?:emulator|qemu[\w-]*)(?:\.exe)?\b/i.test(command)) {
5146
+ 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.
5147
+
5148
+ Use the 'task_create' tool instead so the process is tracked and stoppable:
5149
+ task_create(command: "<sdk>\\emulator\\emulator.exe -avd <name> -no-boot-anim", description: "launch emulator")
5150
+
5151
+ Then poll readiness with SHORT bash calls (these DO exit):
5152
+ adb devices # is the device listed?
5153
+ adb shell getprop sys.boot_completed # prints "1" once fully booted
5154
+
5155
+ [Do NOT launch long-running GUI processes via Start-Process in bash \u2014 use task_create.]`;
5156
+ }
5124
5157
  return null;
5125
5158
  }
5126
5159
  function blockingMessage(example, what) {
@@ -5200,7 +5233,7 @@ function pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd) {
5200
5233
  }
5201
5234
  function runShell(command, opts) {
5202
5235
  return new Promise((resolvePromise) => {
5203
- const shellArgs = IS_WINDOWS ? ["-NoProfile", "-NonInteractive", "-Command", command] : ["-c", command];
5236
+ const shellArgs = IS_WINDOWS2 ? ["-NoProfile", "-NonInteractive", "-Command", command] : ["-c", command];
5204
5237
  const child = spawn(SHELL, shellArgs, {
5205
5238
  cwd: opts.cwd,
5206
5239
  env: opts.env,
@@ -5263,7 +5296,7 @@ function runShell(command, opts) {
5263
5296
  }
5264
5297
  function killChild(child) {
5265
5298
  if (child.killed || child.exitCode !== null) return;
5266
- if (IS_WINDOWS && child.pid) {
5299
+ if (IS_WINDOWS2 && child.pid) {
5267
5300
  try {
5268
5301
  spawn("taskkill", ["/PID", String(child.pid), "/T", "/F"], {
5269
5302
  windowsHide: true,
@@ -5688,7 +5721,7 @@ ${content}`;
5688
5721
  };
5689
5722
 
5690
5723
  // src/tools/builtin/write-file.ts
5691
- import { writeFileSync as writeFileSync3, appendFileSync, mkdirSync as mkdirSync4 } from "fs";
5724
+ import { appendFileSync, mkdirSync as mkdirSync4 } from "fs";
5692
5725
  import { dirname as dirname3 } from "path";
5693
5726
 
5694
5727
  // src/tools/executor.ts
@@ -6211,8 +6244,8 @@ var ToolExecutor = class {
6211
6244
  /** confirm() 的取消回调,由 SIGINT handler 调用 */
6212
6245
  cancelConfirmFn = null;
6213
6246
  /**
6214
- * 会话级 auto-approve:跳过所有 write/destructive 确认(仅当前会话有效)。
6215
- * 通过 /yolo 命令切换。destructive 操作仍会显示警告但不阻塞。
6247
+ * 会话级 auto-approve:跳过 write 确认(仅当前会话有效)。
6248
+ * destructive 操作仍然必须逐次确认。
6216
6249
  */
6217
6250
  sessionAutoApprove = false;
6218
6251
  /**
@@ -6296,7 +6329,7 @@ var ToolExecutor = class {
6296
6329
  }
6297
6330
  }
6298
6331
  }
6299
- if (this.sessionAutoApprove && dangerLevel !== "safe") {
6332
+ if (this.sessionAutoApprove && dangerLevel === "write") {
6300
6333
  this.printToolCall(call);
6301
6334
  if (dangerLevel === "write") this.printDiffPreview(call);
6302
6335
  console.log(theme.warning(" \u26A1 Auto-approved (session /yolo mode)"));
@@ -6825,7 +6858,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
6825
6858
  if (appendMode) {
6826
6859
  appendFileSync(filePath, content, encoding);
6827
6860
  } else {
6828
- writeFileSync3(filePath, content, encoding);
6861
+ atomicWriteFileSync(filePath, content);
6829
6862
  }
6830
6863
  const lines = content.split("\n").length;
6831
6864
  const mode = appendMode ? "appended" : "written";
@@ -6841,7 +6874,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
6841
6874
  };
6842
6875
 
6843
6876
  // src/tools/builtin/edit-file.ts
6844
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync8 } from "fs";
6877
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
6845
6878
 
6846
6879
  // src/tools/builtin/patch-apply.ts
6847
6880
  function parseUnifiedDiff(patch) {
@@ -7240,7 +7273,7 @@ Note: Path can be absolute or relative to cwd.`,
7240
7273
  if (res.ok && res.appliedCount > 0 && res.content !== void 0) {
7241
7274
  undoStack.push(filePath, `edit_file (patch ${res.appliedCount}/${hunks.length}): ${filePath}`);
7242
7275
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
7243
- writeFileSync4(filePath, res.content, encoding);
7276
+ atomicWriteFileSync(filePath, res.content);
7244
7277
  }
7245
7278
  return lines.join("\n");
7246
7279
  }
@@ -7274,7 +7307,7 @@ Note: Path can be absolute or relative to cwd.`,
7274
7307
  if (writeChanges) {
7275
7308
  undoStack.push(filePath, `edit_file (batch ${appliedCount}/${edits.length}): ${filePath}`);
7276
7309
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
7277
- writeFileSync4(filePath, working, encoding);
7310
+ atomicWriteFileSync(filePath, working);
7278
7311
  }
7279
7312
  const lines = [];
7280
7313
  if (anyFailed && stopOnError) {
@@ -7312,7 +7345,7 @@ Please read the file first and use exact text.`;
7312
7345
  const label = res.info?.mode === "ignore_whitespace" ? "edit_file (ws-replace)" : res.info?.mode === "replace_all" ? "edit_file (replace_all)" : "edit_file (replace)";
7313
7346
  undoStack.push(filePath, `${label}: ${filePath}`);
7314
7347
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
7315
- writeFileSync4(filePath, res.content, encoding);
7348
+ atomicWriteFileSync(filePath, res.content);
7316
7349
  if (res.info?.mode === "replace_all") {
7317
7350
  return `Successfully edited ${filePath}${modeLabel}
7318
7351
  Replaced: ${res.info.replacedCount} occurrence(s) of ${truncatePreview(oldStr)}
@@ -7336,7 +7369,7 @@ Please read the file first and use exact text.`;
7336
7369
  undoStack.push(filePath, `edit_file (insert): ${filePath}`);
7337
7370
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
7338
7371
  lines.splice(afterLine, 0, content);
7339
- writeFileSync4(filePath, lines.join("\n"), encoding);
7372
+ atomicWriteFileSync(filePath, lines.join("\n"));
7340
7373
  return `Successfully inserted ${content.split("\n").length} line(s) after line ${afterLine} in ${filePath}`;
7341
7374
  }
7342
7375
  if (args["delete_from_line"] !== void 0) {
@@ -7352,7 +7385,7 @@ Please read the file first and use exact text.`;
7352
7385
  undoStack.push(filePath, `edit_file (delete): ${filePath}`);
7353
7386
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
7354
7387
  const deleted = lines.splice(fromLine - 1, toLine - fromLine + 1);
7355
- writeFileSync4(filePath, lines.join("\n"), encoding);
7388
+ atomicWriteFileSync(filePath, lines.join("\n"));
7356
7389
  return `Successfully deleted lines ${fromLine}-${toLine} (${deleted.length} lines) from ${filePath}`;
7357
7390
  }
7358
7391
  throw new ToolError(
@@ -7837,8 +7870,8 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
7837
7870
 
7838
7871
  // src/tools/builtin/run-interactive.ts
7839
7872
  import { spawn as spawn2 } from "child_process";
7840
- import { platform as platform2 } from "os";
7841
- var IS_WINDOWS2 = platform2() === "win32";
7873
+ import { platform as platform3 } from "os";
7874
+ var IS_WINDOWS3 = platform3() === "win32";
7842
7875
  var runInteractiveTool = {
7843
7876
  definition: {
7844
7877
  name: "run_interactive",
@@ -7918,7 +7951,7 @@ var runInteractiveTool = {
7918
7951
  let lineIdx = 0;
7919
7952
  const writeNextLine = () => {
7920
7953
  if (lineIdx < stdinLines.length && !child.stdin.destroyed) {
7921
- const line = stdinLines[lineIdx++] + (IS_WINDOWS2 ? "\r\n" : "\n");
7954
+ const line = stdinLines[lineIdx++] + (IS_WINDOWS3 ? "\r\n" : "\n");
7922
7955
  const canContinue = child.stdin.write(line);
7923
7956
  if (canContinue) {
7924
7957
  setTimeout(writeNextLine, 150);
@@ -8557,7 +8590,7 @@ ${preamble}`;
8557
8590
  }
8558
8591
 
8559
8592
  // src/tools/builtin/save-last-response.ts
8560
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, unlinkSync as unlinkSync3, rmdirSync as rmdirSync2 } from "fs";
8593
+ import { mkdirSync as mkdirSync5, unlinkSync as unlinkSync3, rmdirSync as rmdirSync2 } from "fs";
8561
8594
  import { dirname as dirname4 } from "path";
8562
8595
  var lastResponseStore = { content: "" };
8563
8596
  function cleanupRejectedTeeFile(filePath) {
@@ -8608,7 +8641,7 @@ Any of these triggers means use save_last_response, NOT write_file:
8608
8641
  }
8609
8642
  undoStack.push(filePath, `save_last_response: ${filePath}`);
8610
8643
  mkdirSync5(dirname4(filePath), { recursive: true });
8611
- writeFileSync5(filePath, content, "utf-8");
8644
+ atomicWriteFileSync(filePath, content);
8612
8645
  const lines = content.split("\n").length;
8613
8646
  return `File saved: ${filePath} (${lines} lines, ${content.length} bytes)`;
8614
8647
  }
@@ -9231,16 +9264,29 @@ var spawnAgentTool = {
9231
9264
  // src/tools/builtin/task-manager.ts
9232
9265
  import { spawn as spawn3 } from "child_process";
9233
9266
  import { randomUUID } from "crypto";
9234
- import { platform as platform3 } from "os";
9267
+ import { platform as platform4 } from "os";
9235
9268
  var MAX_OUTPUT_CHARS = 1e4;
9269
+ var MAX_OUTPUT_BYTES = 64 * 1024;
9236
9270
  var MAX_TASKS = 20;
9237
9271
  var tasks = /* @__PURE__ */ new Map();
9238
- function appendOutput(task, field, chunk) {
9239
- task[field] += chunk;
9240
- if (task[field].length > MAX_OUTPUT_CHARS) {
9241
- task[field] = "... [truncated] ...\n" + task[field].slice(-MAX_OUTPUT_CHARS);
9272
+ function capChunks(chunks) {
9273
+ let total = 0;
9274
+ for (const c of chunks) total += c.length;
9275
+ while (total > MAX_OUTPUT_BYTES && chunks.length > 1) {
9276
+ total -= chunks.shift().length;
9242
9277
  }
9243
9278
  }
9279
+ function decodeField(chunks) {
9280
+ const buf = Buffer.concat(chunks);
9281
+ const text = IS_WINDOWS ? decodeWindowsBuffer(buf) : buf.toString("utf-8");
9282
+ return text.length > MAX_OUTPUT_CHARS ? "... [truncated] ...\n" + text.slice(-MAX_OUTPUT_CHARS) : text;
9283
+ }
9284
+ function appendOutput(task, field, chunk) {
9285
+ const chunks = field === "stdout" ? task._stdoutChunks : task._stderrChunks;
9286
+ chunks.push(chunk);
9287
+ capChunks(chunks);
9288
+ task[field] = decodeField(chunks);
9289
+ }
9244
9290
  function createTask(command, description) {
9245
9291
  if (tasks.size >= MAX_TASKS) {
9246
9292
  for (const [id2, t] of tasks) {
@@ -9251,13 +9297,11 @@ function createTask(command, description) {
9251
9297
  }
9252
9298
  }
9253
9299
  const id = randomUUID().slice(0, 8);
9254
- const isWin = platform3() === "win32";
9255
- const shell = isWin ? "cmd" : "sh";
9256
- const shellFlag = isWin ? "/c" : "-c";
9257
- const proc = spawn3(shell, [shellFlag, command], {
9300
+ const { shell, args } = shellInvocation(command);
9301
+ const proc = spawn3(shell, args, {
9258
9302
  stdio: ["ignore", "pipe", "pipe"],
9259
9303
  detached: false,
9260
- env: { ...process.env, PYTHONUTF8: "1" }
9304
+ env: { ...process.env, PYTHONUTF8: "1", PYTHONIOENCODING: "utf-8" }
9261
9305
  });
9262
9306
  const task = {
9263
9307
  id,
@@ -9269,10 +9313,12 @@ function createTask(command, description) {
9269
9313
  status: "running",
9270
9314
  exitCode: null,
9271
9315
  stdout: "",
9272
- stderr: ""
9316
+ stderr: "",
9317
+ _stdoutChunks: [],
9318
+ _stderrChunks: []
9273
9319
  };
9274
- proc.stdout?.on("data", (chunk) => appendOutput(task, "stdout", chunk.toString("utf-8")));
9275
- proc.stderr?.on("data", (chunk) => appendOutput(task, "stderr", chunk.toString("utf-8")));
9320
+ proc.stdout?.on("data", (chunk) => appendOutput(task, "stdout", chunk));
9321
+ proc.stderr?.on("data", (chunk) => appendOutput(task, "stderr", chunk));
9276
9322
  proc.on("close", (code) => {
9277
9323
  task.endTime = Date.now();
9278
9324
  task.exitCode = code;
@@ -9288,7 +9334,9 @@ Process error: ${err.message}`;
9288
9334
  return task;
9289
9335
  }
9290
9336
  function listTasks() {
9291
- return [...tasks.values()].map(({ process: _p, ...rest }) => rest);
9337
+ return [...tasks.values()].map(
9338
+ ({ process: _p, _stdoutChunks: _o, _stderrChunks: _e, ...rest }) => rest
9339
+ );
9292
9340
  }
9293
9341
  function getTaskOutput(id) {
9294
9342
  const task = tasks.get(id);
@@ -9300,7 +9348,7 @@ function stopTask(id) {
9300
9348
  if (!task || task.status !== "running") return false;
9301
9349
  try {
9302
9350
  const proc = task.process;
9303
- if (platform3() === "win32" && proc.pid) {
9351
+ if (platform4() === "win32" && proc.pid) {
9304
9352
  try {
9305
9353
  spawn3("taskkill", ["/PID", String(proc.pid), "/T", "/F"], {
9306
9354
  windowsHide: true,
@@ -9335,7 +9383,7 @@ function stopTask(id) {
9335
9383
  var taskCreateTool = {
9336
9384
  definition: {
9337
9385
  name: "task_create",
9338
- 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.`,
9386
+ 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.`,
9339
9387
  parameters: {
9340
9388
  command: {
9341
9389
  type: "string",
@@ -11299,7 +11347,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11299
11347
  }
11300
11348
  }
11301
11349
  this.sendToolCallStart(call);
11302
- if (this.sessionAutoApprove && (dangerLevel === "write" || dangerLevel === "destructive")) {
11350
+ if (this.sessionAutoApprove && dangerLevel === "write") {
11303
11351
  try {
11304
11352
  const rawContent = await runTool(tool, call.arguments, call.name);
11305
11353
  const content = truncateOutput(rawContent, call.name);
@@ -11403,7 +11451,7 @@ import { existsSync as existsSync19, readFileSync as readFileSync13 } from "fs";
11403
11451
  import { join as join12 } from "path";
11404
11452
 
11405
11453
  // src/repl/dev-state.ts
11406
- import { existsSync as existsSync18, readFileSync as readFileSync12, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4, mkdirSync as mkdirSync9 } from "fs";
11454
+ import { existsSync as existsSync18, readFileSync as readFileSync12, writeFileSync as writeFileSync3, unlinkSync as unlinkSync4, mkdirSync as mkdirSync9 } from "fs";
11407
11455
  import { join as join11 } from "path";
11408
11456
  import { homedir as homedir6 } from "os";
11409
11457
  function getDevStatePath() {
@@ -11681,7 +11729,7 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
11681
11729
  }
11682
11730
 
11683
11731
  // src/web/session-handler.ts
11684
- import { existsSync as existsSync21, readFileSync as readFileSync14, appendFileSync as appendFileSync3, writeFileSync as writeFileSync7, mkdirSync as mkdirSync10, readdirSync as readdirSync9, statSync as statSync8, createWriteStream } from "fs";
11732
+ import { existsSync as existsSync21, readFileSync as readFileSync14, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync10, readdirSync as readdirSync9, statSync as statSync8, createWriteStream } from "fs";
11685
11733
  import { join as join15, resolve as resolve5, dirname as dirname5 } from "path";
11686
11734
  import { execSync as execSync3 } from "child_process";
11687
11735
 
@@ -13045,7 +13093,7 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
13045
13093
  try {
13046
13094
  mkdirSync10(dirname5(pendingTeeSave), { recursive: true });
13047
13095
  const bodyToSave = stripOuterCodeFence(content);
13048
- writeFileSync7(pendingTeeSave, bodyToSave, "utf-8");
13096
+ atomicWriteFileSync(pendingTeeSave, bodyToSave);
13049
13097
  undoStack.push(pendingTeeSave, `save_last_response (deferred): ${pendingTeeSave}`);
13050
13098
  const lines = bodyToSave.split("\n").length;
13051
13099
  this.send({ type: "info", message: `\u2705 Saved (from reply): ${pendingTeeSave} (${lines} lines, ${bodyToSave.length} chars)` });
@@ -13220,7 +13268,7 @@ ${summaryContent}`,
13220
13268
  let summary;
13221
13269
  try {
13222
13270
  mkdirSync10(dirname5(saveToFile), { recursive: true });
13223
- fileStream = createWriteStream(saveToFile, { encoding: "utf-8" });
13271
+ fileStream = createWriteStream(saveToFile);
13224
13272
  const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
13225
13273
  const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
13226
13274
  const chatRequest = {
@@ -13260,7 +13308,7 @@ ${summaryContent}`,
13260
13308
  fullContent = "";
13261
13309
  } else {
13262
13310
  if (verdict.kind === "salvaged" || verdict.kind === "fallback") {
13263
- writeFileSync7(saveToFile, verdict.content, "utf-8");
13311
+ atomicWriteFileSync(saveToFile, verdict.content);
13264
13312
  fullContent = verdict.content;
13265
13313
  }
13266
13314
  summary = verdict.summary;
@@ -13712,7 +13760,7 @@ ${activated.meta.description || ""}` });
13712
13760
  this.send({ type: "info", message: "\u{1F512} Auto-approve disabled \u2014 confirmations restored for this session." });
13713
13761
  } else {
13714
13762
  this.toolExecutor.sessionAutoApprove = true;
13715
- this.send({ type: "info", message: "\u26A1 YOLO mode ON \u2014 all write/destructive tools auto-approved for this session.\nUse /yolo off to re-enable confirmations." });
13763
+ this.send({ type: "info", message: "\u26A1 YOLO mode ON \u2014 write tools auto-approved; destructive tools still require confirmation.\nUse /yolo off to re-enable write confirmations." });
13716
13764
  }
13717
13765
  break;
13718
13766
  }
@@ -14325,7 +14373,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14325
14373
  case "test": {
14326
14374
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
14327
14375
  try {
14328
- const { executeTests } = await import("./run-tests-6G2F7OSC.js");
14376
+ const { executeTests } = await import("./run-tests-NBCA4KEG.js");
14329
14377
  const argStr = args.join(" ").trim();
14330
14378
  let testArgs = {};
14331
14379
  if (argStr) {
@@ -14354,7 +14402,7 @@ Use /init --force to overwrite.` });
14354
14402
  const projectInfo = this.scanProject(cwd);
14355
14403
  const prompt = this.buildInitPrompt(projectInfo, cwd);
14356
14404
  const content = await this.chatOnce(prompt, { temperature: 0.3, maxTokens: 4096 });
14357
- writeFileSync7(targetPath, content, "utf-8");
14405
+ writeFileSync4(targetPath, content, "utf-8");
14358
14406
  this.send({ type: "info", message: `\u2713 Generated: ${targetPath} (${content.length} chars)
14359
14407
  Use /context reload to load it.` });
14360
14408
  } catch (err) {
@@ -14833,7 +14881,7 @@ Add .md files to create commands.` });
14833
14881
  const configDir = this.config.getConfigDir();
14834
14882
  const memPath = join15(configDir, MEMORY_FILE_NAME);
14835
14883
  try {
14836
- writeFileSync7(memPath, "", "utf-8");
14884
+ writeFileSync4(memPath, "", "utf-8");
14837
14885
  this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
14838
14886
  } catch (err) {
14839
14887
  this.send({ type: "error", message: `Failed to clear memory: ${err.message}` });
@@ -15309,7 +15357,7 @@ async function setupProxy(configProxy) {
15309
15357
  }
15310
15358
 
15311
15359
  // src/web/auth.ts
15312
- import { existsSync as existsSync22, readFileSync as readFileSync15, writeFileSync as writeFileSync8, mkdirSync as mkdirSync11, readdirSync as readdirSync10, copyFileSync } from "fs";
15360
+ import { existsSync as existsSync22, readFileSync as readFileSync15, writeFileSync as writeFileSync5, mkdirSync as mkdirSync11, readdirSync as readdirSync10, copyFileSync } from "fs";
15313
15361
  import { join as join16 } from "path";
15314
15362
  import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
15315
15363
  var USERS_FILE = "users.json";
@@ -15511,7 +15559,7 @@ var AuthManager = class {
15511
15559
  if (existsSync22(globalConfig)) {
15512
15560
  try {
15513
15561
  const content = readFileSync15(globalConfig, "utf-8");
15514
- writeFileSync8(join16(userDir, "config.json"), content, "utf-8");
15562
+ writeFileSync5(join16(userDir, "config.json"), content, "utf-8");
15515
15563
  } catch {
15516
15564
  }
15517
15565
  }
@@ -15519,7 +15567,7 @@ var AuthManager = class {
15519
15567
  if (existsSync22(globalMemory)) {
15520
15568
  try {
15521
15569
  const content = readFileSync15(globalMemory, "utf-8");
15522
- writeFileSync8(join16(userDir, "memory.md"), content, "utf-8");
15570
+ writeFileSync5(join16(userDir, "memory.md"), content, "utf-8");
15523
15571
  } catch {
15524
15572
  }
15525
15573
  }
@@ -16048,7 +16096,7 @@ async function startWebServer(options = {}) {
16048
16096
  handlers.set(tabId, handler);
16049
16097
  clearPreAuthTimer();
16050
16098
  console.log(` \u2713 User registered & connected: ${username} (tab: ${tabId.slice(0, 12)})`);
16051
- ws.send(JSON.stringify({ type: "auth_result", success: true, token: newToken, username }));
16099
+ ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
16052
16100
  return;
16053
16101
  }
16054
16102
  if (action === "token") {
@@ -16064,7 +16112,7 @@ async function startWebServer(options = {}) {
16064
16112
  handlers.set(tabId, handler);
16065
16113
  clearPreAuthTimer();
16066
16114
  console.log(` \u2713 Token auth: ${verifiedUser} (tab: ${tabId.slice(0, 12)})`);
16067
- ws.send(JSON.stringify({ type: "auth_result", success: true, token: clientToken, username: verifiedUser }));
16115
+ ws.send(JSON.stringify({ type: "auth_result", success: true, username: verifiedUser }));
16068
16116
  return;
16069
16117
  }
16070
16118
  if (action === "login") {
@@ -16079,7 +16127,7 @@ async function startWebServer(options = {}) {
16079
16127
  handlers.set(tabId, handler);
16080
16128
  clearPreAuthTimer();
16081
16129
  console.log(` \u2713 User logged in: ${username} (tab: ${tabId.slice(0, 12)})`);
16082
- ws.send(JSON.stringify({ type: "auth_result", success: true, token: loginToken, username }));
16130
+ ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
16083
16131
  return;
16084
16132
  }
16085
16133
  ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Unknown auth action" }));
@@ -16105,9 +16153,10 @@ async function startWebServer(options = {}) {
16105
16153
  });
16106
16154
  ws.on("close", () => {
16107
16155
  clearPreAuthTimer();
16108
- console.log(` \u2717 Tab disconnected: ${tabId.slice(0, 12)} (total: ${handlers.size - 1})`);
16156
+ const wasTracked = handlers.has(tabId);
16109
16157
  if (handler) handler.onDisconnect();
16110
- handlers.delete(tabId);
16158
+ if (wasTracked) handlers.delete(tabId);
16159
+ console.log(` \u2717 Tab disconnected: ${tabId.slice(0, 12)} (total: ${handlers.size})`);
16111
16160
  });
16112
16161
  ws.on("error", (err) => {
16113
16162
  console.error(` WebSocket error: ${err.message}`);
@@ -154,7 +154,7 @@ ${content}`);
154
154
  }
155
155
  }
156
156
  async function runTaskMode(config, providers, configManager, topic) {
157
- const { TaskOrchestrator } = await import("./task-orchestrator-PKZ2UTOK.js");
157
+ const { TaskOrchestrator } = await import("./task-orchestrator-2DTDVCMR.js");
158
158
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
159
159
  let interrupted = false;
160
160
  const onSigint = () => {