jinzd-ai-cli 0.4.212 → 0.4.214

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-JOJA35RK.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 join22, dirname as dirname6, resolve as resolve9, relative as relative4, sep as sep3 } from "path";
65
+ import { existsSync as existsSync28, readFileSync as readFileSync22, readdirSync as readdirSync13, 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);
@@ -9476,6 +9663,218 @@ function formatResults2(query, data, _requested) {
9476
9663
  return header + "\n" + results.join("\n\n");
9477
9664
  }
9478
9665
 
9666
+ // src/agents/agent-config.ts
9667
+ import { existsSync as existsSync15, readdirSync as readdirSync7, readFileSync as readFileSync12 } from "fs";
9668
+ import { join as join10 } from "path";
9669
+ import { homedir as homedir6 } from "os";
9670
+ var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
9671
+ "read_file",
9672
+ "list_dir",
9673
+ "grep_files",
9674
+ "glob_files",
9675
+ "web_fetch",
9676
+ "google_search",
9677
+ "write_todos",
9678
+ "find_symbol",
9679
+ "get_outline",
9680
+ "find_references",
9681
+ "search_code"
9682
+ ]);
9683
+ var BUILTIN_AGENTS = [
9684
+ {
9685
+ name: "explorer",
9686
+ description: "Read-only code exploration and evidence gathering.",
9687
+ permissionProfile: "read-only",
9688
+ allowedTools: [...READ_ONLY_TOOLS2],
9689
+ maxToolRounds: 10,
9690
+ contextPolicy: "task-only",
9691
+ system: "Act as a read-only explorer. Inspect code, collect evidence, and do not modify files.",
9692
+ source: "builtin"
9693
+ },
9694
+ {
9695
+ name: "worker",
9696
+ description: "Implementation and focused fixes within sub-agent safety limits.",
9697
+ permissionProfile: "workspace-write",
9698
+ maxToolRounds: 15,
9699
+ contextPolicy: "task-only",
9700
+ system: "Act as a focused implementation worker. Keep edits scoped and report changed files.",
9701
+ source: "builtin"
9702
+ },
9703
+ {
9704
+ name: "reviewer",
9705
+ description: "Code review focused on bugs, regressions, and missing tests.",
9706
+ permissionProfile: "read-only",
9707
+ allowedTools: [...READ_ONLY_TOOLS2],
9708
+ maxToolRounds: 12,
9709
+ contextPolicy: "task-only",
9710
+ system: "Act as a code reviewer. Lead with concrete findings, file evidence, and residual risks.",
9711
+ source: "builtin"
9712
+ },
9713
+ {
9714
+ name: "security",
9715
+ description: "Security audit with read-only evidence collection.",
9716
+ permissionProfile: "read-only",
9717
+ allowedTools: [...READ_ONLY_TOOLS2],
9718
+ maxToolRounds: 12,
9719
+ contextPolicy: "task-only",
9720
+ system: "Act as a security reviewer. Look for trust-boundary, injection, filesystem, network, and secret-handling issues.",
9721
+ source: "builtin"
9722
+ },
9723
+ {
9724
+ name: "tester",
9725
+ description: "Test planning and regression analysis without direct shell execution.",
9726
+ permissionProfile: "read-only",
9727
+ allowedTools: [...READ_ONLY_TOOLS2],
9728
+ maxToolRounds: 12,
9729
+ contextPolicy: "task-only",
9730
+ system: "Act as a tester. Inspect tests, identify coverage gaps, and propose precise regression checks.",
9731
+ source: "builtin"
9732
+ }
9733
+ ];
9734
+ function normalizeName(name) {
9735
+ return name.trim().toLowerCase();
9736
+ }
9737
+ function isStringArray(value) {
9738
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
9739
+ }
9740
+ function parseAgentConfig(raw, source, path3) {
9741
+ if (!raw || typeof raw !== "object") return null;
9742
+ const obj = raw;
9743
+ if (typeof obj.name !== "string" || obj.name.trim() === "") return null;
9744
+ const cfg = {
9745
+ name: obj.name.trim(),
9746
+ source,
9747
+ path: path3
9748
+ };
9749
+ if (typeof obj.description === "string") cfg.description = obj.description;
9750
+ if (typeof obj.provider === "string") cfg.provider = obj.provider;
9751
+ if (typeof obj.model === "string") cfg.model = obj.model;
9752
+ if (typeof obj.system === "string") cfg.system = obj.system;
9753
+ if (typeof obj.instructions === "string") cfg.instructions = obj.instructions;
9754
+ if (typeof obj.systemInstructions === "string") cfg.systemInstructions = obj.systemInstructions;
9755
+ if (isStringArray(obj.allowedTools)) cfg.allowedTools = obj.allowedTools;
9756
+ if (isStringArray(obj.blockedTools)) cfg.blockedTools = obj.blockedTools;
9757
+ if (typeof obj.permissionProfile === "string") cfg.permissionProfile = obj.permissionProfile;
9758
+ if (typeof obj.maxToolRounds === "number" && Number.isFinite(obj.maxToolRounds)) cfg.maxToolRounds = obj.maxToolRounds;
9759
+ if (typeof obj.contextPolicy === "string") cfg.contextPolicy = obj.contextPolicy;
9760
+ return cfg;
9761
+ }
9762
+ function loadAgentDir(dir, source) {
9763
+ if (!existsSync15(dir)) return [];
9764
+ const out = [];
9765
+ for (const file of readdirSync7(dir)) {
9766
+ if (!file.endsWith(".json")) continue;
9767
+ const path3 = join10(dir, file);
9768
+ try {
9769
+ const parsed = JSON.parse(readFileSync12(path3, "utf-8"));
9770
+ const cfg = parseAgentConfig(parsed, source, path3);
9771
+ if (cfg) out.push(cfg);
9772
+ } catch {
9773
+ }
9774
+ }
9775
+ return out;
9776
+ }
9777
+ function getAgentDirs(configDir, cwd = process.cwd()) {
9778
+ return {
9779
+ user: join10(configDir ?? join10(homedir6(), CONFIG_DIR_NAME), "agents"),
9780
+ project: join10(cwd, CONFIG_DIR_NAME, "agents")
9781
+ };
9782
+ }
9783
+ function listAgentConfigs(configDir, cwd = process.cwd()) {
9784
+ const dirs = getAgentDirs(configDir, cwd);
9785
+ const byName = /* @__PURE__ */ new Map();
9786
+ for (const cfg of BUILTIN_AGENTS) byName.set(normalizeName(cfg.name), { ...cfg });
9787
+ for (const cfg of loadAgentDir(dirs.user, "user")) byName.set(normalizeName(cfg.name), cfg);
9788
+ for (const cfg of loadAgentDir(dirs.project, "project")) byName.set(normalizeName(cfg.name), cfg);
9789
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
9790
+ }
9791
+ function resolveAgentConfig(name, configDir, cwd = process.cwd()) {
9792
+ const target = normalizeName(name);
9793
+ return listAgentConfigs(configDir, cwd).find((cfg) => normalizeName(cfg.name) === target);
9794
+ }
9795
+ function getAgentInstructions(agent) {
9796
+ return agent.system ?? agent.systemInstructions ?? agent.instructions;
9797
+ }
9798
+ function getEffectiveAgentTools(agent) {
9799
+ let allowed = new Set(SUBAGENT_ALLOWED_TOOLS);
9800
+ if (agent.permissionProfile === "read-only") {
9801
+ allowed = new Set([...allowed].filter((name) => READ_ONLY_TOOLS2.has(name)));
9802
+ }
9803
+ if (agent.allowedTools && agent.allowedTools.length > 0) {
9804
+ const requested = new Set(agent.allowedTools);
9805
+ allowed = new Set([...allowed].filter((name) => requested.has(name)));
9806
+ }
9807
+ if (agent.blockedTools && agent.blockedTools.length > 0) {
9808
+ for (const name of agent.blockedTools) allowed.delete(name);
9809
+ }
9810
+ return allowed;
9811
+ }
9812
+ function getEffectiveMaxRounds(requestedMaxRounds, agent) {
9813
+ const agentMax = agent.maxToolRounds && agent.maxToolRounds > 0 ? Math.round(agent.maxToolRounds) : SUBAGENT_MAX_ROUNDS_LIMIT;
9814
+ return Math.min(requestedMaxRounds, agentMax, SUBAGENT_MAX_ROUNDS_LIMIT);
9815
+ }
9816
+
9817
+ // src/agents/agent-runtime.ts
9818
+ var nextId = 1;
9819
+ var preferredAgentName = "worker";
9820
+ var runs = [];
9821
+ var MAX_RUNS = 50;
9822
+ function nowIso() {
9823
+ return (/* @__PURE__ */ new Date()).toISOString();
9824
+ }
9825
+ function startAgentRun(input) {
9826
+ const run = {
9827
+ id: `agent-${nextId++}`,
9828
+ agentName: input.agentName,
9829
+ task: input.task,
9830
+ status: "running",
9831
+ startedAt: nowIso(),
9832
+ toolNames: [],
9833
+ agentIndex: input.agentIndex,
9834
+ stopRequested: false
9835
+ };
9836
+ runs.unshift(run);
9837
+ if (runs.length > MAX_RUNS) runs.splice(MAX_RUNS);
9838
+ return run;
9839
+ }
9840
+ function recordAgentTool(runId, toolName) {
9841
+ const run = runs.find((r) => r.id === runId);
9842
+ if (!run) return;
9843
+ if (!run.toolNames.includes(toolName)) run.toolNames.push(toolName);
9844
+ }
9845
+ function finishAgentRun(runId, status, summary, error) {
9846
+ const run = runs.find((r) => r.id === runId);
9847
+ if (!run) return;
9848
+ run.status = status;
9849
+ run.finishedAt = nowIso();
9850
+ if (summary) run.summary = summary;
9851
+ if (error) run.error = error;
9852
+ }
9853
+ function listAgentRuns() {
9854
+ return runs.map((run) => ({ ...run, toolNames: [...run.toolNames] }));
9855
+ }
9856
+ function requestAgentStop(idOrAll) {
9857
+ const target = idOrAll.trim().toLowerCase();
9858
+ let count = 0;
9859
+ for (const run of runs) {
9860
+ if (run.status !== "running") continue;
9861
+ if (target === "all" || run.id.toLowerCase() === target) {
9862
+ run.stopRequested = true;
9863
+ count++;
9864
+ }
9865
+ }
9866
+ return count;
9867
+ }
9868
+ function isAgentStopRequested(runId) {
9869
+ return runs.find((r) => r.id === runId)?.stopRequested ?? false;
9870
+ }
9871
+ function setPreferredAgentName(name) {
9872
+ preferredAgentName = name.trim() || "worker";
9873
+ }
9874
+ function getPreferredAgentName() {
9875
+ return preferredAgentName;
9876
+ }
9877
+
9479
9878
  // src/tools/builtin/spawn-agent.ts
9480
9879
  var spawnAgentContext = {
9481
9880
  provider: null,
@@ -9490,18 +9889,27 @@ function agentPrefix(agentIndex) {
9490
9889
  return theme.dim(` \u2503${agentIndex} `);
9491
9890
  }
9492
9891
  var SubAgentExecutor = class {
9493
- constructor(registry, agentIndex = null) {
9892
+ constructor(registry, agentIndex = null, runId) {
9494
9893
  this.registry = registry;
9894
+ this.runId = runId;
9495
9895
  this.prefix = agentPrefix(agentIndex);
9496
9896
  }
9497
9897
  round = 0;
9498
9898
  totalRounds = 0;
9499
9899
  prefix = PREFIX;
9900
+ toolNames = [];
9500
9901
  setRoundInfo(current, total) {
9501
9902
  this.round = current;
9502
9903
  this.totalRounds = total;
9503
9904
  }
9504
9905
  async execute(call) {
9906
+ if (this.runId && isAgentStopRequested(this.runId)) {
9907
+ return {
9908
+ callId: call.id,
9909
+ content: "Sub-agent stop requested.",
9910
+ isError: true
9911
+ };
9912
+ }
9505
9913
  const tool = this.registry.get(call.name);
9506
9914
  if (!tool) {
9507
9915
  return {
@@ -9522,6 +9930,8 @@ var SubAgentExecutor = class {
9522
9930
  };
9523
9931
  }
9524
9932
  this.printToolCall(call, dangerLevel);
9933
+ if (!this.toolNames.includes(call.name)) this.toolNames.push(call.name);
9934
+ if (this.runId) recordAgentTool(this.runId, call.name);
9525
9935
  try {
9526
9936
  const rawContent = await runTool(tool, call.arguments, call.name);
9527
9937
  const content = truncateOutput(rawContent, call.name);
@@ -9541,7 +9951,6 @@ var SubAgentExecutor = class {
9541
9951
  }
9542
9952
  return results;
9543
9953
  }
9544
- // ── 带前缀的终端输出 ──
9545
9954
  printPrefixed(text) {
9546
9955
  for (const line of text.split("\n")) {
9547
9956
  console.log(this.prefix + line);
@@ -9582,7 +9991,7 @@ var SubAgentExecutor = class {
9582
9991
  }
9583
9992
  }
9584
9993
  };
9585
- function buildSubAgentSystemPrompt(task, parentSystemPrompt) {
9994
+ function buildSubAgentSystemPrompt(task, agent, parentSystemPrompt) {
9586
9995
  const parts = [];
9587
9996
  if (parentSystemPrompt) {
9588
9997
  parts.push(parentSystemPrompt);
@@ -9590,6 +9999,8 @@ function buildSubAgentSystemPrompt(task, parentSystemPrompt) {
9590
9999
  parts.push(
9591
10000
  `# Sub-Agent Mode
9592
10001
 
10002
+ **Agent Role**: ${agent.name}${agent.description ? ` \u2014 ${agent.description}` : ""}
10003
+
9593
10004
  You are a focused sub-agent delegated by the main agent to complete a specific task.
9594
10005
 
9595
10006
  **Your Task**:
@@ -9603,13 +10014,19 @@ ${task}
9603
10014
  5. Destructive operations (rm -rf etc.) will be automatically blocked
9604
10015
  6. Be efficient, minimize unnecessary tool call rounds`
9605
10016
  );
10017
+ const agentInstructions = getAgentInstructions(agent);
10018
+ if (agentInstructions) {
10019
+ parts.push(`# Agent Instructions
10020
+
10021
+ ${agentInstructions}`);
10022
+ }
9606
10023
  return parts.join("\n\n---\n\n");
9607
10024
  }
9608
- function printSubAgentHeader(task, maxRounds, agentIndex = null) {
10025
+ function printSubAgentHeader(task, maxRounds, agentName, agentIndex = null) {
9609
10026
  const prefix = agentPrefix(agentIndex);
9610
10027
  console.log();
9611
10028
  console.log(theme.dim(" \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
9612
- const label = agentIndex !== null ? `\u{1F916} Sub-Agent #${agentIndex} Spawned` : "\u{1F916} Sub-Agent Spawned";
10029
+ const label = agentIndex !== null ? `\u{1F916} Sub-Agent #${agentIndex} Spawned (${agentName})` : `\u{1F916} Sub-Agent Spawned (${agentName})`;
9613
10030
  console.log(prefix + theme.toolCall(label));
9614
10031
  console.log(
9615
10032
  prefix + theme.dim("Task: ") + (task.slice(0, 120) + (task.length > 120 ? "..." : ""))
@@ -9632,58 +10049,110 @@ function printSubAgentFooter(usage, agentIndex = null) {
9632
10049
  console.log(theme.dim(" \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
9633
10050
  console.log();
9634
10051
  }
9635
- async function runSubAgent(task, maxRounds, agentIndex, ctx) {
10052
+ function summarizeContent(content) {
10053
+ const flat = content.replace(/\s+/g, " ").trim();
10054
+ return flat.length > 500 ? flat.slice(0, 497) + "..." : flat;
10055
+ }
10056
+ function extractEvidence(content, toolNames) {
10057
+ const evidence = /* @__PURE__ */ new Set();
10058
+ if (toolNames.length > 0) evidence.add(`Tools: ${toolNames.join(", ")}`);
10059
+ const matches = content.match(/(?:[\w.-]+[\\/])+[\w.-]+\.(?:ts|tsx|js|jsx|json|md|css|html|py|go|rs|yml|yaml)/g) ?? [];
10060
+ for (const match of matches.slice(0, 8)) evidence.add(match);
10061
+ return [...evidence].slice(0, 10);
10062
+ }
10063
+ function formatAgentResult(input) {
10064
+ const lines = [input.title, "", `**Agent**: ${input.agentName}`];
10065
+ if (input.task) lines.push(`**Task**: ${input.task}`);
10066
+ lines.push("", "### Summary", input.summary || "(no summary)", "", "### Key Evidence");
10067
+ if (input.evidence.length > 0) {
10068
+ for (const item of input.evidence) lines.push(`- ${item}`);
10069
+ } else {
10070
+ lines.push("- No structured evidence extracted.");
10071
+ }
10072
+ lines.push("", "### Tools Used");
10073
+ lines.push(input.toolNames.length > 0 ? input.toolNames.join(", ") : "No tools used.");
10074
+ lines.push("", "### Full Result", input.content, "");
10075
+ lines.push(`*Tokens: ${input.usage.inputTokens} in / ${input.usage.outputTokens} out*`);
10076
+ return lines;
10077
+ }
10078
+ async function runSubAgent(task, maxRounds, agentIndex, ctx, agent) {
9636
10079
  if (!ctx.provider) {
9637
10080
  throw new ToolError("spawn_agent", "provider not initialized (context not injected)");
9638
10081
  }
10082
+ const providerFromConfig = agent.provider && ctx.providers?.has(agent.provider) ? ctx.providers.get(agent.provider) : void 0;
10083
+ const provider = providerFromConfig ?? ctx.provider;
10084
+ const model = agent.model ?? (providerFromConfig?.info?.defaultModel ?? ctx.model);
10085
+ const run = startAgentRun({
10086
+ agentName: agent.name,
10087
+ task,
10088
+ agentIndex: agentIndex ?? void 0
10089
+ });
10090
+ const effectiveTools = getEffectiveAgentTools(agent);
9639
10091
  const subRegistry = new ToolRegistry();
9640
10092
  for (const tool of subRegistry.listAll()) {
9641
- if (!SUBAGENT_ALLOWED_TOOLS.has(tool.definition.name)) {
10093
+ if (!SUBAGENT_ALLOWED_TOOLS.has(tool.definition.name) || !effectiveTools.has(tool.definition.name)) {
9642
10094
  subRegistry.unregister(tool.definition.name);
9643
10095
  }
9644
10096
  }
9645
- const subExecutor = new SubAgentExecutor(subRegistry, agentIndex);
10097
+ const subExecutor = new SubAgentExecutor(subRegistry, agentIndex, run.id);
9646
10098
  const subMessages = [
9647
10099
  { role: "user", content: task, timestamp: /* @__PURE__ */ new Date() }
9648
10100
  ];
9649
- const subSystemPrompt = buildSubAgentSystemPrompt(task, ctx.systemPrompt);
10101
+ const subSystemPrompt = buildSubAgentSystemPrompt(task, agent, ctx.systemPrompt);
9650
10102
  const toolDefs = subRegistry.getDefinitions();
9651
- printSubAgentHeader(task, maxRounds, agentIndex);
9652
- const loop = await runLeanAgentLoop({
9653
- provider: ctx.provider,
9654
- messages: subMessages,
9655
- model: ctx.model,
9656
- maxRounds,
9657
- chatParams: {
9658
- temperature: ctx.modelParams.temperature,
9659
- maxTokens: ctx.modelParams.maxTokens,
9660
- timeout: ctx.modelParams.timeout,
9661
- thinking: ctx.modelParams.thinking
9662
- },
9663
- executeTools: (calls) => subExecutor.executeAll(calls),
9664
- systemPromptForRound: () => subSystemPrompt,
9665
- toolDefsForRound: () => toolDefs,
9666
- onRoundStart: (round) => {
9667
- subExecutor.setRoundInfo(round + 1, maxRounds);
9668
- if (ctx.configManager) {
9669
- googleSearchContext.configManager = ctx.configManager;
9670
- }
9671
- },
9672
- onRoundsExhausted: () => `(Sub-agent reached maximum rounds (${maxRounds}) without producing a final response)`,
9673
- onError: (errMsg) => {
9674
- process.stderr.write(`
10103
+ printSubAgentHeader(task, maxRounds, agent.name, agentIndex);
10104
+ try {
10105
+ const loop = await runLeanAgentLoop({
10106
+ provider,
10107
+ messages: subMessages,
10108
+ model,
10109
+ maxRounds,
10110
+ chatParams: {
10111
+ temperature: ctx.modelParams.temperature,
10112
+ maxTokens: ctx.modelParams.maxTokens,
10113
+ timeout: ctx.modelParams.timeout,
10114
+ thinking: ctx.modelParams.thinking
10115
+ },
10116
+ executeTools: (calls) => subExecutor.executeAll(calls),
10117
+ systemPromptForRound: () => subSystemPrompt,
10118
+ toolDefsForRound: () => toolDefs,
10119
+ onRoundStart: (round) => {
10120
+ subExecutor.setRoundInfo(round + 1, maxRounds);
10121
+ if (ctx.configManager) {
10122
+ googleSearchContext.configManager = ctx.configManager;
10123
+ }
10124
+ },
10125
+ onRoundsExhausted: () => `(Sub-agent reached maximum rounds (${maxRounds}) without producing a final response)`,
10126
+ onError: (errMsg) => {
10127
+ process.stderr.write(`
9675
10128
  [spawn_agent] Error in sub-agent loop: ${errMsg}
9676
10129
  `);
9677
- return `(Sub-agent error: ${errMsg})`;
9678
- }
9679
- });
9680
- printSubAgentFooter(loop.usage, agentIndex);
9681
- return { content: loop.content, usage: loop.usage };
10130
+ return `(Sub-agent error: ${errMsg})`;
10131
+ }
10132
+ });
10133
+ printSubAgentFooter(loop.usage, agentIndex);
10134
+ const summary = summarizeContent(loop.content);
10135
+ const evidence = extractEvidence(loop.content, subExecutor.toolNames);
10136
+ const stopped = isAgentStopRequested(run.id);
10137
+ finishAgentRun(run.id, stopped ? "stopped" : "ok", summary);
10138
+ return {
10139
+ content: loop.content,
10140
+ usage: loop.usage,
10141
+ toolNames: subExecutor.toolNames,
10142
+ summary,
10143
+ evidence,
10144
+ agentName: agent.name
10145
+ };
10146
+ } catch (err) {
10147
+ const message = err instanceof Error ? err.message : String(err);
10148
+ finishAgentRun(run.id, "error", void 0, message);
10149
+ throw err;
10150
+ }
9682
10151
  }
9683
10152
  var spawnAgentTool = {
9684
10153
  definition: {
9685
10154
  name: "spawn_agent",
9686
- description: "Delegate subtask(s) to independent sub-agent(s). Each sub-agent has its own conversation context and agentic tool-call loop, with access to bash, file read/write, edit, search, etc., but cannot interact with the user or spawn more sub-agents. Suitable for independently completable subtasks like: refactoring a module, writing tests, researching a technical approach, or implementing a specific feature. Pass `task` for a single sub-agent, or `tasks` (array) to run multiple sub-agents IN PARALLEL. Parallel mode is ideal for independent research/analysis tasks \u2014 network-bound API calls overlap while the main thread stays responsive. Returns a summary of each sub-agent result.",
10155
+ description: "Delegate subtask(s) to independent named agents. Each sub-agent has its own conversation context and agentic tool-call loop, inherits the parent safety boundary, and can only narrow the sub-agent tool whitelist. Pass `agent` to select a built-in or configured role. Built-ins: explorer, worker, reviewer, security, tester. Pass `task` for a single sub-agent, or `tasks` (array) to run multiple sub-agents IN PARALLEL. Returns summary, key evidence, and tools used for each sub-agent result.",
9687
10156
  parameters: {
9688
10157
  task: {
9689
10158
  type: "string",
@@ -9701,7 +10170,12 @@ var spawnAgentTool = {
9701
10170
  },
9702
10171
  max_rounds: {
9703
10172
  type: "number",
9704
- description: "Max tool-call rounds per sub-agent (1-15, default 10). Applied to each sub-agent in parallel mode.",
10173
+ description: "Max tool-call rounds per sub-agent (1-30, default 15). Agent config can only reduce this value.",
10174
+ required: false
10175
+ },
10176
+ agent: {
10177
+ type: "string",
10178
+ description: "Agent role name from built-ins or ~/.aicli/agents / .aicli/agents JSON configs. Built-ins: explorer, worker, reviewer, security, tester.",
9705
10179
  required: false
9706
10180
  }
9707
10181
  },
@@ -9721,10 +10195,17 @@ var spawnAgentTool = {
9721
10195
  Math.max(Math.round(rawMaxRounds), 1),
9722
10196
  SUBAGENT_MAX_ROUNDS_LIMIT
9723
10197
  );
10198
+ const requestedAgentName = typeof args["agent"] === "string" && args["agent"].trim() ? args["agent"].trim() : getPreferredAgentName();
9724
10199
  const ctx = spawnAgentContext;
9725
10200
  if (!ctx.provider) {
9726
10201
  throw new ToolError("spawn_agent", "provider not initialized (context not injected)");
9727
10202
  }
10203
+ const agent = resolveAgentConfig(requestedAgentName, ctx.configManager?.getConfigDir());
10204
+ if (!agent) {
10205
+ const available = listAgentConfigs(ctx.configManager?.getConfigDir()).map((cfg) => cfg.name).join(", ");
10206
+ throw new ToolError("spawn_agent", `unknown agent "${requestedAgentName}". Available agents: ${available}`);
10207
+ }
10208
+ const effectiveMaxRounds = getEffectiveMaxRounds(maxRounds, agent);
9728
10209
  const hookConfig = ctx.configManager?.get("hooks") ?? void 0;
9729
10210
  const hookConfigDir = ctx.configManager?.getConfigDir() ?? process.cwd();
9730
10211
  const taskCount = singleTask ? 1 : tasksArr.length;
@@ -9732,7 +10213,8 @@ var spawnAgentTool = {
9732
10213
  task: singleTask || void 0,
9733
10214
  tasks: tasksArr.length > 0 ? tasksArr : void 0,
9734
10215
  taskCount,
9735
- maxRounds
10216
+ maxRounds: effectiveMaxRounds,
10217
+ agent: agent.name
9736
10218
  };
9737
10219
  const startDecisions = runLifecycleHooks(hookConfig, "SubagentStart", hookPayload, hookConfigDir);
9738
10220
  const startDeny = startDecisions.find((d) => d.action === "deny");
@@ -9744,32 +10226,37 @@ var spawnAgentTool = {
9744
10226
  subAgentGuard.active = true;
9745
10227
  try {
9746
10228
  if (singleTask) {
9747
- const { content, usage } = await runSubAgent(singleTask, maxRounds, null, ctx);
10229
+ const result = await runSubAgent(singleTask, effectiveMaxRounds, null, ctx, agent);
9748
10230
  subagentStatus = "ok";
9749
- return [
9750
- "## Sub-Agent Result",
9751
- "",
9752
- content,
9753
- "",
9754
- "---",
9755
- `Token usage: ${usage.inputTokens} input, ${usage.outputTokens} output`
9756
- ].join("\n");
10231
+ return formatAgentResult({
10232
+ title: "## Sub-Agent Result",
10233
+ content: result.content,
10234
+ usage: result.usage,
10235
+ toolNames: result.toolNames,
10236
+ summary: result.summary,
10237
+ evidence: result.evidence,
10238
+ agentName: result.agentName
10239
+ }).join("\n");
9757
10240
  }
9758
10241
  console.log();
9759
- console.log(theme.toolCall(`\u{1F916} Spawning ${tasksArr.length} sub-agents in parallel...`));
10242
+ console.log(theme.toolCall(`\u{1F916} Spawning ${tasksArr.length} sub-agents in parallel as ${agent.name}...`));
9760
10243
  const results = await Promise.all(
9761
- tasksArr.map((t, i) => runSubAgent(t, maxRounds, i + 1, ctx))
10244
+ tasksArr.map((t, i) => runSubAgent(t, effectiveMaxRounds, i + 1, ctx, agent))
9762
10245
  );
9763
10246
  const totalIn = results.reduce((s, r) => s + r.usage.inputTokens, 0);
9764
10247
  const totalOut = results.reduce((s, r) => s + r.usage.outputTokens, 0);
9765
10248
  const lines = [`## Parallel Sub-Agent Results (${results.length} agents)`, ""];
9766
10249
  results.forEach((r, i) => {
9767
- lines.push(`### Sub-Agent #${i + 1}`);
9768
- lines.push(`**Task**: ${tasksArr[i].slice(0, 200)}${tasksArr[i].length > 200 ? "..." : ""}`);
9769
- lines.push("");
9770
- lines.push(r.content);
9771
- lines.push("");
9772
- lines.push(`*Tokens: ${r.usage.inputTokens} in / ${r.usage.outputTokens} out*`);
10250
+ lines.push(...formatAgentResult({
10251
+ title: `### Sub-Agent #${i + 1}`,
10252
+ task: `${tasksArr[i].slice(0, 200)}${tasksArr[i].length > 200 ? "..." : ""}`,
10253
+ content: r.content,
10254
+ usage: r.usage,
10255
+ toolNames: r.toolNames,
10256
+ summary: r.summary,
10257
+ evidence: r.evidence,
10258
+ agentName: r.agentName
10259
+ }));
9773
10260
  lines.push("");
9774
10261
  });
9775
10262
  lines.push("---");
@@ -10000,14 +10487,14 @@ var taskStopTool = {
10000
10487
 
10001
10488
  // src/tools/builtin/git-tools.ts
10002
10489
  import { execFileSync as execFileSync2 } from "child_process";
10003
- import { existsSync as existsSync14 } from "fs";
10004
- import { join as join9 } from "path";
10490
+ import { existsSync as existsSync16 } from "fs";
10491
+ import { join as join11 } from "path";
10005
10492
  function assertGitRepo(cwd) {
10006
10493
  let dir = cwd;
10007
10494
  const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
10008
10495
  while (dir && dir !== root) {
10009
- if (existsSync14(join9(dir, ".git"))) return;
10010
- const parent = join9(dir, "..");
10496
+ if (existsSync16(join11(dir, ".git"))) return;
10497
+ const parent = join11(dir, "..");
10011
10498
  if (parent === dir) break;
10012
10499
  dir = parent;
10013
10500
  }
@@ -10272,9 +10759,9 @@ ${commitOutput.trim()}`;
10272
10759
  };
10273
10760
 
10274
10761
  // src/tools/builtin/notebook-edit.ts
10275
- import { readFileSync as readFileSync11, existsSync as existsSync15 } from "fs";
10762
+ import { readFileSync as readFileSync13, existsSync as existsSync17 } from "fs";
10276
10763
  import { writeFile } from "fs/promises";
10277
- import { resolve as resolve5, extname as extname2 } from "path";
10764
+ import { resolve as resolve6, extname as extname2 } from "path";
10278
10765
  var notebookEditTool = {
10279
10766
  definition: {
10280
10767
  name: "notebook_edit",
@@ -10323,14 +10810,14 @@ var notebookEditTool = {
10323
10810
  if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
10324
10811
  throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
10325
10812
  }
10326
- const absPath = resolve5(filePath);
10813
+ const absPath = resolve6(filePath);
10327
10814
  if (extname2(absPath).toLowerCase() !== ".ipynb") {
10328
10815
  throw new ToolError("notebook_edit", "path must point to a .ipynb file");
10329
10816
  }
10330
- if (!existsSync15(absPath)) {
10817
+ if (!existsSync17(absPath)) {
10331
10818
  throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
10332
10819
  }
10333
- const raw = readFileSync11(absPath, "utf-8");
10820
+ const raw = readFileSync13(absPath, "utf-8");
10334
10821
  const nb = parseNotebook(raw);
10335
10822
  const cellIdx0 = cellIndexRaw - 1;
10336
10823
  undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
@@ -10747,8 +11234,8 @@ function estimateToolDefinitionTokens(def) {
10747
11234
 
10748
11235
  // src/tools/registry.ts
10749
11236
  import { pathToFileURL } from "url";
10750
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync7 } from "fs";
10751
- import { join as join10 } from "path";
11237
+ import { existsSync as existsSync18, mkdirSync as mkdirSync8, readdirSync as readdirSync8 } from "fs";
11238
+ import { join as join12 } from "path";
10752
11239
  var ToolRegistry = class {
10753
11240
  tools = /* @__PURE__ */ new Map();
10754
11241
  pluginToolNames = /* @__PURE__ */ new Set();
@@ -10909,7 +11396,7 @@ var ToolRegistry = class {
10909
11396
  * Returns the number of successfully loaded plugins.
10910
11397
  */
10911
11398
  async loadPlugins(pluginsDir, allowPlugins = false) {
10912
- if (!existsSync16(pluginsDir)) {
11399
+ if (!existsSync18(pluginsDir)) {
10913
11400
  try {
10914
11401
  mkdirSync8(pluginsDir, { recursive: true });
10915
11402
  } catch {
@@ -10918,7 +11405,7 @@ var ToolRegistry = class {
10918
11405
  }
10919
11406
  let files;
10920
11407
  try {
10921
- files = readdirSync7(pluginsDir).filter((f) => f.endsWith(".js"));
11408
+ files = readdirSync8(pluginsDir).filter((f) => f.endsWith(".js"));
10922
11409
  } catch {
10923
11410
  return 0;
10924
11411
  }
@@ -10935,12 +11422,12 @@ var ToolRegistry = class {
10935
11422
  process.stderr.write(
10936
11423
  `
10937
11424
  [plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
10938
- ` + files.map((f) => ` + ${join10(pluginsDir, f)}`).join("\n") + "\n\n"
11425
+ ` + files.map((f) => ` + ${join12(pluginsDir, f)}`).join("\n") + "\n\n"
10939
11426
  );
10940
11427
  let loaded = 0;
10941
11428
  for (const file of files) {
10942
11429
  try {
10943
- const fileUrl = pathToFileURL(join10(pluginsDir, file)).href;
11430
+ const fileUrl = pathToFileURL(join12(pluginsDir, file)).href;
10944
11431
  const mod = await import(fileUrl);
10945
11432
  const tool = mod.tool ?? mod.default?.tool ?? mod.default;
10946
11433
  if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
@@ -11102,7 +11589,7 @@ var McpClient = class {
11102
11589
  // 内部方法:JSON-RPC 通信
11103
11590
  // ══════════════════════════════════════════════════════════════════
11104
11591
  sendRequest(method, params) {
11105
- return new Promise((resolve9, reject) => {
11592
+ return new Promise((resolve10, reject) => {
11106
11593
  if (!this.process?.stdin?.writable) {
11107
11594
  return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
11108
11595
  }
@@ -11126,7 +11613,7 @@ var McpClient = class {
11126
11613
  this.pendingRequests.set(id, {
11127
11614
  resolve: (result) => {
11128
11615
  cleanup();
11129
- resolve9(result);
11616
+ resolve10(result);
11130
11617
  },
11131
11618
  reject: (error) => {
11132
11619
  cleanup();
@@ -11203,13 +11690,13 @@ var McpClient = class {
11203
11690
  }
11204
11691
  /** Promise 超时包装 */
11205
11692
  withTimeout(promise, ms, label) {
11206
- return new Promise((resolve9, reject) => {
11693
+ return new Promise((resolve10, reject) => {
11207
11694
  const timer = setTimeout(() => {
11208
11695
  reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
11209
11696
  }, ms);
11210
11697
  promise.then((val) => {
11211
11698
  clearTimeout(timer);
11212
- resolve9(val);
11699
+ resolve10(val);
11213
11700
  }).catch((err) => {
11214
11701
  clearTimeout(timer);
11215
11702
  reject(err);
@@ -11486,11 +11973,11 @@ var McpManager = class {
11486
11973
  };
11487
11974
 
11488
11975
  // 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";
11976
+ import { existsSync as existsSync19, readdirSync as readdirSync9, mkdirSync as mkdirSync9, statSync as statSync7 } from "fs";
11977
+ import { join as join13 } from "path";
11491
11978
 
11492
11979
  // src/skills/types.ts
11493
- import { readFileSync as readFileSync12 } from "fs";
11980
+ import { readFileSync as readFileSync14 } from "fs";
11494
11981
  import { basename as basename5 } from "path";
11495
11982
  function parseSimpleYaml(yaml) {
11496
11983
  const result = {};
@@ -11512,7 +11999,7 @@ function parseYamlArray(value) {
11512
11999
  function parseSkillFile(filePath) {
11513
12000
  let raw;
11514
12001
  try {
11515
- raw = readFileSync12(filePath, "utf-8");
12002
+ raw = readFileSync14(filePath, "utf-8");
11516
12003
  } catch {
11517
12004
  return null;
11518
12005
  }
@@ -11555,7 +12042,7 @@ var SkillManager = class {
11555
12042
  /** 发现并加载 skillsDir 下所有 .md 文件,返回加载数量 */
11556
12043
  loadSkills() {
11557
12044
  this.skills.clear();
11558
- if (!existsSync17(this.skillsDir)) {
12045
+ if (!existsSync19(this.skillsDir)) {
11559
12046
  try {
11560
12047
  mkdirSync9(this.skillsDir, { recursive: true });
11561
12048
  } catch {
@@ -11564,20 +12051,20 @@ var SkillManager = class {
11564
12051
  }
11565
12052
  let entries;
11566
12053
  try {
11567
- entries = readdirSync8(this.skillsDir);
12054
+ entries = readdirSync9(this.skillsDir);
11568
12055
  } catch {
11569
12056
  return 0;
11570
12057
  }
11571
12058
  for (const entry of entries) {
11572
12059
  let filePath;
11573
- const fullPath = join11(this.skillsDir, entry);
12060
+ const fullPath = join13(this.skillsDir, entry);
11574
12061
  if (entry.endsWith(".md")) {
11575
12062
  filePath = fullPath;
11576
12063
  } else {
11577
12064
  try {
11578
12065
  if (statSync7(fullPath).isDirectory()) {
11579
- const skillMd = join11(fullPath, "SKILL.md");
11580
- if (existsSync17(skillMd)) {
12066
+ const skillMd = join13(fullPath, "SKILL.md");
12067
+ if (existsSync19(skillMd)) {
11581
12068
  filePath = skillMd;
11582
12069
  } else {
11583
12070
  continue;
@@ -11642,7 +12129,7 @@ var SkillManager = class {
11642
12129
  // src/web/tool-executor-web.ts
11643
12130
  import { randomUUID as randomUUID2 } from "crypto";
11644
12131
  import { tmpdir as tmpdir2 } from "os";
11645
- import { existsSync as existsSync18, readFileSync as readFileSync13 } from "fs";
12132
+ import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
11646
12133
  var ToolExecutorWeb = class _ToolExecutorWeb {
11647
12134
  constructor(registry, ws) {
11648
12135
  this.registry = registry;
@@ -11671,6 +12158,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11671
12158
  confirming = false;
11672
12159
  /** Session-level auto-approve toggle (/yolo command) */
11673
12160
  sessionAutoApprove = false;
12161
+ /** Session-level Auto Mode: rule-based low-risk auto approval. */
12162
+ sessionAutoMode = false;
11674
12163
  /** Track tool start times for duration calculation */
11675
12164
  toolStartTimes = /* @__PURE__ */ new Map();
11676
12165
  /**
@@ -11710,33 +12199,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11710
12199
  }
11711
12200
  /** Resolve a pending confirm from client response */
11712
12201
  resolveConfirm(requestId, approved) {
11713
- const resolve9 = this.pendingConfirms.get(requestId);
11714
- if (resolve9) {
12202
+ const resolve10 = this.pendingConfirms.get(requestId);
12203
+ if (resolve10) {
11715
12204
  this.clearPendingTimer(requestId);
11716
12205
  this.pendingConfirms.delete(requestId);
11717
12206
  this.confirming = false;
11718
- resolve9(approved);
12207
+ resolve10(approved);
11719
12208
  }
11720
12209
  }
11721
12210
  /** Resolve a pending batch confirm from client response */
11722
12211
  resolveBatchConfirm(requestId, decision) {
11723
- const resolve9 = this.pendingBatchConfirms.get(requestId);
11724
- if (resolve9) {
12212
+ const resolve10 = this.pendingBatchConfirms.get(requestId);
12213
+ if (resolve10) {
11725
12214
  this.clearPendingTimer(requestId);
11726
12215
  this.pendingBatchConfirms.delete(requestId);
11727
12216
  this.confirming = false;
11728
12217
  if (decision === "all" || decision === "none") {
11729
- resolve9(decision);
12218
+ resolve10(decision);
11730
12219
  } else {
11731
- resolve9(new Set(decision));
12220
+ resolve10(new Set(decision));
11732
12221
  }
11733
12222
  }
11734
12223
  }
11735
12224
  /** Cancel all pending confirms (e.g., on disconnect) */
11736
12225
  cancelAll() {
11737
- for (const resolve9 of this.pendingConfirms.values()) resolve9(false);
12226
+ for (const resolve10 of this.pendingConfirms.values()) resolve10(false);
11738
12227
  this.pendingConfirms.clear();
11739
- for (const resolve9 of this.pendingBatchConfirms.values()) resolve9("none");
12228
+ for (const resolve10 of this.pendingBatchConfirms.values()) resolve10("none");
11740
12229
  this.pendingBatchConfirms.clear();
11741
12230
  this.confirming = false;
11742
12231
  }
@@ -11779,9 +12268,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11779
12268
  if (call.name === "write_file") {
11780
12269
  const filePath = String(call.arguments["path"] ?? "");
11781
12270
  const newContent = String(call.arguments["content"] ?? "");
11782
- if (filePath && existsSync18(filePath)) {
12271
+ if (filePath && existsSync20(filePath)) {
11783
12272
  try {
11784
- const old = readFileSync13(filePath, "utf-8");
12273
+ const old = readFileSync15(filePath, "utf-8");
11785
12274
  return renderDiff(old, newContent, { filePath });
11786
12275
  } catch {
11787
12276
  }
@@ -11812,8 +12301,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11812
12301
  diff: this.getDiffPreview(call)
11813
12302
  };
11814
12303
  this.send(msg);
11815
- return new Promise((resolve9) => {
11816
- this.pendingConfirms.set(requestId, resolve9);
12304
+ return new Promise((resolve10) => {
12305
+ this.pendingConfirms.set(requestId, resolve10);
11817
12306
  this.pendingTimers.set(requestId, setTimeout(() => {
11818
12307
  if (this.pendingConfirms.has(requestId)) {
11819
12308
  this.resolveConfirm(requestId, false);
@@ -11837,8 +12326,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11837
12326
  files
11838
12327
  };
11839
12328
  this.send(msg);
11840
- return new Promise((resolve9) => {
11841
- this.pendingBatchConfirms.set(requestId, resolve9);
12329
+ return new Promise((resolve10) => {
12330
+ this.pendingBatchConfirms.set(requestId, resolve10);
11842
12331
  this.pendingTimers.set(requestId, setTimeout(() => {
11843
12332
  if (this.pendingBatchConfirms.has(requestId)) {
11844
12333
  this.resolveBatchConfirm(requestId, "none");
@@ -11948,6 +12437,35 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11948
12437
  }
11949
12438
  }
11950
12439
  this.sendToolCallStart(call);
12440
+ if (this.sessionAutoMode && networkPermission?.action !== "confirm") {
12441
+ const classification = defaultActionClassifier.classify(call, dangerLevel, {
12442
+ workspaceRoot: this.workspaceRoot,
12443
+ tempDirs: [tmpdir2()]
12444
+ });
12445
+ if (classification.decision === "deny") {
12446
+ recordRecentlyDeniedAutoAction(call, classification);
12447
+ const rejectionMsg = `[Auto denied] ${classification.reason}. Do not retry without asking.`;
12448
+ this.sendToolCallResult(call, rejectionMsg, true);
12449
+ return { callId: call.id, content: rejectionMsg, isError: true };
12450
+ }
12451
+ if (classification.decision === "auto-approve") {
12452
+ this.send({ type: "info", message: `\u26A1 Auto-approved (/auto: ${classification.ruleId})` });
12453
+ try {
12454
+ const rawContent = await runTool(tool, call.arguments, call.name);
12455
+ const content = truncateOutput(rawContent, call.name);
12456
+ this.sendToolCallResult(call, rawContent, false);
12457
+ runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
12458
+ runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
12459
+ return { callId: call.id, content, isError: false };
12460
+ } catch (err) {
12461
+ const message = err instanceof Error ? err.message : String(err);
12462
+ this.sendToolCallResult(call, message, true);
12463
+ runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
12464
+ runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
12465
+ return { callId: call.id, content: message, isError: true };
12466
+ }
12467
+ }
12468
+ }
11951
12469
  if (this.sessionAutoApprove && dangerLevel === "write") {
11952
12470
  try {
11953
12471
  const rawContent = await runTool(tool, call.arguments, call.name);
@@ -12055,20 +12573,20 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12055
12573
  };
12056
12574
 
12057
12575
  // src/core/system-prompt-builder.ts
12058
- import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
12059
- import { join as join13 } from "path";
12576
+ import { existsSync as existsSync22, readFileSync as readFileSync17 } from "fs";
12577
+ import { join as join15 } from "path";
12060
12578
 
12061
12579
  // 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";
12064
- import { homedir as homedir6 } from "os";
12580
+ import { existsSync as existsSync21, readFileSync as readFileSync16, unlinkSync as unlinkSync4, mkdirSync as mkdirSync10 } from "fs";
12581
+ import { join as join14 } from "path";
12582
+ import { homedir as homedir7 } from "os";
12065
12583
  function getDevStatePath() {
12066
- return join12(homedir6(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
12584
+ return join14(homedir7(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
12067
12585
  }
12068
12586
  function loadDevState() {
12069
12587
  const path3 = getDevStatePath();
12070
- if (!existsSync19(path3)) return null;
12071
- const content = readFileSync14(path3, "utf-8").trim();
12588
+ if (!existsSync21(path3)) return null;
12589
+ const content = readFileSync16(path3, "utf-8").trim();
12072
12590
  return content || null;
12073
12591
  }
12074
12592
 
@@ -12126,9 +12644,9 @@ ${ctx.activeSkill.content}`);
12126
12644
  return { stable: stableParts.join("\n\n---\n\n"), volatile };
12127
12645
  }
12128
12646
  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();
12647
+ const memoryPath = join15(configDir, MEMORY_FILE_NAME);
12648
+ if (!existsSync22(memoryPath)) return null;
12649
+ let content = readFileSync17(memoryPath, "utf-8").trim();
12132
12650
  if (!content) return null;
12133
12651
  if (content.length > MEMORY_MAX_CHARS) {
12134
12652
  content = content.slice(-MEMORY_MAX_CHARS);
@@ -12337,8 +12855,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
12337
12855
  }
12338
12856
 
12339
12857
  // 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";
12858
+ import { existsSync as existsSync23, readFileSync as readFileSync18 } from "fs";
12859
+ import { join as join16, relative as relative3, resolve as resolve7 } from "path";
12342
12860
  function uniqueNonEmpty(values) {
12343
12861
  const seen = /* @__PURE__ */ new Set();
12344
12862
  const out = [];
@@ -12361,14 +12879,14 @@ function displayPath(filePath, cwd) {
12361
12879
  return filePath;
12362
12880
  }
12363
12881
  function isInsideOrEqual(parent, child) {
12364
- const rel = relative3(resolve6(parent), resolve6(child));
12882
+ const rel = relative3(resolve7(parent), resolve7(child));
12365
12883
  return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
12366
12884
  }
12367
12885
  function readContextFile(level, filePath, cwd, maxBytes) {
12368
12886
  const name = filePath.split(/[\\/]/).pop() ?? filePath;
12369
12887
  const shown = displayPath(filePath, cwd);
12370
12888
  try {
12371
- const raw = readFileSync16(filePath);
12889
+ const raw = readFileSync18(filePath);
12372
12890
  const byteCount = raw.byteLength;
12373
12891
  if (byteCount === 0) {
12374
12892
  return {
@@ -12415,8 +12933,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
12415
12933
  function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
12416
12934
  const skipped = [];
12417
12935
  for (const candidate of candidates) {
12418
- const filePath = join14(dir, candidate);
12419
- if (!existsSync21(filePath)) continue;
12936
+ const filePath = join16(dir, candidate);
12937
+ if (!existsSync23(filePath)) continue;
12420
12938
  const result = readContextFile(level, filePath, cwd, maxBytes);
12421
12939
  if (result.skipped) skipped.push(result.skipped);
12422
12940
  if (result.layer) return { layer: result.layer, skipped };
@@ -12424,7 +12942,7 @@ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
12424
12942
  return { layer: null, skipped };
12425
12943
  }
12426
12944
  function loadContextFiles(options) {
12427
- const cwd = resolve6(options.cwd);
12945
+ const cwd = resolve7(options.cwd);
12428
12946
  const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
12429
12947
  const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
12430
12948
  const skipped = [];
@@ -12432,7 +12950,7 @@ function loadContextFiles(options) {
12432
12950
  return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
12433
12951
  }
12434
12952
  if (options.setting && options.setting !== "auto") {
12435
- const filePath = resolve6(cwd, String(options.setting));
12953
+ const filePath = resolve7(cwd, String(options.setting));
12436
12954
  if (!isInsideOrEqual(cwd, filePath)) {
12437
12955
  skipped.push({
12438
12956
  level: "single",
@@ -12443,7 +12961,7 @@ function loadContextFiles(options) {
12443
12961
  });
12444
12962
  return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
12445
12963
  }
12446
- if (!existsSync21(filePath)) {
12964
+ if (!existsSync23(filePath)) {
12447
12965
  skipped.push({
12448
12966
  level: "single",
12449
12967
  filePath,
@@ -12476,7 +12994,7 @@ function loadContextFiles(options) {
12476
12994
  skipped.push(...result.skipped);
12477
12995
  if (result.layer) layers.push(result.layer);
12478
12996
  }
12479
- if (resolve6(options.cwd) !== resolve6(options.projectRoot)) {
12997
+ if (resolve7(options.cwd) !== resolve7(options.projectRoot)) {
12480
12998
  const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
12481
12999
  skipped.push(...result.skipped);
12482
13000
  if (result.layer) layers.push(result.layer);
@@ -12494,13 +13012,13 @@ function loadContextFiles(options) {
12494
13012
  }
12495
13013
 
12496
13014
  // 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";
13015
+ import { existsSync as existsSync26, readFileSync as readFileSync20, writeFileSync as writeFileSync2, mkdirSync as mkdirSync11, readdirSync as readdirSync11, statSync as statSync9, createWriteStream } from "fs";
13016
+ import { join as join20, resolve as resolve8, dirname as dirname5 } from "path";
12499
13017
 
12500
13018
  // src/tools/git-context.ts
12501
13019
  import { execSync as execSync2 } from "child_process";
12502
- import { existsSync as existsSync22 } from "fs";
12503
- import { join as join15 } from "path";
13020
+ import { existsSync as existsSync24 } from "fs";
13021
+ import { join as join17 } from "path";
12504
13022
  function runGit2(cmd, cwd) {
12505
13023
  try {
12506
13024
  return execSync2(`git ${cmd}`, {
@@ -12517,7 +13035,7 @@ function getGitRoot(cwd = process.cwd()) {
12517
13035
  return runGit2("rev-parse --show-toplevel", cwd);
12518
13036
  }
12519
13037
  function getGitContext(cwd = process.cwd()) {
12520
- if (!existsSync22(join15(cwd, ".git"))) {
13038
+ if (!existsSync24(join17(cwd, ".git"))) {
12521
13039
  const result = runGit2("rev-parse --git-dir", cwd);
12522
13040
  if (!result) return null;
12523
13041
  }
@@ -12646,8 +13164,8 @@ If no security issues found, state "\u2705 No security vulnerabilities detected"
12646
13164
  }
12647
13165
 
12648
13166
  // 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";
13167
+ import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
13168
+ import { join as join18 } from "path";
12651
13169
  var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
12652
13170
  "node_modules",
12653
13171
  ".git",
@@ -12683,7 +13201,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
12683
13201
  if (depth > maxDepth || count >= maxEntries) return;
12684
13202
  let entries;
12685
13203
  try {
12686
- entries = readdirSync9(d);
13204
+ entries = readdirSync10(d);
12687
13205
  } catch {
12688
13206
  return;
12689
13207
  }
@@ -12691,11 +13209,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
12691
13209
  const sorted = filtered.sort((a, b) => {
12692
13210
  let aIsDir = false, bIsDir = false;
12693
13211
  try {
12694
- aIsDir = statSync8(join16(d, a)).isDirectory();
13212
+ aIsDir = statSync8(join18(d, a)).isDirectory();
12695
13213
  } catch {
12696
13214
  }
12697
13215
  try {
12698
- bIsDir = statSync8(join16(d, b)).isDirectory();
13216
+ bIsDir = statSync8(join18(d, b)).isDirectory();
12699
13217
  } catch {
12700
13218
  }
12701
13219
  if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
@@ -12703,7 +13221,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
12703
13221
  });
12704
13222
  for (let i = 0; i < sorted.length && count < maxEntries; i++) {
12705
13223
  const name = sorted[i];
12706
- const fullPath = join16(d, name);
13224
+ const fullPath = join18(d, name);
12707
13225
  const isLast = i === sorted.length - 1;
12708
13226
  const connector = isLast ? "+-- " : "|-- ";
12709
13227
  let isDir;
@@ -12730,7 +13248,7 @@ function scanProject(cwd) {
12730
13248
  configFiles: [],
12731
13249
  directoryStructure: ""
12732
13250
  };
12733
- const check = (file) => existsSync23(join16(cwd, file));
13251
+ const check = (file) => existsSync25(join18(cwd, file));
12734
13252
  const configCandidates = [
12735
13253
  "package.json",
12736
13254
  "tsconfig.json",
@@ -12757,7 +13275,7 @@ function scanProject(cwd) {
12757
13275
  info.type = "node";
12758
13276
  info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
12759
13277
  try {
12760
- const pkg = JSON.parse(readFileSync17(join16(cwd, "package.json"), "utf-8"));
13278
+ const pkg = JSON.parse(readFileSync19(join18(cwd, "package.json"), "utf-8"));
12761
13279
  const scripts = pkg.scripts ?? {};
12762
13280
  info.buildCommand = scripts.build ? "npm run build" : void 0;
12763
13281
  info.testCommand = scripts.test ? "npm test" : void 0;
@@ -13417,7 +13935,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
13417
13935
  }
13418
13936
 
13419
13937
  // src/hub/persist.ts
13420
- import { join as join17 } from "path";
13938
+ import { join as join19 } from "path";
13421
13939
  function discussionToMessages(state2) {
13422
13940
  const out = [];
13423
13941
  const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
@@ -13455,7 +13973,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
13455
13973
  session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
13456
13974
  session.titleAiGenerated = true;
13457
13975
  await sm.save();
13458
- return { id: session.id, path: join17(config.getHistoryDir(), `${session.id}.json`) };
13976
+ return { id: session.id, path: join19(config.getHistoryDir(), `${session.id}.json`) };
13459
13977
  }
13460
13978
 
13461
13979
  // src/web/session-handler.ts
@@ -13615,7 +14133,19 @@ var SessionHandler = class {
13615
14133
  costUsd,
13616
14134
  providers: providerList,
13617
14135
  branches,
13618
- activeBranchId: sess?.activeBranchId ?? "main"
14136
+ activeBranchId: sess?.activeBranchId ?? "main",
14137
+ agents: listAgentRuns().slice(0, 20).map((run) => ({
14138
+ id: run.id,
14139
+ agentName: run.agentName,
14140
+ task: run.task,
14141
+ status: run.status,
14142
+ startedAt: run.startedAt,
14143
+ finishedAt: run.finishedAt,
14144
+ summary: run.summary,
14145
+ error: run.error,
14146
+ toolNames: run.toolNames,
14147
+ agentIndex: run.agentIndex
14148
+ }))
13619
14149
  });
13620
14150
  }
13621
14151
  async handleMessage(raw) {
@@ -13639,10 +14169,10 @@ var SessionHandler = class {
13639
14169
  return;
13640
14170
  }
13641
14171
  case "ask_user_response": {
13642
- const resolve9 = this.pendingAskUser.get(msg.requestId);
13643
- if (resolve9) {
14172
+ const resolve10 = this.pendingAskUser.get(msg.requestId);
14173
+ if (resolve10) {
13644
14174
  this.pendingAskUser.delete(msg.requestId);
13645
- resolve9(msg.answer);
14175
+ resolve10(msg.answer);
13646
14176
  }
13647
14177
  return;
13648
14178
  }
@@ -13653,10 +14183,10 @@ var SessionHandler = class {
13653
14183
  case "memory_rebuild":
13654
14184
  return this.handleMemoryRebuild(Boolean(msg.full));
13655
14185
  case "auto_pause_response": {
13656
- const resolve9 = this.pendingAutoPause.get(msg.requestId);
13657
- if (resolve9) {
14186
+ const resolve10 = this.pendingAutoPause.get(msg.requestId);
14187
+ if (resolve10) {
13658
14188
  this.pendingAutoPause.delete(msg.requestId);
13659
- resolve9({ action: msg.action, message: msg.message });
14189
+ resolve10({ action: msg.action, message: msg.message });
13660
14190
  }
13661
14191
  return;
13662
14192
  }
@@ -13671,10 +14201,10 @@ var SessionHandler = class {
13671
14201
  this.hubOrchestrator?.abort();
13672
14202
  return;
13673
14203
  case "hub_steer": {
13674
- const resolve9 = this.pendingHubReview.get(msg.requestId);
13675
- if (resolve9) {
14204
+ const resolve10 = this.pendingHubReview.get(msg.requestId);
14205
+ if (resolve10) {
13676
14206
  this.pendingHubReview.delete(msg.requestId);
13677
- resolve9({ action: msg.action, message: msg.message });
14207
+ resolve10({ action: msg.action, message: msg.message });
13678
14208
  }
13679
14209
  return;
13680
14210
  }
@@ -13687,9 +14217,9 @@ var SessionHandler = class {
13687
14217
  runLifecycleHooks(this.config.get("hooks") ?? void 0, "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
13688
14218
  this.toolExecutor.cancelAll();
13689
14219
  if (this.abortController) this.abortController.abort();
13690
- for (const resolve9 of this.pendingAskUser.values()) resolve9(null);
14220
+ for (const resolve10 of this.pendingAskUser.values()) resolve10(null);
13691
14221
  this.pendingAskUser.clear();
13692
- for (const resolve9 of this.pendingAutoPause.values()) resolve9({ action: "stop" });
14222
+ for (const resolve10 of this.pendingAutoPause.values()) resolve10({ action: "stop" });
13693
14223
  this.pendingAutoPause.clear();
13694
14224
  this.saveIfNeeded();
13695
14225
  }
@@ -13823,9 +14353,9 @@ var SessionHandler = class {
13823
14353
  this.hubOrchestrator = orchestrator;
13824
14354
  orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
13825
14355
  if (config.humanSteer) {
13826
- orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve9) => {
14356
+ orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve10) => {
13827
14357
  const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
13828
- this.pendingHubReview.set(requestId, resolve9);
14358
+ this.pendingHubReview.set(requestId, resolve10);
13829
14359
  this.send({ type: "hub_review", requestId, round, maxRounds });
13830
14360
  });
13831
14361
  }
@@ -14092,6 +14622,7 @@ Details: ${errMsg.split("\n")[0]}
14092
14622
  ${systemPromptVolatile}` : systemPrompt;
14093
14623
  spawnAgentContext.modelParams = modelParams;
14094
14624
  spawnAgentContext.configManager = this.config;
14625
+ spawnAgentContext.providers = this.providers;
14095
14626
  ToolExecutor.currentMessageIndex = this.sessions.current?.messages.length ?? 0;
14096
14627
  return this.toolExecutor.executeAll(toolCalls);
14097
14628
  },
@@ -14182,8 +14713,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
14182
14713
  onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
14183
14714
  requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
14184
14715
  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);
14716
+ const pauseResp = await new Promise((resolve10) => {
14717
+ this.pendingAutoPause.set(requestId, resolve10);
14187
14718
  this.send({
14188
14719
  type: "auto_pause_request",
14189
14720
  requestId,
@@ -14362,8 +14893,8 @@ ${summaryContent}`,
14362
14893
  }
14363
14894
  if (chunk.done) break;
14364
14895
  }
14365
- await new Promise((resolve9, reject) => {
14366
- fileStream.end((err) => err ? reject(err) : resolve9());
14896
+ await new Promise((resolve10, reject) => {
14897
+ fileStream.end((err) => err ? reject(err) : resolve10());
14367
14898
  });
14368
14899
  const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
14369
14900
  if (verdict.kind === "reject") {
@@ -14389,7 +14920,7 @@ ${summaryContent}`,
14389
14920
  } catch (err) {
14390
14921
  if (fileStream) {
14391
14922
  try {
14392
- await new Promise((resolve9) => fileStream.end(() => resolve9()));
14923
+ await new Promise((resolve10) => fileStream.end(() => resolve10()));
14393
14924
  } catch {
14394
14925
  }
14395
14926
  }
@@ -14686,6 +15217,7 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
14686
15217
  " /export [md|json] \u2014 Export conversation",
14687
15218
  " /skill [name|off|list|reload] \u2014 Manage agent skills",
14688
15219
  " /memory [show|add|clear] \u2014 Persistent memory management",
15220
+ " /agent list|switch|stop|summary \u2014 Manage named sub-agents",
14689
15221
  " /yolo [on|off] \u2014 Toggle auto-approve (skip confirmations)",
14690
15222
  " /search <keyword> \u2014 Search across all session histories",
14691
15223
  " /undo [list|<n>] \u2014 Undo file operations",
@@ -14818,7 +15350,108 @@ ${activated.meta.description || ""}` });
14818
15350
  }
14819
15351
  break;
14820
15352
  }
15353
+ case "agent": {
15354
+ const sub = args[0]?.toLowerCase() ?? "summary";
15355
+ if (sub === "list") {
15356
+ const agents = listAgentConfigs(this.config.getConfigDir());
15357
+ const lines = [`Agents (${agents.length})`, ""];
15358
+ for (const agent of agents) {
15359
+ const active = agent.name === getPreferredAgentName() ? " *" : " ";
15360
+ const model = [agent.provider, agent.model].filter(Boolean).join("/") || "inherit";
15361
+ lines.push(`${active} ${agent.name} [${agent.source ?? "builtin"}] \u2014 ${agent.description ?? ""}`);
15362
+ lines.push(` model: ${model} \xB7 permission: ${agent.permissionProfile ?? "workspace-write"} \xB7 maxRounds: ${agent.maxToolRounds ?? "inherit"}`);
15363
+ }
15364
+ lines.push("", "Config dirs: ~/.aicli/agents/ and .aicli/agents/");
15365
+ this.send({ type: "info", message: lines.join("\n") });
15366
+ break;
15367
+ }
15368
+ if (sub === "switch") {
15369
+ const name2 = args[1];
15370
+ if (!name2) {
15371
+ this.send({ type: "error", message: "Usage: /agent switch <name>" });
15372
+ break;
15373
+ }
15374
+ const agent = resolveAgentConfig(name2, this.config.getConfigDir());
15375
+ if (!agent) {
15376
+ const available = listAgentConfigs(this.config.getConfigDir()).map((a) => a.name).join(", ");
15377
+ this.send({ type: "error", message: `Unknown agent: ${name2}
15378
+ Available: ${available}` });
15379
+ break;
15380
+ }
15381
+ setPreferredAgentName(agent.name);
15382
+ this.send({ type: "info", message: `Preferred spawn_agent role: ${agent.name}` });
15383
+ this.sendStatus();
15384
+ break;
15385
+ }
15386
+ if (sub === "stop") {
15387
+ const target = args[1] ?? "all";
15388
+ const count = requestAgentStop(target);
15389
+ this.send({ type: "info", message: count > 0 ? `Stop requested for ${count} running agent(s).` : "No matching running agents." });
15390
+ this.sendStatus();
15391
+ break;
15392
+ }
15393
+ if (sub === "summary") {
15394
+ const runs2 = listAgentRuns();
15395
+ if (runs2.length === 0) {
15396
+ this.send({ type: "info", message: "No sub-agent runs yet." });
15397
+ break;
15398
+ }
15399
+ const lines = [`Recent agent runs (${runs2.length})`, ""];
15400
+ for (const run of runs2.slice(0, 20)) {
15401
+ lines.push(`${run.id} ${run.status} ${run.agentName} ${run.startedAt}`);
15402
+ lines.push(` task: ${run.task.slice(0, 120)}${run.task.length > 120 ? "..." : ""}`);
15403
+ if (run.toolNames.length > 0) lines.push(` tools: ${run.toolNames.join(", ")}`);
15404
+ if (run.summary) lines.push(` summary: ${run.summary.slice(0, 180)}${run.summary.length > 180 ? "..." : ""}`);
15405
+ if (run.error) lines.push(` error: ${run.error}`);
15406
+ }
15407
+ this.send({ type: "info", message: lines.join("\n") });
15408
+ break;
15409
+ }
15410
+ this.send({ type: "error", message: "Usage: /agent list|switch <name>|stop <id|all>|summary" });
15411
+ break;
15412
+ }
14821
15413
  // ── /yolo ──────────────────────────────────────────────────────
15414
+ case "auto": {
15415
+ const sub = args[0]?.toLowerCase() ?? "status";
15416
+ if (sub === "on") {
15417
+ this.toolExecutor.sessionAutoMode = true;
15418
+ 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." });
15419
+ } else if (sub === "off") {
15420
+ this.toolExecutor.sessionAutoMode = false;
15421
+ this.send({ type: "info", message: "Auto Mode disabled." });
15422
+ } else if (sub === "status") {
15423
+ this.send({ type: "info", message: `Auto Mode: ${this.toolExecutor.sessionAutoMode ? "on" : "off"}
15424
+ Scope: current session only
15425
+ Denied: /permissions recently-denied` });
15426
+ } else {
15427
+ this.send({ type: "error", message: "Usage: /auto [on|off|status]" });
15428
+ }
15429
+ this.sendStatus();
15430
+ break;
15431
+ }
15432
+ case "permissions": {
15433
+ const sub = args[0]?.toLowerCase() ?? "recently-denied";
15434
+ if (sub === "recently-denied") {
15435
+ const rows = getRecentlyDeniedAutoActions();
15436
+ if (rows.length === 0) {
15437
+ this.send({ type: "info", message: "No recently denied Auto Mode actions." });
15438
+ } else {
15439
+ const lines = [`Recently denied Auto Mode actions (${rows.length})`, ""];
15440
+ for (const r of rows.slice(0, 20)) {
15441
+ lines.push(`- ${r.timestamp} ${r.tool} ${r.ruleId}`);
15442
+ lines.push(` ${r.reason}`);
15443
+ lines.push(` ${r.argsPreview}`);
15444
+ }
15445
+ this.send({ type: "info", message: lines.join("\n") });
15446
+ }
15447
+ } else if (sub === "clear-denied") {
15448
+ clearRecentlyDeniedAutoActions();
15449
+ this.send({ type: "info", message: "Cleared recently denied Auto Mode actions." });
15450
+ } else {
15451
+ this.send({ type: "error", message: "Usage: /permissions recently-denied|clear-denied" });
15452
+ }
15453
+ break;
15454
+ }
14822
15455
  case "yolo": {
14823
15456
  const sub = args[0]?.toLowerCase();
14824
15457
  if (sub === "off") {
@@ -14919,9 +15552,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14919
15552
  let modifiedFiles = 0;
14920
15553
  const diffLines2 = [];
14921
15554
  for (const [filePath, { earliest }] of fileMap) {
14922
- const currentContent = existsSync24(filePath) ? (() => {
15555
+ const currentContent = existsSync26(filePath) ? (() => {
14923
15556
  try {
14924
- return readFileSync18(filePath, "utf-8");
15557
+ return readFileSync20(filePath, "utf-8");
14925
15558
  } catch {
14926
15559
  return null;
14927
15560
  }
@@ -15022,7 +15655,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15022
15655
  break;
15023
15656
  }
15024
15657
  const sub = args[0]?.toLowerCase();
15025
- const resolve9 = (ref) => {
15658
+ const resolve10 = (ref) => {
15026
15659
  const r = session.resolveBranchRef(ref);
15027
15660
  if (r.ok) return r.id;
15028
15661
  if (r.reason === "ambiguous") {
@@ -15076,7 +15709,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15076
15709
  this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
15077
15710
  break;
15078
15711
  }
15079
- const id = resolve9(ref);
15712
+ const id = resolve10(ref);
15080
15713
  if (!id) break;
15081
15714
  const ok = session.switchBranch(id);
15082
15715
  if (ok) {
@@ -15096,7 +15729,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15096
15729
  this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
15097
15730
  break;
15098
15731
  }
15099
- const id = resolve9(ref);
15732
+ const id = resolve10(ref);
15100
15733
  if (!id) break;
15101
15734
  const ok = session.deleteBranch(id);
15102
15735
  if (ok) {
@@ -15115,7 +15748,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15115
15748
  this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
15116
15749
  break;
15117
15750
  }
15118
- const id = resolve9(ref);
15751
+ const id = resolve10(ref);
15119
15752
  if (!id) break;
15120
15753
  const ok = session.renameBranch(id, title);
15121
15754
  if (ok) {
@@ -15133,7 +15766,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15133
15766
  this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
15134
15767
  break;
15135
15768
  }
15136
- const id = resolve9(ref);
15769
+ const id = resolve10(ref);
15137
15770
  if (!id) break;
15138
15771
  const d = session.diffBranches(id);
15139
15772
  if (!d) {
@@ -15175,7 +15808,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15175
15808
  this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
15176
15809
  break;
15177
15810
  }
15178
- const id = resolve9(ref);
15811
+ const id = resolve10(ref);
15179
15812
  if (!id) break;
15180
15813
  const idx = parseInt(idxStr, 10);
15181
15814
  if (Number.isNaN(idx)) {
@@ -15437,7 +16070,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15437
16070
  case "test": {
15438
16071
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
15439
16072
  try {
15440
- const { executeTests } = await import("./run-tests-D7YBY4XB.js");
16073
+ const { executeTests } = await import("./run-tests-R6FL6CQ3.js");
15441
16074
  const argStr = args.join(" ").trim();
15442
16075
  let testArgs = {};
15443
16076
  if (argStr) {
@@ -15454,9 +16087,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15454
16087
  // ── /init ───────────────────────────────────────────────────────
15455
16088
  case "init": {
15456
16089
  const cwd = process.cwd();
15457
- const targetPath = join18(cwd, "AICLI.md");
16090
+ const targetPath = join20(cwd, "AICLI.md");
15458
16091
  const force = args.includes("--force");
15459
- if (existsSync24(targetPath) && !force) {
16092
+ if (existsSync26(targetPath) && !force) {
15460
16093
  this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
15461
16094
  Use /init --force to overwrite.` });
15462
16095
  break;
@@ -15489,7 +16122,7 @@ Use /context reload to load it.` });
15489
16122
  lines.push("**Config Files:**");
15490
16123
  lines.push(` Dir: ${configDir}`);
15491
16124
  const checkFile = (label, filePath) => {
15492
- const exists = existsSync24(filePath);
16125
+ const exists = existsSync26(filePath);
15493
16126
  let extra = "";
15494
16127
  if (exists) {
15495
16128
  try {
@@ -15499,9 +16132,9 @@ Use /context reload to load it.` });
15499
16132
  }
15500
16133
  lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
15501
16134
  };
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"));
16135
+ checkFile("config.json", join20(configDir, "config.json"));
16136
+ checkFile("memory.md", join20(configDir, MEMORY_FILE_NAME));
16137
+ checkFile("dev-state.md", join20(configDir, "dev-state.md"));
15505
16138
  lines.push("");
15506
16139
  if (this.mcpManager) {
15507
16140
  lines.push("**MCP Servers:**");
@@ -15688,8 +16321,8 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
15688
16321
  this.send({ type: "info", message: "\u2713 All added directories removed from context." });
15689
16322
  break;
15690
16323
  }
15691
- const dirPath = resolve7(sub);
15692
- if (!existsSync24(dirPath)) {
16324
+ const dirPath = resolve8(sub);
16325
+ if (!existsSync26(dirPath)) {
15693
16326
  this.send({ type: "error", message: `Directory not found: ${dirPath}` });
15694
16327
  break;
15695
16328
  }
@@ -15705,14 +16338,14 @@ It will be included in AI context for subsequent messages.` });
15705
16338
  // ── /commands ───────────────────────────────────────────────────
15706
16339
  case "commands": {
15707
16340
  const configDir = this.config.getConfigDir();
15708
- const commandsDir = join18(configDir, CUSTOM_COMMANDS_DIR_NAME);
15709
- if (!existsSync24(commandsDir)) {
16341
+ const commandsDir = join20(configDir, CUSTOM_COMMANDS_DIR_NAME);
16342
+ if (!existsSync26(commandsDir)) {
15710
16343
  this.send({ type: "info", message: `No custom commands directory.
15711
16344
  Create: ${commandsDir}/ with .md files.` });
15712
16345
  break;
15713
16346
  }
15714
16347
  try {
15715
- const files = readdirSync10(commandsDir).filter((f) => f.endsWith(".md"));
16348
+ const files = readdirSync11(commandsDir).filter((f) => f.endsWith(".md"));
15716
16349
  if (files.length === 0) {
15717
16350
  this.send({ type: "info", message: `No custom commands found in ${commandsDir}
15718
16351
  Add .md files to create commands.` });
@@ -15732,7 +16365,7 @@ Add .md files to create commands.` });
15732
16365
  // ── /plugins ────────────────────────────────────────────────────
15733
16366
  case "plugins": {
15734
16367
  const configDir = this.config.getConfigDir();
15735
- const pluginsDir = join18(configDir, PLUGINS_DIR_NAME);
16368
+ const pluginsDir = join20(configDir, PLUGINS_DIR_NAME);
15736
16369
  const pluginTools = this.toolRegistry.listPluginTools();
15737
16370
  const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
15738
16371
  if (pluginTools.length === 0) {
@@ -15929,11 +16562,11 @@ Add .md files to create commands.` });
15929
16562
  }
15930
16563
  memoryShow() {
15931
16564
  const configDir = this.config.getConfigDir();
15932
- const memPath = join18(configDir, MEMORY_FILE_NAME);
16565
+ const memPath = join20(configDir, MEMORY_FILE_NAME);
15933
16566
  let content = "";
15934
16567
  try {
15935
- if (existsSync24(memPath)) {
15936
- content = readFileSync18(memPath, "utf-8");
16568
+ if (existsSync26(memPath)) {
16569
+ content = readFileSync20(memPath, "utf-8");
15937
16570
  }
15938
16571
  } catch (err) {
15939
16572
  process.stderr.write(`[web] Failed to read memory file: ${err instanceof Error ? err.message : err}
@@ -15947,11 +16580,11 @@ Add .md files to create commands.` });
15947
16580
  }
15948
16581
  memoryAdd(text) {
15949
16582
  const configDir = this.config.getConfigDir();
15950
- const memPath = join18(configDir, MEMORY_FILE_NAME);
16583
+ const memPath = join20(configDir, MEMORY_FILE_NAME);
15951
16584
  try {
15952
16585
  mkdirSync11(configDir, { recursive: true });
15953
16586
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
15954
- const previous = existsSync24(memPath) ? readFileSync18(memPath, "utf-8") : "";
16587
+ const previous = existsSync26(memPath) ? readFileSync20(memPath, "utf-8") : "";
15955
16588
  const entry = `
15956
16589
  - [${timestamp}] ${text}
15957
16590
  `;
@@ -15963,7 +16596,7 @@ Add .md files to create commands.` });
15963
16596
  }
15964
16597
  memoryClear() {
15965
16598
  const configDir = this.config.getConfigDir();
15966
- const memPath = join18(configDir, MEMORY_FILE_NAME);
16599
+ const memPath = join20(configDir, MEMORY_FILE_NAME);
15967
16600
  try {
15968
16601
  atomicWriteFileSync(memPath, "");
15969
16602
  this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
@@ -16197,8 +16830,8 @@ async function setupProxy(configProxy) {
16197
16830
  }
16198
16831
 
16199
16832
  // 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";
16833
+ import { existsSync as existsSync27, readFileSync as readFileSync21, writeFileSync as writeFileSync3, mkdirSync as mkdirSync12, readdirSync as readdirSync12, copyFileSync } from "fs";
16834
+ import { join as join21 } from "path";
16202
16835
  import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
16203
16836
  var USERS_FILE = "users.json";
16204
16837
  var TOKEN_EXPIRY_HOURS = 24;
@@ -16213,7 +16846,7 @@ var AuthManager = class {
16213
16846
  db;
16214
16847
  constructor(baseDir) {
16215
16848
  this.baseDir = baseDir;
16216
- this.usersFile = join19(baseDir, USERS_FILE);
16849
+ this.usersFile = join21(baseDir, USERS_FILE);
16217
16850
  this.db = this.loadOrCreate();
16218
16851
  }
16219
16852
  // ── Public API ─────────────────────────────────────────────────
@@ -16242,9 +16875,9 @@ var AuthManager = class {
16242
16875
  }
16243
16876
  const salt = randomBytes(16).toString("hex");
16244
16877
  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 });
16878
+ const dataDir = join21(USERS_DIR, username);
16879
+ const fullDataDir = join21(this.baseDir, dataDir);
16880
+ mkdirSync12(join21(fullDataDir, "history"), { recursive: true });
16248
16881
  const user = {
16249
16882
  username,
16250
16883
  passwordHash,
@@ -16359,7 +16992,7 @@ var AuthManager = class {
16359
16992
  getUserDataDir(username) {
16360
16993
  const user = this.db.users.find((u) => u.username === username);
16361
16994
  if (!user) throw new Error(`User not found: ${username}`);
16362
- return join19(this.baseDir, user.dataDir);
16995
+ return join21(this.baseDir, user.dataDir);
16363
16996
  }
16364
16997
  /** List all usernames */
16365
16998
  listUsers() {
@@ -16395,30 +17028,30 @@ var AuthManager = class {
16395
17028
  const err = this.register(username, password);
16396
17029
  if (err) return err;
16397
17030
  const userDir = this.getUserDataDir(username);
16398
- const globalConfig = join19(this.baseDir, "config.json");
16399
- if (existsSync25(globalConfig)) {
17031
+ const globalConfig = join21(this.baseDir, "config.json");
17032
+ if (existsSync27(globalConfig)) {
16400
17033
  try {
16401
- const content = readFileSync19(globalConfig, "utf-8");
16402
- writeFileSync3(join19(userDir, "config.json"), content, "utf-8");
17034
+ const content = readFileSync21(globalConfig, "utf-8");
17035
+ writeFileSync3(join21(userDir, "config.json"), content, "utf-8");
16403
17036
  } catch {
16404
17037
  }
16405
17038
  }
16406
- const globalMemory = join19(this.baseDir, "memory.md");
16407
- if (existsSync25(globalMemory)) {
17039
+ const globalMemory = join21(this.baseDir, "memory.md");
17040
+ if (existsSync27(globalMemory)) {
16408
17041
  try {
16409
- const content = readFileSync19(globalMemory, "utf-8");
16410
- writeFileSync3(join19(userDir, "memory.md"), content, "utf-8");
17042
+ const content = readFileSync21(globalMemory, "utf-8");
17043
+ writeFileSync3(join21(userDir, "memory.md"), content, "utf-8");
16411
17044
  } catch {
16412
17045
  }
16413
17046
  }
16414
- const globalHistory = join19(this.baseDir, "history");
16415
- if (existsSync25(globalHistory)) {
17047
+ const globalHistory = join21(this.baseDir, "history");
17048
+ if (existsSync27(globalHistory)) {
16416
17049
  try {
16417
- const files = readdirSync11(globalHistory).filter((f) => f.endsWith(".json"));
16418
- const userHistory = join19(userDir, "history");
17050
+ const files = readdirSync12(globalHistory).filter((f) => f.endsWith(".json"));
17051
+ const userHistory = join21(userDir, "history");
16419
17052
  for (const f of files) {
16420
17053
  try {
16421
- copyFileSync(join19(globalHistory, f), join19(userHistory, f));
17054
+ copyFileSync(join21(globalHistory, f), join21(userHistory, f));
16422
17055
  } catch {
16423
17056
  }
16424
17057
  }
@@ -16429,9 +17062,9 @@ var AuthManager = class {
16429
17062
  }
16430
17063
  // ── Private methods ────────────────────────────────────────────
16431
17064
  loadOrCreate() {
16432
- if (existsSync25(this.usersFile)) {
17065
+ if (existsSync27(this.usersFile)) {
16433
17066
  try {
16434
- return JSON.parse(readFileSync19(this.usersFile, "utf-8"));
17067
+ return JSON.parse(readFileSync21(this.usersFile, "utf-8"));
16435
17068
  } catch {
16436
17069
  }
16437
17070
  }
@@ -16483,7 +17116,7 @@ function getModuleDir() {
16483
17116
  }
16484
17117
  } catch {
16485
17118
  }
16486
- return resolve8(".");
17119
+ return resolve9(".");
16487
17120
  }
16488
17121
  async function startWebServer(options = {}) {
16489
17122
  const port = options.port ?? 3e3;
@@ -16548,8 +17181,8 @@ async function startWebServer(options = {}) {
16548
17181
  }
16549
17182
  }
16550
17183
  let skillManager = null;
16551
- const skillsDir = join20(config.getConfigDir(), SKILLS_DIR_NAME);
16552
- if (existsSync26(skillsDir)) {
17184
+ const skillsDir = join22(config.getConfigDir(), SKILLS_DIR_NAME);
17185
+ if (existsSync28(skillsDir)) {
16553
17186
  skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
16554
17187
  skillManager.loadSkills();
16555
17188
  const count = skillManager.listSkills().length;
@@ -16620,18 +17253,18 @@ async function startWebServer(options = {}) {
16620
17253
  next();
16621
17254
  };
16622
17255
  const moduleDir = getModuleDir();
16623
- let clientDir = join20(moduleDir, "web", "client");
16624
- if (!existsSync26(clientDir)) {
16625
- clientDir = join20(moduleDir, "client");
17256
+ let clientDir = join22(moduleDir, "web", "client");
17257
+ if (!existsSync28(clientDir)) {
17258
+ clientDir = join22(moduleDir, "client");
16626
17259
  }
16627
- if (!existsSync26(clientDir)) {
16628
- clientDir = join20(moduleDir, "..", "..", "src", "web", "client");
17260
+ if (!existsSync28(clientDir)) {
17261
+ clientDir = join22(moduleDir, "..", "..", "src", "web", "client");
16629
17262
  }
16630
- if (!existsSync26(clientDir)) {
16631
- clientDir = join20(process.cwd(), "src", "web", "client");
17263
+ if (!existsSync28(clientDir)) {
17264
+ clientDir = join22(process.cwd(), "src", "web", "client");
16632
17265
  }
16633
17266
  console.log(` Static files: ${clientDir}`);
16634
- app.use("/vendor", express.static(join20(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
17267
+ app.use("/vendor", express.static(join22(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
16635
17268
  app.use(express.static(clientDir));
16636
17269
  app.get("/api/status", (_req, res) => {
16637
17270
  res.json({
@@ -16698,10 +17331,10 @@ async function startWebServer(options = {}) {
16698
17331
  app.get("/api/files", requireAuth, (req, res) => {
16699
17332
  const cwd = process.cwd();
16700
17333
  const prefix = req.query.prefix || "";
16701
- const targetDir = join20(cwd, prefix);
17334
+ const targetDir = join22(cwd, prefix);
16702
17335
  try {
16703
- const canonicalTarget = realpathSync(resolve8(targetDir));
16704
- const canonicalCwd = realpathSync(resolve8(cwd));
17336
+ const canonicalTarget = realpathSync(resolve9(targetDir));
17337
+ const canonicalCwd = realpathSync(resolve9(cwd));
16705
17338
  if (!canonicalTarget.startsWith(canonicalCwd + sep3) && canonicalTarget !== canonicalCwd) {
16706
17339
  res.json({ files: [] });
16707
17340
  return;
@@ -16712,10 +17345,10 @@ async function startWebServer(options = {}) {
16712
17345
  }
16713
17346
  try {
16714
17347
  const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", "__pycache__", ".next", ".nuxt", "coverage", ".cache"]);
16715
- const entries = readdirSync12(targetDir, { withFileTypes: true });
17348
+ const entries = readdirSync13(targetDir, { withFileTypes: true });
16716
17349
  const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
16717
17350
  name: e.name,
16718
- path: relative4(cwd, join20(targetDir, e.name)).replace(/\\/g, "/"),
17351
+ path: relative4(cwd, join22(targetDir, e.name)).replace(/\\/g, "/"),
16719
17352
  isDir: e.isDirectory()
16720
17353
  }));
16721
17354
  res.json({ files });
@@ -16751,8 +17384,8 @@ async function startWebServer(options = {}) {
16751
17384
  try {
16752
17385
  const authUser = req._authUser;
16753
17386
  const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
16754
- const filePath = join20(histDir, `${id}.json`);
16755
- if (!existsSync26(filePath)) {
17387
+ const filePath = join22(histDir, `${id}.json`);
17388
+ if (!existsSync28(filePath)) {
16756
17389
  res.status(404).json({ error: "Session not found" });
16757
17390
  return;
16758
17391
  }
@@ -16767,7 +17400,7 @@ async function startWebServer(options = {}) {
16767
17400
  res.status(404).json({ error: "Session not found" });
16768
17401
  return;
16769
17402
  }
16770
- const data = JSON.parse(readFileSync20(filePath, "utf-8"));
17403
+ const data = JSON.parse(readFileSync22(filePath, "utf-8"));
16771
17404
  res.json({ session: data });
16772
17405
  } catch (err) {
16773
17406
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
@@ -16780,10 +17413,10 @@ async function startWebServer(options = {}) {
16780
17413
  return;
16781
17414
  }
16782
17415
  const cwd = process.cwd();
16783
- const fullPath = resolve8(join20(cwd, filePath));
17416
+ const fullPath = resolve9(join22(cwd, filePath));
16784
17417
  try {
16785
17418
  const canonicalFull = realpathSync(fullPath);
16786
- const canonicalCwd = realpathSync(resolve8(cwd));
17419
+ const canonicalCwd = realpathSync(resolve9(cwd));
16787
17420
  if (!canonicalFull.startsWith(canonicalCwd + sep3) && canonicalFull !== canonicalCwd) {
16788
17421
  res.json({ error: "Access denied" });
16789
17422
  return;
@@ -16798,7 +17431,7 @@ async function startWebServer(options = {}) {
16798
17431
  res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
16799
17432
  return;
16800
17433
  }
16801
- const content = readFileSync20(fullPath, "utf-8");
17434
+ const content = readFileSync22(fullPath, "utf-8");
16802
17435
  res.json({ content, size: stat.size });
16803
17436
  } catch {
16804
17437
  res.json({ error: "Cannot read file" });
@@ -17002,7 +17635,7 @@ async function startWebServer(options = {}) {
17002
17635
  });
17003
17636
  const MAX_PORT_ATTEMPTS = 10;
17004
17637
  let actualPort = port;
17005
- const result = await new Promise((resolve9, reject) => {
17638
+ const result = await new Promise((resolve10, reject) => {
17006
17639
  const tryListen = (attempt) => {
17007
17640
  server.once("error", (err) => {
17008
17641
  if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
@@ -17033,7 +17666,7 @@ async function startWebServer(options = {}) {
17033
17666
  }
17034
17667
  console.log(` Press Ctrl+C to stop
17035
17668
  `);
17036
- resolve9({ port: actualPort, host, url });
17669
+ resolve10({ port: actualPort, host, url });
17037
17670
  });
17038
17671
  };
17039
17672
  tryListen(1);
@@ -17053,17 +17686,17 @@ function resolveProjectMcpPath() {
17053
17686
  const cwd = process.cwd();
17054
17687
  const gitRoot = getGitRoot(cwd);
17055
17688
  const projectRoot = gitRoot ?? cwd;
17056
- const configPath = join20(projectRoot, MCP_PROJECT_CONFIG_NAME);
17057
- return existsSync26(configPath) ? configPath : null;
17689
+ const configPath = join22(projectRoot, MCP_PROJECT_CONFIG_NAME);
17690
+ return existsSync28(configPath) ? configPath : null;
17058
17691
  }
17059
17692
  function loadProjectMcpConfig() {
17060
17693
  const cwd = process.cwd();
17061
17694
  const gitRoot = getGitRoot(cwd);
17062
17695
  const projectRoot = gitRoot ?? cwd;
17063
- const configPath = join20(projectRoot, MCP_PROJECT_CONFIG_NAME);
17064
- if (!existsSync26(configPath)) return null;
17696
+ const configPath = join22(projectRoot, MCP_PROJECT_CONFIG_NAME);
17697
+ if (!existsSync28(configPath)) return null;
17065
17698
  try {
17066
- const raw = JSON.parse(readFileSync20(configPath, "utf-8"));
17699
+ const raw = JSON.parse(readFileSync22(configPath, "utf-8"));
17067
17700
  return raw.mcpServers ?? raw;
17068
17701
  } catch {
17069
17702
  return null;