jinzd-ai-cli 0.4.212 → 0.4.213

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.
@@ -37,7 +37,7 @@ import {
37
37
  VERSION,
38
38
  buildUserIdentityPrompt,
39
39
  runTestsTool
40
- } from "./chunk-KE4B3NOQ.js";
40
+ } from "./chunk-J35V3IAH.js";
41
41
  import {
42
42
  hasSemanticIndex,
43
43
  semanticSearch
@@ -61,8 +61,8 @@ import {
61
61
  import express from "express";
62
62
  import { createServer } from "http";
63
63
  import { WebSocketServer } from "ws";
64
- import { join as join20, dirname as dirname6, resolve as resolve8, relative as relative4, sep as sep3 } from "path";
65
- import { existsSync as existsSync26, readFileSync as readFileSync20, readdirSync as readdirSync12, statSync as statSync10, realpathSync } from "fs";
64
+ import { join as join21, dirname as dirname6, resolve as resolve9, relative as relative4, sep as sep3 } from "path";
65
+ import { existsSync as existsSync27, readFileSync as readFileSync21, readdirSync as readdirSync12, statSync as statSync10, realpathSync } from "fs";
66
66
  import { networkInterfaces } from "os";
67
67
 
68
68
  // src/config/config-manager.ts
@@ -5790,7 +5790,7 @@ import { dirname as dirname3 } from "path";
5790
5790
 
5791
5791
  // src/tools/executor.ts
5792
5792
  import chalk3 from "chalk";
5793
- import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
5793
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
5794
5794
  import { tmpdir } from "os";
5795
5795
 
5796
5796
  // src/core/readline-internal.ts
@@ -6627,6 +6627,157 @@ function flush() {
6627
6627
  }
6628
6628
  }
6629
6629
 
6630
+ // src/tools/action-classifier.ts
6631
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
6632
+ import { isAbsolute as isAbsolute2, join as join5, resolve as resolve4 } from "path";
6633
+ var RECENT_DENIED_LIMIT = 50;
6634
+ var recentlyDenied = [];
6635
+ function recordRecentlyDeniedAutoAction(call, classification) {
6636
+ recentlyDenied.unshift({
6637
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6638
+ tool: call.name,
6639
+ reason: classification.reason,
6640
+ ruleId: classification.ruleId,
6641
+ argsPreview: JSON.stringify(call.arguments).slice(0, 240)
6642
+ });
6643
+ recentlyDenied.splice(RECENT_DENIED_LIMIT);
6644
+ }
6645
+ function getRecentlyDeniedAutoActions() {
6646
+ return [...recentlyDenied];
6647
+ }
6648
+ function clearRecentlyDeniedAutoActions() {
6649
+ recentlyDenied.splice(0, recentlyDenied.length);
6650
+ }
6651
+ function normalizePath(value) {
6652
+ return value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
6653
+ }
6654
+ function isInside(root, target) {
6655
+ const absoluteRoot = resolve4(root);
6656
+ const absoluteTarget = isAbsolute2(target) ? target : resolve4(root, target);
6657
+ const base = normalizePath(absoluteRoot);
6658
+ const t = normalizePath(absoluteTarget);
6659
+ return t === base || t.startsWith(base + "/");
6660
+ }
6661
+ function getPathArg(call) {
6662
+ const raw = call.arguments.path ?? call.arguments.filePath ?? call.arguments.outputPath ?? call.arguments.file;
6663
+ return typeof raw === "string" && raw.trim() ? raw.trim() : null;
6664
+ }
6665
+ function splitShellWords(command) {
6666
+ const out = [];
6667
+ const re = /"([^"]*)"|'([^']*)'|([^\s]+)/g;
6668
+ let m;
6669
+ while (m = re.exec(command)) out.push(m[1] ?? m[2] ?? m[3] ?? "");
6670
+ return out.filter(Boolean);
6671
+ }
6672
+ function readPackageManifest(root) {
6673
+ const p = join5(root, "package.json");
6674
+ if (!existsSync8(p)) return null;
6675
+ try {
6676
+ return JSON.parse(readFileSync7(p, "utf-8"));
6677
+ } catch {
6678
+ return null;
6679
+ }
6680
+ }
6681
+ function manifestHasDependency(root, name) {
6682
+ const manifest = readPackageManifest(root);
6683
+ if (!manifest) return false;
6684
+ for (const key of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
6685
+ const deps = manifest[key];
6686
+ if (deps && typeof deps === "object" && Object.prototype.hasOwnProperty.call(deps, name)) return true;
6687
+ }
6688
+ return false;
6689
+ }
6690
+ function lockfileMentions(root, name) {
6691
+ for (const file of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]) {
6692
+ const p = join5(root, file);
6693
+ if (!existsSync8(p)) continue;
6694
+ try {
6695
+ if (readFileSync7(p, "utf-8").includes(name)) return true;
6696
+ } catch {
6697
+ }
6698
+ }
6699
+ return false;
6700
+ }
6701
+ function getInstallPackages(command) {
6702
+ const words = splitShellWords(command);
6703
+ const cmd = words[0]?.toLowerCase();
6704
+ const sub = words[1]?.toLowerCase();
6705
+ if (!cmd || !sub) return [];
6706
+ const isInstall = cmd === "npm" && (sub === "install" || sub === "i" || sub === "add") || (cmd === "pnpm" || cmd === "yarn") && (sub === "add" || sub === "install");
6707
+ if (!isInstall) return [];
6708
+ return words.slice(2).filter((w) => !w.startsWith("-")).filter((w) => w !== "." && w !== "./").map((w) => w.replace(/^@([^/]+)\/([^@]+)(?:@.+)?$/, "@$1/$2").replace(/^([^@]+)@.+$/, "$1"));
6709
+ }
6710
+ function isPlainGitPush(command) {
6711
+ if (!/^\s*git\s+push\b/i.test(command)) return false;
6712
+ if (/--force|-f\b|\+[^\s]+/i.test(command)) return false;
6713
+ return !/[;&|`$<>\n\r]/.test(command);
6714
+ }
6715
+ function hasSecretExfiltrationShape(command) {
6716
+ return /(?:\.env|config\.json|id_rsa|id_ed25519|AWS_SECRET|AICLI_API_KEY|OPENAI_API_KEY|ANTHROPIC_API_KEY)/i.test(command) && /\b(curl|wget|scp|rsync|nc|netcat|Invoke-WebRequest|Invoke-RestMethod)\b/i.test(command);
6717
+ }
6718
+ var RuleBasedActionClassifier = class {
6719
+ classify(call, dangerLevel, ctx) {
6720
+ if (call.name === "bash") return this.classifyShell(call, dangerLevel, ctx);
6721
+ if (call.name === "web_fetch" || call.name === "web_search" || call.name === "google_search") {
6722
+ return { decision: "auto-approve", reason: "read-only HTTP tool", ruleId: "auto.readonly-http", confidence: "high" };
6723
+ }
6724
+ if (dangerLevel === "safe") {
6725
+ return { decision: "auto-approve", reason: "safe read-only tool", ruleId: "auto.safe-tool", confidence: "high" };
6726
+ }
6727
+ if (dangerLevel === "write") {
6728
+ const p = getPathArg(call);
6729
+ if (p && (isInside(ctx.workspaceRoot, p) || (ctx.tempDirs ?? []).some((root) => isInside(root, p)))) {
6730
+ return { decision: "auto-approve", reason: "explicit write inside workspace/temp roots", ruleId: "auto.workspace-write", confidence: "high" };
6731
+ }
6732
+ return { decision: "confirm", reason: "write action is not proven low-risk", ruleId: "auto.write-confirm", confidence: "medium" };
6733
+ }
6734
+ return { decision: "confirm", reason: "destructive actions require confirmation", ruleId: "auto.destructive-confirm", confidence: "high" };
6735
+ }
6736
+ classifyShell(call, dangerLevel, ctx) {
6737
+ const command = String(call.arguments.command ?? "");
6738
+ if (/\bcurl\b[^\n|;&]*\|\s*(?:sudo\s+)?(?:bash|sh|zsh)\b/i.test(command)) {
6739
+ return { decision: "deny", reason: "curl pipe shell execution is blocked in auto mode", ruleId: "deny.curl-pipe-shell", confidence: "high" };
6740
+ }
6741
+ if (/\bgit\s+push\b/i.test(command) && /--force|-f\b|\+[^\s]+/i.test(command)) {
6742
+ return { decision: "deny", reason: "force push is blocked in auto mode", ruleId: "deny.force-push", confidence: "high" };
6743
+ }
6744
+ if (hasSecretExfiltrationShape(command)) {
6745
+ return { decision: "deny", reason: "possible secret exfiltration is blocked in auto mode", ruleId: "deny.secret-exfiltration", confidence: "high" };
6746
+ }
6747
+ if (/\b(terraform|tofu)\s+destroy\b|\bkubectl\s+delete\b|\bcdk\s+destroy\b/i.test(command)) {
6748
+ return { decision: "confirm", reason: "infrastructure destroy requires confirmation", ruleId: "confirm.iac-destroy", confidence: "high" };
6749
+ }
6750
+ if (/\b(prisma|sequelize|knex|typeorm)\b[^\n]*\b(migrate|migration)\b|\bdb:migrate\b/i.test(command)) {
6751
+ return { decision: "confirm", reason: "database migration requires confirmation", ruleId: "confirm.database-migration", confidence: "high" };
6752
+ }
6753
+ if (/\b(vercel|netlify|flyctl|railway|wrangler|serverless|firebase)\b[^\n]*\bdeploy\b|\bdeploy\b[^\n]*\b(prod|production)\b/i.test(command)) {
6754
+ return { decision: "confirm", reason: "production deployment requires confirmation", ruleId: "confirm.production-deploy", confidence: "high" };
6755
+ }
6756
+ if (/\b(iam|policy|token|secret|api[-_]?key|credential|permission|role)\b/i.test(command)) {
6757
+ return { decision: "confirm", reason: "credential or permission change requires confirmation", ruleId: "confirm.credentials-permissions", confidence: "medium" };
6758
+ }
6759
+ if (/\b(npx|pnpm\s+dlx|yarn\s+dlx)\s+(claude|codex|aider|cursor-agent|gemini)\b/i.test(command)) {
6760
+ return { decision: "confirm", reason: "third-party agent loop requires confirmation", ruleId: "confirm.third-party-agent", confidence: "high" };
6761
+ }
6762
+ if (isPlainGitPush(command)) {
6763
+ return { decision: "auto-approve", reason: "plain git push without force", ruleId: "auto.git-push", confidence: "high" };
6764
+ }
6765
+ const pkgs = getInstallPackages(command);
6766
+ if (pkgs.length > 0) {
6767
+ const allDeclared = pkgs.every((p) => manifestHasDependency(ctx.workspaceRoot, p) || lockfileMentions(ctx.workspaceRoot, p));
6768
+ return allDeclared ? { decision: "auto-approve", reason: "installing dependencies already declared in manifest/lockfile", ruleId: "auto.declared-dependency-install", confidence: "medium" } : { decision: "confirm", reason: "dependency install includes undeclared packages", ruleId: "confirm.undeclared-dependency-install", confidence: "medium" };
6769
+ }
6770
+ if (/\b(curl|wget|Invoke-WebRequest|Invoke-RestMethod)\b/i.test(command) && !/[>|;&`$]/.test(command)) {
6771
+ return { decision: "auto-approve", reason: "read-only HTTP shell command", ruleId: "auto.readonly-http-shell", confidence: "medium" };
6772
+ }
6773
+ if (dangerLevel === "safe") {
6774
+ return { decision: "auto-approve", reason: "safe shell action", ruleId: "auto.safe-shell", confidence: "medium" };
6775
+ }
6776
+ return { decision: "confirm", reason: "shell action is not proven low-risk", ruleId: "confirm.shell-default", confidence: "medium" };
6777
+ }
6778
+ };
6779
+ var defaultActionClassifier = new RuleBasedActionClassifier();
6780
+
6630
6781
  // src/tools/executor.ts
6631
6782
  function buildErrorPreview(content) {
6632
6783
  const meaningful = content.split("\n").filter(
@@ -6666,6 +6817,8 @@ var ToolExecutor = class {
6666
6817
  * destructive 操作仍然必须逐次确认。
6667
6818
  */
6668
6819
  sessionAutoApprove = false;
6820
+ /** Session-level Auto Mode: rule-based low-risk auto approval. */
6821
+ sessionAutoMode = false;
6669
6822
  /**
6670
6823
  * Logical session key used to scope per-session state in stateful tools
6671
6824
  * (currently only `bash`'s persistent cwd). Web mode sets this per-tab so
@@ -6825,6 +6978,40 @@ var ToolExecutor = class {
6825
6978
  };
6826
6979
  }
6827
6980
  }
6981
+ if (this.sessionAutoMode && networkPermission?.action !== "confirm") {
6982
+ const classification = defaultActionClassifier.classify(call, dangerLevel, {
6983
+ workspaceRoot: this.workspaceRoot,
6984
+ tempDirs: [tmpdir()]
6985
+ });
6986
+ if (classification.decision === "deny") {
6987
+ recordRecentlyDeniedAutoAction(call, classification);
6988
+ return {
6989
+ callId: call.id,
6990
+ content: `[Auto denied] ${classification.reason}. Do not retry without asking.`,
6991
+ isError: true
6992
+ };
6993
+ }
6994
+ if (classification.decision === "auto-approve") {
6995
+ this.printToolCall(call);
6996
+ if (dangerLevel === "write") this.printDiffPreview(call);
6997
+ console.log(theme.warning(` \u26A1 Auto-approved (/auto: ${classification.ruleId})`));
6998
+ try {
6999
+ const rawContent = await runTool(tool, call.arguments, call.name);
7000
+ const content = truncateOutput(rawContent, call.name);
7001
+ const wasTruncated = content !== rawContent;
7002
+ this.printToolResult(call.name, rawContent, false, wasTruncated);
7003
+ runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
7004
+ runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
7005
+ return { callId: call.id, content, isError: false };
7006
+ } catch (err) {
7007
+ const message = err instanceof Error ? err.message : String(err);
7008
+ this.printToolResult(call.name, message, true, false);
7009
+ runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
7010
+ runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
7011
+ return { callId: call.id, content: message, isError: true };
7012
+ }
7013
+ }
7014
+ }
6828
7015
  if (this.sessionAutoApprove && dangerLevel === "write") {
6829
7016
  this.printToolCall(call);
6830
7017
  if (dangerLevel === "write") this.printDiffPreview(call);
@@ -6992,7 +7179,7 @@ var ToolExecutor = class {
6992
7179
  rl.resume();
6993
7180
  process.stdout.write(prompt);
6994
7181
  this.confirming = true;
6995
- return new Promise((resolve9) => {
7182
+ return new Promise((resolve10) => {
6996
7183
  let completed = false;
6997
7184
  const cleanup = (result) => {
6998
7185
  if (completed) return;
@@ -7002,7 +7189,7 @@ var ToolExecutor = class {
7002
7189
  rl.pause();
7003
7190
  rlAny.output = savedOutput;
7004
7191
  this.confirming = false;
7005
- resolve9(result);
7192
+ resolve10(result);
7006
7193
  };
7007
7194
  const onLine = (line) => {
7008
7195
  const trimmed = line.trim();
@@ -7069,10 +7256,10 @@ var ToolExecutor = class {
7069
7256
  const filePath = String(call.arguments["path"] ?? "");
7070
7257
  const newContent = String(call.arguments["content"] ?? "");
7071
7258
  if (!filePath) return;
7072
- if (existsSync8(filePath)) {
7259
+ if (existsSync9(filePath)) {
7073
7260
  let oldContent;
7074
7261
  try {
7075
- oldContent = readFileSync7(filePath, "utf-8");
7262
+ oldContent = readFileSync8(filePath, "utf-8");
7076
7263
  } catch {
7077
7264
  return;
7078
7265
  }
@@ -7095,7 +7282,7 @@ var ToolExecutor = class {
7095
7282
  }
7096
7283
  } else if (call.name === "edit_file") {
7097
7284
  const filePath = String(call.arguments["path"] ?? "");
7098
- if (!filePath || !existsSync8(filePath)) return;
7285
+ if (!filePath || !existsSync9(filePath)) return;
7099
7286
  const oldStr = call.arguments["old_str"];
7100
7287
  const newStr = call.arguments["new_str"];
7101
7288
  if (oldStr !== void 0) {
@@ -7122,7 +7309,7 @@ var ToolExecutor = class {
7122
7309
  const to = Number(call.arguments["delete_to_line"] ?? from);
7123
7310
  let fileContent;
7124
7311
  try {
7125
- fileContent = readFileSync7(filePath, "utf-8");
7312
+ fileContent = readFileSync8(filePath, "utf-8");
7126
7313
  } catch {
7127
7314
  return;
7128
7315
  }
@@ -7176,7 +7363,7 @@ var ToolExecutor = class {
7176
7363
  rl.resume();
7177
7364
  process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
7178
7365
  this.confirming = true;
7179
- return new Promise((resolve9) => {
7366
+ return new Promise((resolve10) => {
7180
7367
  let completed = false;
7181
7368
  const cleanup = (answer) => {
7182
7369
  if (completed) return;
@@ -7186,7 +7373,7 @@ var ToolExecutor = class {
7186
7373
  rl.pause();
7187
7374
  rlAny.output = savedOutput;
7188
7375
  this.confirming = false;
7189
- resolve9(answer === "y");
7376
+ resolve10(answer === "y");
7190
7377
  };
7191
7378
  const onLine = (line) => {
7192
7379
  const trimmed = line.trim();
@@ -7214,11 +7401,11 @@ var ToolExecutor = class {
7214
7401
  };
7215
7402
 
7216
7403
  // src/tools/sensitive-paths.ts
7217
- import { resolve as resolve4, sep as sep2, basename as basename2 } from "path";
7404
+ import { resolve as resolve5, sep as sep2, basename as basename2 } from "path";
7218
7405
  import { homedir as homedir4 } from "os";
7219
7406
  var home = homedir4();
7220
7407
  function norm(p) {
7221
- const abs = resolve4(p);
7408
+ const abs = resolve5(p);
7222
7409
  return process.platform === "win32" ? abs.toLowerCase() : abs;
7223
7410
  }
7224
7411
  function homeRel(p) {
@@ -7377,7 +7564,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
7377
7564
  };
7378
7565
 
7379
7566
  // src/tools/builtin/edit-file.ts
7380
- import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
7567
+ import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
7381
7568
 
7382
7569
  // src/tools/builtin/patch-apply.ts
7383
7570
  function parseUnifiedDiff(patch) {
@@ -7738,7 +7925,7 @@ Note: Path can be absolute or relative to cwd.`,
7738
7925
  const filePath = String(args["path"] ?? "");
7739
7926
  const encoding = args["encoding"] ?? "utf-8";
7740
7927
  if (!filePath) throw new ToolError("edit_file", "path is required");
7741
- if (!existsSync9(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
7928
+ if (!existsSync10(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
7742
7929
  const verdict = classifyWritePath(filePath);
7743
7930
  if (verdict.sensitive && subAgentGuard.active) {
7744
7931
  throw new ToolError(
@@ -7746,7 +7933,7 @@ Note: Path can be absolute or relative to cwd.`,
7746
7933
  `Refused: sub-agents cannot edit sensitive paths \u2014 ${verdict.reason}. If this is genuinely needed, ask the parent agent to perform the edit so the user can approve it.`
7747
7934
  );
7748
7935
  }
7749
- const original = readFileSync8(filePath, encoding);
7936
+ const original = readFileSync9(filePath, encoding);
7750
7937
  if (args["patch"] !== void 0) {
7751
7938
  const patchText = String(args["patch"] ?? "");
7752
7939
  const stopOnError = args["stop_on_error"] !== false;
@@ -7904,8 +8091,8 @@ function truncatePreview(str, maxLen = 80) {
7904
8091
  }
7905
8092
 
7906
8093
  // src/tools/builtin/list-dir.ts
7907
- import { readdirSync as readdirSync4, statSync as statSync3, existsSync as existsSync10 } from "fs";
7908
- import { join as join5, basename as basename3 } from "path";
8094
+ import { readdirSync as readdirSync4, statSync as statSync3, existsSync as existsSync11 } from "fs";
8095
+ import { join as join6, basename as basename3 } from "path";
7909
8096
  var listDirTool = {
7910
8097
  definition: {
7911
8098
  name: "list_dir",
@@ -7927,7 +8114,7 @@ var listDirTool = {
7927
8114
  async execute(args) {
7928
8115
  const dirPath = String(args["path"] ?? process.cwd());
7929
8116
  const recursive = Boolean(args["recursive"] ?? false);
7930
- if (!existsSync10(dirPath)) {
8117
+ if (!existsSync11(dirPath)) {
7931
8118
  const targetName = basename3(dirPath).toLowerCase();
7932
8119
  const cwd = process.cwd();
7933
8120
  const suggestions = [];
@@ -7985,11 +8172,11 @@ function listRecursive(basePath, indent, recursive, lines) {
7985
8172
  if (entry.isDirectory()) {
7986
8173
  lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
7987
8174
  if (recursive) {
7988
- listRecursive(join5(basePath, entry.name), indent + " ", true, lines);
8175
+ listRecursive(join6(basePath, entry.name), indent + " ", true, lines);
7989
8176
  }
7990
8177
  } else {
7991
8178
  try {
7992
- const stat = statSync3(join5(basePath, entry.name));
8179
+ const stat = statSync3(join6(basePath, entry.name));
7993
8180
  const size = formatSize(stat.size);
7994
8181
  lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
7995
8182
  } catch {
@@ -8005,9 +8192,9 @@ function formatSize(bytes) {
8005
8192
  }
8006
8193
 
8007
8194
  // src/tools/builtin/grep-files.ts
8008
- import { readdirSync as readdirSync5, readFileSync as readFileSync9, statSync as statSync4, existsSync as existsSync11 } from "fs";
8195
+ import { readdirSync as readdirSync5, readFileSync as readFileSync10, statSync as statSync4, existsSync as existsSync12 } from "fs";
8009
8196
  import { readFile } from "fs/promises";
8010
- import { join as join6, relative } from "path";
8197
+ import { join as join7, relative } from "path";
8011
8198
  var grepFilesTool = {
8012
8199
  definition: {
8013
8200
  name: "grep_files",
@@ -8058,7 +8245,7 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
8058
8245
  const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
8059
8246
  const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
8060
8247
  if (!pattern) throw new ToolError("grep_files", "pattern is required");
8061
- if (!existsSync11(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
8248
+ if (!existsSync12(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
8062
8249
  const MAX_PATTERN_LENGTH = 1e3;
8063
8250
  if (pattern.length > MAX_PATTERN_LENGTH) {
8064
8251
  throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
@@ -8152,11 +8339,11 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
8152
8339
  if (paths.length >= maxFiles) return;
8153
8340
  if (entry.isDirectory()) {
8154
8341
  if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
8155
- walk(join6(dirPath, entry.name));
8342
+ walk(join7(dirPath, entry.name));
8156
8343
  } else if (entry.isFile()) {
8157
8344
  if (isBinary(entry.name)) continue;
8158
8345
  if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
8159
- const fullPath = join6(dirPath, entry.name);
8346
+ const fullPath = join7(dirPath, entry.name);
8160
8347
  try {
8161
8348
  if (statSync4(fullPath).size > 1e6) continue;
8162
8349
  } catch {
@@ -8211,7 +8398,7 @@ function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, r
8211
8398
  }
8212
8399
  let content;
8213
8400
  try {
8214
- content = readFileSync9(fullPath, "utf-8");
8401
+ content = readFileSync10(fullPath, "utf-8");
8215
8402
  } catch {
8216
8403
  return;
8217
8404
  }
@@ -8242,8 +8429,8 @@ function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, r
8242
8429
  }
8243
8430
 
8244
8431
  // src/tools/builtin/glob-files.ts
8245
- import { readdirSync as readdirSync6, statSync as statSync5, existsSync as existsSync12 } from "fs";
8246
- import { join as join7, relative as relative2, basename as basename4 } from "path";
8432
+ import { readdirSync as readdirSync6, statSync as statSync5, existsSync as existsSync13 } from "fs";
8433
+ import { join as join8, relative as relative2, basename as basename4 } from "path";
8247
8434
  var globFilesTool = {
8248
8435
  definition: {
8249
8436
  name: "glob_files",
@@ -8276,7 +8463,7 @@ Results sorted by most recent modification time. Automatically skips node_module
8276
8463
  const rootPath = String(args["path"] ?? process.cwd());
8277
8464
  const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
8278
8465
  if (!pattern) throw new ToolError("glob_files", "pattern is required");
8279
- if (!existsSync12(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
8466
+ if (!existsSync13(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
8280
8467
  const regex = globToRegex(pattern);
8281
8468
  const matches = [];
8282
8469
  collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
@@ -8353,7 +8540,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
8353
8540
  }
8354
8541
  for (const entry of entries) {
8355
8542
  if (results.length >= maxResults) break;
8356
- const fullPath = join7(dirPath, entry.name);
8543
+ const fullPath = join8(dirPath, entry.name);
8357
8544
  if (entry.isDirectory()) {
8358
8545
  if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
8359
8546
  collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
@@ -8435,7 +8622,7 @@ var runInteractiveTool = {
8435
8622
  PYTHONDONTWRITEBYTECODE: "1"
8436
8623
  };
8437
8624
  const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
8438
- return new Promise((resolve9) => {
8625
+ return new Promise((resolve10) => {
8439
8626
  const child = spawn2(executable, cmdArgs.map(String), {
8440
8627
  cwd: process.cwd(),
8441
8628
  env,
@@ -8468,22 +8655,22 @@ var runInteractiveTool = {
8468
8655
  setTimeout(writeNextLine, 400);
8469
8656
  const timer = setTimeout(() => {
8470
8657
  child.kill();
8471
- resolve9(`${prefixWarnings}[Timeout after ${timeout}ms]
8658
+ resolve10(`${prefixWarnings}[Timeout after ${timeout}ms]
8472
8659
  ${buildOutput(stdout, stderr)}`);
8473
8660
  }, timeout);
8474
8661
  child.on("close", (code) => {
8475
8662
  clearTimeout(timer);
8476
8663
  const output = buildOutput(stdout, stderr);
8477
8664
  if (code !== 0 && code !== null) {
8478
- resolve9(`${prefixWarnings}Exit code ${code}:
8665
+ resolve10(`${prefixWarnings}Exit code ${code}:
8479
8666
  ${output}`);
8480
8667
  } else {
8481
- resolve9(`${prefixWarnings}${output || "(no output)"}`);
8668
+ resolve10(`${prefixWarnings}${output || "(no output)"}`);
8482
8669
  }
8483
8670
  });
8484
8671
  child.on("error", (err) => {
8485
8672
  clearTimeout(timer);
8486
- resolve9(
8673
+ resolve10(
8487
8674
  `${prefixWarnings}Failed to start process "${executable}": ${err.message}
8488
8675
  Hint: On Windows, use the full path to the executable, e.g.:
8489
8676
  C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
@@ -9151,11 +9338,11 @@ Any of these triggers means use save_last_response, NOT write_file:
9151
9338
  };
9152
9339
 
9153
9340
  // src/tools/builtin/save-memory.ts
9154
- import { existsSync as existsSync13, readFileSync as readFileSync10, statSync as statSync6, mkdirSync as mkdirSync7 } from "fs";
9155
- import { join as join8 } from "path";
9341
+ import { existsSync as existsSync14, readFileSync as readFileSync11, statSync as statSync6, mkdirSync as mkdirSync7 } from "fs";
9342
+ import { join as join9 } from "path";
9156
9343
  import { homedir as homedir5 } from "os";
9157
9344
  function getMemoryFilePath() {
9158
- return join8(homedir5(), CONFIG_DIR_NAME, MEMORY_FILE_NAME);
9345
+ return join9(homedir5(), CONFIG_DIR_NAME, MEMORY_FILE_NAME);
9159
9346
  }
9160
9347
  function formatTimestamp() {
9161
9348
  const now = /* @__PURE__ */ new Date();
@@ -9179,8 +9366,8 @@ var saveMemoryTool = {
9179
9366
  const content = String(args["content"] ?? "").trim();
9180
9367
  if (!content) throw new ToolError("save_memory", "content is required");
9181
9368
  const memoryPath = getMemoryFilePath();
9182
- const configDir = join8(homedir5(), CONFIG_DIR_NAME);
9183
- if (!existsSync13(configDir)) {
9369
+ const configDir = join9(homedir5(), CONFIG_DIR_NAME);
9370
+ if (!existsSync14(configDir)) {
9184
9371
  mkdirSync7(configDir, { recursive: true });
9185
9372
  }
9186
9373
  const timestamp = formatTimestamp();
@@ -9188,7 +9375,7 @@ var saveMemoryTool = {
9188
9375
  ## ${timestamp}
9189
9376
  ${content}
9190
9377
  `;
9191
- const previous = existsSync13(memoryPath) ? readFileSync10(memoryPath, "utf-8") : "";
9378
+ const previous = existsSync14(memoryPath) ? readFileSync11(memoryPath, "utf-8") : "";
9192
9379
  atomicWriteFileSync(memoryPath, previous + entry);
9193
9380
  const byteSize = statSync6(memoryPath).size;
9194
9381
  return `Memory saved successfully. File size: ${byteSize} bytes in ${MEMORY_FILE_NAME}`;
@@ -9235,7 +9422,7 @@ function promptUser(rl, question) {
9235
9422
  console.log();
9236
9423
  console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
9237
9424
  process.stdout.write(chalk4.cyan("> "));
9238
- return new Promise((resolve9) => {
9425
+ return new Promise((resolve10) => {
9239
9426
  let completed = false;
9240
9427
  const cleanup = (answer) => {
9241
9428
  if (completed) return;
@@ -9245,7 +9432,7 @@ function promptUser(rl, question) {
9245
9432
  rl.pause();
9246
9433
  rlAny.output = savedOutput;
9247
9434
  askUserContext.prompting = false;
9248
- resolve9(answer);
9435
+ resolve10(answer);
9249
9436
  };
9250
9437
  const onLine = (line) => {
9251
9438
  cleanup(line);
@@ -10000,14 +10187,14 @@ var taskStopTool = {
10000
10187
 
10001
10188
  // src/tools/builtin/git-tools.ts
10002
10189
  import { execFileSync as execFileSync2 } from "child_process";
10003
- import { existsSync as existsSync14 } from "fs";
10004
- import { join as join9 } from "path";
10190
+ import { existsSync as existsSync15 } from "fs";
10191
+ import { join as join10 } from "path";
10005
10192
  function assertGitRepo(cwd) {
10006
10193
  let dir = cwd;
10007
10194
  const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
10008
10195
  while (dir && dir !== root) {
10009
- if (existsSync14(join9(dir, ".git"))) return;
10010
- const parent = join9(dir, "..");
10196
+ if (existsSync15(join10(dir, ".git"))) return;
10197
+ const parent = join10(dir, "..");
10011
10198
  if (parent === dir) break;
10012
10199
  dir = parent;
10013
10200
  }
@@ -10272,9 +10459,9 @@ ${commitOutput.trim()}`;
10272
10459
  };
10273
10460
 
10274
10461
  // src/tools/builtin/notebook-edit.ts
10275
- import { readFileSync as readFileSync11, existsSync as existsSync15 } from "fs";
10462
+ import { readFileSync as readFileSync12, existsSync as existsSync16 } from "fs";
10276
10463
  import { writeFile } from "fs/promises";
10277
- import { resolve as resolve5, extname as extname2 } from "path";
10464
+ import { resolve as resolve6, extname as extname2 } from "path";
10278
10465
  var notebookEditTool = {
10279
10466
  definition: {
10280
10467
  name: "notebook_edit",
@@ -10323,14 +10510,14 @@ var notebookEditTool = {
10323
10510
  if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
10324
10511
  throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
10325
10512
  }
10326
- const absPath = resolve5(filePath);
10513
+ const absPath = resolve6(filePath);
10327
10514
  if (extname2(absPath).toLowerCase() !== ".ipynb") {
10328
10515
  throw new ToolError("notebook_edit", "path must point to a .ipynb file");
10329
10516
  }
10330
- if (!existsSync15(absPath)) {
10517
+ if (!existsSync16(absPath)) {
10331
10518
  throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
10332
10519
  }
10333
- const raw = readFileSync11(absPath, "utf-8");
10520
+ const raw = readFileSync12(absPath, "utf-8");
10334
10521
  const nb = parseNotebook(raw);
10335
10522
  const cellIdx0 = cellIndexRaw - 1;
10336
10523
  undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
@@ -10747,8 +10934,8 @@ function estimateToolDefinitionTokens(def) {
10747
10934
 
10748
10935
  // src/tools/registry.ts
10749
10936
  import { pathToFileURL } from "url";
10750
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync7 } from "fs";
10751
- import { join as join10 } from "path";
10937
+ import { existsSync as existsSync17, mkdirSync as mkdirSync8, readdirSync as readdirSync7 } from "fs";
10938
+ import { join as join11 } from "path";
10752
10939
  var ToolRegistry = class {
10753
10940
  tools = /* @__PURE__ */ new Map();
10754
10941
  pluginToolNames = /* @__PURE__ */ new Set();
@@ -10909,7 +11096,7 @@ var ToolRegistry = class {
10909
11096
  * Returns the number of successfully loaded plugins.
10910
11097
  */
10911
11098
  async loadPlugins(pluginsDir, allowPlugins = false) {
10912
- if (!existsSync16(pluginsDir)) {
11099
+ if (!existsSync17(pluginsDir)) {
10913
11100
  try {
10914
11101
  mkdirSync8(pluginsDir, { recursive: true });
10915
11102
  } catch {
@@ -10935,12 +11122,12 @@ var ToolRegistry = class {
10935
11122
  process.stderr.write(
10936
11123
  `
10937
11124
  [plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
10938
- ` + files.map((f) => ` + ${join10(pluginsDir, f)}`).join("\n") + "\n\n"
11125
+ ` + files.map((f) => ` + ${join11(pluginsDir, f)}`).join("\n") + "\n\n"
10939
11126
  );
10940
11127
  let loaded = 0;
10941
11128
  for (const file of files) {
10942
11129
  try {
10943
- const fileUrl = pathToFileURL(join10(pluginsDir, file)).href;
11130
+ const fileUrl = pathToFileURL(join11(pluginsDir, file)).href;
10944
11131
  const mod = await import(fileUrl);
10945
11132
  const tool = mod.tool ?? mod.default?.tool ?? mod.default;
10946
11133
  if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
@@ -11102,7 +11289,7 @@ var McpClient = class {
11102
11289
  // 内部方法:JSON-RPC 通信
11103
11290
  // ══════════════════════════════════════════════════════════════════
11104
11291
  sendRequest(method, params) {
11105
- return new Promise((resolve9, reject) => {
11292
+ return new Promise((resolve10, reject) => {
11106
11293
  if (!this.process?.stdin?.writable) {
11107
11294
  return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
11108
11295
  }
@@ -11126,7 +11313,7 @@ var McpClient = class {
11126
11313
  this.pendingRequests.set(id, {
11127
11314
  resolve: (result) => {
11128
11315
  cleanup();
11129
- resolve9(result);
11316
+ resolve10(result);
11130
11317
  },
11131
11318
  reject: (error) => {
11132
11319
  cleanup();
@@ -11203,13 +11390,13 @@ var McpClient = class {
11203
11390
  }
11204
11391
  /** Promise 超时包装 */
11205
11392
  withTimeout(promise, ms, label) {
11206
- return new Promise((resolve9, reject) => {
11393
+ return new Promise((resolve10, reject) => {
11207
11394
  const timer = setTimeout(() => {
11208
11395
  reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
11209
11396
  }, ms);
11210
11397
  promise.then((val) => {
11211
11398
  clearTimeout(timer);
11212
- resolve9(val);
11399
+ resolve10(val);
11213
11400
  }).catch((err) => {
11214
11401
  clearTimeout(timer);
11215
11402
  reject(err);
@@ -11486,11 +11673,11 @@ var McpManager = class {
11486
11673
  };
11487
11674
 
11488
11675
  // src/skills/manager.ts
11489
- import { existsSync as existsSync17, readdirSync as readdirSync8, mkdirSync as mkdirSync9, statSync as statSync7 } from "fs";
11490
- import { join as join11 } from "path";
11676
+ import { existsSync as existsSync18, readdirSync as readdirSync8, mkdirSync as mkdirSync9, statSync as statSync7 } from "fs";
11677
+ import { join as join12 } from "path";
11491
11678
 
11492
11679
  // src/skills/types.ts
11493
- import { readFileSync as readFileSync12 } from "fs";
11680
+ import { readFileSync as readFileSync13 } from "fs";
11494
11681
  import { basename as basename5 } from "path";
11495
11682
  function parseSimpleYaml(yaml) {
11496
11683
  const result = {};
@@ -11512,7 +11699,7 @@ function parseYamlArray(value) {
11512
11699
  function parseSkillFile(filePath) {
11513
11700
  let raw;
11514
11701
  try {
11515
- raw = readFileSync12(filePath, "utf-8");
11702
+ raw = readFileSync13(filePath, "utf-8");
11516
11703
  } catch {
11517
11704
  return null;
11518
11705
  }
@@ -11555,7 +11742,7 @@ var SkillManager = class {
11555
11742
  /** 发现并加载 skillsDir 下所有 .md 文件,返回加载数量 */
11556
11743
  loadSkills() {
11557
11744
  this.skills.clear();
11558
- if (!existsSync17(this.skillsDir)) {
11745
+ if (!existsSync18(this.skillsDir)) {
11559
11746
  try {
11560
11747
  mkdirSync9(this.skillsDir, { recursive: true });
11561
11748
  } catch {
@@ -11570,14 +11757,14 @@ var SkillManager = class {
11570
11757
  }
11571
11758
  for (const entry of entries) {
11572
11759
  let filePath;
11573
- const fullPath = join11(this.skillsDir, entry);
11760
+ const fullPath = join12(this.skillsDir, entry);
11574
11761
  if (entry.endsWith(".md")) {
11575
11762
  filePath = fullPath;
11576
11763
  } else {
11577
11764
  try {
11578
11765
  if (statSync7(fullPath).isDirectory()) {
11579
- const skillMd = join11(fullPath, "SKILL.md");
11580
- if (existsSync17(skillMd)) {
11766
+ const skillMd = join12(fullPath, "SKILL.md");
11767
+ if (existsSync18(skillMd)) {
11581
11768
  filePath = skillMd;
11582
11769
  } else {
11583
11770
  continue;
@@ -11642,7 +11829,7 @@ var SkillManager = class {
11642
11829
  // src/web/tool-executor-web.ts
11643
11830
  import { randomUUID as randomUUID2 } from "crypto";
11644
11831
  import { tmpdir as tmpdir2 } from "os";
11645
- import { existsSync as existsSync18, readFileSync as readFileSync13 } from "fs";
11832
+ import { existsSync as existsSync19, readFileSync as readFileSync14 } from "fs";
11646
11833
  var ToolExecutorWeb = class _ToolExecutorWeb {
11647
11834
  constructor(registry, ws) {
11648
11835
  this.registry = registry;
@@ -11671,6 +11858,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11671
11858
  confirming = false;
11672
11859
  /** Session-level auto-approve toggle (/yolo command) */
11673
11860
  sessionAutoApprove = false;
11861
+ /** Session-level Auto Mode: rule-based low-risk auto approval. */
11862
+ sessionAutoMode = false;
11674
11863
  /** Track tool start times for duration calculation */
11675
11864
  toolStartTimes = /* @__PURE__ */ new Map();
11676
11865
  /**
@@ -11710,33 +11899,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11710
11899
  }
11711
11900
  /** Resolve a pending confirm from client response */
11712
11901
  resolveConfirm(requestId, approved) {
11713
- const resolve9 = this.pendingConfirms.get(requestId);
11714
- if (resolve9) {
11902
+ const resolve10 = this.pendingConfirms.get(requestId);
11903
+ if (resolve10) {
11715
11904
  this.clearPendingTimer(requestId);
11716
11905
  this.pendingConfirms.delete(requestId);
11717
11906
  this.confirming = false;
11718
- resolve9(approved);
11907
+ resolve10(approved);
11719
11908
  }
11720
11909
  }
11721
11910
  /** Resolve a pending batch confirm from client response */
11722
11911
  resolveBatchConfirm(requestId, decision) {
11723
- const resolve9 = this.pendingBatchConfirms.get(requestId);
11724
- if (resolve9) {
11912
+ const resolve10 = this.pendingBatchConfirms.get(requestId);
11913
+ if (resolve10) {
11725
11914
  this.clearPendingTimer(requestId);
11726
11915
  this.pendingBatchConfirms.delete(requestId);
11727
11916
  this.confirming = false;
11728
11917
  if (decision === "all" || decision === "none") {
11729
- resolve9(decision);
11918
+ resolve10(decision);
11730
11919
  } else {
11731
- resolve9(new Set(decision));
11920
+ resolve10(new Set(decision));
11732
11921
  }
11733
11922
  }
11734
11923
  }
11735
11924
  /** Cancel all pending confirms (e.g., on disconnect) */
11736
11925
  cancelAll() {
11737
- for (const resolve9 of this.pendingConfirms.values()) resolve9(false);
11926
+ for (const resolve10 of this.pendingConfirms.values()) resolve10(false);
11738
11927
  this.pendingConfirms.clear();
11739
- for (const resolve9 of this.pendingBatchConfirms.values()) resolve9("none");
11928
+ for (const resolve10 of this.pendingBatchConfirms.values()) resolve10("none");
11740
11929
  this.pendingBatchConfirms.clear();
11741
11930
  this.confirming = false;
11742
11931
  }
@@ -11779,9 +11968,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11779
11968
  if (call.name === "write_file") {
11780
11969
  const filePath = String(call.arguments["path"] ?? "");
11781
11970
  const newContent = String(call.arguments["content"] ?? "");
11782
- if (filePath && existsSync18(filePath)) {
11971
+ if (filePath && existsSync19(filePath)) {
11783
11972
  try {
11784
- const old = readFileSync13(filePath, "utf-8");
11973
+ const old = readFileSync14(filePath, "utf-8");
11785
11974
  return renderDiff(old, newContent, { filePath });
11786
11975
  } catch {
11787
11976
  }
@@ -11812,8 +12001,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11812
12001
  diff: this.getDiffPreview(call)
11813
12002
  };
11814
12003
  this.send(msg);
11815
- return new Promise((resolve9) => {
11816
- this.pendingConfirms.set(requestId, resolve9);
12004
+ return new Promise((resolve10) => {
12005
+ this.pendingConfirms.set(requestId, resolve10);
11817
12006
  this.pendingTimers.set(requestId, setTimeout(() => {
11818
12007
  if (this.pendingConfirms.has(requestId)) {
11819
12008
  this.resolveConfirm(requestId, false);
@@ -11837,8 +12026,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11837
12026
  files
11838
12027
  };
11839
12028
  this.send(msg);
11840
- return new Promise((resolve9) => {
11841
- this.pendingBatchConfirms.set(requestId, resolve9);
12029
+ return new Promise((resolve10) => {
12030
+ this.pendingBatchConfirms.set(requestId, resolve10);
11842
12031
  this.pendingTimers.set(requestId, setTimeout(() => {
11843
12032
  if (this.pendingBatchConfirms.has(requestId)) {
11844
12033
  this.resolveBatchConfirm(requestId, "none");
@@ -11948,6 +12137,35 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11948
12137
  }
11949
12138
  }
11950
12139
  this.sendToolCallStart(call);
12140
+ if (this.sessionAutoMode && networkPermission?.action !== "confirm") {
12141
+ const classification = defaultActionClassifier.classify(call, dangerLevel, {
12142
+ workspaceRoot: this.workspaceRoot,
12143
+ tempDirs: [tmpdir2()]
12144
+ });
12145
+ if (classification.decision === "deny") {
12146
+ recordRecentlyDeniedAutoAction(call, classification);
12147
+ const rejectionMsg = `[Auto denied] ${classification.reason}. Do not retry without asking.`;
12148
+ this.sendToolCallResult(call, rejectionMsg, true);
12149
+ return { callId: call.id, content: rejectionMsg, isError: true };
12150
+ }
12151
+ if (classification.decision === "auto-approve") {
12152
+ this.send({ type: "info", message: `\u26A1 Auto-approved (/auto: ${classification.ruleId})` });
12153
+ try {
12154
+ const rawContent = await runTool(tool, call.arguments, call.name);
12155
+ const content = truncateOutput(rawContent, call.name);
12156
+ this.sendToolCallResult(call, rawContent, false);
12157
+ runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
12158
+ runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
12159
+ return { callId: call.id, content, isError: false };
12160
+ } catch (err) {
12161
+ const message = err instanceof Error ? err.message : String(err);
12162
+ this.sendToolCallResult(call, message, true);
12163
+ runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
12164
+ runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
12165
+ return { callId: call.id, content: message, isError: true };
12166
+ }
12167
+ }
12168
+ }
11951
12169
  if (this.sessionAutoApprove && dangerLevel === "write") {
11952
12170
  try {
11953
12171
  const rawContent = await runTool(tool, call.arguments, call.name);
@@ -12055,20 +12273,20 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12055
12273
  };
12056
12274
 
12057
12275
  // src/core/system-prompt-builder.ts
12058
- import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
12059
- import { join as join13 } from "path";
12276
+ import { existsSync as existsSync21, readFileSync as readFileSync16 } from "fs";
12277
+ import { join as join14 } from "path";
12060
12278
 
12061
12279
  // src/repl/dev-state.ts
12062
- import { existsSync as existsSync19, readFileSync as readFileSync14, unlinkSync as unlinkSync4, mkdirSync as mkdirSync10 } from "fs";
12063
- import { join as join12 } from "path";
12280
+ import { existsSync as existsSync20, readFileSync as readFileSync15, unlinkSync as unlinkSync4, mkdirSync as mkdirSync10 } from "fs";
12281
+ import { join as join13 } from "path";
12064
12282
  import { homedir as homedir6 } from "os";
12065
12283
  function getDevStatePath() {
12066
- return join12(homedir6(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
12284
+ return join13(homedir6(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
12067
12285
  }
12068
12286
  function loadDevState() {
12069
12287
  const path3 = getDevStatePath();
12070
- if (!existsSync19(path3)) return null;
12071
- const content = readFileSync14(path3, "utf-8").trim();
12288
+ if (!existsSync20(path3)) return null;
12289
+ const content = readFileSync15(path3, "utf-8").trim();
12072
12290
  return content || null;
12073
12291
  }
12074
12292
 
@@ -12126,9 +12344,9 @@ ${ctx.activeSkill.content}`);
12126
12344
  return { stable: stableParts.join("\n\n---\n\n"), volatile };
12127
12345
  }
12128
12346
  function loadMemoryContent(configDir) {
12129
- const memoryPath = join13(configDir, MEMORY_FILE_NAME);
12130
- if (!existsSync20(memoryPath)) return null;
12131
- let content = readFileSync15(memoryPath, "utf-8").trim();
12347
+ const memoryPath = join14(configDir, MEMORY_FILE_NAME);
12348
+ if (!existsSync21(memoryPath)) return null;
12349
+ let content = readFileSync16(memoryPath, "utf-8").trim();
12132
12350
  if (!content) return null;
12133
12351
  if (content.length > MEMORY_MAX_CHARS) {
12134
12352
  content = content.slice(-MEMORY_MAX_CHARS);
@@ -12337,8 +12555,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
12337
12555
  }
12338
12556
 
12339
12557
  // src/core/context-files.ts
12340
- import { existsSync as existsSync21, readFileSync as readFileSync16 } from "fs";
12341
- import { join as join14, relative as relative3, resolve as resolve6 } from "path";
12558
+ import { existsSync as existsSync22, readFileSync as readFileSync17 } from "fs";
12559
+ import { join as join15, relative as relative3, resolve as resolve7 } from "path";
12342
12560
  function uniqueNonEmpty(values) {
12343
12561
  const seen = /* @__PURE__ */ new Set();
12344
12562
  const out = [];
@@ -12361,14 +12579,14 @@ function displayPath(filePath, cwd) {
12361
12579
  return filePath;
12362
12580
  }
12363
12581
  function isInsideOrEqual(parent, child) {
12364
- const rel = relative3(resolve6(parent), resolve6(child));
12582
+ const rel = relative3(resolve7(parent), resolve7(child));
12365
12583
  return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
12366
12584
  }
12367
12585
  function readContextFile(level, filePath, cwd, maxBytes) {
12368
12586
  const name = filePath.split(/[\\/]/).pop() ?? filePath;
12369
12587
  const shown = displayPath(filePath, cwd);
12370
12588
  try {
12371
- const raw = readFileSync16(filePath);
12589
+ const raw = readFileSync17(filePath);
12372
12590
  const byteCount = raw.byteLength;
12373
12591
  if (byteCount === 0) {
12374
12592
  return {
@@ -12415,8 +12633,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
12415
12633
  function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
12416
12634
  const skipped = [];
12417
12635
  for (const candidate of candidates) {
12418
- const filePath = join14(dir, candidate);
12419
- if (!existsSync21(filePath)) continue;
12636
+ const filePath = join15(dir, candidate);
12637
+ if (!existsSync22(filePath)) continue;
12420
12638
  const result = readContextFile(level, filePath, cwd, maxBytes);
12421
12639
  if (result.skipped) skipped.push(result.skipped);
12422
12640
  if (result.layer) return { layer: result.layer, skipped };
@@ -12424,7 +12642,7 @@ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
12424
12642
  return { layer: null, skipped };
12425
12643
  }
12426
12644
  function loadContextFiles(options) {
12427
- const cwd = resolve6(options.cwd);
12645
+ const cwd = resolve7(options.cwd);
12428
12646
  const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
12429
12647
  const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
12430
12648
  const skipped = [];
@@ -12432,7 +12650,7 @@ function loadContextFiles(options) {
12432
12650
  return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
12433
12651
  }
12434
12652
  if (options.setting && options.setting !== "auto") {
12435
- const filePath = resolve6(cwd, String(options.setting));
12653
+ const filePath = resolve7(cwd, String(options.setting));
12436
12654
  if (!isInsideOrEqual(cwd, filePath)) {
12437
12655
  skipped.push({
12438
12656
  level: "single",
@@ -12443,7 +12661,7 @@ function loadContextFiles(options) {
12443
12661
  });
12444
12662
  return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
12445
12663
  }
12446
- if (!existsSync21(filePath)) {
12664
+ if (!existsSync22(filePath)) {
12447
12665
  skipped.push({
12448
12666
  level: "single",
12449
12667
  filePath,
@@ -12476,7 +12694,7 @@ function loadContextFiles(options) {
12476
12694
  skipped.push(...result.skipped);
12477
12695
  if (result.layer) layers.push(result.layer);
12478
12696
  }
12479
- if (resolve6(options.cwd) !== resolve6(options.projectRoot)) {
12697
+ if (resolve7(options.cwd) !== resolve7(options.projectRoot)) {
12480
12698
  const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
12481
12699
  skipped.push(...result.skipped);
12482
12700
  if (result.layer) layers.push(result.layer);
@@ -12494,13 +12712,13 @@ function loadContextFiles(options) {
12494
12712
  }
12495
12713
 
12496
12714
  // src/web/session-handler.ts
12497
- import { existsSync as existsSync24, readFileSync as readFileSync18, writeFileSync as writeFileSync2, mkdirSync as mkdirSync11, readdirSync as readdirSync10, statSync as statSync9, createWriteStream } from "fs";
12498
- import { join as join18, resolve as resolve7, dirname as dirname5 } from "path";
12715
+ import { existsSync as existsSync25, readFileSync as readFileSync19, writeFileSync as writeFileSync2, mkdirSync as mkdirSync11, readdirSync as readdirSync10, statSync as statSync9, createWriteStream } from "fs";
12716
+ import { join as join19, resolve as resolve8, dirname as dirname5 } from "path";
12499
12717
 
12500
12718
  // src/tools/git-context.ts
12501
12719
  import { execSync as execSync2 } from "child_process";
12502
- import { existsSync as existsSync22 } from "fs";
12503
- import { join as join15 } from "path";
12720
+ import { existsSync as existsSync23 } from "fs";
12721
+ import { join as join16 } from "path";
12504
12722
  function runGit2(cmd, cwd) {
12505
12723
  try {
12506
12724
  return execSync2(`git ${cmd}`, {
@@ -12517,7 +12735,7 @@ function getGitRoot(cwd = process.cwd()) {
12517
12735
  return runGit2("rev-parse --show-toplevel", cwd);
12518
12736
  }
12519
12737
  function getGitContext(cwd = process.cwd()) {
12520
- if (!existsSync22(join15(cwd, ".git"))) {
12738
+ if (!existsSync23(join16(cwd, ".git"))) {
12521
12739
  const result = runGit2("rev-parse --git-dir", cwd);
12522
12740
  if (!result) return null;
12523
12741
  }
@@ -12646,8 +12864,8 @@ If no security issues found, state "\u2705 No security vulnerabilities detected"
12646
12864
  }
12647
12865
 
12648
12866
  // src/repl/commands/project-init.ts
12649
- import { existsSync as existsSync23, readFileSync as readFileSync17, readdirSync as readdirSync9, statSync as statSync8 } from "fs";
12650
- import { join as join16 } from "path";
12867
+ import { existsSync as existsSync24, readFileSync as readFileSync18, readdirSync as readdirSync9, statSync as statSync8 } from "fs";
12868
+ import { join as join17 } from "path";
12651
12869
  var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
12652
12870
  "node_modules",
12653
12871
  ".git",
@@ -12691,11 +12909,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
12691
12909
  const sorted = filtered.sort((a, b) => {
12692
12910
  let aIsDir = false, bIsDir = false;
12693
12911
  try {
12694
- aIsDir = statSync8(join16(d, a)).isDirectory();
12912
+ aIsDir = statSync8(join17(d, a)).isDirectory();
12695
12913
  } catch {
12696
12914
  }
12697
12915
  try {
12698
- bIsDir = statSync8(join16(d, b)).isDirectory();
12916
+ bIsDir = statSync8(join17(d, b)).isDirectory();
12699
12917
  } catch {
12700
12918
  }
12701
12919
  if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
@@ -12703,7 +12921,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
12703
12921
  });
12704
12922
  for (let i = 0; i < sorted.length && count < maxEntries; i++) {
12705
12923
  const name = sorted[i];
12706
- const fullPath = join16(d, name);
12924
+ const fullPath = join17(d, name);
12707
12925
  const isLast = i === sorted.length - 1;
12708
12926
  const connector = isLast ? "+-- " : "|-- ";
12709
12927
  let isDir;
@@ -12730,7 +12948,7 @@ function scanProject(cwd) {
12730
12948
  configFiles: [],
12731
12949
  directoryStructure: ""
12732
12950
  };
12733
- const check = (file) => existsSync23(join16(cwd, file));
12951
+ const check = (file) => existsSync24(join17(cwd, file));
12734
12952
  const configCandidates = [
12735
12953
  "package.json",
12736
12954
  "tsconfig.json",
@@ -12757,7 +12975,7 @@ function scanProject(cwd) {
12757
12975
  info.type = "node";
12758
12976
  info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
12759
12977
  try {
12760
- const pkg = JSON.parse(readFileSync17(join16(cwd, "package.json"), "utf-8"));
12978
+ const pkg = JSON.parse(readFileSync18(join17(cwd, "package.json"), "utf-8"));
12761
12979
  const scripts = pkg.scripts ?? {};
12762
12980
  info.buildCommand = scripts.build ? "npm run build" : void 0;
12763
12981
  info.testCommand = scripts.test ? "npm test" : void 0;
@@ -13417,7 +13635,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
13417
13635
  }
13418
13636
 
13419
13637
  // src/hub/persist.ts
13420
- import { join as join17 } from "path";
13638
+ import { join as join18 } from "path";
13421
13639
  function discussionToMessages(state2) {
13422
13640
  const out = [];
13423
13641
  const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
@@ -13455,7 +13673,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
13455
13673
  session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
13456
13674
  session.titleAiGenerated = true;
13457
13675
  await sm.save();
13458
- return { id: session.id, path: join17(config.getHistoryDir(), `${session.id}.json`) };
13676
+ return { id: session.id, path: join18(config.getHistoryDir(), `${session.id}.json`) };
13459
13677
  }
13460
13678
 
13461
13679
  // src/web/session-handler.ts
@@ -13639,10 +13857,10 @@ var SessionHandler = class {
13639
13857
  return;
13640
13858
  }
13641
13859
  case "ask_user_response": {
13642
- const resolve9 = this.pendingAskUser.get(msg.requestId);
13643
- if (resolve9) {
13860
+ const resolve10 = this.pendingAskUser.get(msg.requestId);
13861
+ if (resolve10) {
13644
13862
  this.pendingAskUser.delete(msg.requestId);
13645
- resolve9(msg.answer);
13863
+ resolve10(msg.answer);
13646
13864
  }
13647
13865
  return;
13648
13866
  }
@@ -13653,10 +13871,10 @@ var SessionHandler = class {
13653
13871
  case "memory_rebuild":
13654
13872
  return this.handleMemoryRebuild(Boolean(msg.full));
13655
13873
  case "auto_pause_response": {
13656
- const resolve9 = this.pendingAutoPause.get(msg.requestId);
13657
- if (resolve9) {
13874
+ const resolve10 = this.pendingAutoPause.get(msg.requestId);
13875
+ if (resolve10) {
13658
13876
  this.pendingAutoPause.delete(msg.requestId);
13659
- resolve9({ action: msg.action, message: msg.message });
13877
+ resolve10({ action: msg.action, message: msg.message });
13660
13878
  }
13661
13879
  return;
13662
13880
  }
@@ -13671,10 +13889,10 @@ var SessionHandler = class {
13671
13889
  this.hubOrchestrator?.abort();
13672
13890
  return;
13673
13891
  case "hub_steer": {
13674
- const resolve9 = this.pendingHubReview.get(msg.requestId);
13675
- if (resolve9) {
13892
+ const resolve10 = this.pendingHubReview.get(msg.requestId);
13893
+ if (resolve10) {
13676
13894
  this.pendingHubReview.delete(msg.requestId);
13677
- resolve9({ action: msg.action, message: msg.message });
13895
+ resolve10({ action: msg.action, message: msg.message });
13678
13896
  }
13679
13897
  return;
13680
13898
  }
@@ -13687,9 +13905,9 @@ var SessionHandler = class {
13687
13905
  runLifecycleHooks(this.config.get("hooks") ?? void 0, "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
13688
13906
  this.toolExecutor.cancelAll();
13689
13907
  if (this.abortController) this.abortController.abort();
13690
- for (const resolve9 of this.pendingAskUser.values()) resolve9(null);
13908
+ for (const resolve10 of this.pendingAskUser.values()) resolve10(null);
13691
13909
  this.pendingAskUser.clear();
13692
- for (const resolve9 of this.pendingAutoPause.values()) resolve9({ action: "stop" });
13910
+ for (const resolve10 of this.pendingAutoPause.values()) resolve10({ action: "stop" });
13693
13911
  this.pendingAutoPause.clear();
13694
13912
  this.saveIfNeeded();
13695
13913
  }
@@ -13823,9 +14041,9 @@ var SessionHandler = class {
13823
14041
  this.hubOrchestrator = orchestrator;
13824
14042
  orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
13825
14043
  if (config.humanSteer) {
13826
- orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve9) => {
14044
+ orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve10) => {
13827
14045
  const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
13828
- this.pendingHubReview.set(requestId, resolve9);
14046
+ this.pendingHubReview.set(requestId, resolve10);
13829
14047
  this.send({ type: "hub_review", requestId, round, maxRounds });
13830
14048
  });
13831
14049
  }
@@ -14182,8 +14400,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
14182
14400
  onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
14183
14401
  requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
14184
14402
  const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
14185
- const pauseResp = await new Promise((resolve9) => {
14186
- this.pendingAutoPause.set(requestId, resolve9);
14403
+ const pauseResp = await new Promise((resolve10) => {
14404
+ this.pendingAutoPause.set(requestId, resolve10);
14187
14405
  this.send({
14188
14406
  type: "auto_pause_request",
14189
14407
  requestId,
@@ -14362,8 +14580,8 @@ ${summaryContent}`,
14362
14580
  }
14363
14581
  if (chunk.done) break;
14364
14582
  }
14365
- await new Promise((resolve9, reject) => {
14366
- fileStream.end((err) => err ? reject(err) : resolve9());
14583
+ await new Promise((resolve10, reject) => {
14584
+ fileStream.end((err) => err ? reject(err) : resolve10());
14367
14585
  });
14368
14586
  const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
14369
14587
  if (verdict.kind === "reject") {
@@ -14389,7 +14607,7 @@ ${summaryContent}`,
14389
14607
  } catch (err) {
14390
14608
  if (fileStream) {
14391
14609
  try {
14392
- await new Promise((resolve9) => fileStream.end(() => resolve9()));
14610
+ await new Promise((resolve10) => fileStream.end(() => resolve10()));
14393
14611
  } catch {
14394
14612
  }
14395
14613
  }
@@ -14819,6 +15037,47 @@ ${activated.meta.description || ""}` });
14819
15037
  break;
14820
15038
  }
14821
15039
  // ── /yolo ──────────────────────────────────────────────────────
15040
+ case "auto": {
15041
+ const sub = args[0]?.toLowerCase() ?? "status";
15042
+ if (sub === "on") {
15043
+ this.toolExecutor.sessionAutoMode = true;
15044
+ this.send({ type: "info", message: "\u26A1 Auto Mode ON \u2014 low-risk actions may auto-run; high-risk actions still confirm or deny. Auto Mode is session-scoped." });
15045
+ } else if (sub === "off") {
15046
+ this.toolExecutor.sessionAutoMode = false;
15047
+ this.send({ type: "info", message: "Auto Mode disabled." });
15048
+ } else if (sub === "status") {
15049
+ this.send({ type: "info", message: `Auto Mode: ${this.toolExecutor.sessionAutoMode ? "on" : "off"}
15050
+ Scope: current session only
15051
+ Denied: /permissions recently-denied` });
15052
+ } else {
15053
+ this.send({ type: "error", message: "Usage: /auto [on|off|status]" });
15054
+ }
15055
+ this.sendStatus();
15056
+ break;
15057
+ }
15058
+ case "permissions": {
15059
+ const sub = args[0]?.toLowerCase() ?? "recently-denied";
15060
+ if (sub === "recently-denied") {
15061
+ const rows = getRecentlyDeniedAutoActions();
15062
+ if (rows.length === 0) {
15063
+ this.send({ type: "info", message: "No recently denied Auto Mode actions." });
15064
+ } else {
15065
+ const lines = [`Recently denied Auto Mode actions (${rows.length})`, ""];
15066
+ for (const r of rows.slice(0, 20)) {
15067
+ lines.push(`- ${r.timestamp} ${r.tool} ${r.ruleId}`);
15068
+ lines.push(` ${r.reason}`);
15069
+ lines.push(` ${r.argsPreview}`);
15070
+ }
15071
+ this.send({ type: "info", message: lines.join("\n") });
15072
+ }
15073
+ } else if (sub === "clear-denied") {
15074
+ clearRecentlyDeniedAutoActions();
15075
+ this.send({ type: "info", message: "Cleared recently denied Auto Mode actions." });
15076
+ } else {
15077
+ this.send({ type: "error", message: "Usage: /permissions recently-denied|clear-denied" });
15078
+ }
15079
+ break;
15080
+ }
14822
15081
  case "yolo": {
14823
15082
  const sub = args[0]?.toLowerCase();
14824
15083
  if (sub === "off") {
@@ -14919,9 +15178,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14919
15178
  let modifiedFiles = 0;
14920
15179
  const diffLines2 = [];
14921
15180
  for (const [filePath, { earliest }] of fileMap) {
14922
- const currentContent = existsSync24(filePath) ? (() => {
15181
+ const currentContent = existsSync25(filePath) ? (() => {
14923
15182
  try {
14924
- return readFileSync18(filePath, "utf-8");
15183
+ return readFileSync19(filePath, "utf-8");
14925
15184
  } catch {
14926
15185
  return null;
14927
15186
  }
@@ -15022,7 +15281,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15022
15281
  break;
15023
15282
  }
15024
15283
  const sub = args[0]?.toLowerCase();
15025
- const resolve9 = (ref) => {
15284
+ const resolve10 = (ref) => {
15026
15285
  const r = session.resolveBranchRef(ref);
15027
15286
  if (r.ok) return r.id;
15028
15287
  if (r.reason === "ambiguous") {
@@ -15076,7 +15335,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15076
15335
  this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
15077
15336
  break;
15078
15337
  }
15079
- const id = resolve9(ref);
15338
+ const id = resolve10(ref);
15080
15339
  if (!id) break;
15081
15340
  const ok = session.switchBranch(id);
15082
15341
  if (ok) {
@@ -15096,7 +15355,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15096
15355
  this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
15097
15356
  break;
15098
15357
  }
15099
- const id = resolve9(ref);
15358
+ const id = resolve10(ref);
15100
15359
  if (!id) break;
15101
15360
  const ok = session.deleteBranch(id);
15102
15361
  if (ok) {
@@ -15115,7 +15374,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15115
15374
  this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
15116
15375
  break;
15117
15376
  }
15118
- const id = resolve9(ref);
15377
+ const id = resolve10(ref);
15119
15378
  if (!id) break;
15120
15379
  const ok = session.renameBranch(id, title);
15121
15380
  if (ok) {
@@ -15133,7 +15392,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15133
15392
  this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
15134
15393
  break;
15135
15394
  }
15136
- const id = resolve9(ref);
15395
+ const id = resolve10(ref);
15137
15396
  if (!id) break;
15138
15397
  const d = session.diffBranches(id);
15139
15398
  if (!d) {
@@ -15175,7 +15434,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15175
15434
  this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
15176
15435
  break;
15177
15436
  }
15178
- const id = resolve9(ref);
15437
+ const id = resolve10(ref);
15179
15438
  if (!id) break;
15180
15439
  const idx = parseInt(idxStr, 10);
15181
15440
  if (Number.isNaN(idx)) {
@@ -15437,7 +15696,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15437
15696
  case "test": {
15438
15697
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
15439
15698
  try {
15440
- const { executeTests } = await import("./run-tests-D7YBY4XB.js");
15699
+ const { executeTests } = await import("./run-tests-7J3PNWVS.js");
15441
15700
  const argStr = args.join(" ").trim();
15442
15701
  let testArgs = {};
15443
15702
  if (argStr) {
@@ -15454,9 +15713,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15454
15713
  // ── /init ───────────────────────────────────────────────────────
15455
15714
  case "init": {
15456
15715
  const cwd = process.cwd();
15457
- const targetPath = join18(cwd, "AICLI.md");
15716
+ const targetPath = join19(cwd, "AICLI.md");
15458
15717
  const force = args.includes("--force");
15459
- if (existsSync24(targetPath) && !force) {
15718
+ if (existsSync25(targetPath) && !force) {
15460
15719
  this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
15461
15720
  Use /init --force to overwrite.` });
15462
15721
  break;
@@ -15489,7 +15748,7 @@ Use /context reload to load it.` });
15489
15748
  lines.push("**Config Files:**");
15490
15749
  lines.push(` Dir: ${configDir}`);
15491
15750
  const checkFile = (label, filePath) => {
15492
- const exists = existsSync24(filePath);
15751
+ const exists = existsSync25(filePath);
15493
15752
  let extra = "";
15494
15753
  if (exists) {
15495
15754
  try {
@@ -15499,9 +15758,9 @@ Use /context reload to load it.` });
15499
15758
  }
15500
15759
  lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
15501
15760
  };
15502
- checkFile("config.json", join18(configDir, "config.json"));
15503
- checkFile("memory.md", join18(configDir, MEMORY_FILE_NAME));
15504
- checkFile("dev-state.md", join18(configDir, "dev-state.md"));
15761
+ checkFile("config.json", join19(configDir, "config.json"));
15762
+ checkFile("memory.md", join19(configDir, MEMORY_FILE_NAME));
15763
+ checkFile("dev-state.md", join19(configDir, "dev-state.md"));
15505
15764
  lines.push("");
15506
15765
  if (this.mcpManager) {
15507
15766
  lines.push("**MCP Servers:**");
@@ -15688,8 +15947,8 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
15688
15947
  this.send({ type: "info", message: "\u2713 All added directories removed from context." });
15689
15948
  break;
15690
15949
  }
15691
- const dirPath = resolve7(sub);
15692
- if (!existsSync24(dirPath)) {
15950
+ const dirPath = resolve8(sub);
15951
+ if (!existsSync25(dirPath)) {
15693
15952
  this.send({ type: "error", message: `Directory not found: ${dirPath}` });
15694
15953
  break;
15695
15954
  }
@@ -15705,8 +15964,8 @@ It will be included in AI context for subsequent messages.` });
15705
15964
  // ── /commands ───────────────────────────────────────────────────
15706
15965
  case "commands": {
15707
15966
  const configDir = this.config.getConfigDir();
15708
- const commandsDir = join18(configDir, CUSTOM_COMMANDS_DIR_NAME);
15709
- if (!existsSync24(commandsDir)) {
15967
+ const commandsDir = join19(configDir, CUSTOM_COMMANDS_DIR_NAME);
15968
+ if (!existsSync25(commandsDir)) {
15710
15969
  this.send({ type: "info", message: `No custom commands directory.
15711
15970
  Create: ${commandsDir}/ with .md files.` });
15712
15971
  break;
@@ -15732,7 +15991,7 @@ Add .md files to create commands.` });
15732
15991
  // ── /plugins ────────────────────────────────────────────────────
15733
15992
  case "plugins": {
15734
15993
  const configDir = this.config.getConfigDir();
15735
- const pluginsDir = join18(configDir, PLUGINS_DIR_NAME);
15994
+ const pluginsDir = join19(configDir, PLUGINS_DIR_NAME);
15736
15995
  const pluginTools = this.toolRegistry.listPluginTools();
15737
15996
  const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
15738
15997
  if (pluginTools.length === 0) {
@@ -15929,11 +16188,11 @@ Add .md files to create commands.` });
15929
16188
  }
15930
16189
  memoryShow() {
15931
16190
  const configDir = this.config.getConfigDir();
15932
- const memPath = join18(configDir, MEMORY_FILE_NAME);
16191
+ const memPath = join19(configDir, MEMORY_FILE_NAME);
15933
16192
  let content = "";
15934
16193
  try {
15935
- if (existsSync24(memPath)) {
15936
- content = readFileSync18(memPath, "utf-8");
16194
+ if (existsSync25(memPath)) {
16195
+ content = readFileSync19(memPath, "utf-8");
15937
16196
  }
15938
16197
  } catch (err) {
15939
16198
  process.stderr.write(`[web] Failed to read memory file: ${err instanceof Error ? err.message : err}
@@ -15947,11 +16206,11 @@ Add .md files to create commands.` });
15947
16206
  }
15948
16207
  memoryAdd(text) {
15949
16208
  const configDir = this.config.getConfigDir();
15950
- const memPath = join18(configDir, MEMORY_FILE_NAME);
16209
+ const memPath = join19(configDir, MEMORY_FILE_NAME);
15951
16210
  try {
15952
16211
  mkdirSync11(configDir, { recursive: true });
15953
16212
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
15954
- const previous = existsSync24(memPath) ? readFileSync18(memPath, "utf-8") : "";
16213
+ const previous = existsSync25(memPath) ? readFileSync19(memPath, "utf-8") : "";
15955
16214
  const entry = `
15956
16215
  - [${timestamp}] ${text}
15957
16216
  `;
@@ -15963,7 +16222,7 @@ Add .md files to create commands.` });
15963
16222
  }
15964
16223
  memoryClear() {
15965
16224
  const configDir = this.config.getConfigDir();
15966
- const memPath = join18(configDir, MEMORY_FILE_NAME);
16225
+ const memPath = join19(configDir, MEMORY_FILE_NAME);
15967
16226
  try {
15968
16227
  atomicWriteFileSync(memPath, "");
15969
16228
  this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
@@ -16197,8 +16456,8 @@ async function setupProxy(configProxy) {
16197
16456
  }
16198
16457
 
16199
16458
  // src/web/auth.ts
16200
- import { existsSync as existsSync25, readFileSync as readFileSync19, writeFileSync as writeFileSync3, mkdirSync as mkdirSync12, readdirSync as readdirSync11, copyFileSync } from "fs";
16201
- import { join as join19 } from "path";
16459
+ import { existsSync as existsSync26, readFileSync as readFileSync20, writeFileSync as writeFileSync3, mkdirSync as mkdirSync12, readdirSync as readdirSync11, copyFileSync } from "fs";
16460
+ import { join as join20 } from "path";
16202
16461
  import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
16203
16462
  var USERS_FILE = "users.json";
16204
16463
  var TOKEN_EXPIRY_HOURS = 24;
@@ -16213,7 +16472,7 @@ var AuthManager = class {
16213
16472
  db;
16214
16473
  constructor(baseDir) {
16215
16474
  this.baseDir = baseDir;
16216
- this.usersFile = join19(baseDir, USERS_FILE);
16475
+ this.usersFile = join20(baseDir, USERS_FILE);
16217
16476
  this.db = this.loadOrCreate();
16218
16477
  }
16219
16478
  // ── Public API ─────────────────────────────────────────────────
@@ -16242,9 +16501,9 @@ var AuthManager = class {
16242
16501
  }
16243
16502
  const salt = randomBytes(16).toString("hex");
16244
16503
  const passwordHash = this.hashPassword(password, salt);
16245
- const dataDir = join19(USERS_DIR, username);
16246
- const fullDataDir = join19(this.baseDir, dataDir);
16247
- mkdirSync12(join19(fullDataDir, "history"), { recursive: true });
16504
+ const dataDir = join20(USERS_DIR, username);
16505
+ const fullDataDir = join20(this.baseDir, dataDir);
16506
+ mkdirSync12(join20(fullDataDir, "history"), { recursive: true });
16248
16507
  const user = {
16249
16508
  username,
16250
16509
  passwordHash,
@@ -16359,7 +16618,7 @@ var AuthManager = class {
16359
16618
  getUserDataDir(username) {
16360
16619
  const user = this.db.users.find((u) => u.username === username);
16361
16620
  if (!user) throw new Error(`User not found: ${username}`);
16362
- return join19(this.baseDir, user.dataDir);
16621
+ return join20(this.baseDir, user.dataDir);
16363
16622
  }
16364
16623
  /** List all usernames */
16365
16624
  listUsers() {
@@ -16395,30 +16654,30 @@ var AuthManager = class {
16395
16654
  const err = this.register(username, password);
16396
16655
  if (err) return err;
16397
16656
  const userDir = this.getUserDataDir(username);
16398
- const globalConfig = join19(this.baseDir, "config.json");
16399
- if (existsSync25(globalConfig)) {
16657
+ const globalConfig = join20(this.baseDir, "config.json");
16658
+ if (existsSync26(globalConfig)) {
16400
16659
  try {
16401
- const content = readFileSync19(globalConfig, "utf-8");
16402
- writeFileSync3(join19(userDir, "config.json"), content, "utf-8");
16660
+ const content = readFileSync20(globalConfig, "utf-8");
16661
+ writeFileSync3(join20(userDir, "config.json"), content, "utf-8");
16403
16662
  } catch {
16404
16663
  }
16405
16664
  }
16406
- const globalMemory = join19(this.baseDir, "memory.md");
16407
- if (existsSync25(globalMemory)) {
16665
+ const globalMemory = join20(this.baseDir, "memory.md");
16666
+ if (existsSync26(globalMemory)) {
16408
16667
  try {
16409
- const content = readFileSync19(globalMemory, "utf-8");
16410
- writeFileSync3(join19(userDir, "memory.md"), content, "utf-8");
16668
+ const content = readFileSync20(globalMemory, "utf-8");
16669
+ writeFileSync3(join20(userDir, "memory.md"), content, "utf-8");
16411
16670
  } catch {
16412
16671
  }
16413
16672
  }
16414
- const globalHistory = join19(this.baseDir, "history");
16415
- if (existsSync25(globalHistory)) {
16673
+ const globalHistory = join20(this.baseDir, "history");
16674
+ if (existsSync26(globalHistory)) {
16416
16675
  try {
16417
16676
  const files = readdirSync11(globalHistory).filter((f) => f.endsWith(".json"));
16418
- const userHistory = join19(userDir, "history");
16677
+ const userHistory = join20(userDir, "history");
16419
16678
  for (const f of files) {
16420
16679
  try {
16421
- copyFileSync(join19(globalHistory, f), join19(userHistory, f));
16680
+ copyFileSync(join20(globalHistory, f), join20(userHistory, f));
16422
16681
  } catch {
16423
16682
  }
16424
16683
  }
@@ -16429,9 +16688,9 @@ var AuthManager = class {
16429
16688
  }
16430
16689
  // ── Private methods ────────────────────────────────────────────
16431
16690
  loadOrCreate() {
16432
- if (existsSync25(this.usersFile)) {
16691
+ if (existsSync26(this.usersFile)) {
16433
16692
  try {
16434
- return JSON.parse(readFileSync19(this.usersFile, "utf-8"));
16693
+ return JSON.parse(readFileSync20(this.usersFile, "utf-8"));
16435
16694
  } catch {
16436
16695
  }
16437
16696
  }
@@ -16483,7 +16742,7 @@ function getModuleDir() {
16483
16742
  }
16484
16743
  } catch {
16485
16744
  }
16486
- return resolve8(".");
16745
+ return resolve9(".");
16487
16746
  }
16488
16747
  async function startWebServer(options = {}) {
16489
16748
  const port = options.port ?? 3e3;
@@ -16548,8 +16807,8 @@ async function startWebServer(options = {}) {
16548
16807
  }
16549
16808
  }
16550
16809
  let skillManager = null;
16551
- const skillsDir = join20(config.getConfigDir(), SKILLS_DIR_NAME);
16552
- if (existsSync26(skillsDir)) {
16810
+ const skillsDir = join21(config.getConfigDir(), SKILLS_DIR_NAME);
16811
+ if (existsSync27(skillsDir)) {
16553
16812
  skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
16554
16813
  skillManager.loadSkills();
16555
16814
  const count = skillManager.listSkills().length;
@@ -16620,18 +16879,18 @@ async function startWebServer(options = {}) {
16620
16879
  next();
16621
16880
  };
16622
16881
  const moduleDir = getModuleDir();
16623
- let clientDir = join20(moduleDir, "web", "client");
16624
- if (!existsSync26(clientDir)) {
16625
- clientDir = join20(moduleDir, "client");
16882
+ let clientDir = join21(moduleDir, "web", "client");
16883
+ if (!existsSync27(clientDir)) {
16884
+ clientDir = join21(moduleDir, "client");
16626
16885
  }
16627
- if (!existsSync26(clientDir)) {
16628
- clientDir = join20(moduleDir, "..", "..", "src", "web", "client");
16886
+ if (!existsSync27(clientDir)) {
16887
+ clientDir = join21(moduleDir, "..", "..", "src", "web", "client");
16629
16888
  }
16630
- if (!existsSync26(clientDir)) {
16631
- clientDir = join20(process.cwd(), "src", "web", "client");
16889
+ if (!existsSync27(clientDir)) {
16890
+ clientDir = join21(process.cwd(), "src", "web", "client");
16632
16891
  }
16633
16892
  console.log(` Static files: ${clientDir}`);
16634
- app.use("/vendor", express.static(join20(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
16893
+ app.use("/vendor", express.static(join21(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
16635
16894
  app.use(express.static(clientDir));
16636
16895
  app.get("/api/status", (_req, res) => {
16637
16896
  res.json({
@@ -16698,10 +16957,10 @@ async function startWebServer(options = {}) {
16698
16957
  app.get("/api/files", requireAuth, (req, res) => {
16699
16958
  const cwd = process.cwd();
16700
16959
  const prefix = req.query.prefix || "";
16701
- const targetDir = join20(cwd, prefix);
16960
+ const targetDir = join21(cwd, prefix);
16702
16961
  try {
16703
- const canonicalTarget = realpathSync(resolve8(targetDir));
16704
- const canonicalCwd = realpathSync(resolve8(cwd));
16962
+ const canonicalTarget = realpathSync(resolve9(targetDir));
16963
+ const canonicalCwd = realpathSync(resolve9(cwd));
16705
16964
  if (!canonicalTarget.startsWith(canonicalCwd + sep3) && canonicalTarget !== canonicalCwd) {
16706
16965
  res.json({ files: [] });
16707
16966
  return;
@@ -16715,7 +16974,7 @@ async function startWebServer(options = {}) {
16715
16974
  const entries = readdirSync12(targetDir, { withFileTypes: true });
16716
16975
  const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
16717
16976
  name: e.name,
16718
- path: relative4(cwd, join20(targetDir, e.name)).replace(/\\/g, "/"),
16977
+ path: relative4(cwd, join21(targetDir, e.name)).replace(/\\/g, "/"),
16719
16978
  isDir: e.isDirectory()
16720
16979
  }));
16721
16980
  res.json({ files });
@@ -16751,8 +17010,8 @@ async function startWebServer(options = {}) {
16751
17010
  try {
16752
17011
  const authUser = req._authUser;
16753
17012
  const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
16754
- const filePath = join20(histDir, `${id}.json`);
16755
- if (!existsSync26(filePath)) {
17013
+ const filePath = join21(histDir, `${id}.json`);
17014
+ if (!existsSync27(filePath)) {
16756
17015
  res.status(404).json({ error: "Session not found" });
16757
17016
  return;
16758
17017
  }
@@ -16767,7 +17026,7 @@ async function startWebServer(options = {}) {
16767
17026
  res.status(404).json({ error: "Session not found" });
16768
17027
  return;
16769
17028
  }
16770
- const data = JSON.parse(readFileSync20(filePath, "utf-8"));
17029
+ const data = JSON.parse(readFileSync21(filePath, "utf-8"));
16771
17030
  res.json({ session: data });
16772
17031
  } catch (err) {
16773
17032
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
@@ -16780,10 +17039,10 @@ async function startWebServer(options = {}) {
16780
17039
  return;
16781
17040
  }
16782
17041
  const cwd = process.cwd();
16783
- const fullPath = resolve8(join20(cwd, filePath));
17042
+ const fullPath = resolve9(join21(cwd, filePath));
16784
17043
  try {
16785
17044
  const canonicalFull = realpathSync(fullPath);
16786
- const canonicalCwd = realpathSync(resolve8(cwd));
17045
+ const canonicalCwd = realpathSync(resolve9(cwd));
16787
17046
  if (!canonicalFull.startsWith(canonicalCwd + sep3) && canonicalFull !== canonicalCwd) {
16788
17047
  res.json({ error: "Access denied" });
16789
17048
  return;
@@ -16798,7 +17057,7 @@ async function startWebServer(options = {}) {
16798
17057
  res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
16799
17058
  return;
16800
17059
  }
16801
- const content = readFileSync20(fullPath, "utf-8");
17060
+ const content = readFileSync21(fullPath, "utf-8");
16802
17061
  res.json({ content, size: stat.size });
16803
17062
  } catch {
16804
17063
  res.json({ error: "Cannot read file" });
@@ -17002,7 +17261,7 @@ async function startWebServer(options = {}) {
17002
17261
  });
17003
17262
  const MAX_PORT_ATTEMPTS = 10;
17004
17263
  let actualPort = port;
17005
- const result = await new Promise((resolve9, reject) => {
17264
+ const result = await new Promise((resolve10, reject) => {
17006
17265
  const tryListen = (attempt) => {
17007
17266
  server.once("error", (err) => {
17008
17267
  if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
@@ -17033,7 +17292,7 @@ async function startWebServer(options = {}) {
17033
17292
  }
17034
17293
  console.log(` Press Ctrl+C to stop
17035
17294
  `);
17036
- resolve9({ port: actualPort, host, url });
17295
+ resolve10({ port: actualPort, host, url });
17037
17296
  });
17038
17297
  };
17039
17298
  tryListen(1);
@@ -17053,17 +17312,17 @@ function resolveProjectMcpPath() {
17053
17312
  const cwd = process.cwd();
17054
17313
  const gitRoot = getGitRoot(cwd);
17055
17314
  const projectRoot = gitRoot ?? cwd;
17056
- const configPath = join20(projectRoot, MCP_PROJECT_CONFIG_NAME);
17057
- return existsSync26(configPath) ? configPath : null;
17315
+ const configPath = join21(projectRoot, MCP_PROJECT_CONFIG_NAME);
17316
+ return existsSync27(configPath) ? configPath : null;
17058
17317
  }
17059
17318
  function loadProjectMcpConfig() {
17060
17319
  const cwd = process.cwd();
17061
17320
  const gitRoot = getGitRoot(cwd);
17062
17321
  const projectRoot = gitRoot ?? cwd;
17063
- const configPath = join20(projectRoot, MCP_PROJECT_CONFIG_NAME);
17064
- if (!existsSync26(configPath)) return null;
17322
+ const configPath = join21(projectRoot, MCP_PROJECT_CONFIG_NAME);
17323
+ if (!existsSync27(configPath)) return null;
17065
17324
  try {
17066
- const raw = JSON.parse(readFileSync20(configPath, "utf-8"));
17325
+ const raw = JSON.parse(readFileSync21(configPath, "utf-8"));
17067
17326
  return raw.mcpServers ?? raw;
17068
17327
  } catch {
17069
17328
  return null;