deepseeker-code 1.0.58 → 1.0.59

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 (2) hide show
  1. package/dist/cli.mjs +1160 -509
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -119,13 +119,16 @@ var init_strings = __esm({
119
119
  planEditTitle: "\u270F\uFE0F \u7F16\u8F91\u65B9\u6848",
120
120
  planEditHint: "Enter \u6309\u6B64\u65B9\u6848\u6267\u884C \xB7 Esc \u53D6\u6D88\u56DE\u5230\u9009\u9879",
121
121
  planKeepPlanning: "\u270B \u5DF2\u7559\u5728\u8BA1\u5212\u6A21\u5F0F\uFF1A\u7EE7\u7EED\u8F93\u5165\u8865\u5145\u8981\u6C42\uFF0C\u6A21\u578B\u5C06\u4FEE\u8BA2\u65B9\u6848\u540E\u518D\u6B21\u63D0\u4EA4\uFF08/plan \u53EF\u9000\u51FA\uFF09\u3002",
122
+ planNoProposal: "\u2139\uFE0F \u672C\u8F6E\u672A\u6536\u5230\u65B9\u6848\uFF08\u6A21\u578B\u672A\u63D0\u4EA4 exit_plan_mode\uFF09\uFF0C\u4ECD\u5728\u8BA1\u5212\u6A21\u5F0F\uFF1A\u53EF\u7EE7\u7EED\u63CF\u8FF0\u9700\u6C42\uFF0C\u6216 /plan \u5173\u95ED\u8BA1\u5212\u6A21\u5F0F\u3002",
122
123
  planEditEmpty: "\u65B9\u6848\u4E0D\u80FD\u4E3A\u7A7A",
123
124
  planAutoExecute: "\u26A1 \u5DF2\u8FDB\u5165\u81EA\u52A8\u6267\u884C\uFF1A\u5B9E\u73B0\u9636\u6BB5\u5DE5\u5177\u5C06\u514D\u5BA1\u6279\u76F4\u63A5\u8FD0\u884C\u3002",
124
125
  inputBadgePlan: "plan mode on\uFF08\u53EA\u8BFB\u8C03\u7814 \u2192 \u65B9\u6848\u5BA1\u6279 \u2192 \u5B9E\u73B0\uFF09",
125
126
  inputBadgeAuto: "auto-accept edits on",
126
127
  approvalTitle: "\u{1F510} \u64CD\u4F5C\u5BA1\u6279",
127
128
  approvalPrompt: "\u2191\u2193 \u9009\u62E9\u540E Enter\uFF08Esc \u62D2\u7EDD\uFF09\uFF1A\u5141\u8BB8\u672C\u6B21 / \u603B\u662F\u5141\u8BB8\uFF08\u5199\u6301\u4E45\u89C4\u5219\uFF09/ \u62D2\u7EDD\u3002",
129
+ approvalFullHint: "\uFF08\u6309 f \u67E5\u770B/\u6536\u8D77\u5B8C\u6574\u5185\u5BB9\uFF09",
128
130
  approvalAllow: "\u5141\u8BB8\u672C\u6B21",
131
+ approvalAllowSession: "\u672C\u4F1A\u8BDD\u5185\u5141\u8BB8\uFF08\u4E0D\u843D\u76D8\uFF09",
129
132
  approvalAllowAlways: "\u603B\u662F\u5141\u8BB8\uFF08\u6B64\u9879\u76EE\uFF09",
130
133
  approvalDeny: "\u62D2\u7EDD",
131
134
  errAborted: "\u5DF2\u4E2D\u6B62\u5F53\u524D\u8F6E\u3002",
@@ -191,6 +194,17 @@ ${dim} \u8FD0\u884C npm i -g deepseeker-code@latest \u66F4\u65B0${reset}
191
194
  forkEmpty: "\uFF08\u5F53\u524D\u4F1A\u8BDD\u6682\u65E0\u53EF\u5206\u53C9\u7684\u68C0\u67E5\u70B9\uFF09",
192
195
  forkDone: (id, roundNo) => `\u5DF2\u4ECE\u7B2C ${roundNo} \u8F6E\u5206\u53C9 \u2192 \u65B0\u4F1A\u8BDD ${id}\uFF08\u5DF2\u8F7D\u5165\uFF0C\u540E\u7EED\u5BF9\u8BDD\u5199\u5165\u65B0\u4F1A\u8BDD\uFF0C\u539F\u4F1A\u8BDD\u4E0D\u53D8\uFF09`,
193
196
  forkFailed: (e) => `\u5206\u53C9\u5931\u8D25\uFF1A${e}`,
197
+ cmdUndo: "\u64A4\u9500\u6700\u8FD1\u4E00\u6B21\u6587\u4EF6\u6539\u52A8\uFF08LIFO \u9010\u6B65\u56DE\u8D70\uFF1B\u56DE\u9000\u524D\u81EA\u52A8\u5907\u4EFD\uFF0C\u53EF\u9006\uFF09",
198
+ undoNoSession: "\u5F53\u524D\u6CA1\u6709\u6D3B\u52A8\u4F1A\u8BDD\uFF0C\u65E0\u53EF\u56DE\u9000\u9879\u3002",
199
+ cmdRewind: "\u6309\u68C0\u67E5\u70B9\u9009\u62E9\u56DE\u9000\u6587\u4EF6\u6539\u52A8\uFF08\u2191\u2193 \u9009\u62E9\uFF0CEnter \u56DE\u9000\uFF09",
200
+ rewindTitle: "\u23EA \u56DE\u9000\u6587\u4EF6\u6539\u52A8\uFF08\u2191\u2193 \u9009\u62E9 \xB7 Enter \u56DE\u9000 \xB7 Esc \u53D6\u6D88\uFF09",
201
+ rewindPrompt: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u56DE\u9000 \xB7 Esc \u53D6\u6D88",
202
+ rewindNoSession: "\u5F53\u524D\u6CA1\u6709\u6D3B\u52A8\u4F1A\u8BDD\uFF0C\u65E0\u53EF\u56DE\u9000\u9879\u3002",
203
+ rewindEmpty: "\u5F53\u524D\u4F1A\u8BDD\u6CA1\u6709\u53EF\u56DE\u9000\u7684\u6587\u4EF6\u6539\u52A8\u3002",
204
+ rewindFail: (e) => `\u56DE\u9000\u5931\u8D25\uFF1A${e}`,
205
+ cmdDiff: "\u672C run \u6587\u4EF6\u6539\u52A8\u7EA2\u7EFF\u6C47\u603B\uFF08\u5907\u4EFD vs \u5F53\u524D\u78C1\u76D8\u7684\u51C0\u53D8\u5316\uFF09",
206
+ diffTitle: "\u{1F4C4} \u672C run \u6587\u4EF6\u6539\u52A8\u6C47\u603B",
207
+ diffEmpty: "\u672C run \u6CA1\u6709\u6587\u4EF6\u6539\u52A8\u8BB0\u5F55\uFF08\u6539\u5B8C\u4E00\u8F6E\u540E\u518D\u8BD5 /diff\uFF09\u3002",
194
208
  modelUsage: (current) => `\u5F53\u524D\u6A21\u578B\uFF1A${current || "\u9ED8\u8BA4\uFF08DEEP_SEEK_MODEL\uFF09"}
195
209
  \u7528\u6CD5\uFF1A/model <\u6A21\u578Bid> \u76F4\u8F93\u5207\u6362\uFF0C\u6216 /switch \u5F39\u51FA\u5019\u9009\u9009\u62E9\u5668`,
196
210
  modelPickerTitle: (current) => `\u{1F9E0} \u9009\u62E9\u6A21\u578B\uFF08\u5F53\u524D\uFF1A${current || "\u9ED8\u8BA4"}\uFF09`,
@@ -263,6 +277,9 @@ ${s}`,
263
277
  "/sessions \u2014 \u9009\u62E9\u5E76\u8F7D\u5165\u5386\u53F2\u4F1A\u8BDD\uFF08\u7EED\u63A5\u5BF9\u8BDD\uFF09",
264
278
  "/new \u2014 \u5F00\u542F\u65B0\u4F1A\u8BDD\uFF08\u65E7\u4F1A\u8BDD\u4FDD\u7559\uFF0C/sessions \u53EF\u627E\u56DE\uFF09",
265
279
  "/fork \u2014 \u4ECE\u5F53\u524D\u4F1A\u8BDD\u7684\u67D0\u8F6E\u56DE\u590D\u5904\u5206\u53C9\u51FA\u65B0\u4F1A\u8BDD",
280
+ "/undo \u2014 \u64A4\u9500\u6700\u8FD1\u4E00\u6B21\u6587\u4EF6\u6539\u52A8\uFF08\u81EA\u52A8\u5907\u4EFD\uFF0C\u53EF\u9006\uFF09",
281
+ "/rewind \u2014 \u6309\u68C0\u67E5\u70B9\u9009\u62E9\u56DE\u9000\u6587\u4EF6\u6539\u52A8\uFF08\u2191\u2193 \u9009\u62E9\uFF09",
282
+ "/diff \u2014 \u672C run \u6587\u4EF6\u6539\u52A8\u7EA2\u7EFF\u6C47\u603B",
266
283
  "/trust \u2014 \u7BA1\u7406\u5DF2\u4FE1\u4EFB\u76EE\u5F55\uFF08\u9879\u76EE\u7EA7 hooks/skills \u7B49\u4EC5\u5728\u4FE1\u4EFB\u76EE\u5F55\u52A0\u8F7D\uFF1BCI \u7528 --trust \u663E\u5F0F\u4FE1\u4EFB\uFF09",
267
284
  "Ctrl+C \u9000\u51FA \xB7 Esc \u4E2D\u6B62/\u6E05\u8F93\u5165 \xB7 Ctrl+G \u4E2D\u6B62 \xB7 Ctrl+T \u5C55\u5F00/\u6536\u8D77\u601D\u8003"
268
285
  ].join("\n"),
@@ -301,13 +318,16 @@ ${s}`,
301
318
  planEditTitle: "\u270F\uFE0F Edit plan",
302
319
  planEditHint: "Enter to implement with this plan \xB7 Esc back to options",
303
320
  planKeepPlanning: "\u270B Staying in plan mode: add feedback and the plan will be revised and resubmitted (/plan to exit).",
321
+ planNoProposal: "\u2139\uFE0F No plan was proposed this turn (exit_plan_mode not called) \u2014 still in plan mode: keep describing the task, or /plan to exit.",
304
322
  planEditEmpty: "Plan cannot be empty",
305
323
  planAutoExecute: "\u26A1 Auto-run: implementation tools will execute without approval.",
306
324
  inputBadgePlan: "plan mode on (read-only research \u2192 approve \u2192 implement)",
307
325
  inputBadgeAuto: "auto-accept edits on",
308
326
  approvalTitle: "\u{1F510} Action approval",
309
327
  approvalPrompt: "\u2191\u2193 then Enter (Esc to deny): allow once / always allow (persist rule) / deny.",
328
+ approvalFullHint: "(f to view/collapse full content)",
310
329
  approvalAllow: "Allow once",
330
+ approvalAllowSession: "Allow for this session (in-memory)",
311
331
  approvalAllowAlways: "Always allow (this project)",
312
332
  approvalDeny: "Deny",
313
333
  errAborted: "Aborted current turn.",
@@ -373,6 +393,17 @@ ${dim} Run npm i -g deepseeker-code@latest to update${reset}
373
393
  forkEmpty: "(no checkpoints to fork from in this session)",
374
394
  forkDone: (id, roundNo) => `Forked from round ${roundNo} \u2192 new session ${id} (loaded; new messages go to the fork, source untouched)`,
375
395
  forkFailed: (e) => `Fork failed: ${e}`,
396
+ cmdUndo: "Undo the most recent file change (LIFO, repeatable; reverse-backed-up, reversible)",
397
+ undoNoSession: "No active session \u2014 nothing to undo.",
398
+ cmdRewind: "Rewind file changes \u2014 pick a checkpoint from the list",
399
+ rewindTitle: "\u23EA Rewind file changes (\u2191\u2193 select \xB7 Enter rewind \xB7 Esc cancel)",
400
+ rewindPrompt: "\u2191\u2193 select \xB7 Enter rewind \xB7 Esc cancel",
401
+ rewindNoSession: "No active session \u2014 nothing to rewind.",
402
+ rewindEmpty: "No rewindable file changes in this session.",
403
+ rewindFail: (e) => `Rewind failed: ${e}`,
404
+ cmdDiff: "Red/green summary of file changes in this run (backup vs current disk)",
405
+ diffTitle: "\u{1F4C4} File changes in this run",
406
+ diffEmpty: "No file changes recorded in this run (try /diff after a turn with edits).",
376
407
  modelUsage: (current) => `Current model: ${current || "default (DEEP_SEEK_MODEL)"}
377
408
  Usage: /model <model-id> to switch directly, or /switch for the picker`,
378
409
  modelPickerTitle: (current) => `\u{1F9E0} Pick a model (current: ${current || "default"})`,
@@ -445,6 +476,9 @@ ${s}`,
445
476
  "/sessions \u2014 Pick a past session to resume",
446
477
  "/new \u2014 Start a new session (old ones remain via /sessions)",
447
478
  "/fork \u2014 Fork the current session from a past round",
479
+ "/undo \u2014 Undo the most recent file change (auto-backed-up, reversible)",
480
+ "/rewind \u2014 Rewind file changes \u2014 pick a checkpoint from the list",
481
+ "/diff \u2014 Red/green summary of file changes in this run",
448
482
  "/trust \u2014 Manage trusted dirs (project hooks/skills load only when trusted; CI uses --trust)",
449
483
  "Ctrl+C exit \xB7 Esc abort/clear input \xB7 Ctrl+G abort \xB7 Ctrl+T toggle thinking"
450
484
  ].join("\n"),
@@ -465,7 +499,7 @@ ${s}`,
465
499
  S = new Proxy({}, {
466
500
  get: (_t, k) => typeof k === "string" ? STRINGS[locale][k] : void 0
467
501
  });
468
- LOCAL_COMMAND_NAMES = ["help", "status", "plan", "auto", "model", "switch", "thinking", "lang", "output-style", "new", "sessions", "fork", "usage", "budget", "context", "permissions", "mcp", "hooks", "trust", "debug", "clear", "exit"];
502
+ LOCAL_COMMAND_NAMES = ["help", "status", "plan", "auto", "model", "switch", "thinking", "lang", "output-style", "new", "sessions", "fork", "undo", "usage", "rewind", "diff", "budget", "context", "permissions", "mcp", "hooks", "trust", "debug", "clear", "exit"];
469
503
  }
470
504
  });
471
505
 
@@ -474,18 +508,31 @@ var config_exports = {};
474
508
  __export(config_exports, {
475
509
  ENGINE_OVERRIDE_KEYS: () => ENGINE_OVERRIDE_KEYS,
476
510
  ENV_SOURCES: () => ENV_SOURCES,
511
+ MODEL_WINDOWS: () => MODEL_WINDOWS,
477
512
  appConfig: () => appConfig,
478
- configSources: () => configSources
513
+ configSources: () => configSources,
514
+ deriveWorkspaceKey: () => deriveWorkspaceKey,
515
+ resolveModelWindow: () => resolveModelWindow
479
516
  });
480
517
  import path3 from "path";
481
518
  import os3 from "os";
482
519
  import { readFileSync as readFileSync3 } from "fs";
483
520
  import { createHash } from "crypto";
484
- var _DataDir, appConfig, ENV_SOURCES, configSources, ENGINE_OVERRIDE_VALIDATORS, ENGINE_OVERRIDE_KEYS, applyEngineOverrides;
521
+ var _DataDir, deriveWorkspaceKey, appConfig, MODEL_WINDOWS, resolveModelWindow, ENV_SOURCES, configSources, ENGINE_OVERRIDE_VALIDATORS, ENGINE_OVERRIDE_KEYS, applyEngineOverrides;
485
522
  var init_config = __esm({
486
523
  "../core/src/config/index.ts"() {
487
524
  "use strict";
488
525
  _DataDir = process.env.DEEPSEEKER_CODE_DATA_DIR ? path3.resolve(process.env.DEEPSEEKER_CODE_DATA_DIR) : path3.join(os3.homedir(), ".deepseeker-code");
526
+ deriveWorkspaceKey = (dir) => {
527
+ let cwd = path3.resolve(dir).replace(/\\/g, "/");
528
+ if (/^[a-z]:/i.test(cwd)) {
529
+ cwd = cwd.charAt(0).toUpperCase() + cwd.slice(1);
530
+ }
531
+ const currentFolder = path3.basename(cwd);
532
+ const parentFolder = path3.basename(path3.dirname(cwd));
533
+ const hash = createHash("sha256").update(cwd).digest("hex").slice(0, 8);
534
+ return `${parentFolder}-${currentFolder}-${hash}`;
535
+ };
489
536
  appConfig = {
490
537
  dataDir: _DataDir,
491
538
  maxReasoningRounds: 3,
@@ -513,16 +560,7 @@ var init_config = __esm({
513
560
  * env RUN_COMMAND_AUTO_BG_MS 覆盖。勿把 IDLE 调到高于 DEEP_SEEK_STREAM_IDLE_TIMEOUT_MS
514
561
  * (空闲分支到期会产出降级消息喂流,不会饿死 collectToolResult 的 idle 熔断)。 */
515
562
  runCommandAutoBgMs: Number(process.env.RUN_COMMAND_AUTO_BG_MS) || 12e4,
516
- userWorkspaceDir: (() => {
517
- let cwd = process.cwd().replace(/\\/g, "/");
518
- if (/^[a-z]:/i.test(cwd)) {
519
- cwd = cwd.charAt(0).toUpperCase() + cwd.slice(1);
520
- }
521
- const currentFolder = path3.basename(cwd);
522
- const parentFolder = path3.basename(path3.dirname(cwd));
523
- const hash = createHash("sha256").update(cwd).digest("hex").slice(0, 8);
524
- return `${parentFolder}-${currentFolder}-${hash}`;
525
- })(),
563
+ userWorkspaceDir: deriveWorkspaceKey(process.cwd()),
526
564
  traceRetentionDays: 7,
527
565
  /** Undo(文件回退)总开关:false 时跳过所有写前备份,紧急降级用。 */
528
566
  undoEnabled: true,
@@ -588,6 +626,12 @@ var init_config = __esm({
588
626
  * 行为与改造前一致。env DEEP_SEEK_HOOK_REWRITE=0 回退。 */
589
627
  hookRewrite: process.env.DEEP_SEEK_HOOK_REWRITE !== "0"
590
628
  };
629
+ MODEL_WINDOWS = {};
630
+ resolveModelWindow = (model) => {
631
+ const id = model?.trim();
632
+ const hit = id ? MODEL_WINDOWS[id] : void 0;
633
+ return typeof hit === "number" && hit > 0 ? hit : appConfig.MAX_HISTORY_TOKENS;
634
+ };
591
635
  ENV_SOURCES = {
592
636
  dataDir: "DEEPSEEKER_CODE_DATA_DIR",
593
637
  searchProvider: "SEARCH_PROVIDER",
@@ -1132,10 +1176,10 @@ var init_batchStrings = __esm({
1132
1176
  const money = c.costYuan !== null ? `\xA5${c.costYuan.toFixed(4)}` : "\uFF08\u65E0\u4EF7\u6A21\u578B\uFF0C\u4E0D\u8BA1\u94B1\uFF09";
1133
1177
  const hit = c.promptTokens > 0 ? Math.round(c.cachedTokens / c.promptTokens * 100) : 0;
1134
1178
  const mins = Math.round(c.durationMs / 6e3) / 10;
1135
- return `${c.rounds} \u8F6E \xB7 ${money} \xB7 \u7F13\u5B58\u547D\u4E2D ${hit}% \xB7 ${mins}min${c.partialPrice ? "\uFF08\u90E8\u5206\u8BB0\u5F55\u65E0\u4EF7\uFF0C\u94B1\u4E3A\u90E8\u5206\u548C\uFF09" : ""}${c.subRuns > 0 ? ` \xB7 \u5B50 run ${c.subRuns} \u4E2A\u5DF2\u8BA1\u5165` : ""}${c.toolCostYuan > 0 ? ` \xB7 \u8054\u7F51 \xA5${c.toolCostYuan.toFixed(4)}` : ""}`;
1179
+ return `${c.rounds} \u8F6E \xB7 ${money} \xB7 \u7F13\u5B58\u547D\u4E2D ${hit}% \xB7 ${mins}min${c.partialPrice ? "\uFF08\u90E8\u5206\u8BB0\u5F55\u65E0\u4EF7\uFF0C\u94B1\u4E3A\u90E8\u5206\u548C\uFF09" : ""}${c.subRuns > 0 ? ` \xB7 \u5B50 run ${c.subRuns} \u4E2A\u5DF2\u8BA1\u5165` : ""}${c.toolCostYuan > 0 ? ` \xB7 \u8054\u7F51 \xA5${c.toolCostYuan.toFixed(4)}` : ""}${c.auxCostYuan > 0 ? ` \xB7 \u8F85\u52A9 \xA5${c.auxCostYuan.toFixed(4)}` : ""}`;
1136
1180
  },
1137
1181
  noReceipt: "\uFF08\u65E0\u7ED3\u7B97\u5355\u2014\u2014run \u672A\u5230\u6536\u5C3E\u5373\u88AB\u622A\u65AD\uFF0C\u82B1\u8D39\u4EE5\u4E0A\u65B9 usageLog \u805A\u5408\u4E3A\u51C6\uFF09",
1138
- receiptLine: (r) => `${r.rounds} \u8F6E \xB7 ${r.costYuan !== null ? `\xA5${r.costYuan.toFixed(4)}` : "\u65E0\u4EF7"} \xB7 \u7F13\u5B58\u547D\u4E2D ${Math.round(r.cacheHitRate * 100)}% \xB7 ${(r.durationMs / 1e3).toFixed(0)}s${r.toolCostYuan ? ` \xB7 \u8054\u7F51 \xA5${r.toolCostYuan.toFixed(4)}` : ""}`,
1182
+ receiptLine: (r) => `${r.rounds} \u8F6E \xB7 ${r.costYuan !== null ? `\xA5${r.costYuan.toFixed(4)}` : "\u65E0\u4EF7"} \xB7 \u7F13\u5B58\u547D\u4E2D ${Math.round(r.cacheHitRate * 100)}% \xB7 ${(r.durationMs / 1e3).toFixed(0)}s${r.toolCostYuan ? ` \xB7 \u8054\u7F51 \xA5${r.toolCostYuan.toFixed(4)}` : ""}${r.auxCostYuan ? ` \xB7 \u8F85\u52A9 \xA5${r.auxCostYuan.toFixed(4)}` : ""}`,
1139
1183
  // —— 报告骨架 ——
1140
1184
  rejectedTitle: "# \u6302\u673A\u4EFB\u52A1\u88AB\u62D2",
1141
1185
  reportTitle: (title) => `# \u6302\u673A\u4EFB\u52A1\u62A5\u544A\uFF1A${title}`,
@@ -1232,10 +1276,10 @@ changes are harvested onto an isolated git worktree (temp branch), kept for manu
1232
1276
  const money = c.costYuan !== null ? `\xA5${c.costYuan.toFixed(4)}` : "(unpriced model, no money counted)";
1233
1277
  const hit = c.promptTokens > 0 ? Math.round(c.cachedTokens / c.promptTokens * 100) : 0;
1234
1278
  const mins = Math.round(c.durationMs / 6e3) / 10;
1235
- return `${c.rounds} rounds \xB7 ${money} \xB7 cache hit ${hit}% \xB7 ${mins}min${c.partialPrice ? " (some records unpriced; money is a partial sum)" : ""}${c.subRuns > 0 ? ` \xB7 ${c.subRuns} sub-runs included` : ""}${c.toolCostYuan > 0 ? ` \xB7 web \xA5${c.toolCostYuan.toFixed(4)}` : ""}`;
1279
+ return `${c.rounds} rounds \xB7 ${money} \xB7 cache hit ${hit}% \xB7 ${mins}min${c.partialPrice ? " (some records unpriced; money is a partial sum)" : ""}${c.subRuns > 0 ? ` \xB7 ${c.subRuns} sub-runs included` : ""}${c.toolCostYuan > 0 ? ` \xB7 web \xA5${c.toolCostYuan.toFixed(4)}` : ""}${c.auxCostYuan > 0 ? ` \xB7 aux \xA5${c.auxCostYuan.toFixed(4)}` : ""}`;
1236
1280
  },
1237
1281
  noReceipt: "(no receipt \u2014 the run was truncated before finishing; cost per the usageLog aggregate above)",
1238
- receiptLine: (r) => `${r.rounds} rounds \xB7 ${r.costYuan !== null ? `\xA5${r.costYuan.toFixed(4)}` : "unpriced"} \xB7 cache hit ${Math.round(r.cacheHitRate * 100)}% \xB7 ${(r.durationMs / 1e3).toFixed(0)}s${r.toolCostYuan ? ` \xB7 web \xA5${r.toolCostYuan.toFixed(4)}` : ""}`,
1282
+ receiptLine: (r) => `${r.rounds} rounds \xB7 ${r.costYuan !== null ? `\xA5${r.costYuan.toFixed(4)}` : "unpriced"} \xB7 cache hit ${Math.round(r.cacheHitRate * 100)}% \xB7 ${(r.durationMs / 1e3).toFixed(0)}s${r.toolCostYuan ? ` \xB7 web \xA5${r.toolCostYuan.toFixed(4)}` : ""}${r.auxCostYuan ? ` \xB7 aux \xA5${r.auxCostYuan.toFixed(4)}` : ""}`,
1239
1283
  rejectedTitle: "# Batch task rejected",
1240
1284
  reportTitle: (title) => `# Batch task report: ${title}`,
1241
1285
  statusEntry: (v) => `- Status: ${v}`,
@@ -1453,7 +1497,8 @@ var init_stream = __esm({
1453
1497
  break;
1454
1498
  }
1455
1499
  }
1456
- if (lastNonSystem?.role === "assistant") return messages;
1500
+ const hasSplitToolTurn = messages.some((m, i) => i > 0 && m?.role === "assistant" && messages[i - 1]?.role === "assistant" && Array.isArray(m.tool_calls) && m.tool_calls.length > 0);
1501
+ if (lastNonSystem?.role === "assistant" || hasSplitToolTurn) return messages;
1457
1502
  return messages.map((m) => {
1458
1503
  const wire = m;
1459
1504
  if (wire?.role !== "assistant" || typeof wire.reasoning_content !== "string") return m;
@@ -1565,6 +1610,10 @@ var init_deepseek = __esm({
1565
1610
  const completion = await getModel().chat.completions.create(requestBody, {
1566
1611
  signal: opts.signal
1567
1612
  });
1613
+ try {
1614
+ opts.onAuxUsage?.(completion.usage, "compact");
1615
+ } catch {
1616
+ }
1568
1617
  if (completion && "choices" in completion) return completion;
1569
1618
  throw new Error("API \u54CD\u5E94\u5F02\u5E38\uFF0C\u672A\u5305\u542B choices \u7ED3\u6784");
1570
1619
  } catch (error) {
@@ -1572,7 +1621,7 @@ var init_deepseek = __esm({
1572
1621
  throw error;
1573
1622
  }
1574
1623
  };
1575
- classifyRisk = async (toolName, args, detail, signal) => {
1624
+ classifyRisk = async (toolName, args, detail, signal, onAuxUsage) => {
1576
1625
  const ac = new AbortController();
1577
1626
  const timer = setTimeout(() => ac.abort(), 1e4);
1578
1627
  if (signal) signal.addEventListener("abort", () => ac.abort(), { once: true });
@@ -1588,6 +1637,10 @@ var init_deepseek = __esm({
1588
1637
  thinking: { type: "disabled" },
1589
1638
  stream: false
1590
1639
  }, { signal: ac.signal });
1640
+ try {
1641
+ onAuxUsage?.(completion.usage, "classify");
1642
+ } catch {
1643
+ }
1591
1644
  const text = (completion.choices[0]?.message?.content ?? "").trim().toUpperCase();
1592
1645
  return text.startsWith("SAFE") ? "safe" : "risky";
1593
1646
  } catch {
@@ -1945,7 +1998,7 @@ var init_model = __esm({
1945
1998
  };
1946
1999
  activeProvider = resolveInitialProvider();
1947
2000
  chatWithModelWithSummary = (messages, tools, opts) => activeProvider.summarize(messages, tools, opts);
1948
- classifyToolRisk = (toolName, args, detail, signal) => activeProvider.classifyRisk(toolName, args, detail, signal);
2001
+ classifyToolRisk = (toolName, args, detail, signal, onAuxUsage) => activeProvider.classifyRisk(toolName, args, detail, signal, onAuxUsage);
1949
2002
  }
1950
2003
  });
1951
2004
 
@@ -2058,8 +2111,10 @@ var init_contextCore = __esm({
2058
2111
  // ../core/src/session/store.ts
2059
2112
  var store_exports = {};
2060
2113
  __export(store_exports, {
2114
+ calibrationEntryFor: () => calibrationEntryFor,
2061
2115
  deleteSession: () => deleteSession,
2062
2116
  ensureSessionsDir: () => ensureSessionsDir,
2117
+ getCalibration: () => getCalibration,
2063
2118
  getMostRecentSessionId: () => getMostRecentSessionId,
2064
2119
  getOrCreateSessionId: () => getOrCreateSessionId,
2065
2120
  getRollingState: () => getRollingState,
@@ -2088,7 +2143,9 @@ async function getRollingState(sessionId) {
2088
2143
  archivedMessageCount: typeof store.archivedMessageCount === "number" && store.archivedMessageCount >= 0 ? store.archivedMessageCount : 0,
2089
2144
  // 读取持久化的连续失败次数,若不存在则默认为 0
2090
2145
  consecutiveFailures: typeof store.consecutiveFailures === "number" ? store.consecutiveFailures : 0,
2091
- // ★ 跨 run 持久化的压缩校准状态(缺省 undefined,调用方按 ?? 回落默认)
2146
+ // ★ 跨 run 持久化的压缩校准状态(缺省 undefined,调用方按 ?? 回落默认)。
2147
+ // P1-4 起这是「扁平镜像视图」:压缩主链路(runAgent/compactSessionNow)须走 getCalibration(sessionId, modelId)
2148
+ // 按 per-model 分键读——切模型后各模型校准互不污染,扁平字段只是最近写入模型的镜像。
2092
2149
  calibRatio: typeof store.calibRatio === "number" ? store.calibRatio : void 0,
2093
2150
  lastRealPromptTokens: typeof store.lastRealPromptTokens === "number" ? store.lastRealPromptTokens : void 0,
2094
2151
  lastCachedTokens: typeof store.lastCachedTokens === "number" ? store.lastCachedTokens : void 0
@@ -2115,7 +2172,7 @@ async function setTodos(sessionId, todos) {
2115
2172
  await writeStore(sessionId, store);
2116
2173
  });
2117
2174
  }
2118
- var getSessionsDirPath, getWorkspaceSessionsDir, getStatePath, getTranscriptPath, ensuredSessionDirs, ensureSessionsDir, migrateLegacyFiles, writeStore, storeLocks, withStoreLock, getOrCreateSessionId, readStore, updateCalibration, listSessions, getMostRecentSessionId, renameSession, deleteSession;
2175
+ var getSessionsDirPath, getWorkspaceSessionsDir, getStatePath, getTranscriptPath, ensuredSessionDirs, ensureSessionsDir, migrateLegacyFiles, writeStore, storeLocks, withStoreLock, getOrCreateSessionId, readStore, calibrationEntryFor, getCalibration, updateCalibration, listSessions, getMostRecentSessionId, renameSession, deleteSession;
2119
2176
  var init_store = __esm({
2120
2177
  "../core/src/session/store.ts"() {
2121
2178
  "use strict";
@@ -2222,8 +2279,11 @@ var init_store = __esm({
2222
2279
  // 总条数消息
2223
2280
  rollingSummary: "",
2224
2281
  // 滚动总结摘要
2225
- consecutiveFailures: 0
2282
+ consecutiveFailures: 0,
2226
2283
  // 失败消息
2284
+ // ★ 项目根盖章(2026-10-21):记录会话归属项目,会话按项目分桶外的兜底元信息——
2285
+ // 多根工作区切根修复后分桶键即跟随项目,此章供诊断/未来跨桶工具用(旧会话无此字段)。
2286
+ projectRoot: process.cwd()
2227
2287
  };
2228
2288
  if (!isPersisted) {
2229
2289
  await writeStore(entry.sessionId, entry);
@@ -2250,13 +2310,41 @@ var init_store = __esm({
2250
2310
  throw err;
2251
2311
  }
2252
2312
  };
2253
- updateCalibration = async (sessionId, calib) => {
2313
+ calibrationEntryFor = (store, modelId) => {
2314
+ if (modelId) {
2315
+ const byModel = store?.calibByModel;
2316
+ const hit = byModel && typeof byModel === "object" ? byModel[modelId] : void 0;
2317
+ if (hit && typeof hit === "object") {
2318
+ return {
2319
+ calibRatio: typeof hit.calibRatio === "number" ? hit.calibRatio : void 0,
2320
+ lastRealPromptTokens: typeof hit.lastRealPromptTokens === "number" ? hit.lastRealPromptTokens : void 0,
2321
+ lastCachedTokens: typeof hit.lastCachedTokens === "number" ? hit.lastCachedTokens : void 0
2322
+ };
2323
+ }
2324
+ }
2325
+ return {
2326
+ calibRatio: typeof store?.calibRatio === "number" ? store.calibRatio : void 0,
2327
+ lastRealPromptTokens: typeof store?.lastRealPromptTokens === "number" ? store.lastRealPromptTokens : void 0,
2328
+ lastCachedTokens: typeof store?.lastCachedTokens === "number" ? store.lastCachedTokens : void 0
2329
+ };
2330
+ };
2331
+ getCalibration = async (sessionId, modelId) => {
2332
+ const store = await readStore(sessionId);
2333
+ return calibrationEntryFor(store, modelId);
2334
+ };
2335
+ updateCalibration = async (sessionId, modelId, calib) => {
2254
2336
  return withStoreLock(sessionId, async () => {
2255
2337
  const store = await readStore(sessionId);
2256
2338
  if (!store) return;
2257
- if (typeof calib.calibRatio === "number" && Number.isFinite(calib.calibRatio)) store.calibRatio = calib.calibRatio;
2258
- if (typeof calib.lastRealPromptTokens === "number") store.lastRealPromptTokens = calib.lastRealPromptTokens;
2259
- if (typeof calib.lastCachedTokens === "number") store.lastCachedTokens = calib.lastCachedTokens;
2339
+ const id = modelId?.trim() || "default";
2340
+ const byModel = store.calibByModel && typeof store.calibByModel === "object" ? store.calibByModel : store.calibByModel = {};
2341
+ const cur = byModel[id] && typeof byModel[id] === "object" ? byModel[id] : byModel[id] = {};
2342
+ if (typeof calib.calibRatio === "number" && Number.isFinite(calib.calibRatio)) cur.calibRatio = calib.calibRatio;
2343
+ if (typeof calib.lastRealPromptTokens === "number") cur.lastRealPromptTokens = calib.lastRealPromptTokens;
2344
+ if (typeof calib.lastCachedTokens === "number") cur.lastCachedTokens = calib.lastCachedTokens;
2345
+ if (typeof cur.calibRatio === "number") store.calibRatio = cur.calibRatio;
2346
+ if (typeof cur.lastRealPromptTokens === "number") store.lastRealPromptTokens = cur.lastRealPromptTokens;
2347
+ if (typeof cur.lastCachedTokens === "number") store.lastCachedTokens = cur.lastCachedTokens;
2260
2348
  await writeStore(sessionId, store);
2261
2349
  });
2262
2350
  };
@@ -2315,7 +2403,8 @@ var init_store = __esm({
2315
2403
  createAt: state?.createAt,
2316
2404
  updatedAt,
2317
2405
  messageCount,
2318
- preview: preview.slice(0, 80)
2406
+ preview: preview.slice(0, 80),
2407
+ projectRoot: typeof state?.projectRoot === "string" ? state.projectRoot : void 0
2319
2408
  });
2320
2409
  }
2321
2410
  summaries.sort((a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""));
@@ -2664,6 +2753,8 @@ var init_planMode = __esm({
2664
2753
  type: "function",
2665
2754
  function: {
2666
2755
  name: "exit_plan_mode",
2756
+ // ★ P1-6 终结声明:调用即提交方案并终结本轮(调度层发 plan.proposed,上层呈现审批)
2757
+ terminal: "plan_exit",
2667
2758
  description: "\u8BA1\u5212\u6A21\u5F0F\u4E13\u7528\uFF1A\u5B8C\u6210\u53EA\u8BFB\u8C03\u7814\u3001\u5F62\u6210\u660E\u786E\u5B9E\u73B0\u65B9\u6848\u540E\u8C03\u7528\u672C\u5DE5\u5177\u63D0\u4EA4\u65B9\u6848\u3002\u63D0\u4EA4\u540E\u65B9\u6848\u4F1A\u5448\u73B0\u7ED9\u7528\u6237\u5BA1\u6279\uFF1B\u5BA1\u6279\u901A\u8FC7\u540E\u624D\u8FDB\u5165\u5B9E\u73B0\u9636\u6BB5\uFF08\u5C4A\u65F6\u624D\u5141\u8BB8\u4FEE\u6539\u6587\u4EF6/\u6267\u884C\u547D\u4EE4\uFF09\u3002\u5728\u8BA1\u5212\u6A21\u5F0F\u4E0B\u4F60\u53EA\u80FD\u8BFB\u53D6\u4E0E\u641C\u7D22\uFF0C\u4E0D\u80FD\u505A\u4EFB\u4F55\u4FEE\u6539\u3002",
2668
2759
  parameters: {
2669
2760
  type: "object",
@@ -2678,6 +2769,8 @@ var init_planMode = __esm({
2678
2769
  type: "function",
2679
2770
  function: {
2680
2771
  name: "enter_plan_mode",
2772
+ // ★ P1-6 终结声明:调用即请求进入计划模式并终结本轮(调度层发 plan.enterRequested)
2773
+ terminal: "plan_enter",
2681
2774
  description: "\u81EA\u4E3B\u8FDB\u5165\u8BA1\u5212\u6A21\u5F0F\uFF1A\u5F53\u4EFB\u52A1\u662F\u975E\u5E73\u51E1\u7684\u5B9E\u73B0\u4EFB\u52A1\uFF08\u591A\u6587\u4EF6\u6539\u52A8\u3001\u67B6\u6784\u51B3\u7B56\u3001\u4E0D\u786E\u5B9A\u8DEF\u5F84\u3001\u9AD8\u98CE\u9669\u64CD\u4F5C\uFF09\u65F6\u8C03\u7528\u3002\u8C03\u7528\u540E\u672C\u8F6E\u7ED3\u675F\u5E76\u8FDB\u5165\u53EA\u8BFB\u8C03\u7814\u9636\u6BB5\u2014\u2014\u4F60\u53EA\u80FD\u8BFB\u53D6\u4E0E\u641C\u7D22\uFF0C\u4EA7\u51FA\u5B8C\u6574\u5B9E\u73B0\u65B9\u6848\uFF08\u8981\u6539\u54EA\u4E9B\u6587\u4EF6\u3001\u600E\u4E48\u6539\u3001\u4E3A\u4F55\u3001\u98CE\u9669\uFF09\u7ECF\u7528\u6237\u5BA1\u6279\u540E\u518D\u5B9E\u73B0\u3002\u7B80\u5355\u4EFB\u52A1\u65E0\u9700\u8C03\u7528\uFF0C\u76F4\u63A5\u5B9E\u73B0\u3002",
2682
2775
  parameters: {
2683
2776
  type: "object",
@@ -2712,7 +2805,12 @@ var init_systemPrompt = __esm({
2712
2805
  - \u5DE5\u5177**\u6267\u884C\u5931\u8D25\u6216\u88AB\u7528\u6237\u62D2\u7EDD**\u65F6\uFF0C\u8BFB\u8FD4\u56DE\u7ED3\u679C\u5B9A\u4F4D\u539F\u56E0\uFF0C\u4FEE\u6B63\u53C2\u6570\u6216\u6362\u7B49\u6548\u65B9\u6848\u7EE7\u7EED\uFF0C\u4E0D\u8981\u9053\u6B49\u4E00\u4E24\u53E5\u5C31\u505C\u4E0B\uFF1B\u786E\u5B9E\u53D7\u963B\u9700\u8981\u7528\u6237\u51B3\u7B56\u65F6\uFF0C\u7528 ask_question \u8BF4\u660E**\u5177\u4F53**\u963B\u585E\u70B9\u3002**\u4E25\u7981**\u5728\u672A\u771F\u6B63\u6267\u884C\u6216\u672A\u9A8C\u8BC1\u7684\u60C5\u51B5\u4E0B\u58F0\u79F0"\u5DF2\u5B8C\u6210/\u5DF2\u4FEE\u6539/\u5DF2\u4FEE\u590D"\u2014\u2014\u6539\u52A8\u82E5\u53EF\u9A8C\u8BC1\uFF08\u91CD\u8BFB\u786E\u8BA4\u3001\u8DD1\u6D4B\u8BD5\u3001\u7C7B\u578B\u68C0\u67E5\uFF09\uFF0C\u5148\u9A8C\u8BC1\u518D\u6536\u5C3E\u3002
2713
2806
 
2714
2807
  \u3010\u56DE\u7B54\u5F62\u5F0F\u3011
2715
- - **\u5148\u7ED3\u8BBA\u540E\u7EC6\u8282**\uFF1A\u5148\u76F4\u63A5\u56DE\u7B54\u6216\u6C47\u62A5\u7ED3\u679C\uFF0C\u518D\u5C55\u5F00\u5FC5\u8981\u7684\u89E3\u91CA\uFF1B\u6536\u5C3E\u603B\u7ED3\u7B80\u77ED\uFF08\u51E0\u884C\u4EE5\u5185\uFF09\uFF0C\u4E0D\u9010\u6587\u4EF6\u6D41\u6C34\u8D26\u3002
2808
+ - **\u5148\u7ED3\u8BBA\u540E\u7EC6\u8282**\uFF1A\u5148\u76F4\u63A5\u56DE\u7B54\u6216\u6C47\u62A5\u7ED3\u679C\uFF0C\u518D\u5C55\u5F00\u5FC5\u8981\u7684\u89E3\u91CA\u3002
2809
+ - **\u8BF4\u4EBA\u8BDD**\uFF1A\u6C47\u62A5\u548C\u6536\u5C3E\u7528\u901A\u4FD7\u8BED\u8A00\u2014\u2014\u5148\u8BB2\u505A\u4E86\u4EC0\u4E48\u3001\u6548\u679C\u662F\u4EC0\u4E48\uFF0C\u518D\u8BB2\u624B\u6BB5\uFF1B\u975E\u5FC5\u8981\u4E0D\u5806\u672F\u8BED\uFF0C\u7ED5\u4E0D\u5F00\u7684\u672F\u8BED\u7D27\u8DDF\u4E00\u53E5\u767D\u8BDD\u89E3\u91CA\uFF1B\u6280\u672F\u7EC6\u8282\u653E\u540E\u9762\uFF0C\u7528\u6237\u8FFD\u95EE\u518D\u5C55\u5F00\u3002
2810
+ - **\u6536\u5C3E\u81EA\u7136\u7B80\u77ED**\uFF1A\u4EFB\u52A1\u6536\u5C3E\u628A\u7ED3\u679C\u8BF4\u6E05\u695A\u5373\u53EF\u2014\u2014\u5148\u4E00\u53E5\u7ED3\u8BBA\uFF08\u505A\u5B8C\u4E86\u4EC0\u4E48\u3001\u6548\u679C\u5982\u4F55\uFF09\uFF0C\u518D\u6309\u9700\u7B80\u8FF0\u6539\u52A8\u8981\u70B9\u4E0E\u9A8C\u8BC1\u7ED3\u679C\uFF0C\u7BC7\u5E45\u4EE5\u8BF4\u6E05\u4E3A\u51C6\uFF08\u4E00\u822C\u51E0\u884C\uFF09\u3002\u4E0D\u5199\u957F\u7BC7\u7ED3\u9898\u62A5\u544A\u3001\u4E0D\u9010\u6587\u4EF6\u6D41\u6C34\u8D26\u3001\u4E0D\u590D\u8FF0\u6267\u884C\u8FC7\u7A0B\uFF1B\u7528\u6237\u8FFD\u95EE\u65F6\u518D\u5C55\u5F00\u3002\u957F\u5EA6\u53C2\u7167\u2014\u2014
2811
+ \u5DF2\u4FEE\u590D\uFF1A\u767B\u5F55\u8FC7\u671F\u540E\u73B0\u5728\u80FD\u6B63\u786E\u8DF3\u56DE\u767B\u5F55\u9875\uFF08\u4E4B\u524D\u4F1A\u505C\u5728\u7A7A\u767D\u9875\uFF09\u3002
2812
+ \u6539\u52A8\uFF1Asrc/auth/redirect.ts \u2014\u2014 \u8FC7\u671F\u5206\u652F\u8865\u4E0A return \u767B\u5F55\u9875\u8DEF\u5F84\u3002
2813
+ \u9A8C\u8BC1\uFF1A\u767B\u5F55\u6A21\u5757\u6D4B\u8BD5 23 \u4F8B\u5168\u90E8\u901A\u8FC7\u3002
2716
2814
  - **\u4E0D\u56DE\u8D34\u5927\u6BB5\u4EE3\u7801**\uFF1A\u6539\u52A8\u5185\u5BB9\u7528\u6237\u5728\u7F16\u8F91\u5668/diff \u91CC\u770B\u5F97\u5230\uFF0C\u6B63\u6587\u53EA\u9700\u4E00\u53E5\u8BDD\u8BF4\u660E\u6539\u4E86\u4EC0\u4E48\u3001\u4E3A\u4EC0\u4E48\uFF0C\u6216\u5F15\u7528\u5173\u952E\u51E0\u884C\u3002
2717
2815
 
2718
2816
  \u3010\u5DE5\u5177\u4F7F\u7528\u51C6\u5219\u3011
@@ -3056,6 +3154,7 @@ var init_truncate = __esm({
3056
3154
  init_content();
3057
3155
  init_systemPrompt();
3058
3156
  init_model();
3157
+ init_createModel();
3059
3158
  init_store();
3060
3159
  init_transcript();
3061
3160
  init_hooks();
@@ -3127,12 +3226,13 @@ var init_truncate = __esm({
3127
3226
  ${tail}`;
3128
3227
  };
3129
3228
  batchSummaryPrompt = (tailContext) => "\u6982\u62EC\u4EE5\u4E0A\u5BF9\u8BDD\u4E0E\u5DE5\u5177\u8C03\u7528\uFF0C\u6309\u300C\u5B8C\u6210\u4E86\u4EC0\u4E48 \u2192 \u6B63\u5728\u505A\u4EC0\u4E48 \u2192 \u4E0B\u4E00\u6B65/\u53D7\u963B\u300D\u7684\u987A\u5E8F\u5199\u6210\u4E00\u6BB5\u3002\u5FC5\u987B\u539F\u6837\u4FDD\u7559\u5173\u952E\u5B9E\u4F53\u540D\uFF08\u6587\u4EF6\u8DEF\u5F84\u3001\u51FD\u6570/\u7C7B\u540D\u3001\u62A5\u9519\u5173\u952E\u8BCD\uFF09\u4EE5\u4FBF\u540E\u7EED\u68C0\u7D22\uFF1B" + (tailContext ? "\u8FD9\u6279\u6458\u8981\u7D27\u90BB\u672A\u538B\u7F29\u7684\u4FDD\u7559\u533A\uFF0C\u8BF7\u989D\u5916\u5199\u660E\u5176\u4E2D\u5BF9\u7D27\u968F\u5176\u540E\u7684\u8FD1\u671F\u5DE5\u4F5C\u6709\u7528\u7684\u80CC\u666F\uFF08\u5DF2\u786E\u8BA4\u7684\u7EA6\u675F\u3001\u6570\u636E\u3001\u7ED3\u8BBA\uFF09\u3002" : "") + '\u5BF9\u8BB0\u4E0D\u51C6\u7684\u7EC6\u8282\u4E0D\u8981\u81C6\u6D4B\uFF0C\u6807\u6CE8"(\u7EC6\u8282\u5DF2\u5F52\u6863)"\u5373\u53EF\u3002\u4E0D\u8981\u8C03\u7528\u5DE5\u5177\u3002';
3130
- compactBatch = async (batch, signal, tailContext) => {
3229
+ compactBatch = async (batch, signal, tailContext, onAuxUsage) => {
3131
3230
  const resp = await chatWithModelWithSummary(
3132
3231
  // ★ 辅助模型是 text-only:送前把 image part 降级为占位符——base64 绝不能进摘要批(烧钱且无意义)
3133
3232
  [...batch.map(degradeImagesForAux), { role: "user", content: batchSummaryPrompt(tailContext) }],
3134
3233
  [],
3135
- { signal }
3234
+ // ★ P0-2:aux usage 回传 run 级成本账(成本信任链:摘要调用不入账则预算被压缩打穿)
3235
+ { signal, onAuxUsage }
3136
3236
  );
3137
3237
  return `- ${resp.choices[0].message.content || ""}`;
3138
3238
  };
@@ -3218,7 +3318,7 @@ ${noteLine}` : noteLine : notes;
3218
3318
  const hasPrevious = trimmedNotes.length > 0 && trimmedNotes !== SLOT_PLACEHOLDER;
3219
3319
  return { index, hasPrevious };
3220
3320
  };
3221
- synthesizeSlotNarrative = async (slotContent, newLines, signal) => {
3321
+ synthesizeSlotNarrative = async (slotContent, newLines, signal, onAuxUsage) => {
3222
3322
  const { index, hasPrevious } = parseSlotNarrativeState(slotContent);
3223
3323
  const lines = newLines.trim();
3224
3324
  const resp = await chatWithModelWithSummary(
@@ -3230,12 +3330,12 @@ ${lines}
3230
3330
  ` : ""}${checkpointPrompt(hasPrevious)}` }
3231
3331
  ],
3232
3332
  [],
3233
- { signal }
3333
+ { signal, onAuxUsage }
3234
3334
  );
3235
3335
  const newNotes = (resp.choices[0].message.content || "").trim();
3236
3336
  return assembleSlot(index, newNotes);
3237
3337
  };
3238
- synthesizeSlotNarrativeFromBatch = async (slotContent, batch, signal) => {
3338
+ synthesizeSlotNarrativeFromBatch = async (slotContent, batch, signal, onAuxUsage) => {
3239
3339
  const { index, hasPrevious } = parseSlotNarrativeState(slotContent);
3240
3340
  const resp = await chatWithModelWithSummary(
3241
3341
  [
@@ -3244,12 +3344,12 @@ ${lines}
3244
3344
  { role: "user", content: checkpointDirectPrompt(hasPrevious) }
3245
3345
  ],
3246
3346
  [],
3247
- { signal }
3347
+ { signal, onAuxUsage }
3248
3348
  );
3249
3349
  const newNotes = (resp.choices[0].message.content || "").trim();
3250
3350
  return assembleSlot(index, newNotes);
3251
3351
  };
3252
- compactSlotNarrative = async (slotContent, _modelWindow, signal) => synthesizeSlotNarrative(slotContent, "", signal);
3352
+ compactSlotNarrative = async (slotContent, _modelWindow, signal, onAuxUsage) => synthesizeSlotNarrative(slotContent, "", signal, onAuxUsage);
3253
3353
  MAX_BATCH_TOKENS = 16e3;
3254
3354
  PRETRUNCATE_FLOOR_TOKENS = 512;
3255
3355
  foldableTokensOf = (m) => Math.max(0, estimateTokens([m]) - 4);
@@ -3335,11 +3435,11 @@ ${lines}
3335
3435
  if (batch.length > 0) batches.push(batch);
3336
3436
  return batches;
3337
3437
  };
3338
- compactBatches = async (batches, signal, onUIEvent) => {
3438
+ compactBatches = async (batches, signal, onUIEvent, onAuxUsage) => {
3339
3439
  if (onUIEvent && batches.length > 0) onUIEvent({ type: "compact.progress", done: 0, total: batches.length });
3340
3440
  let done = 0;
3341
3441
  const lines = await Promise.all(batches.map(
3342
- (b, i) => compactBatch(b, signal, i === batches.length - 1).then((line) => {
3442
+ (b, i) => compactBatch(b, signal, i === batches.length - 1, onAuxUsage).then((line) => {
3343
3443
  done++;
3344
3444
  onUIEvent?.({ type: "compact.progress", done, total: batches.length });
3345
3445
  return line;
@@ -3409,9 +3509,9 @@ ${lines}
3409
3509
  if (toCompact.length > 0) {
3410
3510
  summaryMsg.content = mergeSummarySlot(summaryMsg?.content || "", "", extractArchiveEntities(toCompact));
3411
3511
  const batches = splitIntoBatches(toCompact);
3412
- summaryMsg.content = batches.length === 1 ? await synthesizeSlotNarrativeFromBatch(summaryMsg.content, batches[0], event.signal) : await synthesizeSlotNarrative(summaryMsg.content, await compactBatches(batches, event.signal, event.depth === 0 ? event.onUIEvent : void 0), event.signal);
3512
+ summaryMsg.content = batches.length === 1 ? await synthesizeSlotNarrativeFromBatch(summaryMsg.content, batches[0], event.signal, event.onAuxUsage) : await synthesizeSlotNarrative(summaryMsg.content, await compactBatches(batches, event.signal, event.depth === 0 ? event.onUIEvent : void 0, event.onAuxUsage), event.signal, event.onAuxUsage);
3413
3513
  if (estimateTokens([summaryMsg]) > SUMMARY_SELF_COMPACT_THRESHOLD) {
3414
- summaryMsg.content = await compactSlotNarrative(summaryMsg.content, event.modelWindow, event.signal);
3514
+ summaryMsg.content = await compactSlotNarrative(summaryMsg.content, event.modelWindow, event.signal, event.onAuxUsage);
3415
3515
  }
3416
3516
  const endTime = performance.now();
3417
3517
  event.messageArr.length = 0;
@@ -3438,7 +3538,7 @@ ${lines}
3438
3538
  keep--;
3439
3539
  continue;
3440
3540
  } else if (summaryMsg?.content) {
3441
- summaryMsg.content = await compactSlotNarrative(summaryMsg.content, event.modelWindow, event.signal);
3541
+ summaryMsg.content = await compactSlotNarrative(summaryMsg.content, event.modelWindow, event.signal, event.onAuxUsage);
3442
3542
  event.messageArr.length = 0;
3443
3543
  event.messageArr.push(systemMsg, summaryMsg, ...keepRecent);
3444
3544
  const store = await getRollingState(event.sessionId);
@@ -3523,7 +3623,7 @@ ${lines}
3523
3623
  // ../core/src/tool/permissions.ts
3524
3624
  import fs10 from "fs/promises";
3525
3625
  import path12 from "path";
3526
- var rules2, trustedProject, globCache, globToRegex, DANGER_COMMAND_TOOLS, compileRule, readPermissionConfig, toolNameMatches, ruleMatches, initPermissions, checkPermission, listPermissionRules, buildScopedAllowRule, resolveSettingsPath, addPermissionRule;
3626
+ var rules2, trustedProject, sessionBuckets, SESSION_BUCKET_CAP, bucketFor, clearSessionPermissionRules, globCache, globToRegex, DANGER_COMMAND_TOOLS, compileRule, readPermissionConfig, toolNameMatches, ruleMatches, initPermissions, checkPermission, listPermissionRules, buildScopedAllowRule, resolveSettingsPath, addSessionPermissionRule, addPermissionRule;
3527
3627
  var init_permissions = __esm({
3528
3628
  "../core/src/tool/permissions.ts"() {
3529
3629
  "use strict";
@@ -3531,6 +3631,23 @@ var init_permissions = __esm({
3531
3631
  init_common();
3532
3632
  rules2 = { allow: [], deny: [], ask: [] };
3533
3633
  trustedProject = false;
3634
+ sessionBuckets = /* @__PURE__ */ new Map();
3635
+ SESSION_BUCKET_CAP = 64;
3636
+ bucketFor = (sessionId) => {
3637
+ let bucket = sessionBuckets.get(sessionId);
3638
+ if (!bucket) {
3639
+ if (sessionBuckets.size >= SESSION_BUCKET_CAP) {
3640
+ const oldest = sessionBuckets.keys().next().value;
3641
+ if (oldest !== void 0) sessionBuckets.delete(oldest);
3642
+ }
3643
+ bucket = { allow: [], deny: [], ask: [] };
3644
+ sessionBuckets.set(sessionId, bucket);
3645
+ }
3646
+ return bucket;
3647
+ };
3648
+ clearSessionPermissionRules = (sessionId) => {
3649
+ sessionBuckets.delete(sessionId);
3650
+ };
3534
3651
  globCache = /* @__PURE__ */ new Map();
3535
3652
  globToRegex = (glob) => {
3536
3653
  const cached2 = globCache.get(glob);
@@ -3616,6 +3733,7 @@ var init_permissions = __esm({
3616
3733
  };
3617
3734
  initPermissions = async (includeProject) => {
3618
3735
  trustedProject = includeProject;
3736
+ sessionBuckets.clear();
3619
3737
  try {
3620
3738
  rules2 = await readPermissionConfig(includeProject);
3621
3739
  const total = rules2.allow.length + rules2.deny.length + rules2.ask.length;
@@ -3627,11 +3745,15 @@ var init_permissions = __esm({
3627
3745
  rules2 = { allow: [], deny: [], ask: [] };
3628
3746
  }
3629
3747
  };
3630
- checkPermission = (toolName, args, primaryArg) => {
3748
+ checkPermission = (toolName, args, primaryArg, sessionId) => {
3631
3749
  try {
3632
- for (const r of rules2.deny) if (ruleMatches(r, toolName, args, primaryArg)) return "deny";
3633
- for (const r of rules2.ask) if (ruleMatches(r, toolName, args, primaryArg)) return "ask";
3634
- for (const r of rules2.allow) if (ruleMatches(r, toolName, args, primaryArg)) return "allow";
3750
+ const bucket = sessionId ? sessionBuckets.get(sessionId) : void 0;
3751
+ const deny = bucket ? [...rules2.deny, ...bucket.deny] : rules2.deny;
3752
+ const ask = bucket ? [...rules2.ask, ...bucket.ask] : rules2.ask;
3753
+ const allow = bucket ? [...rules2.allow, ...bucket.allow] : rules2.allow;
3754
+ for (const r of deny) if (ruleMatches(r, toolName, args, primaryArg)) return "deny";
3755
+ for (const r of ask) if (ruleMatches(r, toolName, args, primaryArg)) return "ask";
3756
+ for (const r of allow) if (ruleMatches(r, toolName, args, primaryArg)) return "allow";
3635
3757
  return null;
3636
3758
  } catch {
3637
3759
  return null;
@@ -3649,7 +3771,7 @@ var init_permissions = __esm({
3649
3771
  const cmd = val.trim();
3650
3772
  return cmd ? `${toolName}(${cmd})` : null;
3651
3773
  }
3652
- if (argKey === "path" || argKey === "src") {
3774
+ if (argKey === "path") {
3653
3775
  const norm = val.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^[a-zA-Z]:/, "");
3654
3776
  const slashIdx = norm.indexOf("/");
3655
3777
  if (slashIdx > 0) {
@@ -3661,6 +3783,13 @@ var init_permissions = __esm({
3661
3783
  return `${toolName}(${val})`;
3662
3784
  };
3663
3785
  resolveSettingsPath = (scope) => scope === "global" ? path12.join(appConfig.dataDir, "settings.json") : path12.join(process.cwd(), ".deepseeker-code", "settings.json");
3786
+ addSessionPermissionRule = (ruleStr, kind = "allow", sessionId) => {
3787
+ const compiled2 = compileRule(ruleStr, "runtime:addSessionPermissionRule(session)", kind);
3788
+ if (!compiled2) return false;
3789
+ const target = sessionId ? bucketFor(sessionId) : rules2;
3790
+ if (!target[kind].some((r) => r.raw === compiled2.raw)) target[kind].push(compiled2);
3791
+ return true;
3792
+ };
3664
3793
  addPermissionRule = async (scope, kind, ruleStr) => {
3665
3794
  if (scope === "project" && !trustedProject) return false;
3666
3795
  const compiled2 = compileRule(ruleStr, `runtime:addPermissionRule(${scope})`, kind);
@@ -3988,10 +4117,17 @@ var init_guard = __esm({
3988
4117
  console.log(`\u23F3 [\u5BA1\u6279\u6302\u8D77] ${toolName} | \u4F1A\u8BDD=${ctx.sessionId} | \u51ED\u8BC1=${toolCallId}\uFF08\u4EA4\u7531\u5BBF\u4E3B\u5BA1\u6279\uFF09`);
3989
4118
  await dispatch("PermissionRequest", { sessionId: ctx.sessionId, cwd: ctx.cwd, toolName, toolCallId, detail: safeDetail, safetyLevel, args }).catch(() => {
3990
4119
  });
3991
- const decision = await ctx.requestApproval(safeDetail, { toolName, toolCallId, sessionId: ctx.sessionId });
4120
+ const decision = await ctx.requestApproval(safeDetail, { toolName, toolCallId, sessionId: ctx.sessionId, fullDetail: detail });
3992
4121
  const approved2 = decision !== "deny";
3993
4122
  if (!approved2) {
3994
4123
  ctx.onUIEvent?.({ type: "tool.denied", toolsId: toolCallId, toolName });
4124
+ } else if (decision === "allow-session") {
4125
+ const ruleStr = buildScopedAllowRule(toolName, args, primaryArg);
4126
+ if (ruleStr && addSessionPermissionRule(ruleStr, "allow", ctx.sessionId)) {
4127
+ console.log(`\u{1F4CC} [\u5BA1\u6279\u8BB0\u5FC6] \u672C\u4F1A\u8BDD\u5185\u514D\u5BA1\uFF1A${ruleStr}\uFF08\u4F1A\u8BDD\u7ED3\u675F\u81EA\u52A8\u5931\u6548\uFF0C\u4E0D\u5199\u914D\u7F6E\u6587\u4EF6\uFF09`);
4128
+ } else {
4129
+ console.log(`\u267B\uFE0F [\u5BA1\u6279] ${toolName} \u672C\u6B21\u653E\u884C${safetyLevel ? ` (${safetyLevel})` : ""}\uFF08\u672A\u8BB0\u5FC6\uFF1A\u65E0\u6CD5\u5B89\u5168\u751F\u6210\u4F5C\u7528\u57DF\uFF09`);
4130
+ }
3995
4131
  } else if (decision === "allow-always") {
3996
4132
  const ruleStr = buildScopedAllowRule(toolName, args, primaryArg);
3997
4133
  if (ruleStr) {
@@ -4264,6 +4400,11 @@ var init_background = __esm({
4264
4400
  name: "run_in_background",
4265
4401
  primaryArg: "command",
4266
4402
  autoApproval: "command",
4403
+ // ★ P1-6 声明化:命令语义(灾难命令闸门/script-runner 供应链拦截按此识别;isSync:false 使其
4404
+ // 不享受只读免审);后台挂起不进子 agent(父收尾成孤儿)与挂机批处理(无人盯守)
4405
+ argKind: "command",
4406
+ subagentAllowed: false,
4407
+ batchAllowed: false,
4267
4408
  description: "\u5728\u540E\u53F0\u542F\u52A8\u4E00\u4E2A\u957F\u65F6 shell \u547D\u4EE4\u5E76\u7ACB\u5373\u8FD4\u56DE task_id\uFF0C\u4E0D\u963B\u585E\u540E\u7EED\u63A8\u7406\u3002\u9002\u7528\u4E24\u7C7B\u573A\u666F\uFF1A\u2460 \u957F\u8FDE\u63A5/\u5E38\u9A7B\u8FDB\u7A0B\uFF08dev server\u3001watch\u3001tail -f\uFF09\u2014\u2014\u5148\u8D77\u670D\u52A1\u518D\u7EE7\u7EED\u5E72\u522B\u7684\u6D3B\uFF1B\u2461 \u9884\u8BA1\u8FD0\u884C\u6570\u5206\u949F\u4EE5\u4E0A\u3001\u65E0\u9700\u5B9E\u65F6\u76EF\u770B\u7684\u6709\u9650\u957F\u4EFB\u52A1\uFF08\u5168\u91CF\u6D4B\u8BD5 / \u5927\u6784\u5EFA / \u5927\u4F53\u79EF\u5B89\u88C5\u2014\u2014\u8DD1\u5B8C\u7528 get_background_output \u5E26 wait_seconds \u7B49\u7ED3\u679C\uFF0C\u4E0D\u8981\u7528\u524D\u53F0 run_command \u5E72\u7B49\uFF09\u3002\u7528 get_background_output \u67E5\u65E5\u5FD7\u4E0E\u72B6\u6001\uFF0Cstop_background_task \u7EC8\u6B62\u3002",
4268
4409
  parameters: {
4269
4410
  type: "object",
@@ -6907,7 +7048,9 @@ ${formattedCode}${hasMore ? `
6907
7048
  function: {
6908
7049
  name: "list_dir",
6909
7050
  planAllowed: true,
6910
- primaryArg: "path",
7051
+ // ★ 第 17 项补丁(2026-10-21):原声明 primaryArg:'path' 系漂移——本工具参数表只有 max_depth,
7052
+ // 根本没有 path;且 SAFE 只读工具不走审批路径,allow-always 无意义。删除后由
7053
+ // tool-declaration-drift 的「声明 ⊆ parameters.properties」断言钉住。
6911
7054
  description: "\u626B\u63CF\u5E76\u7CBE\u7B80\u5217\u51FA\u5F53\u524D\u9879\u76EE\u7684\u5DE5\u4F5C\u533A\u76EE\u5F55\u6811\u3002\u672C\u5DE5\u5177\u81EA\u52A8\u5408\u5E76\u901A\u7528\u5FFD\u7565\u89C4\u5219\u4E0E\u591A\u5C42\u5B50\u76EE\u5F55\u7EA7 .gitignore \u89C4\u8303\u3002\u2605 \u53EA\u8BFB\u5B89\u5168\uFF0C\u53EF\u4E0E\u5176\u5B83\u53EA\u8BFB\u5DE5\u5177\u5728\u540C\u4E00\u6761\u6D88\u606F\u91CC\u5E76\u884C\u8C03\u7528\u3002",
6912
7055
  parameters: {
6913
7056
  type: "object",
@@ -7095,6 +7238,9 @@ ${summaries.join("\n")}`;
7095
7238
  triggersUndo: "delete",
7096
7239
  autoApproval: "file",
7097
7240
  primaryArg: "path",
7241
+ // ★ P1-6 声明化收权:递归删除不进子 agent(爆炸半径最大原语之一,SUBAGENT_DENYLIST 兜底同判)与挂机批处理
7242
+ subagentAllowed: false,
7243
+ batchAllowed: false,
7098
7244
  description: "\u4ECE\u672C\u5730\u78C1\u76D8\u5185\u6C38\u4E45\u5220\u9664\u4E00\u4E2A\u6307\u5B9A\u7684\u6587\u4EF6\u6216\u8005\u4E00\u6574\u4E2A\u6587\u4EF6\u5939\u76EE\u5F55\u3002\u5982\u679C\u662F\u76EE\u5F55\uFF0C\u5DE5\u5177\u4F1A\u81EA\u52A8\u6267\u884C\u6DF1\u5EA6\u9012\u5F52\u5F3A\u884C\u9500\u6BC1\u3002\u6B64\u64CD\u4F5C\u4E0D\u53EF\u9006\uFF0C\u8BF7\u4E07\u5206\u5C0F\u5FC3\u3002",
7099
7245
  parameters: {
7100
7246
  type: "object",
@@ -7293,7 +7439,13 @@ ${summaries.join("\n")}`;
7293
7439
  name: "move_file",
7294
7440
  // ★ #8a 策略声明:双路径都受保护路径硬规则检查(P0-3 洞的声明化收口;特判已退役)。
7295
7441
  // 刻意不声明 triggersUndo:move 语义可手动移回,维持「不在 Undo 回退范围」现状。
7296
- primaryArg: "src",
7442
+ // ★ 第 17 项补丁(2026-10-21):主参数名原写 'src',与本工具实际参数 source/destination 不符
7443
+ // → args['src'] 恒 undefined → buildScopedAllowRule 返回 null → 恒降级 allow-once,
7444
+ // 用户点「总是允许」对 move_file 从不生效(安全侧 fail-closed 无风险,纯功能打折)。
7445
+ // 改为 'source' 后走 buildScopedAllowRule 末尾的【精确值】兜底分支(规则 = move_file(<精确源路径>)),
7446
+ // 刻意不并入 path 的顶层目录作用域(argKey === 'path' 分支):move 无 undo 兜底(见上条),
7447
+ // 作用域宁可窄——代价是该源路径逐次授权,move_file 非高频操作,可接受。
7448
+ primaryArg: "source",
7297
7449
  autoApproval: "file",
7298
7450
  pathArgs: ["source", "destination"],
7299
7451
  description: "\u79FB\u52A8\u6216\u91CD\u547D\u540D\u6587\u4EF6/\u76EE\u5F55\uFF08\u540C\u5377\u539F\u5B50\u64CD\u4F5C\uFF09\u3002\u6E90\u4E0D\u5B58\u5728\u5219\u5931\u8D25\uFF1B\u76EE\u6807\u5DF2\u5B58\u5728\u5219\u62D2\u7EDD\uFF08\u9632\u8BEF\u8986\u76D6\uFF0C\u5982\u9700\u8986\u76D6\u8BF7\u5148 delete_path \u518D move_file\uFF09\u3002\u81EA\u52A8\u521B\u5EFA\u76EE\u6807\u7236\u76EE\u5F55\u3002\u26A0\uFE0F \u672C\u64CD\u4F5C\u6682\u4E0D\u5728 Undo \u56DE\u9000\u8303\u56F4\u5185\uFF08\u4E0D\u53EF\u64A4\u9500\uFF09\u3002",
@@ -7576,6 +7728,11 @@ var init_command = __esm({
7576
7728
  name: "run_command",
7577
7729
  primaryArg: "command",
7578
7730
  autoApproval: "command",
7731
+ // ★ P1-6 声明化:命令语义(灾难命令闸门/script-runner 供应链拦截/只读免审按此识别)
7732
+ argKind: "command",
7733
+ // ★ P1-6 声明化收权:shell 执行不进子 agent(爆炸半径最大原语,SUBAGENT_DENYLIST 兜底同判)与挂机批处理
7734
+ subagentAllowed: false,
7735
+ batchAllowed: false,
7579
7736
  description: "\u5728\u5DE5\u4F5C\u533A\u6267\u884C shell \u547D\u4EE4\uFF08\u77ED\u5E73\u5FEB\u7684\u6D4B\u8BD5 / \u6784\u5EFA / git \u7B49\uFF09\uFF0C\u6D41\u5F0F\u8FD4\u56DE stdout/stderr \u5E76\u9644\u5E26\u9000\u51FA\u7801\u3002\u5C5E\u4E8E\u9AD8\u5371\u64CD\u4F5C\uFF0C\u6BCF\u6B21\u6267\u884C\u90FD\u9700\u8981\u7528\u6237\u5BA1\u6279\u3002\u9884\u8BA1\u8FD0\u884C\u6570\u5206\u949F\u4EE5\u4E0A\u7684\u957F\u4EFB\u52A1\uFF08\u5168\u91CF\u6D4B\u8BD5 / \u5927\u6784\u5EFA / \u5927\u4F53\u79EF\u5B89\u88C5\uFF09\u8BF7\u6539\u7528 run_in_background \u540E\u53F0\u6267\u884C\uFF1B\u82E5\u672C\u5DE5\u5177\u6267\u884C\u8FC7\u4E45\u4ECD\u672A\u7ED3\u675F\uFF0C\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u628A\u8FDB\u7A0B\u8F6C\u5165\u540E\u53F0\u5E76\u8FD4\u56DE task_id\uFF08\u7528 get_background_output \u67E5\u7ED3\u679C\u3001stop_background_task \u7EC8\u6B62\uFF09\u3002",
7580
7737
  parameters: {
7581
7738
  type: "object",
@@ -10270,6 +10427,8 @@ var init_ask = __esm({
10270
10427
  type: "function",
10271
10428
  function: {
10272
10429
  name: "ask_question",
10430
+ // ★ P1-6 声明化:阻塞等用户作答,不参与并行调度(原 canParallelize 按名排除退役为声明否决)
10431
+ parallelizable: false,
10273
10432
  description: [
10274
10433
  "\u5411\u7528\u6237\u63D0\u51FA\u591A\u9009\u95EE\u9898\u5E76\u7B49\u5F85\u4F5C\u7B54\uFF1A\u6280\u672F\u65B9\u6848\u4E8C\u9009\u4E00\u3001\u8DEF\u5F84\u53D6\u820D\u3001\u8303\u56F4\u786E\u8BA4\u7B49\u9700\u7528\u6237\u62CD\u677F\u7684\u51B3\u7B56\u6216\u6B67\u4E49\u6F84\u6E05\u2014\u2014\u52FF\u7528\u4E8E\u80FD\u4ECE\u4EE3\u7801/\u4E0A\u4E0B\u6587\u81EA\u884C\u786E\u5B9A\u7684\u95EE\u9898\u3002",
10275
10434
  "2-4 \u4E2A\u9009\u9879\uFF08label + \u53EF\u9009 description\uFF09\uFF1BmultiSelect=true \u591A\u9009\uFF08\u9ED8\u8BA4\u5355\u9009\uFF09\u3002\u7528\u6237\u53D6\u6D88\u8FD4\u56DE\u7A7A\u9009\u62E9\uFF08\u6539\u7528\u9ED8\u8BA4\u6216\u91CD\u65B0\u63D0\u95EE\uFF09\u3002",
@@ -11679,11 +11838,12 @@ var init_backgroundTool = __esm({
11679
11838
 
11680
11839
  // ../core/src/tool/autoPermission.ts
11681
11840
  import path35 from "path";
11682
- var isMcpTool, BUILTIN_AUTO_DENY, matchBuiltinDeny, COMMAND_DENY, matchCommandDeny, hasShellMetachars, READONLY_HEADS, PKG_MGR, SCRIPT_RUNNER_HEADS, SCRIPT_RUNNER_READONLY, isScriptRunnerCommand, hasWriteModifier, SENSITIVE_FILE_ARG_RE, hasSensitiveFileArg, isReadOnlyCommand, isWithinWorkspace, runAutoCheck;
11841
+ var isMcpTool, BUILTIN_AUTO_DENY, matchBuiltinDeny, COMMAND_DENY, matchCommandDeny, hasShellMetachars, READONLY_HEADS, PKG_MGR, SCRIPT_RUNNER_HEADS, SCRIPT_RUNNER_READONLY, isScriptRunnerCommand, INTERP_EXE, INTERP_BARE_EXE, REPO_SCRIPT_TOKEN, isInterpreterRepoScript, hasWriteModifier, SENSITIVE_FILE_ARG_RE, hasSensitiveFileArg, isReadOnlyCommand, isWithinWorkspace, runAutoCheck;
11683
11842
  var init_autoPermission = __esm({
11684
11843
  "../core/src/tool/autoPermission.ts"() {
11685
11844
  "use strict";
11686
11845
  init_model();
11846
+ init_type();
11687
11847
  isMcpTool = (name) => name.startsWith("mcp__");
11688
11848
  BUILTIN_AUTO_DENY = [
11689
11849
  /(^|[\\/])\.env(\.|$)/i,
@@ -11802,6 +11962,20 @@ var init_autoPermission = __esm({
11802
11962
  if (SCRIPT_RUNNER_READONLY.some((re) => re.test(c))) return false;
11803
11963
  return SCRIPT_RUNNER_HEADS.some((re) => re.test(c));
11804
11964
  };
11965
+ INTERP_EXE = String.raw`\s*([^\s"'\`]*[\\/])?(node|deno|bun|tsx|ts-node|python3?|py|ruby|perl|php)(\.exe|\.cmd|\.bat|\.ps1)?\b`;
11966
+ INTERP_BARE_EXE = String.raw`\s*([^\s"'\`]*[\\/])?(tsc|make|cargo|go)(\.exe|\.cmd|\.bat|\.ps1)?\b`;
11967
+ REPO_SCRIPT_TOKEN = /^(?![A-Za-z]:)(?![\\/])(?:\.{1,2}[\\/])?(?:[^\\/: \t"']+[\\/])*[^\\/: \t"']+\.(?:js|mjs|cjs|ts|mts|cts|tsx|jsx|py|pyw|sh|bash|rb|pl|php)$/i;
11968
+ isInterpreterRepoScript = (cmd) => {
11969
+ const c = (cmd || "").trim();
11970
+ if (!c || hasShellMetachars(c)) return false;
11971
+ const head = new RegExp(`^${INTERP_EXE}`, "i");
11972
+ const bare = new RegExp(`^${INTERP_BARE_EXE}`, "i");
11973
+ if (bare.test(c)) return true;
11974
+ const m = head.exec(c);
11975
+ if (!m) return false;
11976
+ const rest = c.slice(m[0].length);
11977
+ return rest.split(/\s+/).filter(Boolean).some((t) => !t.startsWith("-") && REPO_SCRIPT_TOKEN.test(t));
11978
+ };
11805
11979
  hasWriteModifier = (cmd) => {
11806
11980
  if (!cmd) return false;
11807
11981
  if (/^sed\b/.test(cmd) && /(^|\s)-i\b/.test(cmd)) return true;
@@ -11841,13 +12015,17 @@ var init_autoPermission = __esm({
11841
12015
  if (!isWithinWorkspace(s, ctx.cwd)) return "ask";
11842
12016
  if (matchBuiltinDeny(s)) return "deny";
11843
12017
  }
12018
+ const fastPath = decl.safetyLevel !== "danger" /* DANGER */ && (decl.triggersUndo === "overwrite" || decl.triggersUndo === "create");
12019
+ return fastPath ? "allow" : "ask";
11844
12020
  }
11845
12021
  if (decl?.autoApproval === "command") {
11846
12022
  const argKey = decl.primaryArg ?? "command";
11847
12023
  const cmd = typeof args?.[argKey] === "string" ? args[argKey] : "";
11848
12024
  if (matchCommandDeny(cmd)) return "deny";
11849
12025
  }
11850
- const v = await classifyToolRisk(name, args, "", ctx.abortSignal);
12026
+ ctx.emitProgress?.("\u{1F512} \u5B89\u5168\u5224\u5B9A\u4E2D\u2026\uFF08\u8F85\u52A9\u6A21\u578B\u5206\u7C7B\uFF0C\u6700\u957F 10s\uFF09");
12027
+ const v = await classifyToolRisk(name, args, "", ctx.abortSignal, ctx.onAuxUsage);
12028
+ ctx.emitProgress?.(v === "safe" ? "\u{1F512} \u5B89\u5168\u5224\u5B9A\uFF1A\u653E\u884C" : "\u{1F512} \u5B89\u5168\u5224\u5B9A\uFF1A\u672A\u653E\u884C\uFF08\u8F6C\u4EBA\u5DE5\u5BA1\u6279\uFF09");
11851
12029
  return v === "safe" ? "allow" : "ask";
11852
12030
  };
11853
12031
  }
@@ -11855,7 +12033,7 @@ var init_autoPermission = __esm({
11855
12033
 
11856
12034
  // ../core/src/agent/toolExecution.ts
11857
12035
  import path36 from "path";
11858
- var applyPrivacyMasking, scaleTruncBudget, processToolCall;
12036
+ var applyPrivacyMasking, scaleTruncBudget, failResult, emitValidationFailed, resolveEarlyOutcome, runSecurityGates, executeTool, finalizeOutcome, processToolCall;
11859
12037
  var init_toolExecution = __esm({
11860
12038
  "../core/src/agent/toolExecution.ts"() {
11861
12039
  "use strict";
@@ -11895,48 +12073,24 @@ var init_toolExecution = __esm({
11895
12073
  const factor = 1 - (fill - 0.5) / 0.35 * 0.6;
11896
12074
  return Math.max(floor, Math.floor(base * factor));
11897
12075
  };
11898
- processToolCall = async (toolCall, ctx) => {
11899
- const {
11900
- sessionId,
11901
- depth,
11902
- round,
11903
- startTime,
11904
- llmDecisionSource,
11905
- signal,
11906
- rawTools,
11907
- events,
11908
- permissionMode,
11909
- planMode,
11910
- onUIEvent,
11911
- requestApproval: hostRequestApproval,
11912
- requestQuestion: hostRequestQuestion,
11913
- ideAction,
11914
- keepRecentUnits,
11915
- compactRatio,
11916
- modelWindow,
11917
- parentSystemPrompt,
11918
- readTracker,
11919
- reportCost,
11920
- toolCostSink
11921
- } = ctx;
11922
- let calledName = "";
11923
- let calledArgs = {};
11924
- let parseFailed = false;
11925
- if (toolCall.type === "function") {
11926
- calledName = toolCall.function.name;
11927
- try {
11928
- calledArgs = JSON.parse(toolCall.function.arguments || "{}");
11929
- } catch {
11930
- parseFailed = true;
11931
- }
11932
- console.log(`\u{1F916} \u6A21\u578B\u8BF7\u6C42\u8C03\u7528\u5DE5\u5177: ${calledName}\uFF0C\u53C2\u6570:`, calledArgs);
11933
- }
11934
- if (calledName === "exit_plan_mode") {
12076
+ failResult = (state, text, cat = "runtime") => {
12077
+ state.result = text;
12078
+ state.resultStatus = "failed";
12079
+ state.errorCategory = cat;
12080
+ };
12081
+ emitValidationFailed = (ctx, toolCall, state) => {
12082
+ ctx.events({ sessionId: ctx.sessionId, eventType: "tool.validation.failed", metadata: { depth: ctx.depth, decisionSource: ctx.llmDecisionSource, durationMs: performance.now() - ctx.startTime, round: ctx.round, tools_id: toolCall.id, toolName: state.calledName, toolSource: "builtin", ok: false, attempt: ctx.round }, payload: { output: state.result } });
12083
+ };
12084
+ resolveEarlyOutcome = (ctx, toolCall, state) => {
12085
+ const { signal, planMode, planWriteRejections } = ctx;
12086
+ const { calledName, calledArgs, matchedTool, parseFailed } = state;
12087
+ const declaredTerminal = matchedTool?.function?.terminal;
12088
+ if (declaredTerminal === "plan_exit" || !declaredTerminal && calledName === "exit_plan_mode") {
11935
12089
  const plan = typeof calledArgs?.plan === "string" ? calledArgs.plan : "";
11936
12090
  const note = "\u2705 [\u8BA1\u5212\u6A21\u5F0F] \u5B9E\u73B0\u65B9\u6848\u5DF2\u63D0\u4EA4\uFF0C\u7B49\u5F85\u7528\u6237\u5BA1\u6279\u540E\u8FDB\u5165\u5B9E\u73B0\u9636\u6BB5\u3002";
11937
12091
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: note, resultForUser: note, ok: true, terminal: { kind: "exit_plan_mode", plan } };
11938
12092
  }
11939
- if (calledName === "enter_plan_mode") {
12093
+ if (declaredTerminal === "plan_enter" || !declaredTerminal && calledName === "enter_plan_mode") {
11940
12094
  if (planMode) {
11941
12095
  const note2 = "\uFF08\u5DF2\u5728\u8BA1\u5212\u6A21\u5F0F\u4E2D\uFF0C\u65E0\u9700\u91CD\u590D\u8FDB\u5165\u3002\u8BF7\u7EE7\u7EED\u53EA\u8BFB\u8C03\u7814\uFF0C\u5B8C\u6210\u540E\u8C03 exit_plan_mode \u63D0\u4EA4\u65B9\u6848\u3002\uFF09";
11942
12096
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: note2, resultForUser: note2, ok: true };
@@ -11949,227 +12103,294 @@ var init_toolExecution = __esm({
11949
12103
  const placeholder = "\uFF08\u5DF2\u4E2D\u6B62\uFF0C\u672A\u6267\u884C\uFF09";
11950
12104
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: placeholder, resultForUser: placeholder, ok: false, aborted: true };
11951
12105
  }
11952
- const matchedTool = rawTools.find((t) => t.function.name === calledName);
11953
- if (!parseFailed && planMode && appConfig.planEnforcement === "runtime" && calledName !== "enter_plan_mode" && calledName !== "exit_plan_mode" && matchedTool?.function?.planAllowed !== true) {
12106
+ if (!parseFailed && planMode && appConfig.planEnforcement === "runtime" && calledName !== "enter_plan_mode" && calledName !== "exit_plan_mode" && declaredTerminal !== "plan_enter" && declaredTerminal !== "plan_exit" && matchedTool?.function?.planAllowed !== true) {
12107
+ if (matchedTool && matchedTool.function.safetyLevel !== "safe" /* SAFE */) planWriteRejections?.(1);
11954
12108
  const note = `\u274C [\u8BA1\u5212\u6A21\u5F0F] \u5F53\u524D\u4E3A\u53EA\u8BFB\u8C03\u7814\u9636\u6BB5\uFF0C\u7981\u6B62 ${calledName}\u3002\u5B8C\u6210\u65B9\u6848\u8BF7\u8C03 exit_plan_mode \u63D0\u4EA4\uFF0C\u7ECF\u7528\u6237\u5BA1\u6279\u540E\u8FDB\u5165\u5B9E\u73B0\u9636\u6BB5\u3002`;
11955
- events({ sessionId, eventType: "tool.validation.failed", metadata: { depth, decisionSource: llmDecisionSource, durationMs: performance.now() - startTime, round, tools_id: toolCall.id, toolName: calledName, toolSource: "builtin", ok: false, attempt: round }, payload: { output: note } });
12109
+ state.result = note;
12110
+ emitValidationFailed(ctx, toolCall, state);
11956
12111
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: note, resultForUser: note, ok: false };
11957
12112
  }
11958
- const toolCtx = { sessionId, cwd: getActiveWorkspaceRoot(), abortSignal: signal, depth, keepRecentUnits, compactRatio, modelWindow, parentSystemPrompt, events, onUIEvent, requestApproval: hostRequestApproval, requestQuestion: hostRequestQuestion, ideAction, emitProgress: (m) => onUIEvent?.({ type: "tool.progress", toolsId: toolCall.id, toolName: calledName, message: m }), permissionMode, readTracker, reportCost, toolCostSink };
11959
- let result = "";
11960
- let resultStatus = "success";
11961
- let errorCategory;
11962
- const failResult = (text, cat = "runtime") => {
11963
- result = text;
11964
- resultStatus = "failed";
11965
- errorCategory = cat;
11966
- };
11967
- let postHookOverride;
11968
- let imageAttachments;
11969
- if (parseFailed) {
11970
- failResult(`\u53C2\u6570\u89E3\u6790\u5931\u8D25\uFF1A\u6A21\u578B\u8FD4\u56DE\u7684 arguments \u4E0D\u662F\u5408\u6CD5 JSON${JSON.stringify(toolCall).slice(0, 300)}`, "syntax");
11971
- events({ sessionId, eventType: "tool.validation.failed", metadata: { depth, decisionSource: llmDecisionSource, durationMs: performance.now() - startTime, round, tools_id: toolCall.id, toolName: calledName, toolSource: "builtin", ok: false, attempt: round }, payload: { output: result } });
11972
- } else if (matchedTool && typeof matchedTool.function.execute === "function") {
11973
- const level = matchedTool.function.safetyLevel;
11974
- let needApproval = level === "mutation" /* MUTATION */ || level === "danger" /* DANGER */;
11975
- let denied = false;
11976
- let preHooksAdvanced = false;
11977
- if (appConfig.hookRewrite) {
11978
- preHooksAdvanced = true;
11979
- let veto;
11980
- try {
11981
- veto = await runPreHooks(resolveMcpPermissionName(calledName, calledArgs), calledArgs, toolCtx);
11982
- } catch (e) {
11983
- veto = { deny: true, reason: `\u274C [Pre-hook \u5F02\u5E38]\uFF1A${e?.message ?? e}\u3002\u51FA\u4E8E\u5B89\u5168\u9ED8\u8BA4\u62D2\u7EDD [${calledName}] \u7684\u6267\u884C\u3002` };
11984
- }
11985
- if (veto.deny) {
11986
- denied = true;
11987
- failResult(veto.reason || `\u274C [Hook \u62E6\u622A]\uFF1Apre-hook \u62D2\u7EDD\u4E86 [${calledName}] \u7684\u6267\u884C\u3002`, "permission");
11988
- } else if (veto.argsOverride !== void 0 && veto.argsOverride !== null && typeof veto.argsOverride === "object" && !Array.isArray(veto.argsOverride)) {
11989
- console.log(`\u270F\uFE0F [Pre-hook] [${calledName}] args \u5DF2\u88AB hook \u6539\u5199`);
11990
- calledArgs = veto.argsOverride;
11991
- }
12113
+ return null;
12114
+ };
12115
+ runSecurityGates = async (ctx, toolCall, state, toolCtx) => {
12116
+ const { sessionId, permissionMode } = ctx;
12117
+ const { calledName, calledArgs, matchedTool } = state;
12118
+ const level = matchedTool.function.safetyLevel;
12119
+ let needApproval = level === "mutation" /* MUTATION */ || level === "danger" /* DANGER */;
12120
+ let autoGateAsked = false;
12121
+ let preHooksAdvanced = false;
12122
+ if (appConfig.hookRewrite) {
12123
+ preHooksAdvanced = true;
12124
+ let veto;
12125
+ try {
12126
+ veto = await runPreHooks(resolveMcpPermissionName(calledName, calledArgs), calledArgs, toolCtx);
12127
+ } catch (e) {
12128
+ veto = { deny: true, reason: `\u274C [Pre-hook \u5F02\u5E38]\uFF1A${e?.message ?? e}\u3002\u51FA\u4E8E\u5B89\u5168\u9ED8\u8BA4\u62D2\u7EDD [${calledName}] \u7684\u6267\u884C\u3002` };
11992
12129
  }
11993
- {
11994
- const paramsSchema = matchedTool.function.parameters;
11995
- if (paramsSchema && typeof paramsSchema === "object" && !Array.isArray(paramsSchema)) {
11996
- const verdict = validateToolArgs(paramsSchema, calledArgs);
11997
- if (!verdict.ok) {
11998
- denied = true;
11999
- failResult(`\u274C \u53C2\u6570\u6821\u9A8C\u5931\u8D25\uFF1A${verdict.message}\uFF1B\u8BF7\u4FEE\u6B63\u53C2\u6570\u540E\u91CD\u8BD5\uFF08schema \u8981\u6C42\u89C1\u5DE5\u5177 parameters\uFF09\u3002`, "syntax");
12000
- events({ sessionId, eventType: "tool.validation.failed", metadata: { depth, decisionSource: llmDecisionSource, durationMs: performance.now() - startTime, round, tools_id: toolCall.id, toolName: calledName, toolSource: "builtin", ok: false, attempt: round }, payload: { output: result } });
12001
- }
12130
+ if (veto.deny) {
12131
+ state.denied = true;
12132
+ failResult(state, veto.reason || `\u274C [Hook \u62E6\u622A]\uFF1Apre-hook \u62D2\u7EDD\u4E86 [${calledName}] \u7684\u6267\u884C\u3002`, "permission");
12133
+ } else if (veto.argsOverride !== void 0 && veto.argsOverride !== null && typeof veto.argsOverride === "object" && !Array.isArray(veto.argsOverride)) {
12134
+ console.log(`\u270F\uFE0F [Pre-hook] [${calledName}] args \u5DF2\u88AB hook \u6539\u5199`);
12135
+ state.calledArgs = veto.argsOverride;
12136
+ }
12137
+ }
12138
+ {
12139
+ const paramsSchema = matchedTool.function.parameters;
12140
+ if (paramsSchema && typeof paramsSchema === "object" && !Array.isArray(paramsSchema)) {
12141
+ const verdict = validateToolArgs(paramsSchema, state.calledArgs);
12142
+ if (!verdict.ok) {
12143
+ state.denied = true;
12144
+ failResult(state, `\u274C \u53C2\u6570\u6821\u9A8C\u5931\u8D25\uFF1A${verdict.message}\uFF1B\u8BF7\u4FEE\u6B63\u53C2\u6570\u540E\u91CD\u8BD5\uFF08schema \u8981\u6C42\u89C1\u5DE5\u5177 parameters\uFF09\u3002`, "syntax");
12145
+ emitValidationFailed(ctx, toolCall, state);
12002
12146
  }
12003
12147
  }
12004
- const pathArgs = matchedTool.function.pathArgs ?? (matchedTool.function.triggersUndo ? ["path"] : []);
12005
- const protectedPaths = pathArgs.map((k) => calledArgs?.[k]);
12006
- const hitProtected = protectedPaths.find((p) => typeof p === "string" && isProtectedWrite(p, toolCtx.cwd));
12007
- if (hitProtected) {
12008
- denied = true;
12009
- failResult(`\u274C [\u4FDD\u62A4\u8DEF\u5F84] \u7981\u6B62\u4FEE\u6539\u53D7\u4FDD\u62A4\u76EE\u5F55\uFF08.git/.ssh/.aws/.deepseeker-code \u7B49 VCS/\u51ED\u8BC1/\u914D\u7F6E\uFF09\uFF1A${hitProtected}\u3002`, "permission");
12148
+ }
12149
+ const pathArgs = matchedTool.function.pathArgs ?? (matchedTool.function.triggersUndo ? ["path"] : []);
12150
+ const protectedPaths = pathArgs.map((k) => state.calledArgs?.[k]);
12151
+ const hitProtected = protectedPaths.find((p) => typeof p === "string" && isProtectedWrite(p, toolCtx.cwd));
12152
+ if (hitProtected) {
12153
+ state.denied = true;
12154
+ failResult(state, `\u274C [\u4FDD\u62A4\u8DEF\u5F84] \u7981\u6B62\u4FEE\u6539\u53D7\u4FDD\u62A4\u76EE\u5F55\uFF08.git/.ssh/.aws/.deepseeker-code \u7B49 VCS/\u51ED\u8BC1/\u914D\u7F6E\uFF09\uFF1A${hitProtected}\u3002`, "permission");
12155
+ }
12156
+ let perm = null;
12157
+ try {
12158
+ perm = checkPermission(resolveMcpPermissionName(calledName, state.calledArgs), state.calledArgs, matchedTool.function.primaryArg, sessionId);
12159
+ if (perm === "deny") {
12160
+ state.denied = true;
12161
+ failResult(state, `\u274C [\u6743\u9650\u89C4\u5219]\uFF1A[${calledName}] \u88AB\u6743\u9650\u89C4\u5219 deny \u62D2\u7EDD\u3002`, "permission");
12162
+ } else if (perm === "allow") {
12163
+ needApproval = false;
12164
+ } else if (perm === "ask") {
12165
+ needApproval = true;
12010
12166
  }
12011
- let perm = null;
12167
+ } catch {
12168
+ perm = null;
12169
+ }
12170
+ const isCommandTool = matchedTool.function.argKind === "command";
12171
+ const isForegroundCommand = isCommandTool && matchedTool.function.isSync !== false;
12172
+ const cmdIsScriptRunner = isCommandTool && isScriptRunnerCommand(typeof state.calledArgs?.command === "string" ? state.calledArgs.command : "");
12173
+ const argCwd = typeof state.calledArgs?.cwd === "string" ? state.calledArgs.cwd.trim() : "";
12174
+ const samePath = (a, b) => {
12012
12175
  try {
12013
- perm = checkPermission(resolveMcpPermissionName(calledName, calledArgs), calledArgs, matchedTool.function.primaryArg);
12014
- if (perm === "deny") {
12015
- denied = true;
12016
- failResult(`\u274C [\u6743\u9650\u89C4\u5219]\uFF1A[${calledName}] \u88AB\u6743\u9650\u89C4\u5219 deny \u62D2\u7EDD\u3002`, "permission");
12017
- } else if (perm === "allow") {
12018
- needApproval = false;
12019
- } else if (perm === "ask") {
12020
- needApproval = true;
12021
- }
12176
+ const ra = path36.resolve(a), rb = path36.resolve(b);
12177
+ return process.platform === "win32" ? ra.toLowerCase() === rb.toLowerCase() : ra === rb;
12022
12178
  } catch {
12023
- perm = null;
12179
+ return false;
12024
12180
  }
12025
- const cmdIsScriptRunner = (calledName === "run_command" || calledName === "run_in_background") && isScriptRunnerCommand(typeof calledArgs?.command === "string" ? calledArgs.command : "");
12026
- const argCwd = typeof calledArgs?.cwd === "string" ? calledArgs.cwd.trim() : "";
12027
- const samePath = (a, b) => {
12028
- try {
12029
- const ra = path36.resolve(a), rb = path36.resolve(b);
12030
- return process.platform === "win32" ? ra.toLowerCase() === rb.toLowerCase() : ra === rb;
12031
- } catch {
12032
- return false;
12033
- }
12034
- };
12035
- const scriptRunnerCwdDrift = cmdIsScriptRunner && argCwd !== "" && !samePath(argCwd, toolCtx.cwd ?? process.cwd());
12036
- const scriptRunnerBlocked = cmdIsScriptRunner && (perm === null || scriptRunnerCwdDrift);
12037
- if (scriptRunnerCwdDrift) needApproval = true;
12038
- if (perm === null && needApproval && !denied && !scriptRunnerBlocked && calledName === "run_command" && isReadOnlyCommand(typeof calledArgs?.command === "string" ? calledArgs.command : "")) {
12181
+ };
12182
+ const scriptRunnerCwdDrift = cmdIsScriptRunner && argCwd !== "" && !samePath(argCwd, toolCtx.cwd ?? process.cwd());
12183
+ const scriptRunnerBlocked = cmdIsScriptRunner && (perm === null || scriptRunnerCwdDrift);
12184
+ if (scriptRunnerCwdDrift) needApproval = true;
12185
+ if (perm === null && needApproval && !state.denied && !scriptRunnerBlocked && isForegroundCommand && isReadOnlyCommand(typeof state.calledArgs?.command === "string" ? state.calledArgs.command : "")) {
12186
+ needApproval = false;
12187
+ }
12188
+ if (isCommandTool && matchCommandDeny(typeof state.calledArgs?.command === "string" ? state.calledArgs.command : "")) {
12189
+ state.denied = true;
12190
+ failResult(state, `\u274C [\u5B89\u5168] \u707E\u96BE\u547D\u4EE4\u6E05\u5355\u62E6\u622A\uFF08\u4E0D\u53EF\u88AB allow \u89C4\u5219\u7ED5\u8FC7\uFF09\uFF1A[${calledName}] ${String(state.calledArgs?.command ?? "").slice(0, 100)}`, "permission");
12191
+ }
12192
+ state.isBgTool = matchedTool.function.isSync === false;
12193
+ state.lockKey = state.isBgTool ? computeLockKey(matchedTool.function.exclusiveLock, state.calledArgs, toolCtx) : null;
12194
+ if (state.lockKey && isLockHeld(state.lockKey)) {
12195
+ state.denied = true;
12196
+ failResult(state, `\u{1F512} [\u4E92\u65A5\u9501\u963B\u585E]\uFF1A\u5DF2\u6709\u540E\u53F0\u4EFB\u52A1\u6301\u6709\u9501 [${state.lockKey}]\uFF0C[${calledName}] \u8C03\u7528\u88AB\u8DF3\u8FC7\u3002`);
12197
+ }
12198
+ if (needApproval && !state.denied && !scriptRunnerBlocked) {
12199
+ const auto = await runAutoCheck(
12200
+ resolveMcpPermissionName(calledName, state.calledArgs),
12201
+ state.calledArgs,
12202
+ toolCtx,
12203
+ permissionMode === "auto",
12204
+ // ★ P0-3(裁定 B 方案):decl 补齐 safetyLevel/triggersUndo——快路径三条件缺一不可,
12205
+ // 两者都来自工具声明(勿在此处按名特判,名单已 #8a 声明化退役)。
12206
+ { autoApproval: matchedTool.function.autoApproval, pathArgs: matchedTool.function.pathArgs, primaryArg: matchedTool.function.primaryArg, safetyLevel: matchedTool.function.safetyLevel, triggersUndo: matchedTool.function.triggersUndo }
12207
+ );
12208
+ if (auto === "allow") {
12039
12209
  needApproval = false;
12210
+ } else if (auto === "deny") {
12211
+ state.denied = true;
12212
+ failResult(state, `\u274C [auto] \u5185\u7F6E\u9AD8\u5371\u6E05\u5355\u62E6\u622A\uFF1A[${calledName}] ${state.calledArgs?.path ?? ""}\u3002`, "permission");
12213
+ } else {
12214
+ autoGateAsked = true;
12040
12215
  }
12041
- if ((calledName === "run_command" || calledName === "run_in_background") && matchCommandDeny(typeof calledArgs?.command === "string" ? calledArgs.command : "")) {
12042
- denied = true;
12043
- failResult(`\u274C [\u5B89\u5168] \u707E\u96BE\u547D\u4EE4\u6E05\u5355\u62E6\u622A\uFF08\u4E0D\u53EF\u88AB allow \u89C4\u5219\u7ED5\u8FC7\uFF09\uFF1A[${calledName}] ${String(calledArgs?.command ?? "").slice(0, 100)}`, "permission");
12044
- }
12045
- const isBgTool = matchedTool.function.isSync === false;
12046
- const lockKey = isBgTool ? computeLockKey(matchedTool.function.exclusiveLock, calledArgs, toolCtx) : null;
12047
- if (lockKey && isLockHeld(lockKey)) {
12048
- denied = true;
12049
- failResult(`\u{1F512} [\u4E92\u65A5\u9501\u963B\u585E]\uFF1A\u5DF2\u6709\u540E\u53F0\u4EFB\u52A1\u6301\u6709\u9501 [${lockKey}]\uFF0C[${calledName}] \u8C03\u7528\u88AB\u8DF3\u8FC7\u3002`);
12050
- }
12051
- if (needApproval && !denied && !scriptRunnerBlocked) {
12052
- const auto = await runAutoCheck(
12053
- resolveMcpPermissionName(calledName, calledArgs),
12054
- calledArgs,
12055
- toolCtx,
12056
- permissionMode === "auto",
12057
- { autoApproval: matchedTool.function.autoApproval, pathArgs: matchedTool.function.pathArgs, primaryArg: matchedTool.function.primaryArg }
12058
- );
12059
- if (auto === "allow") {
12060
- needApproval = false;
12061
- } else if (auto === "deny") {
12062
- denied = true;
12063
- failResult(`\u274C [auto] \u5185\u7F6E\u9AD8\u5371\u6E05\u5355\u62E6\u622A\uFF1A[${calledName}] ${calledArgs?.path ?? ""}\u3002`, "permission");
12064
- }
12216
+ }
12217
+ if (needApproval && !state.denied) {
12218
+ const ra = matchedTool.function.requireApproval;
12219
+ let detail = `\u7533\u8BF7\u6267\u884C\u9AD8\u5371\u5DE5\u5177 [${calledName}]`;
12220
+ try {
12221
+ if (ra) detail = typeof ra === "function" ? await ra(state.calledArgs, toolCtx) : ra;
12222
+ } catch (e) {
12223
+ state.denied = true;
12224
+ failResult(state, `\u274C [\u5BA1\u6279\u63CF\u8FF0\u751F\u6210\u5F02\u5E38]\uFF1A${e?.message ?? e}\u3002\u51FA\u4E8E\u5B89\u5168\u9ED8\u8BA4\u62D2\u7EDD [${calledName}] \u7684\u6267\u884C\u3002`, "permission");
12065
12225
  }
12066
- if (needApproval && !denied) {
12067
- const ra = matchedTool.function.requireApproval;
12068
- let detail = `\u7533\u8BF7\u6267\u884C\u9AD8\u5371\u5DE5\u5177 [${calledName}]`;
12226
+ if (autoGateAsked) detail += `
12227
+ \uFF08\u2139\uFE0F \u8BF4\u660E\uFF1A\u672C\u8C03\u7528\u672A\u83B7\u81EA\u52A8\u653E\u884C\u2014\u2014\u4E0D\u5728\u81EA\u52A8\u653E\u884C\u8303\u56F4 / \u5B89\u5168\u5206\u7C7B\u5668\u5224 risky / \u5224\u5B9A\u8D85\u65F6\uFF08\u5206\u7C7B\u5668\u4E0A\u9650 10s\uFF09\u6216\u5F02\u5E38\uFF0C\u6309 fail-closed \u8F6C\u4EBA\u5DE5\u5BA1\u6279\u3002\uFF09`;
12228
+ if (!state.denied) {
12229
+ let approved2 = false;
12069
12230
  try {
12070
- if (ra) detail = typeof ra === "function" ? await ra(calledArgs, toolCtx) : ra;
12231
+ approved2 = await requestApproval(calledName, toolCall.id, detail, toolCtx, level, state.calledArgs, matchedTool.function.primaryArg);
12071
12232
  } catch (e) {
12072
- denied = true;
12073
- failResult(`\u274C [\u5BA1\u6279\u63CF\u8FF0\u751F\u6210\u5F02\u5E38]\uFF1A${e?.message ?? e}\u3002\u51FA\u4E8E\u5B89\u5168\u9ED8\u8BA4\u62D2\u7EDD [${calledName}] \u7684\u6267\u884C\u3002`, "permission");
12233
+ state.denied = true;
12234
+ failResult(state, `\u274C [\u5BA1\u6279\u6D41\u7A0B\u5F02\u5E38]\uFF1A${e?.message ?? e}\u3002\u51FA\u4E8E\u5B89\u5168\u9ED8\u8BA4\u62D2\u7EDD [${calledName}] \u7684\u6267\u884C\u3002`, "permission");
12074
12235
  }
12075
- if (!denied) {
12076
- let approved2 = false;
12077
- try {
12078
- approved2 = await requestApproval(calledName, toolCall.id, detail, toolCtx, level, calledArgs, matchedTool.function.primaryArg);
12079
- } catch (e) {
12080
- denied = true;
12081
- failResult(`\u274C [\u5BA1\u6279\u6D41\u7A0B\u5F02\u5E38]\uFF1A${e?.message ?? e}\u3002\u51FA\u4E8E\u5B89\u5168\u9ED8\u8BA4\u62D2\u7EDD [${calledName}] \u7684\u6267\u884C\u3002`, "permission");
12082
- }
12083
- if (!approved2 && !denied) {
12084
- denied = true;
12085
- failResult(`\u274C [\u5B89\u5168\u7194\u65AD]\uFF1A\u7528\u6237\u62D2\u7EDD\u4E86 [${calledName}] \u7684\u6267\u884C\u7533\u8BF7\u3002`, "permission");
12086
- }
12236
+ if (!approved2 && !state.denied) {
12237
+ state.denied = true;
12238
+ failResult(state, `\u274C [\u5B89\u5168\u7194\u65AD]\uFF1A\u7528\u6237\u62D2\u7EDD\u4E86 [${calledName}] \u7684\u6267\u884C\u7533\u8BF7\u3002`, "permission");
12087
12239
  }
12088
12240
  }
12089
- if (!denied && !preHooksAdvanced) {
12090
- let veto;
12091
- try {
12092
- veto = await runPreHooks(resolveMcpPermissionName(calledName, calledArgs), calledArgs, toolCtx);
12093
- } catch (e) {
12094
- veto = { deny: true, reason: `\u274C [Pre-hook \u5F02\u5E38]\uFF1A${e?.message ?? e}\u3002\u51FA\u4E8E\u5B89\u5168\u9ED8\u8BA4\u62D2\u7EDD [${calledName}] \u7684\u6267\u884C\u3002` };
12095
- }
12096
- if (veto.deny) {
12097
- denied = true;
12098
- failResult(veto.reason || `\u274C [Hook \u62E6\u622A]\uFF1Apre-hook \u62D2\u7EDD\u4E86 [${calledName}] \u7684\u6267\u884C\u3002`, "permission");
12099
- }
12241
+ }
12242
+ if (!state.denied && !preHooksAdvanced) {
12243
+ let veto;
12244
+ try {
12245
+ veto = await runPreHooks(resolveMcpPermissionName(calledName, state.calledArgs), state.calledArgs, toolCtx);
12246
+ } catch (e) {
12247
+ veto = { deny: true, reason: `\u274C [Pre-hook \u5F02\u5E38]\uFF1A${e?.message ?? e}\u3002\u51FA\u4E8E\u5B89\u5168\u9ED8\u8BA4\u62D2\u7EDD [${calledName}] \u7684\u6267\u884C\u3002` };
12100
12248
  }
12101
- if (!denied && matchedTool.function.triggersUndo) {
12102
- try {
12103
- await beforeMutationBackup(matchedTool.function.triggersUndo, calledName, calledArgs, toolCall.id, sessionId);
12104
- } catch (e) {
12105
- denied = true;
12106
- failResult(`\u274C [Undo \u5907\u4EFD\u5931\u8D25\xB7\u5B89\u5168\u7194\u65AD]\uFF1A${e?.message ?? e}\u3002\u5199\u5165\u5DF2\u963B\u6B62\uFF08\u51E1\u6539\u5FC5\u53EF\u56DE\u9000\u539F\u5219\uFF09\u3002`);
12107
- }
12249
+ if (veto.deny) {
12250
+ state.denied = true;
12251
+ failResult(state, veto.reason || `\u274C [Hook \u62E6\u622A]\uFF1Apre-hook \u62D2\u7EDD\u4E86 [${calledName}] \u7684\u6267\u884C\u3002`, "permission");
12108
12252
  }
12109
- if (!denied) {
12110
- try {
12111
- events({ sessionId, eventType: "tool.execute.start", metadata: { depth, decisionSource: llmDecisionSource, durationMs: performance.now() - startTime, round, tools_id: toolCall.id, toolName: calledName, toolSource: "builtin" }, payload: { output: JSON.stringify(calledArgs) } });
12112
- const execRet = matchedTool.function.execute(calledArgs, toolCtx);
12113
- const collected = isBgTool ? await runBackgroundTool(execRet, lockKey, calledName, signal) : await collectToolResult(execRet, (chunk) => toolCtx.emitProgress?.(chunk), signal);
12114
- result = collected.content;
12115
- resultStatus = collected.status;
12116
- imageAttachments = collected.images;
12117
- if (collected.status === "failed") errorCategory = collected.errorCategory ?? "runtime";
12118
- if (matchedTool.function.verifyResult) {
12119
- const verdict = matchedTool.function.verifyResult(result, toolCtx);
12120
- if (verdict.status === "failed" /* FAILED */) {
12121
- result = `\u3010\u7CFB\u7EDF\u5224\u5B9A\uFF1A\u6267\u884C\u5931\u8D25\u3011${verdict.summary ?? ""}
12253
+ }
12254
+ const denied = state.denied;
12255
+ if (!denied && matchedTool.function.triggersUndo) {
12256
+ try {
12257
+ await beforeMutationBackup(matchedTool.function.triggersUndo, calledName, state.calledArgs, toolCall.id, sessionId);
12258
+ } catch (e) {
12259
+ state.denied = true;
12260
+ failResult(state, `\u274C [Undo \u5907\u4EFD\u5931\u8D25\xB7\u5B89\u5168\u7194\u65AD]\uFF1A${e?.message ?? e}\u3002\u5199\u5165\u5DF2\u963B\u6B62\uFF08\u51E1\u6539\u5FC5\u53EF\u56DE\u9000\u539F\u5219\uFF09\u3002`);
12261
+ }
12262
+ }
12263
+ };
12264
+ executeTool = async (ctx, toolCall, state, toolCtx) => {
12265
+ const { sessionId, depth, round, startTime, llmDecisionSource, signal, events } = ctx;
12266
+ const { calledName, calledArgs, matchedTool } = state;
12267
+ try {
12268
+ events({ sessionId, eventType: "tool.execute.start", metadata: { depth, decisionSource: llmDecisionSource, durationMs: performance.now() - startTime, round, tools_id: toolCall.id, toolName: calledName, toolSource: "builtin" }, payload: { output: JSON.stringify(calledArgs) } });
12269
+ const execRet = matchedTool.function.execute(calledArgs, toolCtx);
12270
+ const collected = state.isBgTool ? await runBackgroundTool(execRet, state.lockKey, calledName, signal) : await collectToolResult(execRet, (chunk) => toolCtx.emitProgress?.(chunk), signal);
12271
+ state.result = collected.content;
12272
+ state.resultStatus = collected.status;
12273
+ state.imageAttachments = collected.images;
12274
+ if (collected.status === "failed") state.errorCategory = collected.errorCategory ?? "runtime";
12275
+ if (matchedTool.function.verifyResult) {
12276
+ const verdict = matchedTool.function.verifyResult(state.result, toolCtx);
12277
+ if (verdict.status === "failed" /* FAILED */) {
12278
+ state.result = `\u3010\u7CFB\u7EDF\u5224\u5B9A\uFF1A\u6267\u884C\u5931\u8D25\u3011${verdict.summary ?? ""}
12122
12279
  \u8BF7\u6B63\u89C6\u4E0B\u65B9\u8F93\u51FA\uFF0C\u4E0D\u8981\u4E50\u89C2\u5047\u8BBE\u6210\u529F\u3002
12123
12280
 
12124
- ${result}`;
12125
- resultStatus = "failed";
12126
- errorCategory = verdict.errorCategory ?? "unknown";
12127
- }
12128
- }
12129
- if (resultStatus === "success") {
12130
- events({ sessionId, eventType: "tool.execute.end", metadata: { depth, decisionSource: llmDecisionSource, durationMs: performance.now() - startTime, round, tools_id: toolCall.id, toolName: calledName, toolSource: "builtin", ok: true }, payload: { output: result } });
12131
- } else {
12132
- events({ sessionId, eventType: "tool.failed", metadata: { depth, decisionSource: llmDecisionSource, durationMs: performance.now() - startTime, round, tools_id: toolCall.id, toolName: calledName, toolSource: "builtin", ok: false, attempt: round, errorCategory: errorCategory ?? "unknown" }, payload: { output: result } });
12133
- }
12134
- } catch (err) {
12135
- console.error(`\u274C \u6267\u884C\u5DE5\u5177 ${calledName} \u65F6\u53D1\u751F\u9519\u8BEF:`, err);
12136
- failResult(`\u5DE5\u5177\u6267\u884C\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
12137
- events({ sessionId, eventType: "tool.failed", metadata: { depth, decisionSource: llmDecisionSource, durationMs: performance.now() - startTime, round, tools_id: toolCall.id, toolName: calledName, toolSource: "builtin", ok: false, attempt: round, errorCategory: errorCategory ?? "runtime" }, payload: { output: result } });
12138
- }
12139
- try {
12140
- const post = await runPostHooks(resolveMcpPermissionName(calledName, calledArgs), calledArgs, result, toolCtx);
12141
- postHookOverride = post?.resultOverride;
12142
- } catch (e) {
12143
- console.warn(`\u26A0\uFE0F post-hook [${calledName}] \u5F02\u5E38\uFF08\u5DF2\u5FFD\u7565\uFF09:`, e?.message ?? e);
12281
+ ${state.result}`;
12282
+ state.resultStatus = "failed";
12283
+ state.errorCategory = verdict.errorCategory ?? "unknown";
12144
12284
  }
12145
12285
  }
12146
- } else {
12147
- failResult(`\u9519\u8BEF\uFF1A\u672A\u77E5\u5DE5\u5177 "${calledName}" \u6216\u8BE5\u5DE5\u5177\u65E0\u53EF\u6267\u884C\u51FD\u6570`, "syntax");
12148
- events({ sessionId, eventType: "tool.validation.failed", metadata: { depth, decisionSource: llmDecisionSource, durationMs: performance.now() - startTime, round, tools_id: toolCall.id, toolName: calledName, toolSource: "builtin", ok: false, attempt: round }, payload: { output: result } });
12286
+ if (state.resultStatus === "success") {
12287
+ events({ sessionId, eventType: "tool.execute.end", metadata: { depth, decisionSource: llmDecisionSource, durationMs: performance.now() - startTime, round, tools_id: toolCall.id, toolName: calledName, toolSource: "builtin", ok: true }, payload: { output: state.result } });
12288
+ } else {
12289
+ events({ sessionId, eventType: "tool.failed", metadata: { depth, decisionSource: llmDecisionSource, durationMs: performance.now() - startTime, round, tools_id: toolCall.id, toolName: calledName, toolSource: "builtin", ok: false, attempt: round, errorCategory: state.errorCategory ?? "unknown" }, payload: { output: state.result } });
12290
+ }
12291
+ } catch (err) {
12292
+ console.error(`\u274C \u6267\u884C\u5DE5\u5177 ${calledName} \u65F6\u53D1\u751F\u9519\u8BEF:`, err);
12293
+ failResult(state, `\u5DE5\u5177\u6267\u884C\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
12294
+ events({ sessionId, eventType: "tool.failed", metadata: { depth, decisionSource: llmDecisionSource, durationMs: performance.now() - startTime, round, tools_id: toolCall.id, toolName: calledName, toolSource: "builtin", ok: false, attempt: round, errorCategory: state.errorCategory ?? "runtime" }, payload: { output: state.result } });
12149
12295
  }
12150
- result = applyPrivacyMasking(matchedTool?.function?.privacyMaskingRules, calledArgs, result);
12151
- const fill = ctx.contextTokensEst !== void 0 && modelWindow > 0 ? ctx.contextTokensEst / modelWindow : void 0;
12296
+ try {
12297
+ const post = await runPostHooks(resolveMcpPermissionName(calledName, calledArgs), calledArgs, state.result, toolCtx);
12298
+ state.postHookOverride = post?.resultOverride;
12299
+ } catch (e) {
12300
+ console.warn(`\u26A0\uFE0F post-hook [${calledName}] \u5F02\u5E38\uFF08\u5DF2\u5FFD\u7565\uFF09:`, e?.message ?? e);
12301
+ }
12302
+ };
12303
+ finalizeOutcome = async (ctx, toolCall, state) => {
12304
+ const { sessionId, modelWindow, contextTokensEst } = ctx;
12305
+ const { calledName, calledArgs, matchedTool } = state;
12306
+ state.result = applyPrivacyMasking(matchedTool?.function?.privacyMaskingRules, calledArgs, state.result);
12307
+ const fill = contextTokensEst !== void 0 && modelWindow > 0 ? contextTokensEst / modelWindow : void 0;
12152
12308
  const truncBudget = scaleTruncBudget(matchedTool?.function?.maxOutputCharacters ?? appConfig.MAX_TOOL_RESULT_CHARS, fill);
12153
- const sidecarNote = result.length > truncBudget ? await writeSidecarArchive(sessionId, String(toolCall.id), result) : void 0;
12154
- result = truncateToolResult(result, truncBudget, sidecarNote);
12309
+ const sidecarNote = state.result.length > truncBudget ? await writeSidecarArchive(sessionId, String(toolCall.id), state.result) : void 0;
12310
+ state.result = truncateToolResult(state.result, truncBudget, sidecarNote);
12311
+ const resultStatus = state.resultStatus;
12155
12312
  const ok = resultStatus === "success";
12156
- let resultForModel = result;
12157
- let resultForUser = result;
12313
+ let resultForModel = state.result;
12314
+ let resultForUser = state.result;
12158
12315
  if (matchedTool?.function?.outputFilter) {
12159
12316
  try {
12160
- const split = matchedTool.function.outputFilter(result);
12317
+ const split = matchedTool.function.outputFilter(state.result);
12161
12318
  resultForModel = split.toModel;
12162
12319
  resultForUser = split.toUser;
12163
12320
  } catch {
12164
12321
  }
12165
12322
  }
12166
- if (postHookOverride !== void 0) {
12323
+ if (state.postHookOverride !== void 0) {
12167
12324
  console.log(`\u270F\uFE0F [Post-hook] [${calledName}] \u6A21\u578B\u89C6\u56FE\u7ED3\u679C\u5DF2\u88AB hook \u6539\u5199`);
12168
12325
  const overrideBudget = scaleTruncBudget(matchedTool?.function?.maxOutputCharacters ?? appConfig.MAX_TOOL_RESULT_CHARS, fill);
12169
- const overrideNote = postHookOverride.length > overrideBudget ? await writeSidecarArchive(sessionId, String(toolCall.id), postHookOverride) : void 0;
12170
- resultForModel = truncateToolResult(postHookOverride, overrideBudget, overrideNote);
12326
+ const overrideNote = state.postHookOverride.length > overrideBudget ? await writeSidecarArchive(sessionId, String(toolCall.id), state.postHookOverride) : void 0;
12327
+ resultForModel = truncateToolResult(state.postHookOverride, overrideBudget, overrideNote);
12171
12328
  }
12172
- return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel, resultForUser, ok, imageAttachments };
12329
+ const declaredTerminal = matchedTool?.function?.terminal;
12330
+ if (declaredTerminal === true) {
12331
+ return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel, resultForUser, ok, imageAttachments: state.imageAttachments, terminal: { kind: "generic" } };
12332
+ }
12333
+ return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel, resultForUser, ok, imageAttachments: state.imageAttachments };
12334
+ };
12335
+ processToolCall = async (toolCall, ctx) => {
12336
+ const state = {
12337
+ calledName: "",
12338
+ calledArgs: {},
12339
+ parseFailed: false,
12340
+ matchedTool: void 0,
12341
+ result: "",
12342
+ resultStatus: "success",
12343
+ errorCategory: void 0,
12344
+ denied: false,
12345
+ postHookOverride: void 0,
12346
+ imageAttachments: void 0,
12347
+ isBgTool: false,
12348
+ lockKey: null
12349
+ };
12350
+ if (toolCall.type === "function") {
12351
+ state.calledName = toolCall.function.name;
12352
+ try {
12353
+ state.calledArgs = JSON.parse(toolCall.function.arguments || "{}");
12354
+ } catch {
12355
+ state.parseFailed = true;
12356
+ }
12357
+ console.log(`\u{1F916} \u6A21\u578B\u8BF7\u6C42\u8C03\u7528\u5DE5\u5177: ${state.calledName}\uFF0C\u53C2\u6570:`, state.calledArgs);
12358
+ }
12359
+ state.matchedTool = ctx.rawTools.find((t) => t.function.name === state.calledName);
12360
+ const early = resolveEarlyOutcome(ctx, toolCall, state);
12361
+ if (early) return early;
12362
+ const {
12363
+ sessionId,
12364
+ depth,
12365
+ signal,
12366
+ events,
12367
+ permissionMode,
12368
+ onUIEvent,
12369
+ requestApproval: hostRequestApproval,
12370
+ requestQuestion: hostRequestQuestion,
12371
+ ideAction,
12372
+ keepRecentUnits,
12373
+ compactRatio,
12374
+ modelWindow,
12375
+ parentSystemPrompt,
12376
+ readTracker,
12377
+ reportCost,
12378
+ toolCostSink,
12379
+ onAuxUsage,
12380
+ budgetCheck
12381
+ } = ctx;
12382
+ const toolCtx = { sessionId, cwd: getActiveWorkspaceRoot(), abortSignal: signal, depth, keepRecentUnits, compactRatio, modelWindow, parentSystemPrompt, events, onUIEvent, requestApproval: hostRequestApproval, requestQuestion: hostRequestQuestion, ideAction, emitProgress: (m) => onUIEvent?.({ type: "tool.progress", toolsId: toolCall.id, toolName: state.calledName, message: m }), permissionMode, readTracker, reportCost, toolCostSink, onAuxUsage, budgetCheck };
12383
+ if (state.parseFailed) {
12384
+ failResult(state, `\u53C2\u6570\u89E3\u6790\u5931\u8D25\uFF1A\u6A21\u578B\u8FD4\u56DE\u7684 arguments \u4E0D\u662F\u5408\u6CD5 JSON${JSON.stringify(toolCall).slice(0, 300)}`, "syntax");
12385
+ emitValidationFailed(ctx, toolCall, state);
12386
+ } else if (state.matchedTool && typeof state.matchedTool.function.execute === "function") {
12387
+ await runSecurityGates(ctx, toolCall, state, toolCtx);
12388
+ if (!state.denied) await executeTool(ctx, toolCall, state, toolCtx);
12389
+ } else {
12390
+ failResult(state, `\u9519\u8BEF\uFF1A\u672A\u77E5\u5DE5\u5177 "${state.calledName}" \u6216\u8BE5\u5DE5\u5177\u65E0\u53EF\u6267\u884C\u51FD\u6570`, "syntax");
12391
+ emitValidationFailed(ctx, toolCall, state);
12392
+ }
12393
+ return await finalizeOutcome(ctx, toolCall, state);
12173
12394
  };
12174
12395
  }
12175
12396
  });
@@ -12208,7 +12429,7 @@ var init_toolScheduling = __esm({
12208
12429
  const fillRestPlaceholders = async (fromIdx) => {
12209
12430
  const tcs2 = assistantMessage.tool_calls;
12210
12431
  for (let j = fromIdx + 1; j < tcs2.length; j++) {
12211
- const ph = "\uFF08\u5DF2\u8DF3\u8FC7\uFF1A\u7EC8\u7ED3\u7C7B\u5DE5\u5177 enter/exit_plan_mode \u4E4B\u540E\u7684\u5E76\u884C\u8C03\u7528\u672A\u6267\u884C\uFF09";
12432
+ const ph = "\uFF08\u5DF2\u8DF3\u8FC7\uFF1A\u7EC8\u7ED3\u7C7B\u5DE5\u5177\u4E4B\u540E\u7684\u5E76\u884C\u8C03\u7528\u672A\u6267\u884C\uFF09";
12212
12433
  message.push({ role: "tool", tool_call_id: tcs2[j].id, content: ph });
12213
12434
  await appendMessage({ sessionId, role: "tool", tool_call_id: tcs2[j].id, content: ph });
12214
12435
  }
@@ -12227,14 +12448,15 @@ var init_toolScheduling = __esm({
12227
12448
  };
12228
12449
  const canParallelize = (name, args, parseFailed) => {
12229
12450
  if (!parallelSafeToolsEnabled || parseFailed || signal?.aborted) return false;
12230
- if (name === "exit_plan_mode" || name === "enter_plan_mode" || name === "ask_question") return false;
12231
12451
  const matched = rawTools.find((t) => t.function.name === name);
12232
12452
  if (!matched) return false;
12453
+ if (matched.function.terminal) return false;
12454
+ if (matched.function.parallelizable === false) return false;
12233
12455
  if (matched.function.triggersUndo) return false;
12234
12456
  if (matched.function.safetyLevel !== "safe" /* SAFE */) return false;
12235
12457
  if (matched.function.isSync === false) return false;
12236
12458
  try {
12237
- const perm = checkPermission(name, args, matched.function.primaryArg);
12459
+ const perm = checkPermission(name, args, matched.function.primaryArg, sessionId);
12238
12460
  if (perm === "ask" || perm === "deny") return false;
12239
12461
  } catch {
12240
12462
  return false;
@@ -12303,9 +12525,11 @@ var init_toolScheduling = __esm({
12303
12525
  if (oc.terminal.kind === "exit_plan_mode") {
12304
12526
  yield { type: "plan.proposed", plan: oc.terminal.plan || "" };
12305
12527
  return { kind: "terminal", terminalText: oc.terminal.plan || oc.resultForUser };
12306
- } else {
12528
+ } else if (oc.terminal.kind === "enter_plan_mode") {
12307
12529
  yield { type: "plan.enterRequested", reason: oc.terminal.reason || "" };
12308
12530
  return { kind: "terminal", terminalText: oc.terminal.reason ? `\u{1F4CB} \u6A21\u578B\u8BF7\u6C42\u8FDB\u5165\u8BA1\u5212\u6A21\u5F0F\uFF1A${oc.terminal.reason}` : oc.resultForUser };
12531
+ } else {
12532
+ return { kind: "terminal", terminalText: oc.resultForUser };
12309
12533
  }
12310
12534
  }
12311
12535
  yield { type: "tool.end", toolCallId: oc.toolCallId, toolName: oc.calledName, result: oc.resultForUser, ok: oc.ok };
@@ -12518,7 +12742,8 @@ var init_streamInference = __esm({
12518
12742
  depth,
12519
12743
  signal,
12520
12744
  toolsTokens,
12521
- onUIEvent: ctx.onUIEvent
12745
+ onUIEvent: ctx.onUIEvent,
12746
+ onAuxUsage: ctx.onAuxUsage
12522
12747
  });
12523
12748
  inferenceMessages = withNudgeTail(message, nudgeMsg);
12524
12749
  compactedThisRound = true;
@@ -12600,7 +12825,7 @@ var init_streamInference = __esm({
12600
12825
  });
12601
12826
 
12602
12827
  // ../core/src/agent/agentNudges.ts
12603
- var NUDGE_EVERY, NUDGE_FENCE, PHANTOM_FENCE, PHANTOM_RETRY_MAX, EARLY_FINAL_FENCE, EARLY_FINAL_TURN_THRESHOLD, EARLY_FINAL_MAX, TOOL_DIGEST_FENCE, TOOL_DIGEST_MAX, TOOL_DIGEST_WINDOW, TOOL_DIGEST_TEXT_MAX_LEN, PLAN_FIRST_FENCE, REPEAT_FENCE, READ_REPEAT_THRESHOLD, GREP_REPEAT_THRESHOLD, REPEAT_NUDGE_MAX, TODO_INCOMPLETE_FENCE, TODO_INCOMPLETE_MAX, BUDGET_FENCE, BUDGET_SOFT_TEXT, PHANTOM_TEXT, EARLY_FINAL_TEXT, TOOL_DIGEST_TEXT, usageNoteText, NUDGE_TEXT, COMPLEX_VERBS, COMPLEX_OBJECTS, COMPLEX_MARKERS, QUERY_LEAD, looksComplex, PLAN_FIRST_TEXT, looksComplete, parseToolCall, normPath, normQuery, REPEAT_READ_TEXT, REPEAT_GREP_TEXT, TODO_INCOMPLETE_TEXT, createNudgeScheduler;
12828
+ var NUDGE_EVERY, NUDGE_FENCE, PHANTOM_FENCE, PHANTOM_RETRY_MAX, EARLY_FINAL_FENCE, EARLY_FINAL_TURN_THRESHOLD, EARLY_FINAL_MAX, TOOL_DIGEST_FENCE, TOOL_DIGEST_MAX, TOOL_DIGEST_WINDOW, TOOL_DIGEST_TEXT_MAX_LEN, PLAN_FIRST_FENCE, REPEAT_FENCE, READ_REPEAT_THRESHOLD, GREP_REPEAT_THRESHOLD, REPEAT_NUDGE_MAX, TODO_INCOMPLETE_FENCE, TODO_INCOMPLETE_MAX, BUDGET_FENCE, BUDGET_SOFT_TEXT, PLAN_REJECT_FENCE, PLAN_REJECT_NUDGE_AT, PLAN_REJECT_FORCE_AT, PLAN_REJECT_TEXT, PLAN_REJECT_FORCE_FINAL_TEXT, PHANTOM_TEXT, EARLY_FINAL_TEXT, TOOL_DIGEST_TEXT, usageNoteText, NUDGE_TEXT, COMPLEX_VERBS, COMPLEX_OBJECTS, COMPLEX_MARKERS, QUERY_LEAD, looksComplex, PLAN_FIRST_TEXT, looksComplete, parseToolCall, normPath, normQuery, REPEAT_READ_TEXT, REPEAT_GREP_TEXT, TODO_INCOMPLETE_TEXT, createNudgeScheduler;
12604
12829
  var init_agentNudges = __esm({
12605
12830
  "../core/src/agent/agentNudges.ts"() {
12606
12831
  "use strict";
@@ -12627,9 +12852,14 @@ var init_agentNudges = __esm({
12627
12852
  const pct = budget > 0 ? Math.round(spent / budget * 100) : 100;
12628
12853
  return `\u5DF2\u82B1\u8D39 \xA5${spent.toFixed(3)} / \u9884\u7B97 \xA5${budget.toFixed(3)}\uFF08${pct}%\uFF09\uFF1A\u8BF7\u6536\u655B\u6536\u5C3E\uFF0C\u4E0D\u518D\u5C55\u5F00\u65B0\u63A2\u7D22\uFF1B\u82E5\u786E\u9700\u7EE7\u7EED\uFF0C\u7528\u4E00\u6BB5\u8BF4\u660E\u5269\u4F59\u5DE5\u4F5C\u540E\u5373\u6B62\u3002`;
12629
12854
  };
12855
+ PLAN_REJECT_FENCE = "\u27E6DSC:PLAN_REJECT\u27E7";
12856
+ PLAN_REJECT_NUDGE_AT = 2;
12857
+ PLAN_REJECT_FORCE_AT = 4;
12858
+ PLAN_REJECT_TEXT = (n) => `\u8BA1\u5212\u6A21\u5F0F\u4E0B\u4F60\u8C03\u7528\u5199\u7C7B\u5DE5\u5177\u5DF2\u88AB\u62D2\u7EDD ${n} \u6B21\uFF1A\u8BA1\u5212\u6A21\u5F0F\u53EA\u5141\u8BB8\u53EA\u8BFB\u8C03\u7814\uFF0C\u5199\u7C7B\u64CD\u4F5C\u987B\u5F85\u65B9\u6848\u7ECF\u7528\u6237\u6279\u51C6\u540E\u624D\u80FD\u6267\u884C\uFF0C\u7EE7\u7EED\u91CD\u8BD5\u53EA\u4F1A\u88AB\u518D\u6B21\u62D2\u7EDD\u3002\u8BF7\u505C\u6B62\u5199\u64CD\u4F5C\uFF0C\u6539\u7528\u53EA\u8BFB\u5DE5\u5177\uFF08read_file / search_grep / web_fetch \u7B49\uFF09\u7EE7\u7EED\u5B8C\u5584\u8C03\u7814\uFF0C\u6216\u6574\u7406\u5DF2\u6709\u53D1\u73B0\uFF0C\u7528 exit_plan_mode \u63D0\u4EA4\u5B9E\u73B0\u65B9\u6848\u3002`;
12859
+ PLAN_REJECT_FORCE_FINAL_TEXT = (n) => `\u5DF2\u4E2D\u6B62\uFF1A\u8BA1\u5212\u6A21\u5F0F\u4E0B\u8FDE\u7EED ${n} \u6B21\u5199\u7C7B\u5DE5\u5177\u8C03\u7528\u88AB\u62D2\u7EDD\uFF0C\u6A21\u578B\u672A\u80FD\u4EA7\u51FA\u5B9E\u73B0\u65B9\u6848\u3002\u8BA1\u5212\u9636\u6BB5\u5230\u6B64\u4E3A\u6B62\uFF0C\u5DF2\u67E5\u660E\u7684\u4E8B\u5B9E\u4E0E\u8C03\u7814\u8BB0\u5F55\u4FDD\u7559\u5728\u4E0A\u4E0B\u6587\u4E2D\u2014\u2014\u4F60\u53EF\u4EE5\uFF1A\u2460\u8865\u5145\u8BF4\u660E\u9700\u6C42\u6216\u653E\u5BBD\u7EA6\u675F\u540E\u91CD\u8BD5\uFF1B\u2461\u8BA9 agent \u8DF3\u8FC7\u8BA1\u5212\u6A21\u5F0F\u76F4\u63A5\u5B9E\u73B0\uFF08\u9700\u81EA\u884C\u786E\u8BA4\u98CE\u9669\uFF09\uFF1B\u2462\u4EBA\u5DE5\u63A5\u624B\u5B8C\u6210\u5269\u4F59\u8C03\u7814\u3002`;
12630
12860
  PHANTOM_TEXT = "\u4F60\u7684\u4E0A\u4E00\u6761\u56DE\u590D\u6CA1\u6709\u4EFB\u4F55\u5185\u5BB9\u3001\u4E5F\u6CA1\u6709\u8C03\u7528\u4EFB\u4F55\u5DE5\u5177\uFF0C\u4F46\u4EFB\u52A1\u5C1A\u672A\u5B8C\u6210\u3002\u8BF7\u7EE7\u7EED\u63A8\u8FDB\uFF08\u8C03\u7528\u5DE5\u5177\u6216\u7ED9\u51FA\u5B9E\u8D28\u56DE\u7B54\uFF09\uFF1B\u82E5\u786E\u5B9E\u53D7\u963B\u3001\u9700\u8981\u7528\u6237\u51B3\u7B56\uFF0C\u7528 ask_question \u8BF4\u660E\u5177\u4F53\u963B\u585E\u70B9\u3002\u4E0D\u8981\u8FD4\u56DE\u7A7A\u56DE\u590D\u3002";
12631
- EARLY_FINAL_TEXT = (round) => `\u4F60\u4EC5 ${round} \u8F6E\u5C31\u51C6\u5907\u6536\u5C3E\uFF0C\u4E14\u56DE\u7B54\u4E2D\u6CA1\u6709\u660E\u786E\u7684\u5B8C\u6210\u58F0\u660E\u3002\u8BF7\u81EA\u68C0\uFF1A\u82E5\u7528\u6237\u8F93\u5165\u672C\u5C31\u65E0\u9700\u5DE5\u5177\uFF08\u5BD2\u6684/\u95F2\u804A/\u7EAF\u95EE\u7B54\uFF09\u3001\u6216\u4F60\u5728\u7B49\u7528\u6237\u6F84\u6E05\u51B3\u7B56\uFF0C\u76F4\u63A5\u6B63\u5E38\u56DE\u5E94\u5373\u53EF\uFF0C\u5FFD\u7565\u672C\u81EA\u68C0\u3001\u4E0D\u8981\u8F93\u51FA\u4EFB\u4F55\u81EA\u68C0\u8BF4\u660E\uFF1B\u82E5\u786E\u6709\u4EFB\u52A1\u672A\u843D\u5730\uFF08\u6240\u9700\u4FE1\u606F\u672A\u83B7\u53D6\u9F50 / \u8BE5\u6539\u7684\u6587\u4EF6\u672A\u6539\u5B8C / \u672A\u9A8C\u8BC1\uFF09\uFF0C\u7ACB\u5373\u7EE7\u7EED\u8C03\u7528\u5DE5\u5177\u63A8\u8FDB\uFF0C\u4E0D\u8981\u8349\u7387\u603B\u7ED3\u6536\u5C3E\uFF1B\u82E5\u786E\u5DF2\u5168\u90E8\u5B8C\u6210\uFF0C\u8BF7\u660E\u786E\u56DE\u590D\u300C\u5DF2\u5B8C\u6210\u300D\u5E76\u7B80\u8FF0\u6210\u679C\u3002`;
12632
- TOOL_DIGEST_TEXT = "\u4F60\u521A\u6267\u884C\u5B8C\u5DE5\u5177\u62FF\u5230\u7ED3\u679C\uFF0C\u5374\u672A\u57FA\u4E8E\u8BE5\u7ED3\u679C\u7ED9\u51FA\u5B9E\u8D28\u56DE\u5E94\u5C31\u51C6\u5907\u6536\u5C3E\u3002\u8BF7\u7ED3\u5408\u5DE5\u5177\u8FD4\u56DE\u7ED3\u679C\u7EE7\u7EED\u63A8\u8FDB\uFF1B\u82E5\u7ED3\u679C\u8868\u660E\u4EFB\u52A1\u5C1A\u672A\u5B8C\u6210\uFF08\u5982\u4ECD\u5728\u7F16\u8BD1/\u8FD0\u884C\u3001\u9700\u7EE7\u7EED\u8F6E\u8BE2\uFF09\uFF0C\u7ACB\u5373\u91C7\u53D6\u4E0B\u4E00\u6B65\u884C\u52A8\uFF0C\u4E0D\u8981\u7A7A\u624B\u6536\u5C3E\u3002\u82E5\u786E\u5DF2\u5168\u90E8\u5B8C\u6210\uFF0C\u8BF7\u660E\u786E\u56DE\u590D\u300C\u5DF2\u5B8C\u6210\u300D\u5E76\u7B80\u8FF0\u6210\u679C\u3002";
12861
+ EARLY_FINAL_TEXT = (round) => `\u4F60\u4EC5 ${round} \u8F6E\u5C31\u51C6\u5907\u6536\u5C3E\uFF0C\u4E14\u56DE\u7B54\u4E2D\u6CA1\u6709\u660E\u786E\u7684\u5B8C\u6210\u58F0\u660E\u3002\u8BF7\u81EA\u68C0\uFF1A\u82E5\u7528\u6237\u8F93\u5165\u672C\u5C31\u65E0\u9700\u5DE5\u5177\uFF08\u5BD2\u6684/\u95F2\u804A/\u7EAF\u95EE\u7B54\uFF09\u3001\u6216\u4F60\u5728\u7B49\u7528\u6237\u6F84\u6E05\u51B3\u7B56\uFF0C\u76F4\u63A5\u6B63\u5E38\u56DE\u5E94\u5373\u53EF\uFF0C\u5FFD\u7565\u672C\u81EA\u68C0\u3001\u4E0D\u8981\u8F93\u51FA\u4EFB\u4F55\u81EA\u68C0\u8BF4\u660E\uFF1B\u82E5\u786E\u6709\u4EFB\u52A1\u672A\u843D\u5730\uFF08\u6240\u9700\u4FE1\u606F\u672A\u83B7\u53D6\u9F50 / \u8BE5\u6539\u7684\u6587\u4EF6\u672A\u6539\u5B8C / \u672A\u9A8C\u8BC1\uFF09\uFF0C\u7ACB\u5373\u7EE7\u7EED\u8C03\u7528\u5DE5\u5177\u63A8\u8FDB\uFF0C\u4E0D\u8981\u8349\u7387\u603B\u7ED3\u6536\u5C3E\uFF1B\u82E5\u786E\u5DF2\u5168\u90E8\u5B8C\u6210\uFF0C\u8BF7\u660E\u786E\u56DE\u590D\u300C\u5DF2\u5B8C\u6210\u300D\u5E76\u7528\u7B80\u77ED\u81EA\u7136\u7684\u603B\u7ED3\u4EA4\u4EE3\u6210\u679C\uFF08\u7ED3\u8BBA\u4E00\u53E5 + \u6539\u52A8\u4E0E\u9A8C\u8BC1\u8981\u70B9\uFF09\uFF0C\u52FF\u5199\u957F\u7BC7\u62A5\u544A\u3002`;
12862
+ TOOL_DIGEST_TEXT = "\u4F60\u521A\u6267\u884C\u5B8C\u5DE5\u5177\u62FF\u5230\u7ED3\u679C\uFF0C\u5374\u672A\u57FA\u4E8E\u8BE5\u7ED3\u679C\u7ED9\u51FA\u5B9E\u8D28\u56DE\u5E94\u5C31\u51C6\u5907\u6536\u5C3E\u3002\u8BF7\u7ED3\u5408\u5DE5\u5177\u8FD4\u56DE\u7ED3\u679C\u7EE7\u7EED\u63A8\u8FDB\uFF1B\u82E5\u7ED3\u679C\u8868\u660E\u4EFB\u52A1\u5C1A\u672A\u5B8C\u6210\uFF08\u5982\u4ECD\u5728\u7F16\u8BD1/\u8FD0\u884C\u3001\u9700\u7EE7\u7EED\u8F6E\u8BE2\uFF09\uFF0C\u7ACB\u5373\u91C7\u53D6\u4E0B\u4E00\u6B65\u884C\u52A8\uFF0C\u4E0D\u8981\u7A7A\u624B\u6536\u5C3E\u3002\u82E5\u786E\u5DF2\u5168\u90E8\u5B8C\u6210\uFF0C\u8BF7\u660E\u786E\u56DE\u590D\u300C\u5DF2\u5B8C\u6210\u300D\u5E76\u7528\u7B80\u77ED\u81EA\u7136\u7684\u603B\u7ED3\u4EA4\u4EE3\u6210\u679C\uFF08\u7ED3\u8BBA\u4E00\u53E5 + \u6539\u52A8\u4E0E\u9A8C\u8BC1\u8981\u70B9\uFF09\uFF0C\u52FF\u5199\u957F\u7BC7\u62A5\u544A\u3002";
12633
12863
  usageNoteText = (usage) => {
12634
12864
  const pct = usage.windowTokens > 0 ? Math.min(100, Math.max(0, Math.round(usage.usedTokens / usage.windowTokens * 100))) : 0;
12635
12865
  return `\u5F53\u524D\u4E0A\u4E0B\u6587\u5DF2\u7528\u7EA6 ${pct}%\uFF08\u4F30\u7B97 \u2248${usage.usedTokens}/${usage.windowTokens} token\uFF09\u3002\u82E5\u5360\u6BD4\u5DF2\u9AD8\uFF08>70%\uFF09\uFF1A\u4F18\u5148\u6536\u655B\u6536\u5C3E\u3001\u51CF\u5C0F\u5355\u6B21\u8BFB\u53D6\u91CF\uFF08search_grep \u5B9A\u4F4D\u540E\u5C40\u90E8\u8BFB\u53D6\uFF09\uFF0C\u907F\u514D\u89E6\u53D1\u5F3A\u5236\u538B\u7F29\u3002`;
@@ -12692,8 +12922,16 @@ var init_agentNudges = __esm({
12692
12922
  let todoNudges = 0;
12693
12923
  let todoPending = null;
12694
12924
  let budgetSoftSent = false;
12925
+ let planRejectCount = 0;
12926
+ let planRejectNudged = false;
12927
+ let planRejectPending = null;
12695
12928
  return {
12696
12929
  pickNudge(round) {
12930
+ if (planRejectPending) {
12931
+ const m = planRejectPending;
12932
+ planRejectPending = null;
12933
+ return m;
12934
+ }
12697
12935
  if (!planFirstSent && round === 1 && !planMode && looksComplex(firstPrompt)) {
12698
12936
  planFirstSent = true;
12699
12937
  return { role: "system", content: `${PLAN_FIRST_FENCE}
@@ -12819,6 +13057,45 @@ ${REPEAT_GREP_TEXT(q, n)}` };
12819
13057
  continue;
12820
13058
  }
12821
13059
  }
13060
+ },
13061
+ notePlanWriteRejected(total) {
13062
+ planRejectCount = Math.max(planRejectCount, Math.floor(total) || 0);
13063
+ if (!planRejectNudged && planRejectCount >= PLAN_REJECT_NUDGE_AT) {
13064
+ planRejectNudged = true;
13065
+ planRejectPending = { role: "system", content: `${PLAN_REJECT_FENCE}
13066
+ ${PLAN_REJECT_TEXT(planRejectCount)}` };
13067
+ }
13068
+ },
13069
+ planWriteRejections() {
13070
+ return planRejectCount;
13071
+ }
13072
+ };
13073
+ };
13074
+ }
13075
+ });
13076
+
13077
+ // ../core/src/agent/costRelay.ts
13078
+ var createCostRelay;
13079
+ var init_costRelay = __esm({
13080
+ "../core/src/agent/costRelay.ts"() {
13081
+ "use strict";
13082
+ createCostRelay = (sink) => {
13083
+ let reported = 0;
13084
+ return {
13085
+ /** 上报当前累计总额,向 sink 递送相对上次上报的正增量。 */
13086
+ flush(total) {
13087
+ if (!sink) return;
13088
+ if (!Number.isFinite(total)) return;
13089
+ const delta = total - reported;
13090
+ if (delta <= 1e-9) return;
13091
+ const r = Math.round(delta * 1e6) / 1e6;
13092
+ if (r <= 0) return;
13093
+ reported += r;
13094
+ sink(r);
13095
+ },
13096
+ /** 已累计上报给 sink 的金额(元),父级/调试可读。 */
13097
+ get reported() {
13098
+ return reported;
12822
13099
  }
12823
13100
  };
12824
13101
  };
@@ -13015,18 +13292,16 @@ async function* runAgent(message, options) {
13015
13292
  const keepRecentUnits = options.keepRecentUnits;
13016
13293
  const compactRatio = options.compactRatio;
13017
13294
  const parentSystemPrompt = options.parentSystemPrompt;
13295
+ const effectiveModel = options.model?.trim() || MODEL_NAME;
13018
13296
  const { rawTools, cleanedToolSchemas, toolsTokens } = await prepareToolsAndInjections(message, options, events);
13019
13297
  let round = 0;
13020
13298
  let lastContent = "";
13021
13299
  let stopReason = "normal";
13022
13300
  let budgetTruncated = false;
13023
13301
  const MAX_AGENT_ROUNDS = 500;
13024
- const firstPrompt = (() => {
13025
- if (depth !== 0) return "";
13026
- const m = message.find((x) => x?.role === "user");
13027
- return msgText(m?.content);
13028
- })();
13302
+ const firstPrompt = resolveFirstPrompt(message, options);
13029
13303
  let budgetSpentYuan = 0;
13304
+ let subCostYuanAcc = 0;
13030
13305
  let runCompactCount = 0;
13031
13306
  const nudges = createNudgeScheduler({
13032
13307
  firstPrompt,
@@ -13050,13 +13325,12 @@ async function* runAgent(message, options) {
13050
13325
  });
13051
13326
  };
13052
13327
  const startTime = performance.now();
13053
- const persistedCalib = await getRollingState(sessionId);
13328
+ const persistedCalib = await getCalibration(sessionId, effectiveModel);
13054
13329
  let calibRatio = persistedCalib.calibRatio ?? 1.4;
13055
13330
  let lastRealPromptTokens = persistedCalib.lastRealPromptTokens;
13056
13331
  let lastCachedTokens = persistedCalib.lastCachedTokens;
13057
13332
  const breaker = createRepeatBreaker({ sessionId, depth, llmDecisionSource, startTime, events });
13058
13333
  let usageSum;
13059
- const effectiveModel = options.model?.trim() || MODEL_NAME;
13060
13334
  let costYuanAcc = 0;
13061
13335
  let costKnown = true;
13062
13336
  const addUsage = (u) => {
@@ -13081,6 +13355,23 @@ async function* runAgent(message, options) {
13081
13355
  budgetSpentYuan += yuan;
13082
13356
  };
13083
13357
  const toolCostSink = process.env.DEEP_SEEK_TOOL_COST === "0" ? void 0 : addToolCost;
13358
+ let auxCostYuanAcc = 0;
13359
+ const reportAuxUsage = (usage, kind) => {
13360
+ if (usage) {
13361
+ const cost = calcCostYuan(AUX_MODEL_NAME, { promptTokens: usage.prompt_tokens, completionTokens: usage.completion_tokens, cachedTokens: usage.cached_tokens });
13362
+ if (cost != null && Number.isFinite(cost) && cost > 0) {
13363
+ auxCostYuanAcc += cost;
13364
+ budgetSpentYuan += cost;
13365
+ }
13366
+ }
13367
+ try {
13368
+ options.onAuxUsage?.(usage, kind);
13369
+ } catch {
13370
+ }
13371
+ };
13372
+ const costRelay = createCostRelay(options.costSink);
13373
+ const inheritedBudgetCheck = options.budgetCheck ?? (depth === 0 && options.budgetYuan != null && options.budgetYuan > 0 ? () => budgetSpentYuan >= options.budgetYuan * 1.2 : void 0);
13374
+ let planWriteRej = 0;
13084
13375
  const toolCounts = {};
13085
13376
  await appendEvent(sessionId, { dscEvent: "run.start", runId, depth });
13086
13377
  try {
@@ -13117,6 +13408,12 @@ async function* runAgent(message, options) {
13117
13408
  \uFF08\u{1F4B0} \u9884\u7B97\u622A\u65AD\uFF1A\u672C run \u5DF2\u82B1\u8D39 \xA5${budgetSpentYuan.toFixed(3)} / \u9884\u7B97 \xA5${options.budgetYuan.toFixed(3)}\u3002/budget \u8C03\u6574\u9884\u7B97\u540E\u56DE\u590D"\u7EE7\u7EED"\u5373\u53EF\u63A5\u7EED\u3002\uFF09` };
13118
13409
  return;
13119
13410
  }
13411
+ if (depth > 0 && options.budgetCheck?.()) {
13412
+ stopReason = "limit";
13413
+ yield { type: "final", text: (lastContent || "") + `
13414
+ \uFF08\u{1F4B0} \u9884\u7B97\u4E2D\u6B62\uFF1A\u7236\u7EA7\u9884\u7B97\u5DF2\u8017\u5C3D\uFF0C\u672C\u5B50\u4EFB\u52A1\u5728\u8F6E\u8FB9\u754C\u88AB\u7194\u65AD\u6536\u5C3E\uFF0C\u4EFB\u52A1\u672A\u5B8C\u6210\u3002\u5DF2\u5B8C\u6210\u8FDB\u5C55\u5982\u4E0A\uFF1B\u8BF7\u52FF\u91CD\u8BD5\u6216\u7EE7\u7EED\u6D3E\u751F\u5B50\u4EFB\u52A1\u3002\uFF09` };
13415
+ return;
13416
+ }
13120
13417
  const inboxTexts = claimSessionInbox(sessionId);
13121
13418
  if (inboxTexts.length > 0) {
13122
13419
  for (const t of inboxTexts) {
@@ -13141,6 +13438,8 @@ async function* runAgent(message, options) {
13141
13438
  lastRealPromptTokens,
13142
13439
  lastCachedTokens,
13143
13440
  toolsTokens,
13441
+ // ★ P0-2:压缩摘要(summarize)的 aux usage 回传计价(见 reportAuxUsage 声明处)
13442
+ onAuxUsage: reportAuxUsage,
13144
13443
  // ★ H1:旁路数 compact.done 供 context.usage.compactCount(透传不拦截)
13145
13444
  onUIEvent: (evt) => {
13146
13445
  if (evt.type === "compact.done") runCompactCount++;
@@ -13179,7 +13478,8 @@ async function* runAgent(message, options) {
13179
13478
  compactRatio,
13180
13479
  modelWindow,
13181
13480
  toolsTokens,
13182
- onUIEvent: options.onUIEvent
13481
+ onUIEvent: options.onUIEvent,
13482
+ onAuxUsage: reportAuxUsage
13183
13483
  });
13184
13484
  if (infResult.kind === "aborted") {
13185
13485
  yield { type: "final", text: infResult.partialText || lastContent || "\uFF08\u5DF2\u4E2D\u6B62\uFF09" };
@@ -13209,6 +13509,7 @@ async function* runAgent(message, options) {
13209
13509
  if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
13210
13510
  await appendEvent(sessionId, { dscEvent: "round.end", runId, round, usage: infResult.usage });
13211
13511
  emitContextUsage();
13512
+ costRelay.flush(budgetSpentYuan);
13212
13513
  const finalText = (typeof assistantMessage.content === "string" ? assistantMessage.content : "") || "";
13213
13514
  const intercepted = nudges.interceptFinal(finalText, round);
13214
13515
  if (intercepted) continue;
@@ -13261,11 +13562,23 @@ async function* runAgent(message, options) {
13261
13562
  // ★ 子 run 成本回灌(成本信任链第二环 E1):runSubagent 经 toolCtx.reportCost 拿到闭包,
13262
13563
  // 子 run 结束时把总花费记进本 run 预算累计器——父预算自动继承约束(子花费计入父 spent)。
13263
13564
  reportCost: (yuan) => {
13264
- if (Number.isFinite(yuan) && yuan > 0) budgetSpentYuan += yuan;
13565
+ if (Number.isFinite(yuan) && yuan > 0) {
13566
+ budgetSpentYuan += yuan;
13567
+ subCostYuanAcc += yuan;
13568
+ }
13265
13569
  },
13266
13570
  // ★ 按次计费工具成本上报(DeepSeek Search/Fetch):web 工具经 toolCtx.toolCostSink 拿到闭包,
13267
13571
  // 成功调用后按次上报进本 run 累计器。
13268
- toolCostSink
13572
+ toolCostSink,
13573
+ // ★ P0-2:auto 档风险分类器(classifyToolRisk)的 aux usage 回传计价(见 reportAuxUsage 声明处)
13574
+ onAuxUsage: reportAuxUsage,
13575
+ // ★ P1-7 计划拒绝熔断:计划期非 SAFE 工具被拒增量回传(计数 + 通知 nudge 调度器发转向文案)
13576
+ planWriteRejections: (delta) => {
13577
+ planWriteRej += delta;
13578
+ nudges.notePlanWriteRejected(planWriteRej);
13579
+ },
13580
+ // ★ P1-5 子任务预算熔断:runSubagent 经 toolCtx.budgetCheck 拿到探测闭包,子 runAgent 每轮边界自检
13581
+ budgetCheck: inheritedBudgetCheck
13269
13582
  };
13270
13583
  const scheduleResult = yield* scheduleToolCalls(assistantMessage, message, toolCallCtx);
13271
13584
  if (scheduleResult.kind === "aborted") {
@@ -13278,6 +13591,12 @@ async function* runAgent(message, options) {
13278
13591
  }
13279
13592
  await appendEvent(sessionId, { dscEvent: "round.end", runId, round, usage: infResult.usage });
13280
13593
  emitContextUsage();
13594
+ costRelay.flush(budgetSpentYuan);
13595
+ if (options.planMode && planWriteRej >= PLAN_REJECT_FORCE_AT) {
13596
+ stopReason = "limit";
13597
+ yield { type: "final", text: PLAN_REJECT_FORCE_FINAL_TEXT(planWriteRej) };
13598
+ return;
13599
+ }
13281
13600
  }
13282
13601
  } catch (toolErr) {
13283
13602
  if (signal?.aborted) {
@@ -13317,13 +13636,14 @@ async function* runAgent(message, options) {
13317
13636
  costYuan: costKnown ? Math.round(costYuanAcc * 1e6) / 1e6 : void 0,
13318
13637
  toolCostYuan: toolCostYuanAcc > 0 ? Math.round(toolCostYuanAcc * 1e6) / 1e6 : void 0,
13319
13638
  // 按次计费工具花费(Search/Fetch)
13639
+ auxCostYuan: auxCostYuanAcc > 0 ? Math.round(auxCostYuanAcc * 1e6) / 1e6 : void 0,
13640
+ // 辅助模型花费(压缩摘要+风险分类器,P0-2)
13320
13641
  tools: toolCounts
13321
13642
  });
13322
13643
  } catch {
13323
13644
  }
13324
13645
  try {
13325
- const spentTotal = costYuanAcc + toolCostYuanAcc;
13326
- if (options.costSink && spentTotal > 0) options.costSink(Math.round(spentTotal * 1e6) / 1e6);
13646
+ costRelay.flush(budgetSpentYuan);
13327
13647
  } catch {
13328
13648
  }
13329
13649
  try {
@@ -13339,6 +13659,10 @@ async function* runAgent(message, options) {
13339
13659
  costYuan: costKnown ? Math.round(costYuanAcc * 1e6) / 1e6 : null,
13340
13660
  toolCostYuan: toolCostYuanAcc > 0 ? Math.round(toolCostYuanAcc * 1e6) / 1e6 : void 0,
13341
13661
  // 按次计费工具花费(undefined 自动省略)
13662
+ auxCostYuan: auxCostYuanAcc > 0 ? Math.round(auxCostYuanAcc * 1e6) / 1e6 : void 0,
13663
+ // 辅助模型花费(压缩摘要+风险分类器;undefined 自动省略)
13664
+ subCostYuan: subCostYuanAcc > 0 ? Math.round(subCostYuanAcc * 1e6) / 1e6 : void 0,
13665
+ // ★ Q12:子 run 花费并列展示(展示层补偿;undefined 自动省略)
13342
13666
  durationMs: Math.round(performance.now() - startTime),
13343
13667
  budgetTruncated: budgetTruncated || void 0
13344
13668
  // 预算截断标注(E2;undefined 自动省略)
@@ -13353,12 +13677,13 @@ async function* runAgent(message, options) {
13353
13677
  console.warn("\u26A0\uFE0F Stop hook \u5916\u5C42\u515C\u5E95\uFF08dispatch \u5185\u90E8\u5BB9\u9519\u5931\u6548\uFF0C\u5C5E\u5F02\u5E38\u4FE1\u53F7\uFF09:", e instanceof Error ? e.message : e);
13354
13678
  }
13355
13679
  try {
13356
- await updateCalibration(sessionId, { calibRatio, lastRealPromptTokens, lastCachedTokens });
13680
+ await updateCalibration(sessionId, effectiveModel, { calibRatio, lastRealPromptTokens, lastCachedTokens });
13357
13681
  } catch (e) {
13358
13682
  console.warn("\u26A0\uFE0F \u538B\u7F29\u6821\u51C6\u72B6\u6001\u843D\u76D8\u5931\u8D25\uFF08\u4E0B run \u6821\u51C6\u91CD\u96F6\uFF0C\u7F13\u5B58\u611F\u77E5\u538B\u7F29\u9000\u5316\u56DE\u7F3A\u7701 1.4\uFF09:", e instanceof Error ? e.message : e);
13359
13683
  }
13360
13684
  }
13361
13685
  }
13686
+ var resolveFirstPrompt;
13362
13687
  var init_runAgent = __esm({
13363
13688
  "../core/src/agent/runAgent.ts"() {
13364
13689
  "use strict";
@@ -13375,9 +13700,23 @@ var init_runAgent = __esm({
13375
13700
  init_toolScheduling();
13376
13701
  init_streamInference();
13377
13702
  init_agentNudges();
13703
+ init_costRelay();
13378
13704
  init_usageLog();
13379
13705
  init_prices();
13380
13706
  init_createModel();
13707
+ init_client();
13708
+ resolveFirstPrompt = (message, options) => {
13709
+ if ((options.depth ?? 0) !== 0) return "";
13710
+ const explicit = options.currentUserText?.trim();
13711
+ if (explicit) return explicit;
13712
+ const arr = message;
13713
+ for (let i = arr.length - 1; i >= 0; i--) {
13714
+ if (arr[i]?.role !== "user") continue;
13715
+ const t = msgText(arr[i]?.content);
13716
+ if (t.trim()) return t;
13717
+ }
13718
+ return "";
13719
+ };
13381
13720
  }
13382
13721
  });
13383
13722
 
@@ -13435,11 +13774,12 @@ var init_lineage = __esm({
13435
13774
  });
13436
13775
 
13437
13776
  // ../core/src/agent/subagent.ts
13438
- var SUBAGENT_DENYLIST, activeSubagents, failText, RESUME_NOTE, createProgressThrottler, runSubagent;
13777
+ var SUBAGENT_DENYLIST, filterToolsForSubagent, activeSubagents, failText, RESUME_NOTE, createProgressThrottler, runSubagent;
13439
13778
  var init_subagent = __esm({
13440
13779
  "../core/src/agent/subagent.ts"() {
13441
13780
  "use strict";
13442
13781
  init_runAgent();
13782
+ init_config();
13443
13783
  init_content();
13444
13784
  init_transcript();
13445
13785
  init_common();
@@ -13447,7 +13787,14 @@ var init_subagent = __esm({
13447
13787
  init_registry4();
13448
13788
  init_hooks();
13449
13789
  init_lineage();
13450
- SUBAGENT_DENYLIST = /* @__PURE__ */ new Set(["run_command", "delete_path"]);
13790
+ SUBAGENT_DENYLIST = /* @__PURE__ */ new Set(["run_command", "delete_path", "run_in_background"]);
13791
+ filterToolsForSubagent = (allTools, manifest) => {
13792
+ if (manifest && manifest.tools.length > 0) {
13793
+ const wantsMcp = manifest.tools.some((t) => t === "mcp_call" || t === "mcp_list_tools" || t.startsWith("mcp__"));
13794
+ return allTools.filter((t) => manifest.tools.includes(t.function.name) || wantsMcp && (t.function.name === "mcp_call" || t.function.name === "mcp_list_tools"));
13795
+ }
13796
+ return allTools.filter((t) => t.function.subagentAllowed !== false && !SUBAGENT_DENYLIST.has(t.function.name));
13797
+ };
13451
13798
  activeSubagents = /* @__PURE__ */ new Set();
13452
13799
  failText = (msg) => toolFailure(msg).content;
13453
13800
  RESUME_NOTE = [
@@ -13551,8 +13898,7 @@ var init_subagent = __esm({
13551
13898
  ...resuming ? [RESUME_NOTE] : []
13552
13899
  ].filter(Boolean).join("\n\n");
13553
13900
  const allTools = getGlobalTools();
13554
- const wantsMcp = manifest?.tools.some((t) => t === "mcp_call" || t === "mcp_list_tools" || t.startsWith("mcp__")) ?? false;
13555
- const toolSchemas = manifest && manifest.tools.length > 0 ? allTools.filter((t) => manifest.tools.includes(t.function.name) || wantsMcp && (t.function.name === "mcp_call" || t.function.name === "mcp_list_tools")) : allTools.filter((t) => !SUBAGENT_DENYLIST.has(t.function.name));
13901
+ const toolSchemas = filterToolsForSubagent(allTools, manifest);
13556
13902
  console.log(`${resuming ? "\u{1F501} \u7EED\u8DD1" : "\u{1F423} \u6D3E\u751F"}\u5B50 Agent [\u6DF1\u5EA6: ${ctx.depth + 1}/${MAX_AGENT_DEPTH}][${subSessionId}]${manifest ? ` \u58F0\u660E\u5F0F=${manifest.name}` : ""} \u4EFB\u52A1: "${task.slice(0, 50)}..."`);
13557
13903
  activeSubagents.add(subSessionId);
13558
13904
  ctx.onUIEvent?.({ type: "subagent.count", running: activeSubagents.size, phase: "start", name: manifest?.name, depth: ctx.depth + 1 });
@@ -13567,7 +13913,9 @@ var init_subagent = __esm({
13567
13913
  // ★ 透传父级工作目录,子 agent 的 hook/工具相对路径与父级一致
13568
13914
  toolSchemas,
13569
13915
  abortSignal: ctx.abortSignal,
13570
- modelWindow: ctx.modelWindow,
13916
+ // ★ P1-4:manifest.model 覆盖时窗口按该模型解析(MODEL_WINDOWS 表 / 全局回退);
13917
+ // 未覆盖(与父同模型)直接继承父窗口,行为不变。
13918
+ modelWindow: manifest?.model ? resolveModelWindow(manifest.model) : ctx.modelWindow,
13571
13919
  depth: ctx.depth + 1,
13572
13920
  keepRecentUnits: ctx.keepRecentUnits,
13573
13921
  compactRatio: ctx.compactRatio,
@@ -13585,13 +13933,15 @@ var init_subagent = __esm({
13585
13933
  // ★ 子 agent final 是交付父级的汇报,EARLY_FINAL 误推一轮纯浪费;父可经续跑纠错
13586
13934
  model: manifest?.model,
13587
13935
  // ★ per-agent 模型覆盖;undefined 时 model.ts 回退全局 MODEL_NAME
13588
- costSink: ctx.reportCost
13936
+ costSink: ctx.reportCost,
13589
13937
  // ★ 子 run 成本回灌父累计器(成本信任链第二环 E1):父预算自动继承约束
13938
+ budgetCheck: ctx.budgetCheck
13939
+ // ★ P1-5 预算熔断链式透传:子 runAgent 轮边界探测父预算,超支即收尾(不等烧完)
13590
13940
  };
13591
13941
  const startCtx = { sessionId: subSessionId, parentSessionId: ctx.sessionId, task, depth: ctx.depth + 1, name: manifest?.name, cwd: ctx.cwd };
13592
13942
  await dispatch("SubagentStart", startCtx);
13593
13943
  let result;
13594
- const progress = createProgressThrottler((line) => ctx.onUIEvent?.({ type: "subagent.progress", text: line }));
13944
+ const progress = createProgressThrottler((line) => ctx.onUIEvent?.({ type: "subagent.progress", text: line, agentKey: subSessionId, name: manifest?.name }));
13595
13945
  try {
13596
13946
  for await (const e of runAgent(subMessages, subOptions)) {
13597
13947
  if (ctx.abortSignal?.aborted) {
@@ -13615,7 +13965,7 @@ var init_subagent = __esm({
13615
13965
  result = { ok: false, output: failText(`[\u5B50Agent\u5D29\u6E83]\uFF1A\u5B50 Agent \u5728\u8FED\u4EE3\u63A8\u7406\u4E3B\u5FAA\u73AF\u65F6\u906D\u9047\u5E95\u5C42\u5F02\u5E38: ${streamError.message}`), sessionId: subSessionId, manifestName: manifest?.name };
13616
13966
  } finally {
13617
13967
  try {
13618
- ctx.onUIEvent?.({ type: "subagent.progress", text: "", done: true });
13968
+ ctx.onUIEvent?.({ type: "subagent.progress", text: "", done: true, agentKey: subSessionId });
13619
13969
  } catch {
13620
13970
  }
13621
13971
  if (result) {
@@ -14861,14 +15211,15 @@ var createWebRequestApproval;
14861
15211
  var init_webHost = __esm({
14862
15212
  "../core/src/host/webHost.ts"() {
14863
15213
  "use strict";
14864
- createWebRequestApproval = (onUIEvent, abortSignal) => {
15214
+ createWebRequestApproval = (onUIEvent, abortSignal, opts) => {
14865
15215
  return async (detail, meta) => {
14866
15216
  onUIEvent?.({
14867
15217
  type: "approval_request",
14868
15218
  sessionId: meta.sessionId,
14869
15219
  toolsId: meta.toolCallId,
14870
15220
  toolName: meta.toolName,
14871
- detail
15221
+ detail,
15222
+ ...opts?.includeFullDetail && meta.fullDetail ? { fullDetail: meta.fullDetail } : {}
14872
15223
  });
14873
15224
  const { waitForUserApproval: waitForUserApproval2 } = await Promise.resolve().then(() => (init_approvalGate(), approvalGate_exports));
14874
15225
  return waitForUserApproval2(meta.sessionId, meta.toolCallId, abortSignal);
@@ -15012,7 +15363,8 @@ ${envLines.join("\n")}`;
15012
15363
  // 宿主可按需裁剪(挂机批处理);缺省全量,行为不变
15013
15364
  abortSignal,
15014
15365
  // ← 透传中止信号
15015
- modelWindow: appConfig.MAX_HISTORY_TOKENS,
15366
+ modelWindow: resolveModelWindow(opts?.model),
15367
+ // ★ P1-4:窗口按本轮模型解析(MODEL_WINDOWS 表 / 全局回退)
15016
15368
  keepRecentUnits: appConfig.KEEP_RECENT_UNITS,
15017
15369
  compactRatio: appConfig.COMPACT_RATIO,
15018
15370
  parentSystemPrompt: sysPrompt,
@@ -15031,8 +15383,11 @@ ${envLines.join("\n")}`;
15031
15383
  thinkingLevel: opts?.thinkingLevel,
15032
15384
  locale: opts?.locale,
15033
15385
  outputStyle: opts?.outputStyle,
15034
- budgetYuan: opts?.budgetYuan
15386
+ budgetYuan: opts?.budgetYuan,
15035
15387
  // ★ 预算制运行(成本信任链第二环 E1):主 run 传入,子 run 经 costSink 回灌
15388
+ // ★ P0-1:本轮用户输入原文(inbound.content 已过 slash 展开与 hook 注入定型),供 firstPrompt
15389
+ // 判定取「当前轮」而非消息数组首条(首条复杂 + 本轮寒暄曾误触发 PLAN_FIRST/EARLY_FINAL)。
15390
+ currentUserText: inbound.content
15036
15391
  };
15037
15392
  await runWithSessionContext(sessionId, async () => {
15038
15393
  try {
@@ -15263,13 +15618,153 @@ ${s.slice(-half)}`;
15263
15618
  }
15264
15619
  });
15265
15620
 
15621
+ // src/lineDiff.ts
15622
+ var splitLines, lineDiff, collapseContext;
15623
+ var init_lineDiff = __esm({
15624
+ "src/lineDiff.ts"() {
15625
+ "use strict";
15626
+ splitLines = (s) => {
15627
+ const t = String(s ?? "").replace(/\r\n/g, "\n");
15628
+ return t === "" ? [] : t.split("\n");
15629
+ };
15630
+ lineDiff = (oldStr, newStr) => {
15631
+ const a = splitLines(oldStr);
15632
+ const b = splitLines(newStr);
15633
+ let s = 0;
15634
+ while (s < a.length && s < b.length && a[s] === b[s]) s++;
15635
+ let e = 0;
15636
+ while (e < a.length - s && e < b.length - s && a[a.length - 1 - e] === b[b.length - 1 - e]) e++;
15637
+ const m1 = a.slice(s, a.length - e);
15638
+ const m2 = b.slice(s, b.length - e);
15639
+ const rows = [];
15640
+ for (let i = 0; i < s; i++) rows.push({ t: "ctx", s: a[i] });
15641
+ const n = m1.length, m = m2.length;
15642
+ if (n === 0 && m === 0) {
15643
+ } else if (n * m > 4e6) {
15644
+ for (const l of m1) rows.push({ t: "del", s: l });
15645
+ for (const l of m2) rows.push({ t: "add", s: l });
15646
+ } else {
15647
+ const w = m + 1;
15648
+ const dp = new Uint32Array((n + 1) * w);
15649
+ for (let i2 = n - 1; i2 >= 0; i2--) {
15650
+ for (let j2 = m - 1; j2 >= 0; j2--) {
15651
+ dp[i2 * w + j2] = m1[i2] === m2[j2] ? dp[(i2 + 1) * w + j2 + 1] + 1 : Math.max(dp[(i2 + 1) * w + j2], dp[i2 * w + j2 + 1]);
15652
+ }
15653
+ }
15654
+ let i = 0, j = 0;
15655
+ while (i < n && j < m) {
15656
+ if (m1[i] === m2[j]) {
15657
+ rows.push({ t: "ctx", s: m1[i] });
15658
+ i++;
15659
+ j++;
15660
+ } else if (dp[(i + 1) * w + j] >= dp[i * w + j + 1]) {
15661
+ rows.push({ t: "del", s: m1[i] });
15662
+ i++;
15663
+ } else {
15664
+ rows.push({ t: "add", s: m2[j] });
15665
+ j++;
15666
+ }
15667
+ }
15668
+ while (i < n) {
15669
+ rows.push({ t: "del", s: m1[i] });
15670
+ i++;
15671
+ }
15672
+ while (j < m) {
15673
+ rows.push({ t: "add", s: m2[j] });
15674
+ j++;
15675
+ }
15676
+ }
15677
+ for (let k = 0; k < e; k++) rows.push({ t: "ctx", s: a[a.length - e + k] });
15678
+ return rows;
15679
+ };
15680
+ collapseContext = (rows, keep = 2) => {
15681
+ const out = [];
15682
+ let i = 0;
15683
+ while (i < rows.length) {
15684
+ if (rows[i].t !== "ctx") {
15685
+ out.push(rows[i]);
15686
+ i++;
15687
+ continue;
15688
+ }
15689
+ let j = i;
15690
+ while (j < rows.length && rows[j].t === "ctx") j++;
15691
+ const run = j - i;
15692
+ if (run > keep * 2 + 1) {
15693
+ for (let k = 0; k < keep; k++) out.push(rows[i + k]);
15694
+ out.push({ t: "ellip", n: run - keep * 2 });
15695
+ for (let k = run - keep; k < run; k++) out.push(rows[i + k]);
15696
+ } else {
15697
+ for (let k = 0; k < run; k++) out.push(rows[i + k]);
15698
+ }
15699
+ i = j;
15700
+ }
15701
+ return out;
15702
+ };
15703
+ }
15704
+ });
15705
+
15706
+ // src/diffSummary.ts
15707
+ import fs48 from "fs/promises";
15708
+ import path49 from "path";
15709
+ var readTextIfExists, buildRunDiff;
15710
+ var init_diffSummary = __esm({
15711
+ "src/diffSummary.ts"() {
15712
+ "use strict";
15713
+ init_lineDiff();
15714
+ init_store2();
15715
+ readTextIfExists = async (p) => {
15716
+ try {
15717
+ return await fs48.readFile(p, "utf-8");
15718
+ } catch {
15719
+ return null;
15720
+ }
15721
+ };
15722
+ buildRunDiff = async (sessionId, records) => {
15723
+ const byPath = /* @__PURE__ */ new Map();
15724
+ for (const r of records) {
15725
+ if (r.restored) continue;
15726
+ const prev = byPath.get(r.relativePath);
15727
+ if (!prev || (r.timestamp || "") < (prev.timestamp || "")) byPath.set(r.relativePath, r);
15728
+ }
15729
+ const root = process.env.WORKSPACE_ROOT ?? process.cwd();
15730
+ const out = [];
15731
+ for (const r of [...byPath.values()].sort((a, b) => a.relativePath.localeCompare(b.relativePath))) {
15732
+ const abs = path49.join(root, ...r.relativePath.split("/"));
15733
+ if (r.backupKind === "directory_tree") {
15734
+ out.push({ path: r.relativePath, note: "\u25A4 \u76EE\u5F55\u6811\u6539\u52A8\uFF08\u5185\u5BB9 diff \u7565\uFF0C\u53EF /rewind \u56DE\u9000\uFF09", rows: null });
15735
+ continue;
15736
+ }
15737
+ const disk = await readTextIfExists(abs);
15738
+ if (disk === null) {
15739
+ out.push({ path: r.relativePath, note: "\uFF08\u6587\u4EF6\u5DF2\u4E0D\u5728\u78C1\u76D8\u2014\u2014\u5DF2\u88AB\u56DE\u9000\u6216\u624B\u52A8\u5220\u9664\uFF09", rows: null });
15740
+ continue;
15741
+ }
15742
+ let before;
15743
+ if (r.backupKind === "creation_marker") {
15744
+ before = "";
15745
+ } else {
15746
+ before = await readTextIfExists(path49.join(getUndoBackupRoot(sessionId), r.undoId, "content"));
15747
+ }
15748
+ if (before === null) {
15749
+ out.push({ path: r.relativePath, note: "\uFF08\u5907\u4EFD\u5DF2\u88AB\u6E05\u7406\uFF0C\u65E0\u6CD5\u751F\u6210\u7EA2\u7EFF\u5BF9\u6BD4\uFF09", rows: null });
15750
+ continue;
15751
+ }
15752
+ const rows = collapseContext(lineDiff(before, disk));
15753
+ if (!rows.some((c) => c.t === "add" || c.t === "del")) continue;
15754
+ out.push({ path: r.relativePath, rows });
15755
+ }
15756
+ return out;
15757
+ };
15758
+ }
15759
+ });
15760
+
15266
15761
  // src/cliHost.ts
15267
15762
  var createCliRequestApproval, createCliRequestQuestion;
15268
15763
  var init_cliHost = __esm({
15269
15764
  "src/cliHost.ts"() {
15270
15765
  "use strict";
15271
15766
  createCliRequestApproval = (askApproval) => {
15272
- return async (detail, meta) => askApproval(detail, meta.toolName);
15767
+ return async (detail, meta) => askApproval(detail, meta.toolName, meta.fullDetail);
15273
15768
  };
15274
15769
  createCliRequestQuestion = (askQuestion) => {
15275
15770
  return async (req) => askQuestion(req);
@@ -15461,6 +15956,9 @@ var init_useChatState = __esm({
15461
15956
  init_guard();
15462
15957
  init_createModel();
15463
15958
  init_tool();
15959
+ init_permissions();
15960
+ init_restore();
15961
+ init_diffSummary();
15464
15962
  init_cliHost();
15465
15963
  init_prefs();
15466
15964
  init_strings();
@@ -15486,6 +15984,7 @@ var init_useChatState = __esm({
15486
15984
  const [pendingSessions, setPendingSessions] = useState(null);
15487
15985
  const [pendingFork, setPendingFork] = useState(null);
15488
15986
  const [pendingModel, setPendingModel] = useState(null);
15987
+ const [pendingRewind, setPendingRewind] = useState(null);
15489
15988
  const nextId = useRef(1);
15490
15989
  const assistantStreamingId = useRef(null);
15491
15990
  const thinkingStreamingId = useRef(null);
@@ -15513,6 +16012,7 @@ var init_useChatState = __esm({
15513
16012
  const budgetYuanRef = useRef(void 0);
15514
16013
  const lastRunCacheHitRef = useRef(void 0);
15515
16014
  const contextUsageRef = useRef(void 0);
16015
+ const lastSubmitAtRef = useRef(0);
15516
16016
  const compactProgressIdRef = useRef(null);
15517
16017
  const dropCompactProgressRow = useCallback(() => {
15518
16018
  const pid = compactProgressIdRef.current;
@@ -15520,12 +16020,20 @@ var init_useChatState = __esm({
15520
16020
  compactProgressIdRef.current = null;
15521
16021
  setRows((prev) => prev.filter((r) => r.id !== pid));
15522
16022
  }, []);
15523
- const subagentProgressIdRef = useRef(null);
15524
- const dropSubagentProgressRow = useCallback(() => {
15525
- const pid = subagentProgressIdRef.current;
15526
- if (pid == null) return;
15527
- subagentProgressIdRef.current = null;
15528
- setRows((prev) => prev.filter((r) => r.id !== pid));
16023
+ const subagentProgressRowsRef = useRef(/* @__PURE__ */ new Map());
16024
+ const dropSubagentProgressRow = useCallback((key) => {
16025
+ const m = subagentProgressRowsRef.current;
16026
+ if (key) {
16027
+ const rid = m.get(key);
16028
+ if (rid == null) return;
16029
+ m.delete(key);
16030
+ setRows((prev) => prev.filter((r) => r.id !== rid));
16031
+ return;
16032
+ }
16033
+ if (m.size === 0) return;
16034
+ const ids = new Set(m.values());
16035
+ m.clear();
16036
+ setRows((prev) => prev.filter((r) => !ids.has(r.id)));
15529
16037
  }, []);
15530
16038
  const outputStyleRef = useRef(void 0);
15531
16039
  const lastUsageRef = useRef(null);
@@ -15631,6 +16139,24 @@ var init_useChatState = __esm({
15631
16139
  scheduleFlush();
15632
16140
  break;
15633
16141
  }
16142
+ case "text.reset": {
16143
+ if (flushTimer.current) {
16144
+ clearTimeout(flushTimer.current);
16145
+ flushTimer.current = null;
16146
+ }
16147
+ textBuffer.current = "";
16148
+ thinkingBuffer.current = "";
16149
+ const raId = assistantStreamingId.current;
16150
+ const rthId = thinkingStreamingId.current;
16151
+ if (raId != null || rthId != null) {
16152
+ setRows((prev) => prev.map((row) => {
16153
+ if (row.id === raId && row.kind === "assistant") return { ...row, text: "" };
16154
+ if (row.id === rthId && row.kind === "thinking") return { ...row, text: "" };
16155
+ return row;
16156
+ }));
16157
+ }
16158
+ break;
16159
+ }
15634
16160
  case "tool.start": {
15635
16161
  closeStreaming();
15636
16162
  const id = newRowId();
@@ -15700,16 +16226,19 @@ var init_useChatState = __esm({
15700
16226
  const durText = durMs >= 6e4 ? `${Math.floor(durMs / 6e4)}m${Math.round(durMs % 6e4 / 1e3)}s` : `${(durMs / 1e3).toFixed(1)}s`;
15701
16227
  const cost = obj.costYuan;
15702
16228
  const toolCost = typeof obj.toolCostYuan === "number" && obj.toolCostYuan > 0 ? obj.toolCostYuan : null;
16229
+ const auxCost = typeof obj.auxCostYuan === "number" && obj.auxCostYuan > 0 ? obj.auxCostYuan : null;
16230
+ const subCost = typeof obj.subCostYuan === "number" && obj.subCostYuan > 0 ? obj.subCostYuan : null;
15703
16231
  const tokTotal = promptT + completionT;
15704
16232
  const tokText = tokTotal >= 1e4 ? `${(tokTotal / 1e3).toFixed(1)}k` : String(tokTotal);
15705
16233
  const costText = typeof cost === "number" ? `\xA5${cost.toFixed(cost < 5e-3 ? 4 : 3)}` : `${tokText} tok`;
15706
16234
  setRows((prev) => [...prev, {
15707
16235
  id: newRowId(),
15708
16236
  kind: "meta",
15709
- text: `\u{1F4B0} \u672C\u6B21 ${rounds} \u8F6E \xB7 ${costText}${toolCost != null ? ` \xB7 \u8054\u7F51 \xA5${toolCost.toFixed(3)}` : ""} \xB7 \u7F13\u5B58\u547D\u4E2D ${hitPct}% \xB7 ${durText}${obj.budgetTruncated ? " \xB7 \u9884\u7B97\u622A\u65AD" : ""}`
16237
+ text: `\u{1F4B0} \u672C\u6B21 ${rounds} \u8F6E \xB7 ${costText}${toolCost != null ? ` \xB7 \u8054\u7F51 \xA5${toolCost.toFixed(3)}` : ""}${auxCost != null ? ` \xB7 \u8F85\u52A9 \xA5${auxCost.toFixed(3)}` : ""}${subCost != null ? ` \xB7 \u5B50\u4EFB\u52A1 \xA5${subCost.toFixed(3)}` : ""} \xB7 \u7F13\u5B58\u547D\u4E2D ${hitPct}% \xB7 ${durText}${obj.budgetTruncated ? " \xB7 \u9884\u7B97\u622A\u65AD" : ""}`
15710
16238
  }]);
15711
- lastRunCostRef.current = typeof cost === "number" ? cost : void 0;
16239
+ lastRunCostRef.current = typeof cost === "number" ? cost + (auxCost ?? 0) : auxCost != null ? auxCost : void 0;
15712
16240
  sessionCostRef.current = typeof cost === "number" ? (sessionCostRef.current ?? 0) + cost : sessionCostRef.current;
16241
+ if (auxCost != null) sessionCostRef.current = (sessionCostRef.current ?? 0) + auxCost;
15713
16242
  lastRunCacheHitRef.current = typeof obj.cacheHitRate === "number" ? obj.cacheHitRate : void 0;
15714
16243
  break;
15715
16244
  }
@@ -15738,19 +16267,23 @@ var init_useChatState = __esm({
15738
16267
  }
15739
16268
  case "subagent.progress": {
15740
16269
  flush();
16270
+ const agentKey = typeof obj.agentKey === "string" && obj.agentKey ? obj.agentKey : "__default__";
15741
16271
  if (obj.done) {
15742
- dropSubagentProgressRow();
16272
+ dropSubagentProgressRow(agentKey);
15743
16273
  break;
15744
16274
  }
15745
16275
  const ptext = String(obj.text ?? "").slice(0, 160);
15746
16276
  if (!ptext) break;
15747
- if (subagentProgressIdRef.current == null) {
16277
+ const agentName = typeof obj.name === "string" && obj.name ? `\u27E8${obj.name}\u27E9 ` : "";
16278
+ const pline = `${agentName}${ptext}`;
16279
+ const rowMap = subagentProgressRowsRef.current;
16280
+ const existingId = rowMap.get(agentKey);
16281
+ if (existingId == null) {
15748
16282
  const id = newRowId();
15749
- subagentProgressIdRef.current = id;
15750
- setRows((prev) => [...prev, { id, kind: "subagent-progress", text: ptext }]);
16283
+ rowMap.set(agentKey, id);
16284
+ setRows((prev) => [...prev, { id, kind: "subagent-progress", text: pline }]);
15751
16285
  } else {
15752
- const pid = subagentProgressIdRef.current;
15753
- setRows((prev) => prev.map((r) => r.id === pid && r.kind === "subagent-progress" ? { ...r, text: ptext } : r));
16286
+ setRows((prev) => prev.map((r) => r.id === existingId && r.kind === "subagent-progress" ? { ...r, text: pline } : r));
15754
16287
  }
15755
16288
  break;
15756
16289
  }
@@ -15830,7 +16363,7 @@ var init_useChatState = __esm({
15830
16363
  break;
15831
16364
  }
15832
16365
  }, [closeStreaming, flush, scheduleFlush, pushInfo]);
15833
- const askApproval = useCallback((detail, toolName) => new Promise((resolve) => setPendingApproval({ detail, toolName, resolve })), []);
16366
+ const askApproval = useCallback((detail, toolName, fullDetail) => new Promise((resolve) => setPendingApproval({ detail, toolName, fullDetail, resolve })), []);
15834
16367
  const resolveApproval = useCallback((v) => {
15835
16368
  setPendingApproval((prev) => {
15836
16369
  prev?.resolve(v);
@@ -15839,7 +16372,7 @@ var init_useChatState = __esm({
15839
16372
  }, []);
15840
16373
  const autoRequestApproval = useCallback(async (_detail, meta) => {
15841
16374
  const t = findTool(meta.toolName);
15842
- if (!t || t.function.safetyLevel === "danger" /* DANGER */) return askApproval(_detail, meta.toolName);
16375
+ if (!t || t.function.safetyLevel === "danger" /* DANGER */) return askApproval(_detail, meta.toolName, meta.fullDetail);
15843
16376
  return "allow-once";
15844
16377
  }, [askApproval]);
15845
16378
  const askQuestion = useCallback((req) => new Promise((resolve) => setPendingQuestion({ req, resolve })), []);
@@ -15935,9 +16468,12 @@ ${finalPlan}`, false, res.autoExecute);
15935
16468
  const runPlanStage = useCallback(async (sid, researchPrompt) => {
15936
16469
  await runOnce(sid, researchPrompt, true);
15937
16470
  const plan = takeProposedPlan();
15938
- if (plan == null) return false;
16471
+ if (plan == null) {
16472
+ pushInfo(S.planNoProposal);
16473
+ return false;
16474
+ }
15939
16475
  return await approveAndImplement(sid, plan);
15940
- }, [runOnce, takeProposedPlan, approveAndImplement]);
16476
+ }, [runOnce, takeProposedPlan, approveAndImplement, pushInfo]);
15941
16477
  const queueInput = useCallback((content) => {
15942
16478
  const text = content.trim();
15943
16479
  const sid = sessionIdRef.current;
@@ -15952,6 +16488,7 @@ ${finalPlan}`, false, res.autoExecute);
15952
16488
  if (!text || busy) return;
15953
16489
  setBusy(true);
15954
16490
  const myGen = ++turnGenRef.current;
16491
+ lastSubmitAtRef.current = Date.now();
15955
16492
  try {
15956
16493
  if (sessionIdRef.current == null) {
15957
16494
  sessionIdRef.current = await getOrCreateSessionId(initialSessionId ?? void 0);
@@ -16022,6 +16559,8 @@ ${r.output.trim() || S.bangNoOutput}`);
16022
16559
  toolRowByCallId.current.clear();
16023
16560
  }, [flush]);
16024
16561
  const loadSession = useCallback(async (id) => {
16562
+ const prev = sessionIdRef.current;
16563
+ if (prev && prev !== id) clearSessionPermissionRules(prev);
16025
16564
  sessionIdRef.current = id;
16026
16565
  lastRunCostRef.current = void 0;
16027
16566
  sessionCostRef.current = void 0;
@@ -16052,6 +16591,8 @@ ${r.output.trim() || S.bangNoOutput}`);
16052
16591
  if (picked) await loadSession(picked);
16053
16592
  }, [pushInfo, loadSession]);
16054
16593
  const newSession = useCallback(async () => {
16594
+ const prev = sessionIdRef.current;
16595
+ if (prev) clearSessionPermissionRules(prev);
16055
16596
  sessionIdRef.current = await getOrCreateSessionId(void 0);
16056
16597
  lastRunCostRef.current = void 0;
16057
16598
  sessionCostRef.current = void 0;
@@ -16109,6 +16650,60 @@ ${r.output.trim() || S.bangNoOutput}`);
16109
16650
  }
16110
16651
  return picked;
16111
16652
  }, [pushInfo, setModelOverride]);
16653
+ const resolveRewind = useCallback((undoId) => {
16654
+ setPendingRewind((prev) => {
16655
+ prev?.resolve(undoId);
16656
+ return null;
16657
+ });
16658
+ }, []);
16659
+ const openRewindPicker = useCallback(async () => {
16660
+ const sid = sessionIdRef.current;
16661
+ if (!sid) {
16662
+ pushInfo(S.rewindNoSession);
16663
+ return;
16664
+ }
16665
+ let records = [];
16666
+ try {
16667
+ records = await listUndoable(sid);
16668
+ } catch {
16669
+ }
16670
+ if (records.length === 0) {
16671
+ pushInfo(S.rewindEmpty);
16672
+ return;
16673
+ }
16674
+ const picked = await new Promise((resolve) => setPendingRewind({ records, resolve }));
16675
+ if (!picked) return;
16676
+ try {
16677
+ pushInfo(await restoreByUndoId(picked, sid));
16678
+ } catch (e) {
16679
+ pushInfo(S.rewindFail(e?.message ?? String(e)));
16680
+ }
16681
+ }, [pushInfo]);
16682
+ const openDiff = useCallback(async () => {
16683
+ const sid = sessionIdRef.current;
16684
+ const sinceMs = lastSubmitAtRef.current;
16685
+ if (!sid || !sinceMs) {
16686
+ pushInfo(S.diffEmpty);
16687
+ return;
16688
+ }
16689
+ let records = [];
16690
+ try {
16691
+ records = await listUndoable(sid);
16692
+ } catch {
16693
+ }
16694
+ const sinceIso = new Date(sinceMs).toISOString();
16695
+ const runRecords = records.filter((r) => (r.timestamp || "") >= sinceIso);
16696
+ if (runRecords.length === 0) {
16697
+ pushInfo(S.diffEmpty);
16698
+ return;
16699
+ }
16700
+ const files = await buildRunDiff(sid, runRecords);
16701
+ if (files.length === 0) {
16702
+ pushInfo(S.diffEmpty);
16703
+ return;
16704
+ }
16705
+ setRows((prev) => [...prev, { id: newRowId(), kind: "diff-summary", title: S.diffTitle, files }]);
16706
+ }, [pushInfo]);
16112
16707
  const setPlanMode = useCallback((on) => {
16113
16708
  planModeRef.current = on;
16114
16709
  }, []);
@@ -16147,6 +16742,7 @@ ${r.output.trim() || S.bangNoOutput}`);
16147
16742
  pendingSessions,
16148
16743
  pendingFork,
16149
16744
  pendingModel,
16745
+ pendingRewind,
16150
16746
  sessionIdRef,
16151
16747
  // 动作
16152
16748
  submit,
@@ -16186,7 +16782,10 @@ ${r.output.trim() || S.bangNoOutput}`);
16186
16782
  resolveFork,
16187
16783
  newSession,
16188
16784
  openModelPicker,
16189
- resolveModel
16785
+ resolveModel,
16786
+ openRewindPicker,
16787
+ resolveRewind,
16788
+ openDiff
16190
16789
  };
16191
16790
  };
16192
16791
  }
@@ -16569,91 +17168,6 @@ var init_ThinkingBlock = __esm({
16569
17168
  }
16570
17169
  });
16571
17170
 
16572
- // src/lineDiff.ts
16573
- var splitLines, lineDiff, collapseContext;
16574
- var init_lineDiff = __esm({
16575
- "src/lineDiff.ts"() {
16576
- "use strict";
16577
- splitLines = (s) => {
16578
- const t = String(s ?? "").replace(/\r\n/g, "\n");
16579
- return t === "" ? [] : t.split("\n");
16580
- };
16581
- lineDiff = (oldStr, newStr) => {
16582
- const a = splitLines(oldStr);
16583
- const b = splitLines(newStr);
16584
- let s = 0;
16585
- while (s < a.length && s < b.length && a[s] === b[s]) s++;
16586
- let e = 0;
16587
- while (e < a.length - s && e < b.length - s && a[a.length - 1 - e] === b[b.length - 1 - e]) e++;
16588
- const m1 = a.slice(s, a.length - e);
16589
- const m2 = b.slice(s, b.length - e);
16590
- const rows = [];
16591
- for (let i = 0; i < s; i++) rows.push({ t: "ctx", s: a[i] });
16592
- const n = m1.length, m = m2.length;
16593
- if (n === 0 && m === 0) {
16594
- } else if (n * m > 4e6) {
16595
- for (const l of m1) rows.push({ t: "del", s: l });
16596
- for (const l of m2) rows.push({ t: "add", s: l });
16597
- } else {
16598
- const w = m + 1;
16599
- const dp = new Uint32Array((n + 1) * w);
16600
- for (let i2 = n - 1; i2 >= 0; i2--) {
16601
- for (let j2 = m - 1; j2 >= 0; j2--) {
16602
- dp[i2 * w + j2] = m1[i2] === m2[j2] ? dp[(i2 + 1) * w + j2 + 1] + 1 : Math.max(dp[(i2 + 1) * w + j2], dp[i2 * w + j2 + 1]);
16603
- }
16604
- }
16605
- let i = 0, j = 0;
16606
- while (i < n && j < m) {
16607
- if (m1[i] === m2[j]) {
16608
- rows.push({ t: "ctx", s: m1[i] });
16609
- i++;
16610
- j++;
16611
- } else if (dp[(i + 1) * w + j] >= dp[i * w + j + 1]) {
16612
- rows.push({ t: "del", s: m1[i] });
16613
- i++;
16614
- } else {
16615
- rows.push({ t: "add", s: m2[j] });
16616
- j++;
16617
- }
16618
- }
16619
- while (i < n) {
16620
- rows.push({ t: "del", s: m1[i] });
16621
- i++;
16622
- }
16623
- while (j < m) {
16624
- rows.push({ t: "add", s: m2[j] });
16625
- j++;
16626
- }
16627
- }
16628
- for (let k = 0; k < e; k++) rows.push({ t: "ctx", s: a[a.length - e + k] });
16629
- return rows;
16630
- };
16631
- collapseContext = (rows, keep = 2) => {
16632
- const out = [];
16633
- let i = 0;
16634
- while (i < rows.length) {
16635
- if (rows[i].t !== "ctx") {
16636
- out.push(rows[i]);
16637
- i++;
16638
- continue;
16639
- }
16640
- let j = i;
16641
- while (j < rows.length && rows[j].t === "ctx") j++;
16642
- const run = j - i;
16643
- if (run > keep * 2 + 1) {
16644
- for (let k = 0; k < keep; k++) out.push(rows[i + k]);
16645
- out.push({ t: "ellip", n: run - keep * 2 });
16646
- for (let k = run - keep; k < run; k++) out.push(rows[i + k]);
16647
- } else {
16648
- for (let k = 0; k < run; k++) out.push(rows[i + k]);
16649
- }
16650
- i = j;
16651
- }
16652
- return out;
16653
- };
16654
- }
16655
- });
16656
-
16657
17171
  // src/diffView.ts
16658
17172
  var EDIT_TOOL_NAMES, extractEditPairs, pairsToDisplayRows, parseApprovalDiff;
16659
17173
  var init_diffView = __esm({
@@ -16812,7 +17326,7 @@ var init_TodosPanel = __esm({
16812
17326
  // src/components/ApprovalModal.tsx
16813
17327
  import { Box as Box5, Text as Text7 } from "ink";
16814
17328
  import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
16815
- var OPTIONS, MAX_DIFF_LINES, ApprovalModal;
17329
+ var OPTIONS, MAX_DIFF_LINES, MAX_FULL_LINES, ApprovalModal;
16816
17330
  var init_ApprovalModal = __esm({
16817
17331
  "src/components/ApprovalModal.tsx"() {
16818
17332
  "use strict";
@@ -16822,14 +17336,20 @@ var init_ApprovalModal = __esm({
16822
17336
  init_diffView();
16823
17337
  OPTIONS = [
16824
17338
  { label: S.approvalAllow, value: "allow-once", color: THEME.ok },
17339
+ // ★ I-4:会话级记忆(对标 CC「本会话不再询问」)——纯内存,会话结束失效
17340
+ { label: S.approvalAllowSession, value: "allow-session", color: THEME.ok },
16825
17341
  { label: S.approvalAllowAlways, value: "allow-always", color: THEME.warn },
16826
17342
  { label: S.approvalDeny, value: "deny", color: THEME.danger }
16827
17343
  ];
16828
17344
  MAX_DIFF_LINES = 18;
16829
- ApprovalModal = ({ toolName, detail, selectedIndex, wrapW }) => {
17345
+ MAX_FULL_LINES = 400;
17346
+ ApprovalModal = ({ toolName, detail, fullDetail, expanded, selectedIndex, wrapW }) => {
16830
17347
  const contentW = Math.max(16, wrapW);
16831
- const parsed = parseApprovalDiff(detail, toolName);
17348
+ const showFull = expanded && !!fullDetail;
17349
+ const sourceText = showFull ? fullDetail : detail;
17350
+ const parsed = parseApprovalDiff(sourceText, toolName);
16832
17351
  const diffRows = parsed ? pairsToDisplayRows(parsed.sections) : null;
17352
+ const cap = showFull ? MAX_FULL_LINES : MAX_DIFF_LINES;
16833
17353
  return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "round", borderColor: THEME.warn, paddingX: 1, paddingY: 1, marginTop: 1, children: [
16834
17354
  /* @__PURE__ */ jsxs5(Text7, { color: THEME.warn, bold: true, children: [
16835
17355
  S.approvalTitle,
@@ -16838,9 +17358,10 @@ var init_ApprovalModal = __esm({
16838
17358
  ] }),
16839
17359
  /* @__PURE__ */ jsx7(Box5, { flexDirection: "column", marginTop: 0.5, marginBottom: 0.5, children: parsed && diffRows ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
16840
17360
  wrapText(parsed.header, contentW).map((line, j) => /* @__PURE__ */ jsx7(Text7, { color: THEME.white, children: line }, `h-${j}`)),
16841
- diffRows.slice(0, MAX_DIFF_LINES).map((r, i) => /* @__PURE__ */ jsx7(Text7, { color: r.t === "del" ? THEME.danger : r.t === "add" ? THEME.ok : THEME.grayDim, children: r.t === "ellip" ? r.n > 0 ? ` \u22EF ${r.n} \u884C\u672A\u53D8` : " \u22EF" : `${r.t === "del" ? "- " : r.t === "add" ? "+ " : " "}${truncateMiddle(r.s, Math.max(20, contentW - 2))}` }, `d-${i}`)),
16842
- diffRows.length > MAX_DIFF_LINES ? /* @__PURE__ */ jsx7(Text7, { color: THEME.grayDim, children: ` \u2026\uFF08\u53E6\u6709 ${diffRows.length - MAX_DIFF_LINES} \u884C\u672A\u663E\u793A\uFF09` }) : null
16843
- ] }) : wrapText(detail || "(\u65E0\u8BF4\u660E)", contentW).map((line, j) => /* @__PURE__ */ jsx7(Text7, { color: THEME.white, children: line }, j)) }),
17361
+ diffRows.slice(0, cap).map((r, i) => /* @__PURE__ */ jsx7(Text7, { color: r.t === "del" ? THEME.danger : r.t === "add" ? THEME.ok : THEME.grayDim, children: r.t === "ellip" ? r.n > 0 ? ` \u22EF ${r.n} \u884C\u672A\u53D8` : " \u22EF" : `${r.t === "del" ? "- " : r.t === "add" ? "+ " : " "}${truncateMiddle(r.s, Math.max(20, contentW - 2))}` }, `d-${i}`)),
17362
+ diffRows.length > cap ? /* @__PURE__ */ jsx7(Text7, { color: THEME.grayDim, children: ` \u2026\uFF08\u53E6\u6709 ${diffRows.length - cap} \u884C\u672A\u663E\u793A${showFull ? "" : "\uFF0C\u6309 f \u67E5\u770B\u5B8C\u6574\u5185\u5BB9"}\uFF09` }) : null
17363
+ ] }) : wrapText(sourceText || "(\u65E0\u8BF4\u660E)", contentW).map((line, j) => /* @__PURE__ */ jsx7(Text7, { color: THEME.white, children: line }, j)) }),
17364
+ fullDetail ? /* @__PURE__ */ jsx7(Text7, { color: THEME.grayDim, children: S.approvalFullHint }) : null,
16844
17365
  /* @__PURE__ */ jsx7(Text7, { color: THEME.grayDim, children: S.approvalPrompt }),
16845
17366
  OPTIONS.map((opt, i) => {
16846
17367
  const active = i === selectedIndex;
@@ -17157,9 +17678,74 @@ var init_ModelPicker = __esm({
17157
17678
  }
17158
17679
  });
17159
17680
 
17160
- // src/components/SlashMenu.tsx
17681
+ // src/components/RewindPicker.tsx
17161
17682
  import { Box as Box13, Text as Text15 } from "ink";
17162
17683
  import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
17684
+ var PAGE_SIZE4, kindMark, RewindPicker;
17685
+ var init_RewindPicker = __esm({
17686
+ "src/components/RewindPicker.tsx"() {
17687
+ "use strict";
17688
+ init_theme();
17689
+ init_strings();
17690
+ init_util();
17691
+ PAGE_SIZE4 = 8;
17692
+ kindMark = (k) => k === "creation_marker" ? "\uFF0B" : k === "directory_tree" ? "\u25A4" : "~";
17693
+ RewindPicker = ({ records, selectedIndex, wrapW }) => {
17694
+ const start = Math.floor(Math.max(0, selectedIndex) / PAGE_SIZE4) * PAGE_SIZE4;
17695
+ const visible = records.slice(start, start + PAGE_SIZE4);
17696
+ const contentW = Math.max(24, wrapW);
17697
+ const metaSample = " \xB7 edit_file \xB7 59 \u5206\u949F\u524D";
17698
+ const pathW = Math.max(16, contentW - strWidth(metaSample) - 4);
17699
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", borderStyle: "round", borderColor: THEME.warn, paddingX: 1, paddingY: 1, marginTop: 1, children: [
17700
+ /* @__PURE__ */ jsx15(Text15, { color: THEME.warn, bold: true, children: S.rewindTitle }),
17701
+ /* @__PURE__ */ jsx15(Text15, { color: THEME.grayDim, children: S.rewindPrompt }),
17702
+ /* @__PURE__ */ jsx15(Box13, { flexDirection: "column", marginTop: 0.5, children: visible.map((r, i) => {
17703
+ const idx = start + i;
17704
+ const active = idx === selectedIndex;
17705
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "row", children: [
17706
+ /* @__PURE__ */ jsx15(Text15, { color: active ? THEME.coralBright : void 0, bold: true, children: active ? "\u203A " : " " }),
17707
+ /* @__PURE__ */ jsxs13(Text15, { color: THEME.grayDim, children: [
17708
+ kindMark(r.backupKind),
17709
+ " "
17710
+ ] }),
17711
+ /* @__PURE__ */ jsx15(Text15, { color: active ? THEME.white : THEME.gray, bold: active, children: truncateMiddle(r.relativePath, pathW) }),
17712
+ /* @__PURE__ */ jsx15(Text15, { color: THEME.grayDim, children: ` \xB7 ${truncateMiddle(r.operationType, 20)} \xB7 ${S.relTime(r.timestamp)}` })
17713
+ ] }, r.undoId);
17714
+ }) })
17715
+ ] });
17716
+ };
17717
+ }
17718
+ });
17719
+
17720
+ // src/components/DiffSummaryCard.tsx
17721
+ import { Box as Box14, Text as Text16 } from "ink";
17722
+ import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
17723
+ var MAX_ROWS_PER_FILE, DiffSummaryCard;
17724
+ var init_DiffSummaryCard = __esm({
17725
+ "src/components/DiffSummaryCard.tsx"() {
17726
+ "use strict";
17727
+ init_theme();
17728
+ init_util();
17729
+ MAX_ROWS_PER_FILE = 40;
17730
+ DiffSummaryCard = ({ title, files, wrapW }) => {
17731
+ const contentW = Math.max(16, wrapW);
17732
+ return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", marginTop: 1, children: [
17733
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.grayDim, bold: true, children: title }),
17734
+ files.map((f) => /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", marginTop: 0.5, children: [
17735
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.white, bold: true, children: truncateMiddle(f.path, Math.max(20, contentW)) }),
17736
+ f.note ? /* @__PURE__ */ jsx16(Text16, { color: THEME.grayDim, children: ` ${f.note}` }) : f.rows ? /* @__PURE__ */ jsxs14(Fragment3, { children: [
17737
+ f.rows.slice(0, MAX_ROWS_PER_FILE).map((r, i) => /* @__PURE__ */ jsx16(Text16, { color: r.t === "del" ? THEME.danger : r.t === "add" ? THEME.ok : THEME.grayDim, children: r.t === "ellip" ? r.n > 0 ? ` \u22EF ${r.n} \u884C\u672A\u53D8` : " \u22EF" : `${r.t === "del" ? "- " : r.t === "add" ? "+ " : " "}${truncateMiddle(r.s, Math.max(20, contentW - 2))}` }, i)),
17738
+ f.rows.length > MAX_ROWS_PER_FILE ? /* @__PURE__ */ jsx16(Text16, { color: THEME.grayDim, children: ` \u2026\uFF08\u53E6\u6709 ${f.rows.length - MAX_ROWS_PER_FILE} \u884C\u672A\u663E\u793A\uFF09` }) : null
17739
+ ] }) : null
17740
+ ] }, f.path))
17741
+ ] });
17742
+ };
17743
+ }
17744
+ });
17745
+
17746
+ // src/components/SlashMenu.tsx
17747
+ import { Box as Box15, Text as Text17 } from "ink";
17748
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
17163
17749
  var MAX_ROWS, SlashMenu;
17164
17750
  var init_SlashMenu = __esm({
17165
17751
  "src/components/SlashMenu.tsx"() {
@@ -17171,22 +17757,22 @@ var init_SlashMenu = __esm({
17171
17757
  const visible = entries.slice(start, start + MAX_ROWS);
17172
17758
  const cmdW = Math.min(22, Math.max(8, ...visible.map((e) => e.name.length)) + 1);
17173
17759
  const descMax = Math.max(12, cols - cmdW - 6);
17174
- return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", borderStyle: "round", borderColor: THEME.coral, paddingX: 1, paddingY: 0, marginTop: 1, children: [
17760
+ return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", borderStyle: "round", borderColor: THEME.coral, paddingX: 1, paddingY: 0, marginTop: 1, children: [
17175
17761
  visible.map((e, i) => {
17176
17762
  const active = i + start === selectedIndex;
17177
17763
  const desc = e.description.length > descMax ? `${e.description.slice(0, descMax - 1)}\u2026` : e.description;
17178
17764
  const gap = Math.max(1, cmdW - e.name.length);
17179
- return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "row", children: [
17180
- /* @__PURE__ */ jsx15(Text15, { color: active ? THEME.coralBright : void 0, bold: true, children: active ? "\u276F " : " " }),
17181
- /* @__PURE__ */ jsxs13(Text15, { color: active ? THEME.white : THEME.gray, bold: active, children: [
17765
+ return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "row", children: [
17766
+ /* @__PURE__ */ jsx17(Text17, { color: active ? THEME.coralBright : void 0, bold: true, children: active ? "\u276F " : " " }),
17767
+ /* @__PURE__ */ jsxs15(Text17, { color: active ? THEME.white : THEME.gray, bold: active, children: [
17182
17768
  "/",
17183
17769
  e.name
17184
17770
  ] }),
17185
- /* @__PURE__ */ jsx15(Text15, { color: THEME.grayDim, children: " ".repeat(gap) }),
17186
- /* @__PURE__ */ jsx15(Text15, { color: active ? THEME.gray : THEME.grayDim, children: desc })
17771
+ /* @__PURE__ */ jsx17(Text17, { color: THEME.grayDim, children: " ".repeat(gap) }),
17772
+ /* @__PURE__ */ jsx17(Text17, { color: active ? THEME.gray : THEME.grayDim, children: desc })
17187
17773
  ] }, e.name);
17188
17774
  }),
17189
- entries.length > MAX_ROWS ? /* @__PURE__ */ jsxs13(Text15, { color: THEME.coralMuted, children: [
17775
+ entries.length > MAX_ROWS ? /* @__PURE__ */ jsxs15(Text17, { color: THEME.coralMuted, children: [
17190
17776
  "\u2191\u2193 \u6EDA\u52A8 \xB7 ",
17191
17777
  selectedIndex + 1,
17192
17778
  "/",
@@ -17199,8 +17785,8 @@ var init_SlashMenu = __esm({
17199
17785
  });
17200
17786
 
17201
17787
  // src/components/StatusStrip.tsx
17202
- import { Box as Box14, Text as Text16 } from "ink";
17203
- import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
17788
+ import { Box as Box16, Text as Text18 } from "ink";
17789
+ import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
17204
17790
  var StatusStrip;
17205
17791
  var init_StatusStrip = __esm({
17206
17792
  "src/components/StatusStrip.tsx"() {
@@ -17217,18 +17803,18 @@ var init_StatusStrip = __esm({
17217
17803
  `state: ${stateText}`,
17218
17804
  sessionShort ? `session: ${sessionShort}` : ""
17219
17805
  ].filter(Boolean);
17220
- return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", flexShrink: 0, children: [
17221
- /* @__PURE__ */ jsx16(Text16, { color: THEME.coralMuted, children: customLine && customLine.trim() ? customLine : bits.join(" \xB7 ") }),
17222
- /* @__PURE__ */ jsx16(Text16, { color: stateColor, children: "" }),
17223
- /* @__PURE__ */ jsx16(Text16, { color: THEME.grayDim, children: S.cmdHint })
17806
+ return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", flexShrink: 0, children: [
17807
+ /* @__PURE__ */ jsx18(Text18, { color: THEME.coralMuted, children: customLine && customLine.trim() ? customLine : bits.join(" \xB7 ") }),
17808
+ /* @__PURE__ */ jsx18(Text18, { color: stateColor, children: "" }),
17809
+ /* @__PURE__ */ jsx18(Text18, { color: THEME.grayDim, children: S.cmdHint })
17224
17810
  ] });
17225
17811
  };
17226
17812
  }
17227
17813
  });
17228
17814
 
17229
17815
  // src/components/TopPanel.tsx
17230
- import { Box as Box15, Text as Text17 } from "ink";
17231
- import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
17816
+ import { Box as Box17, Text as Text19 } from "ink";
17817
+ import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
17232
17818
  var ROBOT, INNER_H, TopPanel;
17233
17819
  var init_TopPanel = __esm({
17234
17820
  "src/components/TopPanel.tsx"() {
@@ -17248,36 +17834,36 @@ var init_TopPanel = __esm({
17248
17834
  const leftW = Math.floor(innerW * 0.33);
17249
17835
  const rightW = innerW - leftW - 1;
17250
17836
  const ruleLen = Math.max(8, rightW - 2);
17251
- const VDivider = /* @__PURE__ */ jsx17(Box15, { flexDirection: "column", width: 1, flexShrink: 0, children: Array.from({ length: INNER_H }).map((_, i) => /* @__PURE__ */ jsx17(Text17, { color: THEME.coral, children: "\u2502" }, i)) });
17252
- return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", width: panelW, marginBottom: 1, children: [
17253
- /* @__PURE__ */ jsxs15(Text17, { children: [
17254
- /* @__PURE__ */ jsx17(Text17, { color: THEME.coralBright, bold: true, children: "* Welcome to " }),
17255
- /* @__PURE__ */ jsx17(Text17, { bold: true, color: THEME.white, children: "deep" }),
17256
- /* @__PURE__ */ jsx17(Text17, { bold: true, color: THEME.coralBright, children: "Seek" }),
17257
- /* @__PURE__ */ jsx17(Text17, { bold: true, color: THEME.white, children: "Code" }),
17258
- /* @__PURE__ */ jsxs15(Text17, { color: THEME.coralMuted, children: [
17837
+ const VDivider = /* @__PURE__ */ jsx19(Box17, { flexDirection: "column", width: 1, flexShrink: 0, children: Array.from({ length: INNER_H }).map((_, i) => /* @__PURE__ */ jsx19(Text19, { color: THEME.coral, children: "\u2502" }, i)) });
17838
+ return /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", width: panelW, marginBottom: 1, children: [
17839
+ /* @__PURE__ */ jsxs17(Text19, { children: [
17840
+ /* @__PURE__ */ jsx19(Text19, { color: THEME.coralBright, bold: true, children: "* Welcome to " }),
17841
+ /* @__PURE__ */ jsx19(Text19, { bold: true, color: THEME.white, children: "deep" }),
17842
+ /* @__PURE__ */ jsx19(Text19, { bold: true, color: THEME.coralBright, children: "Seek" }),
17843
+ /* @__PURE__ */ jsx19(Text19, { bold: true, color: THEME.white, children: "Code" }),
17844
+ /* @__PURE__ */ jsxs17(Text19, { color: THEME.coralMuted, children: [
17259
17845
  " ",
17260
17846
  VERSION
17261
17847
  ] })
17262
17848
  ] }),
17263
- /* @__PURE__ */ jsx17(Text17, { color: THEME.grayDim, children: cwd }),
17264
- /* @__PURE__ */ jsxs15(Box15, { flexDirection: "row", borderStyle: "round", borderColor: THEME.coral, marginTop: 1, paddingX: 0, paddingY: 0, children: [
17265
- /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", width: leftW, height: INNER_H, alignItems: "center", justifyContent: "center", children: [
17266
- /* @__PURE__ */ jsx17(Text17, { color: THEME.white, bold: true, children: "Welcome back!" }),
17267
- ROBOT.map((l, i) => /* @__PURE__ */ jsx17(Text17, { color: THEME.coralBright, children: l }, i)),
17268
- /* @__PURE__ */ jsx17(Text17, { color: THEME.grayDim, children: "Ctrl+C \u9000\u51FA \xB7 Esc \u4E2D\u6B62" }),
17269
- /* @__PURE__ */ jsx17(Text17, { color: THEME.grayDim, children: "\u8F93\u5165 / \u67E5\u770B\u547D\u4EE4" })
17849
+ /* @__PURE__ */ jsx19(Text19, { color: THEME.grayDim, children: cwd }),
17850
+ /* @__PURE__ */ jsxs17(Box17, { flexDirection: "row", borderStyle: "round", borderColor: THEME.coral, marginTop: 1, paddingX: 0, paddingY: 0, children: [
17851
+ /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", width: leftW, height: INNER_H, alignItems: "center", justifyContent: "center", children: [
17852
+ /* @__PURE__ */ jsx19(Text19, { color: THEME.white, bold: true, children: "Welcome back!" }),
17853
+ ROBOT.map((l, i) => /* @__PURE__ */ jsx19(Text19, { color: THEME.coralBright, children: l }, i)),
17854
+ /* @__PURE__ */ jsx19(Text19, { color: THEME.grayDim, children: "Ctrl+C \u9000\u51FA \xB7 Esc \u4E2D\u6B62" }),
17855
+ /* @__PURE__ */ jsx19(Text19, { color: THEME.grayDim, children: "\u8F93\u5165 / \u67E5\u770B\u547D\u4EE4" })
17270
17856
  ] }),
17271
17857
  VDivider,
17272
- /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", width: rightW, height: INNER_H, justifyContent: "space-between", paddingX: 1, children: [
17273
- /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", children: [
17274
- /* @__PURE__ */ jsx17(Text17, { color: THEME.coralBright, bold: true, children: "Tips for getting started" }),
17275
- /* @__PURE__ */ jsx17(Text17, { color: THEME.white, children: "\u8F93\u5165\u9700\u6C42\u5F00\u59CB\uFF0C\u8F93\u5165 / \u5524\u51FA\u547D\u4EE4\u3002" })
17858
+ /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", width: rightW, height: INNER_H, justifyContent: "space-between", paddingX: 1, children: [
17859
+ /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", children: [
17860
+ /* @__PURE__ */ jsx19(Text19, { color: THEME.coralBright, bold: true, children: "Tips for getting started" }),
17861
+ /* @__PURE__ */ jsx19(Text19, { color: THEME.white, children: "\u8F93\u5165\u9700\u6C42\u5F00\u59CB\uFF0C\u8F93\u5165 / \u5524\u51FA\u547D\u4EE4\u3002" })
17276
17862
  ] }),
17277
- /* @__PURE__ */ jsx17(Text17, { color: THEME.grayDim, children: "\u2500".repeat(ruleLen) }),
17278
- /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", children: [
17279
- /* @__PURE__ */ jsx17(Text17, { color: THEME.coralBright, bold: true, children: "What's new" }),
17280
- /* @__PURE__ */ jsx17(Text17, { color: THEME.white, children: "\u5DF2\u5BF9\u63A5\u672C\u5730 Tool Call \u667A\u80FD\u51FD\u6570\u8C03\u7528\u3002" })
17863
+ /* @__PURE__ */ jsx19(Text19, { color: THEME.grayDim, children: "\u2500".repeat(ruleLen) }),
17864
+ /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", children: [
17865
+ /* @__PURE__ */ jsx19(Text19, { color: THEME.coralBright, bold: true, children: "What's new" }),
17866
+ /* @__PURE__ */ jsx19(Text19, { color: THEME.white, children: "\u5DF2\u5BF9\u63A5\u672C\u5730 Tool Call \u667A\u80FD\u51FD\u6570\u8C03\u7528\u3002" })
17281
17867
  ] })
17282
17868
  ] })
17283
17869
  ] })
@@ -17287,7 +17873,7 @@ var init_TopPanel = __esm({
17287
17873
  });
17288
17874
 
17289
17875
  // ../core/src/observability/inspect.ts
17290
- import fs48 from "fs";
17876
+ import fs49 from "fs";
17291
17877
  var OBS, obsT, CWD, fmt, readTrace, fmtK, inspectUsage, inspectContext, inspectPermissions, inspectMcp, inspectHooks, inspectDebug;
17292
17878
  var init_inspect = __esm({
17293
17879
  "../core/src/observability/inspect.ts"() {
@@ -17410,7 +17996,7 @@ var init_inspect = __esm({
17410
17996
  if (!sessionId) return [];
17411
17997
  try {
17412
17998
  const p = await getTraceStorePath(sessionId);
17413
- const text = await fs48.promises.readFile(p, "utf8");
17999
+ const text = await fs49.promises.readFile(p, "utf8");
17414
18000
  const out = [];
17415
18001
  for (const line of text.split("\n")) {
17416
18002
  if (!line.trim()) continue;
@@ -17519,7 +18105,7 @@ var init_inspect = __esm({
17519
18105
  }
17520
18106
  let sidecarCount = 0;
17521
18107
  try {
17522
- sidecarCount = (await fs48.promises.readdir(getToolOutputSidecarDir(sessionId))).filter((f) => f.endsWith(".txt")).length;
18108
+ sidecarCount = (await fs49.promises.readdir(getToolOutputSidecarDir(sessionId))).filter((f) => f.endsWith(".txt")).length;
17523
18109
  } catch {
17524
18110
  }
17525
18111
  const window = appConfig.MAX_HISTORY_TOKENS;
@@ -17594,8 +18180,8 @@ __export(App_exports, {
17594
18180
  App: () => App
17595
18181
  });
17596
18182
  import { useCallback as useCallback2, useEffect as useEffect4, useMemo, useRef as useRef2, useState as useState4 } from "react";
17597
- import { Box as Box16, Static, Text as Text18, useApp, useInput as useInput2, useStdout } from "ink";
17598
- import { Fragment as Fragment3, jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
18183
+ import { Box as Box18, Static, Text as Text20, useApp, useInput as useInput2, useStdout } from "ink";
18184
+ import { Fragment as Fragment4, jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
17599
18185
  var CWD2, RowView, isDynamicRow, SPINNER_FRAMES, App;
17600
18186
  var init_App = __esm({
17601
18187
  "src/App.tsx"() {
@@ -17622,23 +18208,27 @@ var init_App = __esm({
17622
18208
  init_SessionPicker();
17623
18209
  init_ForkPicker();
17624
18210
  init_ModelPicker();
18211
+ init_RewindPicker();
18212
+ init_DiffSummaryCard();
17625
18213
  init_SlashMenu();
17626
18214
  init_MultilineInput();
17627
18215
  init_StatusStrip();
17628
18216
  init_TopPanel();
17629
18217
  init_inspect();
18218
+ init_restore();
17630
18219
  CWD2 = process.cwd();
17631
18220
  RowView = ({ row, wrapW, streamTail, showThinking }) => {
17632
- if (row.kind === "todos") return /* @__PURE__ */ jsx18(TodosPanel, { todos: row.todos, wrapW });
17633
- if (row.kind === "tool") return /* @__PURE__ */ jsx18(ToolCard, { toolName: row.toolName, args: row.args, result: row.result, ok: row.ok, status: row.status, progress: row.progress, wrapW });
17634
- if (row.kind === "thinking") return /* @__PURE__ */ jsx18(ThinkingBlock, { streaming: row.streaming ?? false, startedAt: row.startedAt, durationMs: row.durationMs, tokens: row.tokens, text: row.text, expanded: showThinking, wrapW });
18221
+ if (row.kind === "todos") return /* @__PURE__ */ jsx20(TodosPanel, { todos: row.todos, wrapW });
18222
+ if (row.kind === "tool") return /* @__PURE__ */ jsx20(ToolCard, { toolName: row.toolName, args: row.args, result: row.result, ok: row.ok, status: row.status, progress: row.progress, wrapW });
18223
+ if (row.kind === "diff-summary") return /* @__PURE__ */ jsx20(DiffSummaryCard, { title: row.title, files: row.files, wrapW });
18224
+ if (row.kind === "thinking") return /* @__PURE__ */ jsx20(ThinkingBlock, { streaming: row.streaming ?? false, startedAt: row.startedAt, durationMs: row.durationMs, tokens: row.tokens, text: row.text, expanded: showThinking, wrapW });
17635
18225
  if (row.kind === "user") {
17636
- return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", marginTop: 1, children: [
17637
- /* @__PURE__ */ jsx18(Text18, { color: THEME.grayDim, children: "\u2500".repeat(Math.max(8, wrapW)) }),
17638
- /* @__PURE__ */ jsx18(MessageBlock, { row, wrapW, streamTail })
18226
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", marginTop: 1, children: [
18227
+ /* @__PURE__ */ jsx20(Text20, { color: THEME.grayDim, children: "\u2500".repeat(Math.max(8, wrapW)) }),
18228
+ /* @__PURE__ */ jsx20(MessageBlock, { row, wrapW, streamTail })
17639
18229
  ] });
17640
18230
  }
17641
- return /* @__PURE__ */ jsx18(MessageBlock, { row, wrapW, streamTail });
18231
+ return /* @__PURE__ */ jsx20(MessageBlock, { row, wrapW, streamTail });
17642
18232
  };
17643
18233
  isDynamicRow = (r) => r.kind === "assistant" && !!r.streaming || r.kind === "thinking" && !!r.streaming || r.kind === "tool" && r.status === "running" || r.kind === "todos" && !!r.active || r.kind === "compact-progress" || r.kind === "subagent-progress";
17644
18234
  SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
@@ -17667,6 +18257,7 @@ var init_App = __esm({
17667
18257
  const [planEditing, setPlanEditing] = useState4(false);
17668
18258
  const [planDraft, setPlanDraft] = useState4("");
17669
18259
  const [planCursor, setPlanCursor] = useState4(0);
18260
+ const [approvalFull, setApprovalFull] = useState4(false);
17670
18261
  const [qCursor, setQCursor] = useState4(0);
17671
18262
  const [qChecked, setQChecked] = useState4(/* @__PURE__ */ new Set());
17672
18263
  const [qEditing, setQEditing] = useState4(false);
@@ -17778,8 +18369,14 @@ var init_App = __esm({
17778
18369
  return S.cmdSessions;
17779
18370
  case "fork":
17780
18371
  return S.cmdFork;
18372
+ case "undo":
18373
+ return S.cmdUndo;
17781
18374
  case "usage":
17782
18375
  return S.cmdUsage;
18376
+ case "rewind":
18377
+ return S.cmdRewind;
18378
+ case "diff":
18379
+ return S.cmdDiff;
17783
18380
  case "budget":
17784
18381
  return S.cmdBudget;
17785
18382
  case "context":
@@ -17812,7 +18409,7 @@ var init_App = __esm({
17812
18409
  const q = input.slice(1).toLowerCase();
17813
18410
  return menuEntries.filter((c) => c.name.toLowerCase().startsWith(q));
17814
18411
  }, [input, menuEntries]);
17815
- const menuActive = state.pendingApproval != null || state.pendingQuestion != null || state.pendingPlan != null || state.pendingSessions != null || state.pendingFork != null || state.pendingModel != null;
18412
+ const menuActive = state.pendingApproval != null || state.pendingQuestion != null || state.pendingPlan != null || state.pendingSessions != null || state.pendingFork != null || state.pendingModel != null || state.pendingRewind != null;
17816
18413
  const [busyClock, setBusyClock] = useState4({ frame: 0, sec: 0 });
17817
18414
  const busyStartRef = useRef2(0);
17818
18415
  useEffect4(() => {
@@ -17836,7 +18433,8 @@ var init_App = __esm({
17836
18433
  useEffect4(() => {
17837
18434
  setSelectIdx(0);
17838
18435
  setPlanEditing(false);
17839
- }, [state.pendingApproval, state.pendingQuestion, state.pendingPlan, state.pendingSessions, state.pendingFork, state.pendingModel, slashVisible, filteredCommands.length]);
18436
+ setApprovalFull(false);
18437
+ }, [state.pendingApproval, state.pendingQuestion, state.pendingPlan, state.pendingSessions, state.pendingFork, state.pendingModel, state.pendingRewind, slashVisible, filteredCommands.length]);
17840
18438
  useEffect4(() => {
17841
18439
  setQCursor(0);
17842
18440
  setQChecked(/* @__PURE__ */ new Set());
@@ -17874,6 +18472,21 @@ var init_App = __esm({
17874
18472
  case "/fork":
17875
18473
  void state.openForkPicker();
17876
18474
  return true;
18475
+ case "/undo": {
18476
+ const sid = state.sessionIdRef.current;
18477
+ if (!sid) {
18478
+ state.pushInfo(S.undoNoSession);
18479
+ return true;
18480
+ }
18481
+ state.pushInfo(await restoreLast(sid));
18482
+ return true;
18483
+ }
18484
+ case "/rewind":
18485
+ void state.openRewindPicker();
18486
+ return true;
18487
+ case "/diff":
18488
+ void state.openDiff();
18489
+ return true;
17877
18490
  case "/help":
17878
18491
  state.pushInfo(S.helpText(modelDisplay, state.getThinkingLevel(), getLocale()));
17879
18492
  return true;
@@ -18096,7 +18709,11 @@ ${listText}`);
18096
18709
  return;
18097
18710
  }
18098
18711
  if (state.pendingApproval) {
18099
- const APPROVAL_DECISIONS = ["allow-once", "allow-always", "deny"];
18712
+ const APPROVAL_DECISIONS = ["allow-once", "allow-session", "allow-always", "deny"];
18713
+ if (ch === "f" || ch === "F") {
18714
+ setApprovalFull((v) => !v);
18715
+ return;
18716
+ }
18100
18717
  if (key.upArrow) setSelectIdx((i) => (i - 1 + APPROVAL_DECISIONS.length) % APPROVAL_DECISIONS.length);
18101
18718
  else if (key.downArrow) setSelectIdx((i) => (i + 1) % APPROVAL_DECISIONS.length);
18102
18719
  else if (key.return) state.resolveApproval(APPROVAL_DECISIONS[selectIdxRef.current] ?? "deny");
@@ -18190,6 +18807,16 @@ ${listText}`);
18190
18807
  } else if (key.escape || key.ctrl && ch === "g") state.resolveModel(null);
18191
18808
  return;
18192
18809
  }
18810
+ if (state.pendingRewind) {
18811
+ const list = state.pendingRewind.records;
18812
+ if (key.upArrow) setSelectIdx((i) => (i - 1 + list.length) % list.length);
18813
+ else if (key.downArrow) setSelectIdx((i) => (i + 1) % list.length);
18814
+ else if (key.return) {
18815
+ const sel = list[Math.min(selectIdxRef.current, list.length - 1)];
18816
+ state.resolveRewind(sel?.undoId ?? null);
18817
+ } else if (key.escape || key.ctrl && ch === "g") state.resolveRewind(null);
18818
+ return;
18819
+ }
18193
18820
  if (key.ctrl && ch === "t") {
18194
18821
  state.toggleShowThinking();
18195
18822
  return;
@@ -18230,14 +18857,14 @@ ${listText}`);
18230
18857
  }
18231
18858
  });
18232
18859
  const sessionShort = state.sessionIdRef.current ? truncateMiddle(state.sessionIdRef.current, 12) : "";
18233
- return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", width: cols, children: [
18234
- /* @__PURE__ */ jsx18(Static, { items: staticItems, children: (item) => item.kind === "__header" ? /* @__PURE__ */ jsx18(TopPanel, { cwd: CWD2, cols }, "header") : /* @__PURE__ */ jsx18(RowView, { row: item, wrapW, streamTail, showThinking: state.showThinkingText }, item.id) }),
18235
- /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", width: cols, children: [
18236
- menuActive ? null : /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", paddingX: 1, children: [
18237
- dynamicRows.map((row) => /* @__PURE__ */ jsx18(RowView, { row, wrapW, streamTail, showThinking: state.showThinkingText }, row.id)),
18238
- state.busy ? /* @__PURE__ */ jsx18(Box16, { marginTop: 0.5, children: /* @__PURE__ */ jsxs16(Text18, { color: THEME.coralBright, children: [
18860
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", width: cols, children: [
18861
+ /* @__PURE__ */ jsx20(Static, { items: staticItems, children: (item) => item.kind === "__header" ? /* @__PURE__ */ jsx20(TopPanel, { cwd: CWD2, cols }, "header") : /* @__PURE__ */ jsx20(RowView, { row: item, wrapW, streamTail, showThinking: state.showThinkingText }, item.id) }),
18862
+ /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", width: cols, children: [
18863
+ menuActive ? null : /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", paddingX: 1, children: [
18864
+ dynamicRows.map((row) => /* @__PURE__ */ jsx20(RowView, { row, wrapW, streamTail, showThinking: state.showThinkingText }, row.id)),
18865
+ state.busy ? /* @__PURE__ */ jsx20(Box18, { marginTop: 0.5, children: /* @__PURE__ */ jsxs18(Text20, { color: THEME.coralBright, children: [
18239
18866
  `${SPINNER_FRAMES[busyClock.frame]} ${S.thinkVerbs[Math.floor(busyClock.sec / 3) % S.thinkVerbs.length]}\u2026 (${busyClock.sec}s \xB7 ${S.escInterrupt})`,
18240
- state.runningAgents > 0 ? /* @__PURE__ */ jsxs16(Text18, { color: "green", children: [
18867
+ state.runningAgents > 0 ? /* @__PURE__ */ jsxs18(Text20, { color: "green", children: [
18241
18868
  " \xB7 \u25CF ",
18242
18869
  state.runningAgents,
18243
18870
  " agent",
@@ -18245,9 +18872,9 @@ ${listText}`);
18245
18872
  ] }) : null
18246
18873
  ] }) }) : null
18247
18874
  ] }),
18248
- /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", paddingX: 1, flexShrink: 0, children: [
18249
- state.pendingApproval ? /* @__PURE__ */ jsx18(ApprovalModal, { toolName: state.pendingApproval.toolName, detail: state.pendingApproval.detail, selectedIndex: selectIdx, wrapW }) : null,
18250
- state.pendingQuestion ? /* @__PURE__ */ jsx18(
18875
+ /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", paddingX: 1, flexShrink: 0, children: [
18876
+ state.pendingApproval ? /* @__PURE__ */ jsx20(ApprovalModal, { toolName: state.pendingApproval.toolName, detail: state.pendingApproval.detail, fullDetail: state.pendingApproval.fullDetail, expanded: approvalFull, selectedIndex: selectIdx, wrapW }) : null,
18877
+ state.pendingQuestion ? /* @__PURE__ */ jsx20(
18251
18878
  QuestionModal,
18252
18879
  {
18253
18880
  question: state.pendingQuestion.req.question,
@@ -18266,7 +18893,7 @@ ${listText}`);
18266
18893
  onDraftSubmit: confirmQuestionDraft
18267
18894
  }
18268
18895
  ) : null,
18269
- state.pendingPlan ? planEditing ? /* @__PURE__ */ jsx18(
18896
+ state.pendingPlan ? planEditing ? /* @__PURE__ */ jsx20(
18270
18897
  PlanEditor,
18271
18898
  {
18272
18899
  value: planDraft,
@@ -18277,16 +18904,17 @@ ${listText}`);
18277
18904
  },
18278
18905
  onSubmit: confirmPlanEdit
18279
18906
  }
18280
- ) : /* @__PURE__ */ jsx18(PlanModal, { selectedIndex: selectIdx }) : null,
18281
- state.pendingSessions ? /* @__PURE__ */ jsx18(SessionPicker, { sessions: state.pendingSessions.sessions, selectedIndex: selectIdx, wrapW }) : null,
18282
- state.pendingFork ? /* @__PURE__ */ jsx18(ForkPicker, { anchors: state.pendingFork.anchors, selectedIndex: selectIdx, wrapW }) : null,
18283
- state.pendingModel ? /* @__PURE__ */ jsx18(ModelPicker, { models: state.pendingModel.models, currentModel: modelDisplay, selectedIndex: selectIdx, wrapW }) : null,
18284
- slashVisible ? /* @__PURE__ */ jsx18(SlashMenu, { entries: filteredCommands, selectedIndex: selectIdx, cols }) : null
18907
+ ) : /* @__PURE__ */ jsx20(PlanModal, { selectedIndex: selectIdx }) : null,
18908
+ state.pendingSessions ? /* @__PURE__ */ jsx20(SessionPicker, { sessions: state.pendingSessions.sessions, selectedIndex: selectIdx, wrapW }) : null,
18909
+ state.pendingFork ? /* @__PURE__ */ jsx20(ForkPicker, { anchors: state.pendingFork.anchors, selectedIndex: selectIdx, wrapW }) : null,
18910
+ state.pendingModel ? /* @__PURE__ */ jsx20(ModelPicker, { models: state.pendingModel.models, currentModel: modelDisplay, selectedIndex: selectIdx, wrapW }) : null,
18911
+ state.pendingRewind ? /* @__PURE__ */ jsx20(RewindPicker, { records: state.pendingRewind.records, selectedIndex: selectIdx, wrapW }) : null,
18912
+ slashVisible ? /* @__PURE__ */ jsx20(SlashMenu, { entries: filteredCommands, selectedIndex: selectIdx, cols }) : null
18285
18913
  ] }),
18286
- /* @__PURE__ */ jsx18(Box16, { flexDirection: "column", paddingX: 1, marginTop: 1, flexShrink: 0, children: (() => {
18914
+ /* @__PURE__ */ jsx20(Box18, { flexDirection: "column", paddingX: 1, marginTop: 1, flexShrink: 0, children: (() => {
18287
18915
  const planOn = state.getPlanMode();
18288
18916
  const autoOn = !planOn && state.getAutoMode();
18289
- const inputEl = /* @__PURE__ */ jsx18(
18917
+ const inputEl = /* @__PURE__ */ jsx20(
18290
18918
  MultilineInput,
18291
18919
  {
18292
18920
  value: input,
@@ -18304,17 +18932,17 @@ ${listText}`);
18304
18932
  }
18305
18933
  );
18306
18934
  if (planOn || autoOn) {
18307
- return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", borderStyle: "round", borderColor: planOn ? THEME.thinking : THEME.warn, paddingX: 1, children: [
18308
- /* @__PURE__ */ jsx18(Text18, { color: planOn ? THEME.thinking : THEME.warn, children: planOn ? S.inputBadgePlan : S.inputBadgeAuto }),
18935
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", borderStyle: "round", borderColor: planOn ? THEME.thinking : THEME.warn, paddingX: 1, children: [
18936
+ /* @__PURE__ */ jsx20(Text20, { color: planOn ? THEME.thinking : THEME.warn, children: planOn ? S.inputBadgePlan : S.inputBadgeAuto }),
18309
18937
  inputEl
18310
18938
  ] });
18311
18939
  }
18312
- return /* @__PURE__ */ jsxs16(Fragment3, { children: [
18313
- /* @__PURE__ */ jsx18(Text18, { dimColor: true, color: THEME.gray, children: dimRule(cols) }),
18940
+ return /* @__PURE__ */ jsxs18(Fragment4, { children: [
18941
+ /* @__PURE__ */ jsx20(Text20, { dimColor: true, color: THEME.gray, children: dimRule(cols) }),
18314
18942
  inputEl
18315
18943
  ] });
18316
18944
  })() }),
18317
- /* @__PURE__ */ jsx18(Box16, { paddingX: 1, marginTop: 1, flexShrink: 0, children: /* @__PURE__ */ jsx18(StatusStrip, { model: modelDisplay, busy: state.busy, aborting: state.aborting, planMode: state.getPlanMode(), autoMode: state.getAutoMode(), sessionShort, cols, customLine: statusLineText }) })
18945
+ /* @__PURE__ */ jsx20(Box18, { paddingX: 1, marginTop: 1, flexShrink: 0, children: /* @__PURE__ */ jsx20(StatusStrip, { model: modelDisplay, busy: state.busy, aborting: state.aborting, planMode: state.getPlanMode(), autoMode: state.getAutoMode(), sessionShort, cols, customLine: statusLineText }) })
18318
18946
  ] })
18319
18947
  ] });
18320
18948
  };
@@ -18327,7 +18955,7 @@ __export(batchRunner_exports, {
18327
18955
  parseWaitWindow: () => parseWaitWindow,
18328
18956
  runBatchQueue: () => runBatchQueue
18329
18957
  });
18330
- import fs49 from "fs";
18958
+ import fs50 from "fs";
18331
18959
  import { spawn as spawn6 } from "child_process";
18332
18960
  var BATCH_EDIT_TOOLS, BATCH_DEFAULT_DENYLIST, ACCEPT_TIMEOUT_MIN, ACCEPT_TAIL_LINES, emit2, ts, runAcceptCommand, collectSessionCost, buildReport, runOneTask, parseWaitWindow, nowMinutes, sleep, waitUntilWindow, runBatchQueue;
18333
18961
  var init_batchRunner = __esm({
@@ -18335,6 +18963,7 @@ var init_batchRunner = __esm({
18335
18963
  "use strict";
18336
18964
  init_chatProcessing();
18337
18965
  init_tool();
18966
+ init_autoPermission();
18338
18967
  init_manager();
18339
18968
  init_sessionRegistry();
18340
18969
  init_background();
@@ -18346,7 +18975,7 @@ var init_batchRunner = __esm({
18346
18975
  init_batchStrings();
18347
18976
  init_strings();
18348
18977
  BATCH_EDIT_TOOLS = /* @__PURE__ */ new Set(["edit_file", "create_file", "write_file", "move_file", "notebook_edit"]);
18349
- BATCH_DEFAULT_DENYLIST = /* @__PURE__ */ new Set(["run_command", "delete_path"]);
18978
+ BATCH_DEFAULT_DENYLIST = /* @__PURE__ */ new Set(["run_command", "delete_path", "run_in_background"]);
18350
18979
  ACCEPT_TIMEOUT_MIN = 10;
18351
18980
  ACCEPT_TAIL_LINES = 60;
18352
18981
  emit2 = (s = "") => {
@@ -18396,8 +19025,8 @@ ${lines.slice(-ACCEPT_TAIL_LINES).join("\n")}` : text;
18396
19025
  });
18397
19026
  };
18398
19027
  collectSessionCost = async (sessionId) => {
18399
- const recs = (await readUsageRecords(2)).filter((r) => r.sessionId === sessionId);
18400
- const c = { rounds: 0, mainRounds: 0, subRuns: 0, promptTokens: 0, completionTokens: 0, cachedTokens: 0, costYuan: null, toolCostYuan: 0, partialPrice: false, durationMs: 0 };
19028
+ const recs = (await readUsageRecords(2)).filter((r) => r.sessionId === sessionId || r.sessionId.startsWith(sessionId + "__sub__"));
19029
+ const c = { rounds: 0, mainRounds: 0, subRuns: 0, promptTokens: 0, completionTokens: 0, cachedTokens: 0, costYuan: null, toolCostYuan: 0, auxCostYuan: 0, partialPrice: false, durationMs: 0 };
18401
19030
  for (const r of recs) {
18402
19031
  c.rounds += r.rounds || 0;
18403
19032
  if (r.depth === 0) c.mainRounds += r.rounds || 0;
@@ -18407,6 +19036,7 @@ ${lines.slice(-ACCEPT_TAIL_LINES).join("\n")}` : text;
18407
19036
  c.cachedTokens += r.cachedTokens ?? 0;
18408
19037
  c.durationMs += r.durationMs || 0;
18409
19038
  c.toolCostYuan += r.toolCostYuan ?? 0;
19039
+ c.auxCostYuan += r.auxCostYuan ?? 0;
18410
19040
  if (typeof r.costYuan === "number") c.costYuan = (c.costYuan ?? 0) + r.costYuan;
18411
19041
  else c.partialPrice = true;
18412
19042
  }
@@ -18469,10 +19099,10 @@ ${diff}
18469
19099
  };
18470
19100
  runOneTask = async (task) => {
18471
19101
  const T = batchT(getLocale());
18472
- const source = await fs49.promises.readFile(task.path, "utf-8");
19102
+ const source = await fs50.promises.readFile(task.path, "utf-8");
18473
19103
  const parsed = parseBatchTicket(source, getLocale());
18474
19104
  if (!parsed.ok) {
18475
- const cost2 = { rounds: 0, mainRounds: 0, subRuns: 0, promptTokens: 0, completionTokens: 0, cachedTokens: 0, costYuan: 0, toolCostYuan: 0, partialPrice: false, durationMs: 0 };
19105
+ const cost2 = { rounds: 0, mainRounds: 0, subRuns: 0, promptTokens: 0, completionTokens: 0, cachedTokens: 0, costYuan: 0, toolCostYuan: 0, auxCostYuan: 0, partialPrice: false, durationMs: 0 };
18476
19106
  await finishTicket(task.id, `---
18477
19107
  id: ${task.id}
18478
19108
  title: ${task.id}
@@ -18491,10 +19121,31 @@ ${parsed.error}`);
18491
19121
  emit2(`\u25B6 [${task.id}] ${t.title}`);
18492
19122
  emit2(T.taskMeta(t.budgetYuan, timeoutMin, t.accept));
18493
19123
  const unlocked = new Set(t.tools ?? []);
18494
- const denySchemas = new Set([...BATCH_DEFAULT_DENYLIST].filter((n) => !unlocked.has(n)));
18495
- const toolSchemas = agentTools.filter((x) => !denySchemas.has(x?.function?.name));
19124
+ const toolSchemas = agentTools.filter((x) => unlocked.has(x?.function?.name) || x?.function?.batchAllowed !== false && !BATCH_DEFAULT_DENYLIST.has(x?.function?.name));
18496
19125
  const allowApproval = /* @__PURE__ */ new Set([...BATCH_EDIT_TOOLS, ...unlocked]);
18497
19126
  const deniedTools = [];
19127
+ const wrapInterpreterGate = (x) => {
19128
+ const name = x?.function?.name;
19129
+ if (name !== "run_command" && name !== "run_in_background") return x;
19130
+ const innerExecute = x.function.execute.bind(x.function);
19131
+ return {
19132
+ ...x,
19133
+ function: {
19134
+ ...x.function,
19135
+ execute: async (args, ctx) => {
19136
+ const cmd = typeof args?.command === "string" ? args.command : "";
19137
+ if (cmd && isInterpreterRepoScript(cmd) && !unlocked.has("interpreter")) {
19138
+ const why = `\u{1F6AB} [batch \u4F9B\u5E94\u94FE\u95F8\u95E8] \u89E3\u91CA\u5668\u76F4\u8DD1\u4ED3\u5E93\u5185\u811A\u672C\u5DF2\u62E6\u622A\uFF1A${cmd.slice(0, 120)}\u3002\u6267\u884C\u4ED3\u5E93\u4F5C\u8005\u63D0\u4EA4\u7684\u4EE3\u7801\u4E0E npm \u65CF\u540C\u6E90\u98CE\u9669\uFF0C\u6302\u673A\u573A\u666F\u9ED8\u8BA4\u62D2\u7EDD\uFF1B\u786E\u9700\u6267\u884C\u8BF7\u5728\u672C\u5DE5\u5355 tools: \u4E2D\u52A0 "interpreter" \u89E3\u9501\u3002`;
19139
+ deniedTools.push({ tool: name, detail: why.replace(/\n+/g, " ").slice(0, 140) });
19140
+ emit2(`\u{1F6AB} [${task.id}] \u62E6\u622A\u89E3\u91CA\u5668\u811A\u672C\uFF1A${cmd.slice(0, 80)}`);
19141
+ return why;
19142
+ }
19143
+ return innerExecute(args, ctx);
19144
+ }
19145
+ }
19146
+ };
19147
+ };
19148
+ const gatedSchemas = toolSchemas.map(wrapInterpreterGate);
18498
19149
  let wt = null;
18499
19150
  try {
18500
19151
  wt = await createWorktree(sessionId, task.id);
@@ -18576,7 +19227,7 @@ ${s.output}
18576
19227
  if (evt.type === "run.receipt") receipt = evt;
18577
19228
  },
18578
19229
  budgetYuan: t.budgetYuan,
18579
- toolSchemas
19230
+ toolSchemas: gatedSchemas
18580
19231
  }
18581
19232
  );
18582
19233
  } catch (e) {
@@ -18756,9 +19407,9 @@ init_updateCheck();
18756
19407
  init_trust();
18757
19408
  import { render } from "ink";
18758
19409
  import os5 from "os";
18759
- import path49 from "path";
19410
+ import path50 from "path";
18760
19411
  import { emitKeypressEvents } from "node:readline";
18761
- import { jsx as jsx19 } from "react/jsx-runtime";
19412
+ import { jsx as jsx21 } from "react/jsx-runtime";
18762
19413
  var parseArgs = (argv) => {
18763
19414
  const out = {};
18764
19415
  for (let i = 0; i < argv.length; i++) {
@@ -18905,7 +19556,7 @@ var main = async () => {
18905
19556
  process.stdout.write("\x1B[2J\x1B[3J\x1B[1;1H");
18906
19557
  const cwdIsHome = () => {
18907
19558
  try {
18908
- return path49.resolve(cwd).toLowerCase() === path49.resolve(os5.homedir()).toLowerCase();
19559
+ return path50.resolve(cwd).toLowerCase() === path50.resolve(os5.homedir()).toLowerCase();
18909
19560
  } catch {
18910
19561
  return false;
18911
19562
  }
@@ -18924,7 +19575,7 @@ var main = async () => {
18924
19575
  resumeId = await getMostRecentSessionId2() ?? void 0;
18925
19576
  }
18926
19577
  const { waitUntilExit } = render(
18927
- /* @__PURE__ */ jsx19(App2, { resumeSessionId: resumeId, initialPlanMode: args.plan, initialAutoMode: args.auto, initialIncludeProject: includeProject, engineReady }),
19578
+ /* @__PURE__ */ jsx21(App2, { resumeSessionId: resumeId, initialPlanMode: args.plan, initialAutoMode: args.auto, initialIncludeProject: includeProject, engineReady }),
18928
19579
  // exitOnCtrlC:false:Ctrl+C 交由 App useInput 处理(统一退出/中止语义);
18929
19580
  // patchConsole:false:避免 console 劫持与全屏重绘叠加闪屏。
18930
19581
  { exitOnCtrlC: false, patchConsole: false }