deepseeker-code 1.0.62 → 1.0.63

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 +1296 -166
  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;
@@ -1975,8 +2024,19 @@ var init_replay = __esm({
1975
2024
  });
1976
2025
 
1977
2026
  // ../core/src/llm/model.ts
2027
+ var model_exports = {};
2028
+ __export(model_exports, {
2029
+ activeProvider: () => activeProvider,
2030
+ chatWithModelWithSummary: () => chatWithModelWithSummary,
2031
+ classifyToolRisk: () => classifyToolRisk,
2032
+ isContextLengthError: () => isContextLengthError2,
2033
+ isImageUnsupportedError: () => isImageUnsupportedError2,
2034
+ isTransientApiError: () => isTransientApiError,
2035
+ resetActiveProvider: () => resetActiveProvider,
2036
+ setActiveProvider: () => setActiveProvider
2037
+ });
1978
2038
  import { readFileSync as readFileSync4 } from "fs";
1979
- var resolveInitialProvider, activeProvider, chatWithModelWithSummary, classifyToolRisk;
2039
+ var resolveInitialProvider, activeProvider, setActiveProvider, resetActiveProvider, chatWithModelWithSummary, classifyToolRisk, isContextLengthError2, isTransientApiError, isImageUnsupportedError2;
1980
2040
  var init_model = __esm({
1981
2041
  "../core/src/llm/model.ts"() {
1982
2042
  "use strict";
@@ -1997,8 +2057,17 @@ var init_model = __esm({
1997
2057
  return createReplayProvider(Array.isArray(raw) ? { turns: raw } : raw);
1998
2058
  };
1999
2059
  activeProvider = resolveInitialProvider();
2060
+ setActiveProvider = (p) => {
2061
+ activeProvider = p;
2062
+ };
2063
+ resetActiveProvider = () => {
2064
+ activeProvider = deepseekProvider;
2065
+ };
2000
2066
  chatWithModelWithSummary = (messages, tools, opts) => activeProvider.summarize(messages, tools, opts);
2001
2067
  classifyToolRisk = (toolName, args, detail, signal, onAuxUsage) => activeProvider.classifyRisk(toolName, args, detail, signal, onAuxUsage);
2068
+ isContextLengthError2 = (e) => activeProvider.isContextLengthError(e);
2069
+ isTransientApiError = (e) => activeProvider.isTransientError(e);
2070
+ isImageUnsupportedError2 = (e) => activeProvider.isImageUnsupportedError(e);
2002
2071
  }
2003
2072
  });
2004
2073
 
@@ -5548,6 +5617,153 @@ var init_httpExecutor = __esm({
5548
5617
  }
5549
5618
  });
5550
5619
 
5620
+ // ../core/src/agent/agentWake.ts
5621
+ 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;
5622
+ var init_agentWake = __esm({
5623
+ "../core/src/agent/agentWake.ts"() {
5624
+ "use strict";
5625
+ init_inbox();
5626
+ init_transcript();
5627
+ 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";
5628
+ SUBAGENT_REPORT_MAX_CHARS = 4096;
5629
+ wakeDrivers = /* @__PURE__ */ new Map();
5630
+ wakeQueued = /* @__PURE__ */ new Set();
5631
+ busyRuns = /* @__PURE__ */ new Set();
5632
+ inFlightByParent = /* @__PURE__ */ new Map();
5633
+ quiesceWaiters = /* @__PURE__ */ new Map();
5634
+ registerWakeDriver = (sessionId, driver) => {
5635
+ if (!sessionId) return;
5636
+ wakeDrivers.set(sessionId, driver);
5637
+ };
5638
+ registerWakeDriverIfAbsent = (sessionId, driver) => {
5639
+ if (!sessionId || wakeDrivers.has(sessionId)) return;
5640
+ wakeDrivers.set(sessionId, driver);
5641
+ };
5642
+ unregisterWakeDriver = (sessionId) => {
5643
+ wakeDrivers.delete(sessionId);
5644
+ };
5645
+ markRunStart = (sessionId) => {
5646
+ if (!sessionId) return;
5647
+ busyRuns.add(sessionId);
5648
+ };
5649
+ markRunEnd = (sessionId) => {
5650
+ if (!sessionId) return;
5651
+ busyRuns.delete(sessionId);
5652
+ pumpWake(sessionId);
5653
+ notifyQuiesceWaiters(sessionId);
5654
+ };
5655
+ pumpWake = (sessionId) => {
5656
+ if (!wakeQueued.has(sessionId)) return;
5657
+ if (busyRuns.has(sessionId)) return;
5658
+ wakeQueued.delete(sessionId);
5659
+ const driver = wakeDrivers.get(sessionId);
5660
+ if (!driver) return;
5661
+ busyRuns.add(sessionId);
5662
+ void (async () => {
5663
+ try {
5664
+ await driver();
5665
+ } catch (e) {
5666
+ 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}`);
5667
+ } finally {
5668
+ busyRuns.delete(sessionId);
5669
+ notifyQuiesceWaiters(sessionId);
5670
+ pumpWake(sessionId);
5671
+ }
5672
+ })();
5673
+ };
5674
+ scheduleWake = (sessionId) => {
5675
+ if (!sessionId || wakeQueued.has(sessionId)) return;
5676
+ wakeQueued.add(sessionId);
5677
+ pumpWake(sessionId);
5678
+ notifyQuiesceWaiters(sessionId);
5679
+ };
5680
+ clearQueuedWake = (sessionId) => {
5681
+ wakeQueued.delete(sessionId);
5682
+ };
5683
+ notifySubagentDone = async (parentSessionId, report) => {
5684
+ if (!parentSessionId || !report) return;
5685
+ const delivered = pushSessionInbox(parentSessionId, report);
5686
+ if (!delivered) {
5687
+ try {
5688
+ await appendMessage({ sessionId: parentSessionId, role: "user", content: report });
5689
+ } catch {
5690
+ }
5691
+ }
5692
+ scheduleWake(parentSessionId);
5693
+ };
5694
+ buildSubagentDoneReport = (agentId, taskBrief, result) => {
5695
+ const status = result.status ?? (result.output ? "final" : "error");
5696
+ const statusLabel = status === "final" ? "final\uFF08\u6B63\u5E38\u5B8C\u6210\uFF09" : status === "aborted" ? "aborted\uFF08\u88AB\u4E2D\u6B62\uFF09" : "error\uFF08\u5F02\u5E38\uFF09";
5697
+ const over = result.output.length > SUBAGENT_REPORT_MAX_CHARS;
5698
+ const body = over ? result.output.slice(0, SUBAGENT_REPORT_MAX_CHARS) : result.output;
5699
+ return [
5700
+ `\u{1F916} [\u5B50\u4EE3\u7406\u5B8C\u6210] \u{1F194} ${agentId}${taskBrief ? `\uFF08${taskBrief}\uFF09` : ""}`,
5701
+ `\u72B6\u6001\uFF1A${statusLabel}`,
5702
+ `\u6C47\u62A5\uFF1A`,
5703
+ body,
5704
+ over ? `
5705
+ \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` : ""
5706
+ ].filter((s) => s !== "").join("\n");
5707
+ };
5708
+ trackSubagentInFlight = (parentSessionId, subSessionId) => {
5709
+ if (!parentSessionId) return;
5710
+ let set = inFlightByParent.get(parentSessionId);
5711
+ if (!set) {
5712
+ set = /* @__PURE__ */ new Set();
5713
+ inFlightByParent.set(parentSessionId, set);
5714
+ }
5715
+ set.add(subSessionId);
5716
+ };
5717
+ releaseSubagentInFlight = (parentSessionId, subSessionId) => {
5718
+ const set = inFlightByParent.get(parentSessionId);
5719
+ if (!set) return;
5720
+ set.delete(subSessionId);
5721
+ if (set.size === 0) inFlightByParent.delete(parentSessionId);
5722
+ notifyQuiesceWaiters(parentSessionId);
5723
+ };
5724
+ notifyQuiesceWaiters = (sessionId) => {
5725
+ const waiters = quiesceWaiters.get(sessionId);
5726
+ if (!waiters || waiters.size === 0) return;
5727
+ for (const w of [...waiters]) w();
5728
+ };
5729
+ isQuiescent = (sessionId) => !inFlightByParent.get(sessionId)?.size && !busyRuns.has(sessionId) && !wakeQueued.has(sessionId);
5730
+ waitSessionQuiescent = (sessionId, signal, timeoutMs2) => {
5731
+ if (isQuiescent(sessionId)) return Promise.resolve(true);
5732
+ return new Promise((resolve) => {
5733
+ let settled = false;
5734
+ const finish = (ok) => {
5735
+ if (settled) return;
5736
+ settled = true;
5737
+ if (timer) clearTimeout(timer);
5738
+ if (onAbort) signal?.removeEventListener("abort", onAbort);
5739
+ const set = quiesceWaiters.get(sessionId);
5740
+ if (set) {
5741
+ set.delete(check);
5742
+ if (set.size === 0) quiesceWaiters.delete(sessionId);
5743
+ }
5744
+ resolve(ok);
5745
+ };
5746
+ const check = () => {
5747
+ if (isQuiescent(sessionId)) finish(true);
5748
+ };
5749
+ let timer;
5750
+ const onAbort = () => finish(false);
5751
+ if (!quiesceWaiters.has(sessionId)) quiesceWaiters.set(sessionId, /* @__PURE__ */ new Set());
5752
+ quiesceWaiters.get(sessionId).add(check);
5753
+ if (signal) {
5754
+ if (signal.aborted) {
5755
+ finish(false);
5756
+ return;
5757
+ }
5758
+ signal.addEventListener("abort", onAbort, { once: true });
5759
+ }
5760
+ if (timeoutMs2 != null && timeoutMs2 > 0) timer = setTimeout(() => finish(false), timeoutMs2);
5761
+ check();
5762
+ });
5763
+ };
5764
+ }
5765
+ });
5766
+
5551
5767
  // ../core/src/agent/repeatBreaker.ts
5552
5768
  var createRepeatBreaker;
5553
5769
  var init_repeatBreaker = __esm({
@@ -5889,7 +6105,7 @@ ${style.body}`);
5889
6105
  });
5890
6106
 
5891
6107
  // ../core/src/memory/registry.ts
5892
- var memories, registerMemory, listMemories, getMemory, unregisterMemory, clearMemories, memoryIndexLine, getMemoryIndex;
6108
+ var memories, registerMemory, listMemories, getMemory, unregisterMemory, clearMemories, memoryIndexLine, MEMORY_LIMITS, getMemoryIndex;
5893
6109
  var init_registry6 = __esm({
5894
6110
  "../core/src/memory/registry.ts"() {
5895
6111
  "use strict";
@@ -5908,6 +6124,7 @@ var init_registry6 = __esm({
5908
6124
  };
5909
6125
  clearMemories = memories.clear;
5910
6126
  memoryIndexLine = (m) => `- **${m.name}** (${m.type}) \u2014 ${m.description}`;
6127
+ MEMORY_LIMITS = { maxCount: 200, maxIndexBytes: 16 * 1024 };
5911
6128
  getMemoryIndex = () => {
5912
6129
  const all = memories.list();
5913
6130
  if (all.length === 0) return null;
@@ -6102,7 +6319,12 @@ var init_agent = __esm({
6102
6319
  type: "function",
6103
6320
  function: {
6104
6321
  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",
6322
+ description: [
6323
+ "\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",
6324
+ "\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",
6325
+ "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",
6326
+ "\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"
6327
+ ].join("\n"),
6106
6328
  parameters: {
6107
6329
  type: "object",
6108
6330
  properties: {
@@ -6114,30 +6336,29 @@ var init_agent = __esm({
6114
6336
  required: ["task"]
6115
6337
  },
6116
6338
  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(
6339
+ isSync: false,
6340
+ async *execute(args, ctx) {
6341
+ if (!ctx) {
6342
+ yield toolFailure("[\u6D3E\u751F\u5931\u8D25]\uFF1Aspawn_agent \u7F3A\u5C11\u5FC5\u987B\u7684\u667A\u80FD\u4F53\u8FD0\u884C\u4E0A\u4E0B\u6587\u3002");
6343
+ return;
6344
+ }
6345
+ const prep = await prepareSubagent(
6121
6346
  { task: args.task, name: args.name, role: args.role, resumeSessionId: args.resume_session_id },
6122
6347
  ctx,
6123
6348
  getGlobalTools
6124
6349
  );
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("");
6350
+ if (!prep.ok) {
6351
+ yield asToolFailure(prep.error);
6352
+ return;
6353
+ }
6354
+ const agentId = prep.prepared.subSessionId;
6355
+ yield [
6356
+ `\u2705 \u5B50\u4EE3\u7406\u5DF2\u5F02\u6B65\u542F\u52A8 \u{1F194} ${agentId}${prep.prepared.manifest ? `\uFF08\u58F0\u660E\u5F0F=${prep.prepared.manifest.name}\uFF09` : ""}`,
6357
+ `\u4EFB\u52A1\u6458\u8981\uFF1A${prep.prepared.taskBrief}${args.task.length > 60 ? "\u2026" : ""}`,
6358
+ `\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`,
6359
+ `\uFF08\u8FFD\u95EE/\u7EED\u8DD1\uFF1Aspawn_agent \u4F20 resume_session_id="${agentId}"\uFF0C\u5176\u5386\u53F2\u8BB0\u5FC6\u5B8C\u6574\u4FDD\u7559\u3002\uFF09`
6360
+ ].join("\n");
6361
+ await runPreparedSubagent(prep.prepared, ctx, { notifyDone: true });
6141
6362
  }
6142
6363
  }
6143
6364
  }
@@ -6370,6 +6591,7 @@ var init_workflow = __esm({
6370
6591
  init_config();
6371
6592
  init_guard();
6372
6593
  init_manager();
6594
+ init_agentWake();
6373
6595
  init_workflowHelpers();
6374
6596
  createWorkflowTools = (getGlobalTools) => [
6375
6597
  {
@@ -6377,7 +6599,8 @@ var init_workflow = __esm({
6377
6599
  function: {
6378
6600
  name: "run_workflow",
6379
6601
  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",
6602
+ "\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",
6603
+ "\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
6604
  '\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
6605
  "\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
6606
  ].join("\n"),
@@ -6417,18 +6640,29 @@ var init_workflow = __esm({
6417
6640
  required: ["steps"]
6418
6641
  },
6419
6642
  safetyLevel: "safe" /* SAFE */,
6420
- isSync: true,
6643
+ isSync: false,
6421
6644
  // 聚合输出整体兜底截断(各子 agent 结果已按 workflowPerStepChars 单独截断)。
6645
+ // ★ 异步化后工具结果 = 首个 yield 的启动回执(天然短);此上限改用于终态汇总的 inbox 报文截断。
6422
6646
  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");
6647
+ // ★ 子代理异步化(方案 §4.4):async* + 首个 yield 立即回执;全部步骤在 generator 挂起期间后台执行,
6648
+ // 完成后一条终态汇总经 agentWake.notifySubagentDone 回流(中间态不回流,防事件洪峰)。
6649
+ // 宿主中止时执行层 gen.return() 拦在 await 前 → 不产生中止后的唤醒(与 spawn_agent 同语义)。
6650
+ async *execute(args, ctx) {
6651
+ if (!ctx) {
6652
+ yield toolFailure("[\u5DE5\u4F5C\u6D41]\uFF1Arun_workflow \u7F3A\u5C11\u5FC5\u987B\u7684\u667A\u80FD\u4F53\u8FD0\u884C\u4E0A\u4E0B\u6587\u3002");
6653
+ return;
6654
+ }
6425
6655
  const maxSteps = appConfig.workflowMaxSteps;
6426
6656
  const validationError = validateWorkflowArgs(args, maxSteps);
6427
- if (validationError) return validationError;
6657
+ if (validationError) {
6658
+ yield validationError;
6659
+ return;
6660
+ }
6428
6661
  const mode = args.mode === "pipeline" ? "pipeline" : "parallel";
6429
6662
  const isolation = args.isolation === "worktree" ? "worktree" : "none";
6430
6663
  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');
6664
+ 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');
6665
+ return;
6432
6666
  }
6433
6667
  const steps = args.steps.map((s) => ({
6434
6668
  task: String(s.task),
@@ -6437,8 +6671,13 @@ var init_workflow = __esm({
6437
6671
  key: s.key ? String(s.key) : void 0
6438
6672
  }));
6439
6673
  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`);
6674
+ 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`);
6675
+ return;
6441
6676
  }
6677
+ yield [
6678
+ `\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`,
6679
+ `\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`
6680
+ ].join("\n");
6442
6681
  const approvalMutex = createSemaphore(1);
6443
6682
  const serializedApproval = ctx.requestApproval ? async (...raArgs) => {
6444
6683
  await approvalMutex.acquire();
@@ -6521,9 +6760,25 @@ var init_workflow = __esm({
6521
6760
  outcomes = await Promise.all(running);
6522
6761
  }
6523
6762
  } catch (err) {
6524
- return toolFailure(`[\u5DE5\u4F5C\u6D41\u7F16\u6392\u5F02\u5E38]\uFF1A${err?.message ?? err}`);
6763
+ await notifySubagentDone(ctx.sessionId, [
6764
+ `\u{1F916} [\u5DE5\u4F5C\u6D41\u5B8C\u6210]\uFF08mode=${mode}\uFF0C\u5171 ${steps.length} \u6B65\uFF09`,
6765
+ `\u72B6\u6001\uFF1Aerror\uFF08\u7F16\u6392\u5F02\u5E38\uFF09`,
6766
+ `\u6C47\u603B\uFF1A`,
6767
+ `\u26A0\uFE0F [\u5DE5\u4F5C\u6D41\u7F16\u6392\u5F02\u5E38]\uFF1A${err?.message ?? err}`
6768
+ ].join("\n"));
6769
+ return;
6525
6770
  }
6526
- return formatWorkflowResult(mode, outcomes);
6771
+ const allOk = outcomes.every((o) => o.ok);
6772
+ const abortedCount = outcomes.filter((o) => !o.ok && o.output.includes("\u5DF2\u4E2D\u6B62")).length;
6773
+ const status = abortedCount === outcomes.length ? "aborted" : allOk ? "final" : "error";
6774
+ 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";
6775
+ await notifySubagentDone(ctx.sessionId, [
6776
+ `\u{1F916} [\u5DE5\u4F5C\u6D41\u5B8C\u6210]\uFF08mode=${mode}\uFF0C\u5171 ${steps.length} \u6B65\uFF09`,
6777
+ `\u72B6\u6001\uFF1A${statusLabel}`,
6778
+ `\u6C47\u603B\uFF1A`,
6779
+ // 截断口径与原同步返回一致(maxOutputCharacters 18000)
6780
+ truncateToolResult(formatWorkflowResult(mode, outcomes), 18e3)
6781
+ ].join("\n"));
6527
6782
  }
6528
6783
  }
6529
6784
  }
@@ -10797,8 +11052,9 @@ var init_frontmatter = __esm({
10797
11052
 
10798
11053
  // ../core/src/memory/loader.ts
10799
11054
  import fs28 from "fs/promises";
11055
+ import os5 from "os";
10800
11056
  import path30 from "path";
10801
- var GLOBAL_MEMORY_DIR, SOURCES, VALID_TYPES, parseMemoryAt, loadMemories, initMemories;
11057
+ 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
11058
  var init_loader3 = __esm({
10803
11059
  "../core/src/memory/loader.ts"() {
10804
11060
  "use strict";
@@ -10812,6 +11068,7 @@ var init_loader3 = __esm({
10812
11068
  { dir: path30.join(process.cwd(), ".deepseeker-code", "memory"), source: "project" }
10813
11069
  ];
10814
11070
  VALID_TYPES = ["user", "feedback", "project", "reference"];
11071
+ NAME_RE = /^[a-z0-9-]+$/;
10815
11072
  parseMemoryAt = async (file, _dir, source) => {
10816
11073
  let raw;
10817
11074
  try {
@@ -10830,7 +11087,7 @@ var init_loader3 = __esm({
10830
11087
  console.warn(`\u26A0\uFE0F [memory] \u7F3A\u5C11 name \u6216 description\uFF08${file}\uFF09\uFF0C\u5DF2\u8DF3\u8FC7`);
10831
11088
  return null;
10832
11089
  }
10833
- if (!/^[a-z0-9-]+$/.test(name)) {
11090
+ if (!NAME_RE.test(name)) {
10834
11091
  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
11092
  return null;
10836
11093
  }
@@ -10845,6 +11102,128 @@ var init_loader3 = __esm({
10845
11102
  }
10846
11103
  return { name, description, type, body: parsed.body.trim(), source, file };
10847
11104
  };
11105
+ MAX_EXTERNAL_ITEMS = 12;
11106
+ EXTERNAL_DESC_MAX_CHARS = 120;
11107
+ EXTERNAL_BODY_MAX_BYTES = 64 * 1024;
11108
+ EXTERNAL_RULES = [
11109
+ // 全局层(~ 下,存在才扫,缺省静默跳过)
11110
+ { dir: path30.join(os5.homedir(), ".claude"), file: "CLAUDE.md", name: "ext-claude", projectLayer: false },
11111
+ { dir: path30.join(os5.homedir(), ".codex"), file: "AGENTS.md", name: "ext-codex", projectLayer: false },
11112
+ { dir: path30.join(os5.homedir(), ".gemini"), file: "GEMINI.md", name: "ext-gemini", projectLayer: false },
11113
+ // 项目层(<cwd> 下)
11114
+ { dir: process.cwd(), file: "CLAUDE.md", name: "ext-claude-project", projectLayer: true },
11115
+ { dir: process.cwd(), file: "AGENTS.md", name: "ext-agents", projectLayer: true },
11116
+ { dir: process.cwd(), file: "GEMINI.md", name: "ext-gemini-project", projectLayer: true },
11117
+ { dir: path30.join(process.cwd(), ".cursor", "rules"), name: (stem) => `ext-cursor-${stem}`, projectLayer: true },
11118
+ { dir: path30.join(process.cwd(), ".github"), file: "copilot-instructions.md", name: "ext-copilot", projectLayer: true }
11119
+ ];
11120
+ sanitizeExternalStem = (stem) => stem.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
11121
+ extractExternalTitle = (raw, fallbackStem) => {
11122
+ const lines = raw.split(/\r?\n/);
11123
+ for (const line of lines) {
11124
+ const m = /^#\s+(.+)$/.exec(line);
11125
+ if (m) return m[1].trim();
11126
+ }
11127
+ for (const line of lines) {
11128
+ const t = line.trim();
11129
+ if (t) return t.replace(/^#+\s*/, "") || fallbackStem;
11130
+ }
11131
+ return fallbackStem;
11132
+ };
11133
+ buildExternalDescription = (label, title) => {
11134
+ const full = `[\u5916\u90E8:${label}] ${title.replace(/\s+/g, " ").trim()}`;
11135
+ return full.length > EXTERNAL_DESC_MAX_CHARS ? full.slice(0, EXTERNAL_DESC_MAX_CHARS) : full;
11136
+ };
11137
+ truncateUtf8ToBytes = (text, maxBytes) => {
11138
+ const buf = Buffer.from(text, "utf-8");
11139
+ if (buf.length <= maxBytes) return { text, truncated: false };
11140
+ return { text: buf.subarray(0, maxBytes).toString("utf-8"), truncated: true };
11141
+ };
11142
+ scanExternalRule = async (rule) => {
11143
+ let entries;
11144
+ try {
11145
+ entries = await fs28.readdir(rule.dir, { withFileTypes: true });
11146
+ } catch {
11147
+ return [];
11148
+ }
11149
+ const out = [];
11150
+ for (const entry of entries) {
11151
+ if (!entry.isFile()) continue;
11152
+ if (rule.file ? entry.name !== rule.file : !entry.name.endsWith(".md")) continue;
11153
+ const stem = entry.name.replace(/\.md$/, "");
11154
+ const file = path30.join(rule.dir, entry.name);
11155
+ const name = typeof rule.name === "function" ? sanitizeExternalStem(stem) ? `ext-cursor-${sanitizeExternalStem(stem)}` : "" : rule.name;
11156
+ if (!name || !NAME_RE.test(name)) {
11157
+ 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`);
11158
+ continue;
11159
+ }
11160
+ let raw;
11161
+ try {
11162
+ raw = await fs28.readFile(file, "utf-8");
11163
+ } catch {
11164
+ continue;
11165
+ }
11166
+ const body = truncateUtf8ToBytes(raw.trim(), EXTERNAL_BODY_MAX_BYTES);
11167
+ if (!body.text) continue;
11168
+ 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`);
11169
+ const stat = await fs28.stat(file).catch(() => null);
11170
+ out.push({
11171
+ item: {
11172
+ name,
11173
+ description: buildExternalDescription(rule.file ?? entry.name, extractExternalTitle(raw, stem)),
11174
+ type: "reference",
11175
+ // 外部二手资料的语义即参考(§4),不冒充 user/feedback 画像
11176
+ body: body.text,
11177
+ source: "external",
11178
+ file
11179
+ },
11180
+ mtimeMs: stat?.mtimeMs ?? 0
11181
+ });
11182
+ }
11183
+ return out;
11184
+ };
11185
+ externalNoticeShown = false;
11186
+ loadExternalMemories = async (includeProject) => {
11187
+ if (!appConfig.memoryExternal) return 0;
11188
+ const candidates = [];
11189
+ for (const rule of EXTERNAL_RULES) {
11190
+ if (rule.projectLayer && !includeProject) continue;
11191
+ candidates.push(...await scanExternalRule(rule));
11192
+ }
11193
+ if (candidates.length === 0) return 0;
11194
+ const dropped = [];
11195
+ if (candidates.length > MAX_EXTERNAL_ITEMS) {
11196
+ candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
11197
+ for (const c of candidates.slice(MAX_EXTERNAL_ITEMS)) dropped.push(c.item.name);
11198
+ candidates.length = MAX_EXTERNAL_ITEMS;
11199
+ }
11200
+ const skipped = [];
11201
+ for (const { item } of candidates) {
11202
+ const all = listMemories();
11203
+ if (all.length >= MEMORY_LIMITS.maxCount) {
11204
+ skipped.push(item.name);
11205
+ continue;
11206
+ }
11207
+ const curBytes = all.length === 0 ? 0 : Buffer.byteLength(all.map(memoryIndexLine).join("\n"), "utf-8");
11208
+ if (curBytes + Buffer.byteLength(memoryIndexLine(item), "utf-8") > MEMORY_LIMITS.maxIndexBytes) {
11209
+ skipped.push(item.name);
11210
+ continue;
11211
+ }
11212
+ registerMemory(item);
11213
+ }
11214
+ if (dropped.length > 0) {
11215
+ 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")}`);
11216
+ }
11217
+ if (skipped.length > 0) {
11218
+ 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")}`);
11219
+ }
11220
+ const n = candidates.length - skipped.length;
11221
+ if (n > 0 && !externalNoticeShown) {
11222
+ externalNoticeShown = true;
11223
+ 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");
11224
+ }
11225
+ return n;
11226
+ };
10848
11227
  loadMemories = async (includeProject) => {
10849
11228
  clearMemories();
10850
11229
  const picked = await scanSources(
@@ -10853,6 +11232,7 @@ var init_loader3 = __esm({
10853
11232
  parseMemoryAt
10854
11233
  );
10855
11234
  for (const { item } of picked) registerMemory(item);
11235
+ await loadExternalMemories(includeProject);
10856
11236
  return listMemories().length;
10857
11237
  };
10858
11238
  initMemories = async (includeProject) => {
@@ -10900,7 +11280,7 @@ var init_usage = __esm({
10900
11280
  // ../core/src/tool/registry/memory.ts
10901
11281
  import fs30 from "fs/promises";
10902
11282
  import path32 from "path";
10903
- var MAX_BODY_BYTES, MAX_DESCRIPTION_CHARS, MEMORY_LIMITS, NAME_RE, VALID_TYPES2, memoryFilePath, sanitizeLine, buildMemoryMd, writeAndRegister, indexLineBytes, memoryTools;
11283
+ var MAX_BODY_BYTES, MAX_DESCRIPTION_CHARS, NAME_RE2, VALID_TYPES2, memoryFilePath, sanitizeLine, buildMemoryMd, writeAndRegister, indexLineBytes, memoryTools;
10904
11284
  var init_memory = __esm({
10905
11285
  "../core/src/tool/registry/memory.ts"() {
10906
11286
  "use strict";
@@ -10911,8 +11291,7 @@ var init_memory = __esm({
10911
11291
  init_usage();
10912
11292
  MAX_BODY_BYTES = 64 * 1024;
10913
11293
  MAX_DESCRIPTION_CHARS = 200;
10914
- MEMORY_LIMITS = { maxCount: 200, maxIndexBytes: 16 * 1024 };
10915
- NAME_RE = /^[a-z0-9-]+$/;
11294
+ NAME_RE2 = /^[a-z0-9-]+$/;
10916
11295
  VALID_TYPES2 = ["user", "feedback", "project", "reference"];
10917
11296
  memoryFilePath = (name, project) => project ? path32.join(process.cwd(), ".deepseeker-code", "memory", `${name}.md`) : path32.join(GLOBAL_MEMORY_DIR, `${name}.md`);
10918
11297
  sanitizeLine = (s) => String(s ?? "").replace(/[\r\n]+/g, " ").trim();
@@ -10975,7 +11354,8 @@ ${body.trim()}
10975
11354
  const body = typeof args?.body === "string" ? args.body : "";
10976
11355
  const type = sanitizeLine(args?.type) || "reference";
10977
11356
  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`);
11357
+ 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`);
11358
+ 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
11359
  if (!description) return toolFailure("[memory_save] description \u4E0D\u80FD\u4E3A\u7A7A\u3002");
10980
11360
  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
11361
  if (!body.trim()) return toolFailure("[memory_save] body \u4E0D\u80FD\u4E3A\u7A7A\u3002");
@@ -11002,7 +11382,7 @@ ${body.trim()}
11002
11382
  type: "function",
11003
11383
  function: {
11004
11384
  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",
11385
+ 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
11386
  parameters: {
11007
11387
  type: "object",
11008
11388
  properties: {
@@ -11061,6 +11441,7 @@ ${lines.join("\n")}`;
11061
11441
  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
11442
  async execute(args) {
11063
11443
  const name = sanitizeLine(args?.name);
11444
+ 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
11445
  const m = getMemory(name);
11065
11446
  if (!m) return toolFailure(`\u672A\u627E\u5230\u8BB0\u5FC6\uFF1A${name}\uFF08\u53EF\u80FD\u5DF2\u88AB\u5220\u9664\uFF09\u3002`);
11066
11447
  try {
@@ -12189,7 +12570,7 @@ var init_toolExecution = __esm({
12189
12570
  const isForegroundCommand = isCommandTool && matchedTool.function.isSync !== false;
12190
12571
  const cmdIsScriptRunner = isCommandTool && isScriptRunnerCommand(typeof state.calledArgs?.command === "string" ? state.calledArgs.command : "");
12191
12572
  const argCwd = typeof state.calledArgs?.cwd === "string" ? state.calledArgs.cwd.trim() : "";
12192
- const samePath = (a, b) => {
12573
+ const samePath2 = (a, b) => {
12193
12574
  try {
12194
12575
  const ra = path36.resolve(a), rb = path36.resolve(b);
12195
12576
  return process.platform === "win32" ? ra.toLowerCase() === rb.toLowerCase() : ra === rb;
@@ -12197,7 +12578,7 @@ var init_toolExecution = __esm({
12197
12578
  return false;
12198
12579
  }
12199
12580
  };
12200
- const scriptRunnerCwdDrift = cmdIsScriptRunner && argCwd !== "" && !samePath(argCwd, toolCtx.cwd ?? process.cwd());
12581
+ const scriptRunnerCwdDrift = cmdIsScriptRunner && argCwd !== "" && !samePath2(argCwd, toolCtx.cwd ?? process.cwd());
12201
12582
  const scriptRunnerBlocked = cmdIsScriptRunner && (perm === null || scriptRunnerCwdDrift);
12202
12583
  if (scriptRunnerCwdDrift) needApproval = true;
12203
12584
  if (perm === null && needApproval && !state.denied && !scriptRunnerBlocked && isForegroundCommand && isReadOnlyCommand(typeof state.calledArgs?.command === "string" ? state.calledArgs.command : "")) {
@@ -13186,7 +13567,7 @@ var init_prices = __esm({
13186
13567
  peak: { input: 9, output: 27, cacheHit: 0.3 }
13187
13568
  }
13188
13569
  };
13189
- PRICES_VERIFIED_AT = "2026-10-19";
13570
+ PRICES_VERIFIED_AT = "2026-11-03";
13190
13571
  PRICES_STALE_DAYS = 30;
13191
13572
  pricesAgeDays = () => Math.max(0, Math.floor((Date.now() - Date.parse(`${PRICES_VERIFIED_AT}T00:00:00Z`)) / 864e5));
13192
13573
  isBeijingPeak = (ts2 = /* @__PURE__ */ new Date()) => {
@@ -13843,6 +14224,7 @@ async function* runAgent(message, options) {
13843
14224
  }
13844
14225
  const inboxTexts = claimSessionInbox(sessionId);
13845
14226
  if (inboxTexts.length > 0) {
14227
+ clearQueuedWake(sessionId);
13846
14228
  for (const t of inboxTexts) {
13847
14229
  message.push({ role: "user", content: t });
13848
14230
  await appendMessage({ sessionId, role: "user", content: t });
@@ -14112,6 +14494,7 @@ var init_runAgent = __esm({
14112
14494
  "use strict";
14113
14495
  init_transcript();
14114
14496
  init_inbox();
14497
+ init_agentWake();
14115
14498
  init_common();
14116
14499
  init_contextCore();
14117
14500
  init_contentParts();
@@ -14198,7 +14581,7 @@ var init_lineage = __esm({
14198
14581
  });
14199
14582
 
14200
14583
  // ../core/src/agent/subagent.ts
14201
- var SUBAGENT_DENYLIST, filterToolsForSubagent, activeSubagents, failText, RESUME_NOTE, createProgressThrottler, runSubagent;
14584
+ var SUBAGENT_DENYLIST, filterToolsForSubagent, activeSubagents, failText, RESUME_NOTE, createProgressThrottler, prepareSubagent, runPreparedSubagent, runSubagent;
14202
14585
  var init_subagent = __esm({
14203
14586
  "../core/src/agent/subagent.ts"() {
14204
14587
  "use strict";
@@ -14211,6 +14594,7 @@ var init_subagent = __esm({
14211
14594
  init_registry4();
14212
14595
  init_hooks();
14213
14596
  init_lineage();
14597
+ init_agentWake();
14214
14598
  SUBAGENT_DENYLIST = /* @__PURE__ */ new Set(["run_command", "delete_path", "run_in_background"]);
14215
14599
  filterToolsForSubagent = (allTools, manifest) => {
14216
14600
  if (manifest && manifest.tools.length > 0) {
@@ -14245,57 +14629,33 @@ var init_subagent = __esm({
14245
14629
  }
14246
14630
  };
14247
14631
  };
14248
- runSubagent = async (spec, ctx, getGlobalTools) => {
14632
+ prepareSubagent = async (spec, ctx, getGlobalTools) => {
14249
14633
  const { task, name, role, resumeSessionId } = spec;
14250
14634
  const resuming = typeof resumeSessionId === "string" && resumeSessionId.length > 0;
14251
14635
  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
- };
14636
+ 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
14637
  }
14258
14638
  if (resuming) {
14259
14639
  const target = resumeSessionId;
14260
14640
  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
- };
14641
+ 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
14642
  }
14267
14643
  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
- };
14644
+ 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
14645
  }
14274
14646
  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
- };
14647
+ 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
14648
  }
14281
14649
  const existing = await readTranscriptLines(target);
14282
14650
  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
- };
14651
+ 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
14652
  }
14289
14653
  }
14290
14654
  const subSessionId = resuming ? resumeSessionId : `${ctx.sessionId}__sub__${createUUID()}`;
14291
14655
  const parentSystemPrompt = ctx.parentSystemPrompt || "";
14292
14656
  const manifest = name ? getAgent(name) : void 0;
14293
14657
  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
- };
14658
+ 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
14659
  }
14300
14660
  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
14661
  const subSystem = manifest ? [
@@ -14327,8 +14687,15 @@ var init_subagent = __esm({
14327
14687
  const allTools = getGlobalTools();
14328
14688
  const toolSchemas = filterToolsForSubagent(allTools, manifest);
14329
14689
  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)}..."`);
14690
+ const startCtx = { sessionId: subSessionId, parentSessionId: ctx.sessionId, task, depth: ctx.depth + 1, name: manifest?.name, cwd: ctx.cwd };
14691
+ return { ok: true, prepared: { subSessionId, resuming, manifest, subSystem, subTaskPrompt, toolSchemas, startCtx, taskBrief: task.slice(0, 60) } };
14692
+ };
14693
+ runPreparedSubagent = async (prepared, ctx, opts) => {
14694
+ const { subSessionId, manifest, subTaskPrompt, subSystem, toolSchemas, startCtx } = prepared;
14695
+ const manifestName = manifest?.name;
14330
14696
  activeSubagents.add(subSessionId);
14331
- ctx.onUIEvent?.({ type: "subagent.count", running: activeSubagents.size, phase: "start", name: manifest?.name, depth: ctx.depth + 1 });
14697
+ trackSubagentInFlight(ctx.sessionId, subSessionId);
14698
+ ctx.onUIEvent?.({ type: "subagent.count", running: activeSubagents.size, phase: "start", name: manifest?.name, depth: startCtx.depth });
14332
14699
  try {
14333
14700
  const subMessages = await buildContextMessages(subSessionId, { role: "user", content: subTaskPrompt }, subSystem, manifest?.model);
14334
14701
  await appendMessage({ sessionId: subSessionId, role: "user", content: subTaskPrompt });
@@ -14346,7 +14713,7 @@ var init_subagent = __esm({
14346
14713
  depth: ctx.depth + 1,
14347
14714
  keepRecentUnits: ctx.keepRecentUnits,
14348
14715
  compactRatio: ctx.compactRatio,
14349
- parentSystemPrompt,
14716
+ parentSystemPrompt: ctx.parentSystemPrompt,
14350
14717
  events: ctx.events,
14351
14718
  onUIEvent: ctx.onUIEvent,
14352
14719
  // ★ 必须透传:否则子 agent 调用需审批工具时前端收不到弹窗,waitForUserApproval 永久挂起(死锁)
@@ -14365,14 +14732,13 @@ var init_subagent = __esm({
14365
14732
  budgetCheck: ctx.budgetCheck
14366
14733
  // ★ P1-5 预算熔断链式透传:子 runAgent 轮边界探测父预算,超支即收尾(不等烧完)
14367
14734
  };
14368
- const startCtx = { sessionId: subSessionId, parentSessionId: ctx.sessionId, task, depth: ctx.depth + 1, name: manifest?.name, cwd: ctx.cwd };
14369
14735
  await dispatch("SubagentStart", startCtx);
14370
14736
  let result;
14371
14737
  const progress = createProgressThrottler((line) => ctx.onUIEvent?.({ type: "subagent.progress", text: line, agentKey: subSessionId, name: manifest?.name }));
14372
14738
  try {
14373
14739
  for await (const e of runAgent(subMessages, subOptions)) {
14374
14740
  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 };
14741
+ 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
14742
  break;
14377
14743
  }
14378
14744
  if (e.type === "final") {
@@ -14386,10 +14752,10 @@ var init_subagent = __esm({
14386
14752
  if (!hasFinalResult || !subResult.trim()) {
14387
14753
  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
14754
  }
14389
- result = { ok: true, output: subResult, sessionId: subSessionId, manifestName: manifest?.name };
14755
+ result = { ok: true, output: subResult, sessionId: subSessionId, manifestName: manifest?.name, status: "final" };
14390
14756
  }
14391
14757
  } 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 };
14758
+ 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
14759
  } finally {
14394
14760
  try {
14395
14761
  ctx.onUIEvent?.({ type: "subagent.progress", text: "", done: true, agentKey: subSessionId });
@@ -14399,13 +14765,27 @@ var init_subagent = __esm({
14399
14765
  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
14766
  }
14401
14767
  }
14402
- return result ?? { ok: false, output: failText(`[\u6D3E\u751F\u6267\u884C\u5931\u8D25]: \u672A\u77E5\u9519\u8BEF`), sessionId: subSessionId, manifestName: manifest?.name };
14768
+ if (opts?.notifyDone && result && result.status !== "aborted") {
14769
+ try {
14770
+ await notifySubagentDone(ctx.sessionId, buildSubagentDoneReport(subSessionId, prepared.taskBrief, { status: result.status, output: result.output }));
14771
+ } catch {
14772
+ }
14773
+ }
14774
+ return result ?? { ok: false, output: failText(`[\u6D3E\u751F\u6267\u884C\u5931\u8D25]: \u672A\u77E5\u9519\u8BEF`), sessionId: subSessionId, manifestName: manifest?.name, status: "error" };
14403
14775
  } catch (error) {
14404
- return { ok: false, output: failText(`[\u6D3E\u751F\u6267\u884C\u5931\u8D25]: ${error.message}`), sessionId: subSessionId, manifestName: manifest?.name };
14776
+ return { ok: false, output: failText(`[\u6D3E\u751F\u6267\u884C\u5931\u8D25]: ${error.message}`), sessionId: subSessionId, manifestName: manifest?.name, status: "error" };
14405
14777
  } finally {
14406
14778
  activeSubagents.delete(subSessionId);
14407
- ctx.onUIEvent?.({ type: "subagent.count", running: activeSubagents.size, phase: "stop", name: manifest?.name, depth: ctx.depth + 1 });
14779
+ releaseSubagentInFlight(ctx.sessionId, subSessionId);
14780
+ ctx.onUIEvent?.({ type: "subagent.count", running: activeSubagents.size, phase: "stop", name: manifestName, depth: startCtx.depth });
14781
+ }
14782
+ };
14783
+ runSubagent = async (spec, ctx, getGlobalTools) => {
14784
+ const prep = await prepareSubagent(spec, ctx, getGlobalTools);
14785
+ if (!prep.ok) {
14786
+ return { ok: false, output: prep.error, sessionId: "", status: "error" };
14408
14787
  }
14788
+ return runPreparedSubagent(prep.prepared, ctx);
14409
14789
  };
14410
14790
  }
14411
14791
  });
@@ -15461,7 +15841,7 @@ var init_expand = __esm({
15461
15841
  // ../core/src/serve/chatProcessing.ts
15462
15842
  import path48 from "path";
15463
15843
  import fs46 from "fs";
15464
- var handleUnifiedChat;
15844
+ var handleUnifiedChat, handleUnifiedChatRun;
15465
15845
  var init_chatProcessing = __esm({
15466
15846
  "../core/src/serve/chatProcessing.ts"() {
15467
15847
  "use strict";
@@ -15481,8 +15861,33 @@ var init_chatProcessing = __esm({
15481
15861
  init_guard();
15482
15862
  init_systemPrompt();
15483
15863
  init_model();
15864
+ init_agentWake();
15484
15865
  handleUnifiedChat = async (inbound, sendOutbound, sseWrite, abortSignal, opts) => {
15866
+ let busyKey = typeof inbound.sessionId === "string" && inbound.sessionId ? inbound.sessionId : "";
15867
+ markRunStart(busyKey);
15868
+ try {
15869
+ await handleUnifiedChatRun(inbound, sendOutbound, sseWrite, abortSignal, opts, (resolved) => {
15870
+ if (busyKey === resolved) return;
15871
+ markRunStart(resolved);
15872
+ if (busyKey) markRunEnd(busyKey);
15873
+ busyKey = resolved;
15874
+ });
15875
+ } finally {
15876
+ markRunEnd(busyKey);
15877
+ }
15878
+ };
15879
+ handleUnifiedChatRun = async (inbound, sendOutbound, sseWrite, abortSignal, opts, transferBusy) => {
15485
15880
  const sessionId = await getOrCreateSessionId(inbound.sessionId);
15881
+ transferBusy?.(sessionId);
15882
+ registerWakeDriverIfAbsent(sessionId, async () => {
15883
+ await handleUnifiedChat(
15884
+ { ...inbound, sessionId, content: AGENT_DONE_SENTINEL, attachments: void 0 },
15885
+ sendOutbound,
15886
+ sseWrite,
15887
+ abortSignal,
15888
+ opts
15889
+ );
15890
+ });
15486
15891
  try {
15487
15892
  inbound.content = expandSlashCommand(inbound.content);
15488
15893
  } catch {
@@ -16162,6 +16567,7 @@ var init_useChatState = __esm({
16162
16567
  init_transcript();
16163
16568
  init_fork();
16164
16569
  init_inbox();
16570
+ init_agentWake();
16165
16571
  init_contextCore();
16166
16572
  init_bashDirect();
16167
16573
  init_guard();
@@ -16632,6 +17038,17 @@ var init_useChatState = __esm({
16632
17038
  budgetYuan: budgetYuanRef.current
16633
17039
  // ★ 预算制运行(成本信任链第二环 E1):/budget 设置,超限 soft nudge + 120% 硬截断
16634
17040
  };
17041
+ registerWakeDriver(sid, async () => {
17042
+ if (sessionIdRef.current !== sid) return;
17043
+ const myGen = ++turnGenRef.current;
17044
+ setBusy(true);
17045
+ pushInfo("\u{1F916} \u5B50\u4EE3\u7406\u5B8C\u6210\uFF0C\u81EA\u52A8\u5524\u9192\u4E3B\u4F1A\u8BDD\u7EE7\u7EED\u2026");
17046
+ try {
17047
+ await runOnce(sid, AGENT_DONE_SENTINEL, false);
17048
+ } finally {
17049
+ if (turnGenRef.current === myGen) setBusy(false);
17050
+ }
17051
+ });
16635
17052
  try {
16636
17053
  await handleUnifiedChat(
16637
17054
  { sessionId: sid, content: body },
@@ -19164,15 +19581,595 @@ ${listText}`);
19164
19581
  }
19165
19582
  });
19166
19583
 
19584
+ // ../core/src/agent/stallDetector.ts
19585
+ import { execFile as execFile2 } from "child_process";
19586
+ 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;
19587
+ var init_stallDetector = __esm({
19588
+ "../core/src/agent/stallDetector.ts"() {
19589
+ "use strict";
19590
+ CHECK_INTERVAL_SEC = 60;
19591
+ GRACE_MIN = 5;
19592
+ R1_WINDOW_MIN = 10;
19593
+ R1_MIN_HITS = 3;
19594
+ R2_WINDOW_MIN = 15;
19595
+ R2_MIN_CALLS = 3;
19596
+ R2_MAX_DISTINCT_SIG = 2;
19597
+ R3_SILENCE_MIN = 5;
19598
+ R4_WINDOW_MIN = 5;
19599
+ R4_COST_DIVISOR = 4;
19600
+ R6_STALL_BAND = 3;
19601
+ HEALTH_GROWTH_LINES = 20;
19602
+ SERIES_CAP = 64;
19603
+ LOCKFILE_RE = /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb|bun\.lock|composer\.lock|poetry\.lock|Cargo\.lock|go\.sum)$/i;
19604
+ EDIT_TOOLS = /* @__PURE__ */ new Set(["edit_file", "create_file", "write_file", "move_file", "notebook_edit"]);
19605
+ READONLY_TOOLS = /* @__PURE__ */ new Set([
19606
+ "read_file",
19607
+ "list_dir",
19608
+ "glob",
19609
+ "search_grep",
19610
+ "read_image",
19611
+ "read_docx",
19612
+ "read_xlsx",
19613
+ "read_project_guide",
19614
+ "recall",
19615
+ "get_git_diff",
19616
+ "git_status",
19617
+ "git_log",
19618
+ "find_references",
19619
+ "get_diagnostics",
19620
+ "goto_definition",
19621
+ "view_symbol_outline",
19622
+ "web_search",
19623
+ "web_fetch"
19624
+ ]);
19625
+ normPath2 = (p) => p.trim().replace(/\\/g, "/").replace(/^[A-Za-z]:/, "").replace(/^\.\/+/, "").replace(/\/+$/, "").toLowerCase();
19626
+ samePath = (a, b) => a === b || a.endsWith("/" + b) || b.endsWith("/" + a);
19627
+ sortKeysDeep = (v) => {
19628
+ if (Array.isArray(v)) return v.map(sortKeysDeep);
19629
+ if (v && typeof v === "object") {
19630
+ const out = {};
19631
+ for (const k of Object.keys(v).sort()) out[k] = sortKeysDeep(v[k]);
19632
+ return out;
19633
+ }
19634
+ return v;
19635
+ };
19636
+ argsKeyOf = (args) => {
19637
+ try {
19638
+ return JSON.stringify(sortKeysDeep(args ?? {})).slice(0, 200);
19639
+ } catch {
19640
+ return String(args);
19641
+ }
19642
+ };
19643
+ similarSigOf = (args) => {
19644
+ let s;
19645
+ try {
19646
+ s = JSON.stringify(sortKeysDeep(args ?? {}));
19647
+ } catch {
19648
+ return String(args);
19649
+ }
19650
+ return s.replace(/\d+/g, "").replace(/\/[^/"]+(?=")/, "");
19651
+ };
19652
+ editTargetOf = (args) => normPath2(String(args?.path ?? args?.source ?? args?.destination ?? args?.file_path ?? ""));
19653
+ extractTargetPaths = (text) => {
19654
+ const out = /* @__PURE__ */ new Set();
19655
+ for (const m of text.match(/(?:[A-Za-z]:)?[\w.\-一-鿿]+(?:[/\\][\w.\-一-鿿]+)+\.[A-Za-z0-9]{1,6}/g) ?? []) {
19656
+ out.add(normPath2(m));
19657
+ }
19658
+ return out;
19659
+ };
19660
+ parseNumstatNet = (stdout) => {
19661
+ let net = 0;
19662
+ for (const line of stdout.split("\n")) {
19663
+ const m = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line.trim());
19664
+ if (!m) continue;
19665
+ if (LOCKFILE_RE.test(m[3])) continue;
19666
+ net += (m[1] === "-" ? 0 : parseInt(m[1], 10)) + (m[2] === "-" ? 0 : parseInt(m[2], 10));
19667
+ }
19668
+ return net;
19669
+ };
19670
+ MIN = 6e4;
19671
+ StallDetector = class {
19672
+ constructor(opts) {
19673
+ this.stopFn = null;
19674
+ this.started = false;
19675
+ this.stopped = false;
19676
+ this.paused = false;
19677
+ this.ticking = false;
19678
+ /** 宽限期基准(豁免解除时重置——豁免期不该吃掉 agent 的起步宽限)。字段名与同名 getter 刻意区分
19679
+ * (tsconfig 为 set 语义,私有字段与只读 getter 同名会在字段初始化时炸 "only a getter")。 */
19680
+ this.activeAt = 0;
19681
+ this.lastEventAt = 0;
19682
+ /** 抑制窗截止(政策层经 setSuppressUntil 设置:batch=nudge 后宽限;影子=强命中去重)。 */
19683
+ this.suppressUntil = 0;
19684
+ this.roundNo = 0;
19685
+ this.calls = [];
19686
+ this.finalSeen = false;
19687
+ /** R6 净变更行数时间序列(batch 基线 = setup 完成后首读;交互 run 首 tick 采样即基线)。 */
19688
+ this.series = [];
19689
+ /** R4 成本采样序列。 */
19690
+ this.costSeries = [];
19691
+ this.opts = opts;
19692
+ this.now = opts.now ?? Date.now;
19693
+ this.intervalMs = opts.intervalMs ?? CHECK_INTERVAL_SEC * 1e3;
19694
+ this.targetPaths = extractTargetPaths(opts.targetText ?? "");
19695
+ this.lastEventAt = this.now();
19696
+ }
19697
+ /** 启动旁观者心跳。判定在解除暂停前不会真正开始(batch setup 执行期挂 PAUSED)。 */
19698
+ start() {
19699
+ if (this.started || this.stopped) return;
19700
+ this.started = true;
19701
+ this.activeAt = this.now();
19702
+ const schedule = this.opts.schedule ?? ((fn, ms) => {
19703
+ const h = setInterval(fn, ms);
19704
+ h.unref?.();
19705
+ return () => clearInterval(h);
19706
+ });
19707
+ this.stopFn = schedule(() => void this.tick(), this.intervalMs);
19708
+ }
19709
+ stop() {
19710
+ this.stopped = true;
19711
+ this.stopFn?.();
19712
+ this.stopFn = null;
19713
+ }
19714
+ /** 暂停判定(batch setup/accept 执行期;交互审批/提问挂起期);解除时对齐事件时钟并重置起步宽限。 */
19715
+ setPaused(p) {
19716
+ if (p === this.paused || this.stopped) return;
19717
+ this.paused = p;
19718
+ if (!p) {
19719
+ const t = this.now();
19720
+ this.lastEventAt = Math.max(this.lastEventAt, t);
19721
+ this.activeAt = t;
19722
+ }
19723
+ }
19724
+ /** 抑制窗(政策层动作语义映射:batch nudge 后宽限 / 影子强命中 5 分钟去重)。 */
19725
+ setSuppressUntil(t) {
19726
+ this.suppressUntil = t;
19727
+ }
19728
+ /** R6 基线:batch 在 setup 完成后取首个读数(装依赖动 lockfile,故基线后置 + 排除 lockfile)。 */
19729
+ async markBaseline() {
19730
+ const s = await this.sampleDiffSafe();
19731
+ this.series = s ? [{ ...s, t: this.now() }] : [];
19732
+ }
19733
+ /**
19734
+ * 事件喂入口:tool.start/tool.end/round.start/text.delta/thinking.delta/final 全喂。
19735
+ * 任何事件都刷新 lastEventAt(R3 断流的对面);豁免期不喂不计时(resume 时对齐)。
19736
+ * 白名单外的事件类型静默忽略(VSCode sink 混流 UIEvent,挂起交互事件天然无害)。
19737
+ */
19738
+ feed(evt) {
19739
+ if (this.stopped || this.paused || !evt?.type) return;
19740
+ const t = this.now();
19741
+ this.lastEventAt = t;
19742
+ if (evt.type === "round.start") {
19743
+ this.roundNo = Number(evt.round) || this.roundNo;
19744
+ return;
19745
+ }
19746
+ if (evt.type === "final") {
19747
+ this.finalSeen = true;
19748
+ return;
19749
+ }
19750
+ if (evt.type === "tool.start") {
19751
+ const toolName = String(evt.toolName ?? "");
19752
+ const args = evt.args ?? {};
19753
+ let digest = "";
19754
+ try {
19755
+ digest = JSON.stringify(args);
19756
+ } catch {
19757
+ digest = String(args);
19758
+ }
19759
+ this.calls.push({
19760
+ toolCallId: String(evt.toolCallId ?? ""),
19761
+ toolName,
19762
+ argsKey: argsKeyOf(args),
19763
+ sig: similarSigOf(args),
19764
+ isEdit: EDIT_TOOLS.has(toolName),
19765
+ isReadonly: READONLY_TOOLS.has(toolName),
19766
+ target: editTargetOf(args),
19767
+ argsDigest: digest.slice(0, 120),
19768
+ t,
19769
+ round: this.roundNo
19770
+ });
19771
+ if (this.calls.length > 512) this.calls.splice(0, this.calls.length - 512);
19772
+ }
19773
+ }
19774
+ /** 判定一步(测试可直接驱动;生产由 scheduler 每 tick 调)。自身异常绝不外泄。 */
19775
+ async tick() {
19776
+ if (!this.started || this.stopped || this.paused || this.ticking) return;
19777
+ this.ticking = true;
19778
+ try {
19779
+ await this.evaluate();
19780
+ } catch {
19781
+ } finally {
19782
+ this.ticking = false;
19783
+ }
19784
+ }
19785
+ // —— 政策层访问器(拼 AUX 任务书 / 日志行用;不含动作语义) ——
19786
+ get round() {
19787
+ return this.roundNo;
19788
+ }
19789
+ get activeAtMs() {
19790
+ return this.activeAt;
19791
+ }
19792
+ /** 最近一次 diff 采样(影子日志 netLines 字段;无采样返回 null)。 */
19793
+ get lastNetSample() {
19794
+ return this.series.length > 0 ? this.series[this.series.length - 1] : null;
19795
+ }
19796
+ /** 成本采样(不进序列——evaluate 每 tick 的采样才进,batch costLine 的即时读数与原实现同形)。 */
19797
+ sampleCost() {
19798
+ return this.sampleCostSafe();
19799
+ }
19800
+ /** 时间窗内调用记录(batch 轨迹拼装:只读合并计数、写入逐条)。 */
19801
+ callsWithin(t, windowMs) {
19802
+ return this.calls.filter((c) => t - c.t <= windowMs);
19803
+ }
19804
+ // —— 评估主体:产出 StallEvaluation 交政策层 ——
19805
+ async evaluate() {
19806
+ const t = this.now();
19807
+ if (t - this.activeAt < GRACE_MIN * MIN) {
19808
+ await this.opts.onEvaluation({ t, skipped: "grace-start", silenceMin: null, health: null, strong: [], weak: [] });
19809
+ return;
19810
+ }
19811
+ if (t < this.suppressUntil) {
19812
+ await this.opts.onEvaluation({ t, skipped: "suppressed", silenceMin: null, health: null, strong: [], weak: [] });
19813
+ return;
19814
+ }
19815
+ const sample = await this.sampleDiffSafe();
19816
+ if (sample) {
19817
+ this.series.push({ ...sample, t });
19818
+ if (this.series.length > SERIES_CAP) this.series.splice(0, this.series.length - SERIES_CAP);
19819
+ }
19820
+ const cost = await this.sampleCostSafe();
19821
+ if (cost) {
19822
+ this.costSeries.push({ costYuan: cost.costYuan, t });
19823
+ if (this.costSeries.length > SERIES_CAP) this.costSeries.splice(0, this.costSeries.length - SERIES_CAP);
19824
+ }
19825
+ const silenceMs = t - this.lastEventAt;
19826
+ if (silenceMs >= R3_SILENCE_MIN * MIN) {
19827
+ await this.opts.onEvaluation({ t, skipped: null, silenceMin: silenceMs / MIN, health: null, strong: [], weak: [] });
19828
+ return;
19829
+ }
19830
+ const health = this.healthCheck();
19831
+ if (health) {
19832
+ await this.opts.onEvaluation({ t, skipped: null, silenceMin: null, health, strong: [], weak: [] });
19833
+ return;
19834
+ }
19835
+ const strong = [this.r1Check(t), this.r6Check()].filter((r) => r !== null);
19836
+ const weak = [this.r2Check(t), this.r4Check(t), this.r5Check(t)].filter((r) => r !== null);
19837
+ await this.opts.onEvaluation({ t, skipped: null, silenceMin: null, health: null, strong, weak });
19838
+ }
19839
+ // —— 规则实现 ——
19840
+ /** R1 重复模式:同 (tool, 规范化 args) 窗口内 ≥3 次且跨轮(轮内重复 repeatBreaker 已管)。 */
19841
+ r1Check(t) {
19842
+ const groups = /* @__PURE__ */ new Map();
19843
+ for (const c of this.calls) {
19844
+ if (t - c.t > R1_WINDOW_MIN * MIN) continue;
19845
+ const key = `${c.toolName} ${c.argsKey}`;
19846
+ const g = groups.get(key) ?? { tool: c.toolName, n: 0, rounds: /* @__PURE__ */ new Set() };
19847
+ g.n += 1;
19848
+ g.rounds.add(c.round);
19849
+ groups.set(key, g);
19850
+ }
19851
+ for (const g of groups.values()) {
19852
+ if (g.n >= R1_MIN_HITS && g.rounds.size >= 2) {
19853
+ 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` };
19854
+ }
19855
+ }
19856
+ return null;
19857
+ }
19858
+ /** R2 只读空转(弱):窗口内调用全只读且朴素规范化后变体 ≤2 种(args 持续变化=调研,防误伤)。 */
19859
+ r2Check(t) {
19860
+ const recent = this.calls.filter((c) => t - c.t <= R2_WINDOW_MIN * MIN);
19861
+ if (recent.length < R2_MIN_CALLS || recent.some((c) => !c.isReadonly)) return null;
19862
+ const sigs = new Set(recent.map((c) => c.sig));
19863
+ if (sigs.size > R2_MAX_DISTINCT_SIG) return null;
19864
+ 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` };
19865
+ }
19866
+ /** R4 烧钱无产出(弱):窗口成本增量 > 预算 1/4 且期间零 edit、零 final。 */
19867
+ r4Check(t) {
19868
+ if (this.opts.budgetYuan === void 0) return null;
19869
+ const win = this.costSeries.filter((s) => t - s.t <= R4_WINDOW_MIN * MIN);
19870
+ if (win.length < 2) return null;
19871
+ const first = win[0];
19872
+ const last = win[win.length - 1];
19873
+ if (first.costYuan == null || last.costYuan == null) return null;
19874
+ const delta = last.costYuan - first.costYuan;
19875
+ if (delta <= this.opts.budgetYuan / R4_COST_DIVISOR) return null;
19876
+ if (this.calls.some((c) => c.isEdit && t - c.t <= R4_WINDOW_MIN * MIN) || this.finalSeen) return null;
19877
+ 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` };
19878
+ }
19879
+ /** R5 写入方向偏差(弱):窗口内 edit 目标与任务目标集合全无交集;提取不到目标集合 → 整条关闭。 */
19880
+ r5Check(t) {
19881
+ if (this.targetPaths.size === 0) return null;
19882
+ const edits = this.calls.filter((c) => c.isEdit && t - c.t <= R2_WINDOW_MIN * MIN);
19883
+ if (edits.length === 0) return null;
19884
+ const hit = edits.some((c) => c.target && [...this.targetPaths].some((p) => samePath(c.target, p)));
19885
+ if (hit) return null;
19886
+ 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` };
19887
+ }
19888
+ /** R6 净进展停滞(强):连续 2 窗口净变更 ≈0(±3 行)且同期 edit 调用 >0——写了又改、没沉淀。 */
19889
+ r6Check() {
19890
+ const s = this.series;
19891
+ if (s.length < 3) return null;
19892
+ const a = s[s.length - 3];
19893
+ const b = s[s.length - 2];
19894
+ const c = s[s.length - 1];
19895
+ if (Math.abs(b.net - a.net) > R6_STALL_BAND || Math.abs(c.net - b.net) > R6_STALL_BAND) return null;
19896
+ const edits = this.calls.filter((e) => e.isEdit && e.t >= a.t).length;
19897
+ if (edits === 0) return null;
19898
+ 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` };
19899
+ }
19900
+ /** 健康短路:净变更显著增长或新文件出现 → 直接 normal(R2/R4 弱信号的反证)。 */
19901
+ healthCheck() {
19902
+ const s = this.series;
19903
+ if (s.length < 2) return null;
19904
+ const prev = s[s.length - 2];
19905
+ const last = s[s.length - 1];
19906
+ if (last.newFiles.length > 0) return `\u65B0\u6587\u4EF6\u51FA\u73B0\uFF1A${last.newFiles.slice(0, 3).join("\u3001")}`;
19907
+ const delta = last.net - prev.net;
19908
+ if (delta > HEALTH_GROWTH_LINES) return `\u51C0\u53D8\u66F4\u589E\u957F +${delta} \u884C\uFF08> ${HEALTH_GROWTH_LINES}\uFF09`;
19909
+ return null;
19910
+ }
19911
+ // —— 采样 ——
19912
+ async sampleDiffSafe() {
19913
+ if (this.opts.sampleDiff) return this.opts.sampleDiff();
19914
+ try {
19915
+ const run = (args) => new Promise((resolve, reject) => {
19916
+ execFile2("git", ["-C", this.opts.worktreePath, ...args], { maxBuffer: 1024 * 1024 }, (err, stdout) => err ? reject(err) : resolve(String(stdout)));
19917
+ });
19918
+ const [numstat, others] = await Promise.all([
19919
+ run(["diff", "--numstat", "HEAD"]),
19920
+ run(["ls-files", "--others", "--exclude-standard"])
19921
+ ]);
19922
+ const newFiles = others.split("\n").map((s) => s.trim()).filter(Boolean);
19923
+ return { net: parseNumstatNet(numstat), newFiles };
19924
+ } catch {
19925
+ return null;
19926
+ }
19927
+ }
19928
+ async sampleCostSafe() {
19929
+ if (!this.opts.getCost) return null;
19930
+ try {
19931
+ return await this.opts.getCost();
19932
+ } catch {
19933
+ return null;
19934
+ }
19935
+ }
19936
+ };
19937
+ }
19938
+ });
19939
+
19940
+ // src/batchWatchdog.ts
19941
+ 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;
19942
+ var init_batchWatchdog = __esm({
19943
+ "src/batchWatchdog.ts"() {
19944
+ "use strict";
19945
+ init_stallDetector();
19946
+ MIN2 = 6e4;
19947
+ NUDGE_GRACE_MIN = 5;
19948
+ FREEZE_MIN = 5;
19949
+ KILL_STRIKES = 3;
19950
+ AUX_TIMEOUT_MS = 6e4;
19951
+ RETRY_MAX = 3;
19952
+ 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`;
19953
+ 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`;
19954
+ 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
19955
+
19956
+ ---
19957
+
19958
+ `;
19959
+ 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"}
19960
+
19961
+ [\u4EFB\u52A1\u76EE\u6807] ${args.title}
19962
+ \u9A8C\u6536\u547D\u4EE4\uFF1A${args.accept}
19963
+ [\u6D88\u8017] ${args.costLine}
19964
+ [\u89C4\u5219\u4FE1\u53F7]
19965
+ ${args.rules}
19966
+ [\u6700\u8FD1\u8F68\u8FF9\uFF0810 \u5206\u949F\uFF09]
19967
+ ${args.trajectory}`;
19968
+ parseVerdictJson = (raw) => {
19969
+ const m = /\{[\s\S]*\}/.exec(raw ?? "");
19970
+ if (!m) return null;
19971
+ try {
19972
+ const j = JSON.parse(m[0]);
19973
+ const v = String(j?.verdict ?? "").trim().toLowerCase();
19974
+ if (v !== "normal" && v !== "idle" && v !== "offtrack") return null;
19975
+ return { verdict: v, evidence: String(j?.evidence ?? "").trim() };
19976
+ } catch {
19977
+ return null;
19978
+ }
19979
+ };
19980
+ defaultAdjudicate = async (prompt, signal) => {
19981
+ const { chatWithModelWithSummary: chatWithModelWithSummary2 } = await Promise.resolve().then(() => (init_model(), model_exports));
19982
+ const resp = await chatWithModelWithSummary2([{ role: "user", content: prompt }], [], { signal });
19983
+ return resp?.choices?.[0]?.message?.content ?? "";
19984
+ };
19985
+ sharedBudgetYuan = (budgetYuan, spentYuan) => {
19986
+ if (budgetYuan === void 0) return void 0;
19987
+ return Math.max(0, Math.round((budgetYuan - (spentYuan ?? 0)) * 1e4) / 1e4);
19988
+ };
19989
+ BatchWatchdog = class {
19990
+ constructor(opts) {
19991
+ /** 连续非 normal 计数(normal 裁决/健康短路清零)。 */
19992
+ this.streak = 0;
19993
+ /** 收尾冻结窗口截止(nudge-2 后 FREEZE_MIN 分钟内动作冻结——采样/R3 照常,二层不调)。 */
19994
+ this.freezeUntil = 0;
19995
+ this.records = [];
19996
+ this.killInfo = null;
19997
+ this.opts = opts;
19998
+ this.now = opts.now ?? Date.now;
19999
+ this.detector = new StallDetector({
20000
+ worktreePath: opts.worktreePath,
20001
+ targetText: opts.targetText,
20002
+ budgetYuan: opts.budgetYuan,
20003
+ getCost: opts.getCost,
20004
+ sampleDiff: opts.sampleDiff,
20005
+ now: opts.now,
20006
+ schedule: opts.schedule,
20007
+ intervalMs: opts.intervalMs,
20008
+ onEvaluation: (e) => this.consume(e)
20009
+ // promise 会被 detector tick await(AUX 裁决 tick 内完成)
20010
+ });
20011
+ }
20012
+ /** 启动旁观者心跳。判定在解除暂停前不会真正开始(setup 执行期挂 PAUSED,batch 方案 §4)。 */
20013
+ start() {
20014
+ this.detector.start();
20015
+ }
20016
+ stop() {
20017
+ this.detector.stop();
20018
+ }
20019
+ /** 暂停判定(setup/accept 命令执行期豁免);解除时对齐事件时钟并重置起步宽限。 */
20020
+ setPaused(p) {
20021
+ this.detector.setPaused(p);
20022
+ }
20023
+ /** R6 基线:setup 完成后取首个读数(装依赖动 lockfile,故基线后置 + 排除 lockfile)。 */
20024
+ markBaseline() {
20025
+ return this.detector.markBaseline();
20026
+ }
20027
+ feed(evt) {
20028
+ this.detector.feed(evt);
20029
+ }
20030
+ /** 判定一步(测试可直接驱动;生产由 scheduler 每 tick 调)。自身异常绝不外泄。 */
20031
+ async tick() {
20032
+ return this.detector.tick();
20033
+ }
20034
+ /** 终止证据快照(runner 据此置 watchdog-killed 与重试前置锚)。 */
20035
+ getKill() {
20036
+ return this.killInfo;
20037
+ }
20038
+ /** 报告证据链快照(跨重试轮由 runner 聚合)。 */
20039
+ snapshot() {
20040
+ return { kill: this.killInfo, records: [...this.records], roundsAtKill: this.detector.round };
20041
+ }
20042
+ // —— 求值消费:StallEvaluation → batch 动作(与影子政策是同一结果的两种消费,方案 §3) ——
20043
+ async consume(e) {
20044
+ if (e.skipped) return;
20045
+ if (e.silenceMin !== null) {
20046
+ 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` });
20047
+ return;
20048
+ }
20049
+ if (e.health) {
20050
+ this.streak = 0;
20051
+ this.records.push({ kind: "health", at: e.t, text: e.health });
20052
+ return;
20053
+ }
20054
+ if (e.strong.length === 0) {
20055
+ if (e.weak.length > 0) this.records.push({ kind: "suspicion", at: e.t, text: e.weak.map((r) => `${r.rule} ${r.evidence}`).join("\uFF1B") });
20056
+ return;
20057
+ }
20058
+ if (e.t < this.freezeUntil) {
20059
+ 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("+")}` });
20060
+ return;
20061
+ }
20062
+ const verdict = await this.adjudicate(e);
20063
+ if (verdict === null) {
20064
+ this.records.push({ kind: "aux-fail", at: e.t });
20065
+ return;
20066
+ }
20067
+ if (verdict.verdict === "normal") {
20068
+ this.streak = 0;
20069
+ this.records.push({ kind: "verdict", at: e.t, verdict: "normal", evidence: verdict.evidence });
20070
+ return;
20071
+ }
20072
+ this.streak += 1;
20073
+ this.records.push({ kind: "verdict", at: e.t, verdict: verdict.verdict, evidence: verdict.evidence });
20074
+ if (this.streak >= KILL_STRIKES) {
20075
+ this.kill({
20076
+ verdict: verdict.verdict,
20077
+ rule: e.strong.map((r) => r.rule).join("+"),
20078
+ evidence: `${verdict.evidence}\uFF08\u8FDE\u7EED ${this.streak} \u6B21\u975E normal\uFF09`
20079
+ });
20080
+ return;
20081
+ }
20082
+ this.nudge(this.streak, verdict.verdict, verdict.evidence, e.t);
20083
+ }
20084
+ /** 二层裁决。返回 null = 调用失败/非法输出(降级 normal,绝不误杀)。 */
20085
+ async adjudicate(e) {
20086
+ const costLine = await this.costLine(e.t);
20087
+ const prompt = ADJUDICATE_PROMPT({
20088
+ title: this.opts.ticketTitle,
20089
+ accept: this.opts.acceptCmd,
20090
+ costLine,
20091
+ rules: [
20092
+ ...e.strong.map((r) => `${r.rule}\uFF08\u5F3A\uFF09\uFF1A${r.evidence}`),
20093
+ ...e.weak.map((r) => `${r.rule}\uFF08\u5F31\uFF0C\u4EC5\u53C2\u8003\uFF09\uFF1A${r.evidence}`)
20094
+ ].join("\n") || "\uFF08\u65E0\uFF09",
20095
+ trajectory: this.trajectory(e.t)
20096
+ });
20097
+ try {
20098
+ const raw = await (this.opts.auxAdjudicate ?? defaultAdjudicate)(prompt, this.auxSignal());
20099
+ return parseVerdictJson(raw);
20100
+ } catch {
20101
+ return null;
20102
+ }
20103
+ }
20104
+ /** AUX 调用信号:任务 abort 或 60s 超时,先到先停(对齐 classifyRisk 合并信号形态)。 */
20105
+ auxSignal() {
20106
+ const ac = new AbortController();
20107
+ const timer = setTimeout(() => ac.abort(), AUX_TIMEOUT_MS);
20108
+ timer.unref?.();
20109
+ this.opts.ac.signal.addEventListener("abort", () => {
20110
+ clearTimeout(timer);
20111
+ ac.abort();
20112
+ }, { once: true });
20113
+ return ac.signal;
20114
+ }
20115
+ async costLine(t) {
20116
+ const cost = await this.detector.sampleCost();
20117
+ const mins = Math.round((t - this.detector.activeAtMs) / MIN2);
20118
+ const rounds = cost?.rounds ?? this.detector.round;
20119
+ const yuan = cost?.costYuan;
20120
+ return `\u5DF2\u7528\u65F6 ${mins} \u5206\u949F \xB7 \u8F6E\u6570 ${rounds}` + (yuan != null ? ` \xB7 \u82B1\u8D39 \xA5${yuan.toFixed(4)}` : "");
20121
+ }
20122
+ /** 最近 10 分钟轨迹(二层任务书):只读类按工具合并计数,写入类逐条带 args 摘要。 */
20123
+ trajectory(t) {
20124
+ const recent = this.detector.callsWithin(t, R1_WINDOW_MIN * MIN2);
20125
+ if (recent.length === 0) return "\uFF08\u65E0\u5DE5\u5177\u8C03\u7528\uFF09";
20126
+ const readonlyCounts = /* @__PURE__ */ new Map();
20127
+ const lines = [];
20128
+ for (const c of recent) {
20129
+ if (!c.isReadonly) {
20130
+ lines.push(`- ${c.toolName}${c.target ? ` \u2192 ${c.target}` : ""}\uFF08${c.argsDigest}\uFF09`);
20131
+ continue;
20132
+ }
20133
+ readonlyCounts.set(c.toolName, (readonlyCounts.get(c.toolName) ?? 0) + 1);
20134
+ }
20135
+ for (const [tool, n] of readonlyCounts) lines.push(`- ${tool} \xD7${n}\uFF08\u53EA\u8BFB\uFF09`);
20136
+ return lines.join("\n").slice(0, 2400);
20137
+ }
20138
+ nudge(streak, verdict, evidence, t) {
20139
+ const text = streak === 1 ? NUDGE_1(evidence) : NUDGE_2(`${verdict}\uFF1A${evidence}`);
20140
+ this.records.push({ kind: "nudge", at: t, level: streak, text });
20141
+ this.detector.setSuppressUntil(t + NUDGE_GRACE_MIN * MIN2);
20142
+ if (streak === 2) this.freezeUntil = t + FREEZE_MIN * MIN2;
20143
+ try {
20144
+ this.opts.onNudge(text);
20145
+ } catch {
20146
+ }
20147
+ }
20148
+ kill(info) {
20149
+ if (this.killInfo) return;
20150
+ this.killInfo = info;
20151
+ this.records.push({ kind: "kill", at: this.now(), verdict: info.verdict, evidence: `${info.rule} ${info.evidence}` });
20152
+ this.detector.stop();
20153
+ try {
20154
+ this.opts.onKill(new Error(`watchdog: ${info.verdict} \u2014 ${info.evidence}`), info);
20155
+ } catch {
20156
+ }
20157
+ }
20158
+ };
20159
+ buildRetryTaskContent = (body, kill, finalText) => RETRY_PREAMBLE(kill.verdict, kill.evidence, finalText.trim().replace(/\s+/g, " ").slice(0, 400)) + body;
20160
+ }
20161
+ });
20162
+
19167
20163
  // src/batchRunner.ts
19168
20164
  var batchRunner_exports = {};
19169
20165
  __export(batchRunner_exports, {
20166
+ buildReport: () => buildReport,
19170
20167
  parseWaitWindow: () => parseWaitWindow,
19171
20168
  runBatchQueue: () => runBatchQueue
19172
20169
  });
19173
20170
  import fs50 from "fs";
19174
20171
  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;
20172
+ 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
20173
  var init_batchRunner = __esm({
19177
20174
  "src/batchRunner.ts"() {
19178
20175
  "use strict";
@@ -19189,6 +20186,9 @@ var init_batchRunner = __esm({
19189
20186
  init_queue();
19190
20187
  init_batchStrings();
19191
20188
  init_strings();
20189
+ init_batchWatchdog();
20190
+ init_inbox();
20191
+ init_agentWake();
19192
20192
  BATCH_EDIT_TOOLS = /* @__PURE__ */ new Set(["edit_file", "create_file", "write_file", "move_file", "notebook_edit"]);
19193
20193
  BATCH_DEFAULT_DENYLIST = /* @__PURE__ */ new Set(["run_command", "delete_path", "run_in_background"]);
19194
20194
  ACCEPT_TIMEOUT_MIN = 10;
@@ -19258,6 +20258,33 @@ ${lines.slice(-ACCEPT_TAIL_LINES).join("\n")}` : text;
19258
20258
  if (c.costYuan === null) c.partialPrice = false;
19259
20259
  return c;
19260
20260
  };
20261
+ buildWatchdogLines = (snaps, costYuan) => {
20262
+ if (snaps.length === 0) return null;
20263
+ const T = batchT(getLocale());
20264
+ const render2 = (r) => {
20265
+ switch (r.kind) {
20266
+ case "suspicion":
20267
+ return T.wdSuspicionRec(r.text ?? "");
20268
+ case "verdict":
20269
+ return T.wdVerdictRec(r.verdict ?? "", r.evidence ?? "");
20270
+ case "nudge":
20271
+ return T.wdNudgeRec(r.level ?? 0, r.text ?? "");
20272
+ case "health":
20273
+ return T.wdHealthRec(r.text ?? "");
20274
+ case "aux-fail":
20275
+ return T.wdAuxFailRec();
20276
+ case "kill":
20277
+ return T.wdKillRec(r.verdict ?? "", r.evidence ?? "");
20278
+ }
20279
+ };
20280
+ const lines = [];
20281
+ for (const snap of snaps) {
20282
+ lines.push(...snap.records.map(render2));
20283
+ if (snap.kill) lines.push(T.wdAtKillRec(snap.roundsAtKill, costYuan !== null ? costYuan.toFixed(4) : "-"));
20284
+ }
20285
+ if (snaps.length > 1) lines.push(T.wdRetryRec(snaps.length - 1));
20286
+ return lines;
20287
+ };
19261
20288
  buildReport = (args) => {
19262
20289
  const { id, ticket, status, budgetTruncated, cost, receipt, finalText, accept, deniedTools, diff, wt } = args;
19263
20290
  const T = batchT(getLocale());
@@ -19280,7 +20307,11 @@ ${T.statusEntry(`${T.statusLabel(status)}${budgetNote}`)}
19280
20307
  ${T.costEntry(T.costSummary(cost))}
19281
20308
  ${T.receiptEntry(receipt ? T.receiptLine(receipt) : T.noReceipt)}
19282
20309
  ${args.errorMessage ? T.errorEntry(args.errorMessage) : ""}
19283
- ${T.taskH}
20310
+ ${args.watchdogLines?.length ? `${T.wdH}
20311
+
20312
+ ${args.watchdogLines.map(T.wdRecord).join("\n")}
20313
+
20314
+ ` : ""}${T.taskH}
19284
20315
 
19285
20316
  ${ticket.body}
19286
20317
 
@@ -19384,89 +20415,187 @@ ${parsed.error}`);
19384
20415
  emit2(`\u{1F4A5} [${task.id}] ${msg}`);
19385
20416
  return { status: "error", cost: cost2, budgetTruncated: false };
19386
20417
  }
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 : ""}
20418
+ const watchdogEnabled = process.env.DEEP_SEEK_BATCH_WATCHDOG !== "0";
20419
+ const wdSnapshots = [];
20420
+ const maxRounds = 1 + Math.min(Math.max(0, Math.floor(t.retry ?? 0)), RETRY_MAX);
20421
+ let content = t.body;
20422
+ let roundBudget = t.budgetYuan;
20423
+ let receipt;
20424
+ let finalText = "";
20425
+ let agentError;
20426
+ let budgetTruncated = false;
20427
+ let wdKill = null;
20428
+ let status;
20429
+ let accept = null;
20430
+ for (let round = 1; ; round++) {
20431
+ wdKill = null;
20432
+ finalText = "";
20433
+ receipt = void 0;
20434
+ agentError = void 0;
20435
+ let nudgeCount = 0;
20436
+ const roundAc = new AbortController();
20437
+ const wd = watchdogEnabled ? new BatchWatchdog({
20438
+ sessionId,
20439
+ ticketTitle: t.title,
20440
+ acceptCmd: t.accept,
20441
+ targetText: `${t.title}
20442
+ ${t.body}
20443
+ ${t.accept}${t.setup ? `
20444
+ ${t.setup}` : ""}`,
20445
+ ac: roundAc,
20446
+ worktreePath: wt.path,
20447
+ budgetYuan: roundBudget,
20448
+ getCost: () => collectSessionCost(sessionId),
20449
+ onNudge: (text) => {
20450
+ nudgeCount++;
20451
+ pushSessionInbox(sessionId, text);
20452
+ emit2(T.wdNudgeNote(task.id, nudgeCount));
20453
+ },
20454
+ onKill: (err, info) => {
20455
+ wdKill = info;
20456
+ emit2(T.wdKillNote(task.id, `${info.verdict} \u2014 ${info.evidence}`));
20457
+ roundAc.abort(err);
20458
+ }
20459
+ }) : null;
20460
+ wd?.start();
20461
+ wd?.setPaused(true);
20462
+ if (round === 1 && t.setup) {
20463
+ emit2(T.setupLine(t.setup));
20464
+ const s = await runAcceptCommand(t.setup, wt.path);
20465
+ if (s.code !== 0 || s.timedOut) {
20466
+ wd?.stop();
20467
+ const msg = `${T.setupFail(t.setup, s.code)}${s.timedOut ? T.acceptTimeoutSuffix : ""}
19393
20468
 
19394
20469
  \`\`\`
19395
20470
  ${s.output}
19396
20471
  \`\`\``;
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 };
20472
+ await killBackgroundTasksUnder(wt.path).catch(() => {
20473
+ });
20474
+ const cost2 = await collectSessionCost(sessionId);
20475
+ await finishTicket(task.id, buildReport({
20476
+ id: task.id,
20477
+ ticket: t,
20478
+ status: "setup-failed",
20479
+ budgetTruncated: false,
20480
+ cost: cost2,
20481
+ receipt: void 0,
20482
+ finalText: "",
20483
+ accept: null,
20484
+ deniedTools: [],
20485
+ diff: T.diffNone,
20486
+ wt,
20487
+ errorMessage: msg
20488
+ }));
20489
+ emit2(`\u{1F6E0}\uFE0F [${task.id}] ${T.setupFail(t.setup, s.code)}`);
20490
+ return { status: "setup-failed", cost: cost2, budgetTruncated: false };
20491
+ }
19416
20492
  }
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";
20493
+ wd?.setPaused(false);
20494
+ await wd?.markBaseline();
20495
+ const timer = setTimeout(() => roundAc.abort(new Error(T.timeoutAbort(timeoutMin))), timeoutMin * 6e4);
20496
+ setSessionWorktree(sessionId, wt);
20497
+ registerWakeDriver(sessionId, async () => {
20498
+ emit2(`\u{1F916} [${task.id}] \u5B50\u4EE3\u7406\u5B8C\u6210\uFF0C\u5524\u9192 run \u7EED\u8DD1\u2026`);
20499
+ setSessionWorktree(sessionId, wt);
20500
+ const wakeAc = new AbortController();
20501
+ const wakeTimer = setTimeout(() => wakeAc.abort(new Error(T.timeoutAbort(timeoutMin))), timeoutMin * 6e4);
20502
+ try {
20503
+ await handleUnifiedChat(
20504
+ { sessionId, content: AGENT_DONE_SENTINEL },
20505
+ async () => {
20506
+ },
20507
+ (evt) => {
20508
+ if (evt?.type === "text.delta") finalText += evt.text;
20509
+ else if (evt?.type === "text.reset") finalText = "";
20510
+ else if (evt?.type === "final") finalText = evt.text || finalText;
20511
+ },
20512
+ wakeAc.signal,
20513
+ {
20514
+ requestApproval: async (detail, meta) => {
20515
+ if (allowApproval.has(meta.toolName)) return "allow-once";
20516
+ deniedTools.push({ tool: meta.toolName, detail: detail.replace(/\n+/g, " ").slice(0, 140) });
20517
+ return "deny";
20518
+ },
20519
+ onUIEvent: (evt) => {
20520
+ if (evt.type === "run.receipt") receipt = evt;
20521
+ },
20522
+ budgetYuan: roundBudget,
20523
+ toolSchemas: gatedSchemas
20524
+ }
20525
+ );
20526
+ } finally {
20527
+ clearTimeout(wakeTimer);
20528
+ clearSessionWorktree(sessionId);
20529
+ }
20530
+ });
20531
+ try {
20532
+ await handleUnifiedChat(
20533
+ { sessionId, content },
20534
+ async () => {
19440
20535
  },
19441
- onUIEvent: (evt) => {
19442
- if (evt.type === "run.receipt") receipt = evt;
20536
+ (evt) => {
20537
+ wd?.feed(evt);
20538
+ if (evt?.type === "text.delta") finalText += evt.text;
20539
+ else if (evt?.type === "text.reset") finalText = "";
20540
+ else if (evt?.type === "final") finalText = evt.text || finalText;
20541
+ else if (evt?.type === "round.start") emit2(T.roundLine(ts(), evt.round));
19443
20542
  },
19444
- budgetYuan: t.budgetYuan,
19445
- toolSchemas: gatedSchemas
20543
+ roundAc.signal,
20544
+ {
20545
+ requestApproval: async (detail, meta) => {
20546
+ if (allowApproval.has(meta.toolName)) return "allow-once";
20547
+ deniedTools.push({ tool: meta.toolName, detail: detail.replace(/\n+/g, " ").slice(0, 140) });
20548
+ return "deny";
20549
+ },
20550
+ onUIEvent: (evt) => {
20551
+ if (evt.type === "run.receipt") receipt = evt;
20552
+ },
20553
+ budgetYuan: roundBudget,
20554
+ toolSchemas: gatedSchemas
20555
+ }
20556
+ );
20557
+ } catch (e) {
20558
+ agentError = e instanceof Error ? e.message : String(e);
20559
+ } finally {
20560
+ clearTimeout(timer);
20561
+ wd?.stop();
20562
+ clearSessionWorktree(sessionId);
20563
+ }
20564
+ if (wd) wdSnapshots.push(wd.snapshot());
20565
+ budgetTruncated = receipt?.budgetTruncated === true;
20566
+ if (agentError) {
20567
+ status = "error";
20568
+ emit2(T.agentErrorLine(task.id, agentError));
20569
+ break;
20570
+ }
20571
+ if (wdKill) {
20572
+ const spent = await collectSessionCost(sessionId);
20573
+ const nextBudget = sharedBudgetYuan(t.budgetYuan, spent.costYuan);
20574
+ if (round < maxRounds && (nextBudget === void 0 || nextBudget > 0)) {
20575
+ emit2(T.wdRetryNote(task.id, round + 1));
20576
+ content = buildRetryTaskContent(t.body, wdKill, finalText);
20577
+ roundBudget = nextBudget;
20578
+ continue;
19446
20579
  }
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 {
20580
+ if (round < maxRounds) emit2(T.wdBudgetStopNote(task.id));
20581
+ status = "watchdog-killed";
20582
+ break;
20583
+ }
20584
+ if (roundAc.signal.aborted) {
20585
+ status = "timeout";
20586
+ emit2(T.timeoutLine(task.id, timeoutMin));
20587
+ break;
20588
+ }
20589
+ const quiet = await waitSessionQuiescent(sessionId, void 0, timeoutMin * 6e4);
20590
+ if (!quiet) emit2(`\u23F3 [${task.id}] \u5524\u9192\u94FE\u672A\u5728\u65F6\u9650\u5185\u6536\u655B\uFF0C\u6309\u73B0\u72B6\u9A8C\u6536`);
20591
+ wd?.setPaused(true);
19465
20592
  const r = await runAcceptCommand(t.accept, wt.path);
19466
20593
  accept = { cmd: t.accept, code: r.code, output: r.output, timedOut: r.timedOut };
19467
20594
  status = r.code === 0 && !r.timedOut ? "passed" : "accept-failed";
19468
20595
  emit2(T.acceptLine(status === "passed", task.id, r.code) + (r.timedOut ? T.acceptKilledSuffix : ""));
20596
+ break;
19469
20597
  }
20598
+ unregisterWakeDriver(sessionId);
19470
20599
  await killBackgroundTasksUnder(wt.path).catch(() => {
19471
20600
  });
19472
20601
  const diff = await harvestDiff(wt);
@@ -19483,7 +20612,8 @@ ${s.output}
19483
20612
  deniedTools,
19484
20613
  diff,
19485
20614
  wt,
19486
- errorMessage: agentError
20615
+ errorMessage: agentError,
20616
+ watchdogLines: buildWatchdogLines(wdSnapshots, cost.costYuan) ?? void 0
19487
20617
  }));
19488
20618
  emit2(` ${T.statusLabel(status)} \xB7 ${T.costSummary(cost)}`);
19489
20619
  emit2(T.artifactLine(wt.path, wt.branch));
@@ -19621,7 +20751,7 @@ init_prefs();
19621
20751
  init_updateCheck();
19622
20752
  init_trust();
19623
20753
  import { render } from "ink";
19624
- import os5 from "os";
20754
+ import os6 from "os";
19625
20755
  import path50 from "path";
19626
20756
  import { emitKeypressEvents } from "node:readline";
19627
20757
  import { jsx as jsx21 } from "react/jsx-runtime";
@@ -19771,7 +20901,7 @@ var main = async () => {
19771
20901
  process.stdout.write("\x1B[2J\x1B[3J\x1B[1;1H");
19772
20902
  const cwdIsHome = () => {
19773
20903
  try {
19774
- return path50.resolve(cwd).toLowerCase() === path50.resolve(os5.homedir()).toLowerCase();
20904
+ return path50.resolve(cwd).toLowerCase() === path50.resolve(os6.homedir()).toLowerCase();
19775
20905
  } catch {
19776
20906
  return false;
19777
20907
  }