jinzd-ai-cli 0.4.210 → 0.4.211

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-HZQVX7VF.js";
40
+ } from "./chunk-ZY2N2N6T.js";
41
41
  import {
42
42
  hasSemanticIndex,
43
43
  semanticSearch
@@ -60,7 +60,7 @@ import {
60
60
  import express from "express";
61
61
  import { createServer } from "http";
62
62
  import { WebSocketServer } from "ws";
63
- import { join as join19, dirname as dirname6, resolve as resolve7, relative as relative4, sep as sep3 } from "path";
63
+ import { join as join19, dirname as dirname6, resolve as resolve8, relative as relative4, sep as sep3 } from "path";
64
64
  import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync12, statSync as statSync10, realpathSync } from "fs";
65
65
  import { networkInterfaces } from "os";
66
66
 
@@ -213,7 +213,41 @@ var ConfigSchema = z.object({
213
213
  preToolExecution: z.string().optional(),
214
214
  postToolExecution: z.string().optional()
215
215
  }).optional(),
216
- // 工具权限规则(按顺序匹配第一个生效)
216
+ // 网络访问治理(v0.4.211+):默认关闭以保持兼容;开启后统一治理 web/search/MCP/shell 网络出口。
217
+ networkPolicy: z.object({
218
+ enabled: z.boolean().default(false),
219
+ defaultAction: z.enum(["allow", "confirm", "deny"]).default("confirm"),
220
+ allowDomains: z.array(z.string()).default([]),
221
+ denyDomains: z.array(z.string()).default([]),
222
+ allowPorts: z.array(z.number().int().min(1).max(65535)).default([]),
223
+ denyPorts: z.array(z.number().int().min(1).max(65535)).default([]),
224
+ allowPrivateNetwork: z.boolean().default(false),
225
+ tools: z.object({
226
+ web_fetch: z.enum(["allow", "confirm", "deny"]).optional(),
227
+ web_search: z.enum(["allow", "confirm", "deny"]).optional(),
228
+ google_search: z.enum(["allow", "confirm", "deny"]).optional(),
229
+ shell: z.enum(["allow", "confirm", "deny"]).optional(),
230
+ mcp: z.enum(["allow", "confirm", "deny"]).optional()
231
+ }).default({})
232
+ }).default({ enabled: false, defaultAction: "confirm", allowDomains: [], denyDomains: [], allowPorts: [], denyPorts: [], allowPrivateNetwork: false, tools: {} }),
233
+ // 权限 Profile(v0.4.211+):legacy 保持旧行为;其余 profile 提供更清晰的安全边界。
234
+ defaultPermissionProfile: z.enum(["legacy", "read-only", "workspace-write", "danger-full-access"]).default("legacy"),
235
+ allowedPermissionProfiles: z.array(z.string()).default(["legacy", "read-only", "workspace-write", "danger-full-access"]),
236
+ permissionProfiles: z.record(z.object({
237
+ extends: z.enum(["legacy", "read-only", "workspace-write", "danger-full-access"]).optional(),
238
+ defaultAction: z.enum(["auto-approve", "deny", "confirm"]).optional(),
239
+ workspaceRoots: z.array(z.string()).optional(),
240
+ allowTemp: z.boolean().optional(),
241
+ rules: z.array(z.object({
242
+ tool: z.string(),
243
+ action: z.enum(["auto-approve", "deny", "confirm"]),
244
+ when: z.object({
245
+ dangerLevel: z.enum(["safe", "write", "destructive"]).optional(),
246
+ pathPattern: z.string().optional()
247
+ }).optional()
248
+ })).optional()
249
+ })).default({}),
250
+ // 工具权限规则(按顺序匹配第一个生效;作为当前权限 Profile 内的细粒度规则)
217
251
  permissionRules: z.array(z.object({
218
252
  tool: z.string(),
219
253
  action: z.enum(["auto-approve", "deny", "confirm"]),
@@ -222,7 +256,7 @@ var ConfigSchema = z.object({
222
256
  pathPattern: z.string().optional()
223
257
  }).optional()
224
258
  })).default([]),
225
- // 无规则匹配时的默认权限动作
259
+ // 无规则匹配时的默认权限动作(legacy profile 兼容旧行为)
226
260
  defaultPermission: z.enum(["auto-approve", "deny", "confirm"]).default("confirm"),
227
261
  // 自动上下文压缩开关
228
262
  // 当对话估算 token 数超过模型 contextWindow 的 80% 时,自动触发 compact 压缩旧消息
@@ -5733,6 +5767,7 @@ import { dirname as dirname3 } from "path";
5733
5767
  // src/tools/executor.ts
5734
5768
  import chalk3 from "chalk";
5735
5769
  import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
5770
+ import { tmpdir } from "os";
5736
5771
 
5737
5772
  // src/core/readline-internal.ts
5738
5773
  function rlInternal(rl) {
@@ -5967,6 +6002,7 @@ function runHook(template, vars) {
5967
6002
  }
5968
6003
 
5969
6004
  // src/tools/permissions.ts
6005
+ import { isAbsolute, resolve as resolve3 } from "path";
5970
6006
  function checkPermission(toolName, args, dangerLevel, rules, defaultAction = "confirm") {
5971
6007
  for (const rule of rules) {
5972
6008
  if (rule.tool !== "*" && rule.tool !== toolName) continue;
@@ -5987,6 +6023,225 @@ function checkPermission(toolName, args, dangerLevel, rules, defaultAction = "co
5987
6023
  }
5988
6024
  return defaultAction;
5989
6025
  }
6026
+ var BUILTIN_PROFILE_NAMES = /* @__PURE__ */ new Set(["legacy", "read-only", "workspace-write", "danger-full-access"]);
6027
+ var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
6028
+ "read_file",
6029
+ "list_dir",
6030
+ "grep_files",
6031
+ "glob_files",
6032
+ "find_symbol",
6033
+ "get_outline",
6034
+ "find_references",
6035
+ "search_code",
6036
+ "git_status",
6037
+ "git_diff",
6038
+ "git_log",
6039
+ "task_list",
6040
+ "web_fetch",
6041
+ "web_search",
6042
+ "google_search",
6043
+ "ask_user",
6044
+ "write_todos",
6045
+ "recall_memory"
6046
+ ]);
6047
+ function normalizePathForPermission(value) {
6048
+ return value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
6049
+ }
6050
+ function isInsidePath(root, target) {
6051
+ const absoluteTarget = isAbsolute(target) ? target : resolve3(root, target);
6052
+ const r = normalizePathForPermission(resolve3(root));
6053
+ const t = normalizePathForPermission(absoluteTarget);
6054
+ return t === r || t.startsWith(`${r}/`);
6055
+ }
6056
+ function extractPathArg(toolName, args) {
6057
+ const raw = args["path"] ?? args["filePath"] ?? args["notebook_path"];
6058
+ if (typeof raw === "string" && raw.trim()) return raw.trim();
6059
+ if (toolName === "save_last_response") {
6060
+ const output = args["outputPath"] ?? args["file"] ?? args["path"];
6061
+ if (typeof output === "string" && output.trim()) return output.trim();
6062
+ }
6063
+ return null;
6064
+ }
6065
+ function isExplicitFileWriteTool(toolName) {
6066
+ return toolName === "write_file" || toolName === "edit_file" || toolName === "notebook_edit" || toolName === "save_last_response";
6067
+ }
6068
+ function resolveProfileName(ctx) {
6069
+ const requested = ctx?.profileName ?? "legacy";
6070
+ if (ctx?.allowedProfiles && ctx.allowedProfiles.length > 0 && !ctx.allowedProfiles.includes(requested)) {
6071
+ return "legacy";
6072
+ }
6073
+ if (BUILTIN_PROFILE_NAMES.has(requested)) return requested;
6074
+ const ext = ctx?.profile?.extends;
6075
+ return ext && BUILTIN_PROFILE_NAMES.has(ext) ? ext : "legacy";
6076
+ }
6077
+ function getProfileRules(ctx) {
6078
+ return ctx?.profile?.rules ?? [];
6079
+ }
6080
+ function getProfileDefaultAction(name, ctx) {
6081
+ if (ctx?.profile?.defaultAction) return ctx.profile.defaultAction;
6082
+ if (name === "danger-full-access") return "auto-approve";
6083
+ return "confirm";
6084
+ }
6085
+ function getWorkspaceRoots(ctx) {
6086
+ const roots = [ctx?.workspaceRoot, ...ctx?.profile?.workspaceRoots ?? []].filter((v) => typeof v === "string" && v.trim().length > 0);
6087
+ return roots;
6088
+ }
6089
+ function getAllowedWriteRoots(ctx) {
6090
+ const roots = [...getWorkspaceRoots(ctx)];
6091
+ const allowTemp = ctx?.profile?.allowTemp !== false;
6092
+ if (allowTemp) roots.push(...ctx?.tempDirs ?? []);
6093
+ return roots.filter((v) => v.trim().length > 0);
6094
+ }
6095
+ function profileHardDecision(profileName, toolName, args, dangerLevel, ctx) {
6096
+ if (profileName === "legacy") return null;
6097
+ if (profileName === "read-only") {
6098
+ const mcpReadOnly = toolName.startsWith("mcp__") && dangerLevel === "safe";
6099
+ if (dangerLevel !== "safe" || !READ_ONLY_TOOLS.has(toolName) && !mcpReadOnly) {
6100
+ return { action: "deny", reason: "blocked by read-only permission profile", profileName };
6101
+ }
6102
+ return null;
6103
+ }
6104
+ if (profileName === "workspace-write") {
6105
+ if (dangerLevel === "destructive") return { action: "confirm", reason: "destructive tools always require confirmation", profileName };
6106
+ if (dangerLevel === "write" && isExplicitFileWriteTool(toolName)) {
6107
+ const target = extractPathArg(toolName, args);
6108
+ if (!target) return { action: "confirm", reason: "file write target is not explicit", profileName };
6109
+ const allowedRoots = getAllowedWriteRoots(ctx);
6110
+ if (allowedRoots.length === 0 || !allowedRoots.some((root) => isInsidePath(root, target))) {
6111
+ return { action: "deny", reason: "file write target is outside workspace/temp roots", profileName };
6112
+ }
6113
+ }
6114
+ return null;
6115
+ }
6116
+ if (profileName === "danger-full-access") {
6117
+ if (dangerLevel === "destructive") return { action: "confirm", reason: "destructive tools still require confirmation", profileName };
6118
+ return null;
6119
+ }
6120
+ return null;
6121
+ }
6122
+ function checkPermissionWithProfile(toolName, args, dangerLevel, rules, defaultAction = "confirm", profileContext) {
6123
+ const profileName = resolveProfileName(profileContext);
6124
+ const hard = profileHardDecision(profileName, toolName, args, dangerLevel, profileContext);
6125
+ if (hard?.action === "deny") return hard;
6126
+ const combinedRules = [...getProfileRules(profileContext), ...rules];
6127
+ const profileDefault = profileName === "legacy" ? defaultAction : getProfileDefaultAction(profileName, profileContext);
6128
+ const action = checkPermission(toolName, args, dangerLevel, combinedRules, profileDefault);
6129
+ if (hard?.action === "confirm" && action === "auto-approve") return hard;
6130
+ return { action, profileName };
6131
+ }
6132
+ function formatPermissionProfileWarning(profileName) {
6133
+ if (profileName === "danger-full-access") {
6134
+ return "\u26A0 danger-full-access permission profile is active. Write tools may auto-run; destructive tools still require confirmation.";
6135
+ }
6136
+ if (profileName === "read-only") return "Read-only permission profile is active. Write and destructive tools are blocked.";
6137
+ if (profileName === "workspace-write") return "Workspace-write permission profile is active. Writes outside workspace/temp roots are blocked.";
6138
+ return null;
6139
+ }
6140
+ var NETWORK_TOOL_HOSTS = {
6141
+ web_search: ["cn.bing.com", "www.google.com"],
6142
+ google_search: ["www.googleapis.com"]
6143
+ };
6144
+ var NETWORK_TOOL_PORTS = {
6145
+ web_search: [443],
6146
+ google_search: [443]
6147
+ };
6148
+ function networkActionToPermission(action) {
6149
+ if (action === "allow") return null;
6150
+ return action;
6151
+ }
6152
+ function domainMatches(pattern, host) {
6153
+ const p = pattern.trim().toLowerCase().replace(/^\*\./, "");
6154
+ const h = host.trim().toLowerCase().replace(/:\d+$/, "");
6155
+ return h === p || h.endsWith(`.${p}`);
6156
+ }
6157
+ function isPrivateNetworkHost(host) {
6158
+ const h = host.toLowerCase().replace(/^\[|\]$/g, "");
6159
+ if (h === "" || h === "localhost") return true;
6160
+ if (/^(127|10)\./.test(h)) return true;
6161
+ if (/^192\.168\./.test(h)) return true;
6162
+ if (/^172\.(1[6-9]|2\d|3[0-1])\./.test(h)) return true;
6163
+ if (/^169\.254\./.test(h)) return true;
6164
+ if (h === "::1" || h === "::") return true;
6165
+ if (/^f[cd][0-9a-f]*:/i.test(h) || /^fe[89ab][0-9a-f]*:/i.test(h)) return true;
6166
+ return false;
6167
+ }
6168
+ function extractNetworkHosts(toolName, args) {
6169
+ if (toolName === "web_fetch") {
6170
+ const raw = String(args["url"] ?? "").trim();
6171
+ try {
6172
+ return raw ? [new URL(raw).hostname] : [];
6173
+ } catch {
6174
+ return [];
6175
+ }
6176
+ }
6177
+ return NETWORK_TOOL_HOSTS[toolName] ?? [];
6178
+ }
6179
+ function extractNetworkPorts(toolName, args) {
6180
+ if (toolName === "web_fetch") {
6181
+ const raw = String(args["url"] ?? "").trim();
6182
+ try {
6183
+ if (!raw) return [];
6184
+ const url = new URL(raw);
6185
+ if (url.port) return [Number(url.port)];
6186
+ return [url.protocol === "http:" ? 80 : 443];
6187
+ } catch {
6188
+ return [];
6189
+ }
6190
+ }
6191
+ return NETWORK_TOOL_PORTS[toolName] ?? [];
6192
+ }
6193
+ function isShellNetworkCommand(command) {
6194
+ return /\b(curl|wget|aria2c|Invoke-WebRequest|Invoke-RestMethod|iwr|irm|ssh|scp|sftp|ftp|telnet|nc|ncat|netcat)\b/i.test(command) || /\b(git\s+(clone|fetch|pull|push)|npm\s+(install|publish|view)|pnpm\s+(add|install)|yarn\s+(add|install)|pip\s+install|python\s+-m\s+pip\s+install)\b/i.test(command);
6195
+ }
6196
+ function networkToolAction(toolName, policy) {
6197
+ if (toolName === "web_fetch") return policy.tools?.web_fetch ?? policy.defaultAction ?? "confirm";
6198
+ if (toolName === "web_search") return policy.tools?.web_search ?? policy.defaultAction ?? "confirm";
6199
+ if (toolName === "google_search") return policy.tools?.google_search ?? policy.defaultAction ?? "confirm";
6200
+ if (toolName.startsWith("mcp__")) return policy.tools?.mcp ?? policy.defaultAction ?? "confirm";
6201
+ return null;
6202
+ }
6203
+ function checkNetworkPolicy(toolName, args, policy) {
6204
+ if (!policy?.enabled) return null;
6205
+ let action = networkToolAction(toolName, policy);
6206
+ let reason = "network access requires confirmation";
6207
+ if (toolName === "bash") {
6208
+ const command = String(args["command"] ?? "");
6209
+ if (!isShellNetworkCommand(command)) return null;
6210
+ action = policy.tools?.shell ?? policy.defaultAction ?? "confirm";
6211
+ reason = "shell command may access the network";
6212
+ }
6213
+ if (!action) return null;
6214
+ const hosts = extractNetworkHosts(toolName, args);
6215
+ for (const host of hosts) {
6216
+ if (policy.denyDomains?.some((pattern) => domainMatches(pattern, host))) {
6217
+ return { action: "deny", reason: `network host "${host}" is denied by networkPolicy`, profileName: "networkPolicy" };
6218
+ }
6219
+ if (!policy.allowPrivateNetwork && isPrivateNetworkHost(host)) {
6220
+ return { action: "deny", reason: `network host "${host}" is private/internal`, profileName: "networkPolicy" };
6221
+ }
6222
+ }
6223
+ if (hosts.length > 0 && policy.allowDomains && policy.allowDomains.length > 0) {
6224
+ const allAllowed = hosts.every((host) => policy.allowDomains.some((pattern) => domainMatches(pattern, host)));
6225
+ if (!allAllowed) {
6226
+ return { action: "deny", reason: `network host is not in networkPolicy.allowDomains`, profileName: "networkPolicy" };
6227
+ }
6228
+ }
6229
+ const ports = extractNetworkPorts(toolName, args);
6230
+ for (const port of ports) {
6231
+ if (policy.denyPorts?.includes(port)) {
6232
+ return { action: "deny", reason: `network port ${port} is denied by networkPolicy`, profileName: "networkPolicy" };
6233
+ }
6234
+ }
6235
+ if (ports.length > 0 && policy.allowPorts && policy.allowPorts.length > 0) {
6236
+ const allPortsAllowed = ports.every((port) => policy.allowPorts.includes(port));
6237
+ if (!allPortsAllowed) {
6238
+ return { action: "deny", reason: `network port is not in networkPolicy.allowPorts`, profileName: "networkPolicy" };
6239
+ }
6240
+ }
6241
+ const permissionAction = networkActionToPermission(action);
6242
+ if (!permissionAction) return null;
6243
+ return { action: permissionAction, reason, profileName: "networkPolicy" };
6244
+ }
5990
6245
 
5991
6246
  // src/tools/truncate.ts
5992
6247
  var DEFAULT_MAX_TOOL_OUTPUT_CHARS = 12e3;
@@ -6289,11 +6544,21 @@ var ToolExecutor = class {
6289
6544
  /** 权限规则(可选) */
6290
6545
  permissionRules = [];
6291
6546
  defaultPermission = "confirm";
6547
+ permissionProfileName = "legacy";
6548
+ permissionProfile;
6549
+ allowedPermissionProfiles = [];
6550
+ workspaceRoot = process.cwd();
6551
+ networkPolicy;
6292
6552
  /** 注入 hooks 和 permission rules 配置 */
6293
6553
  setConfig(opts) {
6294
6554
  this.hookConfig = opts.hookConfig;
6295
6555
  if (opts.permissionRules) this.permissionRules = opts.permissionRules;
6296
6556
  if (opts.defaultPermission) this.defaultPermission = opts.defaultPermission;
6557
+ if (opts.permissionProfileName) this.permissionProfileName = opts.permissionProfileName;
6558
+ if (opts.permissionProfile) this.permissionProfile = opts.permissionProfile;
6559
+ if (opts.allowedPermissionProfiles) this.allowedPermissionProfiles = opts.allowedPermissionProfiles;
6560
+ if (opts.workspaceRoot) this.workspaceRoot = opts.workspaceRoot;
6561
+ if (opts.networkPolicy) this.networkPolicy = opts.networkPolicy;
6297
6562
  }
6298
6563
  async execute(call) {
6299
6564
  return runWithSessionKey(this.sessionKey, () => this.executeInner(call));
@@ -6308,31 +6573,69 @@ var ToolExecutor = class {
6308
6573
  };
6309
6574
  }
6310
6575
  const dangerLevel = getDangerLevel(call.name, call.arguments);
6576
+ let toolCallAlreadyPrinted = false;
6311
6577
  runHook(this.hookConfig?.preToolExecution, {
6312
6578
  tool: call.name,
6313
6579
  dangerLevel,
6314
6580
  args: JSON.stringify(call.arguments).slice(0, 200)
6315
6581
  });
6316
- if (this.permissionRules.length > 0) {
6317
- const action = checkPermission(call.name, call.arguments, dangerLevel, this.permissionRules, this.defaultPermission);
6318
- if (action === "deny") {
6319
- return { callId: call.id, content: `[Permission denied] Tool ${call.name} is blocked by permission rules. Do not retry.`, isError: true };
6582
+ const permission = checkPermissionWithProfile(
6583
+ call.name,
6584
+ call.arguments,
6585
+ dangerLevel,
6586
+ this.permissionRules,
6587
+ this.defaultPermission,
6588
+ {
6589
+ profileName: this.permissionProfileName,
6590
+ profile: this.permissionProfile,
6591
+ allowedProfiles: this.allowedPermissionProfiles,
6592
+ workspaceRoot: this.workspaceRoot,
6593
+ tempDirs: [tmpdir()]
6320
6594
  }
6321
- if (action === "auto-approve") {
6322
- this.printToolCall(call);
6323
- try {
6324
- const rawContent = await runTool(tool, call.arguments, call.name);
6325
- const content = truncateOutput(rawContent, call.name);
6326
- const wasTruncated = content !== rawContent;
6327
- this.printToolResult(call.name, rawContent, false, wasTruncated);
6328
- runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
6329
- return { callId: call.id, content, isError: false };
6330
- } catch (err) {
6331
- const message = err instanceof Error ? err.message : String(err);
6332
- this.printToolResult(call.name, message, true, false);
6333
- runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
6334
- return { callId: call.id, content: message, isError: true };
6335
- }
6595
+ );
6596
+ const networkPermission = checkNetworkPolicy(call.name, call.arguments, this.networkPolicy);
6597
+ if (networkPermission?.action === "deny") {
6598
+ const reason = networkPermission.reason ? ` (${networkPermission.reason})` : "";
6599
+ return {
6600
+ callId: call.id,
6601
+ content: `[Permission denied] Tool ${call.name} is blocked by networkPolicy${reason}. Do not retry.`,
6602
+ isError: true
6603
+ };
6604
+ }
6605
+ if (permission.action === "deny") {
6606
+ const reason = permission.reason ? ` (${permission.reason})` : "";
6607
+ return {
6608
+ callId: call.id,
6609
+ content: `[Permission denied] Tool ${call.name} is blocked by permission profile "${permission.profileName}"${reason}. Do not retry.`,
6610
+ isError: true
6611
+ };
6612
+ }
6613
+ if (permission.action === "auto-approve" && networkPermission?.action !== "confirm") {
6614
+ this.printToolCall(call);
6615
+ try {
6616
+ const rawContent = await runTool(tool, call.arguments, call.name);
6617
+ const content = truncateOutput(rawContent, call.name);
6618
+ const wasTruncated = content !== rawContent;
6619
+ this.printToolResult(call.name, rawContent, false, wasTruncated);
6620
+ runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
6621
+ return { callId: call.id, content, isError: false };
6622
+ } catch (err) {
6623
+ const message = err instanceof Error ? err.message : String(err);
6624
+ this.printToolResult(call.name, message, true, false);
6625
+ runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
6626
+ return { callId: call.id, content: message, isError: true };
6627
+ }
6628
+ }
6629
+ if (networkPermission?.action === "confirm" && dangerLevel === "safe") {
6630
+ this.printToolCall(call);
6631
+ toolCallAlreadyPrinted = true;
6632
+ const confirmed = await this.confirm(call, "write");
6633
+ if (!confirmed) {
6634
+ return {
6635
+ callId: call.id,
6636
+ content: `[User cancelled] The user declined network access for ${call.name}. Do not retry without asking.`,
6637
+ isError: true
6638
+ };
6336
6639
  }
6337
6640
  }
6338
6641
  if (this.sessionAutoApprove && dangerLevel === "write") {
@@ -6422,6 +6725,9 @@ var ToolExecutor = class {
6422
6725
  * 然后让用户 approve all / reject all / 选择性 approve。
6423
6726
  */
6424
6727
  async executeBatchFileWrites(calls) {
6728
+ if (this.permissionProfileName !== "legacy" || this.permissionRules.length > 0) {
6729
+ return Promise.all(calls.map((call) => this.execute(call)));
6730
+ }
6425
6731
  console.log();
6426
6732
  console.log(theme.heading(`\u270E Batch file writes (${calls.length} files):`));
6427
6733
  console.log(theme.dim("\u2500".repeat(50)));
@@ -6495,7 +6801,7 @@ var ToolExecutor = class {
6495
6801
  rl.resume();
6496
6802
  process.stdout.write(prompt);
6497
6803
  this.confirming = true;
6498
- return new Promise((resolve8) => {
6804
+ return new Promise((resolve9) => {
6499
6805
  let completed = false;
6500
6806
  const cleanup = (result) => {
6501
6807
  if (completed) return;
@@ -6505,7 +6811,7 @@ var ToolExecutor = class {
6505
6811
  rl.pause();
6506
6812
  rlAny.output = savedOutput;
6507
6813
  this.confirming = false;
6508
- resolve8(result);
6814
+ resolve9(result);
6509
6815
  };
6510
6816
  const onLine = (line) => {
6511
6817
  const trimmed = line.trim();
@@ -6679,7 +6985,7 @@ var ToolExecutor = class {
6679
6985
  rl.resume();
6680
6986
  process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
6681
6987
  this.confirming = true;
6682
- return new Promise((resolve8) => {
6988
+ return new Promise((resolve9) => {
6683
6989
  let completed = false;
6684
6990
  const cleanup = (answer) => {
6685
6991
  if (completed) return;
@@ -6689,7 +6995,7 @@ var ToolExecutor = class {
6689
6995
  rl.pause();
6690
6996
  rlAny.output = savedOutput;
6691
6997
  this.confirming = false;
6692
- resolve8(answer === "y");
6998
+ resolve9(answer === "y");
6693
6999
  };
6694
7000
  const onLine = (line) => {
6695
7001
  const trimmed = line.trim();
@@ -6717,11 +7023,11 @@ var ToolExecutor = class {
6717
7023
  };
6718
7024
 
6719
7025
  // src/tools/sensitive-paths.ts
6720
- import { resolve as resolve3, sep as sep2, basename as basename2 } from "path";
7026
+ import { resolve as resolve4, sep as sep2, basename as basename2 } from "path";
6721
7027
  import { homedir as homedir4 } from "os";
6722
7028
  var home = homedir4();
6723
7029
  function norm(p) {
6724
- const abs = resolve3(p);
7030
+ const abs = resolve4(p);
6725
7031
  return process.platform === "win32" ? abs.toLowerCase() : abs;
6726
7032
  }
6727
7033
  function homeRel(p) {
@@ -7938,7 +8244,7 @@ var runInteractiveTool = {
7938
8244
  PYTHONDONTWRITEBYTECODE: "1"
7939
8245
  };
7940
8246
  const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
7941
- return new Promise((resolve8) => {
8247
+ return new Promise((resolve9) => {
7942
8248
  const child = spawn2(executable, cmdArgs.map(String), {
7943
8249
  cwd: process.cwd(),
7944
8250
  env,
@@ -7971,22 +8277,22 @@ var runInteractiveTool = {
7971
8277
  setTimeout(writeNextLine, 400);
7972
8278
  const timer = setTimeout(() => {
7973
8279
  child.kill();
7974
- resolve8(`${prefixWarnings}[Timeout after ${timeout}ms]
8280
+ resolve9(`${prefixWarnings}[Timeout after ${timeout}ms]
7975
8281
  ${buildOutput(stdout, stderr)}`);
7976
8282
  }, timeout);
7977
8283
  child.on("close", (code) => {
7978
8284
  clearTimeout(timer);
7979
8285
  const output = buildOutput(stdout, stderr);
7980
8286
  if (code !== 0 && code !== null) {
7981
- resolve8(`${prefixWarnings}Exit code ${code}:
8287
+ resolve9(`${prefixWarnings}Exit code ${code}:
7982
8288
  ${output}`);
7983
8289
  } else {
7984
- resolve8(`${prefixWarnings}${output || "(no output)"}`);
8290
+ resolve9(`${prefixWarnings}${output || "(no output)"}`);
7985
8291
  }
7986
8292
  });
7987
8293
  child.on("error", (err) => {
7988
8294
  clearTimeout(timer);
7989
- resolve8(
8295
+ resolve9(
7990
8296
  `${prefixWarnings}Failed to start process "${executable}": ${err.message}
7991
8297
  Hint: On Windows, use the full path to the executable, e.g.:
7992
8298
  C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
@@ -8738,7 +9044,7 @@ function promptUser(rl, question) {
8738
9044
  console.log();
8739
9045
  console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
8740
9046
  process.stdout.write(chalk4.cyan("> "));
8741
- return new Promise((resolve8) => {
9047
+ return new Promise((resolve9) => {
8742
9048
  let completed = false;
8743
9049
  const cleanup = (answer) => {
8744
9050
  if (completed) return;
@@ -8748,7 +9054,7 @@ function promptUser(rl, question) {
8748
9054
  rl.pause();
8749
9055
  rlAny.output = savedOutput;
8750
9056
  askUserContext.prompting = false;
8751
- resolve8(answer);
9057
+ resolve9(answer);
8752
9058
  };
8753
9059
  const onLine = (line) => {
8754
9060
  cleanup(line);
@@ -9759,7 +10065,7 @@ ${commitOutput.trim()}`;
9759
10065
  // src/tools/builtin/notebook-edit.ts
9760
10066
  import { readFileSync as readFileSync10, existsSync as existsSync14 } from "fs";
9761
10067
  import { writeFile } from "fs/promises";
9762
- import { resolve as resolve4, extname as extname2 } from "path";
10068
+ import { resolve as resolve5, extname as extname2 } from "path";
9763
10069
  var notebookEditTool = {
9764
10070
  definition: {
9765
10071
  name: "notebook_edit",
@@ -9808,7 +10114,7 @@ var notebookEditTool = {
9808
10114
  if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
9809
10115
  throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
9810
10116
  }
9811
- const absPath = resolve4(filePath);
10117
+ const absPath = resolve5(filePath);
9812
10118
  if (extname2(absPath).toLowerCase() !== ".ipynb") {
9813
10119
  throw new ToolError("notebook_edit", "path must point to a .ipynb file");
9814
10120
  }
@@ -10587,7 +10893,7 @@ var McpClient = class {
10587
10893
  // 内部方法:JSON-RPC 通信
10588
10894
  // ══════════════════════════════════════════════════════════════════
10589
10895
  sendRequest(method, params) {
10590
- return new Promise((resolve8, reject) => {
10896
+ return new Promise((resolve9, reject) => {
10591
10897
  if (!this.process?.stdin?.writable) {
10592
10898
  return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
10593
10899
  }
@@ -10611,7 +10917,7 @@ var McpClient = class {
10611
10917
  this.pendingRequests.set(id, {
10612
10918
  resolve: (result) => {
10613
10919
  cleanup();
10614
- resolve8(result);
10920
+ resolve9(result);
10615
10921
  },
10616
10922
  reject: (error) => {
10617
10923
  cleanup();
@@ -10688,13 +10994,13 @@ var McpClient = class {
10688
10994
  }
10689
10995
  /** Promise 超时包装 */
10690
10996
  withTimeout(promise, ms, label) {
10691
- return new Promise((resolve8, reject) => {
10997
+ return new Promise((resolve9, reject) => {
10692
10998
  const timer = setTimeout(() => {
10693
10999
  reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
10694
11000
  }, ms);
10695
11001
  promise.then((val) => {
10696
11002
  clearTimeout(timer);
10697
- resolve8(val);
11003
+ resolve9(val);
10698
11004
  }).catch((err) => {
10699
11005
  clearTimeout(timer);
10700
11006
  reject(err);
@@ -11126,6 +11432,7 @@ var SkillManager = class {
11126
11432
 
11127
11433
  // src/web/tool-executor-web.ts
11128
11434
  import { randomUUID as randomUUID2 } from "crypto";
11435
+ import { tmpdir as tmpdir2 } from "os";
11129
11436
  import { existsSync as existsSync17, readFileSync as readFileSync12 } from "fs";
11130
11437
  var ToolExecutorWeb = class _ToolExecutorWeb {
11131
11438
  constructor(registry, ws) {
@@ -11137,6 +11444,11 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11137
11444
  hookConfig;
11138
11445
  permissionRules = [];
11139
11446
  defaultPermission = "confirm";
11447
+ permissionProfileName = "legacy";
11448
+ permissionProfile;
11449
+ allowedPermissionProfiles = [];
11450
+ workspaceRoot = process.cwd();
11451
+ networkPolicy;
11140
11452
  /** Pending confirm promises keyed by requestId */
11141
11453
  pendingConfirms = /* @__PURE__ */ new Map();
11142
11454
  /** Pending batch confirm promises */
@@ -11171,6 +11483,11 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11171
11483
  this.hookConfig = opts.hookConfig;
11172
11484
  if (opts.permissionRules) this.permissionRules = opts.permissionRules;
11173
11485
  if (opts.defaultPermission) this.defaultPermission = opts.defaultPermission;
11486
+ if (opts.permissionProfileName) this.permissionProfileName = opts.permissionProfileName;
11487
+ if (opts.permissionProfile) this.permissionProfile = opts.permissionProfile;
11488
+ if (opts.allowedPermissionProfiles) this.allowedPermissionProfiles = opts.allowedPermissionProfiles;
11489
+ if (opts.workspaceRoot) this.workspaceRoot = opts.workspaceRoot;
11490
+ if (opts.networkPolicy) this.networkPolicy = opts.networkPolicy;
11174
11491
  }
11175
11492
  /** Clear M7 timeout timer for a requestId */
11176
11493
  clearPendingTimer(requestId) {
@@ -11182,33 +11499,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11182
11499
  }
11183
11500
  /** Resolve a pending confirm from client response */
11184
11501
  resolveConfirm(requestId, approved) {
11185
- const resolve8 = this.pendingConfirms.get(requestId);
11186
- if (resolve8) {
11502
+ const resolve9 = this.pendingConfirms.get(requestId);
11503
+ if (resolve9) {
11187
11504
  this.clearPendingTimer(requestId);
11188
11505
  this.pendingConfirms.delete(requestId);
11189
11506
  this.confirming = false;
11190
- resolve8(approved);
11507
+ resolve9(approved);
11191
11508
  }
11192
11509
  }
11193
11510
  /** Resolve a pending batch confirm from client response */
11194
11511
  resolveBatchConfirm(requestId, decision) {
11195
- const resolve8 = this.pendingBatchConfirms.get(requestId);
11196
- if (resolve8) {
11512
+ const resolve9 = this.pendingBatchConfirms.get(requestId);
11513
+ if (resolve9) {
11197
11514
  this.clearPendingTimer(requestId);
11198
11515
  this.pendingBatchConfirms.delete(requestId);
11199
11516
  this.confirming = false;
11200
11517
  if (decision === "all" || decision === "none") {
11201
- resolve8(decision);
11518
+ resolve9(decision);
11202
11519
  } else {
11203
- resolve8(new Set(decision));
11520
+ resolve9(new Set(decision));
11204
11521
  }
11205
11522
  }
11206
11523
  }
11207
11524
  /** Cancel all pending confirms (e.g., on disconnect) */
11208
11525
  cancelAll() {
11209
- for (const resolve8 of this.pendingConfirms.values()) resolve8(false);
11526
+ for (const resolve9 of this.pendingConfirms.values()) resolve9(false);
11210
11527
  this.pendingConfirms.clear();
11211
- for (const resolve8 of this.pendingBatchConfirms.values()) resolve8("none");
11528
+ for (const resolve9 of this.pendingBatchConfirms.values()) resolve9("none");
11212
11529
  this.pendingBatchConfirms.clear();
11213
11530
  this.confirming = false;
11214
11531
  }
@@ -11219,6 +11536,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11219
11536
  }
11220
11537
  sendToolCallStart(call) {
11221
11538
  const dangerLevel = getDangerLevel(call.name, call.arguments);
11539
+ let toolCallStarted = false;
11222
11540
  const startTime = Date.now();
11223
11541
  this.toolStartTimes.set(call.id, startTime);
11224
11542
  const msg = {
@@ -11284,8 +11602,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11284
11602
  diff: this.getDiffPreview(call)
11285
11603
  };
11286
11604
  this.send(msg);
11287
- return new Promise((resolve8) => {
11288
- this.pendingConfirms.set(requestId, resolve8);
11605
+ return new Promise((resolve9) => {
11606
+ this.pendingConfirms.set(requestId, resolve9);
11289
11607
  this.pendingTimers.set(requestId, setTimeout(() => {
11290
11608
  if (this.pendingConfirms.has(requestId)) {
11291
11609
  this.resolveConfirm(requestId, false);
@@ -11309,8 +11627,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11309
11627
  files
11310
11628
  };
11311
11629
  this.send(msg);
11312
- return new Promise((resolve8) => {
11313
- this.pendingBatchConfirms.set(requestId, resolve8);
11630
+ return new Promise((resolve9) => {
11631
+ this.pendingBatchConfirms.set(requestId, resolve9);
11314
11632
  this.pendingTimers.set(requestId, setTimeout(() => {
11315
11633
  if (this.pendingBatchConfirms.has(requestId)) {
11316
11634
  this.resolveBatchConfirm(requestId, "none");
@@ -11327,30 +11645,66 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11327
11645
  return { callId: call.id, content: `Unknown tool: ${call.name}`, isError: true };
11328
11646
  }
11329
11647
  const dangerLevel = getDangerLevel(call.name, call.arguments);
11648
+ let toolCallStarted = false;
11330
11649
  runHook(this.hookConfig?.preToolExecution, {
11331
11650
  tool: call.name,
11332
11651
  dangerLevel,
11333
11652
  args: JSON.stringify(call.arguments).slice(0, 200)
11334
11653
  });
11335
- if (this.permissionRules.length > 0) {
11336
- const action = checkPermission(call.name, call.arguments, dangerLevel, this.permissionRules, this.defaultPermission);
11337
- if (action === "deny") {
11338
- return { callId: call.id, content: `[Permission denied] Tool ${call.name} is blocked by permission rules. Do not retry.`, isError: true };
11654
+ const permission = checkPermissionWithProfile(
11655
+ call.name,
11656
+ call.arguments,
11657
+ dangerLevel,
11658
+ this.permissionRules,
11659
+ this.defaultPermission,
11660
+ {
11661
+ profileName: this.permissionProfileName,
11662
+ profile: this.permissionProfile,
11663
+ allowedProfiles: this.allowedPermissionProfiles,
11664
+ workspaceRoot: this.workspaceRoot,
11665
+ tempDirs: [tmpdir2()]
11339
11666
  }
11340
- if (action === "auto-approve") {
11341
- this.sendToolCallStart(call);
11342
- try {
11343
- const rawContent = await runTool(tool, call.arguments, call.name);
11344
- const content = truncateOutput(rawContent, call.name);
11345
- this.sendToolCallResult(call, rawContent, false);
11346
- runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
11347
- return { callId: call.id, content, isError: false };
11348
- } catch (err) {
11349
- const message = err instanceof Error ? err.message : String(err);
11350
- this.sendToolCallResult(call, message, true);
11351
- runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
11352
- return { callId: call.id, content: message, isError: true };
11353
- }
11667
+ );
11668
+ const networkPermission = checkNetworkPolicy(call.name, call.arguments, this.networkPolicy);
11669
+ if (networkPermission?.action === "deny") {
11670
+ const reason = networkPermission.reason ? ` (${networkPermission.reason})` : "";
11671
+ return {
11672
+ callId: call.id,
11673
+ content: `[Permission denied] Tool ${call.name} is blocked by networkPolicy${reason}. Do not retry.`,
11674
+ isError: true
11675
+ };
11676
+ }
11677
+ if (permission.action === "deny") {
11678
+ const reason = permission.reason ? ` (${permission.reason})` : "";
11679
+ return {
11680
+ callId: call.id,
11681
+ content: `[Permission denied] Tool ${call.name} is blocked by permission profile "${permission.profileName}"${reason}. Do not retry.`,
11682
+ isError: true
11683
+ };
11684
+ }
11685
+ if (permission.action === "auto-approve" && networkPermission?.action !== "confirm") {
11686
+ this.sendToolCallStart(call);
11687
+ try {
11688
+ const rawContent = await runTool(tool, call.arguments, call.name);
11689
+ const content = truncateOutput(rawContent, call.name);
11690
+ this.sendToolCallResult(call, rawContent, false);
11691
+ runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
11692
+ return { callId: call.id, content, isError: false };
11693
+ } catch (err) {
11694
+ const message = err instanceof Error ? err.message : String(err);
11695
+ this.sendToolCallResult(call, message, true);
11696
+ runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
11697
+ return { callId: call.id, content: message, isError: true };
11698
+ }
11699
+ }
11700
+ if (networkPermission?.action === "confirm" && dangerLevel === "safe") {
11701
+ this.sendToolCallStart(call);
11702
+ toolCallStarted = true;
11703
+ const confirmed = await this.confirm(call, "write");
11704
+ if (!confirmed) {
11705
+ const rejectionMsg = `[User cancelled] The user declined network access for ${call.name}. Do not retry without asking.`;
11706
+ this.sendToolCallResult(call, rejectionMsg, true);
11707
+ return { callId: call.id, content: rejectionMsg, isError: true };
11354
11708
  }
11355
11709
  }
11356
11710
  this.sendToolCallStart(call);
@@ -11409,6 +11763,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11409
11763
  return results;
11410
11764
  }
11411
11765
  async executeBatchFileWrites(items) {
11766
+ if (this.permissionProfileName !== "legacy" || this.permissionRules.length > 0) {
11767
+ return Promise.all(items.map(({ call }) => this.execute(call)));
11768
+ }
11412
11769
  const calls = items.map((i) => i.call);
11413
11770
  const decision = this.sessionAutoApprove ? "all" : await this.batchConfirm(calls);
11414
11771
  const results = new Array(calls.length);
@@ -11737,7 +12094,7 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
11737
12094
 
11738
12095
  // src/core/context-files.ts
11739
12096
  import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
11740
- import { join as join13, relative as relative3, resolve as resolve5 } from "path";
12097
+ import { join as join13, relative as relative3, resolve as resolve6 } from "path";
11741
12098
  function uniqueNonEmpty(values) {
11742
12099
  const seen = /* @__PURE__ */ new Set();
11743
12100
  const out = [];
@@ -11760,7 +12117,7 @@ function displayPath(filePath, cwd) {
11760
12117
  return filePath;
11761
12118
  }
11762
12119
  function isInsideOrEqual(parent, child) {
11763
- const rel = relative3(resolve5(parent), resolve5(child));
12120
+ const rel = relative3(resolve6(parent), resolve6(child));
11764
12121
  return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
11765
12122
  }
11766
12123
  function readContextFile(level, filePath, cwd, maxBytes) {
@@ -11823,7 +12180,7 @@ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
11823
12180
  return { layer: null, skipped };
11824
12181
  }
11825
12182
  function loadContextFiles(options) {
11826
- const cwd = resolve5(options.cwd);
12183
+ const cwd = resolve6(options.cwd);
11827
12184
  const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
11828
12185
  const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
11829
12186
  const skipped = [];
@@ -11831,7 +12188,7 @@ function loadContextFiles(options) {
11831
12188
  return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
11832
12189
  }
11833
12190
  if (options.setting && options.setting !== "auto") {
11834
- const filePath = resolve5(cwd, String(options.setting));
12191
+ const filePath = resolve6(cwd, String(options.setting));
11835
12192
  if (!isInsideOrEqual(cwd, filePath)) {
11836
12193
  skipped.push({
11837
12194
  level: "single",
@@ -11875,7 +12232,7 @@ function loadContextFiles(options) {
11875
12232
  skipped.push(...result.skipped);
11876
12233
  if (result.layer) layers.push(result.layer);
11877
12234
  }
11878
- if (resolve5(options.cwd) !== resolve5(options.projectRoot)) {
12235
+ if (resolve6(options.cwd) !== resolve6(options.projectRoot)) {
11879
12236
  const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
11880
12237
  skipped.push(...result.skipped);
11881
12238
  if (result.layer) layers.push(result.layer);
@@ -11894,7 +12251,7 @@ function loadContextFiles(options) {
11894
12251
 
11895
12252
  // src/web/session-handler.ts
11896
12253
  import { existsSync as existsSync23, readFileSync as readFileSync17, writeFileSync as writeFileSync2, mkdirSync as mkdirSync10, readdirSync as readdirSync10, statSync as statSync9, createWriteStream } from "fs";
11897
- import { join as join17, resolve as resolve6, dirname as dirname5 } from "path";
12254
+ import { join as join17, resolve as resolve7, dirname as dirname5 } from "path";
11898
12255
 
11899
12256
  // src/tools/git-context.ts
11900
12257
  import { execSync as execSync2 } from "child_process";
@@ -12953,7 +13310,22 @@ var SessionHandler = class {
12953
13310
  const hooks = this.config.get("hooks");
12954
13311
  const permissionRules = this.config.get("permissionRules");
12955
13312
  const defaultPermission = this.config.get("defaultPermission");
12956
- this.toolExecutor.setConfig({ hookConfig: hooks, permissionRules, defaultPermission });
13313
+ const permissionProfileName = this.config.get("defaultPermissionProfile") ?? "legacy";
13314
+ const permissionProfiles = this.config.get("permissionProfiles") ?? {};
13315
+ this.toolExecutor.setConfig({
13316
+ hookConfig: hooks,
13317
+ permissionRules,
13318
+ defaultPermission,
13319
+ permissionProfileName,
13320
+ permissionProfile: permissionProfiles[permissionProfileName],
13321
+ allowedPermissionProfiles: this.config.get("allowedPermissionProfiles") ?? [],
13322
+ workspaceRoot: process.cwd(),
13323
+ networkPolicy: this.config.get("networkPolicy")
13324
+ });
13325
+ const permissionProfileWarning = formatPermissionProfileWarning(permissionProfileName);
13326
+ if (permissionProfileWarning) {
13327
+ this.send({ type: "info", message: permissionProfileWarning });
13328
+ }
12957
13329
  this.sendStatus();
12958
13330
  askUserContext.rl = null;
12959
13331
  askUserContext.prompting = false;
@@ -12988,6 +13360,7 @@ var SessionHandler = class {
12988
13360
  messageCount: this.sessions.current?.messages.length ?? 0,
12989
13361
  planMode: this.planMode,
12990
13362
  thinkingMode: this.runtimeThinking ?? false,
13363
+ permissionProfile: this.config.get("defaultPermissionProfile") ?? "legacy",
12991
13364
  tokenUsage: { ...this.sessionTokenUsage },
12992
13365
  costUsd,
12993
13366
  providers: providerList,
@@ -13016,10 +13389,10 @@ var SessionHandler = class {
13016
13389
  return;
13017
13390
  }
13018
13391
  case "ask_user_response": {
13019
- const resolve8 = this.pendingAskUser.get(msg.requestId);
13020
- if (resolve8) {
13392
+ const resolve9 = this.pendingAskUser.get(msg.requestId);
13393
+ if (resolve9) {
13021
13394
  this.pendingAskUser.delete(msg.requestId);
13022
- resolve8(msg.answer);
13395
+ resolve9(msg.answer);
13023
13396
  }
13024
13397
  return;
13025
13398
  }
@@ -13030,10 +13403,10 @@ var SessionHandler = class {
13030
13403
  case "memory_rebuild":
13031
13404
  return this.handleMemoryRebuild(Boolean(msg.full));
13032
13405
  case "auto_pause_response": {
13033
- const resolve8 = this.pendingAutoPause.get(msg.requestId);
13034
- if (resolve8) {
13406
+ const resolve9 = this.pendingAutoPause.get(msg.requestId);
13407
+ if (resolve9) {
13035
13408
  this.pendingAutoPause.delete(msg.requestId);
13036
- resolve8({ action: msg.action, message: msg.message });
13409
+ resolve9({ action: msg.action, message: msg.message });
13037
13410
  }
13038
13411
  return;
13039
13412
  }
@@ -13048,10 +13421,10 @@ var SessionHandler = class {
13048
13421
  this.hubOrchestrator?.abort();
13049
13422
  return;
13050
13423
  case "hub_steer": {
13051
- const resolve8 = this.pendingHubReview.get(msg.requestId);
13052
- if (resolve8) {
13424
+ const resolve9 = this.pendingHubReview.get(msg.requestId);
13425
+ if (resolve9) {
13053
13426
  this.pendingHubReview.delete(msg.requestId);
13054
- resolve8({ action: msg.action, message: msg.message });
13427
+ resolve9({ action: msg.action, message: msg.message });
13055
13428
  }
13056
13429
  return;
13057
13430
  }
@@ -13063,9 +13436,9 @@ var SessionHandler = class {
13063
13436
  onDisconnect() {
13064
13437
  this.toolExecutor.cancelAll();
13065
13438
  if (this.abortController) this.abortController.abort();
13066
- for (const resolve8 of this.pendingAskUser.values()) resolve8(null);
13439
+ for (const resolve9 of this.pendingAskUser.values()) resolve9(null);
13067
13440
  this.pendingAskUser.clear();
13068
- for (const resolve8 of this.pendingAutoPause.values()) resolve8({ action: "stop" });
13441
+ for (const resolve9 of this.pendingAutoPause.values()) resolve9({ action: "stop" });
13069
13442
  this.pendingAutoPause.clear();
13070
13443
  this.saveIfNeeded();
13071
13444
  }
@@ -13199,9 +13572,9 @@ var SessionHandler = class {
13199
13572
  this.hubOrchestrator = orchestrator;
13200
13573
  orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
13201
13574
  if (config.humanSteer) {
13202
- orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve8) => {
13575
+ orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve9) => {
13203
13576
  const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
13204
- this.pendingHubReview.set(requestId, resolve8);
13577
+ this.pendingHubReview.set(requestId, resolve9);
13205
13578
  this.send({ type: "hub_review", requestId, round, maxRounds });
13206
13579
  });
13207
13580
  }
@@ -13538,8 +13911,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
13538
13911
  onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
13539
13912
  requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
13540
13913
  const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
13541
- const pauseResp = await new Promise((resolve8) => {
13542
- this.pendingAutoPause.set(requestId, resolve8);
13914
+ const pauseResp = await new Promise((resolve9) => {
13915
+ this.pendingAutoPause.set(requestId, resolve9);
13543
13916
  this.send({
13544
13917
  type: "auto_pause_request",
13545
13918
  requestId,
@@ -13718,8 +14091,8 @@ ${summaryContent}`,
13718
14091
  }
13719
14092
  if (chunk.done) break;
13720
14093
  }
13721
- await new Promise((resolve8, reject) => {
13722
- fileStream.end((err) => err ? reject(err) : resolve8());
14094
+ await new Promise((resolve9, reject) => {
14095
+ fileStream.end((err) => err ? reject(err) : resolve9());
13723
14096
  });
13724
14097
  const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
13725
14098
  if (verdict.kind === "reject") {
@@ -13745,7 +14118,7 @@ ${summaryContent}`,
13745
14118
  } catch (err) {
13746
14119
  if (fileStream) {
13747
14120
  try {
13748
- await new Promise((resolve8) => fileStream.end(() => resolve8()));
14121
+ await new Promise((resolve9) => fileStream.end(() => resolve9()));
13749
14122
  } catch {
13750
14123
  }
13751
14124
  }
@@ -14378,7 +14751,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14378
14751
  break;
14379
14752
  }
14380
14753
  const sub = args[0]?.toLowerCase();
14381
- const resolve8 = (ref) => {
14754
+ const resolve9 = (ref) => {
14382
14755
  const r = session.resolveBranchRef(ref);
14383
14756
  if (r.ok) return r.id;
14384
14757
  if (r.reason === "ambiguous") {
@@ -14432,7 +14805,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14432
14805
  this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
14433
14806
  break;
14434
14807
  }
14435
- const id = resolve8(ref);
14808
+ const id = resolve9(ref);
14436
14809
  if (!id) break;
14437
14810
  const ok = session.switchBranch(id);
14438
14811
  if (ok) {
@@ -14452,7 +14825,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14452
14825
  this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
14453
14826
  break;
14454
14827
  }
14455
- const id = resolve8(ref);
14828
+ const id = resolve9(ref);
14456
14829
  if (!id) break;
14457
14830
  const ok = session.deleteBranch(id);
14458
14831
  if (ok) {
@@ -14471,7 +14844,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14471
14844
  this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
14472
14845
  break;
14473
14846
  }
14474
- const id = resolve8(ref);
14847
+ const id = resolve9(ref);
14475
14848
  if (!id) break;
14476
14849
  const ok = session.renameBranch(id, title);
14477
14850
  if (ok) {
@@ -14489,7 +14862,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14489
14862
  this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
14490
14863
  break;
14491
14864
  }
14492
- const id = resolve8(ref);
14865
+ const id = resolve9(ref);
14493
14866
  if (!id) break;
14494
14867
  const d = session.diffBranches(id);
14495
14868
  if (!d) {
@@ -14531,7 +14904,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14531
14904
  this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
14532
14905
  break;
14533
14906
  }
14534
- const id = resolve8(ref);
14907
+ const id = resolve9(ref);
14535
14908
  if (!id) break;
14536
14909
  const idx = parseInt(idxStr, 10);
14537
14910
  if (Number.isNaN(idx)) {
@@ -14793,7 +15166,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14793
15166
  case "test": {
14794
15167
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
14795
15168
  try {
14796
- const { executeTests } = await import("./run-tests-NT2UIUVB.js");
15169
+ const { executeTests } = await import("./run-tests-PACN4UYX.js");
14797
15170
  const argStr = args.join(" ").trim();
14798
15171
  let testArgs = {};
14799
15172
  if (argStr) {
@@ -15044,7 +15417,7 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
15044
15417
  this.send({ type: "info", message: "\u2713 All added directories removed from context." });
15045
15418
  break;
15046
15419
  }
15047
- const dirPath = resolve6(sub);
15420
+ const dirPath = resolve7(sub);
15048
15421
  if (!existsSync23(dirPath)) {
15049
15422
  this.send({ type: "error", message: `Directory not found: ${dirPath}` });
15050
15423
  break;
@@ -15816,7 +16189,7 @@ function getModuleDir() {
15816
16189
  }
15817
16190
  } catch {
15818
16191
  }
15819
- return resolve7(".");
16192
+ return resolve8(".");
15820
16193
  }
15821
16194
  async function startWebServer(options = {}) {
15822
16195
  const port = options.port ?? 3e3;
@@ -16033,8 +16406,8 @@ async function startWebServer(options = {}) {
16033
16406
  const prefix = req.query.prefix || "";
16034
16407
  const targetDir = join19(cwd, prefix);
16035
16408
  try {
16036
- const canonicalTarget = realpathSync(resolve7(targetDir));
16037
- const canonicalCwd = realpathSync(resolve7(cwd));
16409
+ const canonicalTarget = realpathSync(resolve8(targetDir));
16410
+ const canonicalCwd = realpathSync(resolve8(cwd));
16038
16411
  if (!canonicalTarget.startsWith(canonicalCwd + sep3) && canonicalTarget !== canonicalCwd) {
16039
16412
  res.json({ files: [] });
16040
16413
  return;
@@ -16113,10 +16486,10 @@ async function startWebServer(options = {}) {
16113
16486
  return;
16114
16487
  }
16115
16488
  const cwd = process.cwd();
16116
- const fullPath = resolve7(join19(cwd, filePath));
16489
+ const fullPath = resolve8(join19(cwd, filePath));
16117
16490
  try {
16118
16491
  const canonicalFull = realpathSync(fullPath);
16119
- const canonicalCwd = realpathSync(resolve7(cwd));
16492
+ const canonicalCwd = realpathSync(resolve8(cwd));
16120
16493
  if (!canonicalFull.startsWith(canonicalCwd + sep3) && canonicalFull !== canonicalCwd) {
16121
16494
  res.json({ error: "Access denied" });
16122
16495
  return;
@@ -16335,7 +16708,7 @@ async function startWebServer(options = {}) {
16335
16708
  });
16336
16709
  const MAX_PORT_ATTEMPTS = 10;
16337
16710
  let actualPort = port;
16338
- const result = await new Promise((resolve8, reject) => {
16711
+ const result = await new Promise((resolve9, reject) => {
16339
16712
  const tryListen = (attempt) => {
16340
16713
  server.once("error", (err) => {
16341
16714
  if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
@@ -16366,7 +16739,7 @@ async function startWebServer(options = {}) {
16366
16739
  }
16367
16740
  console.log(` Press Ctrl+C to stop
16368
16741
  `);
16369
- resolve8({ port: actualPort, host, url });
16742
+ resolve9({ port: actualPort, host, url });
16370
16743
  });
16371
16744
  };
16372
16745
  tryListen(1);