jinzd-ai-cli 0.4.205 → 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-RBPABHS7.js → batch-6PORMJWT.js} +2 -2
  3. package/dist/{chunk-XIYSFN4U.js → chunk-2WV6AGHM.js} +1 -1
  4. package/dist/{chunk-43H3D2PK.js → chunk-BQIV4ZQT.js} +1 -1
  5. package/dist/{chunk-JVYH66N2.js → chunk-KJHMRAJU.js} +1 -1
  6. package/dist/{chunk-ZSC4YLEJ.js → chunk-KOU4KX7J.js} +2 -2
  7. package/dist/{chunk-UZISJ3KZ.js → chunk-QMXC327F.js} +3 -3
  8. package/dist/{chunk-3RET7PL7.js → chunk-QNUVZO5X.js} +1 -1
  9. package/dist/{chunk-43MWRT2C.js → chunk-QQYSZMET.js} +20 -17
  10. package/dist/{chunk-VOV2PWZU.js → chunk-SFCNFX42.js} +1 -1
  11. package/dist/{chunk-TZ7SNVSG.js → chunk-TW47X5AO.js} +1 -1
  12. package/dist/{chunk-SMFRJCXB.js → chunk-XPBEJB27.js} +8 -1
  13. package/dist/{ci-ABE74AXH.js → ci-AYMV66P3.js} +9 -9
  14. package/dist/{constants-GMQMJRIO.js → constants-NHGTSHKT.js} +1 -1
  15. package/dist/{doctor-cli-M5PPO3L3.js → doctor-cli-WKH7T4AW.js} +6 -6
  16. package/dist/electron-server.js +43 -35
  17. package/dist/{hub-NCU5JNCK.js → hub-6R3M3NTC.js} +1 -1
  18. package/dist/index.js +29 -27
  19. package/dist/{run-tests-ZFAEFEYK.js → run-tests-5F2OBWIK.js} +2 -2
  20. package/dist/{run-tests-FGRWBNVF.js → run-tests-NBCA4KEG.js} +1 -1
  21. package/dist/{server-RKP7JRRE.js → server-37EXC4EB.js} +5 -5
  22. package/dist/{server-ML6ZL3J5.js → server-LDPQ3DLK.js} +23 -20
  23. package/dist/{task-orchestrator-LJP5XZSC.js → task-orchestrator-2DTDVCMR.js} +5 -5
  24. package/dist/{usage-YX2235W5.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-TZ7SNVSG.js";
4
+ } from "./chunk-TW47X5AO.js";
5
5
  import "./chunk-TZQHYZKT.js";
6
- import "./chunk-3RET7PL7.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-3RET7PL7.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.205";
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-3RET7PL7.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-43MWRT2C.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-3RET7PL7.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.205";
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-VOV2PWZU.js";
8
+ } from "./chunk-SFCNFX42.js";
9
9
  import {
10
10
  runTool
11
- } from "./chunk-XIYSFN4U.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-3RET7PL7.js";
29
+ } from "./chunk-QNUVZO5X.js";
30
30
  import {
31
31
  fileCheckpoints
32
32
  } from "./chunk-4BKXL7SM.js";
@@ -40,6 +40,9 @@ 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";
@@ -1047,7 +1050,7 @@ ${content}`;
1047
1050
  };
1048
1051
 
1049
1052
  // src/tools/builtin/write-file.ts
1050
- import { writeFileSync as writeFileSync2, appendFileSync, mkdirSync } from "fs";
1053
+ import { appendFileSync, mkdirSync } from "fs";
1051
1054
  import { dirname as dirname2 } from "path";
1052
1055
 
1053
1056
  // src/tools/executor.ts
@@ -1522,8 +1525,8 @@ var ToolExecutor = class {
1522
1525
  /** confirm() 的取消回调,由 SIGINT handler 调用 */
1523
1526
  cancelConfirmFn = null;
1524
1527
  /**
1525
- * 会话级 auto-approve:跳过所有 write/destructive 确认(仅当前会话有效)。
1526
- * 通过 /yolo 命令切换。destructive 操作仍会显示警告但不阻塞。
1528
+ * 会话级 auto-approve:跳过 write 确认(仅当前会话有效)。
1529
+ * destructive 操作仍然必须逐次确认。
1527
1530
  */
1528
1531
  sessionAutoApprove = false;
1529
1532
  /**
@@ -1607,7 +1610,7 @@ var ToolExecutor = class {
1607
1610
  }
1608
1611
  }
1609
1612
  }
1610
- if (this.sessionAutoApprove && dangerLevel !== "safe") {
1613
+ if (this.sessionAutoApprove && dangerLevel === "write") {
1611
1614
  this.printToolCall(call);
1612
1615
  if (dangerLevel === "write") this.printDiffPreview(call);
1613
1616
  console.log(theme.warning(" \u26A1 Auto-approved (session /yolo mode)"));
@@ -2136,7 +2139,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
2136
2139
  if (appendMode) {
2137
2140
  appendFileSync(filePath, content, encoding);
2138
2141
  } else {
2139
- writeFileSync2(filePath, content, encoding);
2142
+ atomicWriteFileSync(filePath, content);
2140
2143
  }
2141
2144
  const lines = content.split("\n").length;
2142
2145
  const mode = appendMode ? "appended" : "written";
@@ -2152,7 +2155,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
2152
2155
  };
2153
2156
 
2154
2157
  // src/tools/builtin/edit-file.ts
2155
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
2158
+ import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
2156
2159
 
2157
2160
  // src/tools/builtin/patch-apply.ts
2158
2161
  function parseUnifiedDiff(patch) {
@@ -2551,7 +2554,7 @@ Note: Path can be absolute or relative to cwd.`,
2551
2554
  if (res.ok && res.appliedCount > 0 && res.content !== void 0) {
2552
2555
  undoStack.push(filePath, `edit_file (patch ${res.appliedCount}/${hunks.length}): ${filePath}`);
2553
2556
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
2554
- writeFileSync3(filePath, res.content, encoding);
2557
+ atomicWriteFileSync(filePath, res.content);
2555
2558
  }
2556
2559
  return lines.join("\n");
2557
2560
  }
@@ -2585,7 +2588,7 @@ Note: Path can be absolute or relative to cwd.`,
2585
2588
  if (writeChanges) {
2586
2589
  undoStack.push(filePath, `edit_file (batch ${appliedCount}/${edits.length}): ${filePath}`);
2587
2590
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
2588
- writeFileSync3(filePath, working, encoding);
2591
+ atomicWriteFileSync(filePath, working);
2589
2592
  }
2590
2593
  const lines = [];
2591
2594
  if (anyFailed && stopOnError) {
@@ -2623,7 +2626,7 @@ Please read the file first and use exact text.`;
2623
2626
  const label = res.info?.mode === "ignore_whitespace" ? "edit_file (ws-replace)" : res.info?.mode === "replace_all" ? "edit_file (replace_all)" : "edit_file (replace)";
2624
2627
  undoStack.push(filePath, `${label}: ${filePath}`);
2625
2628
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
2626
- writeFileSync3(filePath, res.content, encoding);
2629
+ atomicWriteFileSync(filePath, res.content);
2627
2630
  if (res.info?.mode === "replace_all") {
2628
2631
  return `Successfully edited ${filePath}${modeLabel}
2629
2632
  Replaced: ${res.info.replacedCount} occurrence(s) of ${truncatePreview(oldStr)}
@@ -2647,7 +2650,7 @@ Please read the file first and use exact text.`;
2647
2650
  undoStack.push(filePath, `edit_file (insert): ${filePath}`);
2648
2651
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
2649
2652
  lines.splice(afterLine, 0, content);
2650
- writeFileSync3(filePath, lines.join("\n"), encoding);
2653
+ atomicWriteFileSync(filePath, lines.join("\n"));
2651
2654
  return `Successfully inserted ${content.split("\n").length} line(s) after line ${afterLine} in ${filePath}`;
2652
2655
  }
2653
2656
  if (args["delete_from_line"] !== void 0) {
@@ -2663,7 +2666,7 @@ Please read the file first and use exact text.`;
2663
2666
  undoStack.push(filePath, `edit_file (delete): ${filePath}`);
2664
2667
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
2665
2668
  const deleted = lines.splice(fromLine - 1, toLine - fromLine + 1);
2666
- writeFileSync3(filePath, lines.join("\n"), encoding);
2669
+ atomicWriteFileSync(filePath, lines.join("\n"));
2667
2670
  return `Successfully deleted lines ${fromLine}-${toLine} (${deleted.length} lines) from ${filePath}`;
2668
2671
  }
2669
2672
  throw new ToolError(
@@ -3868,7 +3871,7 @@ ${preamble}`;
3868
3871
  }
3869
3872
 
3870
3873
  // src/tools/builtin/save-last-response.ts
3871
- 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";
3872
3875
  import { dirname as dirname3 } from "path";
3873
3876
  var lastResponseStore = { content: "" };
3874
3877
  function cleanupRejectedTeeFile(filePath) {
@@ -3919,7 +3922,7 @@ Any of these triggers means use save_last_response, NOT write_file:
3919
3922
  }
3920
3923
  undoStack.push(filePath, `save_last_response: ${filePath}`);
3921
3924
  mkdirSync2(dirname3(filePath), { recursive: true });
3922
- writeFileSync4(filePath, content, "utf-8");
3925
+ atomicWriteFileSync(filePath, content);
3923
3926
  const lines = content.split("\n").length;
3924
3927
  return `File saved: ${filePath} (${lines} lines, ${content.length} bytes)`;
3925
3928
  }
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-3RET7PL7.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-3RET7PL7.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-TZ7SNVSG.js";
13
+ } from "./chunk-TW47X5AO.js";
14
14
  import "./chunk-TZQHYZKT.js";
15
15
  import {
16
16
  VERSION
17
- } from "./chunk-3RET7PL7.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-3RET7PL7.js";
39
+ } from "./chunk-QNUVZO5X.js";
40
40
  export {
41
41
  AGENTIC_BEHAVIOR_GUIDELINE,
42
42
  APP_NAME,
@@ -2,26 +2,26 @@
2
2
  import {
3
3
  getConfigDirUsage,
4
4
  listRecentCrashes
5
- } from "./chunk-JVYH66N2.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-XIYSFN4U.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-TZ7SNVSG.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-3RET7PL7.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-43H3D2PK.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";
@@ -5714,7 +5721,7 @@ ${content}`;
5714
5721
  };
5715
5722
 
5716
5723
  // src/tools/builtin/write-file.ts
5717
- import { writeFileSync as writeFileSync3, appendFileSync, mkdirSync as mkdirSync4 } from "fs";
5724
+ import { appendFileSync, mkdirSync as mkdirSync4 } from "fs";
5718
5725
  import { dirname as dirname3 } from "path";
5719
5726
 
5720
5727
  // src/tools/executor.ts
@@ -6237,8 +6244,8 @@ var ToolExecutor = class {
6237
6244
  /** confirm() 的取消回调,由 SIGINT handler 调用 */
6238
6245
  cancelConfirmFn = null;
6239
6246
  /**
6240
- * 会话级 auto-approve:跳过所有 write/destructive 确认(仅当前会话有效)。
6241
- * 通过 /yolo 命令切换。destructive 操作仍会显示警告但不阻塞。
6247
+ * 会话级 auto-approve:跳过 write 确认(仅当前会话有效)。
6248
+ * destructive 操作仍然必须逐次确认。
6242
6249
  */
6243
6250
  sessionAutoApprove = false;
6244
6251
  /**
@@ -6322,7 +6329,7 @@ var ToolExecutor = class {
6322
6329
  }
6323
6330
  }
6324
6331
  }
6325
- if (this.sessionAutoApprove && dangerLevel !== "safe") {
6332
+ if (this.sessionAutoApprove && dangerLevel === "write") {
6326
6333
  this.printToolCall(call);
6327
6334
  if (dangerLevel === "write") this.printDiffPreview(call);
6328
6335
  console.log(theme.warning(" \u26A1 Auto-approved (session /yolo mode)"));
@@ -6851,7 +6858,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
6851
6858
  if (appendMode) {
6852
6859
  appendFileSync(filePath, content, encoding);
6853
6860
  } else {
6854
- writeFileSync3(filePath, content, encoding);
6861
+ atomicWriteFileSync(filePath, content);
6855
6862
  }
6856
6863
  const lines = content.split("\n").length;
6857
6864
  const mode = appendMode ? "appended" : "written";
@@ -6867,7 +6874,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
6867
6874
  };
6868
6875
 
6869
6876
  // src/tools/builtin/edit-file.ts
6870
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync8 } from "fs";
6877
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
6871
6878
 
6872
6879
  // src/tools/builtin/patch-apply.ts
6873
6880
  function parseUnifiedDiff(patch) {
@@ -7266,7 +7273,7 @@ Note: Path can be absolute or relative to cwd.`,
7266
7273
  if (res.ok && res.appliedCount > 0 && res.content !== void 0) {
7267
7274
  undoStack.push(filePath, `edit_file (patch ${res.appliedCount}/${hunks.length}): ${filePath}`);
7268
7275
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
7269
- writeFileSync4(filePath, res.content, encoding);
7276
+ atomicWriteFileSync(filePath, res.content);
7270
7277
  }
7271
7278
  return lines.join("\n");
7272
7279
  }
@@ -7300,7 +7307,7 @@ Note: Path can be absolute or relative to cwd.`,
7300
7307
  if (writeChanges) {
7301
7308
  undoStack.push(filePath, `edit_file (batch ${appliedCount}/${edits.length}): ${filePath}`);
7302
7309
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
7303
- writeFileSync4(filePath, working, encoding);
7310
+ atomicWriteFileSync(filePath, working);
7304
7311
  }
7305
7312
  const lines = [];
7306
7313
  if (anyFailed && stopOnError) {
@@ -7338,7 +7345,7 @@ Please read the file first and use exact text.`;
7338
7345
  const label = res.info?.mode === "ignore_whitespace" ? "edit_file (ws-replace)" : res.info?.mode === "replace_all" ? "edit_file (replace_all)" : "edit_file (replace)";
7339
7346
  undoStack.push(filePath, `${label}: ${filePath}`);
7340
7347
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
7341
- writeFileSync4(filePath, res.content, encoding);
7348
+ atomicWriteFileSync(filePath, res.content);
7342
7349
  if (res.info?.mode === "replace_all") {
7343
7350
  return `Successfully edited ${filePath}${modeLabel}
7344
7351
  Replaced: ${res.info.replacedCount} occurrence(s) of ${truncatePreview(oldStr)}
@@ -7362,7 +7369,7 @@ Please read the file first and use exact text.`;
7362
7369
  undoStack.push(filePath, `edit_file (insert): ${filePath}`);
7363
7370
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
7364
7371
  lines.splice(afterLine, 0, content);
7365
- writeFileSync4(filePath, lines.join("\n"), encoding);
7372
+ atomicWriteFileSync(filePath, lines.join("\n"));
7366
7373
  return `Successfully inserted ${content.split("\n").length} line(s) after line ${afterLine} in ${filePath}`;
7367
7374
  }
7368
7375
  if (args["delete_from_line"] !== void 0) {
@@ -7378,7 +7385,7 @@ Please read the file first and use exact text.`;
7378
7385
  undoStack.push(filePath, `edit_file (delete): ${filePath}`);
7379
7386
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
7380
7387
  const deleted = lines.splice(fromLine - 1, toLine - fromLine + 1);
7381
- writeFileSync4(filePath, lines.join("\n"), encoding);
7388
+ atomicWriteFileSync(filePath, lines.join("\n"));
7382
7389
  return `Successfully deleted lines ${fromLine}-${toLine} (${deleted.length} lines) from ${filePath}`;
7383
7390
  }
7384
7391
  throw new ToolError(
@@ -8583,7 +8590,7 @@ ${preamble}`;
8583
8590
  }
8584
8591
 
8585
8592
  // src/tools/builtin/save-last-response.ts
8586
- 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";
8587
8594
  import { dirname as dirname4 } from "path";
8588
8595
  var lastResponseStore = { content: "" };
8589
8596
  function cleanupRejectedTeeFile(filePath) {
@@ -8634,7 +8641,7 @@ Any of these triggers means use save_last_response, NOT write_file:
8634
8641
  }
8635
8642
  undoStack.push(filePath, `save_last_response: ${filePath}`);
8636
8643
  mkdirSync5(dirname4(filePath), { recursive: true });
8637
- writeFileSync5(filePath, content, "utf-8");
8644
+ atomicWriteFileSync(filePath, content);
8638
8645
  const lines = content.split("\n").length;
8639
8646
  return `File saved: ${filePath} (${lines} lines, ${content.length} bytes)`;
8640
8647
  }
@@ -11340,7 +11347,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11340
11347
  }
11341
11348
  }
11342
11349
  this.sendToolCallStart(call);
11343
- if (this.sessionAutoApprove && (dangerLevel === "write" || dangerLevel === "destructive")) {
11350
+ if (this.sessionAutoApprove && dangerLevel === "write") {
11344
11351
  try {
11345
11352
  const rawContent = await runTool(tool, call.arguments, call.name);
11346
11353
  const content = truncateOutput(rawContent, call.name);
@@ -11444,7 +11451,7 @@ import { existsSync as existsSync19, readFileSync as readFileSync13 } from "fs";
11444
11451
  import { join as join12 } from "path";
11445
11452
 
11446
11453
  // src/repl/dev-state.ts
11447
- 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";
11448
11455
  import { join as join11 } from "path";
11449
11456
  import { homedir as homedir6 } from "os";
11450
11457
  function getDevStatePath() {
@@ -11722,7 +11729,7 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
11722
11729
  }
11723
11730
 
11724
11731
  // src/web/session-handler.ts
11725
- 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";
11726
11733
  import { join as join15, resolve as resolve5, dirname as dirname5 } from "path";
11727
11734
  import { execSync as execSync3 } from "child_process";
11728
11735
 
@@ -13086,7 +13093,7 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
13086
13093
  try {
13087
13094
  mkdirSync10(dirname5(pendingTeeSave), { recursive: true });
13088
13095
  const bodyToSave = stripOuterCodeFence(content);
13089
- writeFileSync7(pendingTeeSave, bodyToSave, "utf-8");
13096
+ atomicWriteFileSync(pendingTeeSave, bodyToSave);
13090
13097
  undoStack.push(pendingTeeSave, `save_last_response (deferred): ${pendingTeeSave}`);
13091
13098
  const lines = bodyToSave.split("\n").length;
13092
13099
  this.send({ type: "info", message: `\u2705 Saved (from reply): ${pendingTeeSave} (${lines} lines, ${bodyToSave.length} chars)` });
@@ -13261,7 +13268,7 @@ ${summaryContent}`,
13261
13268
  let summary;
13262
13269
  try {
13263
13270
  mkdirSync10(dirname5(saveToFile), { recursive: true });
13264
- fileStream = createWriteStream(saveToFile, { encoding: "utf-8" });
13271
+ fileStream = createWriteStream(saveToFile);
13265
13272
  const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
13266
13273
  const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
13267
13274
  const chatRequest = {
@@ -13301,7 +13308,7 @@ ${summaryContent}`,
13301
13308
  fullContent = "";
13302
13309
  } else {
13303
13310
  if (verdict.kind === "salvaged" || verdict.kind === "fallback") {
13304
- writeFileSync7(saveToFile, verdict.content, "utf-8");
13311
+ atomicWriteFileSync(saveToFile, verdict.content);
13305
13312
  fullContent = verdict.content;
13306
13313
  }
13307
13314
  summary = verdict.summary;
@@ -13753,7 +13760,7 @@ ${activated.meta.description || ""}` });
13753
13760
  this.send({ type: "info", message: "\u{1F512} Auto-approve disabled \u2014 confirmations restored for this session." });
13754
13761
  } else {
13755
13762
  this.toolExecutor.sessionAutoApprove = true;
13756
- 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." });
13757
13764
  }
13758
13765
  break;
13759
13766
  }
@@ -14366,7 +14373,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14366
14373
  case "test": {
14367
14374
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
14368
14375
  try {
14369
- const { executeTests } = await import("./run-tests-FGRWBNVF.js");
14376
+ const { executeTests } = await import("./run-tests-NBCA4KEG.js");
14370
14377
  const argStr = args.join(" ").trim();
14371
14378
  let testArgs = {};
14372
14379
  if (argStr) {
@@ -14395,7 +14402,7 @@ Use /init --force to overwrite.` });
14395
14402
  const projectInfo = this.scanProject(cwd);
14396
14403
  const prompt = this.buildInitPrompt(projectInfo, cwd);
14397
14404
  const content = await this.chatOnce(prompt, { temperature: 0.3, maxTokens: 4096 });
14398
- writeFileSync7(targetPath, content, "utf-8");
14405
+ writeFileSync4(targetPath, content, "utf-8");
14399
14406
  this.send({ type: "info", message: `\u2713 Generated: ${targetPath} (${content.length} chars)
14400
14407
  Use /context reload to load it.` });
14401
14408
  } catch (err) {
@@ -14874,7 +14881,7 @@ Add .md files to create commands.` });
14874
14881
  const configDir = this.config.getConfigDir();
14875
14882
  const memPath = join15(configDir, MEMORY_FILE_NAME);
14876
14883
  try {
14877
- writeFileSync7(memPath, "", "utf-8");
14884
+ writeFileSync4(memPath, "", "utf-8");
14878
14885
  this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
14879
14886
  } catch (err) {
14880
14887
  this.send({ type: "error", message: `Failed to clear memory: ${err.message}` });
@@ -15350,7 +15357,7 @@ async function setupProxy(configProxy) {
15350
15357
  }
15351
15358
 
15352
15359
  // src/web/auth.ts
15353
- 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";
15354
15361
  import { join as join16 } from "path";
15355
15362
  import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
15356
15363
  var USERS_FILE = "users.json";
@@ -15552,7 +15559,7 @@ var AuthManager = class {
15552
15559
  if (existsSync22(globalConfig)) {
15553
15560
  try {
15554
15561
  const content = readFileSync15(globalConfig, "utf-8");
15555
- writeFileSync8(join16(userDir, "config.json"), content, "utf-8");
15562
+ writeFileSync5(join16(userDir, "config.json"), content, "utf-8");
15556
15563
  } catch {
15557
15564
  }
15558
15565
  }
@@ -15560,7 +15567,7 @@ var AuthManager = class {
15560
15567
  if (existsSync22(globalMemory)) {
15561
15568
  try {
15562
15569
  const content = readFileSync15(globalMemory, "utf-8");
15563
- writeFileSync8(join16(userDir, "memory.md"), content, "utf-8");
15570
+ writeFileSync5(join16(userDir, "memory.md"), content, "utf-8");
15564
15571
  } catch {
15565
15572
  }
15566
15573
  }
@@ -16089,7 +16096,7 @@ async function startWebServer(options = {}) {
16089
16096
  handlers.set(tabId, handler);
16090
16097
  clearPreAuthTimer();
16091
16098
  console.log(` \u2713 User registered & connected: ${username} (tab: ${tabId.slice(0, 12)})`);
16092
- ws.send(JSON.stringify({ type: "auth_result", success: true, token: newToken, username }));
16099
+ ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
16093
16100
  return;
16094
16101
  }
16095
16102
  if (action === "token") {
@@ -16105,7 +16112,7 @@ async function startWebServer(options = {}) {
16105
16112
  handlers.set(tabId, handler);
16106
16113
  clearPreAuthTimer();
16107
16114
  console.log(` \u2713 Token auth: ${verifiedUser} (tab: ${tabId.slice(0, 12)})`);
16108
- 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 }));
16109
16116
  return;
16110
16117
  }
16111
16118
  if (action === "login") {
@@ -16120,7 +16127,7 @@ async function startWebServer(options = {}) {
16120
16127
  handlers.set(tabId, handler);
16121
16128
  clearPreAuthTimer();
16122
16129
  console.log(` \u2713 User logged in: ${username} (tab: ${tabId.slice(0, 12)})`);
16123
- ws.send(JSON.stringify({ type: "auth_result", success: true, token: loginToken, username }));
16130
+ ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
16124
16131
  return;
16125
16132
  }
16126
16133
  ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Unknown auth action" }));
@@ -16146,9 +16153,10 @@ async function startWebServer(options = {}) {
16146
16153
  });
16147
16154
  ws.on("close", () => {
16148
16155
  clearPreAuthTimer();
16149
- console.log(` \u2717 Tab disconnected: ${tabId.slice(0, 12)} (total: ${handlers.size - 1})`);
16156
+ const wasTracked = handlers.has(tabId);
16150
16157
  if (handler) handler.onDisconnect();
16151
- handlers.delete(tabId);
16158
+ if (wasTracked) handlers.delete(tabId);
16159
+ console.log(` \u2717 Tab disconnected: ${tabId.slice(0, 12)} (total: ${handlers.size})`);
16152
16160
  });
16153
16161
  ws.on("error", (err) => {
16154
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-LJP5XZSC.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 = () => {
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  saveDevState,
16
16
  sessionHasMeaningfulContent,
17
17
  setupProxy
18
- } from "./chunk-ZSC4YLEJ.js";
18
+ } from "./chunk-KOU4KX7J.js";
19
19
  import {
20
20
  ToolExecutor,
21
21
  ToolRegistry,
@@ -35,10 +35,10 @@ import {
35
35
  spawnAgentContext,
36
36
  theme,
37
37
  undoStack
38
- } from "./chunk-43MWRT2C.js";
38
+ } from "./chunk-QQYSZMET.js";
39
39
  import "./chunk-T2NL5ZIA.js";
40
40
  import "./chunk-BXP6YZ2P.js";
41
- import "./chunk-VOV2PWZU.js";
41
+ import "./chunk-SFCNFX42.js";
42
42
  import {
43
43
  SessionManager,
44
44
  getContentText
@@ -55,16 +55,16 @@ import {
55
55
  getConfigDirUsage,
56
56
  listRecentCrashes,
57
57
  writeCrashLog
58
- } from "./chunk-JVYH66N2.js";
58
+ } from "./chunk-KJHMRAJU.js";
59
59
  import {
60
60
  ProviderRegistry
61
- } from "./chunk-UZISJ3KZ.js";
61
+ } from "./chunk-QMXC327F.js";
62
62
  import {
63
63
  getStatsSnapshot,
64
64
  getTopFailingTools,
65
65
  getTopUsedTools,
66
66
  installFlushOnExit
67
- } from "./chunk-XIYSFN4U.js";
67
+ } from "./chunk-2WV6AGHM.js";
68
68
  import {
69
69
  CONTENT_ONLY_STREAM_REMINDER,
70
70
  TEE_FINAL_USER_NUDGE,
@@ -83,10 +83,10 @@ import {
83
83
  stripOuterCodeFence,
84
84
  stripToolCallReminder,
85
85
  teeStreamErrorSummary
86
- } from "./chunk-SMFRJCXB.js";
86
+ } from "./chunk-XPBEJB27.js";
87
87
  import {
88
88
  ConfigManager
89
- } from "./chunk-TZ7SNVSG.js";
89
+ } from "./chunk-TW47X5AO.js";
90
90
  import {
91
91
  AuthError,
92
92
  ProviderError,
@@ -113,7 +113,7 @@ import {
113
113
  SKILLS_DIR_NAME,
114
114
  VERSION,
115
115
  buildUserIdentityPrompt
116
- } from "./chunk-3RET7PL7.js";
116
+ } from "./chunk-QNUVZO5X.js";
117
117
  import {
118
118
  formatGitContextForPrompt,
119
119
  getGitContext,
@@ -135,14 +135,16 @@ import {
135
135
  } from "./chunk-SLSWPBK3.js";
136
136
  import "./chunk-O6UFCEUZ.js";
137
137
  import "./chunk-CKH4KQ4E.js";
138
- import "./chunk-IW3Q7AE5.js";
138
+ import {
139
+ atomicWriteFileSync
140
+ } from "./chunk-IW3Q7AE5.js";
139
141
 
140
142
  // src/index.ts
141
143
  import { program } from "commander";
142
144
 
143
145
  // src/repl/repl.ts
144
146
  import * as readline from "readline";
145
- import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync3, statSync as statSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync4 } from "fs";
147
+ import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync3, statSync as statSync3, mkdirSync as mkdirSync4 } from "fs";
146
148
  import { join as join4, resolve as resolve2, extname as extname2, dirname as dirname3, basename as basename2 } from "path";
147
149
  import chalk4 from "chalk";
148
150
 
@@ -1828,7 +1830,7 @@ No tools match "${filter}".
1828
1830
  const { join: join5 } = await import("path");
1829
1831
  const { existsSync: existsSync5 } = await import("fs");
1830
1832
  const { getGitRoot: getGitRoot2 } = await import("./git-context-EXOEHQSF.js");
1831
- const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-GMQMJRIO.js");
1833
+ const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-NHGTSHKT.js");
1832
1834
  const { approveProject, hashMcpFile } = await import("./project-trust-NKYHL3VZ.js");
1833
1835
  const cwd = process.cwd();
1834
1836
  const projectRoot = getGitRoot2(cwd) ?? cwd;
@@ -2889,7 +2891,7 @@ ${hint}` : "")
2889
2891
  usage: "/test [command|filter]",
2890
2892
  async execute(args, ctx) {
2891
2893
  try {
2892
- const { executeTests } = await import("./run-tests-ZFAEFEYK.js");
2894
+ const { executeTests } = await import("./run-tests-5F2OBWIK.js");
2893
2895
  const argStr = args.join(" ").trim();
2894
2896
  let testArgs = {};
2895
2897
  if (argStr) {
@@ -3567,7 +3569,7 @@ Summary: ${fileMap.size} file(s) \u2014 ${newFiles} new, ${modifiedFiles} modifi
3567
3569
  console.log(theme.info(" \u{1F512} Auto-approve disabled \u2014 confirmations restored for this session."));
3568
3570
  } else {
3569
3571
  executor.sessionAutoApprove = true;
3570
- console.log(theme.warning(" \u26A1 YOLO mode ON \u2014 all write/destructive tools auto-approved for this session."));
3572
+ console.log(theme.warning(" \u26A1 YOLO mode ON \u2014 write tools auto-approved; destructive tools still require confirmation."));
3571
3573
  console.log(theme.dim(" Use /yolo off to re-enable confirmations."));
3572
3574
  }
3573
3575
  console.log();
@@ -4047,11 +4049,11 @@ Managing ${displayName} API Key`);
4047
4049
  console.log(" alwaysYolo: " + (current ? theme.warning("ON") : theme.dim("off")));
4048
4050
  console.log(
4049
4051
  theme.dim(
4050
- " When ON, REPL starts with sessionAutoApprove enabled \u2014 all write/destructive\n tools (edit_file, bash, etc.) run without confirmation prompts. Only enable this\n if you fully trust the working directory you run aicli in.\n"
4052
+ " When ON, REPL starts with sessionAutoApprove enabled \u2014 write\n tools (write_file/edit_file, etc.) run without confirmation prompts. Destructive tools still require confirmation. Only enable this\n if you fully trust the working directory you run aicli in.\n"
4051
4053
  )
4052
4054
  );
4053
4055
  const next = await confirm({
4054
- message: current ? "Keep alwaysYolo enabled?" : "Enable alwaysYolo (skip ALL write/destructive confirmations)?",
4056
+ message: current ? "Keep alwaysYolo enabled?" : "Enable alwaysYolo (skip write confirmations; destructive still confirms)?",
4055
4057
  default: current
4056
4058
  });
4057
4059
  if (next === current) {
@@ -5391,7 +5393,7 @@ Session '${this.resumeSessionId}' not found.
5391
5393
  this.renderer.printWelcome(this.currentProvider, this.currentModel, welcomeModelInfo?.contextWindow, profileNickname);
5392
5394
  if (this.toolExecutor.sessionAutoApprove && this.config.get("alwaysYolo") === true) {
5393
5395
  process.stdout.write(
5394
- theme.warning(" \u26A1 YOLO mode ON (alwaysYolo=true in config) \u2014 all write/destructive tools auto-approved.\n")
5396
+ theme.warning(" \u26A1 YOLO mode ON (alwaysYolo=true in config) \u2014 write tools auto-approved; destructive tools still require confirmation.\n")
5395
5397
  );
5396
5398
  }
5397
5399
  const cfgOutputCap = this.config.get("maxToolOutputChars");
@@ -6642,7 +6644,7 @@ ${decision.displayMessage}
6642
6644
  try {
6643
6645
  mkdirSync4(dirname3(pendingTeeSave), { recursive: true });
6644
6646
  const bodyToSave = stripOuterCodeFence(content);
6645
- writeFileSync2(pendingTeeSave, bodyToSave, "utf-8");
6647
+ atomicWriteFileSync(pendingTeeSave, bodyToSave);
6646
6648
  undoStack.push(pendingTeeSave, `save_last_response (deferred): ${pendingTeeSave}`);
6647
6649
  const lines = bodyToSave.split("\n").length;
6648
6650
  process.stdout.write(theme.success(
@@ -6911,7 +6913,7 @@ Tip: You can continue the conversation by asking the AI to proceed.`
6911
6913
  }
6912
6914
  if (verdict.kind === "salvaged" || verdict.kind === "fallback") {
6913
6915
  try {
6914
- writeFileSync2(saveToFile, verdict.content, "utf-8");
6916
+ atomicWriteFileSync(saveToFile, verdict.content);
6915
6917
  const banner = verdict.kind === "fallback" ? `
6916
6918
  \u26A0 Fallback save: fresh stream had no usable body (matched: ${verdict.matched})
6917
6919
  ${saveToFile} now holds the response you previously saw (${verdict.content.length} chars).
@@ -7267,7 +7269,7 @@ program.command("web").description("Start Web UI server with browser-based chat
7267
7269
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
7268
7270
  process.exit(1);
7269
7271
  }
7270
- const { startWebServer } = await import("./server-ML6ZL3J5.js");
7272
+ const { startWebServer } = await import("./server-LDPQ3DLK.js");
7271
7273
  await startWebServer({ port, host: options.host });
7272
7274
  });
7273
7275
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | logout-all <name> | migrate <name>)").action(async (action, username) => {
@@ -7434,16 +7436,16 @@ program.command("sessions").description("List recent conversation sessions").opt
7434
7436
  console.log(footer + "\n");
7435
7437
  });
7436
7438
  program.command("usage").description("Show token + cost usage grouped by provider/model (cross-session)").option("--days <n>", "Only the last N days (inclusive of today)").option("--month <ym>", "Only a specific month, format YYYY-MM (e.g. 2026-06)").option("--json", "Output as JSON (for scripting)").action(async (options) => {
7437
- const { runUsageCli } = await import("./usage-YX2235W5.js");
7439
+ const { runUsageCli } = await import("./usage-SZWZFX3T.js");
7438
7440
  await runUsageCli(options);
7439
7441
  });
7440
7442
  program.command("doctor").description("Health check: API keys, config, MCP, recent crashes, tool usage, disk usage").option("--json", "Output as JSON (for scripting)").option("--reset-stats", "Reset accumulated tool usage statistics").action(async (options) => {
7441
- const { runDoctorCli } = await import("./doctor-cli-M5PPO3L3.js");
7443
+ const { runDoctorCli } = await import("./doctor-cli-WKH7T4AW.js");
7442
7444
  await runDoctorCli({ json: !!options.json, resetStats: !!options.resetStats });
7443
7445
  });
7444
7446
  program.command("batch <action> [arg] [arg2]").description("Anthropic Message Batches: submit | list | status <id> | results <id> [out] | cancel <id>").option("--dry-run", "Parse and validate input without submitting (submit only)").action(async (action, arg, arg2, options) => {
7445
7447
  try {
7446
- const batch = await import("./batch-RBPABHS7.js");
7448
+ const batch = await import("./batch-6PORMJWT.js");
7447
7449
  switch (action) {
7448
7450
  case "submit":
7449
7451
  if (!arg) {
@@ -7486,7 +7488,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
7486
7488
  }
7487
7489
  });
7488
7490
  program.command("mcp-serve").description("Start an MCP server over STDIO, exposing aicli's built-in tools to Claude Desktop / Cursor / other MCP clients").option("--allow-destructive", "Allow bash / run_interactive / task_create (always destructive in MCP mode)").option("--allow-outside-cwd", "Allow tool path arguments to escape the sandbox root \u2014 disabled by default").option("--tools <list>", "Comma-separated whitelist of tools to expose (default: all eligible tools)").option("--cwd <path>", "Working directory AND sandbox root (default: current directory)").action(async (options) => {
7489
- const { startMcpServer } = await import("./server-RKP7JRRE.js");
7491
+ const { startMcpServer } = await import("./server-37EXC4EB.js");
7490
7492
  await startMcpServer({
7491
7493
  allowDestructive: !!options.allowDestructive,
7492
7494
  allowOutsideCwd: !!options.allowOutsideCwd,
@@ -7495,7 +7497,7 @@ program.command("mcp-serve").description("Start an MCP server over STDIO, exposi
7495
7497
  });
7496
7498
  });
7497
7499
  program.command("ci").description("Headless PR review (code + security) \u2014 reads git/gh diff, optionally posts to PR. Designed for GitHub Actions.").option("--pr <num>", "PR number; diff fetched via `gh pr diff <num>`", (v) => parseInt(v, 10)).option("--base <ref>", "Base ref for `git diff <ref>...HEAD` (ignored when --pr set)").option("--post", "Post review as a PR comment (requires gh CLI + GH_TOKEN, needs --pr)").option("--no-update", "Always create a new comment instead of updating the previous aicli review").option("--skip-code", "Skip the code review section").option("--skip-security", "Skip the security review section").option("--detailed", "Use the detailed code-review prompt").option("--max-diff <n>", "Max diff chars sent to the model (default 30000)", (v) => parseInt(v, 10)).option("--provider <id>", "Override provider (default: config.defaultProvider)").option("--model <id>", "Override model").option("--dry-run", "Print result to stdout instead of posting (overrides --post)").action(async (options) => {
7498
- const { runCi } = await import("./ci-ABE74AXH.js");
7500
+ const { runCi } = await import("./ci-AYMV66P3.js");
7499
7501
  const result = await runCi({
7500
7502
  pr: options.pr,
7501
7503
  base: options.base,
@@ -7593,7 +7595,7 @@ program.command("help").description("Show a comprehensive guide to all aicli fea
7593
7595
  ` ${M}/checkpoint${R} ${M}/fork${R} Save/restore/fork session state`,
7594
7596
  ` ${M}/branch${R} List/create/switch/delete conversation branches (B2)`,
7595
7597
  ` ${M}/undo${R} Undo file operations`,
7596
- ` ${M}/yolo${R} Skip all tool confirmations`,
7598
+ ` ${M}/yolo${R} Skip write confirmations`,
7597
7599
  ` ${M}/mcp${R} Manage MCP server connections`,
7598
7600
  ` ${M}/skill${R} Manage agent skills`,
7599
7601
  ` ${M}/memory${R} View/edit persistent memory`,
@@ -7641,7 +7643,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
7641
7643
  }),
7642
7644
  config.get("customProviders")
7643
7645
  );
7644
- const { startHub } = await import("./hub-NCU5JNCK.js");
7646
+ const { startHub } = await import("./hub-6R3M3NTC.js");
7645
7647
  await startHub(
7646
7648
  {
7647
7649
  topic: topic ?? "",
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-VOV2PWZU.js";
6
- import "./chunk-3RET7PL7.js";
5
+ } from "./chunk-SFCNFX42.js";
6
+ import "./chunk-QNUVZO5X.js";
7
7
  export {
8
8
  executeTests,
9
9
  runTestsTool
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-43H3D2PK.js";
4
+ } from "./chunk-BQIV4ZQT.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -1,21 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ToolRegistry
4
- } from "./chunk-43MWRT2C.js";
4
+ } from "./chunk-QQYSZMET.js";
5
5
  import "./chunk-T2NL5ZIA.js";
6
6
  import "./chunk-BXP6YZ2P.js";
7
- import "./chunk-VOV2PWZU.js";
7
+ import "./chunk-SFCNFX42.js";
8
8
  import {
9
9
  runTool
10
- } from "./chunk-XIYSFN4U.js";
10
+ } from "./chunk-2WV6AGHM.js";
11
11
  import {
12
12
  getDangerLevel,
13
13
  schemaToJsonSchema
14
- } from "./chunk-SMFRJCXB.js";
14
+ } from "./chunk-XPBEJB27.js";
15
15
  import "./chunk-TZQHYZKT.js";
16
16
  import {
17
17
  VERSION
18
- } from "./chunk-3RET7PL7.js";
18
+ } from "./chunk-QNUVZO5X.js";
19
19
  import "./chunk-4BKXL7SM.js";
20
20
  import "./chunk-TB4W4Y4T.js";
21
21
  import "./chunk-KHYD3WXE.js";
@@ -19,7 +19,7 @@ import {
19
19
  loadDevState,
20
20
  persistToolRound,
21
21
  setupProxy
22
- } from "./chunk-ZSC4YLEJ.js";
22
+ } from "./chunk-KOU4KX7J.js";
23
23
  import {
24
24
  ToolExecutor,
25
25
  ToolRegistry,
@@ -38,10 +38,10 @@ import {
38
38
  spawnAgentContext,
39
39
  truncateOutput,
40
40
  undoStack
41
- } from "./chunk-43MWRT2C.js";
41
+ } from "./chunk-QQYSZMET.js";
42
42
  import "./chunk-T2NL5ZIA.js";
43
43
  import "./chunk-BXP6YZ2P.js";
44
- import "./chunk-VOV2PWZU.js";
44
+ import "./chunk-SFCNFX42.js";
45
45
  import {
46
46
  SessionManager,
47
47
  getContentText
@@ -52,10 +52,10 @@ import {
52
52
  } from "./chunk-E44DTERW.js";
53
53
  import {
54
54
  ProviderRegistry
55
- } from "./chunk-UZISJ3KZ.js";
55
+ } from "./chunk-QMXC327F.js";
56
56
  import {
57
57
  runTool
58
- } from "./chunk-XIYSFN4U.js";
58
+ } from "./chunk-2WV6AGHM.js";
59
59
  import {
60
60
  CONTENT_ONLY_STREAM_REMINDER,
61
61
  TEE_FINAL_USER_NUDGE,
@@ -71,10 +71,10 @@ import {
71
71
  stripOuterCodeFence,
72
72
  stripToolCallReminder,
73
73
  teeStreamErrorSummary
74
- } from "./chunk-SMFRJCXB.js";
74
+ } from "./chunk-XPBEJB27.js";
75
75
  import {
76
76
  ConfigManager
77
- } from "./chunk-TZ7SNVSG.js";
77
+ } from "./chunk-TW47X5AO.js";
78
78
  import "./chunk-TZQHYZKT.js";
79
79
  import {
80
80
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -94,7 +94,7 @@ import {
94
94
  SKILLS_DIR_NAME,
95
95
  VERSION,
96
96
  buildUserIdentityPrompt
97
- } from "./chunk-3RET7PL7.js";
97
+ } from "./chunk-QNUVZO5X.js";
98
98
  import {
99
99
  formatGitContextForPrompt,
100
100
  getGitContext,
@@ -106,7 +106,9 @@ import "./chunk-KHYD3WXE.js";
106
106
  import "./chunk-SLSWPBK3.js";
107
107
  import "./chunk-O6UFCEUZ.js";
108
108
  import "./chunk-CKH4KQ4E.js";
109
- import "./chunk-IW3Q7AE5.js";
109
+ import {
110
+ atomicWriteFileSync
111
+ } from "./chunk-IW3Q7AE5.js";
110
112
 
111
113
  // src/web/server.ts
112
114
  import express from "express";
@@ -346,7 +348,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
346
348
  }
347
349
  }
348
350
  this.sendToolCallStart(call);
349
- if (this.sessionAutoApprove && (dangerLevel === "write" || dangerLevel === "destructive")) {
351
+ if (this.sessionAutoApprove && dangerLevel === "write") {
350
352
  try {
351
353
  const rawContent = await runTool(tool, call.arguments, call.name);
352
354
  const content = truncateOutput(rawContent, call.name);
@@ -1176,7 +1178,7 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
1176
1178
  try {
1177
1179
  mkdirSync(dirname(pendingTeeSave), { recursive: true });
1178
1180
  const bodyToSave = stripOuterCodeFence(content);
1179
- writeFileSync(pendingTeeSave, bodyToSave, "utf-8");
1181
+ atomicWriteFileSync(pendingTeeSave, bodyToSave);
1180
1182
  undoStack.push(pendingTeeSave, `save_last_response (deferred): ${pendingTeeSave}`);
1181
1183
  const lines = bodyToSave.split("\n").length;
1182
1184
  this.send({ type: "info", message: `\u2705 Saved (from reply): ${pendingTeeSave} (${lines} lines, ${bodyToSave.length} chars)` });
@@ -1351,7 +1353,7 @@ ${summaryContent}`,
1351
1353
  let summary;
1352
1354
  try {
1353
1355
  mkdirSync(dirname(saveToFile), { recursive: true });
1354
- fileStream = createWriteStream(saveToFile, { encoding: "utf-8" });
1356
+ fileStream = createWriteStream(saveToFile);
1355
1357
  const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
1356
1358
  const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
1357
1359
  const chatRequest = {
@@ -1391,7 +1393,7 @@ ${summaryContent}`,
1391
1393
  fullContent = "";
1392
1394
  } else {
1393
1395
  if (verdict.kind === "salvaged" || verdict.kind === "fallback") {
1394
- writeFileSync(saveToFile, verdict.content, "utf-8");
1396
+ atomicWriteFileSync(saveToFile, verdict.content);
1395
1397
  fullContent = verdict.content;
1396
1398
  }
1397
1399
  summary = verdict.summary;
@@ -1843,7 +1845,7 @@ ${activated.meta.description || ""}` });
1843
1845
  this.send({ type: "info", message: "\u{1F512} Auto-approve disabled \u2014 confirmations restored for this session." });
1844
1846
  } else {
1845
1847
  this.toolExecutor.sessionAutoApprove = true;
1846
- 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." });
1848
+ 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." });
1847
1849
  }
1848
1850
  break;
1849
1851
  }
@@ -2456,7 +2458,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2456
2458
  case "test": {
2457
2459
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
2458
2460
  try {
2459
- const { executeTests } = await import("./run-tests-ZFAEFEYK.js");
2461
+ const { executeTests } = await import("./run-tests-5F2OBWIK.js");
2460
2462
  const argStr = args.join(" ").trim();
2461
2463
  let testArgs = {};
2462
2464
  if (argStr) {
@@ -3891,7 +3893,7 @@ async function startWebServer(options = {}) {
3891
3893
  handlers.set(tabId, handler);
3892
3894
  clearPreAuthTimer();
3893
3895
  console.log(` \u2713 User registered & connected: ${username} (tab: ${tabId.slice(0, 12)})`);
3894
- ws.send(JSON.stringify({ type: "auth_result", success: true, token: newToken, username }));
3896
+ ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
3895
3897
  return;
3896
3898
  }
3897
3899
  if (action === "token") {
@@ -3907,7 +3909,7 @@ async function startWebServer(options = {}) {
3907
3909
  handlers.set(tabId, handler);
3908
3910
  clearPreAuthTimer();
3909
3911
  console.log(` \u2713 Token auth: ${verifiedUser} (tab: ${tabId.slice(0, 12)})`);
3910
- ws.send(JSON.stringify({ type: "auth_result", success: true, token: clientToken, username: verifiedUser }));
3912
+ ws.send(JSON.stringify({ type: "auth_result", success: true, username: verifiedUser }));
3911
3913
  return;
3912
3914
  }
3913
3915
  if (action === "login") {
@@ -3922,7 +3924,7 @@ async function startWebServer(options = {}) {
3922
3924
  handlers.set(tabId, handler);
3923
3925
  clearPreAuthTimer();
3924
3926
  console.log(` \u2713 User logged in: ${username} (tab: ${tabId.slice(0, 12)})`);
3925
- ws.send(JSON.stringify({ type: "auth_result", success: true, token: loginToken, username }));
3927
+ ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
3926
3928
  return;
3927
3929
  }
3928
3930
  ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Unknown auth action" }));
@@ -3948,9 +3950,10 @@ async function startWebServer(options = {}) {
3948
3950
  });
3949
3951
  ws.on("close", () => {
3950
3952
  clearPreAuthTimer();
3951
- console.log(` \u2717 Tab disconnected: ${tabId.slice(0, 12)} (total: ${handlers.size - 1})`);
3953
+ const wasTracked = handlers.has(tabId);
3952
3954
  if (handler) handler.onDisconnect();
3953
- handlers.delete(tabId);
3955
+ if (wasTracked) handlers.delete(tabId);
3956
+ console.log(` \u2717 Tab disconnected: ${tabId.slice(0, 12)} (total: ${handlers.size})`);
3954
3957
  });
3955
3958
  ws.on("error", (err) => {
3956
3959
  console.error(` WebSocket error: ${err.message}`);
@@ -3,21 +3,21 @@ import {
3
3
  ToolRegistry,
4
4
  googleSearchContext,
5
5
  truncateOutput
6
- } from "./chunk-43MWRT2C.js";
6
+ } from "./chunk-QQYSZMET.js";
7
7
  import "./chunk-T2NL5ZIA.js";
8
8
  import "./chunk-BXP6YZ2P.js";
9
- import "./chunk-VOV2PWZU.js";
9
+ import "./chunk-SFCNFX42.js";
10
10
  import {
11
11
  runTool
12
- } from "./chunk-XIYSFN4U.js";
12
+ } from "./chunk-2WV6AGHM.js";
13
13
  import {
14
14
  getDangerLevel,
15
15
  runLeanAgentLoop
16
- } from "./chunk-SMFRJCXB.js";
16
+ } from "./chunk-XPBEJB27.js";
17
17
  import "./chunk-TZQHYZKT.js";
18
18
  import {
19
19
  SUBAGENT_ALLOWED_TOOLS
20
- } from "./chunk-3RET7PL7.js";
20
+ } from "./chunk-QNUVZO5X.js";
21
21
  import "./chunk-4BKXL7SM.js";
22
22
  import "./chunk-TB4W4Y4T.js";
23
23
  import "./chunk-KHYD3WXE.js";
@@ -8,9 +8,9 @@ import {
8
8
  } from "./chunk-E44DTERW.js";
9
9
  import {
10
10
  ConfigManager
11
- } from "./chunk-TZ7SNVSG.js";
11
+ } from "./chunk-TW47X5AO.js";
12
12
  import "./chunk-TZQHYZKT.js";
13
- import "./chunk-3RET7PL7.js";
13
+ import "./chunk-QNUVZO5X.js";
14
14
  import "./chunk-IW3Q7AE5.js";
15
15
 
16
16
  // src/cli/usage.ts
@@ -2805,8 +2805,8 @@ async function handleAuth(event) {
2805
2805
  }
2806
2806
  }
2807
2807
 
2808
- // Legacy WS auth_result handler — kept for backwards-compat with clients/
2809
- // flows that still hit the WS auth path. Token field, if present, is ignored.
2808
+ // Legacy WS auth_result handler — kept for flows that still hit the WS auth path.
2809
+ // The server no longer echoes tokens; auth state is carried by httpOnly cookie.
2810
2810
  function handleAuthResult(msg) {
2811
2811
  const errorEl = document.getElementById('auth-error');
2812
2812
  const submitBtn = document.getElementById('auth-submit');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.205",
3
+ "version": "0.4.206",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",