jinzd-ai-cli 0.4.286 → 0.4.288

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/{batch-GY3I53DE.js → batch-YAEXKK2C.js} +2 -2
  2. package/dist/{chunk-BRLLV3FX.js → chunk-37WBQV3E.js} +90 -127
  3. package/dist/{chunk-EYUCJBGS.js → chunk-5G6HX57U.js} +1 -1
  4. package/dist/{chunk-S7ABWTDV.js → chunk-6NEEFOVF.js} +1 -1
  5. package/dist/{chunk-7G7RZHJI.js → chunk-7RC7AN6V.js} +1 -1
  6. package/dist/{chunk-UGY5SVNX.js → chunk-ALSSNFNK.js} +94 -4
  7. package/dist/{chunk-6SCELZG6.js → chunk-C5UAM4X7.js} +4 -4
  8. package/dist/{chunk-L4FDTQ42.js → chunk-E66SCDTD.js} +1 -1
  9. package/dist/{chunk-D7HYLMCC.js → chunk-EYA4IXQJ.js} +1 -1
  10. package/dist/{chunk-2YDLBM3Z.js → chunk-G5QZS5TN.js} +1 -1
  11. package/dist/{chunk-UKITSNSH.js → chunk-JBNNJ32H.js} +1 -1
  12. package/dist/{chunk-53V3RQY5.js → chunk-NJDQCQ7P.js} +7 -7
  13. package/dist/{chunk-THUXZNJL.js → chunk-O4CZPTPU.js} +1 -1
  14. package/dist/{chunk-GIJL4EFA.js → chunk-U4NQ2IRE.js} +1 -1
  15. package/dist/{chunk-3ZQZO5F7.js → chunk-WQ2YQ5YC.js} +4 -4
  16. package/dist/{ci-JBUIVM7P.js → ci-RO2NLIRH.js} +5 -5
  17. package/dist/{ci-format-B4AHV5SI.js → ci-format-GBSRK2J4.js} +2 -2
  18. package/dist/{constants-P75MLFV7.js → constants-JQP6BENR.js} +1 -1
  19. package/dist/{constants-5FYUQLKN.js → constants-T2B3SBMR.js} +1 -1
  20. package/dist/{doctor-cli-VLFE46PX.js → doctor-cli-CVOE7FAE.js} +5 -5
  21. package/dist/electron-server.js +211 -239
  22. package/dist/{hub-CNORVRYW.js → hub-LFWL5PGH.js} +1 -1
  23. package/dist/index.js +18 -18
  24. package/dist/{persistent-memory-LVX2OT4L.js → persistent-memory-4UR27ACM.js} +2 -2
  25. package/dist/{persistent-memory-3KL4N27E.js → persistent-memory-LFE3YHZO.js} +2 -2
  26. package/dist/{pr-2YUV4KII.js → pr-3QAG7XO7.js} +5 -5
  27. package/dist/{run-tests-KZ2IUZHK.js → run-tests-GGNFI7CL.js} +2 -2
  28. package/dist/{run-tests-ERD6X534.js → run-tests-R3JB4UEI.js} +2 -2
  29. package/dist/{server-A3OX52UC.js → server-I6DXVJRO.js} +40 -115
  30. package/dist/{server-7VSLW446.js → server-THHSJRNI.js} +5 -5
  31. package/dist/{task-orchestrator-XAMKOFUE.js → task-orchestrator-6HH3KQUQ.js} +5 -5
  32. package/dist/{usage-ZXNYCAZB.js → usage-UB4ZT5PE.js} +2 -2
  33. package/package.json +1 -1
@@ -50,7 +50,7 @@ import {
50
50
  touchMemoryReferences,
51
51
  updateMemoryApproval,
52
52
  updateMemoryEntry
53
- } from "./chunk-D7HYLMCC.js";
53
+ } from "./chunk-EYA4IXQJ.js";
54
54
  import {
55
55
  DEFAULT_PATTERNS,
56
56
  redactJson,
@@ -66,7 +66,7 @@ import {
66
66
  } from "./chunk-CGLYJRFA.js";
67
67
  import {
68
68
  runTestsTool
69
- } from "./chunk-L4FDTQ42.js";
69
+ } from "./chunk-E66SCDTD.js";
70
70
  import {
71
71
  AGENTIC_BEHAVIOR_GUIDELINE,
72
72
  APP_NAME,
@@ -100,7 +100,7 @@ import {
100
100
  SUBAGENT_MAX_ROUNDS_LIMIT,
101
101
  VERSION,
102
102
  buildUserIdentityPrompt
103
- } from "./chunk-GIJL4EFA.js";
103
+ } from "./chunk-U4NQ2IRE.js";
104
104
 
105
105
  // src/web/server.ts
106
106
  import express from "express";
@@ -1288,9 +1288,76 @@ function schemaToJsonSchema(schema) {
1288
1288
  return result;
1289
1289
  }
1290
1290
 
1291
+ // src/core/network-error.ts
1292
+ var NETWORK_CODES = /* @__PURE__ */ new Set([
1293
+ "ECONNREFUSED",
1294
+ // 端口没人听(代理没起来最常见)
1295
+ "ECONNRESET",
1296
+ // 对端断开
1297
+ "ENOTFOUND",
1298
+ // DNS 查不到
1299
+ "EAI_AGAIN",
1300
+ // DNS 临时失败
1301
+ "ETIMEDOUT",
1302
+ // 连接超时
1303
+ "EHOSTUNREACH",
1304
+ "ENETUNREACH",
1305
+ "EPIPE",
1306
+ "UND_ERR_SOCKET",
1307
+ // undici:socket 意外关闭
1308
+ "UND_ERR_CONNECT_TIMEOUT"
1309
+ ]);
1310
+ var TRANSIENT_CODES = /* @__PURE__ */ new Set(["ECONNRESET", "UND_ERR_SOCKET", "EPIPE", "EAI_AGAIN"]);
1311
+ function findCause(err, depth = 0) {
1312
+ if (!err || depth > 4) return null;
1313
+ const e = err;
1314
+ if (typeof e.code === "string") {
1315
+ return {
1316
+ code: e.code,
1317
+ address: e.address ? `${e.address}${e.port !== void 0 ? `:${e.port}` : ""}` : void 0,
1318
+ message: e.message
1319
+ };
1320
+ }
1321
+ if (Array.isArray(e.errors) && e.errors.length > 0) {
1322
+ for (const inner of e.errors) {
1323
+ const found = findCause(inner, depth + 1);
1324
+ if (found) return found;
1325
+ }
1326
+ }
1327
+ return findCause(e.cause, depth + 1);
1328
+ }
1329
+ function describeNetworkFailure(err, ctx = {}) {
1330
+ const cause = findCause(err);
1331
+ const topMessage = err instanceof Error ? err.message : String(err);
1332
+ const looksNetwork = cause?.code && NETWORK_CODES.has(cause.code) || topMessage === "fetch failed" || topMessage.includes("Connection error");
1333
+ if (!looksNetwork) return null;
1334
+ const code = cause?.code;
1335
+ const address = cause?.address;
1336
+ const transient = !!code && TRANSIENT_CODES.has(code);
1337
+ const what = code ? `${code}${address ? ` ${address}` : ""}` : cause?.message ?? "no further detail from the network layer";
1338
+ const where = ctx.target ? ` while connecting to ${ctx.target}` : "";
1339
+ const parts = [`Connection failed${where} (${what}).`];
1340
+ if (ctx.proxyUrl) {
1341
+ parts.push(`The request went through the proxy from config.proxy (${ctx.proxyUrl}).`);
1342
+ if (code === "ECONNREFUSED") {
1343
+ parts.push(`That address refused the connection \u2014 the proxy is probably not running. Start it, or unset config.proxy to connect directly.`);
1344
+ }
1345
+ }
1346
+ if (transient) {
1347
+ parts.push(`This usually means an idle connection was dropped by the other side \u2014 just send the message again.`);
1348
+ } else if (code === "ENOTFOUND" || code === "EAI_AGAIN") {
1349
+ parts.push(`DNS lookup failed \u2014 check the network connection${ctx.proxyUrl ? " and the proxy" : ""}.`);
1350
+ } else if (code === "ETIMEDOUT" || code === "UND_ERR_CONNECT_TIMEOUT") {
1351
+ parts.push(`The connection timed out before it was established${ctx.proxyUrl ? " \u2014 check whether the proxy can reach this host" : ""}.`);
1352
+ }
1353
+ return { code, address, transient, message: parts.join(" ") };
1354
+ }
1355
+
1291
1356
  // src/providers/claude.ts
1292
1357
  var CACHE_MIN_SYSTEM_CHARS = 2e3;
1293
1358
  var ClaudeProvider = class extends BaseProvider {
1359
+ /** 生效的代理(网络失败时点名它,见 core/network-error.ts)。 */
1360
+ activeProxyUrl;
1294
1361
  client;
1295
1362
  info = {
1296
1363
  id: "claude",
@@ -1325,6 +1392,7 @@ var ClaudeProvider = class extends BaseProvider {
1325
1392
  baseURL: options?.baseUrl
1326
1393
  };
1327
1394
  const proxyUrl = options?.proxy;
1395
+ this.activeProxyUrl = proxyUrl;
1328
1396
  try {
1329
1397
  const { Agent: Agent2, ProxyAgent, fetch: undiciFetch } = await import("undici");
1330
1398
  const STREAM_BODY_TIMEOUT = 30 * 60 * 1e3;
@@ -1849,6 +1917,8 @@ var ClaudeProvider = class extends BaseProvider {
1849
1917
  if (err instanceof Anthropic.RateLimitError) {
1850
1918
  return new RateLimitError("claude");
1851
1919
  }
1920
+ const net2 = describeNetworkFailure(err, { target: "api.anthropic.com", proxyUrl: this.activeProxyUrl });
1921
+ if (net2) return new ProviderError("claude", net2.message, err instanceof Error ? err : void 0);
1852
1922
  if (err instanceof Error) {
1853
1923
  return new ProviderError("claude", err.message, err);
1854
1924
  }
@@ -2165,10 +2235,11 @@ var GeminiProvider = class extends BaseProvider {
2165
2235
  err
2166
2236
  );
2167
2237
  }
2168
- if (msg.includes("fetch failed") || msg.includes("ECONNREFUSED") || msg.includes("ETIMEDOUT") || msg.includes("ENOTFOUND") || msg.includes("socket hang up")) {
2238
+ const net2 = describeNetworkFailure(err, { target: "generativelanguage.googleapis.com" });
2239
+ if (net2 || msg.includes("socket hang up")) {
2169
2240
  return new ProviderError(
2170
2241
  "gemini",
2171
- `Network connection failed: ${msg}
2242
+ `${net2?.message ?? `Network connection failed: ${msg}`}
2172
2243
  Node.js does not automatically use system proxies. Try one of the following:
2173
2244
  1. Set environment variable: set HTTPS_PROXY=http://127.0.0.1:<proxy-port>
2174
2245
  2. Configure an accessible Gemini API mirror URL in config.json under customBaseUrls.gemini`,
@@ -3360,6 +3431,8 @@ function readRetryCount(headers) {
3360
3431
  }
3361
3432
  var OpenAICompatibleProvider = class extends BaseProvider {
3362
3433
  client;
3434
+ /** 生效的代理(wrapError 要在网络失败时点名它);未配代理时为 undefined。 */
3435
+ activeProxyUrl;
3363
3436
  defaultTimeout = 6e4;
3364
3437
  // ms
3365
3438
  /**
@@ -3389,13 +3462,17 @@ var OpenAICompatibleProvider = class extends BaseProvider {
3389
3462
  const { Agent: Agent2, ProxyAgent, fetch: undiciFetch } = await import("undici");
3390
3463
  const STREAM_BODY_TIMEOUT = 30 * 60 * 1e3;
3391
3464
  const STREAM_HEADERS_TIMEOUT = 5 * 60 * 1e3;
3465
+ const KEEP_ALIVE_TIMEOUT = 2e3;
3466
+ this.activeProxyUrl = proxyUrl;
3392
3467
  const dispatcher = proxyUrl ? new ProxyAgent({
3393
3468
  uri: proxyUrl,
3394
3469
  bodyTimeout: STREAM_BODY_TIMEOUT,
3395
- headersTimeout: STREAM_HEADERS_TIMEOUT
3470
+ headersTimeout: STREAM_HEADERS_TIMEOUT,
3471
+ keepAliveTimeout: KEEP_ALIVE_TIMEOUT
3396
3472
  }) : new Agent2({
3397
3473
  bodyTimeout: STREAM_BODY_TIMEOUT,
3398
- headersTimeout: STREAM_HEADERS_TIMEOUT
3474
+ headersTimeout: STREAM_HEADERS_TIMEOUT,
3475
+ keepAliveTimeout: KEEP_ALIVE_TIMEOUT
3399
3476
  });
3400
3477
  clientOptions.fetch = ((url, init) => {
3401
3478
  const retryCount = readRetryCount(init?.headers);
@@ -3869,11 +3946,24 @@ var OpenAICompatibleProvider = class extends BaseProvider {
3869
3946
  if (err instanceof OpenAI.RateLimitError) {
3870
3947
  return new RateLimitError(this.info.id);
3871
3948
  }
3949
+ const net2 = describeNetworkFailure(err, {
3950
+ target: this.hostLabel(),
3951
+ proxyUrl: this.activeProxyUrl
3952
+ });
3953
+ if (net2) return new ProviderError(this.info.id, net2.message, err instanceof Error ? err : void 0);
3872
3954
  if (err instanceof Error) {
3873
3955
  return new ProviderError(this.info.id, err.message, err);
3874
3956
  }
3875
3957
  return new ProviderError(this.info.id, String(err));
3876
3958
  }
3959
+ /** 请求实际打向的主机,用于「连谁失败了」。 */
3960
+ hostLabel() {
3961
+ try {
3962
+ return new URL(this.info.baseUrl ?? this.defaultBaseUrl).host;
3963
+ } catch {
3964
+ return this.info.id;
3965
+ }
3966
+ }
3877
3967
  };
3878
3968
 
3879
3969
  // src/providers/deepseek.ts
@@ -7172,6 +7262,63 @@ function runHook(template, vars) {
7172
7262
  }
7173
7263
  }
7174
7264
 
7265
+ // src/core/tool-gate.ts
7266
+ var gateMessages = {
7267
+ hookDenied: (toolName, reason) => `[Hook denied] ${reason ?? `PreToolUse blocked ${toolName}`}. Do not retry without asking.`,
7268
+ networkDenied: (toolName, reason) => `[Permission denied] Tool ${toolName} is blocked by networkPolicy${reason ? ` (${reason})` : ""}. Do not retry.`,
7269
+ profileDenied: (toolName, profileName, reason) => `[Permission denied] Tool ${toolName} is blocked by permission profile "${profileName}"${reason ? ` (${reason})` : ""}. Do not retry.`,
7270
+ autoDenied: (reason) => `[Auto denied] ${reason}. Do not retry without asking.`,
7271
+ permissionCancelled: (toolName) => `[User cancelled] The user declined permission for ${toolName}. Do not retry without asking.`,
7272
+ operationCancelled: (toolName, level) => `[User cancelled] The user declined the ${level === "destructive" ? "destructive " : ""}${toolName} operation. Do not retry without asking.`
7273
+ };
7274
+ function needsPermissionRequestHook(input) {
7275
+ return input.networkPermission?.action === "confirm" || input.permission.action === "confirm";
7276
+ }
7277
+ function decideToolGate(call, input) {
7278
+ const { dangerLevel, permission, networkPermission } = input;
7279
+ if (input.hookDeny) {
7280
+ return { kind: "denied", source: "hook", message: gateMessages.hookDenied(call.name, input.hookDeny.reason) };
7281
+ }
7282
+ if (networkPermission?.action === "deny") {
7283
+ return { kind: "denied", source: "network", message: gateMessages.networkDenied(call.name, networkPermission.reason) };
7284
+ }
7285
+ if (permission.action === "deny") {
7286
+ return {
7287
+ kind: "denied",
7288
+ source: "profile",
7289
+ message: gateMessages.profileDenied(call.name, permission.profileName ?? "default", permission.reason)
7290
+ };
7291
+ }
7292
+ if (permission.action === "auto-approve" && networkPermission?.action !== "confirm") {
7293
+ return { kind: "allow", source: "profile" };
7294
+ }
7295
+ const requiresHardConfirmation = permission.action === "confirm" && permission.hardBoundary === true;
7296
+ if ((networkPermission?.action === "confirm" || requiresHardConfirmation) && dangerLevel === "safe") {
7297
+ return { kind: "confirm", level: "write", source: "permission" };
7298
+ }
7299
+ if (input.sessionAutoMode && networkPermission?.action !== "confirm") {
7300
+ const classification = input.classify();
7301
+ if (classification.decision === "deny") {
7302
+ return {
7303
+ kind: "denied",
7304
+ source: "auto-mode",
7305
+ message: gateMessages.autoDenied(classification.reason),
7306
+ classification
7307
+ };
7308
+ }
7309
+ if (classification.decision === "auto-approve" && !requiresHardConfirmation) {
7310
+ return { kind: "allow", source: "auto-mode" };
7311
+ }
7312
+ }
7313
+ if (input.sessionAutoApprove && dangerLevel === "write" && !requiresHardConfirmation) {
7314
+ return { kind: "allow", source: "yolo" };
7315
+ }
7316
+ if (dangerLevel === "write" || dangerLevel === "destructive") {
7317
+ return { kind: "confirm", level: dangerLevel, source: "danger" };
7318
+ }
7319
+ return { kind: "allow", source: "safe" };
7320
+ }
7321
+
7175
7322
  // src/tools/permissions.ts
7176
7323
  import { existsSync as existsSync9, realpathSync } from "fs";
7177
7324
  import { basename as basename2, dirname as dirname3, isAbsolute, resolve as resolve3 } from "path";
@@ -7966,13 +8113,6 @@ var ToolExecutor = class {
7966
8113
  { configDir: this.hookConfigDir, onSummary: (msg) => console.log(theme.dim(` [hook] ${msg}`)) }
7967
8114
  );
7968
8115
  const preDeny = preToolDecisions.find((d) => d.action === "deny");
7969
- if (preDeny) {
7970
- return {
7971
- callId: call.id,
7972
- content: `[Hook denied] ${preDeny.reason ?? `PreToolUse blocked ${call.name}`}. Do not retry without asking.`,
7973
- isError: true
7974
- };
7975
- }
7976
8116
  for (const d of preToolDecisions) {
7977
8117
  const warnings = [d.warning, ...d.warnings ?? []].filter(Boolean);
7978
8118
  for (const w of warnings) console.log(theme.warning(` \u26A0 Hook warning: ${w}`));
@@ -7992,7 +8132,19 @@ var ToolExecutor = class {
7992
8132
  }
7993
8133
  );
7994
8134
  const networkPermission = checkNetworkPolicy(call.name, call.arguments, this.networkPolicy);
7995
- if (networkPermission?.action === "confirm" || permission.action === "confirm") {
8135
+ const gate = decideToolGate(call, {
8136
+ dangerLevel,
8137
+ permission,
8138
+ networkPermission,
8139
+ hookDeny: preDeny ? { reason: preDeny.reason } : void 0,
8140
+ sessionAutoMode: this.sessionAutoMode,
8141
+ sessionAutoApprove: this.sessionAutoApprove,
8142
+ classify: () => defaultActionClassifier.classify(call, dangerLevel, {
8143
+ workspaceRoot: this.workspaceRoot,
8144
+ tempDirs: [tmpdir()]
8145
+ })
8146
+ });
8147
+ if (needsPermissionRequestHook({ permission, networkPermission })) {
7996
8148
  runLifecycleHooks(
7997
8149
  this.hookConfig,
7998
8150
  "PermissionRequest",
@@ -8000,131 +8152,29 @@ var ToolExecutor = class {
8000
8152
  { configDir: this.hookConfigDir, onSummary: (msg) => console.log(theme.dim(" [hook] " + msg)) }
8001
8153
  );
8002
8154
  }
8003
- if (networkPermission?.action === "deny") {
8004
- const reason = networkPermission.reason ? ` (${networkPermission.reason})` : "";
8005
- return {
8006
- callId: call.id,
8007
- content: `[Permission denied] Tool ${call.name} is blocked by networkPolicy${reason}. Do not retry.`,
8008
- isError: true
8009
- };
8010
- }
8011
- if (permission.action === "deny") {
8012
- const reason = permission.reason ? ` (${permission.reason})` : "";
8013
- return {
8014
- callId: call.id,
8015
- content: `[Permission denied] Tool ${call.name} is blocked by permission profile "${permission.profileName}"${reason}. Do not retry.`,
8016
- isError: true
8017
- };
8018
- }
8019
- if (permission.action === "auto-approve" && networkPermission?.action !== "confirm") {
8020
- if (!toolCallAlreadyPrinted) this.printToolCall(call);
8021
- try {
8022
- const rawContent = await runTool(tool, call.arguments, call.name);
8023
- const content = truncateOutput(rawContent, call.name);
8024
- const wasTruncated = content !== rawContent;
8025
- this.printToolResult(call.name, rawContent, false, wasTruncated);
8026
- runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
8027
- runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
8028
- return { callId: call.id, content, isError: false };
8029
- } catch (err) {
8030
- const message = err instanceof Error ? err.message : err && typeof err === "object" && "message" in err ? String(err.message) : String(err);
8031
- this.printToolResult(call.name, message, true, false);
8032
- runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
8033
- runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
8034
- return { callId: call.id, content: message, isError: true };
8035
- }
8036
- }
8037
- const requiresHardConfirmation = permission.action === "confirm" && permission.hardBoundary === true;
8038
- if ((networkPermission?.action === "confirm" || requiresHardConfirmation) && dangerLevel === "safe") {
8039
- this.printToolCall(call);
8040
- toolCallAlreadyPrinted = true;
8041
- const confirmed = await this.confirm(call, "write");
8042
- if (!confirmed) {
8043
- return {
8044
- callId: call.id,
8045
- content: `[User cancelled] The user declined permission for ${call.name}. Do not retry without asking.`,
8046
- isError: true
8047
- };
8048
- }
8155
+ if (gate.kind === "denied") {
8156
+ if (gate.source === "auto-mode") recordRecentlyDeniedAutoAction(call, gate.classification);
8157
+ return { callId: call.id, content: gate.message, isError: true };
8049
8158
  }
8050
- if (this.sessionAutoMode && networkPermission?.action !== "confirm") {
8051
- const classification = defaultActionClassifier.classify(call, dangerLevel, {
8052
- workspaceRoot: this.workspaceRoot,
8053
- tempDirs: [tmpdir()]
8054
- });
8055
- if (classification.decision === "deny") {
8056
- recordRecentlyDeniedAutoAction(call, classification);
8057
- return {
8058
- callId: call.id,
8059
- content: `[Auto denied] ${classification.reason}. Do not retry without asking.`,
8060
- isError: true
8061
- };
8062
- }
8063
- if (classification.decision === "auto-approve" && !requiresHardConfirmation) {
8159
+ if (gate.kind === "confirm") {
8160
+ if (gate.source === "danger" && gate.level === "write") {
8064
8161
  this.printToolCall(call);
8065
- if (dangerLevel === "write") this.printDiffPreview(call);
8066
- console.log(theme.warning(` \u26A1 Auto-approved (/auto: ${classification.ruleId})`));
8067
- try {
8068
- const rawContent = await runTool(tool, call.arguments, call.name);
8069
- const content = truncateOutput(rawContent, call.name);
8070
- const wasTruncated = content !== rawContent;
8071
- this.printToolResult(call.name, rawContent, false, wasTruncated);
8072
- runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
8073
- runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
8074
- return { callId: call.id, content, isError: false };
8075
- } catch (err) {
8076
- const message = err instanceof Error ? err.message : err && typeof err === "object" && "message" in err ? String(err.message) : String(err);
8077
- this.printToolResult(call.name, message, true, false);
8078
- runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
8079
- runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
8080
- return { callId: call.id, content: message, isError: true };
8081
- }
8082
- }
8083
- }
8084
- if (this.sessionAutoApprove && dangerLevel === "write" && !requiresHardConfirmation) {
8085
- this.printToolCall(call);
8086
- if (dangerLevel === "write") this.printDiffPreview(call);
8087
- console.log(theme.warning(" \u26A1 Auto-approved (session /yolo mode)"));
8088
- try {
8089
- const rawContent = await runTool(tool, call.arguments, call.name);
8090
- const content = truncateOutput(rawContent, call.name);
8091
- const wasTruncated = content !== rawContent;
8092
- this.printToolResult(call.name, rawContent, false, wasTruncated);
8093
- runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
8094
- runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
8095
- return { callId: call.id, content, isError: false };
8096
- } catch (err) {
8097
- const message = err instanceof Error ? err.message : err && typeof err === "object" && "message" in err ? String(err.message) : String(err);
8098
- this.printToolResult(call.name, message, true, false);
8099
- runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
8100
- runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
8101
- return { callId: call.id, content: message, isError: true };
8102
- }
8103
- }
8104
- if (dangerLevel === "write") {
8105
- this.printToolCall(call);
8106
- this.printDiffPreview(call);
8107
- const confirmed = await this.confirm(call, dangerLevel);
8108
- if (!confirmed) {
8109
- return {
8110
- callId: call.id,
8111
- content: `[User cancelled] The user declined the ${call.name} operation. Do not retry without asking.`,
8112
- isError: true
8113
- };
8162
+ this.printDiffPreview(call);
8163
+ toolCallAlreadyPrinted = true;
8164
+ } else if (gate.source === "permission") {
8165
+ this.printToolCall(call);
8166
+ toolCallAlreadyPrinted = true;
8114
8167
  }
8115
- } else if (dangerLevel === "destructive") {
8116
- const confirmed = await this.confirm(call, dangerLevel);
8168
+ const confirmed = await this.confirm(call, gate.level);
8117
8169
  if (!confirmed) {
8118
8170
  return {
8119
8171
  callId: call.id,
8120
- content: `[User cancelled] The user declined the destructive ${call.name} operation. Do not retry without asking.`,
8172
+ content: gate.source === "permission" ? gateMessages.permissionCancelled(call.name) : gateMessages.operationCancelled(call.name, gate.level),
8121
8173
  isError: true
8122
8174
  };
8123
8175
  }
8124
- this.printToolCall(call);
8125
- } else {
8126
- this.printToolCall(call);
8127
8176
  }
8177
+ if (!toolCallAlreadyPrinted) this.printToolCall(call);
8128
8178
  try {
8129
8179
  const rawContent = await runTool(tool, call.arguments, call.name);
8130
8180
  const content = truncateOutput(rawContent, call.name);
@@ -13958,15 +14008,6 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
13958
14008
  { configDir: this.hookConfigDir, onSummary: (message) => this.send({ type: "info", message: `[hook] ${message}` }) }
13959
14009
  );
13960
14010
  const preDeny = preToolDecisions.find((d) => d.action === "deny");
13961
- if (preDeny) {
13962
- const result = {
13963
- callId: call.id,
13964
- content: `[Hook denied] ${preDeny.reason ?? `PreToolUse blocked ${call.name}`}. Do not retry without asking.`,
13965
- isError: true
13966
- };
13967
- this.sendToolCallResult(call, result.content, true);
13968
- return result;
13969
- }
13970
14011
  for (const d of preToolDecisions) {
13971
14012
  const warnings = [d.warning, ...d.warnings ?? []].filter(Boolean);
13972
14013
  for (const warning of warnings) this.send({ type: "info", message: `[hook] ${warning}` });
@@ -13986,7 +14027,19 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
13986
14027
  }
13987
14028
  );
13988
14029
  const networkPermission = checkNetworkPolicy(call.name, call.arguments, this.networkPolicy);
13989
- if (networkPermission?.action === "confirm" || permission.action === "confirm") {
14030
+ const gate = decideToolGate(call, {
14031
+ dangerLevel,
14032
+ permission,
14033
+ networkPermission,
14034
+ hookDeny: preDeny ? { reason: preDeny.reason } : void 0,
14035
+ sessionAutoMode: this.sessionAutoMode,
14036
+ sessionAutoApprove: this.sessionAutoApprove,
14037
+ classify: () => defaultActionClassifier.classify(call, dangerLevel, {
14038
+ workspaceRoot: this.workspaceRoot,
14039
+ tempDirs: [tmpdir2()]
14040
+ })
14041
+ });
14042
+ if (needsPermissionRequestHook({ permission, networkPermission })) {
13990
14043
  runLifecycleHooks(
13991
14044
  this.hookConfig,
13992
14045
  "PermissionRequest",
@@ -13994,105 +14047,24 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
13994
14047
  { configDir: this.hookConfigDir, onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
13995
14048
  );
13996
14049
  }
13997
- if (networkPermission?.action === "deny") {
13998
- const reason = networkPermission.reason ? ` (${networkPermission.reason})` : "";
13999
- return {
14000
- callId: call.id,
14001
- content: `[Permission denied] Tool ${call.name} is blocked by networkPolicy${reason}. Do not retry.`,
14002
- isError: true
14003
- };
14004
- }
14005
- if (permission.action === "deny") {
14006
- const reason = permission.reason ? ` (${permission.reason})` : "";
14007
- return {
14008
- callId: call.id,
14009
- content: `[Permission denied] Tool ${call.name} is blocked by permission profile "${permission.profileName}"${reason}. Do not retry.`,
14010
- isError: true
14011
- };
14012
- }
14013
- if (permission.action === "auto-approve" && networkPermission?.action !== "confirm") {
14014
- this.sendToolCallStart(call);
14015
- try {
14016
- const rawContent = await runTool(tool, call.arguments, call.name);
14017
- const content = truncateOutput(rawContent, call.name);
14018
- this.sendToolCallResult(call, rawContent, false);
14019
- runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
14020
- runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
14021
- return { callId: call.id, content, isError: false };
14022
- } catch (err) {
14023
- const message = err instanceof Error ? err.message : err && typeof err === "object" && "message" in err ? String(err.message) : String(err);
14024
- this.sendToolCallResult(call, message, true);
14025
- runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
14026
- runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
14027
- return { callId: call.id, content: message, isError: true };
14028
- }
14029
- }
14030
- const requiresHardConfirmation = permission.action === "confirm" && permission.hardBoundary === true;
14031
- if ((networkPermission?.action === "confirm" || requiresHardConfirmation) && dangerLevel === "safe") {
14032
- this.sendToolCallStart(call);
14033
- toolCallStarted = true;
14034
- const confirmed = await this.confirm(call, "write");
14035
- if (!confirmed) {
14036
- const rejectionMsg = `[User cancelled] The user declined permission for ${call.name}. Do not retry without asking.`;
14037
- this.sendToolCallResult(call, rejectionMsg, true);
14038
- return { callId: call.id, content: rejectionMsg, isError: true };
14039
- }
14040
- }
14041
- if (!toolCallStarted) this.sendToolCallStart(call);
14042
- if (this.sessionAutoMode && networkPermission?.action !== "confirm") {
14043
- const classification = defaultActionClassifier.classify(call, dangerLevel, {
14044
- workspaceRoot: this.workspaceRoot,
14045
- tempDirs: [tmpdir2()]
14046
- });
14047
- if (classification.decision === "deny") {
14048
- recordRecentlyDeniedAutoAction(call, classification);
14049
- const rejectionMsg = `[Auto denied] ${classification.reason}. Do not retry without asking.`;
14050
- this.sendToolCallResult(call, rejectionMsg, true);
14051
- return { callId: call.id, content: rejectionMsg, isError: true };
14052
- }
14053
- if (classification.decision === "auto-approve" && !requiresHardConfirmation) {
14054
- this.send({ type: "info", message: `\u26A1 Auto-approved (/auto: ${classification.ruleId})` });
14055
- try {
14056
- const rawContent = await runTool(tool, call.arguments, call.name);
14057
- const content = truncateOutput(rawContent, call.name);
14058
- this.sendToolCallResult(call, rawContent, false);
14059
- runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
14060
- runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
14061
- return { callId: call.id, content, isError: false };
14062
- } catch (err) {
14063
- const message = err instanceof Error ? err.message : err && typeof err === "object" && "message" in err ? String(err.message) : String(err);
14064
- this.sendToolCallResult(call, message, true);
14065
- runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
14066
- runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
14067
- return { callId: call.id, content: message, isError: true };
14068
- }
14069
- }
14050
+ if (gate.kind === "denied") {
14051
+ if (gate.source === "auto-mode") recordRecentlyDeniedAutoAction(call, gate.classification);
14052
+ this.sendToolCallResult(call, gate.message, true);
14053
+ return { callId: call.id, content: gate.message, isError: true };
14070
14054
  }
14071
- if (this.sessionAutoApprove && dangerLevel === "write" && !requiresHardConfirmation) {
14072
- try {
14073
- const rawContent = await runTool(tool, call.arguments, call.name);
14074
- const content = truncateOutput(rawContent, call.name);
14075
- this.sendToolCallResult(call, rawContent, false);
14076
- runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
14077
- runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
14078
- return { callId: call.id, content, isError: false };
14079
- } catch (err) {
14080
- const message = err instanceof Error ? err.message : err && typeof err === "object" && "message" in err ? String(err.message) : String(err);
14081
- this.sendToolCallResult(call, message, true);
14082
- runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
14083
- runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
14084
- return { callId: call.id, content: message, isError: true };
14055
+ if (gate.kind === "confirm") {
14056
+ if (!toolCallStarted) {
14057
+ this.sendToolCallStart(call);
14058
+ toolCallStarted = true;
14085
14059
  }
14086
- }
14087
- if (dangerLevel === "write" || dangerLevel === "destructive") {
14088
- const confirmed = await this.confirm(call, dangerLevel);
14060
+ const confirmed = await this.confirm(call, gate.level);
14089
14061
  if (!confirmed) {
14090
- const label = dangerLevel === "destructive" ? "destructive " : "";
14091
- const rejectionMsg = `[User cancelled] The user declined the ${label}${call.name} operation. Do not retry without asking.`;
14062
+ const rejectionMsg = gate.source === "permission" ? gateMessages.permissionCancelled(call.name) : gateMessages.operationCancelled(call.name, gate.level);
14092
14063
  this.sendToolCallResult(call, rejectionMsg, true);
14093
14064
  return { callId: call.id, content: rejectionMsg, isError: true };
14094
14065
  }
14095
14066
  }
14067
+ if (!toolCallStarted) this.sendToolCallStart(call);
14096
14068
  try {
14097
14069
  const rawContent = await runTool(tool, call.arguments, call.name);
14098
14070
  const content = truncateOutput(rawContent, call.name);
@@ -16907,7 +16879,7 @@ Cache: write=${cacheCreate} read=${cacheRead}` : "";
16907
16879
  Cost: ${formatCost(cost)}` : "";
16908
16880
  let memoryLine = "";
16909
16881
  try {
16910
- const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-3KL4N27E.js");
16882
+ const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-LFE3YHZO.js");
16911
16883
  const pending = countPendingMemories2(ctx.config.getConfigDir());
16912
16884
  if (pending > 0) memoryLine = `
16913
16885
  Memory: \u26A0 ${pending} pending approval \u2014 see the Memory panel or /memory`;
@@ -17890,7 +17862,7 @@ async function handleTest(args, ctx) {
17890
17862
  const isCommand = argStr.includes(" ") || /^(mvn|gradle|npm|pytest|cargo|go)\b/.test(argStr);
17891
17863
  testArgs = isCommand ? { command: argStr } : { filter: argStr };
17892
17864
  }
17893
- const runTests = ctx.runTests ?? (await import("./run-tests-ERD6X534.js")).executeTests;
17865
+ const runTests = ctx.runTests ?? (await import("./run-tests-R3JB4UEI.js")).executeTests;
17894
17866
  const report = await runTests(testArgs);
17895
17867
  ctx.send({ type: "info", message: report });
17896
17868
  } catch (err) {
@@ -18280,7 +18252,7 @@ async function handleMemory(args, ctx) {
18280
18252
  }
18281
18253
  if (intent.kind === "status") {
18282
18254
  const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-DGUOUUQW.js");
18283
- const { listMemoryEntries: listMemoryEntries2, isMemoryExpired: isMemoryExpired2, formatMemoryForPrompt: formatMemoryForPrompt2 } = await import("./persistent-memory-3KL4N27E.js");
18255
+ const { listMemoryEntries: listMemoryEntries2, isMemoryExpired: isMemoryExpired2, formatMemoryForPrompt: formatMemoryForPrompt2 } = await import("./persistent-memory-LFE3YHZO.js");
18284
18256
  const memories = listMemoryEntries2(configDir, { includeExpired: true, includeRejected: true });
18285
18257
  const injected = formatMemoryForPrompt2(configDir, process.cwd(), ctx.config.get("memory")?.maxChars);
18286
18258
  const view = buildMemoryStatusView({
@@ -18303,7 +18275,7 @@ async function handleMemory(args, ctx) {
18303
18275
  return;
18304
18276
  }
18305
18277
  if (intent.kind === "recall") {
18306
- const { searchPersistentMemories: searchPersistentMemories2, touchMemoryReferences: touchMemoryReferences2 } = await import("./persistent-memory-3KL4N27E.js");
18278
+ const { searchPersistentMemories: searchPersistentMemories2, touchMemoryReferences: touchMemoryReferences2 } = await import("./persistent-memory-LFE3YHZO.js");
18307
18279
  const { searchChatMemory: searchChatMemory2 } = await import("./chat-index-DGUOUUQW.js");
18308
18280
  const persistentHits = searchPersistentMemories2(configDir, intent.query, process.cwd(), 3);
18309
18281
  if (persistentHits.length > 0) {
@@ -18424,7 +18396,7 @@ async function handleMemory(args, ctx) {
18424
18396
  ctx.handleMemoryManage(intent.action, args[1], intent.action === "expire" ? args[2] : void 0);
18425
18397
  } else if (intent.kind === "export") {
18426
18398
  if (args[1] === "md") {
18427
- const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-3KL4N27E.js");
18399
+ const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-LFE3YHZO.js");
18428
18400
  ctx.send({
18429
18401
  type: "export_data",
18430
18402
  format: "md",
@@ -19429,7 +19401,7 @@ No tools match "${filter}".
19429
19401
  const { join: join29 } = await import("path");
19430
19402
  const { existsSync: existsSync33 } = await import("fs");
19431
19403
  const { getGitRoot: getGitRoot2 } = await import("./git-context-VNCA2GU7.js");
19432
- const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-P75MLFV7.js");
19404
+ const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-JQP6BENR.js");
19433
19405
  const { approveProject, hashMcpFile } = await import("./project-trust-MUG325AW.js");
19434
19406
  const cwd = process.cwd();
19435
19407
  const projectRoot = getGitRoot2(cwd) ?? cwd;
@@ -20344,7 +20316,7 @@ function createReviewCommands() {
20344
20316
  usage: "/test [command|filter]",
20345
20317
  async execute(args, ctx) {
20346
20318
  try {
20347
- const { executeTests } = await import("./run-tests-ERD6X534.js");
20319
+ const { executeTests } = await import("./run-tests-R3JB4UEI.js");
20348
20320
  const argStr = args.join(" ").trim();
20349
20321
  let testArgs = {};
20350
20322
  if (argStr) {
@@ -158,7 +158,7 @@ ${content}`);
158
158
  }
159
159
  }
160
160
  async function runTaskMode(config, providers, configManager, topic) {
161
- const { TaskOrchestrator } = await import("./task-orchestrator-XAMKOFUE.js");
161
+ const { TaskOrchestrator } = await import("./task-orchestrator-6HH3KQUQ.js");
162
162
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
163
163
  let interrupted = false;
164
164
  const onSigint = () => {