deepseeker-code 1.0.2 → 1.0.4

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 +1142 -216
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -52,6 +52,9 @@ var init_strings = __esm({
52
52
  approvalDeny: "\u62D2\u7EDD",
53
53
  errAborted: "\u5DF2\u4E2D\u6B62\u5F53\u524D\u8F6E\u3002",
54
54
  busyBlockSend: "\u23F3 \u6B63\u5728\u751F\u6210\uFF0C\u8BF7\u7B49\u5F85\u6216\u6309 Esc \u4E2D\u6B62\u540E\u518D\u53D1\u9001\u3002",
55
+ inboxQueued: "\u{1F4EE} \u5DF2\u6392\u961F\uFF0C\u5C06\u5728\u56DE\u5408\u8FB9\u754C\u9001\u8FBE\u6A21\u578B\uFF08Esc \u4ECD\u53EF\u4E2D\u6B62\uFF09\u3002",
56
+ inboxClaimed: (n) => `\u{1F4EC} \u5DF2\u9001\u8FBE\u6A21\u578B\uFF08${n} \u6761\u8865\u5145\u8F93\u5165\uFF09`,
57
+ inboxCarryover: (n) => `\u{1F4EC} \u672C\u8F6E\u7ED3\u675F\u65F6 ${n} \u6761\u8865\u5145\u8F93\u5165\u672A\u5728\u56DE\u5408\u8FB9\u754C\u9001\u8FBE\uFF0C\u5DF2\u5199\u5165\u4F1A\u8BDD\u5386\u53F2\uFF0C\u4E0B\u8F6E\u5BF9\u8BDD\u81EA\u52A8\u5E26\u5165\u3002`,
55
58
  cmdHint: "/help \u5E2E\u52A9 \xB7 /plan \u8BA1\u5212\u6A21\u5F0F \xB7 /auto \u81EA\u52A8\u6A21\u5F0F \xB7 /model \u5207\u6362\u6A21\u578B \xB7 /thinking \u601D\u8003\u7B49\u7EA7 \xB7 /lang \u8BED\u8A00 \xB7 /sessions \u5386\u53F2 \xB7 /clear \u6E05\u5C4F \xB7 /exit \u9000\u51FA",
56
59
  statusStreaming: "\u751F\u6210\u4E2D",
57
60
  statusIdle: "\u5C31\u7EEA",
@@ -67,6 +70,13 @@ var init_strings = __esm({
67
70
  ${yellow} \u5DE5\u5177\u5C06\u4EE5\u8BE5\u76EE\u5F55\u4E3A\u9879\u76EE\u6839\u8BFB\u5199\u6587\u4EF6\uFF0C\u5EFA\u8BAE\u5148 cd \u5230\u5177\u4F53\u9879\u76EE\u76EE\u5F55\u518D\u8FD0\u884C\u3002${reset}
68
71
  ${dim} \u9879\u76EE\u952E\uFF1A${projectKey}${reset}
69
72
 
73
+ `;
74
+ },
75
+ updateAvailable: (latest, current) => {
76
+ const cyan = "\x1B[36m", dim = "\x1B[2m", reset = "\x1B[0m";
77
+ return `${cyan}\u21BB \u68C0\u6D4B\u5230\u65B0\u7248\u672C ${latest}${reset} ${dim}\uFF08\u5F53\u524D ${current}\uFF09${reset}
78
+ ${dim} \u8FD0\u884C npm i -g deepseeker-code@latest \u66F4\u65B0${reset}
79
+
70
80
  `;
71
81
  },
72
82
  cmdHelp: "\u67E5\u770B\u5E2E\u52A9\u4E0E\u5FEB\u6377\u952E",
@@ -91,6 +101,12 @@ ${dim} \u9879\u76EE\u952E\uFF1A${projectKey}${reset}
91
101
  sessionsPrompt: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u8F7D\u5165 \xB7 Esc \u53D6\u6D88",
92
102
  noHistory: "\uFF08\u6682\u65E0\u5386\u53F2\u4F1A\u8BDD\uFF09",
93
103
  sessionLoaded: (id) => `\u5DF2\u8F7D\u5165\u4F1A\u8BDD ${id}\uFF08\u7EE7\u7EED\u5BF9\u8BDD\u5C06\u7EED\u63A5\u6B64\u4F1A\u8BDD\uFF09`,
104
+ cmdFork: "\u4ECE\u5F53\u524D\u4F1A\u8BDD\u7684\u67D0\u8F6E\u56DE\u590D\u5904\u5206\u53C9\u51FA\u65B0\u4F1A\u8BDD",
105
+ forkTitle: "\u{1F33F} \u5206\u53C9\u5F53\u524D\u4F1A\u8BDD\uFF08\u2191\u2193 \u9009\u68C0\u67E5\u70B9 \xB7 Enter \u5206\u53C9\uFF09",
106
+ forkPrompt: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u5206\u53C9 \xB7 Esc \u53D6\u6D88",
107
+ forkEmpty: "\uFF08\u5F53\u524D\u4F1A\u8BDD\u6682\u65E0\u53EF\u5206\u53C9\u7684\u68C0\u67E5\u70B9\uFF09",
108
+ forkDone: (id, roundNo) => `\u5DF2\u4ECE\u7B2C ${roundNo} \u8F6E\u5206\u53C9 \u2192 \u65B0\u4F1A\u8BDD ${id}\uFF08\u5DF2\u8F7D\u5165\uFF0C\u540E\u7EED\u5BF9\u8BDD\u5199\u5165\u65B0\u4F1A\u8BDD\uFF0C\u539F\u4F1A\u8BDD\u4E0D\u53D8\uFF09`,
109
+ forkFailed: (e) => `\u5206\u53C9\u5931\u8D25\uFF1A${e}`,
94
110
  relTime: (iso) => {
95
111
  const t = Date.parse(iso);
96
112
  if (!t) return "";
@@ -126,6 +142,7 @@ DeepSeeker-Code \u5C06\u5728\u6B64\u76EE\u5F55\u8BFB\u53D6\u3001\u7F16\u8F91\u54
126
142
  `/thinking [off|high|max] \u2014 \u5207\u6362\u601D\u8003\u7B49\u7EA7\uFF08\u5F53\u524D ${thinking}\uFF09`,
127
143
  `/lang [zh|en] \u2014 \u5207\u6362\u754C\u9762\u8BED\u8A00\uFF08\u5F53\u524D ${locale2}\uFF09`,
128
144
  "/sessions \u2014 \u9009\u62E9\u5E76\u8F7D\u5165\u5386\u53F2\u4F1A\u8BDD\uFF08\u7EED\u63A5\u5BF9\u8BDD\uFF09",
145
+ "/fork \u2014 \u4ECE\u5F53\u524D\u4F1A\u8BDD\u7684\u67D0\u8F6E\u56DE\u590D\u5904\u5206\u53C9\u51FA\u65B0\u4F1A\u8BDD",
129
146
  "/trust \u2014 \u7BA1\u7406\u5DF2\u4FE1\u4EFB\u76EE\u5F55\uFF08\u9879\u76EE\u7EA7 hooks/skills \u7B49\u4EC5\u5728\u4FE1\u4EFB\u76EE\u5F55\u52A0\u8F7D\uFF1BCI \u7528 --trust \u663E\u5F0F\u4FE1\u4EFB\uFF09",
130
147
  "Ctrl+C \u9000\u51FA \xB7 Esc \u4E2D\u6B62/\u6E05\u8F93\u5165 \xB7 Ctrl+G \u4E2D\u6B62 \xB7 Ctrl+T \u5C55\u5F00/\u6536\u8D77\u601D\u8003"
131
148
  ].join("\n"),
@@ -173,6 +190,9 @@ DeepSeeker-Code \u5C06\u5728\u6B64\u76EE\u5F55\u8BFB\u53D6\u3001\u7F16\u8F91\u54
173
190
  approvalDeny: "Deny",
174
191
  errAborted: "Aborted current turn.",
175
192
  busyBlockSend: "\u23F3 Still generating\u2014wait or press Esc to abort before sending.",
193
+ inboxQueued: "\u{1F4EE} Queued\u2014will reach the model at the next round boundary (Esc still aborts).",
194
+ inboxClaimed: (n) => `\u{1F4EC} Delivered to the model (${n} supplemental input${n > 1 ? "s" : ""})`,
195
+ inboxCarryover: (n) => `\u{1F4EC} ${n} supplemental input${n > 1 ? "s" : ""} not claimed at a round boundary before this run ended; saved to session history and carried into the next turn.`,
176
196
  cmdHint: "/help help \xB7 /plan plan mode \xB7 /auto auto mode \xB7 /model switch model \xB7 /thinking thinking level \xB7 /lang language \xB7 /sessions history \xB7 /clear clear \xB7 /exit quit",
177
197
  statusStreaming: "streaming",
178
198
  statusIdle: "ready",
@@ -188,6 +208,13 @@ Set it as an env var and retry, or start serve for the Web UI.`,
188
208
  ${yellow} Tools will read/write files with this as project root\u2014cd into the actual project first.${reset}
189
209
  ${dim} Project key: ${projectKey}${reset}
190
210
 
211
+ `;
212
+ },
213
+ updateAvailable: (latest, current) => {
214
+ const cyan = "\x1B[36m", dim = "\x1B[2m", reset = "\x1B[0m";
215
+ return `${cyan}\u21BB New version ${latest} available${reset} ${dim}(current ${current})${reset}
216
+ ${dim} Run npm i -g deepseeker-code@latest to update${reset}
217
+
191
218
  `;
192
219
  },
193
220
  cmdHelp: "Show help & shortcuts",
@@ -212,6 +239,12 @@ ${dim} Project key: ${projectKey}${reset}
212
239
  sessionsPrompt: "\u2191\u2193 pick \xB7 Enter resume \xB7 Esc cancel",
213
240
  noHistory: "(no past sessions)",
214
241
  sessionLoaded: (id) => `Resumed session ${id} (new messages continue it)`,
242
+ cmdFork: "Fork the current session from a past round",
243
+ forkTitle: "\u{1F33F} Fork current session (\u2191\u2193 to pick \xB7 Enter to fork)",
244
+ forkPrompt: "\u2191\u2193 pick \xB7 Enter fork \xB7 Esc cancel",
245
+ forkEmpty: "(no checkpoints to fork from in this session)",
246
+ forkDone: (id, roundNo) => `Forked from round ${roundNo} \u2192 new session ${id} (loaded; new messages go to the fork, source untouched)`,
247
+ forkFailed: (e) => `Fork failed: ${e}`,
215
248
  relTime: (iso) => {
216
249
  const t = Date.parse(iso);
217
250
  if (!t) return "";
@@ -247,6 +280,7 @@ DeepSeeker-Code will read, edit, and execute files here.`,
247
280
  `/thinking [off|high|max] \u2014 Switch thinking level (current ${thinking})`,
248
281
  `/lang [zh|en] \u2014 Switch interface language (current ${locale2})`,
249
282
  "/sessions \u2014 Pick a past session to resume",
283
+ "/fork \u2014 Fork the current session from a past round",
250
284
  "/trust \u2014 Manage trusted dirs (project hooks/skills load only when trusted; CI uses --trust)",
251
285
  "Ctrl+C exit \xB7 Esc abort/clear input \xB7 Ctrl+G abort \xB7 Ctrl+T toggle thinking"
252
286
  ].join("\n"),
@@ -267,7 +301,7 @@ DeepSeeker-Code will read, edit, and execute files here.`,
267
301
  S = new Proxy({}, {
268
302
  get: (_t, k) => typeof k === "string" ? STRINGS[locale][k] : void 0
269
303
  });
270
- LOCAL_COMMAND_NAMES = ["help", "status", "plan", "auto", "model", "thinking", "lang", "output-style", "sessions", "usage", "context", "permissions", "mcp", "hooks", "trust", "debug", "clear", "exit"];
304
+ LOCAL_COMMAND_NAMES = ["help", "status", "plan", "auto", "model", "thinking", "lang", "output-style", "sessions", "fork", "usage", "context", "permissions", "mcp", "hooks", "trust", "debug", "clear", "exit"];
271
305
  }
272
306
  });
273
307
 
@@ -275,13 +309,15 @@ DeepSeeker-Code will read, edit, and execute files here.`,
275
309
  var config_exports = {};
276
310
  __export(config_exports, {
277
311
  ENGINE_OVERRIDE_KEYS: () => ENGINE_OVERRIDE_KEYS,
278
- appConfig: () => appConfig
312
+ ENV_SOURCES: () => ENV_SOURCES,
313
+ appConfig: () => appConfig,
314
+ configSources: () => configSources
279
315
  });
280
316
  import path2 from "path";
281
317
  import os2 from "os";
282
318
  import { readFileSync as readFileSync2 } from "fs";
283
319
  import { createHash } from "crypto";
284
- var _DataDir, appConfig, ENGINE_OVERRIDE_VALIDATORS, ENGINE_OVERRIDE_KEYS, applyEngineOverrides;
320
+ var _DataDir, appConfig, ENV_SOURCES, configSources, ENGINE_OVERRIDE_VALIDATORS, ENGINE_OVERRIDE_KEYS, applyEngineOverrides;
285
321
  var init_config = __esm({
286
322
  "../core/src/config/index.ts"() {
287
323
  "use strict";
@@ -356,8 +392,51 @@ var init_config = __esm({
356
392
  workflowMaxSteps: Number(process.env.DEEP_SEEK_WORKFLOW_MAX_STEPS) || 8,
357
393
  /** P0-3 run_workflow 单个子 agent 结果的字符预算:超出按头尾截断,避免单个巨型结果挤占聚合输出。
358
394
  * 最终聚合再受工具 maxOutputCharacters 兜底。 */
359
- workflowPerStepChars: 6e3
360
- };
395
+ workflowPerStepChars: 6e3,
396
+ /** ★ P0-A 计划模式强制位置:'runtime'(默认)= 工具表全会话恒定(两态统一 appendPlanControlTools),
397
+ * 计划期写工具由 processToolCall 执行层按 PLAN_ALLOWED_TOOLS 拒绝——保 DeepSeek 前缀缓存
398
+ * (tools 参数序列化在请求头部,计划模式裁表翻转 = 全会话历史 re-prefill 两次);
399
+ * 'schema' = 旧路径(计划期裁成只读白名单子表)。env DEEP_SEEK_PLAN_ENFORCEMENT=schema 回退。 */
400
+ planEnforcement: process.env.DEEP_SEEK_PLAN_ENFORCEMENT === "schema" ? "schema" : "runtime",
401
+ /** ★ P1-MCP 工具暴露模式:'dispatcher'(默认)= 只注入恒定 schema 的 mcp_list_tools + mcp_call
402
+ * (目录按需查、审批/权限用合成名 mcp__<server>__<tool> 匹配既有规则;工具表不随 MCP server
403
+ * 工具数增长,L1 恒定);'schemas' = 旧路径(每个 MCP 工具独立 schema 常驻)。
404
+ * env DEEP_SEEK_MCP_EXPOSE_MODE=schemas 回退。 */
405
+ mcpExposeMode: process.env.DEEP_SEEK_MCP_EXPOSE_MODE === "schemas" ? "schemas" : "dispatcher",
406
+ /** ★ 事件日志化:transcript 追加 turn/step 边界事件行(run.start/round.end/run.end/compaction/
407
+ * run.abandoned,带 dscEvent 标记,readMessages 自动过滤)——崩溃恢复从盲扫启发式升级为事件确认,
408
+ * compaction 事件携带归档计数+摘要文本使 transcript 自包含(分叉/审计不再押 state.json 单点)。
409
+ * 关闭时 appendEvent 全 no-op、恢复走纯内存启发式(= 改造前行为)。env DEEP_SEEK_TRANSCRIPT_EVENTS=0 回退。 */
410
+ transcriptEvents: process.env.DEEP_SEEK_TRANSCRIPT_EVENTS !== "0",
411
+ /** ★ inbox steering(第二梯队 #2):agent 运行中用户补充输入进 per-session 队列,runAgent 回合边界
412
+ * claim 注入上下文并落盘 transcript(长任务中途补指示不再只能中止重跑)。
413
+ * 关闭时全链路 no-op:端点 501 / CLI 回退 busy 提示 / VSCode 不排队。env DEEP_SEEK_INBOX=0 回退。 */
414
+ inboxSteering: process.env.DEEP_SEEK_INBOX !== "0",
415
+ /** ★ hooks 改写型拦截点(第二梯队 #3):PreToolUse 可改 args(argsOverride,前置到全部安全门禁
416
+ * 之前生效——门禁/审批评估的是改写后参数)、PostToolUse 可改 resultForModel(仅模型视图,
417
+ * 用户视图 resultForUser 不变)。关闭时 PreToolUse 回退旧位(审批后、仅 deny),改写字段全忽略,
418
+ * 行为与改造前一致。env DEEP_SEEK_HOOK_REWRITE=0 回退。 */
419
+ hookRewrite: process.env.DEEP_SEEK_HOOK_REWRITE !== "0"
420
+ };
421
+ ENV_SOURCES = {
422
+ dataDir: "DEEPSEEKER_CODE_DATA_DIR",
423
+ searchProvider: "SEARCH_PROVIDER",
424
+ tavilyApiKey: "TAVILY_API_KEY",
425
+ webFetchAllowPrivate: "WEB_FETCH_ALLOW_PRIVATE",
426
+ parallelSafeTools: "DEEP_SEEK_PARALLEL_SAFE_TOOLS",
427
+ workflowConcurrency: "DEEP_SEEK_WORKFLOW_CONCURRENCY",
428
+ workflowMaxSteps: "DEEP_SEEK_WORKFLOW_MAX_STEPS",
429
+ planEnforcement: "DEEP_SEEK_PLAN_ENFORCEMENT",
430
+ mcpExposeMode: "DEEP_SEEK_MCP_EXPOSE_MODE",
431
+ transcriptEvents: "DEEP_SEEK_TRANSCRIPT_EVENTS",
432
+ inboxSteering: "DEEP_SEEK_INBOX",
433
+ hookRewrite: "DEEP_SEEK_HOOK_REWRITE"
434
+ };
435
+ configSources = {};
436
+ for (const [key, envName] of Object.entries(ENV_SOURCES)) {
437
+ const v = process.env[envName];
438
+ if (v !== void 0 && v !== "") configSources[key] = "env";
439
+ }
361
440
  ENGINE_OVERRIDE_VALIDATORS = {
362
441
  undoEnabled: (v) => typeof v === "boolean" ? v : void 0,
363
442
  undoBackupSensitive: (v) => v === "skip" || v === "deny" || v === "allow" ? v : void 0,
@@ -368,12 +447,12 @@ var init_config = __esm({
368
447
  ENGINE_OVERRIDE_KEYS = Object.keys(ENGINE_OVERRIDE_VALIDATORS);
369
448
  applyEngineOverrides = (cfg) => {
370
449
  const paths = [
371
- path2.join(cfg.dataDir, "settings.json"),
450
+ { scope: "settings.global", configPath: path2.join(cfg.dataDir, "settings.json") },
372
451
  // 全局用户级
373
- path2.join(process.cwd(), ".deepseeker-code", "settings.json")
452
+ { scope: "settings.project", configPath: path2.join(process.cwd(), ".deepseeker-code", "settings.json") }
374
453
  // 项目级(覆盖全局)
375
454
  ];
376
- for (const configPath of paths) {
455
+ for (const { scope, configPath } of paths) {
377
456
  let raw;
378
457
  try {
379
458
  raw = readFileSync2(configPath, "utf-8");
@@ -397,6 +476,7 @@ var init_config = __esm({
397
476
  continue;
398
477
  }
399
478
  cfg[key] = valid;
479
+ configSources[key] = scope;
400
480
  }
401
481
  }
402
482
  };
@@ -477,13 +557,20 @@ var init_common = __esm({
477
557
  injectMarkedBlock = (message, fence, body) => {
478
558
  const sys = message[0];
479
559
  if (!sys || sys.role !== "system" || typeof sys.content !== "string") return;
480
- if (sys.content.includes(fence)) {
481
- sys.content = sys.content.split(fence)[0].trimEnd();
482
- }
483
- sys.content += `
560
+ const idx = sys.content.indexOf(fence);
561
+ if (idx === -1) {
562
+ sys.content += `
484
563
 
485
564
  ${fence}
486
565
  ${body}`;
566
+ return;
567
+ }
568
+ const after = sys.content.slice(idx + fence.length);
569
+ const next = after.match(/\n\n⟦[^\n]*?⟧/);
570
+ const oldBody = next?.index !== void 0 ? after.slice(0, next.index) : after;
571
+ if (oldBody === `
572
+ ${body}`) return;
573
+ console.warn(`\u26A0\uFE0F ${fence} \u5DF2\u9501\u5B9A\uFF08\u4F1A\u8BDD\u5185\u4E0D\u53D8\u4EE5\u4FDD\u524D\u7F00\u7F13\u5B58\uFF09\uFF0C\u5185\u5BB9\u53D8\u5316\u5C06\u4E8E\u4E0B\u4E2A\u65B0\u4F1A\u8BDD\u751F\u6548\u3002`);
487
574
  };
488
575
  decodeProcessOutput = (buf) => {
489
576
  const utf8 = buf.toString("utf8");
@@ -797,17 +884,120 @@ var init_deepseek = __esm({
797
884
  }
798
885
  });
799
886
 
887
+ // ../core/src/llm/providers/replay/index.ts
888
+ var createReplayProvider;
889
+ var init_replay = __esm({
890
+ "../core/src/llm/providers/replay/index.ts"() {
891
+ "use strict";
892
+ createReplayProvider = (script) => {
893
+ const turns = Array.isArray(script.turns) ? script.turns : [];
894
+ let cursor = 0;
895
+ let attemptCounts = [];
896
+ const calls = [];
897
+ const makeFaultError = (fault) => {
898
+ const msg = fault.message ?? (fault.type === "idle" ? "stream_idle_timeout" : fault.type === "context_length" ? "context_length_exceeded\uFF08\u56DE\u653E\u6CE8\u5165\uFF09" : fault.type === "transient" ? "HTTP 429 rate limit\uFF08\u56DE\u653E\u6CE8\u5165\uFF09" : "\u56DE\u653E\u81F4\u547D\u9519\u8BEF\uFF08fatal\uFF09");
899
+ const e = new Error(msg);
900
+ e.replayFault = fault.type;
901
+ if (fault.type === "transient") e.status = 429;
902
+ return e;
903
+ };
904
+ const streamChat2 = async function* (messages, tools, opts) {
905
+ const turnIndex = cursor;
906
+ calls.push({ turnIndex, messages: [...messages], tools, opts });
907
+ const turn = turns[turnIndex];
908
+ if (!turn) return;
909
+ const attempt = attemptCounts[turnIndex] ?? 0;
910
+ attemptCounts[turnIndex] = attempt + 1;
911
+ const fault = turn.fault;
912
+ const shouldFault = !!fault && (fault.type === "fatal" || attempt < (fault.times ?? 1));
913
+ if (shouldFault) {
914
+ const f = fault;
915
+ const n = Math.min(f.afterChars ?? 0, (turn.content ?? "").length);
916
+ if (n > 0) yield { kind: "text", text: (turn.content ?? "").slice(0, n) };
917
+ throw makeFaultError(f);
918
+ }
919
+ if (turn.reasoning) yield { kind: "reasoning", text: turn.reasoning };
920
+ if (turn.content) yield { kind: "text", text: turn.content };
921
+ const tcs = turn.toolCalls ?? [];
922
+ for (let i = 0; i < tcs.length; i++) {
923
+ const tc = tcs[i];
924
+ yield { kind: "tool_call_delta", index: i, id: tc.id ?? `call_replay_${turnIndex}_${i}`, type: "function", nameDelta: tc.name };
925
+ yield { kind: "tool_call_delta", index: i, argumentsDelta: JSON.stringify(tc.args ?? {}) };
926
+ }
927
+ if (turn.usage) yield { kind: "usage", usage: turn.usage };
928
+ cursor++;
929
+ };
930
+ const summarize2 = async (_messages, _tools, _opts) => ({
931
+ id: "replay-summary",
932
+ object: "chat.completion",
933
+ created: 0,
934
+ // 确定性:固定值(回放产物不掺时间戳,diff 稳定)
935
+ model: "replay",
936
+ choices: [{
937
+ index: 0,
938
+ message: { role: "assistant", content: script.summarizeText ?? "\uFF08\u56DE\u653E\u6458\u8981\uFF1A\u538B\u7F29\u8DEF\u5F84\u7684\u786E\u5B9A\u6027\u5360\u4F4D\u6587\u672C\uFF09", refusal: null },
939
+ finish_reason: "stop",
940
+ logprobs: null
941
+ }],
942
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
943
+ });
944
+ return {
945
+ id: "replay",
946
+ displayName: "Replay",
947
+ modelLabel: "Replay",
948
+ streamChat: streamChat2,
949
+ summarize: summarize2,
950
+ classifyRisk: async () => script.riskVerdict ?? "safe",
951
+ buildAssistantMessage: (parts) => {
952
+ const m = { role: "assistant", content: parts.content };
953
+ if (parts.reasoning) m.reasoning_content = parts.reasoning;
954
+ if (parts.toolCalls?.length) m.tool_calls = parts.toolCalls;
955
+ return m;
956
+ },
957
+ // 自识别回放注入的故障(streamInference 经 activeProvider 调用这两个分类器驱动重试通道)
958
+ isContextLengthError: (e) => e?.replayFault === "context_length",
959
+ isTransientError: (e) => e?.replayFault === "transient",
960
+ get calls() {
961
+ return calls;
962
+ },
963
+ get cursor() {
964
+ return cursor;
965
+ },
966
+ reset: () => {
967
+ cursor = 0;
968
+ attemptCounts = [];
969
+ calls.length = 0;
970
+ }
971
+ };
972
+ };
973
+ }
974
+ });
975
+
800
976
  // ../core/src/llm/model.ts
801
- var activeProvider, chatWithModelWithSummary, classifyToolRisk, isContextLengthError2, isTransientApiError;
977
+ import { readFileSync as readFileSync3 } from "fs";
978
+ var resolveInitialProvider, activeProvider, chatWithModelWithSummary, classifyToolRisk;
802
979
  var init_model = __esm({
803
980
  "../core/src/llm/model.ts"() {
804
981
  "use strict";
805
982
  init_deepseek();
806
- activeProvider = deepseekProvider;
807
- chatWithModelWithSummary = deepseekProvider.summarize;
808
- classifyToolRisk = deepseekProvider.classifyRisk;
809
- isContextLengthError2 = deepseekProvider.isContextLengthError;
810
- isTransientApiError = deepseekProvider.isTransientError;
983
+ init_replay();
984
+ resolveInitialProvider = () => {
985
+ if (process.env.DEEP_SEEK_PROVIDER !== "replay") return deepseekProvider;
986
+ const scriptPath = process.env.DEEP_SEEK_REPLAY_SCRIPT;
987
+ if (!scriptPath) {
988
+ throw new Error("DEEP_SEEK_PROVIDER=replay \u9700\u540C\u65F6\u8BBE\u7F6E DEEP_SEEK_REPLAY_SCRIPT=<script.json>\uFF08\u56DE\u653E\u5267\u672C\u8DEF\u5F84\uFF09");
989
+ }
990
+ let raw;
991
+ try {
992
+ raw = JSON.parse(readFileSync3(scriptPath, "utf-8"));
993
+ } catch (e) {
994
+ throw new Error(`\u56DE\u653E\u5267\u672C\u52A0\u8F7D\u5931\u8D25\uFF08${scriptPath}\uFF09: ${e?.message ?? e}`);
995
+ }
996
+ return createReplayProvider(Array.isArray(raw) ? { turns: raw } : raw);
997
+ };
998
+ activeProvider = resolveInitialProvider();
999
+ chatWithModelWithSummary = (messages, tools, opts) => activeProvider.summarize(messages, tools, opts);
1000
+ classifyToolRisk = (toolName, args, detail, signal) => activeProvider.classifyRisk(toolName, args, detail, signal);
811
1001
  }
812
1002
  });
813
1003
 
@@ -1125,6 +1315,7 @@ var init_store = __esm({
1125
1315
  } catch {
1126
1316
  continue;
1127
1317
  }
1318
+ if (typeof msg?.dscEvent === "string") continue;
1128
1319
  messageCount++;
1129
1320
  if (!preview && msg?.role === "user" && typeof msg.content === "string") {
1130
1321
  const t = msg.content.replace(/\s+/g, " ").trim();
@@ -1179,6 +1370,87 @@ var init_store = __esm({
1179
1370
  }
1180
1371
  });
1181
1372
 
1373
+ // ../core/src/session/transcript.ts
1374
+ import fs5 from "fs/promises";
1375
+ var isEventLine, readTranscriptLines, appendLineWithRetry, appendMessage, appendEvent;
1376
+ var init_transcript = __esm({
1377
+ "../core/src/session/transcript.ts"() {
1378
+ "use strict";
1379
+ init_store();
1380
+ init_common();
1381
+ init_config();
1382
+ isEventLine = (l) => typeof l?.dscEvent === "string";
1383
+ readTranscriptLines = async (sessionId) => {
1384
+ await ensureSessionsDir(sessionId);
1385
+ const p = getTranscriptPath(sessionId);
1386
+ try {
1387
+ const text = await fs5.readFile(p, "utf-8");
1388
+ const lines = [];
1389
+ for (const s of text.split("\n")) {
1390
+ const line = s.trim();
1391
+ if (line.length === 0) continue;
1392
+ try {
1393
+ lines.push(JSON.parse(line));
1394
+ } catch {
1395
+ console.warn(`\u26A0\uFE0F [transcript] \u8DF3\u8FC7\u65E0\u6CD5\u89E3\u6790\u7684\u635F\u574F\u884C: ${line.slice(0, 120)}`);
1396
+ }
1397
+ }
1398
+ return lines;
1399
+ } catch (err) {
1400
+ if (err?.code === "ENOENT") return [];
1401
+ throw err;
1402
+ }
1403
+ };
1404
+ appendLineWithRetry = async (p, payload) => {
1405
+ const MAX_ATTEMPTS = 3;
1406
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
1407
+ let beforeSize = 0;
1408
+ try {
1409
+ beforeSize = (await fs5.stat(p)).size;
1410
+ } catch {
1411
+ }
1412
+ try {
1413
+ await fs5.appendFile(p, payload, "utf-8");
1414
+ return;
1415
+ } catch (e) {
1416
+ try {
1417
+ await fs5.truncate(p, beforeSize);
1418
+ } catch {
1419
+ }
1420
+ if (attempt === MAX_ATTEMPTS) throw e;
1421
+ await new Promise((r) => setTimeout(r, 50 * attempt));
1422
+ }
1423
+ }
1424
+ };
1425
+ appendMessage = async (entry) => {
1426
+ try {
1427
+ await ensureSessionsDir(entry.sessionId);
1428
+ const { sessionId, ...rest } = entry;
1429
+ const line = { id: createUUID(), ...rest };
1430
+ const p = getTranscriptPath(sessionId);
1431
+ const payload = JSON.stringify(line) + "\n";
1432
+ if (payload.includes("\uFFFD")) {
1433
+ const fffdCount = (payload.match(/\uFFFD/g) || []).length;
1434
+ console.warn(`\u26A0\uFE0F [transcript] \u6D88\u606F\u542B ${fffdCount} \u4E2A\u4E71\u7801\u5B57\u7B26\uFF08U+FFFD\uFF09\uFF0C\u5DF2\u539F\u6837\u5199\u5165\u5386\u53F2\u3002\u6765\u6E90\u53EF\u80FD\u662F GBK \u5B50\u8FDB\u7A0B\u8F93\u51FA\u6216\u4E71\u7801\u7C98\u8D34\u3002`);
1435
+ }
1436
+ await appendLineWithRetry(p, payload);
1437
+ } catch (e) {
1438
+ console.warn(`\u26A0\uFE0F \u6D88\u606F\u843D\u76D8\u5931\u8D25\uFF08\u5DF2\u91CD\u8BD5 3 \u6B21\uFF0C\u5185\u5B58\u4E0E\u78C1\u76D8\u8F6C\u5F55\u53EF\u80FD\u4E0D\u4E00\u81F4\uFF09:`, e);
1439
+ }
1440
+ };
1441
+ appendEvent = async (sessionId, ev) => {
1442
+ if (!appConfig.transcriptEvents) return;
1443
+ try {
1444
+ await ensureSessionsDir(sessionId);
1445
+ const line = { id: createUUID(), ts: (/* @__PURE__ */ new Date()).toISOString(), ...ev };
1446
+ await appendLineWithRetry(getTranscriptPath(sessionId), JSON.stringify(line) + "\n");
1447
+ } catch (e) {
1448
+ console.warn(`\u26A0\uFE0F [transcript] \u4E8B\u4EF6\u884C\u843D\u76D8\u5931\u8D25\uFF08${ev.dscEvent}\uFF0C\u6062\u590D/\u5206\u53C9\u5C06\u56DE\u9000\u542F\u53D1\u5F0F\uFF09:`, e);
1449
+ }
1450
+ };
1451
+ }
1452
+ });
1453
+
1182
1454
  // ../core/src/hooks/types.ts
1183
1455
  var INTERCEPTABLE_EVENTS, TOOL_EVENTS, ALL_EVENTS, DEFAULT_TIMEOUT_BY_EVENT;
1184
1456
  var init_types = __esm({
@@ -1326,12 +1598,13 @@ var init_shellExecutor = __esm({
1326
1598
  });
1327
1599
 
1328
1600
  // ../core/src/hooks/registry.ts
1329
- var rules, matches, registerHooks, listHooks, dispatch, runPreHooks, HOOK_RESULT_MAX, truncateForHook, runPostHooks;
1601
+ var rules, matches, registerHooks, listHooks, isPlainObject, dispatch, runPreHooks, HOOK_RESULT_MAX, truncateForHook, runPostHooks;
1330
1602
  var init_registry = __esm({
1331
1603
  "../core/src/hooks/registry.ts"() {
1332
1604
  "use strict";
1333
1605
  init_types();
1334
1606
  init_shellExecutor();
1607
+ init_config();
1335
1608
  rules = [];
1336
1609
  matches = (matcher, toolName) => {
1337
1610
  if (typeof matcher === "string") return matcher === "*" || matcher === toolName;
@@ -1354,6 +1627,7 @@ var init_registry = __esm({
1354
1627
  };
1355
1628
  return rules.map((r) => ({ event: r.event, matcher: strMatcher(r.matcher), source: r.source, onError: r.onError }));
1356
1629
  };
1630
+ isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
1357
1631
  dispatch = async (event, ctx) => {
1358
1632
  const interceptable = INTERCEPTABLE_EVENTS.has(event);
1359
1633
  const isToolEvent = TOOL_EVENTS.has(event);
@@ -1381,14 +1655,29 @@ var init_registry = __esm({
1381
1655
  )
1382
1656
  ));
1383
1657
  const additions2 = [];
1384
- for (const r of results) if (r?.contextAdditions) additions2.push(...r.contextAdditions);
1385
- return pack(additions2);
1658
+ let resultOverride;
1659
+ for (const r of results) {
1660
+ if (r?.contextAdditions) additions2.push(...r.contextAdditions);
1661
+ if (event === "PostToolUse" && appConfig.hookRewrite && typeof r?.resultOverride === "string" && r.resultOverride) {
1662
+ resultOverride = r.resultOverride;
1663
+ }
1664
+ }
1665
+ return { ...pack(additions2), resultOverride };
1386
1666
  }
1387
1667
  const additions = [];
1668
+ let argsOverride = void 0;
1388
1669
  for (const rule of matched) {
1389
1670
  try {
1390
1671
  const res = await rule.run(ctx);
1391
1672
  if (res?.contextAdditions) additions.push(...res.contextAdditions);
1673
+ if (event === "PreToolUse" && appConfig.hookRewrite && res?.argsOverride !== void 0) {
1674
+ if (isPlainObject(res.argsOverride)) {
1675
+ ctx.args = res.argsOverride;
1676
+ argsOverride = res.argsOverride;
1677
+ } else {
1678
+ console.warn(`\u26A0\uFE0F [hook:PreToolUse] argsOverride \u987B\u4E3A\u666E\u901A\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565`);
1679
+ }
1680
+ }
1392
1681
  if (res && res.deny) {
1393
1682
  return { deny: true, reason: res.reason, contextAdditions: additions.length > 0 ? additions : void 0 };
1394
1683
  }
@@ -1401,7 +1690,7 @@ var init_registry = __esm({
1401
1690
  console.warn(`\u26A0\uFE0F [hook:${event}] \u6267\u884C\u5F02\u5E38\uFF0C\u6309\u653E\u884C\u5904\u7406: ${msg}`);
1402
1691
  }
1403
1692
  }
1404
- return pack(additions);
1693
+ return { ...pack(additions), argsOverride };
1405
1694
  };
1406
1695
  runPreHooks = async (toolName, args, ctx) => {
1407
1696
  return dispatch("PreToolUse", {
@@ -1417,7 +1706,7 @@ var init_registry = __esm({
1417
1706
  truncateForHook = (s) => s.length <= HOOK_RESULT_MAX ? s : s.slice(0, HOOK_RESULT_MAX) + `
1418
1707
  \u2026[hook \u89C6\u56FE\u622A\u65AD\uFF0C\u5171 ${s.length} \u5B57\u7B26]`;
1419
1708
  runPostHooks = async (toolName, args, result, ctx) => {
1420
- await dispatch("PostToolUse", {
1709
+ return dispatch("PostToolUse", {
1421
1710
  sessionId: ctx.sessionId,
1422
1711
  cwd: ctx.cwd,
1423
1712
  env: ctx.env,
@@ -1461,6 +1750,7 @@ var init_truncate = __esm({
1461
1750
  init_model();
1462
1751
  init_contextCore();
1463
1752
  init_store();
1753
+ init_transcript();
1464
1754
  init_hooks();
1465
1755
  ANSI_ESCAPE = /\u001b\[[\d;?]*[ -/]*[@-~]|\u001b\][^\u0007]*(?:\u0007|\u001b\\)/g;
1466
1756
  getFileAccessRoots = () => {
@@ -1616,6 +1906,11 @@ ${line}` : line;
1616
1906
  consecutiveFailures: 0,
1617
1907
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1618
1908
  });
1909
+ await appendEvent(event.sessionId, {
1910
+ dscEvent: "compaction",
1911
+ archivedMessageCount: store.archivedMessageCount,
1912
+ summary: summaryMsg.content
1913
+ });
1619
1914
  } else if (keep > 1) {
1620
1915
  keep--;
1621
1916
  continue;
@@ -1630,6 +1925,11 @@ ${line}` : line;
1630
1925
  consecutiveFailures: 0,
1631
1926
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1632
1927
  });
1928
+ await appendEvent(event.sessionId, {
1929
+ dscEvent: "compaction",
1930
+ archivedMessageCount: store.archivedMessageCount,
1931
+ summary: summaryMsg.content
1932
+ });
1633
1933
  } else {
1634
1934
  break;
1635
1935
  }
@@ -1736,7 +2036,7 @@ ${line}` : line;
1736
2036
  });
1737
2037
 
1738
2038
  // ../core/src/tool/permissions.ts
1739
- import fs5 from "fs/promises";
2039
+ import fs6 from "fs/promises";
1740
2040
  import path8 from "path";
1741
2041
  var PRIMARY_ARG, rules2, trustedProject, globCache, globToRegex, DANGER_COMMAND_TOOLS, compileRule, readPermissionConfig, toolNameMatches, ruleMatches, initPermissions, checkPermission, listPermissionRules, buildScopedAllowRule, resolveSettingsPath, addPermissionRule;
1742
2042
  var init_permissions = __esm({
@@ -1807,7 +2107,7 @@ var init_permissions = __esm({
1807
2107
  for (const configPath of paths) {
1808
2108
  let raw;
1809
2109
  try {
1810
- raw = await fs5.readFile(configPath, "utf-8");
2110
+ raw = await fs6.readFile(configPath, "utf-8");
1811
2111
  } catch {
1812
2112
  continue;
1813
2113
  }
@@ -1904,7 +2204,7 @@ var init_permissions = __esm({
1904
2204
  const arr = Array.isArray(cfg.permissions[kind]) ? cfg.permissions[kind] : cfg.permissions[kind] = [];
1905
2205
  if (!arr.includes(ruleStr)) {
1906
2206
  arr.push(ruleStr);
1907
- await fs5.mkdir(path8.dirname(file), { recursive: true });
2207
+ await fs6.mkdir(path8.dirname(file), { recursive: true });
1908
2208
  await atomicWriteJSON(file, cfg);
1909
2209
  }
1910
2210
  if (!rules2[kind].some((r) => r.raw === compiled.raw)) rules2[kind].push(compiled);
@@ -1942,7 +2242,7 @@ var init_sessionRegistry = __esm({
1942
2242
  });
1943
2243
 
1944
2244
  // ../core/src/tool/guard.ts
1945
- import fs6 from "fs/promises";
2245
+ import fs7 from "fs/promises";
1946
2246
  import fsSync from "fs";
1947
2247
  import path9 from "path";
1948
2248
  import ignore from "ignore";
@@ -2051,7 +2351,16 @@ var init_guard = __esm({
2051
2351
  };
2052
2352
  resolveReadablePath = (rel) => {
2053
2353
  const root = getActiveWorkspaceRoot();
2054
- return path9.resolve(root, rel);
2354
+ const absPath = path9.resolve(root, rel);
2355
+ if (!fsSync.existsSync(absPath)) {
2356
+ const rootName = path9.basename(root);
2357
+ const firstSeg = rel.split(/[\\/]/)[0];
2358
+ if (rootName && firstSeg === rootName) {
2359
+ const alt = path9.resolve(root, rel.slice(firstSeg.length + 1));
2360
+ if (fsSync.existsSync(alt)) return alt;
2361
+ }
2362
+ }
2363
+ return absPath;
2055
2364
  };
2056
2365
  assertWithinWorkspace = (absPath, base) => {
2057
2366
  const root = base ?? getActiveWorkspaceRoot();
@@ -2111,11 +2420,11 @@ var init_guard = __esm({
2111
2420
  };
2112
2421
  scanIgnoreFilesRecursive = async (dirPath, base, engine) => {
2113
2422
  try {
2114
- const entries = await fs6.readdir(dirPath, { withFileTypes: true });
2423
+ const entries = await fs7.readdir(dirPath, { withFileTypes: true });
2115
2424
  for (const entry of entries) {
2116
2425
  if (entry.isFile() && (entry.name === ".gitignore" || entry.name === ".agentignore")) {
2117
2426
  const fullPath = path9.join(dirPath, entry.name);
2118
- const content = await fs6.readFile(fullPath, "utf-8");
2427
+ const content = await fs7.readFile(fullPath, "utf-8");
2119
2428
  const relDir = path9.relative(base, dirPath).replace(/\\/g, "/");
2120
2429
  const rules3 = content.split(/\r?\n/).map((line) => {
2121
2430
  const trimmed = line.trim();
@@ -3012,7 +3321,7 @@ ${capped || JSON.stringify(result)}`;
3012
3321
  });
3013
3322
 
3014
3323
  // ../core/src/tool/mcp/loader.ts
3015
- import fs7 from "fs/promises";
3324
+ import fs8 from "fs/promises";
3016
3325
  import path11 from "path";
3017
3326
  import { homedir } from "os";
3018
3327
  function sanitize(s) {
@@ -3022,7 +3331,7 @@ async function readMcpConfig() {
3022
3331
  const configPath = process.env.MCP_CONFIG || path11.join(appConfig.dataDir, "mcp.json");
3023
3332
  let raw;
3024
3333
  try {
3025
- raw = await fs7.readFile(configPath, "utf-8");
3334
+ raw = await fs8.readFile(configPath, "utf-8");
3026
3335
  } catch {
3027
3336
  return {};
3028
3337
  }
@@ -3113,9 +3422,14 @@ async function loadMcpTools() {
3113
3422
  }
3114
3423
  async function initMcpTools(into) {
3115
3424
  const mcpTools = await loadMcpTools();
3116
- if (mcpTools.length > 0) {
3425
+ if (appConfig.mcpExposeMode === "dispatcher") {
3426
+ if (clients.length > 0) {
3427
+ into.push(...mcpDispatcherTools);
3428
+ console.log(`\u{1F50C} [MCP] dispatcher \u6A21\u5F0F\uFF1A\u6CE8\u5165 mcp_list_tools + mcp_call\uFF08\u8986\u76D6 ${clients.length} \u4E2A server / ${mcpTools.length} \u4E2A\u5DE5\u5177\uFF09`);
3429
+ }
3430
+ } else if (mcpTools.length > 0) {
3117
3431
  into.push(...mcpTools);
3118
- console.log(`\u{1F50C} [MCP] \u5171\u6CE8\u5165 ${mcpTools.length} \u4E2A MCP \u5DE5\u5177`);
3432
+ console.log(`\u{1F50C} [MCP] \u5171\u6CE8\u5165 ${mcpTools.length} \u4E2A MCP \u5DE5\u5177 schema\uFF08schemas \u6A21\u5F0F\uFF09`);
3119
3433
  }
3120
3434
  if (clients.some((c) => c.supportsResources)) {
3121
3435
  into.push(...mcpResourceTools);
@@ -3151,7 +3465,7 @@ async function listMcpClients() {
3151
3465
  }
3152
3466
  return out;
3153
3467
  }
3154
- var clients, expandPlaceholders, expandServerConfig, mcpResourceTools, mcpPromptTools;
3468
+ var clients, expandPlaceholders, expandServerConfig, mcpResourceTools, mcpPromptTools, resolveMcpPermissionName, mcpDispatcherTools;
3155
3469
  var init_loader = __esm({
3156
3470
  "../core/src/tool/mcp/loader.ts"() {
3157
3471
  "use strict";
@@ -3315,6 +3629,88 @@ ${text}`;
3315
3629
  }
3316
3630
  }
3317
3631
  ];
3632
+ resolveMcpPermissionName = (calledName, args) => {
3633
+ if (calledName !== "mcp_call") return calledName;
3634
+ const server = typeof args?.server === "string" ? sanitize(args.server) : "";
3635
+ const tool = typeof args?.tool === "string" ? sanitize(args.tool) : "";
3636
+ return server && tool ? `mcp__${server}__${tool}` : calledName;
3637
+ };
3638
+ mcpDispatcherTools = [
3639
+ {
3640
+ type: "function",
3641
+ function: {
3642
+ name: "mcp_list_tools",
3643
+ description: "\u5217\u51FA\u5DF2\u8FDE\u63A5 MCP server \u7684\u5916\u90E8\u5DE5\u5177\u76EE\u5F55\uFF08server / \u5DE5\u5177\u540D / \u4E00\u884C\u63CF\u8FF0 / \u53C2\u6570\u540D\uFF0C\u5FC5\u586B\u53C2\u6570\u5E26 *\uFF09\u3002\u8C03\u7528 MCP \u5DE5\u5177\u7528 mcp_call\uFF08\u987B\u4F20\u56DE server \u4E0E\u5DE5\u5177\u539F\u540D\uFF09\u3002\u76EE\u5F55\u5B9E\u65F6\u67E5\u8BE2\u2014\u2014\u65B0\u589E server \u540E\u5373\u67E5\u5373\u89C1\uFF0C\u65E0\u9700\u4EFB\u4F55\u53D8\u66F4\u3002",
3644
+ parameters: {
3645
+ type: "object",
3646
+ properties: {
3647
+ server: { type: "string", description: "\u4EC5\u5217\u51FA\u8BE5 server \u7684\u5DE5\u5177\uFF08\u7F3A\u7701\u5217\u51FA\u5168\u90E8\u5DF2\u8FDE\u63A5 server\uFF09" }
3648
+ }
3649
+ },
3650
+ safetyLevel: "safe" /* SAFE */,
3651
+ isSync: true,
3652
+ async execute(args) {
3653
+ const server = args?.server;
3654
+ const lines = [];
3655
+ for (const c of clients) {
3656
+ if (server && c.serverName !== server) continue;
3657
+ try {
3658
+ const ts = await c.listTools();
3659
+ for (const t of ts) {
3660
+ const props = t?.inputSchema?.properties && typeof t.inputSchema.properties === "object" ? Object.keys(t.inputSchema.properties) : [];
3661
+ const required = Array.isArray(t?.inputSchema?.required) ? t.inputSchema.required : [];
3662
+ const argList = props.map((p) => required.includes(p) ? `${p}*` : p).join(", ");
3663
+ lines.push(`- [${c.serverName}] ${t.name}${t.description ? `\uFF1A${String(t.description).split("\n")[0]}` : ""}${argList ? `\uFF08\u53C2\u6570\uFF1A${argList}\uFF09` : ""}`);
3664
+ }
3665
+ } catch (e) {
3666
+ console.warn(`\u26A0\uFE0F [MCP] listTools "${c.serverName}" \u5931\u8D25\uFF08\u5DF2\u8DF3\u8FC7\uFF09: ${e?.message ?? e}`);
3667
+ }
3668
+ }
3669
+ if (lines.length === 0) return server ? `(server "${server}" \u672A\u8FDE\u63A5\u6216\u65E0\u5DE5\u5177)` : "(\u65E0\u5DF2\u8FDE\u63A5\u7684 MCP server)";
3670
+ return `[MCP tools]
3671
+ ${lines.join("\n")}`;
3672
+ }
3673
+ }
3674
+ },
3675
+ {
3676
+ type: "function",
3677
+ function: {
3678
+ name: "mcp_call",
3679
+ description: '\u8C03\u7528\u6307\u5B9A MCP server \u7684\u5916\u90E8\u5DE5\u5177\u3002server \u4E0E\u5DE5\u5177\u540D\u5FC5\u586B\u2014\u2014\u5747\u6765\u81EA mcp_list_tools \u7684\u76EE\u5F55\u8F93\u51FA\uFF08\u7528\u539F\u540D\uFF0C\u4E0D\u8981\u6539\u5199\uFF09\u3002args \u4E3A\u8BE5\u5DE5\u5177\u53C2\u6570\u7684 JSON \u5B57\u7B26\u4E32\uFF08\u53C2\u6570\u540D\u89C1\u76EE\u5F55\uFF1B\u65E0\u53C2\u6570\u4F20 "{}"\uFF09\u3002\u6267\u884C\u5916\u90E8\u903B\u8F91\uFF0C\u6BCF\u6B21\u8C03\u7528\u9700\u5BA1\u6279\u3002',
3680
+ parameters: {
3681
+ type: "object",
3682
+ properties: {
3683
+ server: { type: "string", description: "MCP server \u540D\uFF08\u89C1 mcp_list_tools \u8F93\u51FA\uFF09" },
3684
+ tool: { type: "string", description: "\u5DE5\u5177\u540D\uFF08\u89C1 mcp_list_tools \u8F93\u51FA\uFF0C\u7528\u539F\u540D\uFF09" },
3685
+ args: { type: "string", description: '\u5DE5\u5177\u53C2\u6570\u7684 JSON \u5B57\u7B26\u4E32\uFF0C\u5982 {"query":"..."}\uFF1B\u65E0\u53C2\u6570\u4F20 "{}"' }
3686
+ },
3687
+ required: ["server", "tool"]
3688
+ },
3689
+ safetyLevel: "danger" /* DANGER */,
3690
+ isSync: true,
3691
+ requireApproval: (args) => `\u26A0\uFE0F\u3010MCP \u5DE5\u5177\u5BA1\u6279\u3011
3692
+ \u670D\u52A1: ${args?.server} / \u5DE5\u5177: ${args?.tool}
3693
+ \u53C2\u6570: ${args?.args}`,
3694
+ async execute(args) {
3695
+ const { server, tool } = args ?? {};
3696
+ if (!server || !tool) return "\u274C [mcp_call] \u7F3A\u5C11 server \u6216 tool\uFF08\u5148\u7528 mcp_list_tools \u67E5\u76EE\u5F55\uFF09\u3002";
3697
+ let parsed = {};
3698
+ if (typeof args.args === "string" && args.args.trim() && args.args.trim() !== "{}") {
3699
+ try {
3700
+ parsed = JSON.parse(args.args);
3701
+ } catch {
3702
+ return `\u274C [mcp_call] args \u4E0D\u662F\u5408\u6CD5 JSON\uFF1A${String(args.args).slice(0, 200)}\u3002\u8BF7\u4F20\u8BE5\u5DE5\u5177\u53C2\u6570\u7684 JSON \u5B57\u7B26\u4E32\uFF08\u65E0\u53C2\u6570\u4F20 "{}"\uFF09\u3002`;
3703
+ }
3704
+ }
3705
+ const c = findClient(server);
3706
+ if (!c) return `\u274C \u672A\u8FDE\u63A5\u7684 MCP server\uFF1A${server}\uFF08\u7528 mcp_list_tools \u67E5\u770B\u5DF2\u8FDE\u63A5\u5217\u8868\uFF09`;
3707
+ const result = await c.callTool(tool, parsed);
3708
+ return `[MCP ${server}/${tool}]
3709
+ ${result}`;
3710
+ }
3711
+ }
3712
+ }
3713
+ ];
3318
3714
  }
3319
3715
  });
3320
3716
 
@@ -3350,68 +3746,44 @@ var init_httpExecutor = __esm({
3350
3746
  }
3351
3747
  });
3352
3748
 
3353
- // ../core/src/session/transcript.ts
3354
- import fs8 from "fs/promises";
3355
- var readMessages, appendMessage;
3356
- var init_transcript = __esm({
3357
- "../core/src/session/transcript.ts"() {
3749
+ // ../core/src/agent/inbox.ts
3750
+ var INBOX_MAX_ITEMS, sessionInboxes, pushSessionInbox, claimSessionInbox, flushLeftoverToTranscript;
3751
+ var init_inbox = __esm({
3752
+ "../core/src/agent/inbox.ts"() {
3358
3753
  "use strict";
3359
- init_store();
3360
- init_common();
3361
- readMessages = async (sessionId) => {
3362
- await ensureSessionsDir(sessionId);
3363
- const p = getTranscriptPath(sessionId);
3364
- try {
3365
- const text = await fs8.readFile(p, "utf-8");
3366
- const lines = [];
3367
- for (const s of text.split("\n")) {
3368
- const line = s.trim();
3369
- if (line.length === 0) continue;
3370
- try {
3371
- lines.push(JSON.parse(line));
3372
- } catch {
3373
- console.warn(`\u26A0\uFE0F [transcript] \u8DF3\u8FC7\u65E0\u6CD5\u89E3\u6790\u7684\u635F\u574F\u884C: ${line.slice(0, 120)}`);
3374
- }
3375
- }
3376
- return lines;
3377
- } catch (err) {
3378
- if (err?.code === "ENOENT") return [];
3379
- throw err;
3754
+ init_transcript();
3755
+ init_config();
3756
+ INBOX_MAX_ITEMS = 50;
3757
+ sessionInboxes = /* @__PURE__ */ new Map();
3758
+ pushSessionInbox = (sessionId, content) => {
3759
+ if (!appConfig.inboxSteering) return false;
3760
+ const text = (content ?? "").trim();
3761
+ if (!text) return false;
3762
+ const q = sessionInboxes.get(sessionId) ?? [];
3763
+ if (q.length >= INBOX_MAX_ITEMS) {
3764
+ console.warn(`[inbox] \u4F1A\u8BDD ${sessionId} \u961F\u5217\u5DF2\u6EE1\uFF08${INBOX_MAX_ITEMS}\uFF09\uFF0C\u4E22\u5F03\u65B0\u8F93\u5165`);
3765
+ return false;
3380
3766
  }
3767
+ q.push(text);
3768
+ sessionInboxes.set(sessionId, q);
3769
+ return true;
3381
3770
  };
3382
- appendMessage = async (entry) => {
3383
- try {
3384
- await ensureSessionsDir(entry.sessionId);
3385
- const { sessionId, ...rest } = entry;
3386
- const line = { id: createUUID(), ...rest };
3387
- const p = getTranscriptPath(sessionId);
3388
- const payload = JSON.stringify(line) + "\n";
3389
- if (payload.includes("\uFFFD")) {
3390
- const fffdCount = (payload.match(/\uFFFD/g) || []).length;
3391
- console.warn(`\u26A0\uFE0F [transcript] \u6D88\u606F\u542B ${fffdCount} \u4E2A\u4E71\u7801\u5B57\u7B26\uFF08U+FFFD\uFF09\uFF0C\u5DF2\u539F\u6837\u5199\u5165\u5386\u53F2\u3002\u6765\u6E90\u53EF\u80FD\u662F GBK \u5B50\u8FDB\u7A0B\u8F93\u51FA\u6216\u4E71\u7801\u7C98\u8D34\u3002`);
3392
- }
3393
- const MAX_ATTEMPTS = 3;
3394
- for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
3395
- let beforeSize = 0;
3396
- try {
3397
- beforeSize = (await fs8.stat(p)).size;
3398
- } catch {
3399
- }
3400
- try {
3401
- await fs8.appendFile(p, payload, "utf-8");
3402
- return;
3403
- } catch (e) {
3404
- try {
3405
- await fs8.truncate(p, beforeSize);
3406
- } catch {
3407
- }
3408
- if (attempt === MAX_ATTEMPTS) throw e;
3409
- await new Promise((r) => setTimeout(r, 50 * attempt));
3410
- }
3411
- }
3412
- } catch (e) {
3413
- console.warn(`\u26A0\uFE0F \u6D88\u606F\u843D\u76D8\u5931\u8D25\uFF08\u5DF2\u91CD\u8BD5 3 \u6B21\uFF0C\u5185\u5B58\u4E0E\u78C1\u76D8\u8F6C\u5F55\u53EF\u80FD\u4E0D\u4E00\u81F4\uFF09:`, e);
3771
+ claimSessionInbox = (sessionId) => {
3772
+ if (!appConfig.inboxSteering) return [];
3773
+ const q = sessionInboxes.get(sessionId);
3774
+ if (!q || q.length === 0) return [];
3775
+ sessionInboxes.delete(sessionId);
3776
+ return q;
3777
+ };
3778
+ flushLeftoverToTranscript = async (sessionId) => {
3779
+ const texts = claimSessionInbox(sessionId);
3780
+ for (const t of texts) {
3781
+ await appendMessage({ sessionId, role: "user", content: t });
3782
+ }
3783
+ if (texts.length > 0) {
3784
+ console.warn(`[inbox] \u4F1A\u8BDD ${sessionId} \u7ED3\u675F\u65F6 ${texts.length} \u6761\u8865\u5145\u8F93\u5165\u672A\u8BA4\u9886\uFF0C\u5DF2\u5199\u5165 transcript\uFF08\u4E0B\u8F6E\u81EA\u52A8\u5E26\u5165\uFF09`);
3414
3785
  }
3786
+ return texts.length;
3415
3787
  };
3416
3788
  }
3417
3789
  });
@@ -3538,6 +3910,9 @@ var init_planMode = __esm({
3538
3910
  "git_status",
3539
3911
  "git_log",
3540
3912
  "inspect_dependencies",
3913
+ "get_diagnostics",
3914
+ "goto_definition",
3915
+ // ★ P0-A 补漏:LSP 导航/诊断恰是调研阶段最需要的(原白名单漂移漏登记,schema 档与 runtime 档共用此表)
3541
3916
  "web_fetch",
3542
3917
  "web_search"
3543
3918
  // 只读研究类(虽为 DANGER 但不写本地状态;仍走各自审批)
@@ -3715,7 +4090,7 @@ var init_inject2 = __esm({
3715
4090
  init_registry4();
3716
4091
  AGENT_CATALOG_MARKER = "\u3010\u53EF\u7528\u5B50 Agent \u76EE\u5F55\u3011";
3717
4092
  AGENT_CATALOG_FENCE = "\u27E6DSC:AGENT_CATALOG\u27E7";
3718
- AGENT_USAGE_HINT = '\u5F53\u67D0\u5B50\u4EFB\u52A1\u5339\u914D\u4E0B\u5217\u67D0\u4E2A\u5B50 Agent \u7684\u4E13\u957F\u65F6\uFF0C\u8C03\u7528 spawn_agent \u5E76\u4F20\u5165\u5BF9\u5E94 name \u53C2\u6570\uFF08\u5982 spawn_agent(name="code-reviewer", task="..."\uFF09\uFF09\u5C06\u5176\u59D4\u6D3E\u7ED9\u8BE5\u58F0\u660E\u5F0F\u5B50 Agent\uFF1B\u4E0D\u4F20 name \u5219\u8D70\u9ED8\u8BA4\u901A\u7528\u5B50 agent\u3002';
4093
+ AGENT_USAGE_HINT = '\u5F53\u67D0\u5B50\u4EFB\u52A1\u5339\u914D\u4E0B\u5217\u67D0\u4E2A\u5B50 Agent \u7684\u4E13\u957F\u65F6\uFF0C\u8C03\u7528 spawn_agent \u5E76\u4F20\u5165\u5BF9\u5E94 name \u53C2\u6570\uFF08\u5982 spawn_agent(name="code-reviewer", task="..."\uFF09\uFF09\u5C06\u5176\u59D4\u6D3E\u7ED9\u8BE5\u58F0\u660E\u5F0F\u5B50 Agent\uFF1B\u4E0D\u4F20 name \u5219\u8D70\u9ED8\u8BA4\u901A\u7528\u5B50 agent\u3002\n\u59D4\u6D3E\u4F18\u5148\u539F\u5219\uFF08P1-C\uFF09\uFF1A\u9700\u8981\u4E00\u5927\u5768\u4E13\u7528\u5DE5\u5177\u6216\u72EC\u7ACB\u4E0A\u4E0B\u6587\u7684\u5B50\u4EFB\u52A1\uFF08\u6DF1\u5EA6 web \u8C03\u7814\u3001\u6279\u91CF MCP \u5DE5\u5177\u64CD\u4F5C\u3001\u72EC\u7ACB worktree \u8BD5\u9A8C\u7B49\uFF09\uFF0C\u4F18\u5148\u59D4\u6D3E\u7ED9\u5BF9\u5E94\u5B50 Agent\uFF0C\u800C\u975E\u5728\u4E3B\u4F1A\u8BDD\u5806\u53E0\u8C03\u7528\u2014\u2014\u5B50 Agent \u6709\u81EA\u5DF1\u7684\u5DE5\u5177\u767D\u540D\u5355\u4E0E\u72EC\u7ACB\u4E0A\u4E0B\u6587\uFF0C\u4E3B\u4F1A\u8BDD\u4FDD\u6301\u7CBE\u7B80\u3001\u5DE5\u5177\u8868\u4FDD\u6301\u6052\u5B9A\u3002';
3719
4094
  injectAgentCatalog = (message) => {
3720
4095
  const catalog = getAgentCatalog();
3721
4096
  if (!catalog) return;
@@ -3871,6 +4246,7 @@ var prepareToolsAndInjections;
3871
4246
  var init_systemInjections = __esm({
3872
4247
  "../core/src/agent/systemInjections.ts"() {
3873
4248
  "use strict";
4249
+ init_config();
3874
4250
  init_planMode();
3875
4251
  init_toolFilter();
3876
4252
  init_truncate();
@@ -3882,7 +4258,7 @@ var init_systemInjections = __esm({
3882
4258
  init_common();
3883
4259
  prepareToolsAndInjections = async (message, options, events) => {
3884
4260
  const { toolSchemas, planMode, locale: locale2, outputStyle } = options;
3885
- const rawToolsPreEnv = planMode ? filterToolsForPlanMode(toolSchemas ?? []) : appendPlanControlTools(toolSchemas ?? []);
4261
+ const rawToolsPreEnv = planMode && appConfig.planEnforcement === "schema" ? filterToolsForPlanMode(toolSchemas ?? []) : appendPlanControlTools(toolSchemas ?? []);
3886
4262
  const validationCtx = {
3887
4263
  sessionId: options.sessionId,
3888
4264
  cwd: options.cwd ?? process.cwd(),
@@ -4648,9 +5024,19 @@ var init_fs = __esm({
4648
5024
  }
4649
5025
  if (hits.length === 0) return { ok: false, reason: "none", count: 0 };
4650
5026
  if (!replaceAll && hits.length > 1) return { ok: false, reason: "conflict", count: hits.length };
4651
- const newLines = newBlock.split("\n");
4652
5027
  const out = [...contentLines];
4653
- for (let k = hits.length - 1; k >= 0; k--) out.splice(hits[k], oldLines.length, ...newLines);
5028
+ for (let k = hits.length - 1; k >= 0; k--) {
5029
+ const start = hits[k];
5030
+ let baseIndent = "";
5031
+ for (let j = start; j < start + oldLines.length; j++) {
5032
+ if (contentLines[j].trim() !== "") {
5033
+ baseIndent = contentLines[j].match(/^[ \t]*/)?.[0] ?? "";
5034
+ break;
5035
+ }
5036
+ }
5037
+ const aligned = baseIndent ? reindentToBase(newBlock, baseIndent) : newBlock;
5038
+ out.splice(start, oldLines.length, ...aligned.split("\n"));
5039
+ }
4654
5040
  return { ok: true, content: out.join("\n"), count: hits.length };
4655
5041
  };
4656
5042
  diagnoseOldStr = (content, oldBlock) => {
@@ -8778,6 +9164,9 @@ var init_toolExecution = __esm({
8778
9164
  init_backgroundTool();
8779
9165
  init_backup();
8780
9166
  init_autoPermission();
9167
+ init_loader();
9168
+ init_config();
9169
+ init_planMode();
8781
9170
  applyPrivacyMasking = (rules3, args, output) => {
8782
9171
  if (!rules3) return output;
8783
9172
  try {
@@ -8803,6 +9192,7 @@ var init_toolExecution = __esm({
8803
9192
  rawTools,
8804
9193
  events,
8805
9194
  permissionMode,
9195
+ planMode,
8806
9196
  onUIEvent,
8807
9197
  requestApproval: hostRequestApproval,
8808
9198
  requestQuestion: hostRequestQuestion,
@@ -8829,6 +9219,10 @@ var init_toolExecution = __esm({
8829
9219
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: note, resultForUser: note, ok: true, terminal: { kind: "exit_plan_mode", plan } };
8830
9220
  }
8831
9221
  if (calledName === "enter_plan_mode") {
9222
+ if (planMode) {
9223
+ const note2 = "\uFF08\u5DF2\u5728\u8BA1\u5212\u6A21\u5F0F\u4E2D\uFF0C\u65E0\u9700\u91CD\u590D\u8FDB\u5165\u3002\u8BF7\u7EE7\u7EED\u53EA\u8BFB\u8C03\u7814\uFF0C\u5B8C\u6210\u540E\u8C03 exit_plan_mode \u63D0\u4EA4\u65B9\u6848\u3002\uFF09";
9224
+ return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: note2, resultForUser: note2, ok: true };
9225
+ }
8832
9226
  const reason = typeof calledArgs?.reason === "string" ? calledArgs.reason : "";
8833
9227
  const note = "\u{1F4CB} [\u8FDB\u5165\u8BA1\u5212\u6A21\u5F0F] \u6A21\u578B\u8BF7\u6C42\u5148\u4EE5\u53EA\u8BFB\u65B9\u5F0F\u8C03\u7814\u5E76\u89C4\u5212\u65B9\u6848\uFF0C\u5DF2\u5207\u6362\u81F3\u8BA1\u5212\u6A21\u5F0F\u3002";
8834
9228
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: note, resultForUser: note, ok: true, terminal: { kind: "enter_plan_mode", reason } };
@@ -8837,10 +9231,16 @@ var init_toolExecution = __esm({
8837
9231
  const placeholder = "\uFF08\u5DF2\u4E2D\u6B62\uFF0C\u672A\u6267\u884C\uFF09";
8838
9232
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: placeholder, resultForUser: placeholder, ok: false, aborted: true };
8839
9233
  }
9234
+ if (!parseFailed && planMode && appConfig.planEnforcement === "runtime" && calledName !== "enter_plan_mode" && calledName !== "exit_plan_mode" && !PLAN_ALLOWED_TOOLS.has(calledName)) {
9235
+ const note = `\u274C [\u8BA1\u5212\u6A21\u5F0F] \u5F53\u524D\u4E3A\u53EA\u8BFB\u8C03\u7814\u9636\u6BB5\uFF0C\u7981\u6B62 ${calledName}\u3002\u5B8C\u6210\u65B9\u6848\u8BF7\u8C03 exit_plan_mode \u63D0\u4EA4\uFF0C\u7ECF\u7528\u6237\u5BA1\u6279\u540E\u8FDB\u5165\u5B9E\u73B0\u9636\u6BB5\u3002`;
9236
+ events({ sessionId, eventType: "tool.validation.failed", metadata: { depth, decisionSource: llmDecisionSource, durationMs: performance.now() - startTime, round, tools_id: toolCall.id, toolName: calledName, toolSource: "builtin", ok: false, attempt: round }, payload: { output: note } });
9237
+ return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: note, resultForUser: note, ok: false };
9238
+ }
8840
9239
  const matchedTool = rawTools.find((t) => t.function.name === calledName);
8841
9240
  const toolCtx = { sessionId, cwd: getActiveWorkspaceRoot(), abortSignal: signal, depth, keepRecentUnits, compactRatio, modelWindow, parentSystemPrompt, events, onUIEvent, requestApproval: hostRequestApproval, requestQuestion: hostRequestQuestion, emitProgress: (m) => onUIEvent?.({ type: "tool.progress", toolsId: toolCall.id, toolName: calledName, message: m }), permissionMode };
8842
9241
  let result = "";
8843
9242
  let explicitOk = null;
9243
+ let postHookOverride;
8844
9244
  if (parseFailed) {
8845
9245
  result = `\u53C2\u6570\u89E3\u6790\u5931\u8D25\uFF1A\u6A21\u578B\u8FD4\u56DE\u7684 arguments \u4E0D\u662F\u5408\u6CD5 JSON${JSON.stringify(toolCall).slice(0, 300)}`;
8846
9246
  events({ sessionId, eventType: "tool.validation.failed", metadata: { depth, decisionSource: llmDecisionSource, durationMs: performance.now() - startTime, round, tools_id: toolCall.id, toolName: calledName, toolSource: "builtin", ok: false, attempt: round }, payload: { output: result } });
@@ -8848,6 +9248,23 @@ var init_toolExecution = __esm({
8848
9248
  const level = matchedTool.function.safetyLevel;
8849
9249
  let needApproval = level === "mutation" /* MUTATION */ || level === "danger" /* DANGER */;
8850
9250
  let denied = false;
9251
+ let preHooksAdvanced = false;
9252
+ if (appConfig.hookRewrite) {
9253
+ preHooksAdvanced = true;
9254
+ let veto;
9255
+ try {
9256
+ veto = await runPreHooks(calledName, calledArgs, toolCtx);
9257
+ } catch (e) {
9258
+ veto = { deny: true, reason: `\u274C [Pre-hook \u5F02\u5E38]\uFF1A${e?.message ?? e}\u3002\u51FA\u4E8E\u5B89\u5168\u9ED8\u8BA4\u62D2\u7EDD [${calledName}] \u7684\u6267\u884C\u3002` };
9259
+ }
9260
+ if (veto.deny) {
9261
+ denied = true;
9262
+ result = veto.reason || `\u274C [Hook \u62E6\u622A]\uFF1Apre-hook \u62D2\u7EDD\u4E86 [${calledName}] \u7684\u6267\u884C\u3002`;
9263
+ } else if (veto.argsOverride !== void 0 && veto.argsOverride !== null && typeof veto.argsOverride === "object" && !Array.isArray(veto.argsOverride)) {
9264
+ console.log(`\u270F\uFE0F [Pre-hook] [${calledName}] args \u5DF2\u88AB hook \u6539\u5199`);
9265
+ calledArgs = veto.argsOverride;
9266
+ }
9267
+ }
8851
9268
  const protectedPaths = calledName === "move_file" ? [calledArgs?.source, calledArgs?.destination] : isUndoTrigger(calledName) ? [calledArgs?.path] : [];
8852
9269
  const hitProtected = protectedPaths.find((p) => typeof p === "string" && isProtectedWrite(p, toolCtx.cwd));
8853
9270
  if (hitProtected) {
@@ -8856,7 +9273,7 @@ var init_toolExecution = __esm({
8856
9273
  }
8857
9274
  let perm = null;
8858
9275
  try {
8859
- perm = checkPermission(calledName, calledArgs);
9276
+ perm = checkPermission(resolveMcpPermissionName(calledName, calledArgs), calledArgs);
8860
9277
  if (perm === "deny") {
8861
9278
  denied = true;
8862
9279
  result = `\u274C [\u6743\u9650\u89C4\u5219]\uFF1A[${calledName}] \u88AB\u6743\u9650\u89C4\u5219 deny \u62D2\u7EDD\u3002`;
@@ -8882,7 +9299,7 @@ var init_toolExecution = __esm({
8882
9299
  result = `\u{1F512} [\u4E92\u65A5\u9501\u963B\u585E]\uFF1A\u5DF2\u6709\u540E\u53F0\u4EFB\u52A1\u6301\u6709\u9501 [${lockKey}]\uFF0C[${calledName}] \u8C03\u7528\u88AB\u8DF3\u8FC7\u3002`;
8883
9300
  }
8884
9301
  if (needApproval && !denied) {
8885
- const auto = await runAutoCheck(calledName, calledArgs, toolCtx, permissionMode === "auto");
9302
+ const auto = await runAutoCheck(resolveMcpPermissionName(calledName, calledArgs), calledArgs, toolCtx, permissionMode === "auto");
8886
9303
  if (auto === "allow") {
8887
9304
  needApproval = false;
8888
9305
  } else if (auto === "deny") {
@@ -8913,7 +9330,7 @@ var init_toolExecution = __esm({
8913
9330
  }
8914
9331
  }
8915
9332
  }
8916
- if (!denied) {
9333
+ if (!denied && !preHooksAdvanced) {
8917
9334
  let veto;
8918
9335
  try {
8919
9336
  veto = await runPreHooks(calledName, calledArgs, toolCtx);
@@ -8957,9 +9374,12 @@ ${result}`;
8957
9374
  result = `\u5DE5\u5177\u6267\u884C\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`;
8958
9375
  events({ sessionId, eventType: "tool.failed", metadata: { depth, decisionSource: llmDecisionSource, durationMs: performance.now() - startTime, round, tools_id: toolCall.id, toolName: calledName, toolSource: "builtin", ok: false, attempt: round }, payload: { output: result } });
8959
9376
  }
8960
- await runPostHooks(calledName, calledArgs, result, toolCtx).catch((e) => {
9377
+ try {
9378
+ const post = await runPostHooks(calledName, calledArgs, result, toolCtx);
9379
+ postHookOverride = post?.resultOverride;
9380
+ } catch (e) {
8961
9381
  console.warn(`\u26A0\uFE0F post-hook [${calledName}] \u5F02\u5E38\uFF08\u5DF2\u5FFD\u7565\uFF09:`, e?.message ?? e);
8962
- });
9382
+ }
8963
9383
  }
8964
9384
  } else {
8965
9385
  result = `\u9519\u8BEF\uFF1A\u672A\u77E5\u5DE5\u5177 "${calledName}" \u6216\u8BE5\u5DE5\u5177\u65E0\u53EF\u6267\u884C\u51FD\u6570`;
@@ -8980,6 +9400,10 @@ ${result}`;
8980
9400
  } catch {
8981
9401
  }
8982
9402
  }
9403
+ if (postHookOverride !== void 0) {
9404
+ console.log(`\u270F\uFE0F [Post-hook] [${calledName}] \u6A21\u578B\u89C6\u56FE\u7ED3\u679C\u5DF2\u88AB hook \u6539\u5199`);
9405
+ resultForModel = truncateToolResult(postHookOverride, matchedTool?.function?.maxOutputCharacters);
9406
+ }
8983
9407
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel, resultForUser, ok };
8984
9408
  };
8985
9409
  }
@@ -9503,6 +9927,18 @@ async function* runAgent(message, options) {
9503
9927
  let lastRealPromptTokens = persistedCalib.lastRealPromptTokens;
9504
9928
  let lastCachedTokens = persistedCalib.lastCachedTokens;
9505
9929
  const breaker = createRepeatBreaker({ sessionId, depth, llmDecisionSource, startTime, events });
9930
+ const runId = createUUID();
9931
+ let usageSum;
9932
+ const addUsage = (u) => {
9933
+ if (!u) return;
9934
+ usageSum = {
9935
+ prompt_tokens: (usageSum?.prompt_tokens ?? 0) + (u.prompt_tokens ?? 0),
9936
+ completion_tokens: (usageSum?.completion_tokens ?? 0) + (u.completion_tokens ?? 0),
9937
+ total_tokens: (usageSum?.total_tokens ?? 0) + (u.total_tokens ?? 0),
9938
+ cached_tokens: (usageSum?.cached_tokens ?? 0) + (u.cached_tokens ?? 0)
9939
+ };
9940
+ };
9941
+ await appendEvent(sessionId, { dscEvent: "run.start", runId, depth });
9506
9942
  try {
9507
9943
  while (true) {
9508
9944
  round++;
@@ -9530,6 +9966,14 @@ async function* runAgent(message, options) {
9530
9966
  yield { type: "final", text: lastContent || "\uFF08\u5DF2\u4E2D\u6B62\uFF09" };
9531
9967
  return;
9532
9968
  }
9969
+ const inboxTexts = claimSessionInbox(sessionId);
9970
+ if (inboxTexts.length > 0) {
9971
+ for (const t of inboxTexts) {
9972
+ message.push({ role: "user", content: t });
9973
+ await appendMessage({ sessionId, role: "user", content: t });
9974
+ }
9975
+ yield { type: "inbox.claimed", texts: inboxTexts };
9976
+ }
9533
9977
  yield { type: "round.start", round };
9534
9978
  try {
9535
9979
  await ensureFitsWindow(
@@ -9596,6 +10040,7 @@ async function* runAgent(message, options) {
9596
10040
  lastRealPromptTokens = infResult.usage.prompt_tokens;
9597
10041
  lastCachedTokens = infResult.usage.cached_tokens;
9598
10042
  }
10043
+ addUsage(infResult.usage);
9599
10044
  const assistantMessage = infResult.assistantMessage;
9600
10045
  message.push(assistantMessage);
9601
10046
  await appendMessage({ ...assistantMessage, sessionId });
@@ -9604,6 +10049,7 @@ async function* runAgent(message, options) {
9604
10049
  }
9605
10050
  const tcCount = assistantMessage.tool_calls?.length ?? 0;
9606
10051
  if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
10052
+ await appendEvent(sessionId, { dscEvent: "round.end", runId, round, usage: infResult.usage });
9607
10053
  const finalText = (typeof assistantMessage.content === "string" ? assistantMessage.content : "") || "";
9608
10054
  const intercepted = nudges.interceptFinal(finalText, round);
9609
10055
  if (intercepted) continue;
@@ -9614,6 +10060,11 @@ async function* runAgent(message, options) {
9614
10060
  const repeatVerdict = breaker.check(assistantMessage.tool_calls, round, lastContent);
9615
10061
  if (repeatVerdict.tripped) {
9616
10062
  stopReason = "repeat";
10063
+ for (const tc of assistantMessage.tool_calls) {
10064
+ const ph = "\uFF08\u91CD\u590D\u8C03\u7528\u7194\u65AD\uFF0C\u672A\u6267\u884C\uFF09";
10065
+ message.push({ role: "tool", tool_call_id: tc.id, content: ph });
10066
+ await appendMessage({ sessionId, role: "tool", tool_call_id: tc.id, content: ph });
10067
+ }
9617
10068
  yield { type: "final", text: repeatVerdict.text };
9618
10069
  return;
9619
10070
  }
@@ -9628,6 +10079,8 @@ async function* runAgent(message, options) {
9628
10079
  rawTools,
9629
10080
  events,
9630
10081
  permissionMode: options.permissionMode,
10082
+ planMode: !!options.planMode,
10083
+ // ★ P0-A runtime 档:计划期写工具在 processToolCall 执行层拒绝(工具表恒定不裁剪)
9631
10084
  onUIEvent: options.onUIEvent,
9632
10085
  requestApproval: options.requestApproval,
9633
10086
  requestQuestion: options.requestQuestion,
@@ -9645,6 +10098,7 @@ async function* runAgent(message, options) {
9645
10098
  yield { type: "final", text: scheduleResult.terminalText };
9646
10099
  return;
9647
10100
  }
10101
+ await appendEvent(sessionId, { dscEvent: "round.end", runId, round, usage: infResult.usage });
9648
10102
  }
9649
10103
  } catch (toolErr) {
9650
10104
  if (signal?.aborted) {
@@ -9658,6 +10112,14 @@ async function* runAgent(message, options) {
9658
10112
  \uFF08\u5DE5\u5177\u6267\u884C\u5F02\u5E38\uFF1A${errMsg}\uFF09` };
9659
10113
  return;
9660
10114
  } finally {
10115
+ try {
10116
+ await flushLeftoverToTranscript(sessionId);
10117
+ } catch {
10118
+ }
10119
+ try {
10120
+ await appendEvent(sessionId, { dscEvent: "run.end", runId, stopReason: signal?.aborted ? "aborted" : stopReason, rounds: round, usage: usageSum });
10121
+ } catch {
10122
+ }
9661
10123
  try {
9662
10124
  const reason = signal?.aborted ? "aborted" : stopReason;
9663
10125
  await dispatch("Stop", { sessionId, cwd, lastText: lastContent || "", reason });
@@ -9673,6 +10135,8 @@ var init_runAgent = __esm({
9673
10135
  "../core/src/agent/runAgent.ts"() {
9674
10136
  "use strict";
9675
10137
  init_transcript();
10138
+ init_inbox();
10139
+ init_common();
9676
10140
  init_contextCore();
9677
10141
  init_store();
9678
10142
  init_truncate();
@@ -9685,17 +10149,30 @@ var init_runAgent = __esm({
9685
10149
  }
9686
10150
  });
9687
10151
 
9688
- // ../core/src/session/content.ts
9689
- var repairOrphanToolCalls, decayOldToolResults, buildContextMessages;
9690
- var init_content = __esm({
9691
- "../core/src/session/content.ts"() {
10152
+ // ../core/src/session/recovery.ts
10153
+ var detectInterruption, repairOrphanToolCalls, reconcileCompaction, recoverSession;
10154
+ var init_recovery = __esm({
10155
+ "../core/src/session/recovery.ts"() {
9692
10156
  "use strict";
9693
- init_contextCore();
9694
10157
  init_transcript();
9695
10158
  init_store();
9696
- init_config();
9697
- repairOrphanToolCalls = (msgs) => {
10159
+ detectInterruption = (lines) => {
10160
+ let sawEvents = false;
10161
+ let lastStart;
10162
+ const closed = /* @__PURE__ */ new Set();
10163
+ for (const l of lines) {
10164
+ if (!isEventLine(l)) continue;
10165
+ sawEvents = true;
10166
+ if (l.dscEvent === "run.start") lastStart = l;
10167
+ else if (l.dscEvent === "run.end" || l.dscEvent === "run.abandoned") closed.add(l.runId);
10168
+ }
10169
+ if (!sawEvents || !lastStart) return { kind: "legacy" };
10170
+ return closed.has(lastStart.runId) ? { kind: "clean" } : { kind: "crashed", runId: lastStart.runId };
10171
+ };
10172
+ repairOrphanToolCalls = (msgs, confirmedCrash) => {
10173
+ const placeholder = confirmedCrash ? "\uFF08\u4E0A\u6B21\u4F1A\u8BDD\u5728\u5DE5\u5177\u6267\u884C\u4E2D\u5D29\u6E83\uFF08run \u672A\u95ED\u5408\uFF09\uFF0C\u8BE5\u8C03\u7528\u672A\u6267\u884C\uFF0C\u5DF2\u8DF3\u8FC7\u3002\uFF09" : "\uFF08\u8BE5\u5DE5\u5177\u8C03\u7528\u56E0\u4E0A\u6B21\u4F1A\u8BDD\u5F02\u5E38\u4E2D\u65AD\u672A\u7559\u4E0B\u7ED3\u679C\uFF0C\u5DF2\u8DF3\u8FC7\u3002\uFF09";
9698
10174
  const out = [];
10175
+ const repairedToolCallIds = [];
9699
10176
  for (let i = 0; i < msgs.length; i++) {
9700
10177
  const m = msgs[i];
9701
10178
  out.push(m);
@@ -9708,13 +10185,60 @@ var init_content = __esm({
9708
10185
  }
9709
10186
  for (const tc of m.tool_calls) {
9710
10187
  if (tc.id && !answered.has(tc.id)) {
9711
- out.push({ role: "tool", tool_call_id: tc.id, content: "\uFF08\u8BE5\u5DE5\u5177\u8C03\u7528\u56E0\u4E0A\u6B21\u4F1A\u8BDD\u5F02\u5E38\u4E2D\u65AD\u672A\u7559\u4E0B\u7ED3\u679C\uFF0C\u5DF2\u8DF3\u8FC7\u3002\uFF09" });
10188
+ out.push({ role: "tool", tool_call_id: tc.id, content: placeholder });
10189
+ repairedToolCallIds.push(tc.id);
9712
10190
  }
9713
10191
  }
9714
10192
  }
9715
10193
  }
9716
- return out;
10194
+ return { msgs: out, repairedToolCallIds, confirmedCrash };
10195
+ };
10196
+ reconcileCompaction = (state, lines) => {
10197
+ let last;
10198
+ for (const l of lines) {
10199
+ if (isEventLine(l) && l.dscEvent === "compaction") last = l;
10200
+ }
10201
+ if (!last) return { archivedMessageCount: state.archivedMessageCount, rollingSummary: state.rollingSummary, desync: "legacy" };
10202
+ let desync = "none";
10203
+ if (last.archivedMessageCount !== state.archivedMessageCount) {
10204
+ desync = state.archivedMessageCount > last.archivedMessageCount ? "state-ahead" : "event-ahead";
10205
+ console.warn(
10206
+ `\u26A0\uFE0F [recovery] \u538B\u7F29\u72B6\u6001 desync\uFF08${desync}\uFF09\uFF1Astate.json archivedMessageCount=${state.archivedMessageCount} vs \u672B\u4E2A compaction \u4E8B\u4EF6=${last.archivedMessageCount}\u3002\u4EE5 state \u4E3A\u51C6\uFF08slice \u6267\u884C\u95F8\u95E8\uFF09\uFF0C\u4E8B\u4EF6\u4FA7\u4EC5\u955C\u50CF\u3002`
10207
+ );
10208
+ }
10209
+ return { archivedMessageCount: state.archivedMessageCount, rollingSummary: state.rollingSummary, desync };
9717
10210
  };
10211
+ recoverSession = async (sessionId) => {
10212
+ const lines = await readTranscriptLines(sessionId);
10213
+ const interruption = detectInterruption(lines);
10214
+ if (interruption.kind === "crashed") {
10215
+ await appendEvent(sessionId, {
10216
+ dscEvent: "run.abandoned",
10217
+ runId: interruption.runId,
10218
+ reason: "run.end missing: interrupted by process exit/kill"
10219
+ });
10220
+ console.warn(`\u27E6recovery\u27E7 \u68C0\u6D4B\u5230\u672A\u95ED\u5408 run\uFF08${interruption.runId.slice(0, 8)}\u2026\uFF09\u2192 \u5DF2\u8865 run.abandoned \u95ED\u5893\u6807\u8BB0\uFF1B\u5B64\u513F\u4FEE\u590D\u6309\u300C\u5D29\u6E83\u786E\u8BA4\u300D\u6863\u6267\u884C\u3002`);
10221
+ }
10222
+ const state = await getRollingState(sessionId);
10223
+ const compaction = reconcileCompaction(
10224
+ { archivedMessageCount: state.archivedMessageCount, rollingSummary: state.rollingSummary },
10225
+ lines
10226
+ );
10227
+ return { interruption, compaction };
10228
+ };
10229
+ }
10230
+ });
10231
+
10232
+ // ../core/src/session/content.ts
10233
+ var decayOldToolResults, buildContextMessages;
10234
+ var init_content = __esm({
10235
+ "../core/src/session/content.ts"() {
10236
+ "use strict";
10237
+ init_contextCore();
10238
+ init_transcript();
10239
+ init_store();
10240
+ init_config();
10241
+ init_recovery();
9718
10242
  decayOldToolResults = (msgs) => {
9719
10243
  const units = groupUnits(msgs);
9720
10244
  const cutoffUnitIdx = Math.max(0, units.length - appConfig.KEEP_RECENT_UNITS);
@@ -9740,21 +10264,28 @@ var init_content = __esm({
9740
10264
  });
9741
10265
  };
9742
10266
  buildContextMessages = async (sessionId, currentUserMsg, systemPrompt) => {
9743
- const all = (await readMessages(sessionId)).map(cleanMsg);
10267
+ const lines = await readTranscriptLines(sessionId);
9744
10268
  const store = await getRollingState(sessionId);
10269
+ const eventAware = appConfig.transcriptEvents || lines.some(isEventLine);
10270
+ const recovery = eventAware ? await recoverSession(sessionId) : {
10271
+ interruption: { kind: "legacy" },
10272
+ compaction: { archivedMessageCount: store.archivedMessageCount, rollingSummary: store.rollingSummary, desync: "legacy" }
10273
+ };
10274
+ const all = lines.filter((l) => !isEventLine(l)).map(cleanMsg);
9745
10275
  const result = [
9746
10276
  { role: "system", content: systemPrompt },
9747
10277
  // 存储系统提示词
9748
- { role: "system", content: store.rollingSummary }
10278
+ { role: "system", content: recovery.compaction.rollingSummary }
9749
10279
  // 后续存储摘要使用
9750
10280
  ];
9751
- const archivedMessageCount = store.archivedMessageCount;
10281
+ const archivedMessageCount = recovery.compaction.archivedMessageCount;
9752
10282
  const messageAll = all.slice(archivedMessageCount);
9753
10283
  if (messageAll.length === 0) {
9754
10284
  result.push(currentUserMsg);
9755
10285
  return result;
9756
10286
  }
9757
- result.push(...decayOldToolResults(repairOrphanToolCalls(messageAll)));
10287
+ const repair = repairOrphanToolCalls(messageAll, recovery.interruption.kind === "crashed");
10288
+ result.push(...decayOldToolResults(repair.msgs));
9758
10289
  result.push(currentUserMsg);
9759
10290
  return result;
9760
10291
  };
@@ -9818,7 +10349,8 @@ var init_subagent = __esm({
9818
10349
  parentSystemPrompt
9819
10350
  ].join("\n");
9820
10351
  const allTools = getGlobalTools();
9821
- const toolSchemas = manifest && manifest.tools.length > 0 ? allTools.filter((t) => manifest.tools.includes(t.function.name)) : allTools.filter((t) => !SUBAGENT_DENYLIST.has(t.function.name));
10352
+ const wantsMcp = manifest?.tools.some((t) => t === "mcp_call" || t === "mcp_list_tools" || t.startsWith("mcp__")) ?? false;
10353
+ const toolSchemas = manifest && manifest.tools.length > 0 ? allTools.filter((t) => manifest.tools.includes(t.function.name) || wantsMcp && (t.function.name === "mcp_call" || t.function.name === "mcp_list_tools")) : allTools.filter((t) => !SUBAGENT_DENYLIST.has(t.function.name));
9822
10354
  console.log(`\u{1F423} \u6D3E\u751F\u5B50 Agent [\u6DF1\u5EA6: ${ctx.depth + 1}/${MAX_AGENT_DEPTH}]${manifest ? ` \u58F0\u660E\u5F0F=${manifest.name}` : ""} \u4EFB\u52A1: "${task.slice(0, 50)}..."`);
9823
10355
  try {
9824
10356
  const subMessages = await buildContextMessages(subSessionId, { role: "user", content: task }, subSystem);
@@ -9884,7 +10416,7 @@ var init_subagent = __esm({
9884
10416
  // ../core/src/hooks/loader.ts
9885
10417
  import fs27 from "fs/promises";
9886
10418
  import path29 from "path";
9887
- var readHooksConfig, appendValidated, validateRule, parseAgentDecision, compileRule2, loadHooks, initHooks;
10419
+ var readHooksConfig, appendValidated, validateRule, parseStdoutDecision, parseAgentDecision, compileRule2, loadHooks, initHooks;
9888
10420
  var init_loader4 = __esm({
9889
10421
  "../core/src/hooks/loader.ts"() {
9890
10422
  "use strict";
@@ -10028,6 +10560,24 @@ var init_loader4 = __esm({
10028
10560
  requireApproval: raw.requireApproval === true ? true : void 0
10029
10561
  };
10030
10562
  };
10563
+ parseStdoutDecision = (stdout) => {
10564
+ const s = (stdout ?? "").trim();
10565
+ if (!s || !s.startsWith("{")) return void 0;
10566
+ try {
10567
+ const parsed = JSON.parse(s);
10568
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
10569
+ const out = {};
10570
+ if (parsed.deny === true) {
10571
+ out.deny = true;
10572
+ if (typeof parsed.reason === "string" && parsed.reason) out.reason = parsed.reason;
10573
+ }
10574
+ if (parsed.argsOverride !== void 0) out.argsOverride = parsed.argsOverride;
10575
+ if (typeof parsed.resultOverride === "string" && parsed.resultOverride) out.resultOverride = parsed.resultOverride;
10576
+ return out.deny !== void 0 || out.argsOverride !== void 0 || out.resultOverride !== void 0 ? out : void 0;
10577
+ } catch {
10578
+ return void 0;
10579
+ }
10580
+ };
10031
10581
  parseAgentDecision = (output) => {
10032
10582
  const lines = (output ?? "").split(/\r?\n/);
10033
10583
  for (let i = lines.length - 1; i >= 0; i--) {
@@ -10096,9 +10646,15 @@ var init_loader4 = __esm({
10096
10646
  }
10097
10647
  try {
10098
10648
  const parsed = JSON.parse(res.responseBody);
10099
- if (parsed && typeof parsed === "object" && parsed.deny === true) {
10100
- const reason = typeof parsed.reason === "string" ? parsed.reason : `hook http \u62D2\u7EDD\uFF08status ${res.status}\uFF09`;
10101
- return { deny: true, reason };
10649
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
10650
+ if (parsed.deny === true) {
10651
+ const reason = typeof parsed.reason === "string" ? parsed.reason : `hook http \u62D2\u7EDD\uFF08status ${res.status}\uFF09`;
10652
+ return { deny: true, reason };
10653
+ }
10654
+ const out = {};
10655
+ if (parsed.argsOverride !== void 0) out.argsOverride = parsed.argsOverride;
10656
+ if (typeof parsed.resultOverride === "string" && parsed.resultOverride) out.resultOverride = parsed.resultOverride;
10657
+ if (out.argsOverride !== void 0 || out.resultOverride !== void 0) return out;
10102
10658
  }
10103
10659
  } catch {
10104
10660
  }
@@ -10127,6 +10683,10 @@ var init_loader4 = __esm({
10127
10683
  const detail = res.stderr ? `\uFF1A${res.stderr.slice(0, 200)}` : "";
10128
10684
  return { deny: true, reason: `hook \u9000\u51FA\u7801 ${res.exitCode}${detail}` };
10129
10685
  }
10686
+ if (res.exitCode === 0) {
10687
+ const out = parseStdoutDecision(res.stdout);
10688
+ if (out) return out;
10689
+ }
10130
10690
  return { deny: false };
10131
10691
  }
10132
10692
  };
@@ -10361,9 +10921,9 @@ var init_loader6 = __esm({
10361
10921
  );
10362
10922
  for (const { item: m, file, source } of picked) {
10363
10923
  if (m.tools.length > 0) {
10364
- const unknown = m.tools.filter((t) => !known.has(t));
10924
+ const unknown = m.tools.filter((t) => !known.has(t) && !t.startsWith("mcp__"));
10365
10925
  if (unknown.length) console.warn(`\u26A0\uFE0F [agents] ${m.name} \u58F0\u660E\u4E86\u672A\u77E5\u5DE5\u5177 [${unknown.join(", ")}]\uFF0C\u5DF2\u5254\u9664`);
10366
- m.tools = m.tools.filter((t) => known.has(t));
10926
+ m.tools = m.tools.filter((t) => known.has(t) || t.startsWith("mcp__"));
10367
10927
  }
10368
10928
  if (source === "project") {
10369
10929
  console.warn(`\u26A0\uFE0F\u3010\u5B89\u5168\u63D0\u793A\u3011\u5DF2\u52A0\u8F7D\u9879\u76EE\u7EA7\u58F0\u660E\u5F0F\u5B50 Agent [${m.name}]\uFF08${file}\uFF09\u3002\u5176 tools \u767D\u540D\u5355\u53EF\u663E\u5F0F\u6388\u6743\u9AD8\u5371\u5DE5\u5177\uFF0C\u8BF7\u5728\u4FE1\u4EFB\u8BE5\u9879\u76EE\u65F6\u542F\u7528\uFF1B\u514B\u9686\u672A\u77E5\u4ED3\u5E93\u524D\u8BF7\u6838\u67E5 .deepseeker-code/agents/\u3002`);
@@ -10564,16 +11124,26 @@ var init_bootstrap = __esm({
10564
11124
  if (typeof AbortSignal.any !== "function" || typeof AbortSignal.timeout !== "function") {
10565
11125
  console.error("\u274C \u5F53\u524D Node \u7248\u672C\u7F3A\u5C11 AbortSignal.any/timeout\uFF08\u9700 Node 20.3+\uFF09\uFF0Cweb_fetch \u76F8\u5173\u80FD\u529B\u5C06\u4E0D\u53EF\u7528\uFF0C\u5EFA\u8BAE\u5347\u7EA7 Node\u3002");
10566
11126
  }
10567
- await sweepOrphanedWorktrees().catch((e) => console.warn(`\u26A0\uFE0F [worktree] \u542F\u52A8\u671F\u6E05\u626B\u5931\u8D25\uFF08\u5DF2\u5FFD\u7565\uFF09: ${e?.message ?? e}`));
10568
- await initMcpTools(into);
10569
- await initHooks(includeProject);
10570
- await initPermissions(includeProject);
11127
+ await Promise.all([
11128
+ // P2-13 worktree 孤儿清扫:进程重启 = 上次 run_workflow 未清理的 worktree 全是孤儿。best-effort,失败不阻断。
11129
+ sweepOrphanedWorktrees().catch((e) => console.warn(`worktree \u542F\u52A8\u671F\u6E05\u626B\u5931\u8D25\uFF08\u5DF2\u5FFD\u7565\uFF09: ${e?.message ?? e}`)),
11130
+ // 连接配置的 MCP 服务器并注入其工具(★ 通常最慢:spawn 子进程 + stdio 握手;无配置静默跳过)。仅读全局 mcp.json,不受 includeProject 影响。
11131
+ initMcpTools(into),
11132
+ // 加载声明式 hooks(settings.json;无配置时静默跳过)。未信任时跳过项目级(hooks 会 spawn 执行命令,高风险)
11133
+ initHooks(includeProject),
11134
+ // 加载细粒度权限规则(settings.json permissions.{allow,deny,ask};无配置时静默跳过)。未信任时跳过项目级(防 auto-approve 绕审批)
11135
+ initPermissions(includeProject),
11136
+ // 缓存项目指引(CLAUDE.md/AGENTS.md/AGENT.md)到内存,供每轮自动注入 system prompt。未信任时整体跳过(无全局源,防提示注入)
11137
+ initProjectGuide(includeProject),
11138
+ // 加载斜杠命令(builtin/global/project;用户输入 /<name> 时前置展开)。未信任时跳过项目级
11139
+ initCommands(includeProject),
11140
+ // 加载输出风格(builtin/global/project;用户经 CLI /output-style <name> 选用,runAgent 注入 persona)。未信任时跳过项目级
11141
+ initOutputStyles(includeProject),
11142
+ // 加载持久记忆(global/project;runAgent 注入一行索引,memory_read 按需召回全文)。未信任时跳过项目级(防提示注入)
11143
+ initMemories(includeProject)
11144
+ ]);
10571
11145
  await initSkills(into, includeProject);
10572
11146
  await initAgents(into, includeProject);
10573
- await initProjectGuide(includeProject);
10574
- await initCommands(includeProject);
10575
- await initOutputStyles(includeProject);
10576
- await initMemories(includeProject);
10577
11147
  return () => {
10578
11148
  try {
10579
11149
  disposeAllMcpClients();
@@ -11219,6 +11789,124 @@ ${lines.join("\n")}
11219
11789
  }
11220
11790
  });
11221
11791
 
11792
+ // ../core/src/session/fork.ts
11793
+ import fs37 from "fs/promises";
11794
+ var listForkAnchors, derivePrefix, forkSession;
11795
+ var init_fork = __esm({
11796
+ "../core/src/session/fork.ts"() {
11797
+ "use strict";
11798
+ init_common();
11799
+ init_transcript();
11800
+ init_store();
11801
+ listForkAnchors = (lines) => {
11802
+ const anchors = [];
11803
+ let lastUser = "";
11804
+ for (const l of lines) {
11805
+ if (isEventLine(l)) continue;
11806
+ const row = l;
11807
+ if (row?.role === "user") {
11808
+ lastUser = typeof row.content === "string" ? row.content : "";
11809
+ } else if (row?.role === "assistant") {
11810
+ const content = typeof row.content === "string" ? row.content : "";
11811
+ const names = Array.isArray(row.tool_calls) ? row.tool_calls.map((tc) => tc?.function?.name ?? "").filter(Boolean).join(", ") : "";
11812
+ anchors.push({
11813
+ lineId: row.id,
11814
+ roundNo: anchors.length + 1,
11815
+ userPreview: lastUser,
11816
+ assistantPreview: content || (names ? `\u{1F527} ${names}` : "(\u7A7A\u56DE\u590D)")
11817
+ });
11818
+ }
11819
+ }
11820
+ return anchors;
11821
+ };
11822
+ derivePrefix = (lines, upToLineId) => {
11823
+ let endIdx = lines.length - 1;
11824
+ if (upToLineId !== void 0) {
11825
+ const idx = lines.findIndex((l) => l?.id === upToLineId);
11826
+ if (idx < 0) throw new Error(`upToLineId\uFF08${upToLineId}\uFF09\u5728\u6E90\u4F1A\u8BDD\u8F6C\u5F55\u4E2D\u672A\u627E\u5230`);
11827
+ endIdx = idx;
11828
+ } else {
11829
+ let lastEnd = -1;
11830
+ let lastStart = -1;
11831
+ lines.forEach((l, i) => {
11832
+ if (!isEventLine(l)) return;
11833
+ if (l.dscEvent === "run.end" || l.dscEvent === "run.abandoned") lastEnd = i;
11834
+ else if (l.dscEvent === "run.start") lastStart = i;
11835
+ });
11836
+ if (lastEnd >= 0) endIdx = lastEnd;
11837
+ else if (lastStart >= 0) endIdx = lastStart - 1;
11838
+ }
11839
+ const prefix = endIdx < 0 ? [] : lines.slice(0, endIdx + 1);
11840
+ let archivedMessageCount = 0;
11841
+ let rollingSummary = "";
11842
+ let lastStartRunId;
11843
+ const closed = /* @__PURE__ */ new Set();
11844
+ for (const l of prefix) {
11845
+ if (!isEventLine(l)) continue;
11846
+ if (l.dscEvent === "compaction") {
11847
+ archivedMessageCount = l.archivedMessageCount;
11848
+ rollingSummary = l.summary;
11849
+ } else if (l.dscEvent === "run.start") {
11850
+ lastStartRunId = l.runId;
11851
+ } else if (l.dscEvent === "run.end" || l.dscEvent === "run.abandoned") {
11852
+ closed.add(l.runId);
11853
+ }
11854
+ }
11855
+ const unclosedRunId = lastStartRunId && !closed.has(lastStartRunId) ? lastStartRunId : void 0;
11856
+ return { lines: prefix, archivedMessageCount, rollingSummary, unclosedRunId };
11857
+ };
11858
+ forkSession = async (sourceId, upToLineId) => {
11859
+ assertSafeSessionId(sourceId);
11860
+ const lines = await readTranscriptLines(sourceId);
11861
+ if (lines.length === 0) throw new Error("\u6E90\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u8F6C\u5F55\u4E3A\u7A7A");
11862
+ const prefix = derivePrefix(lines, upToLineId);
11863
+ const newId = createUUID();
11864
+ await ensureSessionsDir(newId);
11865
+ const rawText = await fs37.readFile(getTranscriptPath(sourceId), "utf-8");
11866
+ const kept = [];
11867
+ let good = 0;
11868
+ for (const s of rawText.split("\n")) {
11869
+ if (good >= prefix.lines.length) break;
11870
+ kept.push(s);
11871
+ const t = s.trim();
11872
+ if (!t) continue;
11873
+ try {
11874
+ JSON.parse(t);
11875
+ good++;
11876
+ } catch {
11877
+ }
11878
+ }
11879
+ let payload = kept.join("\n");
11880
+ if (payload.length > 0 && !payload.endsWith("\n")) payload += "\n";
11881
+ await fs37.writeFile(getTranscriptPath(newId), payload, "utf-8");
11882
+ const srcState = await readStore(sourceId);
11883
+ const srcRolling = await getRollingState(sourceId);
11884
+ const legacy = !lines.some(isEventLine);
11885
+ const wholeFile = prefix.lines.length === lines.length;
11886
+ const archivedMessageCount = legacy && wholeFile ? srcRolling.archivedMessageCount : prefix.archivedMessageCount;
11887
+ const rollingSummary = legacy && wholeFile ? srcRolling.rollingSummary : prefix.rollingSummary;
11888
+ const srcTitle = typeof srcState?.title === "string" && srcState.title ? srcState.title : "\u4F1A\u8BDD";
11889
+ await writeStore(newId, {
11890
+ sessionId: newId,
11891
+ createAt: (/* @__PURE__ */ new Date()).toISOString(),
11892
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11893
+ archivedMessageCount,
11894
+ rollingSummary,
11895
+ consecutiveFailures: 0,
11896
+ title: `${srcTitle}(\u5206\u53C9)`.slice(0, 200),
11897
+ // ★ 校准数据透传:fork 首轮压缩判定不从 1.4 重来(纯收益,语义同源会话续接)
11898
+ calibRatio: srcRolling.calibRatio,
11899
+ lastRealPromptTokens: srcRolling.lastRealPromptTokens,
11900
+ lastCachedTokens: srcRolling.lastCachedTokens
11901
+ });
11902
+ if (prefix.unclosedRunId) {
11903
+ await appendEvent(newId, { dscEvent: "run.abandoned", runId: prefix.unclosedRunId, reason: "forked from interrupted run" });
11904
+ }
11905
+ return { sessionId: newId, copiedLines: prefix.lines.length, archivedMessageCount };
11906
+ };
11907
+ }
11908
+ });
11909
+
11222
11910
  // src/cliHost.ts
11223
11911
  var createCliRequestApproval, createCliRequestQuestion;
11224
11912
  var init_cliHost = __esm({
@@ -11303,13 +11991,47 @@ var init_util = __esm({
11303
11991
 
11304
11992
  // src/replay.ts
11305
11993
  var buildReplayRows;
11306
- var init_replay = __esm({
11994
+ var init_replay2 = __esm({
11307
11995
  "src/replay.ts"() {
11308
11996
  "use strict";
11309
- buildReplayRows = (msgs, nid) => {
11997
+ init_transcript();
11998
+ buildReplayRows = (lines, nid) => {
11310
11999
  const rows = [];
11311
12000
  const pending = /* @__PURE__ */ new Map();
11312
- for (const m of msgs) {
12001
+ let lastStartRunId;
12002
+ const closedRuns = /* @__PURE__ */ new Set();
12003
+ const attachUsage = (u) => {
12004
+ for (let i = rows.length - 1; i >= 0; i--) {
12005
+ const r = rows[i];
12006
+ if (r.kind === "assistant") {
12007
+ rows[i] = {
12008
+ ...r,
12009
+ usage: {
12010
+ ...r.usage ?? {},
12011
+ prompt_tokens: u.prompt_tokens,
12012
+ completion_tokens: u.completion_tokens,
12013
+ total_tokens: u.total_tokens,
12014
+ prompt_cache_hit_tokens: u.cached_tokens
12015
+ }
12016
+ };
12017
+ return;
12018
+ }
12019
+ }
12020
+ };
12021
+ for (const line of lines) {
12022
+ if (isEventLine(line)) {
12023
+ if (line.dscEvent === "run.start") lastStartRunId = line.runId;
12024
+ else if (line.dscEvent === "run.end" || line.dscEvent === "run.abandoned") closedRuns.add(line.runId);
12025
+ if ((line.dscEvent === "round.end" || line.dscEvent === "run.end") && line.usage && (line.usage.prompt_tokens != null || line.usage.total_tokens != null)) {
12026
+ const last = [...rows].reverse().find((r) => r.kind === "assistant");
12027
+ if (line.dscEvent === "round.end" || !last?.usage) attachUsage(line.usage);
12028
+ }
12029
+ if (line.dscEvent === "run.abandoned") {
12030
+ rows.push({ id: nid(), kind: "info", text: "\u26A0\uFE0F \u6B64\u56DE\u5408\u88AB\u4E2D\u65AD\uFF0C\u672A\u5B8C\u6210" });
12031
+ }
12032
+ continue;
12033
+ }
12034
+ const m = line;
11313
12035
  const role = m?.role;
11314
12036
  if (role === "user") {
11315
12037
  const text = typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.filter((p) => typeof p?.text === "string").map((p) => p.text).join("") : "";
@@ -11359,6 +12081,9 @@ var init_replay = __esm({
11359
12081
  rows[idx] = { ...r, result: r.result ?? "", ok: false, status: "done" };
11360
12082
  }
11361
12083
  }
12084
+ if (lastStartRunId && !closedRuns.has(lastStartRunId)) {
12085
+ rows.push({ id: nid(), kind: "info", text: "\u26A0\uFE0F \u6B64\u56DE\u5408\u88AB\u4E2D\u65AD\uFF0C\u672A\u5B8C\u6210" });
12086
+ }
11362
12087
  return rows;
11363
12088
  };
11364
12089
  }
@@ -11373,13 +12098,15 @@ var init_useChatState = __esm({
11373
12098
  init_chatProcessing();
11374
12099
  init_store();
11375
12100
  init_transcript();
12101
+ init_fork();
12102
+ init_inbox();
11376
12103
  init_contextCore();
11377
12104
  init_createModel();
11378
12105
  init_tool();
11379
12106
  init_cliHost();
11380
12107
  init_strings();
11381
12108
  init_util();
11382
- init_replay();
12109
+ init_replay2();
11383
12110
  FLUSH_MS = 120;
11384
12111
  ABORT_GRACE_MS = 8e3;
11385
12112
  ENTER_PLAN_RESEARCH_PROMPT = "\uFF08\u5DF2\u8FDB\u5165\u8BA1\u5212\u6A21\u5F0F\u3002\u8BF7\u4EE5\u53EA\u8BFB\u65B9\u5F0F\u5B8C\u6210\u8C03\u7814\uFF0C\u7136\u540E\u8C03\u7528 exit_plan_mode \u63D0\u4EA4\u5B8C\u6574\u5B9E\u73B0\u65B9\u6848\u3002\uFF09";
@@ -11397,6 +12124,7 @@ var init_useChatState = __esm({
11397
12124
  const [pendingQuestion, setPendingQuestion] = useState(null);
11398
12125
  const [pendingPlan, setPendingPlan] = useState(null);
11399
12126
  const [pendingSessions, setPendingSessions] = useState(null);
12127
+ const [pendingFork, setPendingFork] = useState(null);
11400
12128
  const nextId = useRef(1);
11401
12129
  const assistantStreamingId = useRef(null);
11402
12130
  const thinkingStreamingId = useRef(null);
@@ -11409,6 +12137,7 @@ var init_useChatState = __esm({
11409
12137
  const currentAcRef = useRef(null);
11410
12138
  const turnGenRef = useRef(0);
11411
12139
  const abortGuardRef = useRef(null);
12140
+ const queuedUnclaimedRef = useRef(0);
11412
12141
  const proposedPlanRef = useRef(null);
11413
12142
  const enterPlanReasonRef = useRef(null);
11414
12143
  const modelRef = useRef("");
@@ -11423,8 +12152,8 @@ var init_useChatState = __esm({
11423
12152
  if (!initialSessionId) return;
11424
12153
  void (async () => {
11425
12154
  try {
11426
- const msgs = await readMessages(initialSessionId);
11427
- const seeded = buildReplayRows(msgs, newRowId);
12155
+ const lines = await readTranscriptLines(initialSessionId);
12156
+ const seeded = buildReplayRows(lines, newRowId);
11428
12157
  if (seeded.length) setRows(seeded);
11429
12158
  } catch {
11430
12159
  }
@@ -11607,10 +12336,18 @@ var init_useChatState = __esm({
11607
12336
  }
11608
12337
  case "approval_request":
11609
12338
  break;
12339
+ case "inbox.claimed": {
12340
+ const n = Array.isArray(obj.texts) ? obj.texts.length : 0;
12341
+ if (n > 0) {
12342
+ queuedUnclaimedRef.current = Math.max(0, queuedUnclaimedRef.current - n);
12343
+ pushInfo(S.inboxClaimed(n));
12344
+ }
12345
+ break;
12346
+ }
11610
12347
  default:
11611
12348
  break;
11612
12349
  }
11613
- }, [closeStreaming, flush, scheduleFlush]);
12350
+ }, [closeStreaming, flush, scheduleFlush, pushInfo]);
11614
12351
  const askApproval = useCallback((detail, toolName) => new Promise((resolve) => setPendingApproval({ detail, toolName, resolve })), []);
11615
12352
  const resolveApproval = useCallback((v) => {
11616
12353
  setPendingApproval((prev) => {
@@ -11690,9 +12427,13 @@ var init_useChatState = __esm({
11690
12427
  clearTimeout(abortGuardRef.current);
11691
12428
  abortGuardRef.current = null;
11692
12429
  }
12430
+ if (queuedUnclaimedRef.current > 0) {
12431
+ pushInfo(S.inboxCarryover(queuedUnclaimedRef.current));
12432
+ queuedUnclaimedRef.current = 0;
12433
+ }
11693
12434
  }
11694
12435
  }
11695
- }, [askApproval, autoRequestApproval, flush, onTrace, pushEvent]);
12436
+ }, [askApproval, autoRequestApproval, flush, onTrace, pushEvent, pushInfo]);
11696
12437
  const approveAndImplement = useCallback(async (sid, plan) => {
11697
12438
  const res = await setPlan(plan);
11698
12439
  if (res.action === "accept") {
@@ -11710,6 +12451,15 @@ ${finalPlan}`, false, res.autoExecute);
11710
12451
  const plan = takeProposedPlan();
11711
12452
  if (plan != null) await approveAndImplement(sid, plan);
11712
12453
  }, [runOnce, takeProposedPlan, approveAndImplement]);
12454
+ const queueInput = useCallback((content) => {
12455
+ const text = content.trim();
12456
+ const sid = sessionIdRef.current;
12457
+ if (!text || !sid || !pushSessionInbox(sid, text)) return false;
12458
+ pushUser(text);
12459
+ pushInfo(S.inboxQueued);
12460
+ queuedUnclaimedRef.current++;
12461
+ return true;
12462
+ }, [pushUser, pushInfo]);
11713
12463
  const submit = useCallback(async (content) => {
11714
12464
  const text = content.trim();
11715
12465
  if (!text || busy) return;
@@ -11770,8 +12520,8 @@ ${finalPlan}`, false, res.autoExecute);
11770
12520
  sessionIdRef.current = id;
11771
12521
  clearRows();
11772
12522
  try {
11773
- const msgs = await readMessages(id);
11774
- const seeded = buildReplayRows(msgs, newRowId);
12523
+ const lines = await readTranscriptLines(id);
12524
+ const seeded = buildReplayRows(lines, newRowId);
11775
12525
  if (seeded.length) setRows(seeded);
11776
12526
  } catch {
11777
12527
  }
@@ -11792,6 +12542,37 @@ ${finalPlan}`, false, res.autoExecute);
11792
12542
  const picked = await new Promise((resolve) => setPendingSessions({ sessions: sessions2, resolve }));
11793
12543
  if (picked) await loadSession(picked);
11794
12544
  }, [pushInfo, loadSession]);
12545
+ const resolveFork = useCallback((anchor) => {
12546
+ setPendingFork((prev) => {
12547
+ prev?.resolve(anchor);
12548
+ return null;
12549
+ });
12550
+ }, []);
12551
+ const openForkPicker = useCallback(async () => {
12552
+ const sid = sessionIdRef.current;
12553
+ if (!sid) {
12554
+ pushInfo(S.forkEmpty);
12555
+ return;
12556
+ }
12557
+ let anchors = [];
12558
+ try {
12559
+ anchors = listForkAnchors(await readTranscriptLines(sid));
12560
+ } catch {
12561
+ }
12562
+ if (anchors.length === 0) {
12563
+ pushInfo(S.forkEmpty);
12564
+ return;
12565
+ }
12566
+ const picked = await new Promise((resolve) => setPendingFork({ anchors, resolve }));
12567
+ if (!picked) return;
12568
+ try {
12569
+ const r = await forkSession(sid, picked.lineId);
12570
+ await loadSession(r.sessionId);
12571
+ pushInfo(S.forkDone(truncateMiddle(r.sessionId, 12), picked.roundNo));
12572
+ } catch (e) {
12573
+ pushInfo(S.forkFailed(e?.message ?? String(e)));
12574
+ }
12575
+ }, [pushInfo, loadSession]);
11795
12576
  const setModelOverride = useCallback((m) => {
11796
12577
  modelRef.current = m;
11797
12578
  }, []);
@@ -11821,9 +12602,11 @@ ${finalPlan}`, false, res.autoExecute);
11821
12602
  pendingQuestion,
11822
12603
  pendingPlan,
11823
12604
  pendingSessions,
12605
+ pendingFork,
11824
12606
  sessionIdRef,
11825
12607
  // 动作
11826
12608
  submit,
12609
+ queueInput,
11827
12610
  abortCurrent,
11828
12611
  pushUser,
11829
12612
  pushInfo,
@@ -11846,7 +12629,9 @@ ${finalPlan}`, false, res.autoExecute);
11846
12629
  getOutputStyle: getOutputStyle2,
11847
12630
  openSessionPicker,
11848
12631
  resolveSession,
11849
- loadSession
12632
+ loadSession,
12633
+ openForkPicker,
12634
+ resolveFork
11850
12635
  };
11851
12636
  };
11852
12637
  }
@@ -12448,9 +13233,45 @@ var init_SessionPicker = __esm({
12448
13233
  }
12449
13234
  });
12450
13235
 
12451
- // src/components/SlashMenu.tsx
13236
+ // src/components/ForkPicker.tsx
12452
13237
  import { Box as Box11, Text as Text13 } from "ink";
12453
13238
  import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
13239
+ var PAGE_SIZE2, ForkPicker;
13240
+ var init_ForkPicker = __esm({
13241
+ "src/components/ForkPicker.tsx"() {
13242
+ "use strict";
13243
+ init_theme();
13244
+ init_strings();
13245
+ init_util();
13246
+ PAGE_SIZE2 = 8;
13247
+ ForkPicker = ({ anchors, selectedIndex, wrapW }) => {
13248
+ const start = Math.floor(selectedIndex / PAGE_SIZE2) * PAGE_SIZE2;
13249
+ const visible = anchors.slice(start, start + PAGE_SIZE2);
13250
+ const contentW = Math.max(24, wrapW);
13251
+ const userW = Math.max(10, Math.floor(contentW * 0.45));
13252
+ const replyW = Math.max(10, contentW - userW - 4);
13253
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", borderStyle: "round", borderColor: THEME.blue, paddingX: 1, paddingY: 1, marginTop: 1, children: [
13254
+ /* @__PURE__ */ jsx13(Text13, { color: THEME.blue, bold: true, children: S.forkTitle }),
13255
+ /* @__PURE__ */ jsx13(Text13, { color: THEME.grayDim, children: S.forkPrompt }),
13256
+ /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", marginTop: 0.5, children: visible.map((a, i) => {
13257
+ const active = start + i === selectedIndex;
13258
+ const user = truncateMiddle(a.userPreview || "(\u65E0\u63D0\u95EE)", userW);
13259
+ const reply = truncateMiddle(a.assistantPreview, replyW);
13260
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "row", children: [
13261
+ /* @__PURE__ */ jsx13(Text13, { color: active ? THEME.coralBright : void 0, bold: true, children: active ? "\u203A " : " " }),
13262
+ /* @__PURE__ */ jsx13(Text13, { color: active ? THEME.white : THEME.gray, bold: active, children: `#${a.roundNo} ${user}` }),
13263
+ /* @__PURE__ */ jsx13(Text13, { color: THEME.grayDim, children: ` \u2192 ${reply}` })
13264
+ ] }, a.lineId);
13265
+ }) }),
13266
+ anchors.length > PAGE_SIZE2 ? /* @__PURE__ */ jsx13(Text13, { color: THEME.grayDim, children: `\u7B2C ${start + 1}-${Math.min(start + PAGE_SIZE2, anchors.length)} / ${anchors.length} \u4E2A\uFF08\u2191\u2193 \u9009\u62E9\xB7\u81EA\u52A8\u7FFB\u9875\uFF09` }) : null
13267
+ ] });
13268
+ };
13269
+ }
13270
+ });
13271
+
13272
+ // src/components/SlashMenu.tsx
13273
+ import { Box as Box12, Text as Text14 } from "ink";
13274
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
12454
13275
  var MAX_ROWS, SlashMenu;
12455
13276
  var init_SlashMenu = __esm({
12456
13277
  "src/components/SlashMenu.tsx"() {
@@ -12462,22 +13283,22 @@ var init_SlashMenu = __esm({
12462
13283
  const more = entries.length > MAX_ROWS;
12463
13284
  const cmdW = Math.min(22, Math.max(8, ...visible.map((e) => e.name.length)) + 1);
12464
13285
  const descMax = Math.max(12, cols - cmdW - 6);
12465
- return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", borderStyle: "round", borderColor: THEME.coral, paddingX: 1, paddingY: 0, marginTop: 1, children: [
13286
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", borderStyle: "round", borderColor: THEME.coral, paddingX: 1, paddingY: 0, marginTop: 1, children: [
12466
13287
  visible.map((e, i) => {
12467
13288
  const active = i === selectedIndex;
12468
13289
  const desc = e.description.length > descMax ? `${e.description.slice(0, descMax - 1)}\u2026` : e.description;
12469
13290
  const gap = Math.max(1, cmdW - e.name.length);
12470
- return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "row", children: [
12471
- /* @__PURE__ */ jsx13(Text13, { color: active ? THEME.coralBright : void 0, bold: true, children: active ? "\u276F " : " " }),
12472
- /* @__PURE__ */ jsxs11(Text13, { color: active ? THEME.white : THEME.gray, bold: active, children: [
13291
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "row", children: [
13292
+ /* @__PURE__ */ jsx14(Text14, { color: active ? THEME.coralBright : void 0, bold: true, children: active ? "\u276F " : " " }),
13293
+ /* @__PURE__ */ jsxs12(Text14, { color: active ? THEME.white : THEME.gray, bold: active, children: [
12473
13294
  "/",
12474
13295
  e.name
12475
13296
  ] }),
12476
- /* @__PURE__ */ jsx13(Text13, { color: THEME.grayDim, children: " ".repeat(gap) }),
12477
- /* @__PURE__ */ jsx13(Text13, { color: active ? THEME.gray : THEME.grayDim, children: desc })
13297
+ /* @__PURE__ */ jsx14(Text14, { color: THEME.grayDim, children: " ".repeat(gap) }),
13298
+ /* @__PURE__ */ jsx14(Text14, { color: active ? THEME.gray : THEME.grayDim, children: desc })
12478
13299
  ] }, e.name);
12479
13300
  }),
12480
- more ? /* @__PURE__ */ jsxs11(Text13, { color: THEME.coralMuted, children: [
13301
+ more ? /* @__PURE__ */ jsxs12(Text14, { color: THEME.coralMuted, children: [
12481
13302
  "\u2026 \u8FD8\u6709 ",
12482
13303
  entries.length - MAX_ROWS,
12483
13304
  " \u6761"
@@ -12488,8 +13309,8 @@ var init_SlashMenu = __esm({
12488
13309
  });
12489
13310
 
12490
13311
  // src/components/StatusStrip.tsx
12491
- import { Box as Box12, Text as Text14 } from "ink";
12492
- import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
13312
+ import { Box as Box13, Text as Text15 } from "ink";
13313
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
12493
13314
  var StatusStrip;
12494
13315
  var init_StatusStrip = __esm({
12495
13316
  "src/components/StatusStrip.tsx"() {
@@ -12506,18 +13327,18 @@ var init_StatusStrip = __esm({
12506
13327
  `state: ${stateText}`,
12507
13328
  sessionShort ? `session: ${sessionShort}` : ""
12508
13329
  ].filter(Boolean);
12509
- return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", flexShrink: 0, children: [
12510
- /* @__PURE__ */ jsx14(Text14, { color: THEME.coralMuted, children: customLine && customLine.trim() ? customLine : bits.join(" \xB7 ") }),
12511
- /* @__PURE__ */ jsx14(Text14, { color: stateColor, children: "" }),
12512
- /* @__PURE__ */ jsx14(Text14, { color: THEME.grayDim, children: S.cmdHint })
13330
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexShrink: 0, children: [
13331
+ /* @__PURE__ */ jsx15(Text15, { color: THEME.coralMuted, children: customLine && customLine.trim() ? customLine : bits.join(" \xB7 ") }),
13332
+ /* @__PURE__ */ jsx15(Text15, { color: stateColor, children: "" }),
13333
+ /* @__PURE__ */ jsx15(Text15, { color: THEME.grayDim, children: S.cmdHint })
12513
13334
  ] });
12514
13335
  };
12515
13336
  }
12516
13337
  });
12517
13338
 
12518
13339
  // src/components/TopPanel.tsx
12519
- import { Box as Box13, Text as Text15 } from "ink";
12520
- import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
13340
+ import { Box as Box14, Text as Text16 } from "ink";
13341
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
12521
13342
  var ROBOT, INNER_H, TopPanel;
12522
13343
  var init_TopPanel = __esm({
12523
13344
  "src/components/TopPanel.tsx"() {
@@ -12537,36 +13358,36 @@ var init_TopPanel = __esm({
12537
13358
  const leftW = Math.floor(innerW * 0.33);
12538
13359
  const rightW = innerW - leftW - 1;
12539
13360
  const ruleLen = Math.max(8, rightW - 2);
12540
- const VDivider = /* @__PURE__ */ jsx15(Box13, { flexDirection: "column", width: 1, flexShrink: 0, children: Array.from({ length: INNER_H }).map((_, i) => /* @__PURE__ */ jsx15(Text15, { color: THEME.coral, children: "\u2502" }, i)) });
12541
- return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", width: panelW, marginBottom: 1, children: [
12542
- /* @__PURE__ */ jsxs13(Text15, { children: [
12543
- /* @__PURE__ */ jsx15(Text15, { color: THEME.coralBright, bold: true, children: "* Welcome to " }),
12544
- /* @__PURE__ */ jsx15(Text15, { bold: true, color: THEME.white, children: "deep" }),
12545
- /* @__PURE__ */ jsx15(Text15, { bold: true, color: THEME.coralBright, children: "Seek" }),
12546
- /* @__PURE__ */ jsx15(Text15, { bold: true, color: THEME.white, children: "Code" }),
12547
- /* @__PURE__ */ jsxs13(Text15, { color: THEME.coralMuted, children: [
13361
+ const VDivider = /* @__PURE__ */ jsx16(Box14, { flexDirection: "column", width: 1, flexShrink: 0, children: Array.from({ length: INNER_H }).map((_, i) => /* @__PURE__ */ jsx16(Text16, { color: THEME.coral, children: "\u2502" }, i)) });
13362
+ return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", width: panelW, marginBottom: 1, children: [
13363
+ /* @__PURE__ */ jsxs14(Text16, { children: [
13364
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.coralBright, bold: true, children: "* Welcome to " }),
13365
+ /* @__PURE__ */ jsx16(Text16, { bold: true, color: THEME.white, children: "deep" }),
13366
+ /* @__PURE__ */ jsx16(Text16, { bold: true, color: THEME.coralBright, children: "Seek" }),
13367
+ /* @__PURE__ */ jsx16(Text16, { bold: true, color: THEME.white, children: "Code" }),
13368
+ /* @__PURE__ */ jsxs14(Text16, { color: THEME.coralMuted, children: [
12548
13369
  " ",
12549
13370
  VERSION
12550
13371
  ] })
12551
13372
  ] }),
12552
- /* @__PURE__ */ jsx15(Text15, { color: THEME.grayDim, children: cwd }),
12553
- /* @__PURE__ */ jsxs13(Box13, { flexDirection: "row", borderStyle: "round", borderColor: THEME.coral, marginTop: 1, paddingX: 0, paddingY: 0, children: [
12554
- /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", width: leftW, height: INNER_H, alignItems: "center", justifyContent: "center", children: [
12555
- /* @__PURE__ */ jsx15(Text15, { color: THEME.white, bold: true, children: "Welcome back!" }),
12556
- ROBOT.map((l, i) => /* @__PURE__ */ jsx15(Text15, { color: THEME.coralBright, children: l }, i)),
12557
- /* @__PURE__ */ jsx15(Text15, { color: THEME.grayDim, children: "Ctrl+C \u9000\u51FA \xB7 Esc \u4E2D\u6B62" }),
12558
- /* @__PURE__ */ jsx15(Text15, { color: THEME.grayDim, children: "\u8F93\u5165 / \u67E5\u770B\u547D\u4EE4" })
13373
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.grayDim, children: cwd }),
13374
+ /* @__PURE__ */ jsxs14(Box14, { flexDirection: "row", borderStyle: "round", borderColor: THEME.coral, marginTop: 1, paddingX: 0, paddingY: 0, children: [
13375
+ /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", width: leftW, height: INNER_H, alignItems: "center", justifyContent: "center", children: [
13376
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.white, bold: true, children: "Welcome back!" }),
13377
+ ROBOT.map((l, i) => /* @__PURE__ */ jsx16(Text16, { color: THEME.coralBright, children: l }, i)),
13378
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.grayDim, children: "Ctrl+C \u9000\u51FA \xB7 Esc \u4E2D\u6B62" }),
13379
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.grayDim, children: "\u8F93\u5165 / \u67E5\u770B\u547D\u4EE4" })
12559
13380
  ] }),
12560
13381
  VDivider,
12561
- /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", width: rightW, height: INNER_H, justifyContent: "space-between", paddingX: 1, children: [
12562
- /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
12563
- /* @__PURE__ */ jsx15(Text15, { color: THEME.coralBright, bold: true, children: "Tips for getting started" }),
12564
- /* @__PURE__ */ jsx15(Text15, { color: THEME.white, children: "\u8F93\u5165\u9700\u6C42\u5F00\u59CB\uFF0C\u8F93\u5165 / \u5524\u51FA\u547D\u4EE4\u3002" })
13382
+ /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", width: rightW, height: INNER_H, justifyContent: "space-between", paddingX: 1, children: [
13383
+ /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
13384
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.coralBright, bold: true, children: "Tips for getting started" }),
13385
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.white, children: "\u8F93\u5165\u9700\u6C42\u5F00\u59CB\uFF0C\u8F93\u5165 / \u5524\u51FA\u547D\u4EE4\u3002" })
12565
13386
  ] }),
12566
- /* @__PURE__ */ jsx15(Text15, { color: THEME.grayDim, children: "\u2500".repeat(ruleLen) }),
12567
- /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
12568
- /* @__PURE__ */ jsx15(Text15, { color: THEME.coralBright, bold: true, children: "What's new" }),
12569
- /* @__PURE__ */ jsx15(Text15, { color: THEME.white, children: "\u5DF2\u5BF9\u63A5\u672C\u5730 Tool Call \u667A\u80FD\u51FD\u6570\u8C03\u7528\u3002" })
13387
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.grayDim, children: "\u2500".repeat(ruleLen) }),
13388
+ /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
13389
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.coralBright, bold: true, children: "What's new" }),
13390
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.white, children: "\u5DF2\u5BF9\u63A5\u672C\u5730 Tool Call \u667A\u80FD\u51FD\u6570\u8C03\u7528\u3002" })
12570
13391
  ] })
12571
13392
  ] })
12572
13393
  ] })
@@ -12576,7 +13397,7 @@ var init_TopPanel = __esm({
12576
13397
  });
12577
13398
 
12578
13399
  // src/inspect.ts
12579
- import fs37 from "fs";
13400
+ import fs38 from "fs";
12580
13401
  var CWD, fmt, readTrace, inspectUsage, inspectContext, inspectPermissions, inspectMcp, inspectHooks, inspectDebug;
12581
13402
  var init_inspect = __esm({
12582
13403
  "src/inspect.ts"() {
@@ -12594,7 +13415,7 @@ var init_inspect = __esm({
12594
13415
  if (!sessionId) return [];
12595
13416
  try {
12596
13417
  const p = await getTraceStorePath(sessionId);
12597
- const text = await fs37.promises.readFile(p, "utf8");
13418
+ const text = await fs38.promises.readFile(p, "utf8");
12598
13419
  const out = [];
12599
13420
  for (const line of text.split("\n")) {
12600
13421
  if (!line.trim()) continue;
@@ -12717,8 +13538,8 @@ __export(App_exports, {
12717
13538
  App: () => App
12718
13539
  });
12719
13540
  import { useCallback as useCallback2, useEffect as useEffect4, useMemo, useRef as useRef2, useState as useState4 } from "react";
12720
- import { Box as Box14, Static, Text as Text16, useApp, useInput as useInput2, useStdout } from "ink";
12721
- import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
13541
+ import { Box as Box15, Static, Text as Text17, useApp, useInput as useInput2, useStdout } from "ink";
13542
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
12722
13543
  var KNOWN_MODELS, CWD2, RowView, isDynamicRow, App;
12723
13544
  var init_App = __esm({
12724
13545
  "src/App.tsx"() {
@@ -12743,6 +13564,7 @@ var init_App = __esm({
12743
13564
  init_PlanModal();
12744
13565
  init_PlanEditor();
12745
13566
  init_SessionPicker();
13567
+ init_ForkPicker();
12746
13568
  init_SlashMenu();
12747
13569
  init_MultilineInput();
12748
13570
  init_StatusStrip();
@@ -12751,16 +13573,16 @@ var init_App = __esm({
12751
13573
  KNOWN_MODELS = ["deepseek-v4", "deepseek-v4-flash"];
12752
13574
  CWD2 = process.cwd();
12753
13575
  RowView = ({ row, wrapW, streamTail, showThinking }) => {
12754
- if (row.kind === "todos") return /* @__PURE__ */ jsx16(TodosPanel, { todos: row.todos, wrapW });
12755
- if (row.kind === "tool") return /* @__PURE__ */ jsx16(ToolCard, { toolName: row.toolName, args: row.args, result: row.result, ok: row.ok, status: row.status, progress: row.progress, wrapW });
12756
- if (row.kind === "thinking") return /* @__PURE__ */ jsx16(ThinkingBlock, { streaming: row.streaming ?? false, startedAt: row.startedAt, durationMs: row.durationMs, tokens: row.tokens, text: row.text, expanded: showThinking, wrapW });
13576
+ if (row.kind === "todos") return /* @__PURE__ */ jsx17(TodosPanel, { todos: row.todos, wrapW });
13577
+ if (row.kind === "tool") return /* @__PURE__ */ jsx17(ToolCard, { toolName: row.toolName, args: row.args, result: row.result, ok: row.ok, status: row.status, progress: row.progress, wrapW });
13578
+ if (row.kind === "thinking") return /* @__PURE__ */ jsx17(ThinkingBlock, { streaming: row.streaming ?? false, startedAt: row.startedAt, durationMs: row.durationMs, tokens: row.tokens, text: row.text, expanded: showThinking, wrapW });
12757
13579
  if (row.kind === "user") {
12758
- return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", marginTop: 1, children: [
12759
- /* @__PURE__ */ jsx16(Text16, { color: THEME.grayDim, children: "\u2500".repeat(Math.max(8, wrapW)) }),
12760
- /* @__PURE__ */ jsx16(MessageBlock, { row, wrapW, streamTail })
13580
+ return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", marginTop: 1, children: [
13581
+ /* @__PURE__ */ jsx17(Text17, { color: THEME.grayDim, children: "\u2500".repeat(Math.max(8, wrapW)) }),
13582
+ /* @__PURE__ */ jsx17(MessageBlock, { row, wrapW, streamTail })
12761
13583
  ] });
12762
13584
  }
12763
- return /* @__PURE__ */ jsx16(MessageBlock, { row, wrapW, streamTail });
13585
+ return /* @__PURE__ */ jsx17(MessageBlock, { row, wrapW, streamTail });
12764
13586
  };
12765
13587
  isDynamicRow = (r) => r.kind === "assistant" && !!r.streaming || r.kind === "thinking" && !!r.streaming || r.kind === "tool" && r.status === "running" || r.kind === "todos" && !!r.active;
12766
13588
  App = ({ resumeSessionId, initialPlanMode, initialAutoMode, initialIncludeProject }) => {
@@ -12860,6 +13682,8 @@ var init_App = __esm({
12860
13682
  return S.cmdOutputStyle;
12861
13683
  case "sessions":
12862
13684
  return S.cmdSessions;
13685
+ case "fork":
13686
+ return S.cmdFork;
12863
13687
  case "usage":
12864
13688
  return S.cmdUsage;
12865
13689
  case "context":
@@ -12892,13 +13716,13 @@ var init_App = __esm({
12892
13716
  const q = input.slice(1).toLowerCase();
12893
13717
  return menuEntries.filter((c) => c.name.toLowerCase().startsWith(q));
12894
13718
  }, [input, menuEntries]);
12895
- const menuActive = state.pendingApproval != null || state.pendingQuestion != null || state.pendingPlan != null || state.pendingSessions != null;
13719
+ const menuActive = state.pendingApproval != null || state.pendingQuestion != null || state.pendingPlan != null || state.pendingSessions != null || state.pendingFork != null;
12896
13720
  const slashVisible = !menuActive && input.startsWith("/") && filteredCommands.length > 0;
12897
13721
  const inputActive = !menuActive;
12898
13722
  useEffect4(() => {
12899
13723
  setSelectIdx(0);
12900
13724
  setPlanEditing(false);
12901
- }, [state.pendingApproval, state.pendingQuestion, state.pendingPlan, state.pendingSessions, slashVisible, filteredCommands.length]);
13725
+ }, [state.pendingApproval, state.pendingQuestion, state.pendingPlan, state.pendingSessions, state.pendingFork, slashVisible, filteredCommands.length]);
12902
13726
  useEffect4(() => {
12903
13727
  setQCursor(0);
12904
13728
  setQChecked(/* @__PURE__ */ new Set());
@@ -12917,6 +13741,9 @@ var init_App = __esm({
12917
13741
  case "/sessions":
12918
13742
  void state.openSessionPicker();
12919
13743
  return true;
13744
+ case "/fork":
13745
+ void state.openForkPicker();
13746
+ return true;
12920
13747
  case "/help":
12921
13748
  state.pushInfo(S.helpText(modelDisplay, state.getThinkingLevel(), getLocale()));
12922
13749
  return true;
@@ -13065,7 +13892,12 @@ ${listText}`);
13065
13892
  const v = inputRef.current.trim();
13066
13893
  if (!v) return;
13067
13894
  if (busyRef.current) {
13068
- state.pushEvent({ type: "error", message: S.busyBlockSend });
13895
+ if (!v.startsWith("/") && state.queueInput(v)) {
13896
+ setInput("");
13897
+ setCursor(0);
13898
+ } else {
13899
+ state.pushEvent({ type: "error", message: S.busyBlockSend });
13900
+ }
13069
13901
  return;
13070
13902
  }
13071
13903
  setInput("");
@@ -13147,6 +13979,16 @@ ${listText}`);
13147
13979
  } else if (key.escape || key.ctrl && ch === "g") state.resolveSession(null);
13148
13980
  return;
13149
13981
  }
13982
+ if (state.pendingFork) {
13983
+ const list = state.pendingFork.anchors;
13984
+ if (key.upArrow) setSelectIdx((i) => (i - 1 + list.length) % list.length);
13985
+ else if (key.downArrow) setSelectIdx((i) => (i + 1) % list.length);
13986
+ else if (key.return) {
13987
+ const sel = list[Math.min(selectIdxRef.current, list.length - 1)];
13988
+ state.resolveFork(sel ?? null);
13989
+ } else if (key.escape || key.ctrl && ch === "g") state.resolveFork(null);
13990
+ return;
13991
+ }
13150
13992
  if (key.ctrl && ch === "t") {
13151
13993
  state.toggleShowThinking();
13152
13994
  return;
@@ -13187,19 +14029,19 @@ ${listText}`);
13187
14029
  }
13188
14030
  });
13189
14031
  const sessionShort = state.sessionIdRef.current ? truncateMiddle(state.sessionIdRef.current, 12) : "";
13190
- return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", width: cols, children: [
13191
- /* @__PURE__ */ jsx16(Static, { items: staticItems, children: (item) => item.kind === "__header" ? /* @__PURE__ */ jsx16(TopPanel, { cwd: CWD2, cols }, "header") : /* @__PURE__ */ jsx16(RowView, { row: item, wrapW, streamTail, showThinking: state.showThinkingText }, item.id) }),
13192
- /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", width: cols, children: [
13193
- menuActive ? null : /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", paddingX: 1, children: [
13194
- dynamicRows.map((row) => /* @__PURE__ */ jsx16(RowView, { row, wrapW, streamTail, showThinking: state.showThinkingText }, row.id)),
13195
- state.busy ? /* @__PURE__ */ jsx16(Box14, { marginTop: 0.5, children: /* @__PURE__ */ jsxs14(Text16, { color: THEME.coralBright, children: [
14032
+ return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", width: cols, children: [
14033
+ /* @__PURE__ */ jsx17(Static, { items: staticItems, children: (item) => item.kind === "__header" ? /* @__PURE__ */ jsx17(TopPanel, { cwd: CWD2, cols }, "header") : /* @__PURE__ */ jsx17(RowView, { row: item, wrapW, streamTail, showThinking: state.showThinkingText }, item.id) }),
14034
+ /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", width: cols, children: [
14035
+ menuActive ? null : /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", paddingX: 1, children: [
14036
+ dynamicRows.map((row) => /* @__PURE__ */ jsx17(RowView, { row, wrapW, streamTail, showThinking: state.showThinkingText }, row.id)),
14037
+ state.busy ? /* @__PURE__ */ jsx17(Box15, { marginTop: 0.5, children: /* @__PURE__ */ jsxs15(Text17, { color: THEME.coralBright, children: [
13196
14038
  "\u25CF ",
13197
14039
  S.generating
13198
14040
  ] }) }) : null
13199
14041
  ] }),
13200
- /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", paddingX: 1, flexShrink: 0, children: [
13201
- state.pendingApproval ? /* @__PURE__ */ jsx16(ApprovalModal, { toolName: state.pendingApproval.toolName, detail: state.pendingApproval.detail, selectedIndex: selectIdx, wrapW }) : null,
13202
- state.pendingQuestion ? /* @__PURE__ */ jsx16(
14042
+ /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", paddingX: 1, flexShrink: 0, children: [
14043
+ state.pendingApproval ? /* @__PURE__ */ jsx17(ApprovalModal, { toolName: state.pendingApproval.toolName, detail: state.pendingApproval.detail, selectedIndex: selectIdx, wrapW }) : null,
14044
+ state.pendingQuestion ? /* @__PURE__ */ jsx17(
13203
14045
  QuestionModal,
13204
14046
  {
13205
14047
  question: state.pendingQuestion.req.question,
@@ -13210,7 +14052,7 @@ ${listText}`);
13210
14052
  wrapW
13211
14053
  }
13212
14054
  ) : null,
13213
- state.pendingPlan ? planEditing ? /* @__PURE__ */ jsx16(
14055
+ state.pendingPlan ? planEditing ? /* @__PURE__ */ jsx17(
13214
14056
  PlanEditor,
13215
14057
  {
13216
14058
  value: planDraft,
@@ -13221,13 +14063,14 @@ ${listText}`);
13221
14063
  },
13222
14064
  onSubmit: confirmPlanEdit
13223
14065
  }
13224
- ) : /* @__PURE__ */ jsx16(PlanModal, { selectedIndex: selectIdx }) : null,
13225
- state.pendingSessions ? /* @__PURE__ */ jsx16(SessionPicker, { sessions: state.pendingSessions.sessions, selectedIndex: selectIdx, wrapW }) : null,
13226
- slashVisible ? /* @__PURE__ */ jsx16(SlashMenu, { entries: filteredCommands, selectedIndex: selectIdx, cols }) : null
14066
+ ) : /* @__PURE__ */ jsx17(PlanModal, { selectedIndex: selectIdx }) : null,
14067
+ state.pendingSessions ? /* @__PURE__ */ jsx17(SessionPicker, { sessions: state.pendingSessions.sessions, selectedIndex: selectIdx, wrapW }) : null,
14068
+ state.pendingFork ? /* @__PURE__ */ jsx17(ForkPicker, { anchors: state.pendingFork.anchors, selectedIndex: selectIdx, wrapW }) : null,
14069
+ slashVisible ? /* @__PURE__ */ jsx17(SlashMenu, { entries: filteredCommands, selectedIndex: selectIdx, cols }) : null
13227
14070
  ] }),
13228
- /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", paddingX: 1, marginTop: 1, flexShrink: 0, children: [
13229
- /* @__PURE__ */ jsx16(Text16, { dimColor: true, color: THEME.gray, children: dimRule(cols) }),
13230
- /* @__PURE__ */ jsx16(
14071
+ /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", paddingX: 1, marginTop: 1, flexShrink: 0, children: [
14072
+ /* @__PURE__ */ jsx17(Text17, { dimColor: true, color: THEME.gray, children: dimRule(cols) }),
14073
+ /* @__PURE__ */ jsx17(
13231
14074
  MultilineInput,
13232
14075
  {
13233
14076
  value: input,
@@ -13245,13 +14088,88 @@ ${listText}`);
13245
14088
  }
13246
14089
  )
13247
14090
  ] }),
13248
- /* @__PURE__ */ jsx16(Box14, { paddingX: 1, marginTop: 1, flexShrink: 0, children: /* @__PURE__ */ jsx16(StatusStrip, { model: modelDisplay, busy: state.busy, aborting: state.aborting, planMode: state.getPlanMode(), autoMode: state.getAutoMode(), sessionShort, cols, customLine: statusLineText }) })
14091
+ /* @__PURE__ */ jsx17(Box15, { paddingX: 1, marginTop: 1, flexShrink: 0, children: /* @__PURE__ */ jsx17(StatusStrip, { model: modelDisplay, busy: state.busy, aborting: state.aborting, planMode: state.getPlanMode(), autoMode: state.getAutoMode(), sessionShort, cols, customLine: statusLineText }) })
13249
14092
  ] })
13250
14093
  ] });
13251
14094
  };
13252
14095
  }
13253
14096
  });
13254
14097
 
14098
+ // src/updateCheck.ts
14099
+ var updateCheck_exports = {};
14100
+ __export(updateCheck_exports, {
14101
+ getCachedLatestIfNewer: () => getCachedLatestIfNewer,
14102
+ readLocalVersion: () => readLocalVersion,
14103
+ refreshUpdateCache: () => refreshUpdateCache
14104
+ });
14105
+ import { readFileSync as readFileSync4, writeFileSync, mkdirSync } from "node:fs";
14106
+ import path39 from "node:path";
14107
+ import os4 from "os";
14108
+ import { fileURLToPath as fileURLToPath6 } from "node:url";
14109
+ var PKG_NAME, REGISTRY, CACHE_TTL_MS, FETCH_TIMEOUT_MS2, cacheFile, readLocalVersion, parseSemver, compareVersion, readCache, writeCache, getCachedLatestIfNewer, refreshUpdateCache;
14110
+ var init_updateCheck = __esm({
14111
+ "src/updateCheck.ts"() {
14112
+ "use strict";
14113
+ PKG_NAME = "deepseeker-code";
14114
+ REGISTRY = "https://registry.npmjs.org";
14115
+ CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
14116
+ FETCH_TIMEOUT_MS2 = 3e3;
14117
+ cacheFile = () => path39.join(os4.homedir(), ".deepseeker-code", "update-check.json");
14118
+ readLocalVersion = () => {
14119
+ try {
14120
+ const here = path39.dirname(fileURLToPath6(import.meta.url));
14121
+ return JSON.parse(readFileSync4(path39.join(here, "..", "package.json"), "utf8")).version ?? "0.0.0";
14122
+ } catch {
14123
+ return "0.0.0";
14124
+ }
14125
+ };
14126
+ parseSemver = (v) => {
14127
+ const m = String(v ?? "").match(/^(\d+)\.(\d+)\.(\d+)/);
14128
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : [0, 0, 0];
14129
+ };
14130
+ compareVersion = (a, b) => {
14131
+ const [a1, a2, a3] = parseSemver(a);
14132
+ const [b1, b2, b3] = parseSemver(b);
14133
+ return a1 - b1 || a2 - b2 || a3 - b3;
14134
+ };
14135
+ readCache = () => {
14136
+ try {
14137
+ const obj = JSON.parse(readFileSync4(cacheFile(), "utf8"));
14138
+ return typeof obj.latestVersion === "string" && typeof obj.lastCheck === "number" ? obj : null;
14139
+ } catch {
14140
+ return null;
14141
+ }
14142
+ };
14143
+ writeCache = (latestVersion) => {
14144
+ try {
14145
+ mkdirSync(path39.dirname(cacheFile()), { recursive: true });
14146
+ writeFileSync(cacheFile(), JSON.stringify({ lastCheck: Date.now(), latestVersion }), "utf8");
14147
+ } catch {
14148
+ }
14149
+ };
14150
+ getCachedLatestIfNewer = (currentVersion) => {
14151
+ const cache = readCache();
14152
+ if (!cache) return null;
14153
+ return compareVersion(cache.latestVersion, currentVersion) > 0 ? cache.latestVersion : null;
14154
+ };
14155
+ refreshUpdateCache = () => {
14156
+ const cache = readCache();
14157
+ if (cache && Date.now() - cache.lastCheck < CACHE_TTL_MS) return;
14158
+ void (async () => {
14159
+ try {
14160
+ const res = await fetch(`${REGISTRY}/${PKG_NAME}/latest`, {
14161
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS2)
14162
+ });
14163
+ if (!res.ok) return;
14164
+ const info = await res.json();
14165
+ if (info.version) writeCache(info.version);
14166
+ } catch {
14167
+ }
14168
+ })();
14169
+ };
14170
+ }
14171
+ });
14172
+
13255
14173
  // src/preload-config.ts
13256
14174
  import { readFileSync } from "node:fs";
13257
14175
  import path from "node:path";
@@ -13301,10 +14219,10 @@ init_strings();
13301
14219
  init_prefs();
13302
14220
  init_trust();
13303
14221
  import { render } from "ink";
13304
- import os4 from "os";
13305
- import path39 from "path";
14222
+ import os5 from "os";
14223
+ import path40 from "path";
13306
14224
  import { emitKeypressEvents } from "node:readline";
13307
- import { jsx as jsx17 } from "react/jsx-runtime";
14225
+ import { jsx as jsx18 } from "react/jsx-runtime";
13308
14226
  var parseArgs = (argv) => {
13309
14227
  const out = {};
13310
14228
  for (let i = 0; i < argv.length; i++) {
@@ -13314,6 +14232,7 @@ var parseArgs = (argv) => {
13314
14232
  else if (a === "--auto" || a === "-a") out.auto = true;
13315
14233
  else if (a === "--continue" || a === "-c") out.continue = true;
13316
14234
  else if (a === "--trust") out.trust = true;
14235
+ else if (a === "--no-update-check") out.noUpdateCheck = true;
13317
14236
  }
13318
14237
  return out;
13319
14238
  };
@@ -13428,19 +14347,26 @@ var main = async () => {
13428
14347
  process.stdout.write("\x1B[2J\x1B[3J\x1B[1;1H");
13429
14348
  const cwdIsHome = () => {
13430
14349
  try {
13431
- return path39.resolve(cwd).toLowerCase() === path39.resolve(os4.homedir()).toLowerCase();
14350
+ return path40.resolve(cwd).toLowerCase() === path40.resolve(os5.homedir()).toLowerCase();
13432
14351
  } catch {
13433
14352
  return false;
13434
14353
  }
13435
14354
  };
13436
14355
  process.stdout.write(S.startupBanner(cwd, appConfig2.userWorkspaceDir, cwdIsHome()));
14356
+ if (!args.noUpdateCheck) {
14357
+ const { readLocalVersion: readLocalVersion2, getCachedLatestIfNewer: getCachedLatestIfNewer2, refreshUpdateCache: refreshUpdateCache2 } = await Promise.resolve().then(() => (init_updateCheck(), updateCheck_exports));
14358
+ const current = readLocalVersion2();
14359
+ const latest = getCachedLatestIfNewer2(current);
14360
+ if (latest) process.stdout.write(S.updateAvailable(latest, current));
14361
+ refreshUpdateCache2();
14362
+ }
13437
14363
  let resumeId = args.resume;
13438
14364
  if (!resumeId && args.continue) {
13439
14365
  const { getMostRecentSessionId: getMostRecentSessionId2 } = await Promise.resolve().then(() => (init_store(), store_exports));
13440
14366
  resumeId = await getMostRecentSessionId2() ?? void 0;
13441
14367
  }
13442
14368
  const { waitUntilExit } = render(
13443
- /* @__PURE__ */ jsx17(App2, { resumeSessionId: resumeId, initialPlanMode: args.plan, initialAutoMode: args.auto, initialIncludeProject: includeProject }),
14369
+ /* @__PURE__ */ jsx18(App2, { resumeSessionId: resumeId, initialPlanMode: args.plan, initialAutoMode: args.auto, initialIncludeProject: includeProject }),
13444
14370
  // exitOnCtrlC:false:Ctrl+C 交由 App useInput 处理(统一退出/中止语义);
13445
14371
  // patchConsole:false:避免 console 劫持与全屏重绘叠加闪屏。
13446
14372
  { exitOnCtrlC: false, patchConsole: false }