deepseeker-code 1.0.62 → 1.0.64

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 +1712 -348
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -624,7 +624,13 @@ var init_config = __esm({
624
624
  * 之前生效——门禁/审批评估的是改写后参数)、PostToolUse 可改 resultForModel(仅模型视图,
625
625
  * 用户视图 resultForUser 不变)。关闭时 PreToolUse 回退旧位(审批后、仅 deny),改写字段全忽略,
626
626
  * 行为与改造前一致。env DEEP_SEEK_HOOK_REWRITE=0 回退。 */
627
- hookRewrite: process.env.DEEP_SEEK_HOOK_REWRITE !== "0"
627
+ hookRewrite: process.env.DEEP_SEEK_HOOK_REWRITE !== "0",
628
+ /** ★ 外部记忆桥接开关(External Memory Bridge,2026-11-02):true 时 loader 在 bootstrap 把其他 agent 的
629
+ * 规则/指令文件(~/.claude/CLAUDE.md、~/.codex/AGENTS.md、~/.gemini/GEMINI.md、<cwd>/CLAUDE.md、AGENTS.md、
630
+ * GEMINI.md、.cursor/rules/*.md、.github/copilot-instructions.md,清单硬编码)以只读 ext- 前缀条目纳入记忆索引。
631
+ * 默认 false——关闭时记忆行为与未引入本功能逐字节一致。开启即意味着同意把这些文件内容随请求发给
632
+ * DeepSeek API(loader 首次接入时 console 提示一次)。外部条目只读:memory_save/delete 拒 ext- 前缀。 */
633
+ memoryExternal: false
628
634
  };
629
635
  MODEL_WINDOWS = {};
630
636
  resolveModelWindow = (model) => {
@@ -657,7 +663,9 @@ var init_config = __esm({
657
663
  undoBackupSensitive: (v) => v === "skip" || v === "deny" || v === "allow" ? v : void 0,
658
664
  undoRetentionDays: (v) => typeof v === "number" && v > 0 && Number.isFinite(v) ? v : void 0,
659
665
  traceRetentionDays: (v) => typeof v === "number" && v > 0 && Number.isFinite(v) ? v : void 0,
660
- MAX_TOOL_RESULT_CHARS: (v) => typeof v === "number" && v > 0 && Number.isFinite(v) ? Math.floor(v) : void 0
666
+ MAX_TOOL_RESULT_CHARS: (v) => typeof v === "number" && v > 0 && Number.isFinite(v) ? Math.floor(v) : void 0,
667
+ memoryExternal: (v) => typeof v === "boolean" ? v : void 0
668
+ // 外部记忆桥接(只读接入其他 agent 规则文件;默认 false)
661
669
  };
662
670
  ENGINE_OVERRIDE_KEYS = Object.keys(ENGINE_OVERRIDE_VALIDATORS);
663
671
  applyEngineOverrides = (cfg) => {
@@ -904,6 +912,7 @@ var init_ticket = __esm({
904
912
  noAccept: "frontmatter \u7F3A accept\uFF08\u9A8C\u6536\u547D\u4EE4\uFF09\u3002\u6302\u673A\u4EFB\u52A1\u5FC5\u987B\u5E26\u53EF\u6267\u884C\u9A8C\u6536\uFF0C\u7F3A\u9A8C\u6536\u62D2\u7EDD\u5165\u961F\u3002",
905
913
  badBudget: (v) => `budget-yuan \u975E\u6CD5\uFF08"${v}"\uFF09\uFF1A\u987B\u4E3A\u6B63\u6570\uFF08\u5143\uFF09\u3002`,
906
914
  badTimeout: (v) => `timeout-min \u975E\u6CD5\uFF08"${v}"\uFF09\uFF1A\u987B\u4E3A\u6B63\u6574\u6570\uFF08\u5206\u949F\uFF09\u3002`,
915
+ badRetry: (v) => `retry \u975E\u6CD5\uFF08"${v}"\uFF09\uFF1A\u987B\u4E3A 0-3 \u7684\u6574\u6570\uFF08\u770B\u95E8\u72D7\u7EC8\u6B62\u540E\u5E26\u8BC1\u636E\u91CD\u8BD5\u6B21\u6570\uFF1B\u7F3A\u7701 0=\u4E0D\u91CD\u8BD5\uFF09\u3002`,
907
916
  badTools: "tools \u4E3A\u7A7A\uFF1A\u8981\u4E48\u5220\u6389\u8BE5\u884C\uFF08\u7528\u7F3A\u7701\u7981\u7528\u6E05\u5355\uFF09\uFF0C\u8981\u4E48\u5217\u5DE5\u5177\u540D\uFF08\u9017\u53F7\u5206\u9694\uFF09\u3002",
908
917
  emptyBody: "\u4EFB\u52A1\u6B63\u6587\u4E3A\u7A7A\uFF1Afrontmatter \u4E4B\u540E\u5E94\u5199\u4EFB\u52A1\u63CF\u8FF0\u3002",
909
918
  untitled: "(\u672A\u547D\u540D\u4EFB\u52A1)",
@@ -914,6 +923,7 @@ var init_ticket = __esm({
914
923
  noAccept: "Frontmatter is missing `accept` (acceptance command). Batch tasks must carry an executable acceptance check \u2014 rejected without one.",
915
924
  badBudget: (v) => `Invalid budget-yuan ("${v}"): must be a positive number (yuan).`,
916
925
  badTimeout: (v) => `Invalid timeout-min ("${v}"): must be a positive integer (minutes).`,
926
+ badRetry: (v) => `Invalid retry ("${v}"): must be an integer 0-3 (evidence-carrying retries after a watchdog kill; default 0 = no retry).`,
917
927
  badTools: "Empty tools: either remove the line (use the default denylist) or list tool names (comma-separated).",
918
928
  emptyBody: "Empty task body: write the task description after the frontmatter.",
919
929
  untitled: "(untitled task)",
@@ -955,13 +965,19 @@ var init_ticket = __esm({
955
965
  if (n === null) return { ok: false, error: ticketMsg(locale2, "badTimeout")(fm["timeout-min"]) };
956
966
  timeoutMin = n;
957
967
  }
968
+ let retry;
969
+ if (fm["retry"] !== void 0) {
970
+ const n = Number(fm["retry"].trim());
971
+ if (!Number.isInteger(n) || n < 0 || n > 3) return { ok: false, error: ticketMsg(locale2, "badRetry")(fm["retry"]) };
972
+ retry = n;
973
+ }
958
974
  const tools = fm["tools"] !== void 0 ? parseToolsValue(fm["tools"]) : void 0;
959
975
  if (tools && tools.length === 0) return { ok: false, error: ticketMsg(locale2, "badTools") };
960
976
  const setup = (fm["setup"] ?? "").trim() || void 0;
961
977
  const body = bodyRaw.trim();
962
978
  if (!body) return { ok: false, error: ticketMsg(locale2, "emptyBody") };
963
979
  const title = (fm["title"] ?? "").trim() || body.split(/\r?\n/).find((l) => l.trim())?.slice(0, 40) || ticketMsg(locale2, "untitled");
964
- return { ok: true, ticket: { title, accept, setup, budgetYuan, timeoutMin, tools: tools && tools.length > 0 ? tools : void 0, body } };
980
+ return { ok: true, ticket: { title, accept, setup, budgetYuan, timeoutMin, retry, tools: tools && tools.length > 0 ? tools : void 0, body } };
965
981
  };
966
982
  readBatchTicket = async (filePath, locale2 = "zh") => {
967
983
  let source;
@@ -1117,6 +1133,7 @@ var init_batchStrings = __esm({
1117
1133
  setup: pnpm install # \u53EF\u9009\uFF1Aagent \u524D\u5728 worktree \u5185\u5907\u73AF\u5883\uFF08\u5E72\u51C0\u68C0\u51FA\u65E0 node_modules\uFF0C\u9A8C\u6536\u4F9D\u8D56\u73AF\u5883\u65F6\u5148\u88C5\uFF09
1118
1134
  budget-yuan: 0.5 # \u53EF\u9009\uFF1A\u672C\u4EFB\u52A1\u82B1\u8D39\u4E0A\u9650\uFF08\u5143\uFF09
1119
1135
  timeout-min: 30 # \u53EF\u9009\uFF1Aagent \u9636\u6BB5\u8D85\u65F6\uFF08\u5206\u949F\uFF0C\u7F3A\u7701 30\uFF09
1136
+ retry: 1 # \u53EF\u9009\uFF1A\u88AB\u770B\u95E8\u72D7\u7EC8\u6B62\u540E\u5E26\u8BC1\u636E\u91CD\u8BD5\u6B21\u6570\uFF080-3\uFF0C\u7F3A\u7701 0=\u6740\u5373\u7EC8\u6001\uFF09
1120
1137
  tools: run_command # \u53EF\u9009\uFF1A\u663E\u5F0F\u89E3\u9501\u9AD8\u5371\u5DE5\u5177\uFF08\u7F3A\u7701\u7981 run_command/delete_path\uFF09
1121
1138
  ---
1122
1139
  \u4EFB\u52A1\u63CF\u8FF0\u6B63\u6587\uFF08Markdown\uFF09\u2026
@@ -1143,6 +1160,10 @@ var init_batchStrings = __esm({
1143
1160
  wtCreateFail: (e) => `\u521B\u5EFA worktree \u5931\u8D25\uFF08\u9700\u8981\u4E3B\u5DE5\u4F5C\u533A\u662F git \u4ED3\u5E93\uFF09\uFF1A${e}`,
1144
1161
  agentErrorLine: (id, err) => `\u{1F4A5} [${id}] agent \u5F02\u5E38\uFF1A${err}`,
1145
1162
  timeoutLine: (id, min) => `\u23F1\uFE0F [${id}] \u8D85\u65F6\uFF08${min}min\uFF09\uFF0C\u622A\u65AD\u6536\u5C3E\uFF08\u9A8C\u6536\u4E0D\u8DD1\uFF09`,
1163
+ wdNudgeNote: (id, level) => `\u{1F415} [${id}] watchdog nudge-${level} \u5DF2\u6CE8\u5165\u6536\u4EF6\u7BB1\uFF08\u7EA0\u504F\u63D0\u793A\uFF09`,
1164
+ wdKillNote: (id, why) => `\u{1F415} [${id}] watchdog \u7EC8\u6B62\uFF1A${why}`,
1165
+ wdRetryNote: (id, round) => `\u{1F501} [${id}] watchdog \u5E26\u8BC1\u636E\u91CD\u8BD5\uFF1A\u7B2C ${round} \u8F6E\uFF08\u540C worktree \u7EED\u8DD1\uFF0C\u5269\u4F59\u9884\u7B97\u7EED\u7528\uFF09`,
1166
+ wdBudgetStopNote: (id) => `\u{1F415} [${id}] \u91CD\u8BD5\u5DF2\u8DF3\u8FC7\uFF1A\u9884\u7B97\u8DE8\u8F6E\u7D2F\u8BA1\u5DF2\u8017\u5C3D`,
1146
1167
  acceptLine: (passed, id, code) => `${passed ? "\u2705" : "\u274C"} [${id}] \u9A8C\u6536 exit ${code}`,
1147
1168
  acceptKilledSuffix: "\uFF08\u8D85\u65F6\u88AB\u6740\uFF09",
1148
1169
  timeoutAbort: (min) => `\u4EFB\u52A1\u8D85\u65F6\uFF08${min} \u5206\u949F\uFF09`,
@@ -1163,7 +1184,8 @@ var init_batchStrings = __esm({
1163
1184
  timeout: "\u23F1\uFE0F \u8D85\u65F6\u622A\u65AD",
1164
1185
  error: "\u{1F4A5} agent \u5F02\u5E38",
1165
1186
  "setup-failed": "\u{1F6E0}\uFE0F \u73AF\u5883\u51C6\u5907\u5931\u8D25",
1166
- rejected: "\u{1F6AB} \u5DE5\u5355\u88AB\u62D2"
1187
+ rejected: "\u{1F6AB} \u5DE5\u5355\u88AB\u62D2",
1188
+ "watchdog-killed": "\u{1F415} \u770B\u95E8\u72D7\u7EC8\u6B62"
1167
1189
  })[s],
1168
1190
  // —— 验收命令执行(输出嵌入报告) ——
1169
1191
  spawnFail: (e) => `\u542F\u52A8\u5931\u8D25\uFF1A${e}`,
@@ -1202,7 +1224,18 @@ var init_batchStrings = __esm({
1202
1224
  deniedNone: "\u65E0\u3002",
1203
1225
  diffH: "## Diff \u6458\u8981\uFF08worktree \u76F8\u5BF9\u57FA\u5EA7\uFF0C\u672A\u63D0\u4EA4\uFF0C\u5F85\u4EBA\u5DE5\u5904\u7F6E\uFF09",
1204
1226
  diffNote: (branch, p) => `\u6539\u52A8\u5728\u72EC\u7ACB worktree\uFF08\u5206\u652F \`${branch}\`\uFF0C\u8DEF\u5F84 ${p}\uFF09\u3002\u5904\u7F6E\uFF1A\u68C0\u67E5\u6EE1\u610F \u2192 \u5728 worktree \u5185 commit \u5E76\u5408\u56DE\u4E3B\u4ED3\uFF1B\u4E22\u5F03 \u2192 \u5220\u9664\u8BE5 worktree \u76EE\u5F55 + \`git worktree prune\` + \`git branch -D <\u5206\u652F>\`\u3002`,
1205
- diffNone: "\uFF08\u65E0\uFF09"
1227
+ diffNone: "\uFF08\u65E0\uFF09",
1228
+ // —— 看门狗(进展感知)报告段与进度行 ——
1229
+ wdH: "## \u770B\u95E8\u72D7\uFF08\u8FDB\u5C55\u611F\u77E5\uFF09",
1230
+ wdRecord: (s) => `- ${s}`,
1231
+ wdSuspicionRec: (s) => `\u5ACC\u7591\u8BB0\u5F55\uFF08\u672A\u8FBE\u52A8\u4F5C\u7EBF\uFF09\uFF1A${s}`,
1232
+ wdVerdictRec: (verdict, evidence) => `\u88C1\u51B3 ${verdict}\uFF1A${evidence || "\uFF08\u65E0\u8BC1\u636E\uFF09"}`,
1233
+ wdNudgeRec: (level, text) => `nudge-${level} \u5DF2\u6CE8\u5165\u6536\u4EF6\u7BB1\uFF1A${text}`,
1234
+ wdHealthRec: (s) => `\u5065\u5EB7\u4FE1\u53F7\u77ED\u8DEF\uFF08\u8DF3\u8FC7\u88C1\u51B3\uFF09\uFF1A${s}`,
1235
+ wdAuxFailRec: () => `\u4E8C\u5C42\u88C1\u51B3\u8C03\u7528\u5931\u8D25\uFF0C\u964D\u7EA7 normal\uFF08\u770B\u95E8\u72D7\u81EA\u8EAB\u6545\u969C\u4E0D\u8BEF\u6740\uFF09`,
1236
+ wdKillRec: (verdict, evidence) => `\u26D4 \u7EC8\u6B62\uFF1A${verdict} \u2014 ${evidence}`,
1237
+ wdAtKillRec: (round, costYuan) => `\u7EC8\u6B62\u65F6\u523B\uFF1A\u8F6E ${round} \xB7 \xA5${costYuan}`,
1238
+ wdRetryRec: (n) => `\u5E26\u8BC1\u636E\u91CD\u8BD5\uFF1A\u5171 ${n + 1} \u8F6E\uFF08\u540C worktree \u7EED\u8DD1\uFF0C\u8BC1\u636E\u89C1\u5404\u8F6E\u8BB0\u5F55\uFF09`
1206
1239
  };
1207
1240
  EN = {
1208
1241
  usage: `deepseeker-code batch \u2014 unattended batch runs (cost trust chain)
@@ -1220,6 +1253,7 @@ Ticket format (Markdown; frontmatter \`accept\` is required \u2014 no acceptance
1220
1253
  setup: pnpm install # optional: prep env inside the worktree before the agent (fresh checkout has no node_modules)
1221
1254
  budget-yuan: 0.5 # optional: per-task cost cap (yuan)
1222
1255
  timeout-min: 30 # optional: agent-phase timeout (minutes, default 30)
1256
+ retry: 1 # optional: evidence-carrying retries after a watchdog kill (0-3, default 0 = kill is final)
1223
1257
  tools: run_command # optional: unlock risky tools (run_command/delete_path denied by default)
1224
1258
  ---
1225
1259
 
@@ -1246,6 +1280,10 @@ changes are harvested onto an isolated git worktree (temp branch), kept for manu
1246
1280
  wtCreateFail: (e) => `Failed to create worktree (the main workspace must be a git repo): ${e}`,
1247
1281
  agentErrorLine: (id, err) => `\u{1F4A5} [${id}] agent error: ${err}`,
1248
1282
  timeoutLine: (id, min) => `\u23F1\uFE0F [${id}] Timed out (${min}min); wrapped up truncated (acceptance skipped)`,
1283
+ wdNudgeNote: (id, level) => `\u{1F415} [${id}] watchdog nudge-${level} injected into the inbox (course correction)`,
1284
+ wdKillNote: (id, why) => `\u{1F415} [${id}] watchdog killed: ${why}`,
1285
+ wdRetryNote: (id, round) => `\u{1F501} [${id}] watchdog evidence-carrying retry: round ${round} (same worktree, remaining budget carried over)`,
1286
+ wdBudgetStopNote: (id) => `\u{1F415} [${id}] retry skipped: cumulative budget exhausted across rounds`,
1249
1287
  acceptLine: (passed, id, code) => `${passed ? "\u2705" : "\u274C"} [${id}] acceptance exit ${code}`,
1250
1288
  acceptKilledSuffix: " (killed on timeout)",
1251
1289
  timeoutAbort: (min) => `Task timed out (${min} minutes)`,
@@ -1265,7 +1303,8 @@ changes are harvested onto an isolated git worktree (temp branch), kept for manu
1265
1303
  timeout: "\u23F1\uFE0F timed out",
1266
1304
  error: "\u{1F4A5} agent error",
1267
1305
  "setup-failed": "\u{1F6E0}\uFE0F setup failed",
1268
- rejected: "\u{1F6AB} ticket rejected"
1306
+ rejected: "\u{1F6AB} ticket rejected",
1307
+ "watchdog-killed": "\u{1F415} watchdog killed"
1269
1308
  })[s],
1270
1309
  spawnFail: (e) => `Spawn failed: ${e}`,
1271
1310
  execFail: (e) => `Execution failed: ${e}`,
@@ -1301,7 +1340,17 @@ changes are harvested onto an isolated git worktree (temp branch), kept for manu
1301
1340
  deniedNone: "None.",
1302
1341
  diffH: "## Diff summary (worktree vs base, uncommitted, awaiting manual handling)",
1303
1342
  diffNote: (branch, p) => `Changes live in an isolated worktree (branch \`${branch}\`, path ${p}). To keep: commit inside the worktree and merge back. To discard: delete the worktree directory + \`git worktree prune\` + \`git branch -D <branch>\`.`,
1304
- diffNone: "(none)"
1343
+ diffNone: "(none)",
1344
+ wdH: "## Watchdog (progress-aware)",
1345
+ wdRecord: (s) => `- ${s}`,
1346
+ wdSuspicionRec: (s) => `Suspicion logged (below action line): ${s}`,
1347
+ wdVerdictRec: (verdict, evidence) => `Verdict ${verdict}: ${evidence || "(no evidence)"}`,
1348
+ wdNudgeRec: (level, text) => `nudge-${level} injected into the inbox: ${text}`,
1349
+ wdHealthRec: (s) => `Health signal short-circuit (adjudication skipped): ${s}`,
1350
+ wdAuxFailRec: () => `Level-2 adjudication call failed; degraded to normal (a broken watchdog must not become a false-kill weapon)`,
1351
+ wdKillRec: (verdict, evidence) => `\u26D4 Killed: ${verdict} \u2014 ${evidence}`,
1352
+ wdAtKillRec: (round, costYuan) => `At kill time: round ${round} \xB7 \xA5${costYuan}`,
1353
+ wdRetryRec: (n) => `Evidence-carrying retries: ${n + 1} rounds total (same worktree; per-round evidence above)`
1305
1354
  };
1306
1355
  DICT = { zh: ZH, en: EN };
1307
1356
  batchT = (locale2) => DICT[locale2] ?? ZH;
@@ -1482,27 +1531,29 @@ var init_client = __esm({
1482
1531
  });
1483
1532
 
1484
1533
  // ../core/src/llm/providers/deepseek/stream.ts
1485
- var STREAM_IDLE_TIMEOUT_MS, stripHistoricalReasoning, streamChat;
1534
+ var STREAM_IDLE_TIMEOUT_MS, isEmptyAssistantMsg, stripHistoricalReasoning, streamChat;
1486
1535
  var init_stream = __esm({
1487
1536
  "../core/src/llm/providers/deepseek/stream.ts"() {
1488
1537
  "use strict";
1489
1538
  init_client();
1490
1539
  STREAM_IDLE_TIMEOUT_MS = Number(process.env.DEEP_SEEK_STREAM_IDLE_TIMEOUT_MS) || 12e4;
1540
+ isEmptyAssistantMsg = (m) => m?.role === "assistant" && !(typeof m.content === "string" && m.content.length > 0) && !(Array.isArray(m.tool_calls) && m.tool_calls.length > 0);
1491
1541
  stripHistoricalReasoning = (messages) => {
1492
- if (process.env.DEEP_SEEK_REASONING_PASSTHROUGH === "1") return messages;
1542
+ const wire = messages.some(isEmptyAssistantMsg) ? messages.filter((m) => !isEmptyAssistantMsg(m)) : messages;
1543
+ if (process.env.DEEP_SEEK_REASONING_PASSTHROUGH === "1") return wire;
1493
1544
  let lastNonSystem = null;
1494
- for (let i = messages.length - 1; i >= 0; i--) {
1495
- if (messages[i]?.role !== "system") {
1496
- lastNonSystem = messages[i];
1545
+ for (let i = wire.length - 1; i >= 0; i--) {
1546
+ if (wire[i]?.role !== "system") {
1547
+ lastNonSystem = wire[i];
1497
1548
  break;
1498
1549
  }
1499
1550
  }
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;
1502
- return messages.map((m) => {
1503
- const wire = m;
1504
- if (wire?.role !== "assistant" || typeof wire.reasoning_content !== "string") return m;
1505
- const copy = { ...wire };
1551
+ const hasSplitToolTurn = wire.some((m, i) => i > 0 && m?.role === "assistant" && wire[i - 1]?.role === "assistant" && Array.isArray(m.tool_calls) && m.tool_calls.length > 0);
1552
+ if (lastNonSystem?.role === "assistant" || hasSplitToolTurn) return wire;
1553
+ return wire.map((m) => {
1554
+ const w = m;
1555
+ if (w?.role !== "assistant" || typeof w.reasoning_content !== "string") return m;
1556
+ const copy = { ...w };
1506
1557
  delete copy.reasoning_content;
1507
1558
  return copy;
1508
1559
  });
@@ -1975,8 +2026,19 @@ var init_replay = __esm({
1975
2026
  });
1976
2027
 
1977
2028
  // ../core/src/llm/model.ts
2029
+ var model_exports = {};
2030
+ __export(model_exports, {
2031
+ activeProvider: () => activeProvider,
2032
+ chatWithModelWithSummary: () => chatWithModelWithSummary,
2033
+ classifyToolRisk: () => classifyToolRisk,
2034
+ isContextLengthError: () => isContextLengthError2,
2035
+ isImageUnsupportedError: () => isImageUnsupportedError2,
2036
+ isTransientApiError: () => isTransientApiError,
2037
+ resetActiveProvider: () => resetActiveProvider,
2038
+ setActiveProvider: () => setActiveProvider
2039
+ });
1978
2040
  import { readFileSync as readFileSync4 } from "fs";
1979
- var resolveInitialProvider, activeProvider, chatWithModelWithSummary, classifyToolRisk;
2041
+ var resolveInitialProvider, activeProvider, setActiveProvider, resetActiveProvider, chatWithModelWithSummary, classifyToolRisk, isContextLengthError2, isTransientApiError, isImageUnsupportedError2;
1980
2042
  var init_model = __esm({
1981
2043
  "../core/src/llm/model.ts"() {
1982
2044
  "use strict";
@@ -1997,8 +2059,17 @@ var init_model = __esm({
1997
2059
  return createReplayProvider(Array.isArray(raw) ? { turns: raw } : raw);
1998
2060
  };
1999
2061
  activeProvider = resolveInitialProvider();
2062
+ setActiveProvider = (p) => {
2063
+ activeProvider = p;
2064
+ };
2065
+ resetActiveProvider = () => {
2066
+ activeProvider = deepseekProvider;
2067
+ };
2000
2068
  chatWithModelWithSummary = (messages, tools, opts) => activeProvider.summarize(messages, tools, opts);
2001
2069
  classifyToolRisk = (toolName, args, detail, signal, onAuxUsage) => activeProvider.classifyRisk(toolName, args, detail, signal, onAuxUsage);
2070
+ isContextLengthError2 = (e) => activeProvider.isContextLengthError(e);
2071
+ isTransientApiError = (e) => activeProvider.isTransientError(e);
2072
+ isImageUnsupportedError2 = (e) => activeProvider.isImageUnsupportedError(e);
2002
2073
  }
2003
2074
  });
2004
2075
 
@@ -2532,13 +2603,17 @@ var init_transcript = __esm({
2532
2603
  console.warn(`\u26A0\uFE0F [transcript] \u6D88\u606F\u542B ${fffdCount} \u4E2A\u4E71\u7801\u5B57\u7B26\uFF08U+FFFD\uFF09\uFF0C\u5DF2\u539F\u6837\u5199\u5165\u5386\u53F2\u3002\u6765\u6E90\u53EF\u80FD\u662F GBK \u5B50\u8FDB\u7A0B\u8F93\u51FA\u6216\u4E71\u7801\u7C98\u8D34\u3002`);
2533
2604
  }
2534
2605
  await appendLineWithRetry(p, payload);
2606
+ return true;
2535
2607
  } catch (e) {
2536
2608
  console.warn(`\u26A0\uFE0F \u6D88\u606F\u843D\u76D8\u5931\u8D25\uFF08\u5DF2\u91CD\u8BD5 3 \u6B21\uFF0C\u5185\u5B58\u4E0E\u78C1\u76D8\u8F6C\u5F55\u53EF\u80FD\u4E0D\u4E00\u81F4\uFF09:`, e);
2609
+ return false;
2537
2610
  }
2538
2611
  };
2539
2612
  appendMessages = async (sessionId, entries) => {
2540
- if (entries.length === 0) return;
2541
- if (entries.length === 1) return appendMessage(entries[0]);
2613
+ if (entries.length === 0) return { written: 0, failed: 0 };
2614
+ if (entries.length === 1) {
2615
+ return await appendMessage(entries[0]) ? { written: 1, failed: 0 } : { written: 0, failed: 1 };
2616
+ }
2542
2617
  try {
2543
2618
  await ensureSessionsDir(sessionId);
2544
2619
  const p = getTranscriptPath(sessionId);
@@ -2551,28 +2626,35 @@ var init_transcript = __esm({
2551
2626
  if (fffdTotal > 0) console.warn(`[transcript] \u6279\u91CF\u6D88\u606F\u542B ${fffdTotal} \u4E2A\u4E71\u7801\u5B57\u7B26\uFF08U+FFFD\uFF09\uFF0C\u5DF2\u539F\u6837\u5199\u5165\u5386\u53F2\u3002\u6765\u6E90\u53EF\u80FD\u662F GBK \u5B50\u8FDB\u7A0B\u8F93\u51FA\u6216\u4E71\u7801\u7C98\u8D34\u3002`);
2552
2627
  let handle = null;
2553
2628
  let i = 0;
2629
+ let failed = 0;
2554
2630
  try {
2555
2631
  handle = await fs9.open(p, "a");
2556
2632
  for (; i < payloads.length; i++) await appendLineWithRetry(p, payloads[i], handle);
2557
2633
  } catch (e) {
2558
2634
  console.warn(`[transcript] \u6279\u91CF\u843D\u76D8\u7B2C ${i + 1}/${payloads.length} \u6761\u5931\u8D25\uFF0C\u8BE5\u6761\u8D77\u56DE\u843D\u9010\u6761\u65E7\u8DEF\u5F84:`, e instanceof Error ? e.message : e);
2559
- for (; i < entries.length; i++) await appendMessage(entries[i]);
2635
+ for (; i < entries.length; i++) {
2636
+ if (!await appendMessage(entries[i])) failed++;
2637
+ }
2560
2638
  } finally {
2561
2639
  if (handle) await handle.close().catch(() => {
2562
2640
  });
2563
2641
  }
2642
+ return { written: entries.length - failed, failed };
2564
2643
  } catch (e) {
2565
2644
  console.warn(`\u6279\u91CF\u6D88\u606F\u843D\u76D8\u5931\u8D25\uFF08\u5185\u5B58\u4E0E\u78C1\u76D8\u8F6C\u5F55\u53EF\u80FD\u4E0D\u4E00\u81F4\uFF09:`, e);
2645
+ return { written: 0, failed: entries.length };
2566
2646
  }
2567
2647
  };
2568
2648
  appendEvent = async (sessionId, ev) => {
2569
- if (!appConfig.transcriptEvents) return;
2649
+ if (!appConfig.transcriptEvents) return true;
2570
2650
  try {
2571
2651
  await ensureSessionsDir(sessionId);
2572
2652
  const line = { id: createUUID(), ts: (/* @__PURE__ */ new Date()).toISOString(), ...ev };
2573
2653
  await appendLineWithRetry(getTranscriptPath(sessionId), JSON.stringify(line) + "\n");
2654
+ return true;
2574
2655
  } catch (e) {
2575
2656
  console.warn(`\u26A0\uFE0F [transcript] \u4E8B\u4EF6\u884C\u843D\u76D8\u5931\u8D25\uFF08${ev.dscEvent}\uFF0C\u6062\u590D/\u5206\u53C9\u5C06\u56DE\u9000\u542F\u53D1\u5F0F\uFF09:`, e);
2657
+ return false;
2576
2658
  }
2577
2659
  };
2578
2660
  }
@@ -2598,8 +2680,8 @@ var init_recovery = __esm({
2598
2680
  if (!sawEvents || !lastStart) return { kind: "legacy" };
2599
2681
  return closed.has(lastStart.runId) ? { kind: "clean" } : { kind: "crashed", runId: lastStart.runId };
2600
2682
  };
2601
- repairOrphanToolCalls = (msgs, confirmedCrash) => {
2602
- const placeholder = confirmedCrash ? "\uFF08\u4E0A\u6B21\u4F1A\u8BDD\u5728\u5DE5\u5177\u6267\u884C\u4E2D\u5D29\u6E83\uFF08run \u672A\u95ED\u5408\uFF09\uFF0C\u8BE5\u8C03\u7528\u672A\u6267\u884C\uFF0C\u5DF2\u8DF3\u8FC7\u3002\uFF09" : "\uFF08\u8BE5\u5DE5\u5177\u8C03\u7528\u56E0\u4E0A\u6B21\u4F1A\u8BDD\u5F02\u5E38\u4E2D\u65AD\u672A\u7559\u4E0B\u7ED3\u679C\uFF0C\u5DF2\u8DF3\u8FC7\u3002\uFF09";
2683
+ repairOrphanToolCalls = (msgs, confirmedUnclosed) => {
2684
+ const placeholder = confirmedUnclosed ? "\uFF08\u4E0A\u6B21\u4F1A\u8BDD\u7684\u8FD9\u6B21\u5DE5\u5177\u8C03\u7528\u672A\u5B8C\u6210\uFF08run \u672A\u95ED\u5408\uFF09\uFF0C\u5DF2\u8DF3\u8FC7\u3002\uFF09" : "\uFF08\u8BE5\u5DE5\u5177\u8C03\u7528\u56E0\u4E0A\u6B21\u4F1A\u8BDD\u5F02\u5E38\u4E2D\u65AD\u672A\u7559\u4E0B\u7ED3\u679C\uFF0C\u5DF2\u8DF3\u8FC7\u3002\uFF09";
2603
2685
  const out = [];
2604
2686
  const repairedToolCallIds = [];
2605
2687
  for (let i = 0; i < msgs.length; i++) {
@@ -2620,7 +2702,7 @@ var init_recovery = __esm({
2620
2702
  }
2621
2703
  }
2622
2704
  }
2623
- return { msgs: out, repairedToolCallIds, confirmedCrash };
2705
+ return { msgs: out, repairedToolCallIds, confirmedUnclosed };
2624
2706
  };
2625
2707
  reconcileCompaction = (state, lines) => {
2626
2708
  let last;
@@ -2644,9 +2726,9 @@ var init_recovery = __esm({
2644
2726
  await appendEvent(sessionId, {
2645
2727
  dscEvent: "run.abandoned",
2646
2728
  runId: interruption.runId,
2647
- reason: "run.end missing: interrupted by process exit/kill"
2729
+ reason: "unclosed (cause unknown)"
2648
2730
  });
2649
- console.warn(`\u27E6recovery\u27E7 \u68C0\u6D4B\u5230\u672A\u95ED\u5408 run\uFF08${interruption.runId.slice(0, 8)}\u2026\uFF09\u2192 \u5DF2\u8865 run.abandoned \u95ED\u5893\u6807\u8BB0\uFF1B\u5B64\u513F\u4FEE\u590D\u6309\u300C\u5D29\u6E83\u786E\u8BA4\u300D\u6863\u6267\u884C\u3002`);
2731
+ console.warn(`\u27E6recovery\u27E7 \u68C0\u6D4B\u5230\u672A\u95ED\u5408 run\uFF08${interruption.runId.slice(0, 8)}\u2026\uFF09\u2192 \u5DF2\u8865 run.abandoned \u95ED\u5893\u6807\u8BB0\uFF1B\u5B64\u513F\u4FEE\u590D\u6309\u300C\u672A\u95ED\u5408\u300D\u6863\u6267\u884C\u3002`);
2650
2732
  }
2651
2733
  const state = precomputed?.state ?? await getRollingState(sessionId);
2652
2734
  const compaction = reconcileCompaction(
@@ -3476,19 +3558,63 @@ ${lines}
3476
3558
  const hitRate = event.lastRealPromptTokens && event.lastCachedTokens != null && event.lastRealPromptTokens > 0 ? event.lastCachedTokens / event.lastRealPromptTokens : null;
3477
3559
  const cacheFactor = hitRate == null ? 1 : Math.min(1.1, Math.max(0.9, 0.9 + hitRate * 0.2));
3478
3560
  const effectiveRatio = Math.min(event.compactRatio * cacheFactor, 0.82);
3479
- if (!event.forceCompact && estReal(event.messageArr) <= event.modelWindow * effectiveRatio) return;
3561
+ const thresholdAtCheck = Math.round(event.modelWindow * effectiveRatio);
3562
+ const reportCheck = (decision, decayed2) => {
3563
+ event.onWindowCheck?.({
3564
+ estReal: Math.round(estReal(event.messageArr)),
3565
+ threshold: thresholdAtCheck,
3566
+ correctionRatio,
3567
+ toolsTokens,
3568
+ hitRate,
3569
+ cacheFactor,
3570
+ decision,
3571
+ ...decayed2 === void 0 ? {} : { decayed: decayed2 }
3572
+ });
3573
+ };
3574
+ const estRealAtTrigger = Math.round(estReal(event.messageArr));
3575
+ const emitCheckEvent = (decision) => {
3576
+ event.events({
3577
+ sessionId: event.sessionId,
3578
+ eventType: "context.check",
3579
+ metadata: {
3580
+ depth: event.depth,
3581
+ decisionSource: "summary",
3582
+ ok: true,
3583
+ estReal: estRealAtTrigger,
3584
+ thresholdAtSend: thresholdAtCheck,
3585
+ correctionRatioUsed: correctionRatio,
3586
+ hitRateAtCheck: hitRate,
3587
+ cacheFactorAtCheck: cacheFactor,
3588
+ windowDecision: decision
3589
+ }
3590
+ });
3591
+ };
3592
+ if (!event.forceCompact && estReal(event.messageArr) <= event.modelWindow * effectiveRatio) {
3593
+ reportCheck("noop");
3594
+ return;
3595
+ }
3480
3596
  const preDecaySize = estReal(event.messageArr);
3481
3597
  const decayedArr = decayOldToolResults(event.messageArr);
3482
- if (decayedArr !== event.messageArr) {
3598
+ const decayed = decayedArr !== event.messageArr;
3599
+ if (decayed) {
3483
3600
  event.messageArr.length = 0;
3484
3601
  event.messageArr.push(...decayedArr);
3485
- }
3486
- if (!event.forceCompact && estReal(event.messageArr) <= event.modelWindow * effectiveRatio) {
3487
- console.log(`\u{1F4C9} [\u7A97\u53E3\u6CBB\u7406] \u514D\u8D39\u8870\u51CF\uFF08\u65E7\u5DE5\u5177\u7ED3\u679C\u6298\u53E0\uFF09\u540E\u56DE\u5230\u9608\u503C\u5185\uFF08${Math.round(preDecaySize)} \u2192 ${Math.round(estReal(event.messageArr))} token\uFF09\uFF0C\u8DF3\u8FC7 LLM \u538B\u7F29`);
3488
- return;
3602
+ event.events({
3603
+ sessionId: event.sessionId,
3604
+ eventType: "context.decay",
3605
+ metadata: {
3606
+ depth: event.depth,
3607
+ decisionSource: "summary",
3608
+ ok: true,
3609
+ decayTokensBefore: Math.round(preDecaySize),
3610
+ decayTokensAfter: Math.round(estReal(event.messageArr))
3611
+ }
3612
+ });
3489
3613
  }
3490
3614
  const tokensThreshold = Math.round(event.modelWindow * effectiveRatio);
3615
+ let preCompactSent = false;
3491
3616
  await dispatch("PreCompact", { sessionId: event.sessionId, depth: event.depth, tokensBefore: Math.round(estReal(event.messageArr)), tokensThreshold });
3617
+ preCompactSent = true;
3492
3618
  const systemMsg = event.messageArr[0];
3493
3619
  const summaryMsg = event.messageArr[1];
3494
3620
  let keep = event.keepRecentUnits;
@@ -3496,102 +3622,113 @@ ${lines}
3496
3622
  const preCompactSize = Math.round(lastSize);
3497
3623
  const startTime = performance.now();
3498
3624
  let round = 0;
3499
- let forcedPass = event.forceCompact === true;
3500
- while (forcedPass || lastSize > event.modelWindow * effectiveRatio) {
3501
- forcedPass = false;
3502
- if (event.signal?.aborted) {
3503
- return;
3504
- }
3505
- ;
3506
- const active = event.messageArr.slice(2);
3507
- const { toCompact, keepRecent } = splitUntils(active, keep);
3508
- try {
3509
- if (toCompact.length > 0) {
3510
- summaryMsg.content = mergeSummarySlot(summaryMsg?.content || "", "", extractArchiveEntities(toCompact));
3511
- const batches = splitIntoBatches(toCompact);
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);
3513
- if (estimateTokens([summaryMsg]) > SUMMARY_SELF_COMPACT_THRESHOLD) {
3625
+ let compactDecision = "noop";
3626
+ let forcedPass = event.forceCompact === true || decayed;
3627
+ try {
3628
+ while (forcedPass || lastSize > event.modelWindow * effectiveRatio) {
3629
+ forcedPass = false;
3630
+ if (event.signal?.aborted) {
3631
+ return;
3632
+ }
3633
+ ;
3634
+ const active = event.messageArr.slice(2);
3635
+ const { toCompact, keepRecent } = splitUntils(active, keep);
3636
+ try {
3637
+ if (toCompact.length > 0) {
3638
+ summaryMsg.content = mergeSummarySlot(summaryMsg?.content || "", "", extractArchiveEntities(toCompact));
3639
+ const batches = splitIntoBatches(toCompact);
3640
+ 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);
3641
+ const slotNotes = parseSummarySlot(typeof summaryMsg?.content === "string" ? summaryMsg.content : "").notes;
3642
+ if (estimateTokens([{ role: summaryMsg?.role ?? "system", content: slotNotes }]) > SUMMARY_SELF_COMPACT_THRESHOLD) {
3643
+ summaryMsg.content = await compactSlotNarrative(summaryMsg.content, event.modelWindow, event.signal, event.onAuxUsage);
3644
+ }
3645
+ const endTime = performance.now();
3646
+ event.messageArr.length = 0;
3647
+ event.messageArr.push(systemMsg, summaryMsg, ...keepRecent);
3648
+ event.events({
3649
+ sessionId: event.sessionId,
3650
+ eventType: "session.summary",
3651
+ metadata: {
3652
+ depth: event.depth,
3653
+ decisionSource: "summary",
3654
+ ok: true,
3655
+ durationMs: endTime - startTime,
3656
+ round: round++
3657
+ },
3658
+ usage: {
3659
+ prompt_tokens: lastSize,
3660
+ compress_tokens: estReal(event.messageArr)
3661
+ }
3662
+ });
3663
+ const store = await getRollingState(event.sessionId);
3664
+ store.archivedMessageCount = (store.archivedMessageCount || 0) + toCompact.length;
3665
+ await persistCompaction(event.sessionId, store.archivedMessageCount, summaryMsg.content);
3666
+ compactDecision = "compact";
3667
+ } else if (keep > 1) {
3668
+ keep--;
3669
+ continue;
3670
+ } else if (summaryMsg?.content) {
3514
3671
  summaryMsg.content = await compactSlotNarrative(summaryMsg.content, event.modelWindow, event.signal, event.onAuxUsage);
3672
+ compactDecision = "selfCompact";
3673
+ event.messageArr.length = 0;
3674
+ event.messageArr.push(systemMsg, summaryMsg, ...keepRecent);
3675
+ const store = await getRollingState(event.sessionId);
3676
+ await persistCompaction(event.sessionId, store.archivedMessageCount, summaryMsg.content);
3677
+ } else {
3678
+ break;
3515
3679
  }
3680
+ } catch (error) {
3681
+ if (event.signal?.aborted) return;
3682
+ const err = error instanceof Error ? error : new Error(String(error));
3683
+ console.warn("\u26A0\uFE0F \u672C\u8F6E\u538B\u7F29\u5931\u8D25\uFF0C\u8DF3\u8FC7:", err.message);
3684
+ const store = await getRollingState(event.sessionId);
3685
+ const nextFailures = (store.consecutiveFailures || 0) + 1;
3686
+ await setRollingState(event.sessionId, {
3687
+ archivedMessageCount: store.archivedMessageCount || 0,
3688
+ rollingSummary: summaryMsg?.content || "",
3689
+ consecutiveFailures: nextFailures
3690
+ // 👈 同步落盘
3691
+ });
3516
3692
  const endTime = performance.now();
3517
- event.messageArr.length = 0;
3518
- event.messageArr.push(systemMsg, summaryMsg, ...keepRecent);
3519
3693
  event.events({
3520
3694
  sessionId: event.sessionId,
3695
+ parentId: event.depth > 0 ? event.sessionId : "",
3521
3696
  eventType: "session.summary",
3522
3697
  metadata: {
3523
3698
  depth: event.depth,
3699
+ messageId: summaryMsg?.id ?? "(rolling-summary-slot)",
3524
3700
  decisionSource: "summary",
3525
- ok: true,
3701
+ ok: false,
3526
3702
  durationMs: endTime - startTime,
3527
- round: round++
3703
+ attempt: nextFailures
3528
3704
  },
3529
3705
  usage: {
3530
3706
  prompt_tokens: lastSize,
3531
- compress_tokens: estReal(event.messageArr)
3707
+ compress_tokens: estimateTokens(event.messageArr)
3708
+ },
3709
+ payload: {
3710
+ output: err.message
3532
3711
  }
3533
3712
  });
3534
- const store = await getRollingState(event.sessionId);
3535
- store.archivedMessageCount = (store.archivedMessageCount || 0) + toCompact.length;
3536
- await persistCompaction(event.sessionId, store.archivedMessageCount, summaryMsg.content);
3537
- } else if (keep > 1) {
3538
- keep--;
3539
- continue;
3540
- } else if (summaryMsg?.content) {
3541
- summaryMsg.content = await compactSlotNarrative(summaryMsg.content, event.modelWindow, event.signal, event.onAuxUsage);
3542
- event.messageArr.length = 0;
3543
- event.messageArr.push(systemMsg, summaryMsg, ...keepRecent);
3544
- const store = await getRollingState(event.sessionId);
3545
- await persistCompaction(event.sessionId, store.archivedMessageCount, summaryMsg.content);
3546
- } else {
3713
+ if (nextFailures >= 3) {
3714
+ throw new Error(
3715
+ `\u274C [\u538B\u7F29\u7194\u65AD] \u4E0A\u4E0B\u6587\u538B\u7F29\u8FDE\u7EED ${nextFailures} \u6B21\u5931\u8D25\uFF0C\u4E3A\u9632\u5929\u4EF7\u8D26\u5355\u6B7B\u5FAA\u73AF\u5DF2\u6682\u505C\u81EA\u52A8\u538B\u7F29\u3002\u5E38\u89C1\u6839\u56E0\uFF1A\u6458\u8981\u8F85\u52A9\u6A21\u578B\u670D\u52A1\u5F02\u5E38\u6216\u7F51\u7EDC\u4E0D\u901A\uFF08\u53EF\u67E5 DEEP_SEEK_AUX_MODEL \u670D\u52A1\u53EF\u7528\u6027\uFF1B\u8D85\u957F\u5DE5\u5177\u7ED3\u679C\u5DF2\u7531\u7CFB\u7EDF\u81EA\u52A8\u9884\u622A\u65AD\uFF0C\u4E00\u822C\u4E0D\u518D\u662F\u4E3B\u56E0\uFF09\u3002\u81EA\u6108\uFF1A\u76F4\u63A5\u91CD\u53D1\u6D88\u606F\u5373\u53EF\u91CD\u8BD5\uFF08\u6210\u529F\u538B\u7F29\u4E00\u6B21\u8BA1\u6570\u5373\u6E05\u96F6\uFF09\uFF1B\u6216 /fork \u5206\u53C9\u7EED\u63A5\u3001/new \u5F00\u65B0\u4F1A\u8BDD\u3002`
3716
+ );
3717
+ }
3547
3718
  break;
3548
3719
  }
3549
- } catch (error) {
3550
- if (event.signal?.aborted) return;
3551
- const err = error instanceof Error ? error : new Error(String(error));
3552
- console.warn("\u26A0\uFE0F \u672C\u8F6E\u538B\u7F29\u5931\u8D25\uFF0C\u8DF3\u8FC7:", err.message);
3553
- const store = await getRollingState(event.sessionId);
3554
- const nextFailures = (store.consecutiveFailures || 0) + 1;
3555
- await setRollingState(event.sessionId, {
3556
- archivedMessageCount: store.archivedMessageCount || 0,
3557
- rollingSummary: summaryMsg?.content || "",
3558
- consecutiveFailures: nextFailures
3559
- // 👈 同步落盘
3560
- });
3561
- const endTime = performance.now();
3562
- event.events({
3563
- sessionId: event.sessionId,
3564
- parentId: event.depth > 0 ? event.sessionId : "",
3565
- eventType: "session.summary",
3566
- metadata: {
3567
- depth: event.depth,
3568
- messageId: summaryMsg?.id ?? "(rolling-summary-slot)",
3569
- decisionSource: "summary",
3570
- ok: false,
3571
- durationMs: endTime - startTime,
3572
- attempt: nextFailures
3573
- },
3574
- usage: {
3575
- prompt_tokens: lastSize,
3576
- compress_tokens: estimateTokens(event.messageArr)
3577
- },
3578
- payload: {
3579
- output: err.message
3580
- }
3720
+ const newSize = estReal(event.messageArr);
3721
+ if (newSize >= lastSize) break;
3722
+ lastSize = newSize;
3723
+ }
3724
+ } finally {
3725
+ if (preCompactSent) {
3726
+ await dispatch("PostCompact", { sessionId: event.sessionId, depth: event.depth, tokensBefore: Math.round(lastSize), tokensAfter: Math.round(estReal(event.messageArr)) }).catch(() => {
3581
3727
  });
3582
- if (nextFailures >= 3) {
3583
- throw new Error(
3584
- `\u274C [\u538B\u7F29\u7194\u65AD] \u4E0A\u4E0B\u6587\u538B\u7F29\u8FDE\u7EED ${nextFailures} \u6B21\u5931\u8D25\uFF0C\u4E3A\u9632\u5929\u4EF7\u8D26\u5355\u6B7B\u5FAA\u73AF\u5DF2\u6682\u505C\u81EA\u52A8\u538B\u7F29\u3002\u5E38\u89C1\u6839\u56E0\uFF1A\u6458\u8981\u8F85\u52A9\u6A21\u578B\u670D\u52A1\u5F02\u5E38\u6216\u7F51\u7EDC\u4E0D\u901A\uFF08\u53EF\u67E5 DEEP_SEEK_AUX_MODEL \u670D\u52A1\u53EF\u7528\u6027\uFF1B\u8D85\u957F\u5DE5\u5177\u7ED3\u679C\u5DF2\u7531\u7CFB\u7EDF\u81EA\u52A8\u9884\u622A\u65AD\uFF0C\u4E00\u822C\u4E0D\u518D\u662F\u4E3B\u56E0\uFF09\u3002\u81EA\u6108\uFF1A\u76F4\u63A5\u91CD\u53D1\u6D88\u606F\u5373\u53EF\u91CD\u8BD5\uFF08\u6210\u529F\u538B\u7F29\u4E00\u6B21\u8BA1\u6570\u5373\u6E05\u96F6\uFF09\uFF1B\u6216 /fork \u5206\u53C9\u7EED\u63A5\u3001/new \u5F00\u65B0\u4F1A\u8BDD\u3002`
3585
- );
3586
- }
3587
- break;
3588
3728
  }
3589
- const newSize = estReal(event.messageArr);
3590
- if (newSize >= lastSize) break;
3591
- lastSize = newSize;
3592
3729
  }
3593
- await dispatch("PostCompact", { sessionId: event.sessionId, depth: event.depth, tokensBefore: Math.round(lastSize), tokensAfter: Math.round(estReal(event.messageArr)) }).catch(() => {
3594
- });
3730
+ reportCheck(compactDecision, decayed);
3731
+ if (compactDecision !== "noop") emitCheckEvent(compactDecision);
3595
3732
  if (event.onUIEvent && event.depth === 0) {
3596
3733
  const afterSize = Math.round(estReal(event.messageArr));
3597
3734
  if (preCompactSize - afterSize > 0) {
@@ -4173,13 +4310,19 @@ var init_inbox = __esm({
4173
4310
  };
4174
4311
  flushLeftoverToTranscript = async (sessionId) => {
4175
4312
  const texts = claimSessionInbox(sessionId);
4313
+ let failed = 0;
4176
4314
  for (const t of texts) {
4177
- await appendMessage({ sessionId, role: "user", content: t });
4315
+ const ok = await appendMessage({ sessionId, role: "user", content: t });
4316
+ if (!ok) failed++;
4178
4317
  }
4179
4318
  if (texts.length > 0) {
4180
- console.warn(`[inbox] \u4F1A\u8BDD ${sessionId} \u7ED3\u675F\u65F6 ${texts.length} \u6761\u8865\u5145\u8F93\u5165\u672A\u8BA4\u9886\uFF0C\u5DF2\u5199\u5165 transcript\uFF08\u4E0B\u8F6E\u81EA\u52A8\u5E26\u5165\uFF09`);
4319
+ if (failed > 0) {
4320
+ console.warn(`[inbox] \u4F1A\u8BDD ${sessionId} \u7ED3\u675F\u65F6 ${failed}/${texts.length} \u6761\u8865\u5145\u8F93\u5165\u843D\u76D8\u5931\u8D25\uFF08transcript \u4FA7\u6545\u969C\uFF0C\u4E0B\u8F6E\u65E0\u6CD5\u5E26\u5165\uFF09`);
4321
+ } else {
4322
+ console.warn(`[inbox] \u4F1A\u8BDD ${sessionId} \u7ED3\u675F\u65F6 ${texts.length} \u6761\u8865\u5145\u8F93\u5165\u672A\u8BA4\u9886\uFF0C\u5DF2\u5199\u5165 transcript\uFF08\u4E0B\u8F6E\u81EA\u52A8\u5E26\u5165\uFF09`);
4323
+ }
4181
4324
  }
4182
- return texts.length;
4325
+ return { flushed: texts.length - failed, failed };
4183
4326
  };
4184
4327
  }
4185
4328
  });
@@ -5548,6 +5691,153 @@ var init_httpExecutor = __esm({
5548
5691
  }
5549
5692
  });
5550
5693
 
5694
+ // ../core/src/agent/agentWake.ts
5695
+ var AGENT_DONE_SENTINEL, SUBAGENT_REPORT_MAX_CHARS, wakeDrivers, wakeQueued, busyRuns, inFlightByParent, quiesceWaiters, registerWakeDriver, registerWakeDriverIfAbsent, unregisterWakeDriver, markRunStart, markRunEnd, pumpWake, scheduleWake, clearQueuedWake, notifySubagentDone, buildSubagentDoneReport, trackSubagentInFlight, releaseSubagentInFlight, notifyQuiesceWaiters, isQuiescent, waitSessionQuiescent;
5696
+ var init_agentWake = __esm({
5697
+ "../core/src/agent/agentWake.ts"() {
5698
+ "use strict";
5699
+ init_inbox();
5700
+ init_transcript();
5701
+ AGENT_DONE_SENTINEL = "\u27E6DSC:AGENT-DONE\u27E7 \u5B50\u4EE3\u7406\u5B8C\u6210\u5524\u9192\uFF1A\u672C\u4F1A\u8BDD\u521A\u6CE8\u5165\u4E86\u5B50\u4EE3\u7406\u5B8C\u6210\u6C47\u62A5\uFF08\u89C1\u76F8\u90BB\u6D88\u606F\uFF09\uFF0C\u8BF7\u9010\u6761\u6D88\u5316\u6C47\u62A5\u5185\u5BB9\u5E76\u7EE7\u7EED\u63A8\u8FDB\u4E3B\u4EFB\u52A1\uFF1B\u82E5\u65E0\u5F85\u7EED\u4E8B\u9879\uFF0C\u7B80\u8981\u786E\u8BA4\u5373\u53EF\u3002";
5702
+ SUBAGENT_REPORT_MAX_CHARS = 4096;
5703
+ wakeDrivers = /* @__PURE__ */ new Map();
5704
+ wakeQueued = /* @__PURE__ */ new Set();
5705
+ busyRuns = /* @__PURE__ */ new Set();
5706
+ inFlightByParent = /* @__PURE__ */ new Map();
5707
+ quiesceWaiters = /* @__PURE__ */ new Map();
5708
+ registerWakeDriver = (sessionId, driver) => {
5709
+ if (!sessionId) return;
5710
+ wakeDrivers.set(sessionId, driver);
5711
+ };
5712
+ registerWakeDriverIfAbsent = (sessionId, driver) => {
5713
+ if (!sessionId || wakeDrivers.has(sessionId)) return;
5714
+ wakeDrivers.set(sessionId, driver);
5715
+ };
5716
+ unregisterWakeDriver = (sessionId) => {
5717
+ wakeDrivers.delete(sessionId);
5718
+ };
5719
+ markRunStart = (sessionId) => {
5720
+ if (!sessionId) return;
5721
+ busyRuns.add(sessionId);
5722
+ };
5723
+ markRunEnd = (sessionId) => {
5724
+ if (!sessionId) return;
5725
+ busyRuns.delete(sessionId);
5726
+ pumpWake(sessionId);
5727
+ notifyQuiesceWaiters(sessionId);
5728
+ };
5729
+ pumpWake = (sessionId) => {
5730
+ if (!wakeQueued.has(sessionId)) return;
5731
+ if (busyRuns.has(sessionId)) return;
5732
+ wakeQueued.delete(sessionId);
5733
+ const driver = wakeDrivers.get(sessionId);
5734
+ if (!driver) return;
5735
+ busyRuns.add(sessionId);
5736
+ void (async () => {
5737
+ try {
5738
+ await driver();
5739
+ } catch (e) {
5740
+ console.warn(`\u26A0\uFE0F [agentWake] \u5524\u9192 run \u5F02\u5E38\uFF08\u5DF2\u5FFD\u7565\uFF0C\u94FE\u8DEF\u7EE7\u7EED\u6536\u655B\uFF09: ${e?.message ?? e}`);
5741
+ } finally {
5742
+ busyRuns.delete(sessionId);
5743
+ notifyQuiesceWaiters(sessionId);
5744
+ pumpWake(sessionId);
5745
+ }
5746
+ })();
5747
+ };
5748
+ scheduleWake = (sessionId) => {
5749
+ if (!sessionId || wakeQueued.has(sessionId)) return;
5750
+ wakeQueued.add(sessionId);
5751
+ pumpWake(sessionId);
5752
+ notifyQuiesceWaiters(sessionId);
5753
+ };
5754
+ clearQueuedWake = (sessionId) => {
5755
+ wakeQueued.delete(sessionId);
5756
+ };
5757
+ notifySubagentDone = async (parentSessionId, report) => {
5758
+ if (!parentSessionId || !report) return;
5759
+ const delivered = pushSessionInbox(parentSessionId, report);
5760
+ if (!delivered) {
5761
+ try {
5762
+ await appendMessage({ sessionId: parentSessionId, role: "user", content: report });
5763
+ } catch {
5764
+ }
5765
+ }
5766
+ scheduleWake(parentSessionId);
5767
+ };
5768
+ buildSubagentDoneReport = (agentId, taskBrief, result) => {
5769
+ const status = result.status ?? (result.output ? "final" : "error");
5770
+ const statusLabel = status === "final" ? "final\uFF08\u6B63\u5E38\u5B8C\u6210\uFF09" : status === "aborted" ? "aborted\uFF08\u88AB\u4E2D\u6B62\uFF09" : "error\uFF08\u5F02\u5E38\uFF09";
5771
+ const over = result.output.length > SUBAGENT_REPORT_MAX_CHARS;
5772
+ const body = over ? result.output.slice(0, SUBAGENT_REPORT_MAX_CHARS) : result.output;
5773
+ return [
5774
+ `\u{1F916} [\u5B50\u4EE3\u7406\u5B8C\u6210] \u{1F194} ${agentId}${taskBrief ? `\uFF08${taskBrief}\uFF09` : ""}`,
5775
+ `\u72B6\u6001\uFF1A${statusLabel}`,
5776
+ `\u6C47\u62A5\uFF1A`,
5777
+ body,
5778
+ over ? `
5779
+ \uFF08\u26A0\uFE0F \u6C47\u62A5\u8D85\u957F\u5DF2\u622A\u65AD\u81F3 ${SUBAGENT_REPORT_MAX_CHARS} \u5B57\u7B26\uFF1B\u5B8C\u6574\u5185\u5BB9\u53EF spawn_agent \u4F20 resume_session_id="${agentId}" \u8FFD\u95EE\u3002\uFF09` : ""
5780
+ ].filter((s) => s !== "").join("\n");
5781
+ };
5782
+ trackSubagentInFlight = (parentSessionId, subSessionId) => {
5783
+ if (!parentSessionId) return;
5784
+ let set = inFlightByParent.get(parentSessionId);
5785
+ if (!set) {
5786
+ set = /* @__PURE__ */ new Set();
5787
+ inFlightByParent.set(parentSessionId, set);
5788
+ }
5789
+ set.add(subSessionId);
5790
+ };
5791
+ releaseSubagentInFlight = (parentSessionId, subSessionId) => {
5792
+ const set = inFlightByParent.get(parentSessionId);
5793
+ if (!set) return;
5794
+ set.delete(subSessionId);
5795
+ if (set.size === 0) inFlightByParent.delete(parentSessionId);
5796
+ notifyQuiesceWaiters(parentSessionId);
5797
+ };
5798
+ notifyQuiesceWaiters = (sessionId) => {
5799
+ const waiters = quiesceWaiters.get(sessionId);
5800
+ if (!waiters || waiters.size === 0) return;
5801
+ for (const w of [...waiters]) w();
5802
+ };
5803
+ isQuiescent = (sessionId) => !inFlightByParent.get(sessionId)?.size && !busyRuns.has(sessionId) && !wakeQueued.has(sessionId);
5804
+ waitSessionQuiescent = (sessionId, signal, timeoutMs2) => {
5805
+ if (isQuiescent(sessionId)) return Promise.resolve(true);
5806
+ return new Promise((resolve) => {
5807
+ let settled = false;
5808
+ const finish = (ok) => {
5809
+ if (settled) return;
5810
+ settled = true;
5811
+ if (timer) clearTimeout(timer);
5812
+ if (onAbort) signal?.removeEventListener("abort", onAbort);
5813
+ const set = quiesceWaiters.get(sessionId);
5814
+ if (set) {
5815
+ set.delete(check);
5816
+ if (set.size === 0) quiesceWaiters.delete(sessionId);
5817
+ }
5818
+ resolve(ok);
5819
+ };
5820
+ const check = () => {
5821
+ if (isQuiescent(sessionId)) finish(true);
5822
+ };
5823
+ let timer;
5824
+ const onAbort = () => finish(false);
5825
+ if (!quiesceWaiters.has(sessionId)) quiesceWaiters.set(sessionId, /* @__PURE__ */ new Set());
5826
+ quiesceWaiters.get(sessionId).add(check);
5827
+ if (signal) {
5828
+ if (signal.aborted) {
5829
+ finish(false);
5830
+ return;
5831
+ }
5832
+ signal.addEventListener("abort", onAbort, { once: true });
5833
+ }
5834
+ if (timeoutMs2 != null && timeoutMs2 > 0) timer = setTimeout(() => finish(false), timeoutMs2);
5835
+ check();
5836
+ });
5837
+ };
5838
+ }
5839
+ });
5840
+
5551
5841
  // ../core/src/agent/repeatBreaker.ts
5552
5842
  var createRepeatBreaker;
5553
5843
  var init_repeatBreaker = __esm({
@@ -5889,7 +6179,7 @@ ${style.body}`);
5889
6179
  });
5890
6180
 
5891
6181
  // ../core/src/memory/registry.ts
5892
- var memories, registerMemory, listMemories, getMemory, unregisterMemory, clearMemories, memoryIndexLine, getMemoryIndex;
6182
+ var memories, registerMemory, listMemories, getMemory, unregisterMemory, clearMemories, memoryIndexLine, MEMORY_LIMITS, getMemoryIndex;
5893
6183
  var init_registry6 = __esm({
5894
6184
  "../core/src/memory/registry.ts"() {
5895
6185
  "use strict";
@@ -5908,6 +6198,7 @@ var init_registry6 = __esm({
5908
6198
  };
5909
6199
  clearMemories = memories.clear;
5910
6200
  memoryIndexLine = (m) => `- **${m.name}** (${m.type}) \u2014 ${m.description}`;
6201
+ MEMORY_LIMITS = { maxCount: 200, maxIndexBytes: 16 * 1024 };
5911
6202
  getMemoryIndex = () => {
5912
6203
  const all = memories.list();
5913
6204
  if (all.length === 0) return null;
@@ -5917,7 +6208,7 @@ var init_registry6 = __esm({
5917
6208
  });
5918
6209
 
5919
6210
  // ../core/src/memory/inject.ts
5920
- var MEMORY_FENCE, MEMORY_MARKER, injectMemory;
6211
+ var MEMORY_FENCE, MEMORY_MARKER, memorySnapshots, injectMemory, clearMemorySnapshot;
5921
6212
  var init_inject5 = __esm({
5922
6213
  "../core/src/memory/inject.ts"() {
5923
6214
  "use strict";
@@ -5925,12 +6216,24 @@ var init_inject5 = __esm({
5925
6216
  init_registry6();
5926
6217
  MEMORY_FENCE = "\u27E6DSC:MEMORY\u27E7";
5927
6218
  MEMORY_MARKER = "\u3010\u8BB0\u5FC6\u7D22\u5F15\u3011";
5928
- injectMemory = (message) => {
5929
- const index = getMemoryIndex();
6219
+ memorySnapshots = /* @__PURE__ */ new Map();
6220
+ injectMemory = (message, sessionId) => {
6221
+ let index;
6222
+ if (sessionId === void 0) {
6223
+ index = getMemoryIndex();
6224
+ } else {
6225
+ if (!memorySnapshots.has(sessionId)) {
6226
+ memorySnapshots.set(sessionId, getMemoryIndex());
6227
+ }
6228
+ index = memorySnapshots.get(sessionId) ?? null;
6229
+ }
5930
6230
  if (!index) return;
5931
6231
  injectMarkedBlock(message, MEMORY_FENCE, `${MEMORY_MARKER}
5932
6232
  ${index}`);
5933
6233
  };
6234
+ clearMemorySnapshot = (sessionId) => {
6235
+ memorySnapshots.delete(sessionId);
6236
+ };
5934
6237
  }
5935
6238
  });
5936
6239
 
@@ -6009,7 +6312,7 @@ var init_systemInjections = __esm({
6009
6312
  injectSkillCatalog(message);
6010
6313
  injectAgentCatalog(message);
6011
6314
  injectProjectGuide(message);
6012
- injectMemory(message);
6315
+ injectMemory(message, options.sessionId);
6013
6316
  return { rawTools, cleanedToolSchemas, toolsTokens };
6014
6317
  };
6015
6318
  }
@@ -6102,7 +6405,12 @@ var init_agent = __esm({
6102
6405
  type: "function",
6103
6406
  function: {
6104
6407
  name: "spawn_agent",
6105
- description: "\u6D3E\u751F\u62E5\u6709\u72EC\u7ACB\u6267\u884C\u73AF\u5883\u7684\u5B50 agent \u5904\u7406\u9694\u79BB\u5B50\u4EFB\u52A1\uFF0C\u5B8C\u6210\u540E\u81EA\u52A8\u6C47\u62A5\u6539\u52A8\u70B9\u4E0E\u7ED3\u8BBA\u3002name \u53EF\u6307\u5B9A\u58F0\u660E\u5F0F\u5B50 Agent\uFF08\u89C1\u7CFB\u7EDF\u63D0\u793A\u8BCD\u3010\u53EF\u7528\u5B50 Agent \u76EE\u5F55\u3011\uFF0C\u5957\u7528\u5176\u7CFB\u7EDF\u8BCD/\u5DE5\u5177\u767D\u540D\u5355/model\uFF09\uFF1B\u7F3A\u7701\u8D70\u901A\u7528\u6A21\u677F\u3002\u7EED\u8DD1\uFF1A\u4F20 resume_session_id\uFF08\u6B64\u524D\u8FD4\u56DE\u7684 agent_id\uFF09\u590D\u7528\u8BE5\u5B50 Agent \u5B8C\u6574\u5386\u53F2\u8BB0\u5FC6\uFF0C\u9002\u5408\u8FFD\u95EE/\u7EE7\u7EED\u672A\u5B8C\u4EFB\u52A1\u3002",
6408
+ description: [
6409
+ "\u5F02\u6B65\u6D3E\u751F\u62E5\u6709\u72EC\u7ACB\u6267\u884C\u73AF\u5883\u7684\u5B50 agent \u5904\u7406\u9694\u79BB\u5B50\u4EFB\u52A1\uFF1A\u8C03\u7528\u7ACB\u5373\u8FD4\u56DE agent_id\uFF0C\u4E0D\u7B49\u5F85\u5B8C\u6210\uFF1B\u5B50 agent \u5728\u540E\u53F0\u6267\u884C\uFF0C\u5B8C\u6210\u540E\u5176\u6C47\u62A5\u4F1A\u81EA\u52A8\u6CE8\u5165\u672C\u4F1A\u8BDD\u5E76\u5524\u9192\u4F60\u7EE7\u7EED\u63A8\u8FDB\uFF08\u65E0\u9700\u8F6E\u8BE2\u3001\u65E0\u9700\u7B49\u5F85\uFF09\u3002",
6410
+ "\u6D3E\u51FA\u540E\u4F60\u53EF\u7ACB\u5373\u7EE7\u7EED\u6D3E\u6D3B / \u5904\u7406\u5176\u4ED6\u4E8B\u52A1 / \u56DE\u590D\u7528\u6237\uFF1B\u5F53\u524D\u65E0\u5176\u4ED6\u4E8B\u52A1\u65F6\u53EF\u7B80\u77ED\u544A\u77E5\u5DF2\u59D4\u6D3E\u5E76\u7ED3\u675F\u672C\u8F6E\u2014\u2014\u6C47\u62A5\u9001\u8FBE\u65F6\u4F60\u4F1A\u88AB\u81EA\u52A8\u5524\u9192\u3002",
6411
+ "name \u53EF\u6307\u5B9A\u58F0\u660E\u5F0F\u5B50 Agent\uFF08\u89C1\u7CFB\u7EDF\u63D0\u793A\u8BCD\u3010\u53EF\u7528\u5B50 Agent \u76EE\u5F55\u3011\uFF0C\u5957\u7528\u5176\u7CFB\u7EDF\u8BCD/\u5DE5\u5177\u767D\u540D\u5355/model\uFF09\uFF1B\u7F3A\u7701\u8D70\u901A\u7528\u6A21\u677F\u3002",
6412
+ "\u7EED\u8DD1\uFF1A\u4F20 resume_session_id\uFF08\u6B64\u524D\u8FD4\u56DE\u7684 agent_id\uFF09\u590D\u7528\u8BE5\u5B50 Agent \u5B8C\u6574\u5386\u53F2\u8BB0\u5FC6\uFF0C\u9002\u5408\u8FFD\u95EE/\u7EE7\u7EED\u672A\u5B8C\u4EFB\u52A1\uFF08\u540C\u6837\u5F02\u6B65\uFF0C\u5B8C\u6210\u540E\u81EA\u52A8\u5524\u9192\uFF09\u3002"
6413
+ ].join("\n"),
6106
6414
  parameters: {
6107
6415
  type: "object",
6108
6416
  properties: {
@@ -6114,30 +6422,29 @@ var init_agent = __esm({
6114
6422
  required: ["task"]
6115
6423
  },
6116
6424
  safetyLevel: "safe" /* SAFE */,
6117
- isSync: true,
6118
- async execute(args, ctx) {
6119
- if (!ctx) return toolFailure("[\u6D3E\u751F\u5931\u8D25]\uFF1Aspawn_agent \u7F3A\u5C11\u5FC5\u987B\u7684\u667A\u80FD\u4F53\u8FD0\u884C\u4E0A\u4E0B\u6587\u3002");
6120
- const res = await runSubagent(
6425
+ isSync: false,
6426
+ async *execute(args, ctx) {
6427
+ if (!ctx) {
6428
+ yield toolFailure("[\u6D3E\u751F\u5931\u8D25]\uFF1Aspawn_agent \u7F3A\u5C11\u5FC5\u987B\u7684\u667A\u80FD\u4F53\u8FD0\u884C\u4E0A\u4E0B\u6587\u3002");
6429
+ return;
6430
+ }
6431
+ const prep = await prepareSubagent(
6121
6432
  { task: args.task, name: args.name, role: args.role, resumeSessionId: args.resume_session_id },
6122
6433
  ctx,
6123
6434
  getGlobalTools
6124
6435
  );
6125
- if (!res.ok) return asToolFailure(res.output);
6126
- return [
6127
- `\u{1F389} [\u5B50 Agent \u6267\u884C\u5B8C\u6BD5${args.resume_session_id ? "\xB7\u7EED\u8DD1" : ""}] \u6D3E\u751F\u5B50\u4EFB\u52A1\u6C47\u62A5\u5982\u4E0B\uFF1A
6128
- `,
6129
- `\u{1F194} agent_id: ${res.sessionId}
6130
- \uFF08\u9700\u8981\u7EE7\u7EED\u8BE5\u5B50 Agent \u7684\u4EFB\u52A1\u6216\u8FFD\u95EE\u65F6\uFF0C\u8C03\u7528 spawn_agent \u4F20 resume_session_id=${res.sessionId}\uFF0C\u5176\u5386\u53F2\u8BB0\u5FC6\u5B8C\u6574\u4FDD\u7559\uFF09
6131
- `,
6132
- `==================================================
6133
- `,
6134
- res.output,
6135
- `
6136
- ==================================================
6137
- `,
6138
- `\u{1F6A8}\u3010\u91CD\u8981\u540C\u6B65\u901A\u77E5\u3011: \u8BE5\u5B50 Agent \u521A\u624D\u6781\u6709\u53EF\u80FD\u5DF2\u7ECF\u9AD8\u9891\u4FEE\u6539\u3001\u521B\u5EFA\u6216\u5220\u9664\u4E86\u4F60\u7BA1\u8F96\u533A\u5185\u7684\u672C\u5730\u6E90\u7801\u6587\u4EF6\u3002`,
6139
- `\u5982\u679C\u4F60\uFF08\u7236\u7EA7Agent\uFF09\u63A5\u4E0B\u6765\u9700\u8981\u5BF9\u6587\u4EF6\u7CFB\u7EDF\u7EE7\u7EED\u5B9E\u65BD edit_file\u3001write_file \u6216\u9A8C\u8BC1\uFF0C\u3010\u4F60\u5FC5\u987B\u3011\u9996\u5148\u8C03\u7528 read_file \u6216 list_dir \u91CD\u65B0\u8BFB\u53D6\u6700\u65B0\u78C1\u76D8\u72B6\u6001\uFF0C\u7981\u6B62\u76F2\u76EE\u4F9D\u8D56\u4F60\u4E4B\u524D\u7684\u8BB0\u5FC6\u7F13\u5B58\uFF0C\u5426\u5219\u5FC5\u7136\u4F1A\u5F15\u53D1\u552F\u4E00\u6027\u51B2\u7A81\u5339\u914D\u5931\u8D25\uFF01`
6140
- ].join("");
6436
+ if (!prep.ok) {
6437
+ yield asToolFailure(prep.error);
6438
+ return;
6439
+ }
6440
+ const agentId = prep.prepared.subSessionId;
6441
+ yield [
6442
+ `\u2705 \u5B50\u4EE3\u7406\u5DF2\u5F02\u6B65\u542F\u52A8 \u{1F194} ${agentId}${prep.prepared.manifest ? `\uFF08\u58F0\u660E\u5F0F=${prep.prepared.manifest.name}\uFF09` : ""}`,
6443
+ `\u4EFB\u52A1\u6458\u8981\uFF1A${prep.prepared.taskBrief}${args.task.length > 60 ? "\u2026" : ""}`,
6444
+ `\u5B83\u5B8C\u6210\u540E\u6C47\u62A5\u4F1A\u81EA\u52A8\u9001\u8FBE\u672C\u4F1A\u8BDD\u5E76\u5524\u9192\u4F60\u7EE7\u7EED\uFF1B\u671F\u95F4\u65E0\u9700\u7B49\u5F85\u2014\u2014\u53EF\u7EE7\u7EED\u6D3E\u6D3B / \u5904\u7406\u5176\u4ED6\u4E8B\u52A1\uFF0C\u65E0\u5176\u4ED6\u4E8B\u52A1\u65F6\u7B80\u77ED\u544A\u77E5\u7528\u6237\u5373\u53EF\u6536\u5C3E\u672C\u8F6E\u3002`,
6445
+ `\uFF08\u8FFD\u95EE/\u7EED\u8DD1\uFF1Aspawn_agent \u4F20 resume_session_id="${agentId}"\uFF0C\u5176\u5386\u53F2\u8BB0\u5FC6\u5B8C\u6574\u4FDD\u7559\u3002\uFF09`
6446
+ ].join("\n");
6447
+ await runPreparedSubagent(prep.prepared, ctx, { notifyDone: true });
6141
6448
  }
6142
6449
  }
6143
6450
  }
@@ -6370,6 +6677,7 @@ var init_workflow = __esm({
6370
6677
  init_config();
6371
6678
  init_guard();
6372
6679
  init_manager();
6680
+ init_agentWake();
6373
6681
  init_workflowHelpers();
6374
6682
  createWorkflowTools = (getGlobalTools) => [
6375
6683
  {
@@ -6377,7 +6685,8 @@ var init_workflow = __esm({
6377
6685
  function: {
6378
6686
  name: "run_workflow",
6379
6687
  description: [
6380
- "\u591A\u5B50 agent \u7F16\u6392\uFF1A\u628A\u53EF\u5206\u89E3\u7684\u5927\u4EFB\u52A1\u6D3E\u7ED9\u591A\u4E2A\u72EC\u7ACB\u5B50 agent\u3002parallel\uFF08\u9ED8\u8BA4\uFF09=\u5404\u6B65\u9AA4\u5E76\u53D1\u6267\u884C\u3001\u7ED3\u679C\u6309\u5E8F\u805A\u5408\uFF0C\u9002\u5408\u4E92\u76F8\u72EC\u7ACB\u7684\u53EA\u8BFB/\u5206\u6790\u4EFB\u52A1\uFF1Bpipeline=\u6B65\u9AA4\u4E32\u884C\uFF0C\u524D\u9636\u6BB5\u4EA7\u51FA\u81EA\u52A8\u4F5C\u4E3A\u540E\u9636\u6BB5\u8F93\u5165\uFF0C\u9002\u5408\u5206\u9636\u6BB5\u52A0\u5DE5\uFF08\u62BD\u53D6\u2192\u5F52\u7C7B\u2192\u6C47\u603B\uFF09\u3002",
6688
+ "\u5F02\u6B65\u591A\u5B50 agent \u7F16\u6392\uFF1A\u628A\u53EF\u5206\u89E3\u7684\u5927\u4EFB\u52A1\u6D3E\u7ED9\u591A\u4E2A\u72EC\u7ACB\u5B50 agent\uFF0C\u8C03\u7528\u7ACB\u5373\u8FD4\u56DE\uFF08\u4E0D\u7B49\u5F85\u5B8C\u6210\uFF09\uFF0C\u5168\u90E8\u6B65\u9AA4\u7ED3\u675F\u540E\u4E00\u6761\u7EC8\u6001\u6C47\u603B\u81EA\u52A8\u6CE8\u5165\u672C\u4F1A\u8BDD\u5E76\u5524\u9192\u4F60\u7EE7\u7EED\u63A8\u8FDB\uFF08\u4E2D\u95F4\u6001\u4E0D\u56DE\u6D41\uFF0C\u9632\u4E8B\u4EF6\u6D2A\u5CF0\uFF09\u3002parallel\uFF08\u9ED8\u8BA4\uFF09=\u5404\u6B65\u9AA4\u5E76\u53D1\u6267\u884C\u3001\u7ED3\u679C\u6309\u5E8F\u805A\u5408\uFF0C\u9002\u5408\u4E92\u76F8\u72EC\u7ACB\u7684\u53EA\u8BFB/\u5206\u6790\u4EFB\u52A1\uFF1Bpipeline=\u6B65\u9AA4\u4E32\u884C\uFF0C\u524D\u9636\u6BB5\u4EA7\u51FA\u81EA\u52A8\u4F5C\u4E3A\u540E\u9636\u6BB5\u8F93\u5165\uFF0C\u9002\u5408\u5206\u9636\u6BB5\u52A0\u5DE5\uFF08\u62BD\u53D6\u2192\u5F52\u7C7B\u2192\u6C47\u603B\uFF09\u3002",
6689
+ "\u6D3E\u51FA\u540E\u4F60\u53EF\u7ACB\u5373\u7EE7\u7EED\u6D3E\u6D3B / \u5904\u7406\u5176\u4ED6\u4E8B\u52A1 / \u56DE\u590D\u7528\u6237\uFF1B\u5F53\u524D\u65E0\u5176\u4ED6\u4E8B\u52A1\u65F6\u53EF\u7B80\u77ED\u544A\u77E5\u5DF2\u7F16\u6392\u5E76\u7ED3\u675F\u672C\u8F6E\u2014\u2014\u6C47\u603B\u9001\u8FBE\u65F6\u4F60\u4F1A\u88AB\u81EA\u52A8\u5524\u9192\u3002",
6381
6690
  '\u6B65\u9AA4\u53EF\u7528 name \u5957\u7528\u58F0\u660E\u5F0F\u5B50 Agent\uFF08\u89C1\u3010\u53EF\u7528\u5B50 Agent \u76EE\u5F55\u3011\uFF09\u3001key \u4F5C\u7ED3\u679C\u6807\u7B7E\u3002parallel \u9ED8\u8BA4\u5171\u4EAB\u5DE5\u4F5C\u533A\uFF0C\u4EC5\u9002\u5408\u5E76\u884C\u8BFB/\u5206\u6790\uFF1B\u5E76\u884C\u5199/\u8FC1\u79FB\u7528 isolation="worktree"\uFF08\u6BCF\u6B65\u72EC\u7ACB git worktree\uFF0C\u7ED3\u675F\u540E\u6536\u5272\u5404\u6B65 diff\uFF09\u3002',
6382
6691
  "\u4E0E spawn_agent \u7684\u5206\u5DE5\uFF1A\u5355\u4EFB\u52A1\u4E32\u884C\u59D4\u6D3E\u7528 spawn_agent\uFF1B\u4EFB\u52A1\u53EF\u5206\u89E3\u3001\u591A\u5206\u652F\u6216\u6D41\u6C34\u7EBF\u5F0F\u65F6\u7528\u672C\u5DE5\u5177\u3002"
6383
6692
  ].join("\n"),
@@ -6417,18 +6726,29 @@ var init_workflow = __esm({
6417
6726
  required: ["steps"]
6418
6727
  },
6419
6728
  safetyLevel: "safe" /* SAFE */,
6420
- isSync: true,
6729
+ isSync: false,
6421
6730
  // 聚合输出整体兜底截断(各子 agent 结果已按 workflowPerStepChars 单独截断)。
6731
+ // ★ 异步化后工具结果 = 首个 yield 的启动回执(天然短);此上限改用于终态汇总的 inbox 报文截断。
6422
6732
  maxOutputCharacters: 18e3,
6423
- async execute(args, ctx) {
6424
- if (!ctx) return toolFailure("[\u5DE5\u4F5C\u6D41]\uFF1Arun_workflow \u7F3A\u5C11\u5FC5\u987B\u7684\u667A\u80FD\u4F53\u8FD0\u884C\u4E0A\u4E0B\u6587\u3002");
6733
+ // ★ 子代理异步化(方案 §4.4):async* + 首个 yield 立即回执;全部步骤在 generator 挂起期间后台执行,
6734
+ // 完成后一条终态汇总经 agentWake.notifySubagentDone 回流(中间态不回流,防事件洪峰)。
6735
+ // 宿主中止时执行层 gen.return() 拦在 await 前 → 不产生中止后的唤醒(与 spawn_agent 同语义)。
6736
+ async *execute(args, ctx) {
6737
+ if (!ctx) {
6738
+ yield toolFailure("[\u5DE5\u4F5C\u6D41]\uFF1Arun_workflow \u7F3A\u5C11\u5FC5\u987B\u7684\u667A\u80FD\u4F53\u8FD0\u884C\u4E0A\u4E0B\u6587\u3002");
6739
+ return;
6740
+ }
6425
6741
  const maxSteps = appConfig.workflowMaxSteps;
6426
6742
  const validationError = validateWorkflowArgs(args, maxSteps);
6427
- if (validationError) return validationError;
6743
+ if (validationError) {
6744
+ yield validationError;
6745
+ return;
6746
+ }
6428
6747
  const mode = args.mode === "pipeline" ? "pipeline" : "parallel";
6429
6748
  const isolation = args.isolation === "worktree" ? "worktree" : "none";
6430
6749
  if (isolation === "worktree" && mode === "pipeline") {
6431
- return toolFailure('[\u5DE5\u4F5C\u6D41]\uFF1Aisolation="worktree" \u6682\u4EC5\u652F\u6301 parallel \u6A21\u5F0F\uFF08pipeline \u4E32\u884C\u65E0\u9700\u9694\u79BB\uFF09\u3002\u8BF7\u6539 mode=parallel \u6216 isolation=none\u3002');
6750
+ yield toolFailure('[\u5DE5\u4F5C\u6D41]\uFF1Aisolation="worktree" \u6682\u4EC5\u652F\u6301 parallel \u6A21\u5F0F\uFF08pipeline \u4E32\u884C\u65E0\u9700\u9694\u79BB\uFF09\u3002\u8BF7\u6539 mode=parallel \u6216 isolation=none\u3002');
6751
+ return;
6432
6752
  }
6433
6753
  const steps = args.steps.map((s) => ({
6434
6754
  task: String(s.task),
@@ -6437,8 +6757,13 @@ var init_workflow = __esm({
6437
6757
  key: s.key ? String(s.key) : void 0
6438
6758
  }));
6439
6759
  if (ctx.depth >= MAX_AGENT_DEPTH) {
6440
- return toolFailure(`[\u5B89\u5168\u7194\u65AD]\uFF1A\u5DF2\u8FBE\u5230\u6700\u5927 Agent \u5D4C\u5957\u6DF1\u5EA6\uFF08${MAX_AGENT_DEPTH}\u5C42\uFF09\uFF0C\u65E0\u6CD5\u6D3E\u751F\u5DE5\u4F5C\u6D41\u5B50 agent\u3002\u8BF7\u5728\u672C\u5C42\u7EA7\u81EA\u884C\u6D88\u5316\u4EFB\u52A1\u3002`);
6760
+ yield toolFailure(`[\u5B89\u5168\u7194\u65AD]\uFF1A\u5DF2\u8FBE\u5230\u6700\u5927 Agent \u5D4C\u5957\u6DF1\u5EA6\uFF08${MAX_AGENT_DEPTH}\u5C42\uFF09\uFF0C\u65E0\u6CD5\u6D3E\u751F\u5DE5\u4F5C\u6D41\u5B50 agent\u3002\u8BF7\u5728\u672C\u5C42\u7EA7\u81EA\u884C\u6D88\u5316\u4EFB\u52A1\u3002`);
6761
+ return;
6441
6762
  }
6763
+ yield [
6764
+ `\u2705 \u5DE5\u4F5C\u6D41\u5DF2\u5F02\u6B65\u542F\u52A8\uFF08mode=${mode}\uFF0C\u5171 ${steps.length} \u6B65${isolation === "worktree" ? "\uFF0C\u6BCF\u6B65\u72EC\u7ACB git worktree \u9694\u79BB" : ""}\uFF09\u3002`,
6765
+ `\u5168\u90E8\u6B65\u9AA4\u540E\u53F0\u6267\u884C\u5B8C\u6BD5\u540E\uFF0C\u4E00\u6761\u7EC8\u6001\u6C47\u603B\u4F1A\u81EA\u52A8\u9001\u8FBE\u672C\u4F1A\u8BDD\u5E76\u5524\u9192\u4F60\u7EE7\u7EED\uFF08\u4E2D\u95F4\u6001\u4E0D\u56DE\u6D41\uFF09\uFF1B\u671F\u95F4\u65E0\u9700\u7B49\u5F85\u2014\u2014\u53EF\u7EE7\u7EED\u6D3E\u6D3B / \u5904\u7406\u5176\u4ED6\u4E8B\u52A1\uFF0C\u65E0\u5176\u4ED6\u4E8B\u52A1\u65F6\u7B80\u77ED\u544A\u77E5\u7528\u6237\u5373\u53EF\u6536\u5C3E\u672C\u8F6E\u3002`
6766
+ ].join("\n");
6442
6767
  const approvalMutex = createSemaphore(1);
6443
6768
  const serializedApproval = ctx.requestApproval ? async (...raArgs) => {
6444
6769
  await approvalMutex.acquire();
@@ -6521,9 +6846,25 @@ var init_workflow = __esm({
6521
6846
  outcomes = await Promise.all(running);
6522
6847
  }
6523
6848
  } catch (err) {
6524
- return toolFailure(`[\u5DE5\u4F5C\u6D41\u7F16\u6392\u5F02\u5E38]\uFF1A${err?.message ?? err}`);
6849
+ await notifySubagentDone(ctx.sessionId, [
6850
+ `\u{1F916} [\u5DE5\u4F5C\u6D41\u5B8C\u6210]\uFF08mode=${mode}\uFF0C\u5171 ${steps.length} \u6B65\uFF09`,
6851
+ `\u72B6\u6001\uFF1Aerror\uFF08\u7F16\u6392\u5F02\u5E38\uFF09`,
6852
+ `\u6C47\u603B\uFF1A`,
6853
+ `\u26A0\uFE0F [\u5DE5\u4F5C\u6D41\u7F16\u6392\u5F02\u5E38]\uFF1A${err?.message ?? err}`
6854
+ ].join("\n"));
6855
+ return;
6525
6856
  }
6526
- return formatWorkflowResult(mode, outcomes);
6857
+ const allOk = outcomes.every((o) => o.ok);
6858
+ const abortedCount = outcomes.filter((o) => !o.ok && o.output.includes("\u5DF2\u4E2D\u6B62")).length;
6859
+ const status = abortedCount === outcomes.length ? "aborted" : allOk ? "final" : "error";
6860
+ const statusLabel = status === "final" ? "final\uFF08\u5168\u90E8\u6B65\u9AA4\u6B63\u5E38\u5B8C\u6210\uFF09" : status === "aborted" ? "aborted\uFF08\u5168\u90E8\u6B65\u9AA4\u88AB\u4E2D\u6B62\uFF09" : "error\uFF08\u90E8\u5206\u6B65\u9AA4\u5931\u8D25/\u4E2D\u6B62\uFF09";
6861
+ await notifySubagentDone(ctx.sessionId, [
6862
+ `\u{1F916} [\u5DE5\u4F5C\u6D41\u5B8C\u6210]\uFF08mode=${mode}\uFF0C\u5171 ${steps.length} \u6B65\uFF09`,
6863
+ `\u72B6\u6001\uFF1A${statusLabel}`,
6864
+ `\u6C47\u603B\uFF1A`,
6865
+ // 截断口径与原同步返回一致(maxOutputCharacters 18000)
6866
+ truncateToolResult(formatWorkflowResult(mode, outcomes), 18e3)
6867
+ ].join("\n"));
6527
6868
  }
6528
6869
  }
6529
6870
  }
@@ -10797,8 +11138,9 @@ var init_frontmatter = __esm({
10797
11138
 
10798
11139
  // ../core/src/memory/loader.ts
10799
11140
  import fs28 from "fs/promises";
11141
+ import os5 from "os";
10800
11142
  import path30 from "path";
10801
- var GLOBAL_MEMORY_DIR, SOURCES, VALID_TYPES, parseMemoryAt, loadMemories, initMemories;
11143
+ var GLOBAL_MEMORY_DIR, SOURCES, VALID_TYPES, NAME_RE, parseMemoryAt, MAX_EXTERNAL_ITEMS, EXTERNAL_DESC_MAX_CHARS, EXTERNAL_BODY_MAX_BYTES, EXTERNAL_RULES, sanitizeExternalStem, extractExternalTitle, buildExternalDescription, truncateUtf8ToBytes, scanExternalRule, externalNoticeShown, loadExternalMemories, loadMemories, initMemories;
10802
11144
  var init_loader3 = __esm({
10803
11145
  "../core/src/memory/loader.ts"() {
10804
11146
  "use strict";
@@ -10812,6 +11154,7 @@ var init_loader3 = __esm({
10812
11154
  { dir: path30.join(process.cwd(), ".deepseeker-code", "memory"), source: "project" }
10813
11155
  ];
10814
11156
  VALID_TYPES = ["user", "feedback", "project", "reference"];
11157
+ NAME_RE = /^[a-z0-9-]+$/;
10815
11158
  parseMemoryAt = async (file, _dir, source) => {
10816
11159
  let raw;
10817
11160
  try {
@@ -10830,7 +11173,7 @@ var init_loader3 = __esm({
10830
11173
  console.warn(`\u26A0\uFE0F [memory] \u7F3A\u5C11 name \u6216 description\uFF08${file}\uFF09\uFF0C\u5DF2\u8DF3\u8FC7`);
10831
11174
  return null;
10832
11175
  }
10833
- if (!/^[a-z0-9-]+$/.test(name)) {
11176
+ if (!NAME_RE.test(name)) {
10834
11177
  console.warn(`\u26A0\uFE0F [memory] name "${name}" \u4E0D\u5408\u6CD5\uFF08\u4EC5\u5141\u8BB8\u5C0F\u5199\u5B57\u6BCD/\u6570\u5B57/\u8FDE\u5B57\u7B26\uFF0C${file}\uFF09\uFF0C\u5DF2\u8DF3\u8FC7`);
10835
11178
  return null;
10836
11179
  }
@@ -10845,6 +11188,128 @@ var init_loader3 = __esm({
10845
11188
  }
10846
11189
  return { name, description, type, body: parsed.body.trim(), source, file };
10847
11190
  };
11191
+ MAX_EXTERNAL_ITEMS = 12;
11192
+ EXTERNAL_DESC_MAX_CHARS = 120;
11193
+ EXTERNAL_BODY_MAX_BYTES = 64 * 1024;
11194
+ EXTERNAL_RULES = [
11195
+ // 全局层(~ 下,存在才扫,缺省静默跳过)
11196
+ { dir: path30.join(os5.homedir(), ".claude"), file: "CLAUDE.md", name: "ext-claude", projectLayer: false },
11197
+ { dir: path30.join(os5.homedir(), ".codex"), file: "AGENTS.md", name: "ext-codex", projectLayer: false },
11198
+ { dir: path30.join(os5.homedir(), ".gemini"), file: "GEMINI.md", name: "ext-gemini", projectLayer: false },
11199
+ // 项目层(<cwd> 下)
11200
+ { dir: process.cwd(), file: "CLAUDE.md", name: "ext-claude-project", projectLayer: true },
11201
+ { dir: process.cwd(), file: "AGENTS.md", name: "ext-agents", projectLayer: true },
11202
+ { dir: process.cwd(), file: "GEMINI.md", name: "ext-gemini-project", projectLayer: true },
11203
+ { dir: path30.join(process.cwd(), ".cursor", "rules"), name: (stem) => `ext-cursor-${stem}`, projectLayer: true },
11204
+ { dir: path30.join(process.cwd(), ".github"), file: "copilot-instructions.md", name: "ext-copilot", projectLayer: true }
11205
+ ];
11206
+ sanitizeExternalStem = (stem) => stem.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
11207
+ extractExternalTitle = (raw, fallbackStem) => {
11208
+ const lines = raw.split(/\r?\n/);
11209
+ for (const line of lines) {
11210
+ const m = /^#\s+(.+)$/.exec(line);
11211
+ if (m) return m[1].trim();
11212
+ }
11213
+ for (const line of lines) {
11214
+ const t = line.trim();
11215
+ if (t) return t.replace(/^#+\s*/, "") || fallbackStem;
11216
+ }
11217
+ return fallbackStem;
11218
+ };
11219
+ buildExternalDescription = (label, title) => {
11220
+ const full = `[\u5916\u90E8:${label}] ${title.replace(/\s+/g, " ").trim()}`;
11221
+ return full.length > EXTERNAL_DESC_MAX_CHARS ? full.slice(0, EXTERNAL_DESC_MAX_CHARS) : full;
11222
+ };
11223
+ truncateUtf8ToBytes = (text, maxBytes) => {
11224
+ const buf = Buffer.from(text, "utf-8");
11225
+ if (buf.length <= maxBytes) return { text, truncated: false };
11226
+ return { text: buf.subarray(0, maxBytes).toString("utf-8"), truncated: true };
11227
+ };
11228
+ scanExternalRule = async (rule) => {
11229
+ let entries;
11230
+ try {
11231
+ entries = await fs28.readdir(rule.dir, { withFileTypes: true });
11232
+ } catch {
11233
+ return [];
11234
+ }
11235
+ const out = [];
11236
+ for (const entry of entries) {
11237
+ if (!entry.isFile()) continue;
11238
+ if (rule.file ? entry.name !== rule.file : !entry.name.endsWith(".md")) continue;
11239
+ const stem = entry.name.replace(/\.md$/, "");
11240
+ const file = path30.join(rule.dir, entry.name);
11241
+ const name = typeof rule.name === "function" ? sanitizeExternalStem(stem) ? `ext-cursor-${sanitizeExternalStem(stem)}` : "" : rule.name;
11242
+ if (!name || !NAME_RE.test(name)) {
11243
+ console.warn(`\u26A0\uFE0F [memory] \u5916\u90E8\u6587\u4EF6\u540D\u4E3B\u5E72\u65E0\u6CD5\u751F\u6210\u5408\u6CD5\u8BB0\u5FC6\u540D\uFF08${file}\uFF09\uFF0C\u5DF2\u8DF3\u8FC7`);
11244
+ continue;
11245
+ }
11246
+ let raw;
11247
+ try {
11248
+ raw = await fs28.readFile(file, "utf-8");
11249
+ } catch {
11250
+ continue;
11251
+ }
11252
+ const body = truncateUtf8ToBytes(raw.trim(), EXTERNAL_BODY_MAX_BYTES);
11253
+ if (!body.text) continue;
11254
+ if (body.truncated) console.warn(`\u26A0\uFE0F [memory] \u5916\u90E8\u6587\u4EF6\u8D85 ${EXTERNAL_BODY_MAX_BYTES} \u5B57\u8282\u5DF2\u622A\u65AD\uFF08${file}\uFF09`);
11255
+ const stat = await fs28.stat(file).catch(() => null);
11256
+ out.push({
11257
+ item: {
11258
+ name,
11259
+ description: buildExternalDescription(rule.file ?? entry.name, extractExternalTitle(raw, stem)),
11260
+ type: "reference",
11261
+ // 外部二手资料的语义即参考(§4),不冒充 user/feedback 画像
11262
+ body: body.text,
11263
+ source: "external",
11264
+ file
11265
+ },
11266
+ mtimeMs: stat?.mtimeMs ?? 0
11267
+ });
11268
+ }
11269
+ return out;
11270
+ };
11271
+ externalNoticeShown = false;
11272
+ loadExternalMemories = async (includeProject) => {
11273
+ if (!appConfig.memoryExternal) return 0;
11274
+ const candidates = [];
11275
+ for (const rule of EXTERNAL_RULES) {
11276
+ if (rule.projectLayer && !includeProject) continue;
11277
+ candidates.push(...await scanExternalRule(rule));
11278
+ }
11279
+ if (candidates.length === 0) return 0;
11280
+ const dropped = [];
11281
+ if (candidates.length > MAX_EXTERNAL_ITEMS) {
11282
+ candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
11283
+ for (const c of candidates.slice(MAX_EXTERNAL_ITEMS)) dropped.push(c.item.name);
11284
+ candidates.length = MAX_EXTERNAL_ITEMS;
11285
+ }
11286
+ const skipped = [];
11287
+ for (const { item } of candidates) {
11288
+ const all = listMemories();
11289
+ if (all.length >= MEMORY_LIMITS.maxCount) {
11290
+ skipped.push(item.name);
11291
+ continue;
11292
+ }
11293
+ const curBytes = all.length === 0 ? 0 : Buffer.byteLength(all.map(memoryIndexLine).join("\n"), "utf-8");
11294
+ if (curBytes + Buffer.byteLength(memoryIndexLine(item), "utf-8") > MEMORY_LIMITS.maxIndexBytes) {
11295
+ skipped.push(item.name);
11296
+ continue;
11297
+ }
11298
+ registerMemory(item);
11299
+ }
11300
+ if (dropped.length > 0) {
11301
+ console.warn(`\u26A0\uFE0F [memory] \u5916\u90E8\u6761\u76EE\u8D85\u8F6F\u4E0A\u9650 ${MAX_EXTERNAL_ITEMS}\uFF0C\u6309 mtime \u4E22\u5F03\u6700\u65E7 ${dropped.length} \u6761\uFF1A${dropped.join("\u3001")}`);
11302
+ }
11303
+ if (skipped.length > 0) {
11304
+ console.warn(`\u26A0\uFE0F [memory] \u5916\u90E8\u6761\u76EE\u8D85\u5171\u4EAB\u9884\u7B97\uFF08MEMORY_LIMITS\uFF1A${MEMORY_LIMITS.maxCount} \u6761/${MEMORY_LIMITS.maxIndexBytes} \u5B57\u8282\uFF09\uFF0C\u62D2\u8F7D\uFF1A${skipped.join("\u3001")}`);
11305
+ }
11306
+ const n = candidates.length - skipped.length;
11307
+ if (n > 0 && !externalNoticeShown) {
11308
+ externalNoticeShown = true;
11309
+ console.log("\u{1F310} [memory] \u5916\u90E8\u8BB0\u5FC6\u5DF2\u63A5\u5165\uFF08\u53EA\u8BFB\uFF09\uFF1A\u6765\u6E90\u6587\u4EF6\u5185\u5BB9\u5C06\u968F\u8BF7\u6C42\u53D1\u9001\u7ED9 DeepSeek API\u3002\u5173\u95ED\uFF1Asettings.json engine.memoryExternal=false");
11310
+ }
11311
+ return n;
11312
+ };
10848
11313
  loadMemories = async (includeProject) => {
10849
11314
  clearMemories();
10850
11315
  const picked = await scanSources(
@@ -10853,6 +11318,7 @@ var init_loader3 = __esm({
10853
11318
  parseMemoryAt
10854
11319
  );
10855
11320
  for (const { item } of picked) registerMemory(item);
11321
+ await loadExternalMemories(includeProject);
10856
11322
  return listMemories().length;
10857
11323
  };
10858
11324
  initMemories = async (includeProject) => {
@@ -10900,7 +11366,7 @@ var init_usage = __esm({
10900
11366
  // ../core/src/tool/registry/memory.ts
10901
11367
  import fs30 from "fs/promises";
10902
11368
  import path32 from "path";
10903
- var MAX_BODY_BYTES, MAX_DESCRIPTION_CHARS, MEMORY_LIMITS, NAME_RE, VALID_TYPES2, memoryFilePath, sanitizeLine, buildMemoryMd, writeAndRegister, indexLineBytes, memoryTools;
11369
+ var MAX_BODY_BYTES, MAX_DESCRIPTION_CHARS, NAME_RE2, VALID_TYPES2, memoryFilePath, sanitizeLine, buildMemoryMd, writeAndRegister, indexLineBytes, memoryTools;
10904
11370
  var init_memory = __esm({
10905
11371
  "../core/src/tool/registry/memory.ts"() {
10906
11372
  "use strict";
@@ -10911,8 +11377,7 @@ var init_memory = __esm({
10911
11377
  init_usage();
10912
11378
  MAX_BODY_BYTES = 64 * 1024;
10913
11379
  MAX_DESCRIPTION_CHARS = 200;
10914
- MEMORY_LIMITS = { maxCount: 200, maxIndexBytes: 16 * 1024 };
10915
- NAME_RE = /^[a-z0-9-]+$/;
11380
+ NAME_RE2 = /^[a-z0-9-]+$/;
10916
11381
  VALID_TYPES2 = ["user", "feedback", "project", "reference"];
10917
11382
  memoryFilePath = (name, project) => project ? path32.join(process.cwd(), ".deepseeker-code", "memory", `${name}.md`) : path32.join(GLOBAL_MEMORY_DIR, `${name}.md`);
10918
11383
  sanitizeLine = (s) => String(s ?? "").replace(/[\r\n]+/g, " ").trim();
@@ -10975,7 +11440,8 @@ ${body.trim()}
10975
11440
  const body = typeof args?.body === "string" ? args.body : "";
10976
11441
  const type = sanitizeLine(args?.type) || "reference";
10977
11442
  const project = args?.project === true;
10978
- if (!NAME_RE.test(name)) return toolFailure(`[memory_save] name \u975E\u6CD5\uFF1A\u4EC5\u5141\u8BB8\u5C0F\u5199\u5B57\u6BCD/\u6570\u5B57/\u8FDE\u5B57\u7B26\uFF0C\u6536\u5230 "${name}"\u3002`);
11443
+ if (!NAME_RE2.test(name)) return toolFailure(`[memory_save] name \u975E\u6CD5\uFF1A\u4EC5\u5141\u8BB8\u5C0F\u5199\u5B57\u6BCD/\u6570\u5B57/\u8FDE\u5B57\u7B26\uFF0C\u6536\u5230 "${name}"\u3002`);
11444
+ if (name.startsWith("ext-")) return toolFailure(`[memory_save] "ext-" \u540D\u79F0\u524D\u7F00\u4E3A\u5916\u90E8\u8BB0\u5FC6\u4FDD\u7559\u5B57\uFF08\u8BE5\u524D\u7F00\u6761\u76EE\u7531 loader \u4ECE\u5176\u4ED6 agent \u7684\u89C4\u5219/\u6307\u4EE4\u6587\u4EF6\u53EA\u8BFB\u6865\u63A5\uFF0C\u540D\u79F0\u56FA\u5B9A\u5206\u914D\uFF09\u3002\u8BF7\u6539\u7528\u5176\u4ED6\u540D\u79F0\u3002`);
10979
11445
  if (!description) return toolFailure("[memory_save] description \u4E0D\u80FD\u4E3A\u7A7A\u3002");
10980
11446
  if (description.length > MAX_DESCRIPTION_CHARS) return toolFailure(`[memory_save] description \u8D85\u8FC7 ${MAX_DESCRIPTION_CHARS} \u5B57\u7B26\u4E0A\u9650\uFF08\u5F53\u524D ${description.length}\uFF09\u3002\u8BF7\u538B\u7F29\u4E3A\u4E00\u53E5\u8BDD\u3002`);
10981
11447
  if (!body.trim()) return toolFailure("[memory_save] body \u4E0D\u80FD\u4E3A\u7A7A\u3002");
@@ -11002,7 +11468,7 @@ ${body.trim()}
11002
11468
  type: "function",
11003
11469
  function: {
11004
11470
  name: "memory_read",
11005
- description: "\u8BFB\u53D6\u6307\u5B9A\u8BB0\u5FC6\u7684\u5B8C\u6574\u6B63\u6587\u3002\u7CFB\u7EDF\u63D0\u793A\u8BCD\u4EC5\u6CE8\u5165\u8BB0\u5FC6\u7D22\u5F15\uFF08\u4E00\u884C/\u6761\uFF0C\u7701 token\uFF09\uFF1B\u5F53\u7D22\u5F15\u4E2D\u67D0\u6761\u4E0E\u5F53\u524D\u4EFB\u52A1\u76F8\u5173\u3001\u9700\u8981\u5168\u6587\u7EC6\u8282\u65F6\uFF0C\u8C03\u7528\u672C\u5DE5\u5177\u6309\u9700\u53EC\u56DE\u3002",
11471
+ description: "\u8BFB\u53D6\u6307\u5B9A\u8BB0\u5FC6\u7684\u5B8C\u6574\u6B63\u6587\u3002\u7CFB\u7EDF\u63D0\u793A\u8BCD\u4EC5\u6CE8\u5165\u8BB0\u5FC6\u7D22\u5F15\uFF08\u4E00\u884C/\u6761\uFF0C\u7701 token\uFF09\uFF1B\u5F53\u7D22\u5F15\u4E2D\u67D0\u6761\u4E0E\u5F53\u524D\u4EFB\u52A1\u76F8\u5173\u3001\u9700\u8981\u5168\u6587\u7EC6\u8282\u65F6\uFF0C\u8C03\u7528\u672C\u5DE5\u5177\u6309\u9700\u53EC\u56DE\u3002\u7D22\u5F15\u4E2D\u6807 [\u5916\u90E8:*] \u7684\u6761\u76EE\u662F\u5176\u4ED6 agent \u89C4\u5219\u6587\u4EF6\u7684\u4E8C\u624B\u6458\u5F55\uFF08type=reference\uFF09\uFF1A\u4E0E\u73B0\u573A\u5B9E\u9645\u4E0D\u7B26\u65F6\u4EE5\u73B0\u573A\u4E3A\u51C6\uFF0C\u5E76\u53EF memory_save \u4E00\u6761\u81EA\u6709\u8BB0\u5FC6\u7EA0\u504F\u3002",
11006
11472
  parameters: {
11007
11473
  type: "object",
11008
11474
  properties: {
@@ -11061,6 +11527,7 @@ ${lines.join("\n")}`;
11061
11527
  requireApproval: (args) => `\u5220\u9664\u6301\u4E45\u8BB0\u5FC6 **${sanitizeLine(args?.name)}**\uFF08\u8DE8\u4F1A\u8BDD\u751F\u6548\uFF0C\u5220\u9664\u540E\u4E0D\u53EF\u6062\u590D\uFF09`,
11062
11528
  async execute(args) {
11063
11529
  const name = sanitizeLine(args?.name);
11530
+ if (name.startsWith("ext-")) return toolFailure(`[memory_delete] \u5916\u90E8\u6761\u76EE\u53EA\u8BFB\uFF0C\u8BF7\u5230\u6765\u6E90\u6587\u4EF6\u5904\u4FEE\u6539\uFF08ext- \u524D\u7F00\u6761\u76EE\u7531 loader \u4ECE\u5176\u4ED6 agent \u7684\u89C4\u5219/\u6307\u4EE4\u6587\u4EF6\u53EA\u8BFB\u6865\u63A5\uFF0C\u5220\u9664/\u4FEE\u6539\u8BF7\u76F4\u63A5\u7F16\u8F91\u6765\u6E90\u6587\u4EF6\u540E\u91CD\u542F\u8FDB\u7A0B\uFF09\u3002`);
11064
11531
  const m = getMemory(name);
11065
11532
  if (!m) return toolFailure(`\u672A\u627E\u5230\u8BB0\u5FC6\uFF1A${name}\uFF08\u53EF\u80FD\u5DF2\u88AB\u5220\u9664\uFF09\u3002`);
11066
11533
  try {
@@ -12189,7 +12656,7 @@ var init_toolExecution = __esm({
12189
12656
  const isForegroundCommand = isCommandTool && matchedTool.function.isSync !== false;
12190
12657
  const cmdIsScriptRunner = isCommandTool && isScriptRunnerCommand(typeof state.calledArgs?.command === "string" ? state.calledArgs.command : "");
12191
12658
  const argCwd = typeof state.calledArgs?.cwd === "string" ? state.calledArgs.cwd.trim() : "";
12192
- const samePath = (a, b) => {
12659
+ const samePath2 = (a, b) => {
12193
12660
  try {
12194
12661
  const ra = path36.resolve(a), rb = path36.resolve(b);
12195
12662
  return process.platform === "win32" ? ra.toLowerCase() === rb.toLowerCase() : ra === rb;
@@ -12197,7 +12664,7 @@ var init_toolExecution = __esm({
12197
12664
  return false;
12198
12665
  }
12199
12666
  };
12200
- const scriptRunnerCwdDrift = cmdIsScriptRunner && argCwd !== "" && !samePath(argCwd, toolCtx.cwd ?? process.cwd());
12667
+ const scriptRunnerCwdDrift = cmdIsScriptRunner && argCwd !== "" && !samePath2(argCwd, toolCtx.cwd ?? process.cwd());
12201
12668
  const scriptRunnerBlocked = cmdIsScriptRunner && (perm === null || scriptRunnerCwdDrift);
12202
12669
  if (scriptRunnerCwdDrift) needApproval = true;
12203
12670
  if (perm === null && needApproval && !state.denied && !scriptRunnerBlocked && isForegroundCommand && isReadOnlyCommand(typeof state.calledArgs?.command === "string" ? state.calledArgs.command : "")) {
@@ -12485,13 +12952,21 @@ var init_toolScheduling = __esm({
12485
12952
  scheduleToolCalls = async function* (assistantMessage, message, ctx) {
12486
12953
  const { sessionId, signal, rawTools } = ctx;
12487
12954
  let abortedDuringTools = false;
12955
+ const degraded = [];
12956
+ const markDegraded = (name) => {
12957
+ if (!degraded.includes(name)) degraded.push(name);
12958
+ };
12959
+ const noteWrite = (ok, name) => {
12960
+ if (!ok) markDegraded(name);
12961
+ };
12962
+ const degradedField = () => degraded.length > 0 ? { degraded: [...degraded] } : {};
12488
12963
  const pendingImages = [];
12489
12964
  const fillRestPlaceholders = async (fromIdx) => {
12490
12965
  const tcs2 = assistantMessage.tool_calls;
12491
12966
  for (let j = fromIdx + 1; j < tcs2.length; j++) {
12492
12967
  const ph = "\uFF08\u5DF2\u8DF3\u8FC7\uFF1A\u7EC8\u7ED3\u7C7B\u5DE5\u5177\u4E4B\u540E\u7684\u5E76\u884C\u8C03\u7528\u672A\u6267\u884C\uFF09";
12493
12968
  message.push({ role: "tool", tool_call_id: tcs2[j].id, content: ph });
12494
- await appendMessage({ sessionId, role: "tool", tool_call_id: tcs2[j].id, content: ph });
12969
+ noteWrite(await appendMessage({ sessionId, role: "tool", tool_call_id: tcs2[j].id, content: ph }), "appendMessage(placeholder)");
12495
12970
  }
12496
12971
  };
12497
12972
  const parallelSafeToolsEnabled = appConfig.parallelSafeTools;
@@ -12530,7 +13005,7 @@ var init_toolScheduling = __esm({
12530
13005
  for (let j = idx; j < tcs.length; j++) {
12531
13006
  const ph = "\uFF08\u5DF2\u4E2D\u6B62\uFF0C\u672A\u6267\u884C\uFF09";
12532
13007
  message.push({ role: "tool", tool_call_id: tcs[j].id, content: ph });
12533
- await appendMessage({ sessionId, role: "tool", tool_call_id: tcs[j].id, content: ph });
13008
+ noteWrite(await appendMessage({ sessionId, role: "tool", tool_call_id: tcs[j].id, content: ph }), "appendMessage(placeholder)");
12534
13009
  }
12535
13010
  break;
12536
13011
  }
@@ -12576,7 +13051,8 @@ var init_toolScheduling = __esm({
12576
13051
  if (oc.imageAttachments?.length) pendingImages.push(...oc.imageAttachments);
12577
13052
  if (oc.aborted) abortedDuringTools = true;
12578
13053
  }
12579
- await appendMessages(sessionId, batchEntries);
13054
+ const batchWrite = await appendMessages(sessionId, batchEntries);
13055
+ if (batchWrite.failed > 0) markDegraded("appendMessages(tool-results)");
12580
13056
  if (budget) {
12581
13057
  try {
12582
13058
  const tokensAfter = estimateTokens(message);
@@ -12610,22 +13086,22 @@ var init_toolScheduling = __esm({
12610
13086
  const oc = await processToolCall(tc, ctx);
12611
13087
  if (oc.terminal) {
12612
13088
  message.push({ role: "tool", tool_call_id: oc.toolCallId, content: oc.resultForModel });
12613
- await appendMessage({ sessionId, role: "tool", tool_call_id: oc.toolCallId, content: oc.resultForModel });
13089
+ noteWrite(await appendMessage({ sessionId, role: "tool", tool_call_id: oc.toolCallId, content: oc.resultForModel }), "appendMessage(tool-result)");
12614
13090
  await fillRestPlaceholders(idx);
12615
13091
  yield { type: "tool.end", toolCallId: oc.toolCallId, toolName: oc.calledName, result: oc.resultForUser, ok: oc.ok };
12616
13092
  if (oc.terminal.kind === "exit_plan_mode") {
12617
13093
  yield { type: "plan.proposed", plan: oc.terminal.plan || "" };
12618
- return { kind: "terminal", terminalText: oc.terminal.plan || oc.resultForUser };
13094
+ return { kind: "terminal", terminalText: oc.terminal.plan || oc.resultForUser, ...degradedField() };
12619
13095
  } else if (oc.terminal.kind === "enter_plan_mode") {
12620
13096
  yield { type: "plan.enterRequested", reason: oc.terminal.reason || "" };
12621
- 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 };
13097
+ 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, ...degradedField() };
12622
13098
  } else {
12623
- return { kind: "terminal", terminalText: oc.resultForUser };
13099
+ return { kind: "terminal", terminalText: oc.resultForUser, ...degradedField() };
12624
13100
  }
12625
13101
  }
12626
13102
  yield { type: "tool.end", toolCallId: oc.toolCallId, toolName: oc.calledName, result: oc.resultForUser, ok: oc.ok };
12627
13103
  message.push({ role: "tool", tool_call_id: oc.toolCallId, content: oc.resultForModel });
12628
- await appendMessage({ sessionId, role: "tool", tool_call_id: oc.toolCallId, content: oc.resultForModel });
13104
+ noteWrite(await appendMessage({ sessionId, role: "tool", tool_call_id: oc.toolCallId, content: oc.resultForModel }), "appendMessage(tool-result)");
12629
13105
  if (oc.imageAttachments?.length) pendingImages.push(...oc.imageAttachments);
12630
13106
  if (oc.aborted) abortedDuringTools = true;
12631
13107
  idx++;
@@ -12634,17 +13110,17 @@ var init_toolScheduling = __esm({
12634
13110
  if (!abortedDuringTools && pendingImages.length > 0) {
12635
13111
  const parts = buildImageFollowUpParts(pendingImages);
12636
13112
  message.push({ role: "user", content: parts });
12637
- await appendMessage({ sessionId, role: "user", content: parts });
13113
+ noteWrite(await appendMessage({ sessionId, role: "user", content: parts }), "appendMessage(image-followup)");
12638
13114
  }
12639
- if (abortedDuringTools) return { kind: "aborted" };
12640
- return { kind: "completed" };
13115
+ if (abortedDuringTools) return { kind: "aborted", ...degradedField() };
13116
+ return { kind: "completed", ...degradedField() };
12641
13117
  };
12642
13118
  }
12643
13119
  });
12644
13120
 
12645
13121
  // ../core/src/agent/streamInference.ts
12646
13122
  import { createHash as createHash2 } from "node:crypto";
12647
- var sha1short, toolsFingerprintCache, RETRY_BACKOFF_CAP_MS, extractRetryAfterMs, withNudgeTail, streamInference;
13123
+ var sha1short, toolsFingerprintCache, buildPrefixFingerprint, RETRY_BACKOFF_CAP_MS, extractRetryAfterMs, withNudgeTail, streamInference;
12648
13124
  var init_streamInference = __esm({
12649
13125
  "../core/src/agent/streamInference.ts"() {
12650
13126
  "use strict";
@@ -12657,6 +13133,19 @@ var init_streamInference = __esm({
12657
13133
  init_createModel();
12658
13134
  sha1short = (s) => createHash2("sha1").update(s).digest("hex").slice(0, 10);
12659
13135
  toolsFingerprintCache = /* @__PURE__ */ new WeakMap();
13136
+ buildPrefixFingerprint = (message, cleanedToolSchemas) => ({
13137
+ toolsHash: (() => {
13138
+ let h = toolsFingerprintCache.get(cleanedToolSchemas);
13139
+ if (h === void 0) {
13140
+ h = sha1short(JSON.stringify(cleanedToolSchemas));
13141
+ toolsFingerprintCache.set(cleanedToolSchemas, h);
13142
+ }
13143
+ return h;
13144
+ })(),
13145
+ sysHash: sha1short(String(message[0]?.content ?? "")),
13146
+ sumHash: sha1short(String(message[1]?.content ?? "")),
13147
+ msgCount: message.length
13148
+ });
12660
13149
  RETRY_BACKOFF_CAP_MS = 3e4;
12661
13150
  extractRetryAfterMs = (e) => {
12662
13151
  try {
@@ -12700,28 +13189,32 @@ var init_streamInference = __esm({
12700
13189
  keepRecentUnits,
12701
13190
  compactRatio,
12702
13191
  modelWindow,
12703
- toolsTokens = 0
13192
+ toolsTokens = 0,
13193
+ windowCheck
12704
13194
  } = ctx;
12705
13195
  let inferenceMessages = withNudgeTail(message, nudgeMsg);
12706
13196
  let assistantMessage = { role: "assistant", content: null };
12707
13197
  try {
12708
- const prefixFingerprint = {
12709
- toolsHash: (() => {
12710
- let h = toolsFingerprintCache.get(cleanedToolSchemas);
12711
- if (h === void 0) {
12712
- h = sha1short(JSON.stringify(cleanedToolSchemas));
12713
- toolsFingerprintCache.set(cleanedToolSchemas, h);
12714
- }
12715
- return h;
12716
- })(),
12717
- sysHash: sha1short(String(message[0]?.content ?? "")),
12718
- sumHash: sha1short(String(message[1]?.content ?? "")),
12719
- msgCount: message.length
12720
- };
13198
+ const prefixFingerprint = buildPrefixFingerprint(message, cleanedToolSchemas);
13199
+ const wc = windowCheck;
12721
13200
  events({
12722
13201
  sessionId,
12723
13202
  eventType: "llm.request",
12724
- metadata: { depth, decisionSource: userDecisionSource, ok: true, durationMs: performance.now() - startTime, round, model, ...prefixFingerprint },
13203
+ metadata: {
13204
+ depth,
13205
+ decisionSource: userDecisionSource,
13206
+ ok: true,
13207
+ durationMs: performance.now() - startTime,
13208
+ round,
13209
+ model,
13210
+ ...prefixFingerprint,
13211
+ ...wc ? {
13212
+ estReal: wc.estReal,
13213
+ thresholdAtSend: wc.threshold,
13214
+ correctionRatioUsed: wc.correctionRatio,
13215
+ windowDecision: wc.decision
13216
+ } : {}
13217
+ },
12725
13218
  // ★ P2 口径:估算加上工具 schema 常数项(与 API 真实 prompt_tokens 同口径,trace 里 est/real 才可比)
12726
13219
  usage: { prompt_tokens: estimateTokens(inferenceMessages) + toolsTokens },
12727
13220
  payload: { input: msgText(message[message.length - 1].content) }
@@ -12769,6 +13262,16 @@ var init_streamInference = __esm({
12769
13262
  break;
12770
13263
  }
12771
13264
  }
13265
+ if (!signal?.aborted && !contentBuf && toolCallsBuf.size === 0 && !lastUsage && idleRetries < MAX_STREAM_RETRIES) {
13266
+ idleRetries++;
13267
+ if (reasoningBuf) yield { type: "text.reset" };
13268
+ console.warn(`\u26A0\uFE0F \u6D41\u7A7A\u7ED3\u675F\uFF080 \u5185\u5BB9 0 usage\uFF0C\u7591\u4F3C\u670D\u52A1\u7AEF\u65AD\u6D41\uFF09\uFF0C\u7B2C ${idleRetries}/${MAX_STREAM_RETRIES} \u6B21\u91CD\u8BD5...`);
13269
+ contentBuf = "";
13270
+ reasoningBuf = "";
13271
+ toolCallsBuf.clear();
13272
+ lastUsage = void 0;
13273
+ continue;
13274
+ }
12772
13275
  break;
12773
13276
  } catch (streamErr) {
12774
13277
  if (signal?.aborted) throw streamErr;
@@ -12838,6 +13341,19 @@ var init_streamInference = __esm({
12838
13341
  onUIEvent: ctx.onUIEvent,
12839
13342
  onAuxUsage: ctx.onAuxUsage
12840
13343
  });
13344
+ events({
13345
+ sessionId,
13346
+ eventType: "llm.contextDowngraded",
13347
+ metadata: {
13348
+ depth,
13349
+ decisionSource: llmDecisionSource,
13350
+ ok: true,
13351
+ round,
13352
+ model,
13353
+ estReal: estimateTokens(withNudgeTail(message, nudgeMsg)) + (toolsTokens ?? 0)
13354
+ },
13355
+ payload: { output: streamErr instanceof Error ? streamErr.message : String(streamErr) }
13356
+ });
12841
13357
  inferenceMessages = withNudgeTail(message, nudgeMsg);
12842
13358
  compactedThisRound = true;
12843
13359
  contentBuf = "";
@@ -12885,7 +13401,17 @@ var init_streamInference = __esm({
12885
13401
  events({
12886
13402
  sessionId,
12887
13403
  eventType: "llm.response",
12888
- metadata: { depth, decisionSource: llmDecisionSource, ok: true, durationMs: performance.now() - startTime, round, model },
13404
+ // ★ emptyStream 标记:0 内容 0 usage 的退化完成(重试耗尽仍空)——修复前该形态记 ok:true
13405
+ // 无任何异常痕迹(0d245bd0 取证时 trace 里完全看不出 round 13 是空流),此处如实回报。
13406
+ metadata: {
13407
+ depth,
13408
+ decisionSource: llmDecisionSource,
13409
+ ok: true,
13410
+ durationMs: performance.now() - startTime,
13411
+ round,
13412
+ model,
13413
+ ...!contentBuf && toolCallsBuf.size === 0 && !lastUsage ? { emptyStream: true } : {}
13414
+ },
12889
13415
  usage: {
12890
13416
  prompt_tokens: lastUsage?.prompt_tokens,
12891
13417
  completion_tokens: lastUsage?.completion_tokens,
@@ -13186,7 +13712,7 @@ var init_prices = __esm({
13186
13712
  peak: { input: 9, output: 27, cacheHit: 0.3 }
13187
13713
  }
13188
13714
  };
13189
- PRICES_VERIFIED_AT = "2026-10-19";
13715
+ PRICES_VERIFIED_AT = "2026-11-03";
13190
13716
  PRICES_STALE_DAYS = 30;
13191
13717
  pricesAgeDays = () => Math.max(0, Math.floor((Date.now() - Date.parse(`${PRICES_VERIFIED_AT}T00:00:00Z`)) / 864e5));
13192
13718
  isBeijingPeak = (ts2 = /* @__PURE__ */ new Date()) => {
@@ -13364,7 +13890,7 @@ var init_usageLog = __esm({
13364
13890
  ensuredDir = null;
13365
13891
  getUsageLogPath = (month) => path37.join(usageDir(), `usage-${month}.jsonl`);
13366
13892
  recordUsageRun = async (rec) => {
13367
- if (USAGE_DISABLED) return;
13893
+ if (USAGE_DISABLED) return true;
13368
13894
  try {
13369
13895
  const full = { workspace: appConfig.userWorkspaceDir, ...rec };
13370
13896
  const month = (full.ts || (/* @__PURE__ */ new Date()).toISOString()).slice(0, 7);
@@ -13374,8 +13900,10 @@ var init_usageLog = __esm({
13374
13900
  ensuredDir = dir;
13375
13901
  }
13376
13902
  await fs34.appendFile(getUsageLogPath(month), JSON.stringify(full) + "\n", "utf-8");
13903
+ return true;
13377
13904
  } catch (e) {
13378
13905
  console.warn("\u26A0\uFE0F [usageLog] \u4F7F\u7528\u65E5\u5FD7\u843D\u76D8\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u4E3B\u4E1A\u52A1\uFF09:", e instanceof Error ? e.message : e);
13906
+ return false;
13379
13907
  }
13380
13908
  };
13381
13909
  USAGE_FILE_RE = /^usage-\d{4}-\d{2}\.jsonl$/;
@@ -13653,7 +14181,7 @@ async function evictOldestForSize2(targetBytes) {
13653
14181
  }
13654
14182
  return deleted;
13655
14183
  }
13656
- var CLEANUP_INTERVAL_MS2, ACTIVE_GRACE_MS2, MAX_TRACE_FOLDER_BYTES, SIZE_RECLAIM_TARGET_RATIO2, SIZE_CLEANUP_COOLDOWN_MS2, workspaceTraceBytes, diskLastCleanupAt2, clockLoaded2, lastSizeCleanupAt2, cleanupInProgress2, TRACE_BATCH_LINES, TRACE_BATCH_MS, traceBuf, traceTimers, cleanupTriggerCount, needImmediateFlush, scheduleTraceTimer, flushTraceBuffer, ensureClockLoaded2, tracePayloadMaxChars, truncateTracePayload;
14184
+ var CLEANUP_INTERVAL_MS2, ACTIVE_GRACE_MS2, MAX_TRACE_FOLDER_BYTES, SIZE_RECLAIM_TARGET_RATIO2, SIZE_CLEANUP_COOLDOWN_MS2, workspaceTraceBytes, diskLastCleanupAt2, clockLoaded2, lastSizeCleanupAt2, cleanupInProgress2, TRACE_BATCH_LINES, TRACE_BATCH_MS, traceBuf, droppedBatches, traceTimers, cleanupTriggerCount, needImmediateFlush, scheduleTraceTimer, flushChains, runSerialized, flushTraceBuffer, ensureClockLoaded2, tracePayloadMaxChars, truncateTracePayload;
13657
14185
  var init_trace = __esm({
13658
14186
  "../core/src/observability/trace.ts"() {
13659
14187
  "use strict";
@@ -13673,6 +14201,7 @@ var init_trace = __esm({
13673
14201
  TRACE_BATCH_LINES = 50;
13674
14202
  TRACE_BATCH_MS = 200;
13675
14203
  traceBuf = /* @__PURE__ */ new Map();
14204
+ droppedBatches = /* @__PURE__ */ new Map();
13676
14205
  traceTimers = /* @__PURE__ */ new Map();
13677
14206
  cleanupTriggerCount = 0;
13678
14207
  needImmediateFlush = (ev) => ev.metadata?.ok === false || ev.eventType === "user.aborted" || ev.eventType === "tool.repeat_break" || ev.eventType === "session.end";
@@ -13685,34 +14214,55 @@ var init_trace = __esm({
13685
14214
  t.unref?.();
13686
14215
  traceTimers.set(sessionId, t);
13687
14216
  };
14217
+ flushChains = /* @__PURE__ */ new Map();
14218
+ runSerialized = (id, fn) => {
14219
+ const prev = flushChains.get(id) ?? Promise.resolve();
14220
+ const next = prev.then(fn, fn);
14221
+ flushChains.set(id, next.catch(() => void 0));
14222
+ return next;
14223
+ };
13688
14224
  flushTraceBuffer = async (sessionId) => {
13689
- const ids = sessionId !== void 0 ? [sessionId] : [...traceBuf.keys()];
14225
+ const reportSticky = sessionId === void 0;
14226
+ const ids = sessionId !== void 0 ? [sessionId] : [.../* @__PURE__ */ new Set([...traceBuf.keys(), ...droppedBatches.keys()])];
14227
+ let failedSessions = 0;
13690
14228
  for (const id of ids) {
13691
- const arr = traceBuf.get(id);
13692
- if (!arr || arr.length === 0) {
13693
- traceBuf.delete(id);
13694
- continue;
13695
- }
13696
- traceBuf.set(id, []);
13697
- const timer = traceTimers.get(id);
13698
- if (timer) {
13699
- clearTimeout(timer);
13700
- traceTimers.delete(id);
13701
- }
13702
- try {
13703
- const joined = arr.join("");
13704
- const p = await getTraceStorePath(id);
13705
- await fs36.appendFile(p, joined, "utf-8");
13706
- if (workspaceTraceBytes < 0) {
13707
- workspaceTraceBytes = await getDirBytes(getTracePath());
14229
+ const degraded = await runSerialized(id, async () => {
14230
+ let bad = false;
14231
+ if (reportSticky && droppedBatches.has(id)) {
14232
+ droppedBatches.delete(id);
14233
+ bad = true;
13708
14234
  }
13709
- workspaceTraceBytes += Buffer.byteLength(joined, "utf-8");
13710
- cleanupTriggerCount++;
13711
- void maybeCleanupAfterAppend(id);
13712
- } catch (error) {
13713
- console.warn("\u26A0\uFE0F [Trace] \u6279\u91CF\u843D\u76D8\u5931\u8D25\uFF0C\u4E22\u5F03\u8BE5\u6279\uFF08\u4E0D\u5F71\u54CD\u4E3B\u4E1A\u52A1\uFF09:", error);
13714
- }
14235
+ const arr = traceBuf.get(id);
14236
+ if (!arr || arr.length === 0) {
14237
+ traceBuf.delete(id);
14238
+ return bad;
14239
+ }
14240
+ traceBuf.set(id, []);
14241
+ const timer = traceTimers.get(id);
14242
+ if (timer) {
14243
+ clearTimeout(timer);
14244
+ traceTimers.delete(id);
14245
+ }
14246
+ try {
14247
+ const joined = arr.join("");
14248
+ const p = await getTraceStorePath(id);
14249
+ await fs36.appendFile(p, joined, "utf-8");
14250
+ if (workspaceTraceBytes < 0) {
14251
+ workspaceTraceBytes = await getDirBytes(getTracePath());
14252
+ }
14253
+ workspaceTraceBytes += Buffer.byteLength(joined, "utf-8");
14254
+ cleanupTriggerCount++;
14255
+ void maybeCleanupAfterAppend(id);
14256
+ } catch (error) {
14257
+ bad = true;
14258
+ if (!reportSticky) droppedBatches.set(id, (droppedBatches.get(id) ?? 0) + 1);
14259
+ console.warn("\u26A0\uFE0F [Trace] \u6279\u91CF\u843D\u76D8\u5931\u8D25\uFF0C\u4E22\u5F03\u8BE5\u6279\uFF08\u4E0D\u5F71\u54CD\u4E3B\u4E1A\u52A1\uFF09:", error);
14260
+ }
14261
+ return bad;
14262
+ });
14263
+ if (degraded) failedSessions++;
13715
14264
  }
14265
+ return failedSessions;
13716
14266
  };
13717
14267
  ensureClockLoaded2 = async () => {
13718
14268
  if (clockLoaded2) return;
@@ -13799,7 +14349,16 @@ async function* runAgent(message, options) {
13799
14349
  const inheritedBudgetCheck = options.budgetCheck ?? (depth === 0 ? ledger.overHardLimit : void 0);
13800
14350
  let planWriteRej = 0;
13801
14351
  const toolCounts = {};
13802
- await appendEvent(sessionId, { dscEvent: "run.start", runId, depth });
14352
+ const degraded = [];
14353
+ const markDegraded = (name) => {
14354
+ if (!degraded.includes(name)) degraded.push(name);
14355
+ };
14356
+ try {
14357
+ const ok = await appendEvent(sessionId, { dscEvent: "run.start", runId, depth });
14358
+ if (!ok) markDegraded("appendEvent(run.start)");
14359
+ } catch {
14360
+ markDegraded("appendEvent(run.start)");
14361
+ }
13803
14362
  try {
13804
14363
  while (true) {
13805
14364
  round++;
@@ -13843,13 +14402,20 @@ async function* runAgent(message, options) {
13843
14402
  }
13844
14403
  const inboxTexts = claimSessionInbox(sessionId);
13845
14404
  if (inboxTexts.length > 0) {
14405
+ clearQueuedWake(sessionId);
13846
14406
  for (const t of inboxTexts) {
13847
14407
  message.push({ role: "user", content: t });
13848
- await appendMessage({ sessionId, role: "user", content: t });
14408
+ try {
14409
+ const ok = await appendMessage({ sessionId, role: "user", content: t });
14410
+ if (!ok) markDegraded("appendMessage(user)");
14411
+ } catch {
14412
+ markDegraded("appendMessage(user)");
14413
+ }
13849
14414
  }
13850
14415
  yield { type: "inbox.claimed", texts: inboxTexts };
13851
14416
  }
13852
14417
  yield { type: "round.start", round };
14418
+ let lastWindowCheck;
13853
14419
  try {
13854
14420
  await ensureFitsWindow(
13855
14421
  {
@@ -13871,6 +14437,10 @@ async function* runAgent(message, options) {
13871
14437
  onUIEvent: (evt) => {
13872
14438
  if (evt.type === "compact.done") runCompactCount++;
13873
14439
  options.onUIEvent?.(evt);
14440
+ },
14441
+ // ★ M1:判定快照出口(含 noop;抛异常路径不回调——那轮推理本就不发生)
14442
+ onWindowCheck: (check) => {
14443
+ lastWindowCheck = check;
13874
14444
  }
13875
14445
  }
13876
14446
  );
@@ -13906,7 +14476,8 @@ async function* runAgent(message, options) {
13906
14476
  modelWindow,
13907
14477
  toolsTokens,
13908
14478
  onUIEvent: options.onUIEvent,
13909
- onAuxUsage: reportAuxUsage
14479
+ onAuxUsage: reportAuxUsage,
14480
+ windowCheck: lastWindowCheck
13910
14481
  });
13911
14482
  if (infResult.kind === "aborted") {
13912
14483
  yield { type: "final", text: infResult.partialText || lastContent || "\uFF08\u5DF2\u4E2D\u6B62\uFF09" };
@@ -13928,13 +14499,28 @@ async function* runAgent(message, options) {
13928
14499
  }
13929
14500
  ledger.addUsage(infResult.usage);
13930
14501
  const assistantMessage = infResult.assistantMessage;
13931
- message.push(assistantMessage);
13932
- await appendMessage({ ...assistantMessage, sessionId });
13933
- if (typeof assistantMessage.content === "string" && assistantMessage.content) {
13934
- lastContent = assistantMessage.content;
14502
+ const emptyAssistant = !(typeof assistantMessage.content === "string" && assistantMessage.content.length > 0) && !(Array.isArray(assistantMessage.tool_calls) && assistantMessage.tool_calls.length > 0);
14503
+ if (emptyAssistant) {
14504
+ markDegraded("emptyAssistantSkip");
14505
+ } else {
14506
+ message.push(assistantMessage);
14507
+ try {
14508
+ const ok = await appendMessage({ ...assistantMessage, sessionId });
14509
+ if (!ok) markDegraded("appendMessage(assistant)");
14510
+ } catch {
14511
+ markDegraded("appendMessage(assistant)");
14512
+ }
14513
+ if (typeof assistantMessage.content === "string" && assistantMessage.content) {
14514
+ lastContent = assistantMessage.content;
14515
+ }
13935
14516
  }
13936
14517
  if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
13937
- await appendEvent(sessionId, { dscEvent: "round.end", runId, round, usage: infResult.usage });
14518
+ try {
14519
+ const ok = await appendEvent(sessionId, { dscEvent: "round.end", runId, round, usage: infResult.usage });
14520
+ if (!ok) markDegraded("appendEvent(round.end)");
14521
+ } catch {
14522
+ markDegraded("appendEvent(round.end)");
14523
+ }
13938
14524
  emitContextUsage();
13939
14525
  ledger.flushRelay();
13940
14526
  const finalText = (typeof assistantMessage.content === "string" ? assistantMessage.content : "") || "";
@@ -13955,7 +14541,12 @@ async function* runAgent(message, options) {
13955
14541
  const ph = "\uFF08\u91CD\u590D\u8C03\u7528\u7194\u65AD\uFF0C\u672A\u6267\u884C\uFF09";
13956
14542
  message.push({ role: "tool", tool_call_id: tc.id, content: ph });
13957
14543
  }
13958
- await appendMessages(sessionId, assistantMessage.tool_calls.map((tc) => ({ sessionId, role: "tool", tool_call_id: tc.id, content: "\uFF08\u91CD\u590D\u8C03\u7528\u7194\u65AD\uFF0C\u672A\u6267\u884C\uFF09" })));
14544
+ try {
14545
+ const phWrite = await appendMessages(sessionId, assistantMessage.tool_calls.map((tc) => ({ sessionId, role: "tool", tool_call_id: tc.id, content: "\uFF08\u91CD\u590D\u8C03\u7528\u7194\u65AD\uFF0C\u672A\u6267\u884C\uFF09" })));
14546
+ if (phWrite.failed > 0) markDegraded("appendMessages(repeat-placeholder)");
14547
+ } catch {
14548
+ markDegraded("appendMessages(repeat-placeholder)");
14549
+ }
13959
14550
  yield { type: "final", text: repeatVerdict.text };
13960
14551
  return;
13961
14552
  }
@@ -14006,6 +14597,7 @@ async function* runAgent(message, options) {
14006
14597
  batchBudgetChars: void 0
14007
14598
  };
14008
14599
  const scheduleResult = yield* scheduleToolCalls(assistantMessage, message, toolCallCtx);
14600
+ if (scheduleResult.degraded) for (const name of scheduleResult.degraded) markDegraded(name);
14009
14601
  if (scheduleResult.kind === "aborted") {
14010
14602
  yield { type: "final", text: lastContent || "\uFF08\u5DF2\u4E2D\u6B62\uFF09" };
14011
14603
  return;
@@ -14014,7 +14606,12 @@ async function* runAgent(message, options) {
14014
14606
  yield { type: "final", text: scheduleResult.terminalText };
14015
14607
  return;
14016
14608
  }
14017
- await appendEvent(sessionId, { dscEvent: "round.end", runId, round, usage: infResult.usage });
14609
+ try {
14610
+ const ok = await appendEvent(sessionId, { dscEvent: "round.end", runId, round, usage: infResult.usage });
14611
+ if (!ok) markDegraded("appendEvent(round.end)");
14612
+ } catch {
14613
+ markDegraded("appendEvent(round.end)");
14614
+ }
14018
14615
  emitContextUsage();
14019
14616
  ledger.flushRelay();
14020
14617
  if (options.planMode && planWriteRej >= PLAN_REJECT_FORCE_AT) {
@@ -14036,20 +14633,26 @@ async function* runAgent(message, options) {
14036
14633
  return;
14037
14634
  } finally {
14038
14635
  try {
14039
- await flushLeftoverToTranscript(sessionId);
14636
+ const flushResult = await flushLeftoverToTranscript(sessionId);
14637
+ if (flushResult.failed > 0) markDegraded("flushLeftoverToTranscript");
14040
14638
  } catch {
14639
+ markDegraded("flushLeftoverToTranscript");
14041
14640
  }
14042
14641
  try {
14043
- await appendEvent(sessionId, { dscEvent: "run.end", runId, stopReason: signal?.aborted ? "aborted" : stopReason, rounds: round, usage: ledger.usageSummary() });
14642
+ const ok = await appendEvent(sessionId, { dscEvent: "run.end", runId, stopReason: signal?.aborted ? "aborted" : stopReason, rounds: round, usage: ledger.usageSummary() });
14643
+ if (!ok) markDegraded("appendEvent(run.end)");
14044
14644
  } catch {
14645
+ markDegraded("appendEvent(run.end)");
14045
14646
  }
14046
14647
  try {
14047
- await flushTraceBuffer();
14648
+ const failedSessions = await flushTraceBuffer();
14649
+ if (failedSessions > 0) markDegraded("flushTraceBuffer");
14048
14650
  } catch {
14651
+ markDegraded("flushTraceBuffer");
14049
14652
  }
14050
14653
  try {
14051
14654
  const us = ledger.usageSummary();
14052
- void recordUsageRun({
14655
+ const ok = await recordUsageRun({
14053
14656
  v: 1,
14054
14657
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14055
14658
  sessionId,
@@ -14067,11 +14670,20 @@ async function* runAgent(message, options) {
14067
14670
  // costYuan/toolCostYuan/auxCostYuan(口径与舍入在 runLedger)
14068
14671
  tools: toolCounts
14069
14672
  });
14673
+ if (!ok) markDegraded("recordUsageRun");
14070
14674
  } catch {
14675
+ markDegraded("recordUsageRun");
14071
14676
  }
14072
14677
  try {
14073
14678
  ledger.flushRelay();
14074
14679
  } catch {
14680
+ markDegraded("ledger.flushRelay");
14681
+ }
14682
+ try {
14683
+ await updateCalibration(sessionId, effectiveModel, { calibRatio, lastRealPromptTokens, lastCachedTokens });
14684
+ } catch (e) {
14685
+ markDegraded("updateCalibration");
14686
+ 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);
14075
14687
  }
14076
14688
  try {
14077
14689
  const us = ledger.usageSummary();
@@ -14087,22 +14699,24 @@ async function* runAgent(message, options) {
14087
14699
  ...ledger.receiptFields(),
14088
14700
  // costYuan(null=无价)/toolCostYuan/auxCostYuan/subCostYuan(口径与舍入在 runLedger)
14089
14701
  durationMs: Math.round(performance.now() - startTime),
14090
- budgetTruncated: budgetTruncated || void 0
14702
+ budgetTruncated: budgetTruncated || void 0,
14091
14703
  // 预算截断标注(E2;undefined 自动省略)
14704
+ degraded: degraded.length > 0 ? [...degraded] : void 0
14705
+ // D5:结算单发出前已降级的旁路快照(边界=updateCalibration 之后、Stop hook 之前;undefined 自动省略)
14092
14706
  });
14093
14707
  }
14094
14708
  } catch {
14709
+ markDegraded("run.receipt");
14095
14710
  }
14096
14711
  try {
14097
14712
  const reason = signal?.aborted ? "aborted" : stopReason;
14098
14713
  await dispatch("Stop", { sessionId, cwd, lastText: lastContent || "", reason });
14099
14714
  } catch (e) {
14715
+ markDegraded("Stop-hook");
14100
14716
  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);
14101
14717
  }
14102
- try {
14103
- await updateCalibration(sessionId, effectiveModel, { calibRatio, lastRealPromptTokens, lastCachedTokens });
14104
- } catch (e) {
14105
- 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);
14718
+ if (degraded.length > 0) {
14719
+ console.warn(`\u26A0\uFE0F [run \u964D\u7EA7] ${degraded.length} \u9879\uFF08\u4E0D\u5F71\u54CD\u4E3B\u6D41\u7A0B\uFF1B\u5177\u4F53\u540E\u679C\u89C1\u7EC4\u4EF6\u540D\uFF09: ${degraded.join(", ")}`);
14106
14720
  }
14107
14721
  }
14108
14722
  }
@@ -14112,6 +14726,7 @@ var init_runAgent = __esm({
14112
14726
  "use strict";
14113
14727
  init_transcript();
14114
14728
  init_inbox();
14729
+ init_agentWake();
14115
14730
  init_common();
14116
14731
  init_contextCore();
14117
14732
  init_contentParts();
@@ -14198,7 +14813,7 @@ var init_lineage = __esm({
14198
14813
  });
14199
14814
 
14200
14815
  // ../core/src/agent/subagent.ts
14201
- var SUBAGENT_DENYLIST, filterToolsForSubagent, activeSubagents, failText, RESUME_NOTE, createProgressThrottler, runSubagent;
14816
+ var SUBAGENT_DENYLIST, filterToolsForSubagent, activeSubagents, failText, RESUME_NOTE, createProgressThrottler, prepareSubagent, runPreparedSubagent, runSubagent;
14202
14817
  var init_subagent = __esm({
14203
14818
  "../core/src/agent/subagent.ts"() {
14204
14819
  "use strict";
@@ -14211,6 +14826,7 @@ var init_subagent = __esm({
14211
14826
  init_registry4();
14212
14827
  init_hooks();
14213
14828
  init_lineage();
14829
+ init_agentWake();
14214
14830
  SUBAGENT_DENYLIST = /* @__PURE__ */ new Set(["run_command", "delete_path", "run_in_background"]);
14215
14831
  filterToolsForSubagent = (allTools, manifest) => {
14216
14832
  if (manifest && manifest.tools.length > 0) {
@@ -14245,57 +14861,33 @@ var init_subagent = __esm({
14245
14861
  }
14246
14862
  };
14247
14863
  };
14248
- runSubagent = async (spec, ctx, getGlobalTools) => {
14864
+ prepareSubagent = async (spec, ctx, getGlobalTools) => {
14249
14865
  const { task, name, role, resumeSessionId } = spec;
14250
14866
  const resuming = typeof resumeSessionId === "string" && resumeSessionId.length > 0;
14251
14867
  if (ctx.depth >= MAX_AGENT_DEPTH) {
14252
- return {
14253
- ok: false,
14254
- output: failText(`[\u5B89\u5168\u7194\u65AD]\uFF1A\u5DF2\u8FBE\u5230\u6700\u5927 Agent \u5D4C\u5957\u6DF1\u5EA6\uFF08${MAX_AGENT_DEPTH}\u5C42\uFF09\u3002\u7981\u6B62\u7EE7\u7EED\u65E0\u9650\u5411\u4E0B\u5D4C\u5957\uFF0C\u8BF7\u7236\u5C42\u7EA7 Agent \u81EA\u884C\u6D88\u5316\u5F53\u524D\u4EFB\u52A1\u3002`),
14255
- sessionId: ""
14256
- };
14868
+ return { ok: false, error: failText(`[\u5B89\u5168\u7194\u65AD]\uFF1A\u5DF2\u8FBE\u5230\u6700\u5927 Agent \u5D4C\u5957\u6DF1\u5EA6\uFF08${MAX_AGENT_DEPTH}\u5C42\uFF09\u3002\u7981\u6B62\u7EE7\u7EED\u65E0\u9650\u5411\u4E0B\u5D4C\u5957\uFF0C\u8BF7\u7236\u5C42\u7EA7 Agent \u81EA\u884C\u6D88\u5316\u5F53\u524D\u4EFB\u52A1\u3002`) };
14257
14869
  }
14258
14870
  if (resuming) {
14259
14871
  const target = resumeSessionId;
14260
14872
  if (!target.includes("__sub__")) {
14261
- return {
14262
- ok: false,
14263
- output: failText(`[\u7EED\u8DD1\u5931\u8D25]\uFF1A${target} \u4E0D\u662F\u5B50 Agent \u4F1A\u8BDD ID\uFF08\u5E94\u5F62\u5982 <\u4F1A\u8BDD>__sub__<uuid>\uFF0C\u6765\u81EA\u6B64\u524D spawn_agent \u8FD4\u56DE\u7684 agent_id\uFF09\u3002`),
14264
- sessionId: ""
14265
- };
14873
+ return { ok: false, error: failText(`[\u7EED\u8DD1\u5931\u8D25]\uFF1A${target} \u4E0D\u662F\u5B50 Agent \u4F1A\u8BDD ID\uFF08\u5E94\u5F62\u5982 <\u4F1A\u8BDD>__sub__<uuid>\uFF0C\u6765\u81EA\u6B64\u524D spawn_agent \u8FD4\u56DE\u7684 agent_id\uFF09\u3002`) };
14266
14874
  }
14267
14875
  if (!ownerCanApprove(target, ctx.sessionId)) {
14268
- return {
14269
- ok: false,
14270
- output: failText(`[\u7EED\u8DD1\u5931\u8D25]\uFF1A\u5B50\u4F1A\u8BDD ${target} \u4E0D\u5C5E\u4E8E\u5F53\u524D\u4F1A\u8BDD\uFF08\u53CA\u5176 fork \u8840\u7F18\uFF09\uFF0C\u7981\u6B62\u8DE8\u4F1A\u8BDD\u590D\u6D3B\u3002\u8BF7\u6838\u5BF9 agent_id \u6216\u6539\u7528\u65B0\u5EFA\u6D3E\u751F\u3002`),
14271
- sessionId: ""
14272
- };
14876
+ return { ok: false, error: failText(`[\u7EED\u8DD1\u5931\u8D25]\uFF1A\u5B50\u4F1A\u8BDD ${target} \u4E0D\u5C5E\u4E8E\u5F53\u524D\u4F1A\u8BDD\uFF08\u53CA\u5176 fork \u8840\u7F18\uFF09\uFF0C\u7981\u6B62\u8DE8\u4F1A\u8BDD\u590D\u6D3B\u3002\u8BF7\u6838\u5BF9 agent_id \u6216\u6539\u7528\u65B0\u5EFA\u6D3E\u751F\u3002`) };
14273
14877
  }
14274
14878
  if (activeSubagents.has(target)) {
14275
- return {
14276
- ok: false,
14277
- output: failText(`[\u7EED\u8DD1\u5931\u8D25]\uFF1A\u5B50 Agent ${target} \u6B63\u5728\u6267\u884C\u4E2D\uFF0C\u7981\u6B62\u5E76\u53D1\u7EED\u8DD1\u540C\u4E00\u5B50\u4F1A\u8BDD\uFF08\u4F1A\u4EA4\u53C9\u5199\u574F\u8F6C\u5F55\uFF09\u3002\u8BF7\u7B49\u5F85\u5176\u5B8C\u6210\u540E\u518D\u8BD5\u3002`),
14278
- sessionId: ""
14279
- };
14879
+ return { ok: false, error: failText(`[\u7EED\u8DD1\u5931\u8D25]\uFF1A\u5B50 Agent ${target} \u6B63\u5728\u6267\u884C\u4E2D\uFF0C\u7981\u6B62\u5E76\u53D1\u7EED\u8DD1\u540C\u4E00\u5B50\u4F1A\u8BDD\uFF08\u4F1A\u4EA4\u53C9\u5199\u574F\u8F6C\u5F55\uFF09\u3002\u8BF7\u7B49\u5F85\u5176\u5B8C\u6210\u540E\u518D\u8BD5\u3002`) };
14280
14880
  }
14281
14881
  const existing = await readTranscriptLines(target);
14282
14882
  if (existing.length === 0) {
14283
- return {
14284
- ok: false,
14285
- output: failText(`[\u7EED\u8DD1\u5931\u8D25]\uFF1A\u5B50\u4F1A\u8BDD ${target} \u7684\u8F6C\u5F55\u4E0D\u5B58\u5728\u6216\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u7EED\u8DD1\u3002\u8BF7\u6539\u7528\u65B0\u5EFA\u6D3E\u751F\u3002`),
14286
- sessionId: ""
14287
- };
14883
+ return { ok: false, error: failText(`[\u7EED\u8DD1\u5931\u8D25]\uFF1A\u5B50\u4F1A\u8BDD ${target} \u7684\u8F6C\u5F55\u4E0D\u5B58\u5728\u6216\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u7EED\u8DD1\u3002\u8BF7\u6539\u7528\u65B0\u5EFA\u6D3E\u751F\u3002`) };
14288
14884
  }
14289
14885
  }
14290
14886
  const subSessionId = resuming ? resumeSessionId : `${ctx.sessionId}__sub__${createUUID()}`;
14291
14887
  const parentSystemPrompt = ctx.parentSystemPrompt || "";
14292
14888
  const manifest = name ? getAgent(name) : void 0;
14293
14889
  if (name && !manifest) {
14294
- return {
14295
- ok: false,
14296
- output: failText(`[\u6D3E\u751F\u5931\u8D25]\uFF1A\u672A\u627E\u5230\u58F0\u660E\u5F0F\u5B50 Agent "${name}"\u3002\u8BF7\u6838\u5BF9\u7CFB\u7EDF\u63D0\u793A\u8BCD\u4E2D\u3010\u53EF\u7528\u5B50 Agent \u76EE\u5F55\u3011\u7684\u540D\u79F0\u62FC\u5199\u3002`),
14297
- sessionId: ""
14298
- };
14890
+ return { ok: false, error: failText(`[\u6D3E\u751F\u5931\u8D25]\uFF1A\u672A\u627E\u5230\u58F0\u660E\u5F0F\u5B50 Agent "${name}"\u3002\u8BF7\u6838\u5BF9\u7CFB\u7EDF\u63D0\u793A\u8BCD\u4E2D\u3010\u53EF\u7528\u5B50 Agent \u76EE\u5F55\u3011\u7684\u540D\u79F0\u62FC\u5199\u3002`) };
14299
14891
  }
14300
14892
  const SUBAGENT_REPORT_REQ = `\u3010\u6C47\u62A5\u8981\u6C42\u3011\u6700\u7EC8\u6C47\u62A5\u63A7\u5236\u5728\u4E00\u5C4F\u5185\uFF1A\u5DF2\u5B8C\u6210/\u6539\u52A8\u70B9\uFF08\u6587\u4EF6\u8DEF\u5F84\uFF09/\u9A8C\u8BC1\u7ED3\u679C/\u672A\u5B8C\u6210\u9879\u3002\u4E0D\u8981\u590D\u8FF0\u8FC7\u7A0B\u4E0E\u5DE5\u5177\u8F93\u51FA\u2014\u2014\u7236\u7EA7\u53EF\u76F4\u63A5\u7ECF resume \u8FFD\u95EE\u7EC6\u8282\u3002`;
14301
14893
  const subSystem = manifest ? [
@@ -14327,8 +14919,15 @@ var init_subagent = __esm({
14327
14919
  const allTools = getGlobalTools();
14328
14920
  const toolSchemas = filterToolsForSubagent(allTools, manifest);
14329
14921
  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)}..."`);
14922
+ const startCtx = { sessionId: subSessionId, parentSessionId: ctx.sessionId, task, depth: ctx.depth + 1, name: manifest?.name, cwd: ctx.cwd };
14923
+ return { ok: true, prepared: { subSessionId, resuming, manifest, subSystem, subTaskPrompt, toolSchemas, startCtx, taskBrief: task.slice(0, 60) } };
14924
+ };
14925
+ runPreparedSubagent = async (prepared, ctx, opts) => {
14926
+ const { subSessionId, manifest, subTaskPrompt, subSystem, toolSchemas, startCtx } = prepared;
14927
+ const manifestName = manifest?.name;
14330
14928
  activeSubagents.add(subSessionId);
14331
- ctx.onUIEvent?.({ type: "subagent.count", running: activeSubagents.size, phase: "start", name: manifest?.name, depth: ctx.depth + 1 });
14929
+ trackSubagentInFlight(ctx.sessionId, subSessionId);
14930
+ ctx.onUIEvent?.({ type: "subagent.count", running: activeSubagents.size, phase: "start", name: manifest?.name, depth: startCtx.depth });
14332
14931
  try {
14333
14932
  const subMessages = await buildContextMessages(subSessionId, { role: "user", content: subTaskPrompt }, subSystem, manifest?.model);
14334
14933
  await appendMessage({ sessionId: subSessionId, role: "user", content: subTaskPrompt });
@@ -14346,7 +14945,7 @@ var init_subagent = __esm({
14346
14945
  depth: ctx.depth + 1,
14347
14946
  keepRecentUnits: ctx.keepRecentUnits,
14348
14947
  compactRatio: ctx.compactRatio,
14349
- parentSystemPrompt,
14948
+ parentSystemPrompt: ctx.parentSystemPrompt,
14350
14949
  events: ctx.events,
14351
14950
  onUIEvent: ctx.onUIEvent,
14352
14951
  // ★ 必须透传:否则子 agent 调用需审批工具时前端收不到弹窗,waitForUserApproval 永久挂起(死锁)
@@ -14365,14 +14964,13 @@ var init_subagent = __esm({
14365
14964
  budgetCheck: ctx.budgetCheck
14366
14965
  // ★ P1-5 预算熔断链式透传:子 runAgent 轮边界探测父预算,超支即收尾(不等烧完)
14367
14966
  };
14368
- const startCtx = { sessionId: subSessionId, parentSessionId: ctx.sessionId, task, depth: ctx.depth + 1, name: manifest?.name, cwd: ctx.cwd };
14369
14967
  await dispatch("SubagentStart", startCtx);
14370
14968
  let result;
14371
14969
  const progress = createProgressThrottler((line) => ctx.onUIEvent?.({ type: "subagent.progress", text: line, agentKey: subSessionId, name: manifest?.name }));
14372
14970
  try {
14373
14971
  for await (const e of runAgent(subMessages, subOptions)) {
14374
14972
  if (ctx.abortSignal?.aborted) {
14375
- result = { ok: false, output: failText(`[\u5B50Agent\u4E2D\u65AD]\uFF1A\u6267\u884C\u5DF2\u88AB\u7528\u6237\u4E3B\u52A8\u53D1\u8D77\u7684 AbortSignal \u5F3A\u884C\u7194\u65AD\u3002`), sessionId: subSessionId, manifestName: manifest?.name };
14973
+ result = { ok: false, output: failText(`[\u5B50Agent\u4E2D\u65AD]\uFF1A\u6267\u884C\u5DF2\u88AB\u7528\u6237\u4E3B\u52A8\u53D1\u8D77\u7684 AbortSignal \u5F3A\u884C\u7194\u65AD\u3002`), sessionId: subSessionId, manifestName: manifest?.name, status: "aborted" };
14376
14974
  break;
14377
14975
  }
14378
14976
  if (e.type === "final") {
@@ -14386,10 +14984,10 @@ var init_subagent = __esm({
14386
14984
  if (!hasFinalResult || !subResult.trim()) {
14387
14985
  subResult = `\uFF08\u672A\u80FD\u83B7\u53D6\u5230\u7ED3\u6784\u5316 final \u56DE\u62A5\u3002\u8BF7\u68C0\u67E5\u8BE5\u5B50 Session [${subSessionId}] \u7684\u6267\u884C\u5386\u53F2\uFF09`;
14388
14986
  }
14389
- result = { ok: true, output: subResult, sessionId: subSessionId, manifestName: manifest?.name };
14987
+ result = { ok: true, output: subResult, sessionId: subSessionId, manifestName: manifest?.name, status: "final" };
14390
14988
  }
14391
14989
  } catch (streamError) {
14392
- 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 };
14990
+ 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, status: "error" };
14393
14991
  } finally {
14394
14992
  try {
14395
14993
  ctx.onUIEvent?.({ type: "subagent.progress", text: "", done: true, agentKey: subSessionId });
@@ -14399,13 +14997,27 @@ var init_subagent = __esm({
14399
14997
  await dispatch("SubagentStop", { ...startCtx, ok: result.ok, output: result.output.slice(0, 2e3) }).catch((e) => console.warn(`\u26A0\uFE0F SubagentStop hook \u5F02\u5E38\uFF08\u5DF2\u5FFD\u7565\uFF09: ${e?.message ?? e}`));
14400
14998
  }
14401
14999
  }
14402
- return result ?? { ok: false, output: failText(`[\u6D3E\u751F\u6267\u884C\u5931\u8D25]: \u672A\u77E5\u9519\u8BEF`), sessionId: subSessionId, manifestName: manifest?.name };
15000
+ if (opts?.notifyDone && result && result.status !== "aborted") {
15001
+ try {
15002
+ await notifySubagentDone(ctx.sessionId, buildSubagentDoneReport(subSessionId, prepared.taskBrief, { status: result.status, output: result.output }));
15003
+ } catch {
15004
+ }
15005
+ }
15006
+ return result ?? { ok: false, output: failText(`[\u6D3E\u751F\u6267\u884C\u5931\u8D25]: \u672A\u77E5\u9519\u8BEF`), sessionId: subSessionId, manifestName: manifest?.name, status: "error" };
14403
15007
  } catch (error) {
14404
- return { ok: false, output: failText(`[\u6D3E\u751F\u6267\u884C\u5931\u8D25]: ${error.message}`), sessionId: subSessionId, manifestName: manifest?.name };
15008
+ return { ok: false, output: failText(`[\u6D3E\u751F\u6267\u884C\u5931\u8D25]: ${error.message}`), sessionId: subSessionId, manifestName: manifest?.name, status: "error" };
14405
15009
  } finally {
14406
15010
  activeSubagents.delete(subSessionId);
14407
- ctx.onUIEvent?.({ type: "subagent.count", running: activeSubagents.size, phase: "stop", name: manifest?.name, depth: ctx.depth + 1 });
15011
+ releaseSubagentInFlight(ctx.sessionId, subSessionId);
15012
+ ctx.onUIEvent?.({ type: "subagent.count", running: activeSubagents.size, phase: "stop", name: manifestName, depth: startCtx.depth });
15013
+ }
15014
+ };
15015
+ runSubagent = async (spec, ctx, getGlobalTools) => {
15016
+ const prep = await prepareSubagent(spec, ctx, getGlobalTools);
15017
+ if (!prep.ok) {
15018
+ return { ok: false, output: prep.error, sessionId: "", status: "error" };
14408
15019
  }
15020
+ return runPreparedSubagent(prep.prepared, ctx);
14409
15021
  };
14410
15022
  }
14411
15023
  });
@@ -15461,7 +16073,7 @@ var init_expand = __esm({
15461
16073
  // ../core/src/serve/chatProcessing.ts
15462
16074
  import path48 from "path";
15463
16075
  import fs46 from "fs";
15464
- var handleUnifiedChat;
16076
+ var handleUnifiedChat, handleUnifiedChatRun;
15465
16077
  var init_chatProcessing = __esm({
15466
16078
  "../core/src/serve/chatProcessing.ts"() {
15467
16079
  "use strict";
@@ -15481,8 +16093,34 @@ var init_chatProcessing = __esm({
15481
16093
  init_guard();
15482
16094
  init_systemPrompt();
15483
16095
  init_model();
16096
+ init_agentWake();
16097
+ init_inject5();
15484
16098
  handleUnifiedChat = async (inbound, sendOutbound, sseWrite, abortSignal, opts) => {
16099
+ let busyKey = typeof inbound.sessionId === "string" && inbound.sessionId ? inbound.sessionId : "";
16100
+ markRunStart(busyKey);
16101
+ try {
16102
+ await handleUnifiedChatRun(inbound, sendOutbound, sseWrite, abortSignal, opts, (resolved) => {
16103
+ if (busyKey === resolved) return;
16104
+ markRunStart(resolved);
16105
+ if (busyKey) markRunEnd(busyKey);
16106
+ busyKey = resolved;
16107
+ });
16108
+ } finally {
16109
+ markRunEnd(busyKey);
16110
+ }
16111
+ };
16112
+ handleUnifiedChatRun = async (inbound, sendOutbound, sseWrite, abortSignal, opts, transferBusy) => {
15485
16113
  const sessionId = await getOrCreateSessionId(inbound.sessionId);
16114
+ transferBusy?.(sessionId);
16115
+ registerWakeDriverIfAbsent(sessionId, async () => {
16116
+ await handleUnifiedChat(
16117
+ { ...inbound, sessionId, content: AGENT_DONE_SENTINEL, attachments: void 0 },
16118
+ sendOutbound,
16119
+ sseWrite,
16120
+ abortSignal,
16121
+ opts
16122
+ );
16123
+ });
15486
16124
  try {
15487
16125
  inbound.content = expandSlashCommand(inbound.content);
15488
16126
  } catch {
@@ -15624,6 +16262,7 @@ ${envLines.join("\n")}`;
15624
16262
  }
15625
16263
  await dispatch("SessionEnd", { sessionId, cwd: process.cwd() }).catch(() => {
15626
16264
  });
16265
+ clearMemorySnapshot(sessionId);
15627
16266
  await emitTrace({
15628
16267
  sessionId,
15629
16268
  eventType: "session.end",
@@ -16162,6 +16801,7 @@ var init_useChatState = __esm({
16162
16801
  init_transcript();
16163
16802
  init_fork();
16164
16803
  init_inbox();
16804
+ init_agentWake();
16165
16805
  init_contextCore();
16166
16806
  init_bashDirect();
16167
16807
  init_guard();
@@ -16632,6 +17272,17 @@ var init_useChatState = __esm({
16632
17272
  budgetYuan: budgetYuanRef.current
16633
17273
  // ★ 预算制运行(成本信任链第二环 E1):/budget 设置,超限 soft nudge + 120% 硬截断
16634
17274
  };
17275
+ registerWakeDriver(sid, async () => {
17276
+ if (sessionIdRef.current !== sid) return;
17277
+ const myGen = ++turnGenRef.current;
17278
+ setBusy(true);
17279
+ pushInfo("\u{1F916} \u5B50\u4EE3\u7406\u5B8C\u6210\uFF0C\u81EA\u52A8\u5524\u9192\u4E3B\u4F1A\u8BDD\u7EE7\u7EED\u2026");
17280
+ try {
17281
+ await runOnce(sid, AGENT_DONE_SENTINEL, false);
17282
+ } finally {
17283
+ if (turnGenRef.current === myGen) setBusy(false);
17284
+ }
17285
+ });
16635
17286
  try {
16636
17287
  await handleUnifiedChat(
16637
17288
  { sessionId: sid, content: body },
@@ -19164,15 +19815,595 @@ ${listText}`);
19164
19815
  }
19165
19816
  });
19166
19817
 
19818
+ // ../core/src/agent/stallDetector.ts
19819
+ import { execFile as execFile2 } from "child_process";
19820
+ var CHECK_INTERVAL_SEC, GRACE_MIN, R1_WINDOW_MIN, R1_MIN_HITS, R2_WINDOW_MIN, R2_MIN_CALLS, R2_MAX_DISTINCT_SIG, R3_SILENCE_MIN, R4_WINDOW_MIN, R4_COST_DIVISOR, R6_STALL_BAND, HEALTH_GROWTH_LINES, SERIES_CAP, LOCKFILE_RE, EDIT_TOOLS, READONLY_TOOLS, normPath2, samePath, sortKeysDeep, argsKeyOf, similarSigOf, editTargetOf, extractTargetPaths, parseNumstatNet, MIN, StallDetector;
19821
+ var init_stallDetector = __esm({
19822
+ "../core/src/agent/stallDetector.ts"() {
19823
+ "use strict";
19824
+ CHECK_INTERVAL_SEC = 60;
19825
+ GRACE_MIN = 5;
19826
+ R1_WINDOW_MIN = 10;
19827
+ R1_MIN_HITS = 3;
19828
+ R2_WINDOW_MIN = 15;
19829
+ R2_MIN_CALLS = 3;
19830
+ R2_MAX_DISTINCT_SIG = 2;
19831
+ R3_SILENCE_MIN = 5;
19832
+ R4_WINDOW_MIN = 5;
19833
+ R4_COST_DIVISOR = 4;
19834
+ R6_STALL_BAND = 3;
19835
+ HEALTH_GROWTH_LINES = 20;
19836
+ SERIES_CAP = 64;
19837
+ LOCKFILE_RE = /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb|bun\.lock|composer\.lock|poetry\.lock|Cargo\.lock|go\.sum)$/i;
19838
+ EDIT_TOOLS = /* @__PURE__ */ new Set(["edit_file", "create_file", "write_file", "move_file", "notebook_edit"]);
19839
+ READONLY_TOOLS = /* @__PURE__ */ new Set([
19840
+ "read_file",
19841
+ "list_dir",
19842
+ "glob",
19843
+ "search_grep",
19844
+ "read_image",
19845
+ "read_docx",
19846
+ "read_xlsx",
19847
+ "read_project_guide",
19848
+ "recall",
19849
+ "get_git_diff",
19850
+ "git_status",
19851
+ "git_log",
19852
+ "find_references",
19853
+ "get_diagnostics",
19854
+ "goto_definition",
19855
+ "view_symbol_outline",
19856
+ "web_search",
19857
+ "web_fetch"
19858
+ ]);
19859
+ normPath2 = (p) => p.trim().replace(/\\/g, "/").replace(/^[A-Za-z]:/, "").replace(/^\.\/+/, "").replace(/\/+$/, "").toLowerCase();
19860
+ samePath = (a, b) => a === b || a.endsWith("/" + b) || b.endsWith("/" + a);
19861
+ sortKeysDeep = (v) => {
19862
+ if (Array.isArray(v)) return v.map(sortKeysDeep);
19863
+ if (v && typeof v === "object") {
19864
+ const out = {};
19865
+ for (const k of Object.keys(v).sort()) out[k] = sortKeysDeep(v[k]);
19866
+ return out;
19867
+ }
19868
+ return v;
19869
+ };
19870
+ argsKeyOf = (args) => {
19871
+ try {
19872
+ return JSON.stringify(sortKeysDeep(args ?? {})).slice(0, 200);
19873
+ } catch {
19874
+ return String(args);
19875
+ }
19876
+ };
19877
+ similarSigOf = (args) => {
19878
+ let s;
19879
+ try {
19880
+ s = JSON.stringify(sortKeysDeep(args ?? {}));
19881
+ } catch {
19882
+ return String(args);
19883
+ }
19884
+ return s.replace(/\d+/g, "").replace(/\/[^/"]+(?=")/, "");
19885
+ };
19886
+ editTargetOf = (args) => normPath2(String(args?.path ?? args?.source ?? args?.destination ?? args?.file_path ?? ""));
19887
+ extractTargetPaths = (text) => {
19888
+ const out = /* @__PURE__ */ new Set();
19889
+ for (const m of text.match(/(?:[A-Za-z]:)?[\w.\-一-鿿]+(?:[/\\][\w.\-一-鿿]+)+\.[A-Za-z0-9]{1,6}/g) ?? []) {
19890
+ out.add(normPath2(m));
19891
+ }
19892
+ return out;
19893
+ };
19894
+ parseNumstatNet = (stdout) => {
19895
+ let net = 0;
19896
+ for (const line of stdout.split("\n")) {
19897
+ const m = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line.trim());
19898
+ if (!m) continue;
19899
+ if (LOCKFILE_RE.test(m[3])) continue;
19900
+ net += (m[1] === "-" ? 0 : parseInt(m[1], 10)) + (m[2] === "-" ? 0 : parseInt(m[2], 10));
19901
+ }
19902
+ return net;
19903
+ };
19904
+ MIN = 6e4;
19905
+ StallDetector = class {
19906
+ constructor(opts) {
19907
+ this.stopFn = null;
19908
+ this.started = false;
19909
+ this.stopped = false;
19910
+ this.paused = false;
19911
+ this.ticking = false;
19912
+ /** 宽限期基准(豁免解除时重置——豁免期不该吃掉 agent 的起步宽限)。字段名与同名 getter 刻意区分
19913
+ * (tsconfig 为 set 语义,私有字段与只读 getter 同名会在字段初始化时炸 "only a getter")。 */
19914
+ this.activeAt = 0;
19915
+ this.lastEventAt = 0;
19916
+ /** 抑制窗截止(政策层经 setSuppressUntil 设置:batch=nudge 后宽限;影子=强命中去重)。 */
19917
+ this.suppressUntil = 0;
19918
+ this.roundNo = 0;
19919
+ this.calls = [];
19920
+ this.finalSeen = false;
19921
+ /** R6 净变更行数时间序列(batch 基线 = setup 完成后首读;交互 run 首 tick 采样即基线)。 */
19922
+ this.series = [];
19923
+ /** R4 成本采样序列。 */
19924
+ this.costSeries = [];
19925
+ this.opts = opts;
19926
+ this.now = opts.now ?? Date.now;
19927
+ this.intervalMs = opts.intervalMs ?? CHECK_INTERVAL_SEC * 1e3;
19928
+ this.targetPaths = extractTargetPaths(opts.targetText ?? "");
19929
+ this.lastEventAt = this.now();
19930
+ }
19931
+ /** 启动旁观者心跳。判定在解除暂停前不会真正开始(batch setup 执行期挂 PAUSED)。 */
19932
+ start() {
19933
+ if (this.started || this.stopped) return;
19934
+ this.started = true;
19935
+ this.activeAt = this.now();
19936
+ const schedule = this.opts.schedule ?? ((fn, ms) => {
19937
+ const h = setInterval(fn, ms);
19938
+ h.unref?.();
19939
+ return () => clearInterval(h);
19940
+ });
19941
+ this.stopFn = schedule(() => void this.tick(), this.intervalMs);
19942
+ }
19943
+ stop() {
19944
+ this.stopped = true;
19945
+ this.stopFn?.();
19946
+ this.stopFn = null;
19947
+ }
19948
+ /** 暂停判定(batch setup/accept 执行期;交互审批/提问挂起期);解除时对齐事件时钟并重置起步宽限。 */
19949
+ setPaused(p) {
19950
+ if (p === this.paused || this.stopped) return;
19951
+ this.paused = p;
19952
+ if (!p) {
19953
+ const t = this.now();
19954
+ this.lastEventAt = Math.max(this.lastEventAt, t);
19955
+ this.activeAt = t;
19956
+ }
19957
+ }
19958
+ /** 抑制窗(政策层动作语义映射:batch nudge 后宽限 / 影子强命中 5 分钟去重)。 */
19959
+ setSuppressUntil(t) {
19960
+ this.suppressUntil = t;
19961
+ }
19962
+ /** R6 基线:batch 在 setup 完成后取首个读数(装依赖动 lockfile,故基线后置 + 排除 lockfile)。 */
19963
+ async markBaseline() {
19964
+ const s = await this.sampleDiffSafe();
19965
+ this.series = s ? [{ ...s, t: this.now() }] : [];
19966
+ }
19967
+ /**
19968
+ * 事件喂入口:tool.start/tool.end/round.start/text.delta/thinking.delta/final 全喂。
19969
+ * 任何事件都刷新 lastEventAt(R3 断流的对面);豁免期不喂不计时(resume 时对齐)。
19970
+ * 白名单外的事件类型静默忽略(VSCode sink 混流 UIEvent,挂起交互事件天然无害)。
19971
+ */
19972
+ feed(evt) {
19973
+ if (this.stopped || this.paused || !evt?.type) return;
19974
+ const t = this.now();
19975
+ this.lastEventAt = t;
19976
+ if (evt.type === "round.start") {
19977
+ this.roundNo = Number(evt.round) || this.roundNo;
19978
+ return;
19979
+ }
19980
+ if (evt.type === "final") {
19981
+ this.finalSeen = true;
19982
+ return;
19983
+ }
19984
+ if (evt.type === "tool.start") {
19985
+ const toolName = String(evt.toolName ?? "");
19986
+ const args = evt.args ?? {};
19987
+ let digest = "";
19988
+ try {
19989
+ digest = JSON.stringify(args);
19990
+ } catch {
19991
+ digest = String(args);
19992
+ }
19993
+ this.calls.push({
19994
+ toolCallId: String(evt.toolCallId ?? ""),
19995
+ toolName,
19996
+ argsKey: argsKeyOf(args),
19997
+ sig: similarSigOf(args),
19998
+ isEdit: EDIT_TOOLS.has(toolName),
19999
+ isReadonly: READONLY_TOOLS.has(toolName),
20000
+ target: editTargetOf(args),
20001
+ argsDigest: digest.slice(0, 120),
20002
+ t,
20003
+ round: this.roundNo
20004
+ });
20005
+ if (this.calls.length > 512) this.calls.splice(0, this.calls.length - 512);
20006
+ }
20007
+ }
20008
+ /** 判定一步(测试可直接驱动;生产由 scheduler 每 tick 调)。自身异常绝不外泄。 */
20009
+ async tick() {
20010
+ if (!this.started || this.stopped || this.paused || this.ticking) return;
20011
+ this.ticking = true;
20012
+ try {
20013
+ await this.evaluate();
20014
+ } catch {
20015
+ } finally {
20016
+ this.ticking = false;
20017
+ }
20018
+ }
20019
+ // —— 政策层访问器(拼 AUX 任务书 / 日志行用;不含动作语义) ——
20020
+ get round() {
20021
+ return this.roundNo;
20022
+ }
20023
+ get activeAtMs() {
20024
+ return this.activeAt;
20025
+ }
20026
+ /** 最近一次 diff 采样(影子日志 netLines 字段;无采样返回 null)。 */
20027
+ get lastNetSample() {
20028
+ return this.series.length > 0 ? this.series[this.series.length - 1] : null;
20029
+ }
20030
+ /** 成本采样(不进序列——evaluate 每 tick 的采样才进,batch costLine 的即时读数与原实现同形)。 */
20031
+ sampleCost() {
20032
+ return this.sampleCostSafe();
20033
+ }
20034
+ /** 时间窗内调用记录(batch 轨迹拼装:只读合并计数、写入逐条)。 */
20035
+ callsWithin(t, windowMs) {
20036
+ return this.calls.filter((c) => t - c.t <= windowMs);
20037
+ }
20038
+ // —— 评估主体:产出 StallEvaluation 交政策层 ——
20039
+ async evaluate() {
20040
+ const t = this.now();
20041
+ if (t - this.activeAt < GRACE_MIN * MIN) {
20042
+ await this.opts.onEvaluation({ t, skipped: "grace-start", silenceMin: null, health: null, strong: [], weak: [] });
20043
+ return;
20044
+ }
20045
+ if (t < this.suppressUntil) {
20046
+ await this.opts.onEvaluation({ t, skipped: "suppressed", silenceMin: null, health: null, strong: [], weak: [] });
20047
+ return;
20048
+ }
20049
+ const sample = await this.sampleDiffSafe();
20050
+ if (sample) {
20051
+ this.series.push({ ...sample, t });
20052
+ if (this.series.length > SERIES_CAP) this.series.splice(0, this.series.length - SERIES_CAP);
20053
+ }
20054
+ const cost = await this.sampleCostSafe();
20055
+ if (cost) {
20056
+ this.costSeries.push({ costYuan: cost.costYuan, t });
20057
+ if (this.costSeries.length > SERIES_CAP) this.costSeries.splice(0, this.costSeries.length - SERIES_CAP);
20058
+ }
20059
+ const silenceMs = t - this.lastEventAt;
20060
+ if (silenceMs >= R3_SILENCE_MIN * MIN) {
20061
+ await this.opts.onEvaluation({ t, skipped: null, silenceMin: silenceMs / MIN, health: null, strong: [], weak: [] });
20062
+ return;
20063
+ }
20064
+ const health = this.healthCheck();
20065
+ if (health) {
20066
+ await this.opts.onEvaluation({ t, skipped: null, silenceMin: null, health, strong: [], weak: [] });
20067
+ return;
20068
+ }
20069
+ const strong = [this.r1Check(t), this.r6Check()].filter((r) => r !== null);
20070
+ const weak = [this.r2Check(t), this.r4Check(t), this.r5Check(t)].filter((r) => r !== null);
20071
+ await this.opts.onEvaluation({ t, skipped: null, silenceMin: null, health: null, strong, weak });
20072
+ }
20073
+ // —— 规则实现 ——
20074
+ /** R1 重复模式:同 (tool, 规范化 args) 窗口内 ≥3 次且跨轮(轮内重复 repeatBreaker 已管)。 */
20075
+ r1Check(t) {
20076
+ const groups = /* @__PURE__ */ new Map();
20077
+ for (const c of this.calls) {
20078
+ if (t - c.t > R1_WINDOW_MIN * MIN) continue;
20079
+ const key = `${c.toolName} ${c.argsKey}`;
20080
+ const g = groups.get(key) ?? { tool: c.toolName, n: 0, rounds: /* @__PURE__ */ new Set() };
20081
+ g.n += 1;
20082
+ g.rounds.add(c.round);
20083
+ groups.set(key, g);
20084
+ }
20085
+ for (const g of groups.values()) {
20086
+ if (g.n >= R1_MIN_HITS && g.rounds.size >= 2) {
20087
+ return { rule: "R1", evidence: `\u8FD1 ${R1_WINDOW_MIN} \u5206\u949F ${g.tool} \u540C\u53C2\u6570\u8C03\u7528 ${g.n} \u6B21\uFF08\u8DE8 ${g.rounds.size} \u8F6E\uFF09` };
20088
+ }
20089
+ }
20090
+ return null;
20091
+ }
20092
+ /** R2 只读空转(弱):窗口内调用全只读且朴素规范化后变体 ≤2 种(args 持续变化=调研,防误伤)。 */
20093
+ r2Check(t) {
20094
+ const recent = this.calls.filter((c) => t - c.t <= R2_WINDOW_MIN * MIN);
20095
+ if (recent.length < R2_MIN_CALLS || recent.some((c) => !c.isReadonly)) return null;
20096
+ const sigs = new Set(recent.map((c) => c.sig));
20097
+ if (sigs.size > R2_MAX_DISTINCT_SIG) return null;
20098
+ return { rule: "R2", evidence: `\u8FD1 ${R2_WINDOW_MIN} \u5206\u949F ${recent.length} \u6B21\u8C03\u7528\u5168\u4E3A\u53EA\u8BFB\u4E14\u540C\u5F62\uFF08${sigs.size} \u79CD\u53D8\u4F53\uFF09` };
20099
+ }
20100
+ /** R4 烧钱无产出(弱):窗口成本增量 > 预算 1/4 且期间零 edit、零 final。 */
20101
+ r4Check(t) {
20102
+ if (this.opts.budgetYuan === void 0) return null;
20103
+ const win = this.costSeries.filter((s) => t - s.t <= R4_WINDOW_MIN * MIN);
20104
+ if (win.length < 2) return null;
20105
+ const first = win[0];
20106
+ const last = win[win.length - 1];
20107
+ if (first.costYuan == null || last.costYuan == null) return null;
20108
+ const delta = last.costYuan - first.costYuan;
20109
+ if (delta <= this.opts.budgetYuan / R4_COST_DIVISOR) return null;
20110
+ if (this.calls.some((c) => c.isEdit && t - c.t <= R4_WINDOW_MIN * MIN) || this.finalSeen) return null;
20111
+ return { rule: "R4", evidence: `\u8FD1 ${R4_WINDOW_MIN} \u5206\u949F\u82B1\u8D39 \xA5${delta.toFixed(4)}\uFF08> \u9884\u7B97 1/4\uFF09\uFF0C\u671F\u95F4\u96F6 edit \u96F6 final` };
20112
+ }
20113
+ /** R5 写入方向偏差(弱):窗口内 edit 目标与任务目标集合全无交集;提取不到目标集合 → 整条关闭。 */
20114
+ r5Check(t) {
20115
+ if (this.targetPaths.size === 0) return null;
20116
+ const edits = this.calls.filter((c) => c.isEdit && t - c.t <= R2_WINDOW_MIN * MIN);
20117
+ if (edits.length === 0) return null;
20118
+ const hit = edits.some((c) => c.target && [...this.targetPaths].some((p) => samePath(c.target, p)));
20119
+ if (hit) return null;
20120
+ return { rule: "R5", evidence: `\u8FD1 ${R2_WINDOW_MIN} \u5206\u949F ${edits.length} \u6B21 edit \u5168\u90E8\u843D\u5728\u4EFB\u52A1\u76EE\u6807\u8DEF\u5F84\u4E4B\u5916\uFF08\u5982 ${edits[0].target || "?"}\uFF09` };
20121
+ }
20122
+ /** R6 净进展停滞(强):连续 2 窗口净变更 ≈0(±3 行)且同期 edit 调用 >0——写了又改、没沉淀。 */
20123
+ r6Check() {
20124
+ const s = this.series;
20125
+ if (s.length < 3) return null;
20126
+ const a = s[s.length - 3];
20127
+ const b = s[s.length - 2];
20128
+ const c = s[s.length - 1];
20129
+ if (Math.abs(b.net - a.net) > R6_STALL_BAND || Math.abs(c.net - b.net) > R6_STALL_BAND) return null;
20130
+ const edits = this.calls.filter((e) => e.isEdit && e.t >= a.t).length;
20131
+ if (edits === 0) return null;
20132
+ return { rule: "R6", evidence: `\u51C0\u53D8\u66F4 ${a.net}\u2192${b.net}\u2192${c.net} \u884C\uFF08\u8FDE\u7EED 2 \u7A97\u53E3 \xB1${R6_STALL_BAND} \u5185\uFF09\u671F\u95F4 ${edits} \u6B21 edit` };
20133
+ }
20134
+ /** 健康短路:净变更显著增长或新文件出现 → 直接 normal(R2/R4 弱信号的反证)。 */
20135
+ healthCheck() {
20136
+ const s = this.series;
20137
+ if (s.length < 2) return null;
20138
+ const prev = s[s.length - 2];
20139
+ const last = s[s.length - 1];
20140
+ if (last.newFiles.length > 0) return `\u65B0\u6587\u4EF6\u51FA\u73B0\uFF1A${last.newFiles.slice(0, 3).join("\u3001")}`;
20141
+ const delta = last.net - prev.net;
20142
+ if (delta > HEALTH_GROWTH_LINES) return `\u51C0\u53D8\u66F4\u589E\u957F +${delta} \u884C\uFF08> ${HEALTH_GROWTH_LINES}\uFF09`;
20143
+ return null;
20144
+ }
20145
+ // —— 采样 ——
20146
+ async sampleDiffSafe() {
20147
+ if (this.opts.sampleDiff) return this.opts.sampleDiff();
20148
+ try {
20149
+ const run = (args) => new Promise((resolve, reject) => {
20150
+ execFile2("git", ["-C", this.opts.worktreePath, ...args], { maxBuffer: 1024 * 1024 }, (err, stdout) => err ? reject(err) : resolve(String(stdout)));
20151
+ });
20152
+ const [numstat, others] = await Promise.all([
20153
+ run(["diff", "--numstat", "HEAD"]),
20154
+ run(["ls-files", "--others", "--exclude-standard"])
20155
+ ]);
20156
+ const newFiles = others.split("\n").map((s) => s.trim()).filter(Boolean);
20157
+ return { net: parseNumstatNet(numstat), newFiles };
20158
+ } catch {
20159
+ return null;
20160
+ }
20161
+ }
20162
+ async sampleCostSafe() {
20163
+ if (!this.opts.getCost) return null;
20164
+ try {
20165
+ return await this.opts.getCost();
20166
+ } catch {
20167
+ return null;
20168
+ }
20169
+ }
20170
+ };
20171
+ }
20172
+ });
20173
+
20174
+ // src/batchWatchdog.ts
20175
+ var MIN2, NUDGE_GRACE_MIN, FREEZE_MIN, KILL_STRIKES, AUX_TIMEOUT_MS, RETRY_MAX, NUDGE_1, NUDGE_2, RETRY_PREAMBLE, ADJUDICATE_PROMPT, parseVerdictJson, defaultAdjudicate, sharedBudgetYuan, BatchWatchdog, buildRetryTaskContent;
20176
+ var init_batchWatchdog = __esm({
20177
+ "src/batchWatchdog.ts"() {
20178
+ "use strict";
20179
+ init_stallDetector();
20180
+ MIN2 = 6e4;
20181
+ NUDGE_GRACE_MIN = 5;
20182
+ FREEZE_MIN = 5;
20183
+ KILL_STRIKES = 3;
20184
+ AUX_TIMEOUT_MS = 6e4;
20185
+ RETRY_MAX = 3;
20186
+ NUDGE_1 = (evidence) => `\u26A0\uFE0F [watchdog] ${evidence}\u3002\u82E5\u5728\u539F\u5730\u6253\u8F6C\u8BF7\u6362\u601D\u8DEF\u6216\u6536\u5C3E\uFF1B\u82E5\u5224\u65AD\u6709\u8BEF\u8BF7\u7EE7\u7EED\u5E76\u8BF4\u660E\u7406\u7531\u3002`;
20187
+ NUDGE_2 = (clause) => `\u26D4 [watchdog] \u518D\u6B21\u5224\u5B9A\uFF1A${clause}\u3002\u8BF7\u7ACB\u5373\u505C\u6B62\u63A2\u7D22\uFF0C\u5728 2-3 \u8F6E\u5185\u6536\u5C3E\uFF1A\u603B\u7ED3\u5DF2\u5B8C\u6210 / \u672A\u5B8C\u6210 / \u5361\u70B9\u4E0E\u5EFA\u8BAE\uFF0C\u4F5C\u4E3A\u6700\u7EC8\u7B54\u590D\u3002`;
20188
+ RETRY_PREAMBLE = (verdict, evidence, finalDigest) => `\u3010\u7CFB\u7EDF\u63D0\u793A\u3011\u4E0A\u6B21\u5C1D\u8BD5\u88AB watchdog \u7EC8\u6B62\uFF08\u5224\u5B9A ${verdict}\uFF1A${evidence}\uFF09\u3002\u4E0A\u8F6E\u6536\u5C3E\u524D\u4EA7\u51FA\u6458\u8981\uFF1A${finalDigest || "\uFF08\u65E0\uFF09"}\u3002\u8BF7\u6362\u601D\u8DEF\uFF0C\u52FF\u91CD\u590D\u4E0A\u6B21\u8DEF\u5F84\u3002
20189
+
20190
+ ---
20191
+
20192
+ `;
20193
+ ADJUDICATE_PROMPT = (args) => `\u4F60\u662F\u6279\u5904\u7406\u7F16\u7801\u4EFB\u52A1\u7684\u770B\u95E8\u72D7\u3002\u5224\u65AD\u4EFB\u52A1\u662F\u5426\u5728\u7A7A\u8F6C\uFF08idle\uFF1A\u53CD\u590D\u8BFB\u540C\u4E00\u6279\u6587\u4EF6/\u6362\u59FF\u52BF\u641C\u540C\u4E00\u4E1C\u897F\uFF09\u6216\u65B9\u5411\u504F\u79BB\uFF08offtrack\uFF1A\u5199\u5165\u843D\u70B9\u4E0E\u4EFB\u52A1\u76EE\u6807\u65E0\u5173\uFF09\uFF1B\u6B63\u5E38\u63A8\u8FDB\uFF08\u542B\u6B63\u5F53\u7684\u957F\u65F6\u95F4\u53EA\u8BFB\u8C03\u7814\uFF09\u5224 normal\u3002\u53EA\u8F93\u51FA\u4E25\u683C JSON\uFF1A{"verdict":"normal|idle|offtrack","evidence":"\u4E00\u53E5\u8BDD\u8BC1\u636E\uFF08\u5F15\u7528\u8F68\u8FF9\u4E2D\u7684\u5177\u4F53\u4E8B\u4EF6\uFF09"}
20194
+
20195
+ [\u4EFB\u52A1\u76EE\u6807] ${args.title}
20196
+ \u9A8C\u6536\u547D\u4EE4\uFF1A${args.accept}
20197
+ [\u6D88\u8017] ${args.costLine}
20198
+ [\u89C4\u5219\u4FE1\u53F7]
20199
+ ${args.rules}
20200
+ [\u6700\u8FD1\u8F68\u8FF9\uFF0810 \u5206\u949F\uFF09]
20201
+ ${args.trajectory}`;
20202
+ parseVerdictJson = (raw) => {
20203
+ const m = /\{[\s\S]*\}/.exec(raw ?? "");
20204
+ if (!m) return null;
20205
+ try {
20206
+ const j = JSON.parse(m[0]);
20207
+ const v = String(j?.verdict ?? "").trim().toLowerCase();
20208
+ if (v !== "normal" && v !== "idle" && v !== "offtrack") return null;
20209
+ return { verdict: v, evidence: String(j?.evidence ?? "").trim() };
20210
+ } catch {
20211
+ return null;
20212
+ }
20213
+ };
20214
+ defaultAdjudicate = async (prompt, signal) => {
20215
+ const { chatWithModelWithSummary: chatWithModelWithSummary2 } = await Promise.resolve().then(() => (init_model(), model_exports));
20216
+ const resp = await chatWithModelWithSummary2([{ role: "user", content: prompt }], [], { signal });
20217
+ return resp?.choices?.[0]?.message?.content ?? "";
20218
+ };
20219
+ sharedBudgetYuan = (budgetYuan, spentYuan) => {
20220
+ if (budgetYuan === void 0) return void 0;
20221
+ return Math.max(0, Math.round((budgetYuan - (spentYuan ?? 0)) * 1e4) / 1e4);
20222
+ };
20223
+ BatchWatchdog = class {
20224
+ constructor(opts) {
20225
+ /** 连续非 normal 计数(normal 裁决/健康短路清零)。 */
20226
+ this.streak = 0;
20227
+ /** 收尾冻结窗口截止(nudge-2 后 FREEZE_MIN 分钟内动作冻结——采样/R3 照常,二层不调)。 */
20228
+ this.freezeUntil = 0;
20229
+ this.records = [];
20230
+ this.killInfo = null;
20231
+ this.opts = opts;
20232
+ this.now = opts.now ?? Date.now;
20233
+ this.detector = new StallDetector({
20234
+ worktreePath: opts.worktreePath,
20235
+ targetText: opts.targetText,
20236
+ budgetYuan: opts.budgetYuan,
20237
+ getCost: opts.getCost,
20238
+ sampleDiff: opts.sampleDiff,
20239
+ now: opts.now,
20240
+ schedule: opts.schedule,
20241
+ intervalMs: opts.intervalMs,
20242
+ onEvaluation: (e) => this.consume(e)
20243
+ // promise 会被 detector tick await(AUX 裁决 tick 内完成)
20244
+ });
20245
+ }
20246
+ /** 启动旁观者心跳。判定在解除暂停前不会真正开始(setup 执行期挂 PAUSED,batch 方案 §4)。 */
20247
+ start() {
20248
+ this.detector.start();
20249
+ }
20250
+ stop() {
20251
+ this.detector.stop();
20252
+ }
20253
+ /** 暂停判定(setup/accept 命令执行期豁免);解除时对齐事件时钟并重置起步宽限。 */
20254
+ setPaused(p) {
20255
+ this.detector.setPaused(p);
20256
+ }
20257
+ /** R6 基线:setup 完成后取首个读数(装依赖动 lockfile,故基线后置 + 排除 lockfile)。 */
20258
+ markBaseline() {
20259
+ return this.detector.markBaseline();
20260
+ }
20261
+ feed(evt) {
20262
+ this.detector.feed(evt);
20263
+ }
20264
+ /** 判定一步(测试可直接驱动;生产由 scheduler 每 tick 调)。自身异常绝不外泄。 */
20265
+ async tick() {
20266
+ return this.detector.tick();
20267
+ }
20268
+ /** 终止证据快照(runner 据此置 watchdog-killed 与重试前置锚)。 */
20269
+ getKill() {
20270
+ return this.killInfo;
20271
+ }
20272
+ /** 报告证据链快照(跨重试轮由 runner 聚合)。 */
20273
+ snapshot() {
20274
+ return { kill: this.killInfo, records: [...this.records], roundsAtKill: this.detector.round };
20275
+ }
20276
+ // —— 求值消费:StallEvaluation → batch 动作(与影子政策是同一结果的两种消费,方案 §3) ——
20277
+ async consume(e) {
20278
+ if (e.skipped) return;
20279
+ if (e.silenceMin !== null) {
20280
+ this.kill({ verdict: "stall", rule: "R3", evidence: `\u65AD\u6D41\uFF1A${R3_SILENCE_MIN} \u5206\u949F\u65E0\u4EFB\u4F55\u4E8B\u4EF6\uFF08agent \u53EF\u80FD\u5DF2\u5361\u6B7B\uFF09` });
20281
+ return;
20282
+ }
20283
+ if (e.health) {
20284
+ this.streak = 0;
20285
+ this.records.push({ kind: "health", at: e.t, text: e.health });
20286
+ return;
20287
+ }
20288
+ if (e.strong.length === 0) {
20289
+ if (e.weak.length > 0) this.records.push({ kind: "suspicion", at: e.t, text: e.weak.map((r) => `${r.rule} ${r.evidence}`).join("\uFF1B") });
20290
+ return;
20291
+ }
20292
+ if (e.t < this.freezeUntil) {
20293
+ this.records.push({ kind: "suspicion", at: e.t, text: `\u6536\u5C3E\u51BB\u7ED3\u7A97\u53E3\u5185\uFF08nudge-2 \u540E ${FREEZE_MIN} \u5206\u949F\uFF09\uFF0C\u4EC5\u8BB0\u5F55\uFF1A${e.strong.map((r) => r.rule).join("+")}` });
20294
+ return;
20295
+ }
20296
+ const verdict = await this.adjudicate(e);
20297
+ if (verdict === null) {
20298
+ this.records.push({ kind: "aux-fail", at: e.t });
20299
+ return;
20300
+ }
20301
+ if (verdict.verdict === "normal") {
20302
+ this.streak = 0;
20303
+ this.records.push({ kind: "verdict", at: e.t, verdict: "normal", evidence: verdict.evidence });
20304
+ return;
20305
+ }
20306
+ this.streak += 1;
20307
+ this.records.push({ kind: "verdict", at: e.t, verdict: verdict.verdict, evidence: verdict.evidence });
20308
+ if (this.streak >= KILL_STRIKES) {
20309
+ this.kill({
20310
+ verdict: verdict.verdict,
20311
+ rule: e.strong.map((r) => r.rule).join("+"),
20312
+ evidence: `${verdict.evidence}\uFF08\u8FDE\u7EED ${this.streak} \u6B21\u975E normal\uFF09`
20313
+ });
20314
+ return;
20315
+ }
20316
+ this.nudge(this.streak, verdict.verdict, verdict.evidence, e.t);
20317
+ }
20318
+ /** 二层裁决。返回 null = 调用失败/非法输出(降级 normal,绝不误杀)。 */
20319
+ async adjudicate(e) {
20320
+ const costLine = await this.costLine(e.t);
20321
+ const prompt = ADJUDICATE_PROMPT({
20322
+ title: this.opts.ticketTitle,
20323
+ accept: this.opts.acceptCmd,
20324
+ costLine,
20325
+ rules: [
20326
+ ...e.strong.map((r) => `${r.rule}\uFF08\u5F3A\uFF09\uFF1A${r.evidence}`),
20327
+ ...e.weak.map((r) => `${r.rule}\uFF08\u5F31\uFF0C\u4EC5\u53C2\u8003\uFF09\uFF1A${r.evidence}`)
20328
+ ].join("\n") || "\uFF08\u65E0\uFF09",
20329
+ trajectory: this.trajectory(e.t)
20330
+ });
20331
+ try {
20332
+ const raw = await (this.opts.auxAdjudicate ?? defaultAdjudicate)(prompt, this.auxSignal());
20333
+ return parseVerdictJson(raw);
20334
+ } catch {
20335
+ return null;
20336
+ }
20337
+ }
20338
+ /** AUX 调用信号:任务 abort 或 60s 超时,先到先停(对齐 classifyRisk 合并信号形态)。 */
20339
+ auxSignal() {
20340
+ const ac = new AbortController();
20341
+ const timer = setTimeout(() => ac.abort(), AUX_TIMEOUT_MS);
20342
+ timer.unref?.();
20343
+ this.opts.ac.signal.addEventListener("abort", () => {
20344
+ clearTimeout(timer);
20345
+ ac.abort();
20346
+ }, { once: true });
20347
+ return ac.signal;
20348
+ }
20349
+ async costLine(t) {
20350
+ const cost = await this.detector.sampleCost();
20351
+ const mins = Math.round((t - this.detector.activeAtMs) / MIN2);
20352
+ const rounds = cost?.rounds ?? this.detector.round;
20353
+ const yuan = cost?.costYuan;
20354
+ return `\u5DF2\u7528\u65F6 ${mins} \u5206\u949F \xB7 \u8F6E\u6570 ${rounds}` + (yuan != null ? ` \xB7 \u82B1\u8D39 \xA5${yuan.toFixed(4)}` : "");
20355
+ }
20356
+ /** 最近 10 分钟轨迹(二层任务书):只读类按工具合并计数,写入类逐条带 args 摘要。 */
20357
+ trajectory(t) {
20358
+ const recent = this.detector.callsWithin(t, R1_WINDOW_MIN * MIN2);
20359
+ if (recent.length === 0) return "\uFF08\u65E0\u5DE5\u5177\u8C03\u7528\uFF09";
20360
+ const readonlyCounts = /* @__PURE__ */ new Map();
20361
+ const lines = [];
20362
+ for (const c of recent) {
20363
+ if (!c.isReadonly) {
20364
+ lines.push(`- ${c.toolName}${c.target ? ` \u2192 ${c.target}` : ""}\uFF08${c.argsDigest}\uFF09`);
20365
+ continue;
20366
+ }
20367
+ readonlyCounts.set(c.toolName, (readonlyCounts.get(c.toolName) ?? 0) + 1);
20368
+ }
20369
+ for (const [tool, n] of readonlyCounts) lines.push(`- ${tool} \xD7${n}\uFF08\u53EA\u8BFB\uFF09`);
20370
+ return lines.join("\n").slice(0, 2400);
20371
+ }
20372
+ nudge(streak, verdict, evidence, t) {
20373
+ const text = streak === 1 ? NUDGE_1(evidence) : NUDGE_2(`${verdict}\uFF1A${evidence}`);
20374
+ this.records.push({ kind: "nudge", at: t, level: streak, text });
20375
+ this.detector.setSuppressUntil(t + NUDGE_GRACE_MIN * MIN2);
20376
+ if (streak === 2) this.freezeUntil = t + FREEZE_MIN * MIN2;
20377
+ try {
20378
+ this.opts.onNudge(text);
20379
+ } catch {
20380
+ }
20381
+ }
20382
+ kill(info) {
20383
+ if (this.killInfo) return;
20384
+ this.killInfo = info;
20385
+ this.records.push({ kind: "kill", at: this.now(), verdict: info.verdict, evidence: `${info.rule} ${info.evidence}` });
20386
+ this.detector.stop();
20387
+ try {
20388
+ this.opts.onKill(new Error(`watchdog: ${info.verdict} \u2014 ${info.evidence}`), info);
20389
+ } catch {
20390
+ }
20391
+ }
20392
+ };
20393
+ buildRetryTaskContent = (body, kill, finalText) => RETRY_PREAMBLE(kill.verdict, kill.evidence, finalText.trim().replace(/\s+/g, " ").slice(0, 400)) + body;
20394
+ }
20395
+ });
20396
+
19167
20397
  // src/batchRunner.ts
19168
20398
  var batchRunner_exports = {};
19169
20399
  __export(batchRunner_exports, {
20400
+ buildReport: () => buildReport,
19170
20401
  parseWaitWindow: () => parseWaitWindow,
19171
20402
  runBatchQueue: () => runBatchQueue
19172
20403
  });
19173
20404
  import fs50 from "fs";
19174
20405
  import { spawn as spawn6 } from "child_process";
19175
- var BATCH_EDIT_TOOLS, BATCH_DEFAULT_DENYLIST, ACCEPT_TIMEOUT_MIN, ACCEPT_TAIL_LINES, emit2, ts, runAcceptCommand, collectSessionCost, buildReport, runOneTask, parseWaitWindow, nowMinutes, sleep, waitUntilWindow, runBatchQueue;
20406
+ var BATCH_EDIT_TOOLS, BATCH_DEFAULT_DENYLIST, ACCEPT_TIMEOUT_MIN, ACCEPT_TAIL_LINES, emit2, ts, runAcceptCommand, collectSessionCost, buildWatchdogLines, buildReport, runOneTask, parseWaitWindow, nowMinutes, sleep, waitUntilWindow, runBatchQueue;
19176
20407
  var init_batchRunner = __esm({
19177
20408
  "src/batchRunner.ts"() {
19178
20409
  "use strict";
@@ -19189,6 +20420,9 @@ var init_batchRunner = __esm({
19189
20420
  init_queue();
19190
20421
  init_batchStrings();
19191
20422
  init_strings();
20423
+ init_batchWatchdog();
20424
+ init_inbox();
20425
+ init_agentWake();
19192
20426
  BATCH_EDIT_TOOLS = /* @__PURE__ */ new Set(["edit_file", "create_file", "write_file", "move_file", "notebook_edit"]);
19193
20427
  BATCH_DEFAULT_DENYLIST = /* @__PURE__ */ new Set(["run_command", "delete_path", "run_in_background"]);
19194
20428
  ACCEPT_TIMEOUT_MIN = 10;
@@ -19258,6 +20492,33 @@ ${lines.slice(-ACCEPT_TAIL_LINES).join("\n")}` : text;
19258
20492
  if (c.costYuan === null) c.partialPrice = false;
19259
20493
  return c;
19260
20494
  };
20495
+ buildWatchdogLines = (snaps, costYuan) => {
20496
+ if (snaps.length === 0) return null;
20497
+ const T = batchT(getLocale());
20498
+ const render2 = (r) => {
20499
+ switch (r.kind) {
20500
+ case "suspicion":
20501
+ return T.wdSuspicionRec(r.text ?? "");
20502
+ case "verdict":
20503
+ return T.wdVerdictRec(r.verdict ?? "", r.evidence ?? "");
20504
+ case "nudge":
20505
+ return T.wdNudgeRec(r.level ?? 0, r.text ?? "");
20506
+ case "health":
20507
+ return T.wdHealthRec(r.text ?? "");
20508
+ case "aux-fail":
20509
+ return T.wdAuxFailRec();
20510
+ case "kill":
20511
+ return T.wdKillRec(r.verdict ?? "", r.evidence ?? "");
20512
+ }
20513
+ };
20514
+ const lines = [];
20515
+ for (const snap of snaps) {
20516
+ lines.push(...snap.records.map(render2));
20517
+ if (snap.kill) lines.push(T.wdAtKillRec(snap.roundsAtKill, costYuan !== null ? costYuan.toFixed(4) : "-"));
20518
+ }
20519
+ if (snaps.length > 1) lines.push(T.wdRetryRec(snaps.length - 1));
20520
+ return lines;
20521
+ };
19261
20522
  buildReport = (args) => {
19262
20523
  const { id, ticket, status, budgetTruncated, cost, receipt, finalText, accept, deniedTools, diff, wt } = args;
19263
20524
  const T = batchT(getLocale());
@@ -19280,7 +20541,11 @@ ${T.statusEntry(`${T.statusLabel(status)}${budgetNote}`)}
19280
20541
  ${T.costEntry(T.costSummary(cost))}
19281
20542
  ${T.receiptEntry(receipt ? T.receiptLine(receipt) : T.noReceipt)}
19282
20543
  ${args.errorMessage ? T.errorEntry(args.errorMessage) : ""}
19283
- ${T.taskH}
20544
+ ${args.watchdogLines?.length ? `${T.wdH}
20545
+
20546
+ ${args.watchdogLines.map(T.wdRecord).join("\n")}
20547
+
20548
+ ` : ""}${T.taskH}
19284
20549
 
19285
20550
  ${ticket.body}
19286
20551
 
@@ -19384,89 +20649,187 @@ ${parsed.error}`);
19384
20649
  emit2(`\u{1F4A5} [${task.id}] ${msg}`);
19385
20650
  return { status: "error", cost: cost2, budgetTruncated: false };
19386
20651
  }
19387
- setSessionWorktree(sessionId, wt);
19388
- if (t.setup) {
19389
- emit2(T.setupLine(t.setup));
19390
- const s = await runAcceptCommand(t.setup, wt.path);
19391
- if (s.code !== 0 || s.timedOut) {
19392
- const msg = `${T.setupFail(t.setup, s.code)}${s.timedOut ? T.acceptTimeoutSuffix : ""}
20652
+ const watchdogEnabled = process.env.DEEP_SEEK_BATCH_WATCHDOG !== "0";
20653
+ const wdSnapshots = [];
20654
+ const maxRounds = 1 + Math.min(Math.max(0, Math.floor(t.retry ?? 0)), RETRY_MAX);
20655
+ let content = t.body;
20656
+ let roundBudget = t.budgetYuan;
20657
+ let receipt;
20658
+ let finalText = "";
20659
+ let agentError;
20660
+ let budgetTruncated = false;
20661
+ let wdKill = null;
20662
+ let status;
20663
+ let accept = null;
20664
+ for (let round = 1; ; round++) {
20665
+ wdKill = null;
20666
+ finalText = "";
20667
+ receipt = void 0;
20668
+ agentError = void 0;
20669
+ let nudgeCount = 0;
20670
+ const roundAc = new AbortController();
20671
+ const wd = watchdogEnabled ? new BatchWatchdog({
20672
+ sessionId,
20673
+ ticketTitle: t.title,
20674
+ acceptCmd: t.accept,
20675
+ targetText: `${t.title}
20676
+ ${t.body}
20677
+ ${t.accept}${t.setup ? `
20678
+ ${t.setup}` : ""}`,
20679
+ ac: roundAc,
20680
+ worktreePath: wt.path,
20681
+ budgetYuan: roundBudget,
20682
+ getCost: () => collectSessionCost(sessionId),
20683
+ onNudge: (text) => {
20684
+ nudgeCount++;
20685
+ pushSessionInbox(sessionId, text);
20686
+ emit2(T.wdNudgeNote(task.id, nudgeCount));
20687
+ },
20688
+ onKill: (err, info) => {
20689
+ wdKill = info;
20690
+ emit2(T.wdKillNote(task.id, `${info.verdict} \u2014 ${info.evidence}`));
20691
+ roundAc.abort(err);
20692
+ }
20693
+ }) : null;
20694
+ wd?.start();
20695
+ wd?.setPaused(true);
20696
+ if (round === 1 && t.setup) {
20697
+ emit2(T.setupLine(t.setup));
20698
+ const s = await runAcceptCommand(t.setup, wt.path);
20699
+ if (s.code !== 0 || s.timedOut) {
20700
+ wd?.stop();
20701
+ const msg = `${T.setupFail(t.setup, s.code)}${s.timedOut ? T.acceptTimeoutSuffix : ""}
19393
20702
 
19394
20703
  \`\`\`
19395
20704
  ${s.output}
19396
20705
  \`\`\``;
19397
- await killBackgroundTasksUnder(wt.path).catch(() => {
19398
- });
19399
- const cost2 = await collectSessionCost(sessionId);
19400
- await finishTicket(task.id, buildReport({
19401
- id: task.id,
19402
- ticket: t,
19403
- status: "setup-failed",
19404
- budgetTruncated: false,
19405
- cost: cost2,
19406
- receipt: void 0,
19407
- finalText: "",
19408
- accept: null,
19409
- deniedTools: [],
19410
- diff: T.diffNone,
19411
- wt,
19412
- errorMessage: msg
19413
- }));
19414
- emit2(`\u{1F6E0}\uFE0F [${task.id}] ${T.setupFail(t.setup, s.code)}`);
19415
- return { status: "setup-failed", cost: cost2, budgetTruncated: false };
20706
+ await killBackgroundTasksUnder(wt.path).catch(() => {
20707
+ });
20708
+ const cost2 = await collectSessionCost(sessionId);
20709
+ await finishTicket(task.id, buildReport({
20710
+ id: task.id,
20711
+ ticket: t,
20712
+ status: "setup-failed",
20713
+ budgetTruncated: false,
20714
+ cost: cost2,
20715
+ receipt: void 0,
20716
+ finalText: "",
20717
+ accept: null,
20718
+ deniedTools: [],
20719
+ diff: T.diffNone,
20720
+ wt,
20721
+ errorMessage: msg
20722
+ }));
20723
+ emit2(`\u{1F6E0}\uFE0F [${task.id}] ${T.setupFail(t.setup, s.code)}`);
20724
+ return { status: "setup-failed", cost: cost2, budgetTruncated: false };
20725
+ }
19416
20726
  }
19417
- }
19418
- const ac = new AbortController();
19419
- const timer = setTimeout(() => ac.abort(new Error(T.timeoutAbort(timeoutMin))), timeoutMin * 6e4);
19420
- let receipt;
19421
- let finalText = "";
19422
- let agentError;
19423
- try {
19424
- await handleUnifiedChat(
19425
- { sessionId, content: t.body },
19426
- async () => {
19427
- },
19428
- (evt) => {
19429
- if (evt?.type === "text.delta") finalText += evt.text;
19430
- else if (evt?.type === "text.reset") finalText = "";
19431
- else if (evt?.type === "final") finalText = evt.text || finalText;
19432
- else if (evt?.type === "round.start") emit2(T.roundLine(ts(), evt.round));
19433
- },
19434
- ac.signal,
19435
- {
19436
- requestApproval: async (detail, meta) => {
19437
- if (allowApproval.has(meta.toolName)) return "allow-once";
19438
- deniedTools.push({ tool: meta.toolName, detail: detail.replace(/\n+/g, " ").slice(0, 140) });
19439
- return "deny";
20727
+ wd?.setPaused(false);
20728
+ await wd?.markBaseline();
20729
+ const timer = setTimeout(() => roundAc.abort(new Error(T.timeoutAbort(timeoutMin))), timeoutMin * 6e4);
20730
+ setSessionWorktree(sessionId, wt);
20731
+ registerWakeDriver(sessionId, async () => {
20732
+ emit2(`\u{1F916} [${task.id}] \u5B50\u4EE3\u7406\u5B8C\u6210\uFF0C\u5524\u9192 run \u7EED\u8DD1\u2026`);
20733
+ setSessionWorktree(sessionId, wt);
20734
+ const wakeAc = new AbortController();
20735
+ const wakeTimer = setTimeout(() => wakeAc.abort(new Error(T.timeoutAbort(timeoutMin))), timeoutMin * 6e4);
20736
+ try {
20737
+ await handleUnifiedChat(
20738
+ { sessionId, content: AGENT_DONE_SENTINEL },
20739
+ async () => {
20740
+ },
20741
+ (evt) => {
20742
+ if (evt?.type === "text.delta") finalText += evt.text;
20743
+ else if (evt?.type === "text.reset") finalText = "";
20744
+ else if (evt?.type === "final") finalText = evt.text || finalText;
20745
+ },
20746
+ wakeAc.signal,
20747
+ {
20748
+ requestApproval: async (detail, meta) => {
20749
+ if (allowApproval.has(meta.toolName)) return "allow-once";
20750
+ deniedTools.push({ tool: meta.toolName, detail: detail.replace(/\n+/g, " ").slice(0, 140) });
20751
+ return "deny";
20752
+ },
20753
+ onUIEvent: (evt) => {
20754
+ if (evt.type === "run.receipt") receipt = evt;
20755
+ },
20756
+ budgetYuan: roundBudget,
20757
+ toolSchemas: gatedSchemas
20758
+ }
20759
+ );
20760
+ } finally {
20761
+ clearTimeout(wakeTimer);
20762
+ clearSessionWorktree(sessionId);
20763
+ }
20764
+ });
20765
+ try {
20766
+ await handleUnifiedChat(
20767
+ { sessionId, content },
20768
+ async () => {
19440
20769
  },
19441
- onUIEvent: (evt) => {
19442
- if (evt.type === "run.receipt") receipt = evt;
20770
+ (evt) => {
20771
+ wd?.feed(evt);
20772
+ if (evt?.type === "text.delta") finalText += evt.text;
20773
+ else if (evt?.type === "text.reset") finalText = "";
20774
+ else if (evt?.type === "final") finalText = evt.text || finalText;
20775
+ else if (evt?.type === "round.start") emit2(T.roundLine(ts(), evt.round));
19443
20776
  },
19444
- budgetYuan: t.budgetYuan,
19445
- toolSchemas: gatedSchemas
20777
+ roundAc.signal,
20778
+ {
20779
+ requestApproval: async (detail, meta) => {
20780
+ if (allowApproval.has(meta.toolName)) return "allow-once";
20781
+ deniedTools.push({ tool: meta.toolName, detail: detail.replace(/\n+/g, " ").slice(0, 140) });
20782
+ return "deny";
20783
+ },
20784
+ onUIEvent: (evt) => {
20785
+ if (evt.type === "run.receipt") receipt = evt;
20786
+ },
20787
+ budgetYuan: roundBudget,
20788
+ toolSchemas: gatedSchemas
20789
+ }
20790
+ );
20791
+ } catch (e) {
20792
+ agentError = e instanceof Error ? e.message : String(e);
20793
+ } finally {
20794
+ clearTimeout(timer);
20795
+ wd?.stop();
20796
+ clearSessionWorktree(sessionId);
20797
+ }
20798
+ if (wd) wdSnapshots.push(wd.snapshot());
20799
+ budgetTruncated = receipt?.budgetTruncated === true;
20800
+ if (agentError) {
20801
+ status = "error";
20802
+ emit2(T.agentErrorLine(task.id, agentError));
20803
+ break;
20804
+ }
20805
+ if (wdKill) {
20806
+ const spent = await collectSessionCost(sessionId);
20807
+ const nextBudget = sharedBudgetYuan(t.budgetYuan, spent.costYuan);
20808
+ if (round < maxRounds && (nextBudget === void 0 || nextBudget > 0)) {
20809
+ emit2(T.wdRetryNote(task.id, round + 1));
20810
+ content = buildRetryTaskContent(t.body, wdKill, finalText);
20811
+ roundBudget = nextBudget;
20812
+ continue;
19446
20813
  }
19447
- );
19448
- } catch (e) {
19449
- agentError = e instanceof Error ? e.message : String(e);
19450
- } finally {
19451
- clearTimeout(timer);
19452
- clearSessionWorktree(sessionId);
19453
- }
19454
- const timedOut = ac.signal.aborted;
19455
- const budgetTruncated = receipt?.budgetTruncated === true;
19456
- let status;
19457
- let accept = null;
19458
- if (agentError) {
19459
- status = "error";
19460
- emit2(T.agentErrorLine(task.id, agentError));
19461
- } else if (timedOut) {
19462
- status = "timeout";
19463
- emit2(T.timeoutLine(task.id, timeoutMin));
19464
- } else {
20814
+ if (round < maxRounds) emit2(T.wdBudgetStopNote(task.id));
20815
+ status = "watchdog-killed";
20816
+ break;
20817
+ }
20818
+ if (roundAc.signal.aborted) {
20819
+ status = "timeout";
20820
+ emit2(T.timeoutLine(task.id, timeoutMin));
20821
+ break;
20822
+ }
20823
+ const quiet = await waitSessionQuiescent(sessionId, void 0, timeoutMin * 6e4);
20824
+ if (!quiet) emit2(`\u23F3 [${task.id}] \u5524\u9192\u94FE\u672A\u5728\u65F6\u9650\u5185\u6536\u655B\uFF0C\u6309\u73B0\u72B6\u9A8C\u6536`);
20825
+ wd?.setPaused(true);
19465
20826
  const r = await runAcceptCommand(t.accept, wt.path);
19466
20827
  accept = { cmd: t.accept, code: r.code, output: r.output, timedOut: r.timedOut };
19467
20828
  status = r.code === 0 && !r.timedOut ? "passed" : "accept-failed";
19468
20829
  emit2(T.acceptLine(status === "passed", task.id, r.code) + (r.timedOut ? T.acceptKilledSuffix : ""));
20830
+ break;
19469
20831
  }
20832
+ unregisterWakeDriver(sessionId);
19470
20833
  await killBackgroundTasksUnder(wt.path).catch(() => {
19471
20834
  });
19472
20835
  const diff = await harvestDiff(wt);
@@ -19483,7 +20846,8 @@ ${s.output}
19483
20846
  deniedTools,
19484
20847
  diff,
19485
20848
  wt,
19486
- errorMessage: agentError
20849
+ errorMessage: agentError,
20850
+ watchdogLines: buildWatchdogLines(wdSnapshots, cost.costYuan) ?? void 0
19487
20851
  }));
19488
20852
  emit2(` ${T.statusLabel(status)} \xB7 ${T.costSummary(cost)}`);
19489
20853
  emit2(T.artifactLine(wt.path, wt.branch));
@@ -19621,7 +20985,7 @@ init_prefs();
19621
20985
  init_updateCheck();
19622
20986
  init_trust();
19623
20987
  import { render } from "ink";
19624
- import os5 from "os";
20988
+ import os6 from "os";
19625
20989
  import path50 from "path";
19626
20990
  import { emitKeypressEvents } from "node:readline";
19627
20991
  import { jsx as jsx21 } from "react/jsx-runtime";
@@ -19771,7 +21135,7 @@ var main = async () => {
19771
21135
  process.stdout.write("\x1B[2J\x1B[3J\x1B[1;1H");
19772
21136
  const cwdIsHome = () => {
19773
21137
  try {
19774
- return path50.resolve(cwd).toLowerCase() === path50.resolve(os5.homedir()).toLowerCase();
21138
+ return path50.resolve(cwd).toLowerCase() === path50.resolve(os6.homedir()).toLowerCase();
19775
21139
  } catch {
19776
21140
  return false;
19777
21141
  }