jinzd-ai-cli 0.4.234 → 0.4.236

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 (29) hide show
  1. package/README.md +5 -5
  2. package/dist/{auth-FSTAKSYF.js → auth-PE3Z5OHS.js} +1 -1
  3. package/dist/{batch-T3FRX4B6.js → batch-HZI7QAE7.js} +2 -2
  4. package/dist/{chunk-AFT6VFMI.js → chunk-7VAE3I3D.js} +1 -1
  5. package/dist/{chunk-3KTSJ2EL.js → chunk-CGQARJMP.js} +1 -1
  6. package/dist/{chunk-ETJ5X6V7.js → chunk-MFWAL5S2.js} +1 -1
  7. package/dist/{chunk-6MJMHHRK.js → chunk-O4QYY6SF.js} +3 -3
  8. package/dist/{chunk-5DTEEWYC.js → chunk-QDP3NUPX.js} +3 -3
  9. package/dist/{chunk-JVKAL5Q3.js → chunk-RYQBBEMM.js} +33 -3
  10. package/dist/{chunk-E5RLXMSU.js → chunk-SBSWH2EG.js} +89 -56
  11. package/dist/{chunk-TAXIG7HH.js → chunk-SCFGLRB3.js} +1 -1
  12. package/dist/{chunk-II6RZRTF.js → chunk-T6EIBUKW.js} +115 -18
  13. package/dist/{chunk-5VPRZMZB.js → chunk-TT7YURG3.js} +3 -3
  14. package/dist/{chunk-L5PNAQ6O.js → chunk-VBAW4ZPY.js} +1 -1
  15. package/dist/{ci-GJ53GPLA.js → ci-FYLCHRN3.js} +4 -4
  16. package/dist/{ci-format-AXE4QCI6.js → ci-format-TH2ZTFKU.js} +2 -2
  17. package/dist/{constants-DC4EXFSB.js → constants-BF6YNKRW.js} +1 -1
  18. package/dist/{doctor-cli-HKREND6Q.js → doctor-cli-B6GE4BYJ.js} +4 -4
  19. package/dist/electron-server.js +312 -151
  20. package/dist/{hub-LTTKRGZC.js → hub-QBQCKEPE.js} +1 -1
  21. package/dist/index.js +2439 -2437
  22. package/dist/{pr-4VODJVRV.js → pr-KV2WK7SU.js} +4 -4
  23. package/dist/{run-tests-QR2PYR2P.js → run-tests-DI6WEYHX.js} +1 -1
  24. package/dist/{run-tests-G5BNTMWQ.js → run-tests-MEHC73SQ.js} +2 -2
  25. package/dist/{server-EO6VQHKL.js → server-ETIGAZX5.js} +4 -4
  26. package/dist/{server-64WZVZZN.js → server-KZAP5JN3.js} +34 -33
  27. package/dist/{task-orchestrator-4INCZMLT.js → task-orchestrator-WS724BUX.js} +4 -4
  28. package/dist/{usage-T3HFMNNV.js → usage-DHZR5PG7.js} +2 -2
  29. package/package.json +2 -4
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![npm version](https://img.shields.io/npm/v/jinzd-ai-cli)](https://www.npmjs.com/package/jinzd-ai-cli)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
9
9
  [![Node.js](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org)
10
- [![Tests](https://img.shields.io/badge/tests-1393%20passing-brightgreen)]()
10
+ [![Tests](https://img.shields.io/badge/tests-1639%20passing-brightgreen)]()
11
11
  [![GitHub Release](https://img.shields.io/github/v/release/jinzhengdong/ai-cli)](https://github.com/jinzhengdong/ai-cli/releases)
12
12
  [![CI](https://github.com/jinzhengdong/ai-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/jinzhengdong/ai-cli/actions/workflows/ci.yml)
13
13
 
@@ -31,7 +31,7 @@
31
31
  - **Conversation Branching** *(v0.4.74+)* — `/branch list/new/switch/delete/rename` inside the REPL, plus a 🌿 "fork here" button on every replay step — explore alternate directions without losing the original thread
32
32
  - **Symbol Index** *(v0.4.76+, multi-language since v0.4.143)* — persistent tree-sitter index for TypeScript / JavaScript / TSX / Python / Go / Rust / Java / C/C++ powers three new AI tools: `find_symbol`, `get_outline`, `find_references`. Orders of magnitude faster than grep for definition lookups; background refresh on REPL startup, `/index status|rebuild|clear` to manage
33
33
  - **Semantic Code Search** *(v0.4.77+)* — `search_code` tool finds code by meaning, not name. Local sentence embeddings (multilingual MiniLM, 117 MB one-time download) score symbols by cosine similarity against natural-language queries in English or Chinese ("where are users authenticated", "哪里做了速率限制"). No API key, runs on CPU. Manage with `/index semantic-rebuild|semantic-clear`
34
- - **MCP Server Mode** *(v0.4.84+)* — `aicli mcp-serve` reverses ai-cli into an MCP server (JSON-RPC 2.0 over stdio), exposing its 28 built-in tools (incl. `find_symbol` / `search_code` / `run_tests`) to Claude Desktop / Cursor / any MCP client. Opt-in destructive-tool allow, `--tools` whitelist, `--cwd` override
34
+ - **MCP Server Mode** *(v0.4.84+)* — `aicli mcp-serve` reverses ai-cli into an MCP server (JSON-RPC 2.0 over stdio), exposing its 30 built-in tools (incl. `find_symbol` / `search_code` / `run_tests`) to Claude Desktop / Cursor / any MCP client. Opt-in destructive-tool allow, `--tools` whitelist, `--cwd` override
35
35
  - **Session Sensitive-Data Redaction** *(v0.4.88+)* — unified redactor scrubs `password=` / `api_key` / bearer tokens / OpenAI-style keys from every message **before it hits disk**. Query text is redacted too, so secrets never reach embeddings or logs. `/security status` + `/security scan` to audit
36
36
  - **Human-like Long-Term Memory** *(v0.4.89+, B4)* — semantic index over every past chat session + `recall_memory` AI tool + `/memory rebuild|refresh|status|recall` commands. AI is prompted to auto-recall when it sees "last time" / "之前" / ambiguous references. Reuses the same MiniLM embedder as semantic code search
37
37
  - **Governed Persistent Memory** *(v0.4.217+)* — `save_memory` and `/memory add` write auditable `memory.jsonl` entries with id/scope/source/sensitivity/approval/expiry; low-risk entries auto-approve, sensitive entries stay pending until `/memory approve <id>`, and project-scoped memories only inject inside the same project
@@ -156,7 +156,7 @@ aicli
156
156
 
157
157
  ## Built-in Tools (Agentic)
158
158
 
159
- AI autonomously invokes these 27 tools during conversations:
159
+ AI autonomously invokes these 30 tools during conversations:
160
160
 
161
161
  | Tool | Safety | Description |
162
162
  |------|--------|-------------|
@@ -225,7 +225,7 @@ AI autonomously invokes these 27 tools during conversations:
225
225
 
226
226
  **Multi-line input**: Use `\` at end of line for continuation, or paste multi-line content directly (auto-detected and merged).
227
227
 
228
- Type `/help` in the REPL to see all 40 commands.
228
+ Type `/help` in the REPL to see all 48 commands.
229
229
 
230
230
  ## CLI Parameters
231
231
 
@@ -512,7 +512,7 @@ The Web UI (`aicli web`) provides a full-featured browser interface:
512
512
  ## Testing
513
513
 
514
514
  ```bash
515
- npm test # Offline regression suite: 79 files, 1393 passed, 1 skipped
515
+ npm test # Offline regression suite: 91 files, 1639 passed, 2 skipped
516
516
  npm run test:providers # Real-provider smoke tests (network/API-key dependent)
517
517
  npm run test:e2e-web # Playwright browser smoke; may need browser-process permission in sandboxes
518
518
  npm run test:watch # Watch mode
@@ -3,7 +3,7 @@ import {
3
3
  AuthManager,
4
4
  TOKEN_EXPIRY_MS,
5
5
  __resetLoginAttemptsForTests
6
- } from "./chunk-JVKAL5Q3.js";
6
+ } from "./chunk-RYQBBEMM.js";
7
7
  import "./chunk-IW3Q7AE5.js";
8
8
  export {
9
9
  AuthManager,
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigManager
4
- } from "./chunk-L5PNAQ6O.js";
4
+ } from "./chunk-VBAW4ZPY.js";
5
5
  import "./chunk-5ULLIOVC.js";
6
- import "./chunk-3KTSJ2EL.js";
6
+ import "./chunk-CGQARJMP.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
  TEST_TIMEOUT
4
- } from "./chunk-3KTSJ2EL.js";
4
+ } from "./chunk-CGQARJMP.js";
5
5
 
6
6
  // src/tools/builtin/run-tests.ts
7
7
  import { execSync, spawnSync } from "child_process";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.234";
4
+ var VERSION = "0.4.236";
5
5
  var APP_NAME = "ai-cli";
6
6
  var CONFIG_DIR_NAME = ".aicli";
7
7
  var CONFIG_FILE_NAME = "config.json";
@@ -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.234";
9
+ var VERSION = "0.4.236";
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
  evaluateCiGate,
4
4
  normalizeCiThresholds
5
- } from "./chunk-TAXIG7HH.js";
5
+ } from "./chunk-SCFGLRB3.js";
6
6
  import {
7
7
  buildReviewPrompt,
8
8
  buildSecurityReviewPrompt,
@@ -13,10 +13,10 @@ import {
13
13
  } from "./chunk-QYQI7ZWK.js";
14
14
  import {
15
15
  ConfigManager
16
- } from "./chunk-L5PNAQ6O.js";
16
+ } from "./chunk-VBAW4ZPY.js";
17
17
  import {
18
18
  VERSION
19
- } from "./chunk-3KTSJ2EL.js";
19
+ } from "./chunk-CGQARJMP.js";
20
20
 
21
21
  // src/cli/ci.ts
22
22
  import { execFileSync } from "child_process";
@@ -11,10 +11,10 @@ import {
11
11
  listHooks,
12
12
  listInstalledPlugins,
13
13
  resetStats
14
- } from "./chunk-II6RZRTF.js";
14
+ } from "./chunk-T6EIBUKW.js";
15
15
  import {
16
16
  ConfigManager
17
- } from "./chunk-L5PNAQ6O.js";
17
+ } from "./chunk-VBAW4ZPY.js";
18
18
  import {
19
19
  getGitRoot
20
20
  } from "./chunk-HOSJZMQS.js";
@@ -26,7 +26,7 @@ import {
26
26
  MCP_PROJECT_CONFIG_NAME,
27
27
  MEMORY_FILE_NAME,
28
28
  VERSION
29
- } from "./chunk-3KTSJ2EL.js";
29
+ } from "./chunk-CGQARJMP.js";
30
30
 
31
31
  // src/diagnostics/doctor-report.ts
32
32
  import { existsSync as existsSync3, statSync as statSync2 } from "fs";
@@ -5,7 +5,7 @@ import {
5
5
 
6
6
  // src/web/auth.ts
7
7
  import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, copyFileSync } from "fs";
8
- import { join } from "path";
8
+ import { isAbsolute, join, relative } from "path";
9
9
  import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
10
10
  var USERS_FILE = "users.json";
11
11
  var TOKEN_EXPIRY_HOURS = 24;
@@ -241,8 +241,17 @@ var AuthManager = class {
241
241
  loadOrCreate() {
242
242
  if (existsSync(this.usersFile)) {
243
243
  try {
244
- return JSON.parse(readFileSync(this.usersFile, "utf-8"));
245
- } catch {
244
+ const parsed = JSON.parse(readFileSync(this.usersFile, "utf-8"));
245
+ if (!this.isValidUsersDB(parsed)) {
246
+ throw new Error("database structure is invalid");
247
+ }
248
+ return parsed;
249
+ } catch (err) {
250
+ const detail = err instanceof Error ? err.message : String(err);
251
+ throw new Error(
252
+ `Authentication database is unreadable or corrupt: ${this.usersFile} (${detail}). Refusing to start without authentication; restore or remove the file explicitly.`,
253
+ { cause: err }
254
+ );
246
255
  }
247
256
  }
248
257
  const db = {
@@ -253,6 +262,27 @@ var AuthManager = class {
253
262
  this.saveDB(db);
254
263
  return db;
255
264
  }
265
+ isValidUsersDB(value) {
266
+ if (!value || typeof value !== "object") return false;
267
+ const db = value;
268
+ if (db.version !== 1 || typeof db.secret !== "string" || db.secret.length < 32 || !Array.isArray(db.users)) {
269
+ return false;
270
+ }
271
+ const usernames = /* @__PURE__ */ new Set();
272
+ for (const user of db.users) {
273
+ if (!user || typeof user !== "object") return false;
274
+ const record = user;
275
+ if (typeof record.username !== "string" || !/^[a-z0-9_-]{2,32}$/.test(record.username) || usernames.has(record.username) || typeof record.passwordHash !== "string" || record.passwordHash.length === 0 || typeof record.salt !== "string" || record.salt.length === 0 || typeof record.createdAt !== "string" || typeof record.dataDir !== "string" || record.dataDir.length === 0 || isAbsolute(record.dataDir)) {
276
+ return false;
277
+ }
278
+ const dataDirFromBase = relative(this.baseDir, join(this.baseDir, record.dataDir));
279
+ if (dataDirFromBase.startsWith("..") || isAbsolute(dataDirFromBase)) return false;
280
+ if (record.hashVersion !== void 0 && record.hashVersion !== 1 && record.hashVersion !== 2) return false;
281
+ if (record.tokensRevokedBefore !== void 0 && !Number.isFinite(record.tokensRevokedBefore)) return false;
282
+ usernames.add(record.username);
283
+ }
284
+ return true;
285
+ }
256
286
  save() {
257
287
  this.saveDB(this.db);
258
288
  }
@@ -14,7 +14,7 @@ import {
14
14
  runHook,
15
15
  runLifecycleHooks,
16
16
  runTool
17
- } from "./chunk-II6RZRTF.js";
17
+ } from "./chunk-T6EIBUKW.js";
18
18
  import {
19
19
  getDangerLevel,
20
20
  isFileWriteTool,
@@ -33,7 +33,7 @@ import {
33
33
  } from "./chunk-4BKXL7SM.js";
34
34
  import {
35
35
  runTestsTool
36
- } from "./chunk-AFT6VFMI.js";
36
+ } from "./chunk-7VAE3I3D.js";
37
37
  import {
38
38
  CONFIG_DIR_NAME,
39
39
  DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
@@ -43,7 +43,7 @@ import {
43
43
  SUBAGENT_ALLOWED_TOOLS,
44
44
  SUBAGENT_DEFAULT_MAX_ROUNDS,
45
45
  SUBAGENT_MAX_ROUNDS_LIMIT
46
- } from "./chunk-3KTSJ2EL.js";
46
+ } from "./chunk-CGQARJMP.js";
47
47
  import {
48
48
  loadChatIndex,
49
49
  searchChatMemory
@@ -1318,11 +1318,11 @@ ${content}`;
1318
1318
 
1319
1319
  // src/tools/builtin/write-file.ts
1320
1320
  import { appendFileSync, mkdirSync } from "fs";
1321
- import { dirname as dirname2 } from "path";
1321
+ import { dirname as dirname3 } from "path";
1322
1322
 
1323
1323
  // src/tools/executor.ts
1324
1324
  import chalk3 from "chalk";
1325
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
1325
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
1326
1326
  import { tmpdir } from "os";
1327
1327
 
1328
1328
  // src/core/readline-internal.ts
@@ -1529,7 +1529,8 @@ function simpleDiff(oldLines, newLines) {
1529
1529
  }
1530
1530
 
1531
1531
  // src/tools/permissions.ts
1532
- import { isAbsolute, resolve as resolve3 } from "path";
1532
+ import { existsSync as existsSync4, realpathSync } from "fs";
1533
+ import { basename as basename2, dirname as dirname2, isAbsolute, resolve as resolve3 } from "path";
1533
1534
  function checkPermission(toolName, args, dangerLevel, rules, defaultAction = "confirm") {
1534
1535
  for (const rule of rules) {
1535
1536
  if (rule.tool !== "*" && rule.tool !== toolName) continue;
@@ -1574,10 +1575,28 @@ var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
1574
1575
  function normalizePathForPermission(value) {
1575
1576
  return value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
1576
1577
  }
1578
+ function canonicalizeWithNearestParent(value) {
1579
+ const missing = [];
1580
+ let cursor = resolve3(value);
1581
+ while (!existsSync4(cursor)) {
1582
+ const parent = dirname2(cursor);
1583
+ if (parent === cursor) return null;
1584
+ missing.unshift(basename2(cursor));
1585
+ cursor = parent;
1586
+ }
1587
+ try {
1588
+ return resolve3(realpathSync(cursor), ...missing);
1589
+ } catch {
1590
+ return null;
1591
+ }
1592
+ }
1577
1593
  function isInsidePath(root, target) {
1578
1594
  const absoluteTarget = isAbsolute(target) ? target : resolve3(root, target);
1579
- const r = normalizePathForPermission(resolve3(root));
1580
- const t = normalizePathForPermission(absoluteTarget);
1595
+ const canonicalRoot = canonicalizeWithNearestParent(root);
1596
+ const canonicalTarget = canonicalizeWithNearestParent(absoluteTarget);
1597
+ if (!canonicalRoot || !canonicalTarget) return false;
1598
+ const r = normalizePathForPermission(canonicalRoot);
1599
+ const t = normalizePathForPermission(canonicalTarget);
1581
1600
  return t === r || t.startsWith(`${r}/`);
1582
1601
  }
1583
1602
  function extractPathArg(toolName, args) {
@@ -1629,19 +1648,31 @@ function profileHardDecision(profileName, toolName, args, dangerLevel, ctx) {
1629
1648
  return null;
1630
1649
  }
1631
1650
  if (profileName === "workspace-write") {
1632
- if (dangerLevel === "destructive") return { action: "confirm", reason: "destructive tools always require confirmation", profileName };
1651
+ if (dangerLevel === "destructive") {
1652
+ return { action: "confirm", reason: "destructive tools always require confirmation", profileName, hardBoundary: true };
1653
+ }
1633
1654
  if (dangerLevel === "write" && isExplicitFileWriteTool(toolName)) {
1634
1655
  const target = extractPathArg(toolName, args);
1635
- if (!target) return { action: "confirm", reason: "file write target is not explicit", profileName };
1656
+ if (!target) {
1657
+ return { action: "confirm", reason: "file write target is not explicit", profileName, hardBoundary: true };
1658
+ }
1636
1659
  const allowedRoots = getAllowedWriteRoots(ctx);
1637
1660
  if (allowedRoots.length === 0 || !allowedRoots.some((root) => isInsidePath(root, target))) {
1638
- return { action: "deny", reason: "file write target is outside workspace/temp roots", profileName };
1661
+ return { action: "deny", reason: "file write target is outside workspace/temp roots", profileName, hardBoundary: true };
1639
1662
  }
1640
1663
  }
1664
+ if (toolName === "bash" || toolName === "run_interactive" || toolName === "task_create") {
1665
+ return {
1666
+ action: "confirm",
1667
+ reason: "shell/process writes cannot be proven to stay inside workspace/temp roots",
1668
+ profileName,
1669
+ hardBoundary: true
1670
+ };
1671
+ }
1641
1672
  return null;
1642
1673
  }
1643
1674
  if (profileName === "danger-full-access") {
1644
- if (dangerLevel === "destructive") return { action: "confirm", reason: "destructive tools still require confirmation", profileName };
1675
+ if (dangerLevel === "destructive") return { action: "confirm", reason: "destructive tools still require confirmation", profileName, hardBoundary: true };
1645
1676
  return null;
1646
1677
  }
1647
1678
  return null;
@@ -1653,7 +1684,8 @@ function checkPermissionWithProfile(toolName, args, dangerLevel, rules, defaultA
1653
1684
  const combinedRules = [...getProfileRules(profileContext), ...rules];
1654
1685
  const profileDefault = profileName === "legacy" ? defaultAction : getProfileDefaultAction(profileName, profileContext);
1655
1686
  const action = checkPermission(toolName, args, dangerLevel, combinedRules, profileDefault);
1656
- if (hard?.action === "confirm" && action === "auto-approve") return hard;
1687
+ if (action === "deny") return { action, profileName };
1688
+ if (hard?.action === "confirm") return hard;
1657
1689
  return { action, profileName };
1658
1690
  }
1659
1691
  function formatPermissionProfileWarning(profileName) {
@@ -1950,7 +1982,7 @@ var theme = new Proxy(DARK_THEME, {
1950
1982
  });
1951
1983
 
1952
1984
  // src/tools/action-classifier.ts
1953
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
1985
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
1954
1986
  import { isAbsolute as isAbsolute2, join, resolve as resolve4 } from "path";
1955
1987
  var RECENT_DENIED_LIMIT = 50;
1956
1988
  var recentlyDenied = [];
@@ -1993,7 +2025,7 @@ function splitShellWords(command) {
1993
2025
  }
1994
2026
  function readPackageManifest(root) {
1995
2027
  const p = join(root, "package.json");
1996
- if (!existsSync4(p)) return null;
2028
+ if (!existsSync5(p)) return null;
1997
2029
  try {
1998
2030
  return JSON.parse(readFileSync3(p, "utf-8"));
1999
2031
  } catch {
@@ -2012,7 +2044,7 @@ function manifestHasDependency(root, name) {
2012
2044
  function lockfileMentions(root, name) {
2013
2045
  for (const file of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]) {
2014
2046
  const p = join(root, file);
2015
- if (!existsSync4(p)) continue;
2047
+ if (!existsSync5(p)) continue;
2016
2048
  try {
2017
2049
  if (readFileSync3(p, "utf-8").includes(name)) return true;
2018
2050
  } catch {
@@ -2288,14 +2320,15 @@ var ToolExecutor = class {
2288
2320
  return { callId: call.id, content: message, isError: true };
2289
2321
  }
2290
2322
  }
2291
- if (networkPermission?.action === "confirm" && dangerLevel === "safe") {
2323
+ const requiresHardConfirmation = permission.action === "confirm" && permission.hardBoundary === true;
2324
+ if ((networkPermission?.action === "confirm" || requiresHardConfirmation) && dangerLevel === "safe") {
2292
2325
  this.printToolCall(call);
2293
2326
  toolCallAlreadyPrinted = true;
2294
2327
  const confirmed = await this.confirm(call, "write");
2295
2328
  if (!confirmed) {
2296
2329
  return {
2297
2330
  callId: call.id,
2298
- content: `[User cancelled] The user declined network access for ${call.name}. Do not retry without asking.`,
2331
+ content: `[User cancelled] The user declined permission for ${call.name}. Do not retry without asking.`,
2299
2332
  isError: true
2300
2333
  };
2301
2334
  }
@@ -2313,7 +2346,7 @@ var ToolExecutor = class {
2313
2346
  isError: true
2314
2347
  };
2315
2348
  }
2316
- if (classification.decision === "auto-approve") {
2349
+ if (classification.decision === "auto-approve" && !requiresHardConfirmation) {
2317
2350
  this.printToolCall(call);
2318
2351
  if (dangerLevel === "write") this.printDiffPreview(call);
2319
2352
  console.log(theme.warning(` \u26A1 Auto-approved (/auto: ${classification.ruleId})`));
@@ -2334,7 +2367,7 @@ var ToolExecutor = class {
2334
2367
  }
2335
2368
  }
2336
2369
  }
2337
- if (this.sessionAutoApprove && dangerLevel === "write") {
2370
+ if (this.sessionAutoApprove && dangerLevel === "write" && !requiresHardConfirmation) {
2338
2371
  this.printToolCall(call);
2339
2372
  if (dangerLevel === "write") this.printDiffPreview(call);
2340
2373
  console.log(theme.warning(" \u26A1 Auto-approved (session /yolo mode)"));
@@ -2578,7 +2611,7 @@ var ToolExecutor = class {
2578
2611
  const filePath = String(call.arguments["path"] ?? "");
2579
2612
  const newContent = String(call.arguments["content"] ?? "");
2580
2613
  if (!filePath) return;
2581
- if (existsSync5(filePath)) {
2614
+ if (existsSync6(filePath)) {
2582
2615
  let oldContent;
2583
2616
  try {
2584
2617
  oldContent = readFileSync4(filePath, "utf-8");
@@ -2604,7 +2637,7 @@ var ToolExecutor = class {
2604
2637
  }
2605
2638
  } else if (call.name === "edit_file") {
2606
2639
  const filePath = String(call.arguments["path"] ?? "");
2607
- if (!filePath || !existsSync5(filePath)) return;
2640
+ if (!filePath || !existsSync6(filePath)) return;
2608
2641
  const oldStr = call.arguments["old_str"];
2609
2642
  const newStr = call.arguments["new_str"];
2610
2643
  if (oldStr !== void 0) {
@@ -2723,7 +2756,7 @@ var ToolExecutor = class {
2723
2756
  };
2724
2757
 
2725
2758
  // src/tools/sensitive-paths.ts
2726
- import { resolve as resolve5, sep as sep2, basename as basename2 } from "path";
2759
+ import { resolve as resolve5, sep as sep2, basename as basename3 } from "path";
2727
2760
  import { homedir as homedir2 } from "os";
2728
2761
  var home = homedir2();
2729
2762
  function norm(p) {
@@ -2742,7 +2775,7 @@ function homeRel(p) {
2742
2775
  function classifyWritePath(path3) {
2743
2776
  if (!path3) return { sensitive: false };
2744
2777
  const abs = norm(path3);
2745
- const base = basename2(abs);
2778
+ const base = basename3(abs);
2746
2779
  const rel = homeRel(path3);
2747
2780
  if (rel) {
2748
2781
  const shellRc = /* @__PURE__ */ new Set([
@@ -2866,7 +2899,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
2866
2899
  }
2867
2900
  undoStack.push(filePath, `write_file${appendMode ? " (append)" : ""}: ${filePath}`);
2868
2901
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
2869
- mkdirSync(dirname2(filePath), { recursive: true });
2902
+ mkdirSync(dirname3(filePath), { recursive: true });
2870
2903
  if (appendMode) {
2871
2904
  appendFileSync(filePath, content, encoding);
2872
2905
  } else {
@@ -2886,7 +2919,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
2886
2919
  };
2887
2920
 
2888
2921
  // src/tools/builtin/edit-file.ts
2889
- import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
2922
+ import { readFileSync as readFileSync5, existsSync as existsSync7 } from "fs";
2890
2923
 
2891
2924
  // src/tools/builtin/patch-apply.ts
2892
2925
  function parseUnifiedDiff(patch) {
@@ -3247,7 +3280,7 @@ Note: Path can be absolute or relative to cwd.`,
3247
3280
  const filePath = String(args["path"] ?? args["file"] ?? "");
3248
3281
  const encoding = args["encoding"] ?? "utf-8";
3249
3282
  if (!filePath) throw new ToolError("edit_file", "path is required");
3250
- if (!existsSync6(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
3283
+ if (!existsSync7(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
3251
3284
  const verdict = classifyWritePath(filePath);
3252
3285
  if (verdict.sensitive && subAgentGuard.active) {
3253
3286
  throw new ToolError(
@@ -3413,8 +3446,8 @@ function truncatePreview(str, maxLen = 80) {
3413
3446
  }
3414
3447
 
3415
3448
  // src/tools/builtin/list-dir.ts
3416
- import { readdirSync as readdirSync3, statSync as statSync3, existsSync as existsSync7 } from "fs";
3417
- import { join as join2, basename as basename3 } from "path";
3449
+ import { readdirSync as readdirSync3, statSync as statSync3, existsSync as existsSync8 } from "fs";
3450
+ import { join as join2, basename as basename4 } from "path";
3418
3451
  var listDirTool = {
3419
3452
  definition: {
3420
3453
  name: "list_dir",
@@ -3436,8 +3469,8 @@ var listDirTool = {
3436
3469
  async execute(args) {
3437
3470
  const dirPath = String(args["path"] ?? process.cwd());
3438
3471
  const recursive = Boolean(args["recursive"] ?? false);
3439
- if (!existsSync7(dirPath)) {
3440
- const targetName = basename3(dirPath).toLowerCase();
3472
+ if (!existsSync8(dirPath)) {
3473
+ const targetName = basename4(dirPath).toLowerCase();
3441
3474
  const cwd = process.cwd();
3442
3475
  const suggestions = [];
3443
3476
  try {
@@ -3514,7 +3547,7 @@ function formatSize(bytes) {
3514
3547
  }
3515
3548
 
3516
3549
  // src/tools/builtin/grep-files.ts
3517
- import { readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync4, existsSync as existsSync8 } from "fs";
3550
+ import { readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync4, existsSync as existsSync9 } from "fs";
3518
3551
  import { readFile } from "fs/promises";
3519
3552
  import { join as join3, relative } from "path";
3520
3553
  var grepFilesTool = {
@@ -3567,7 +3600,7 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
3567
3600
  const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
3568
3601
  const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
3569
3602
  if (!pattern) throw new ToolError("grep_files", "pattern is required");
3570
- if (!existsSync8(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
3603
+ if (!existsSync9(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
3571
3604
  const MAX_PATTERN_LENGTH = 1e3;
3572
3605
  if (pattern.length > MAX_PATTERN_LENGTH) {
3573
3606
  throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
@@ -3751,8 +3784,8 @@ function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, re
3751
3784
  }
3752
3785
 
3753
3786
  // src/tools/builtin/glob-files.ts
3754
- import { readdirSync as readdirSync5, statSync as statSync5, existsSync as existsSync9 } from "fs";
3755
- import { join as join4, relative as relative2, basename as basename4 } from "path";
3787
+ import { readdirSync as readdirSync5, statSync as statSync5, existsSync as existsSync10 } from "fs";
3788
+ import { join as join4, relative as relative2, basename as basename5 } from "path";
3756
3789
  var globFilesTool = {
3757
3790
  definition: {
3758
3791
  name: "glob_files",
@@ -3785,7 +3818,7 @@ Results sorted by most recent modification time. Automatically skips node_module
3785
3818
  const rootPath = String(args["path"] ?? process.cwd());
3786
3819
  const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
3787
3820
  if (!pattern) throw new ToolError("glob_files", "pattern is required");
3788
- if (!existsSync9(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
3821
+ if (!existsSync10(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
3789
3822
  const regex = globToRegex(pattern);
3790
3823
  const matches = [];
3791
3824
  collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
@@ -3868,7 +3901,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
3868
3901
  collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
3869
3902
  } else if (entry.isFile()) {
3870
3903
  const relPath = relative2(rootPath, fullPath).replace(/\\/g, "/");
3871
- if (regex.test(relPath) || regex.test(basename4(relPath))) {
3904
+ if (regex.test(relPath) || regex.test(basename5(relPath))) {
3872
3905
  try {
3873
3906
  const stat = statSync5(fullPath);
3874
3907
  results.push({ relPath, absPath: fullPath, mtime: stat.mtimeMs });
@@ -4603,7 +4636,7 @@ ${preamble}`;
4603
4636
 
4604
4637
  // src/tools/builtin/save-last-response.ts
4605
4638
  import { mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, rmdirSync as rmdirSync2 } from "fs";
4606
- import { dirname as dirname3 } from "path";
4639
+ import { dirname as dirname4 } from "path";
4607
4640
  var lastResponseStore = { content: "" };
4608
4641
  function cleanupRejectedTeeFile(filePath) {
4609
4642
  try {
@@ -4611,7 +4644,7 @@ function cleanupRejectedTeeFile(filePath) {
4611
4644
  } catch {
4612
4645
  }
4613
4646
  try {
4614
- rmdirSync2(dirname3(filePath));
4647
+ rmdirSync2(dirname4(filePath));
4615
4648
  } catch {
4616
4649
  }
4617
4650
  }
@@ -4652,7 +4685,7 @@ Any of these triggers means use save_last_response, NOT write_file:
4652
4685
  throw new ToolError("save_last_response", "No content to save: AI has not produced any response yet, or the last response was empty.");
4653
4686
  }
4654
4687
  undoStack.push(filePath, `save_last_response: ${filePath}`);
4655
- mkdirSync2(dirname3(filePath), { recursive: true });
4688
+ mkdirSync2(dirname4(filePath), { recursive: true });
4656
4689
  atomicWriteFileSync(filePath, content);
4657
4690
  const lines = content.split("\n").length;
4658
4691
  return `File saved: ${filePath} (${lines} lines, ${content.length} bytes)`;
@@ -4664,9 +4697,9 @@ import { join as join6 } from "path";
4664
4697
  import { homedir as homedir3 } from "os";
4665
4698
 
4666
4699
  // src/memory/persistent-memory.ts
4667
- import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync7 } from "fs";
4700
+ import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync7 } from "fs";
4668
4701
  import { randomUUID, createHash } from "crypto";
4669
- import { dirname as dirname4, join as join5, resolve as resolve6 } from "path";
4702
+ import { dirname as dirname5, join as join5, resolve as resolve6 } from "path";
4670
4703
  function memoryStorePath(configDir) {
4671
4704
  return join5(configDir, MEMORY_STORE_FILE_NAME);
4672
4705
  }
@@ -4707,7 +4740,7 @@ function normalizeEntry(value) {
4707
4740
  }
4708
4741
  function legacyEntriesFromMarkdown(configDir) {
4709
4742
  const file = memoryMarkdownPath(configDir);
4710
- if (!existsSync10(file)) return [];
4743
+ if (!existsSync11(file)) return [];
4711
4744
  const content = readFileSync7(file, "utf-8").trim();
4712
4745
  if (!content) return [];
4713
4746
  const parts = content.split(/\n(?=##\s+\d{4}-\d{2}-\d{2})/g);
@@ -4724,7 +4757,7 @@ ${chunk}`).digest("hex").slice(0, 12);
4724
4757
  }
4725
4758
  function loadMemoryEntries(configDir) {
4726
4759
  const file = memoryStorePath(configDir);
4727
- if (!existsSync10(file)) return legacyEntriesFromMarkdown(configDir);
4760
+ if (!existsSync11(file)) return legacyEntriesFromMarkdown(configDir);
4728
4761
  const text = readFileSync7(file, "utf-8").trim();
4729
4762
  if (!text) return [];
4730
4763
  const entries = [];
@@ -5172,7 +5205,7 @@ function formatResults2(query, data, _requested) {
5172
5205
  }
5173
5206
 
5174
5207
  // src/agents/agent-config.ts
5175
- import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync8 } from "fs";
5208
+ import { existsSync as existsSync12, readdirSync as readdirSync6, readFileSync as readFileSync8 } from "fs";
5176
5209
  import { join as join7 } from "path";
5177
5210
  import { homedir as homedir4 } from "os";
5178
5211
  var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
@@ -5268,7 +5301,7 @@ function parseAgentConfig(raw, source, path3) {
5268
5301
  return cfg;
5269
5302
  }
5270
5303
  function loadAgentDir(dir, source) {
5271
- if (!existsSync11(dir)) return [];
5304
+ if (!existsSync12(dir)) return [];
5272
5305
  const out = [];
5273
5306
  for (const file of readdirSync6(dir)) {
5274
5307
  if (!file.endsWith(".json")) continue;
@@ -6000,13 +6033,13 @@ var taskStopTool = {
6000
6033
 
6001
6034
  // src/tools/builtin/git-tools.ts
6002
6035
  import { execFileSync as execFileSync2 } from "child_process";
6003
- import { existsSync as existsSync12 } from "fs";
6036
+ import { existsSync as existsSync13 } from "fs";
6004
6037
  import { join as join8 } from "path";
6005
6038
  function assertGitRepo(cwd) {
6006
6039
  let dir = cwd;
6007
6040
  const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
6008
6041
  while (dir && dir !== root) {
6009
- if (existsSync12(join8(dir, ".git"))) return;
6042
+ if (existsSync13(join8(dir, ".git"))) return;
6010
6043
  const parent = join8(dir, "..");
6011
6044
  if (parent === dir) break;
6012
6045
  dir = parent;
@@ -6272,7 +6305,7 @@ ${commitOutput.trim()}`;
6272
6305
  };
6273
6306
 
6274
6307
  // src/tools/builtin/notebook-edit.ts
6275
- import { readFileSync as readFileSync9, existsSync as existsSync13 } from "fs";
6308
+ import { readFileSync as readFileSync9, existsSync as existsSync14 } from "fs";
6276
6309
  import { writeFile } from "fs/promises";
6277
6310
  import { resolve as resolve7, extname as extname2 } from "path";
6278
6311
  var notebookEditTool = {
@@ -6327,7 +6360,7 @@ var notebookEditTool = {
6327
6360
  if (extname2(absPath).toLowerCase() !== ".ipynb") {
6328
6361
  throw new ToolError("notebook_edit", "path must point to a .ipynb file");
6329
6362
  }
6330
- if (!existsSync13(absPath)) {
6363
+ if (!existsSync14(absPath)) {
6331
6364
  throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
6332
6365
  }
6333
6366
  const raw = readFileSync9(absPath, "utf-8");
@@ -6747,7 +6780,7 @@ function estimateToolDefinitionTokens(def) {
6747
6780
 
6748
6781
  // src/tools/registry.ts
6749
6782
  import { pathToFileURL } from "url";
6750
- import { existsSync as existsSync14, mkdirSync as mkdirSync4, readdirSync as readdirSync7 } from "fs";
6783
+ import { existsSync as existsSync15, mkdirSync as mkdirSync4, readdirSync as readdirSync7 } from "fs";
6751
6784
  import { join as join9 } from "path";
6752
6785
  var ToolRegistry = class {
6753
6786
  tools = /* @__PURE__ */ new Map();
@@ -6909,7 +6942,7 @@ var ToolRegistry = class {
6909
6942
  * Returns the number of successfully loaded plugins.
6910
6943
  */
6911
6944
  async loadPlugins(pluginsDir, allowPlugins = false) {
6912
- if (!existsSync14(pluginsDir)) {
6945
+ if (!existsSync15(pluginsDir)) {
6913
6946
  try {
6914
6947
  mkdirSync4(pluginsDir, { recursive: true });
6915
6948
  } catch {
@@ -6972,10 +7005,6 @@ export {
6972
7005
  initTheme,
6973
7006
  theme,
6974
7007
  undoStack,
6975
- recordRecentlyDeniedAutoAction,
6976
- getRecentlyDeniedAutoActions,
6977
- clearRecentlyDeniedAutoActions,
6978
- defaultActionClassifier,
6979
7008
  renderDiff,
6980
7009
  memoryStorePath,
6981
7010
  memoryMarkdownPath,
@@ -6987,6 +7016,10 @@ export {
6987
7016
  expireMemoryEntry,
6988
7017
  exportMemoryEntries,
6989
7018
  formatMemoryForPrompt,
7019
+ recordRecentlyDeniedAutoAction,
7020
+ getRecentlyDeniedAutoActions,
7021
+ clearRecentlyDeniedAutoActions,
7022
+ defaultActionClassifier,
6990
7023
  isInterrupted,
6991
7024
  requestInterrupt,
6992
7025
  resetInterrupt,