deepseeker-code 1.0.3 → 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 +1005 -205
  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",
@@ -98,6 +101,12 @@ ${dim} \u8FD0\u884C npm i -g deepseeker-code@latest \u66F4\u65B0${reset}
98
101
  sessionsPrompt: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u8F7D\u5165 \xB7 Esc \u53D6\u6D88",
99
102
  noHistory: "\uFF08\u6682\u65E0\u5386\u53F2\u4F1A\u8BDD\uFF09",
100
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}`,
101
110
  relTime: (iso) => {
102
111
  const t = Date.parse(iso);
103
112
  if (!t) return "";
@@ -133,6 +142,7 @@ DeepSeeker-Code \u5C06\u5728\u6B64\u76EE\u5F55\u8BFB\u53D6\u3001\u7F16\u8F91\u54
133
142
  `/thinking [off|high|max] \u2014 \u5207\u6362\u601D\u8003\u7B49\u7EA7\uFF08\u5F53\u524D ${thinking}\uFF09`,
134
143
  `/lang [zh|en] \u2014 \u5207\u6362\u754C\u9762\u8BED\u8A00\uFF08\u5F53\u524D ${locale2}\uFF09`,
135
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",
136
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",
137
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"
138
148
  ].join("\n"),
@@ -180,6 +190,9 @@ DeepSeeker-Code \u5C06\u5728\u6B64\u76EE\u5F55\u8BFB\u53D6\u3001\u7F16\u8F91\u54
180
190
  approvalDeny: "Deny",
181
191
  errAborted: "Aborted current turn.",
182
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.`,
183
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",
184
197
  statusStreaming: "streaming",
185
198
  statusIdle: "ready",
@@ -226,6 +239,12 @@ ${dim} Run npm i -g deepseeker-code@latest to update${reset}
226
239
  sessionsPrompt: "\u2191\u2193 pick \xB7 Enter resume \xB7 Esc cancel",
227
240
  noHistory: "(no past sessions)",
228
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}`,
229
248
  relTime: (iso) => {
230
249
  const t = Date.parse(iso);
231
250
  if (!t) return "";
@@ -261,6 +280,7 @@ DeepSeeker-Code will read, edit, and execute files here.`,
261
280
  `/thinking [off|high|max] \u2014 Switch thinking level (current ${thinking})`,
262
281
  `/lang [zh|en] \u2014 Switch interface language (current ${locale2})`,
263
282
  "/sessions \u2014 Pick a past session to resume",
283
+ "/fork \u2014 Fork the current session from a past round",
264
284
  "/trust \u2014 Manage trusted dirs (project hooks/skills load only when trusted; CI uses --trust)",
265
285
  "Ctrl+C exit \xB7 Esc abort/clear input \xB7 Ctrl+G abort \xB7 Ctrl+T toggle thinking"
266
286
  ].join("\n"),
@@ -281,7 +301,7 @@ DeepSeeker-Code will read, edit, and execute files here.`,
281
301
  S = new Proxy({}, {
282
302
  get: (_t, k) => typeof k === "string" ? STRINGS[locale][k] : void 0
283
303
  });
284
- 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"];
285
305
  }
286
306
  });
287
307
 
@@ -289,13 +309,15 @@ DeepSeeker-Code will read, edit, and execute files here.`,
289
309
  var config_exports = {};
290
310
  __export(config_exports, {
291
311
  ENGINE_OVERRIDE_KEYS: () => ENGINE_OVERRIDE_KEYS,
292
- appConfig: () => appConfig
312
+ ENV_SOURCES: () => ENV_SOURCES,
313
+ appConfig: () => appConfig,
314
+ configSources: () => configSources
293
315
  });
294
316
  import path2 from "path";
295
317
  import os2 from "os";
296
318
  import { readFileSync as readFileSync2 } from "fs";
297
319
  import { createHash } from "crypto";
298
- var _DataDir, appConfig, ENGINE_OVERRIDE_VALIDATORS, ENGINE_OVERRIDE_KEYS, applyEngineOverrides;
320
+ var _DataDir, appConfig, ENV_SOURCES, configSources, ENGINE_OVERRIDE_VALIDATORS, ENGINE_OVERRIDE_KEYS, applyEngineOverrides;
299
321
  var init_config = __esm({
300
322
  "../core/src/config/index.ts"() {
301
323
  "use strict";
@@ -370,8 +392,51 @@ var init_config = __esm({
370
392
  workflowMaxSteps: Number(process.env.DEEP_SEEK_WORKFLOW_MAX_STEPS) || 8,
371
393
  /** P0-3 run_workflow 单个子 agent 结果的字符预算:超出按头尾截断,避免单个巨型结果挤占聚合输出。
372
394
  * 最终聚合再受工具 maxOutputCharacters 兜底。 */
373
- workflowPerStepChars: 6e3
374
- };
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
+ }
375
440
  ENGINE_OVERRIDE_VALIDATORS = {
376
441
  undoEnabled: (v) => typeof v === "boolean" ? v : void 0,
377
442
  undoBackupSensitive: (v) => v === "skip" || v === "deny" || v === "allow" ? v : void 0,
@@ -382,12 +447,12 @@ var init_config = __esm({
382
447
  ENGINE_OVERRIDE_KEYS = Object.keys(ENGINE_OVERRIDE_VALIDATORS);
383
448
  applyEngineOverrides = (cfg) => {
384
449
  const paths = [
385
- path2.join(cfg.dataDir, "settings.json"),
450
+ { scope: "settings.global", configPath: path2.join(cfg.dataDir, "settings.json") },
386
451
  // 全局用户级
387
- path2.join(process.cwd(), ".deepseeker-code", "settings.json")
452
+ { scope: "settings.project", configPath: path2.join(process.cwd(), ".deepseeker-code", "settings.json") }
388
453
  // 项目级(覆盖全局)
389
454
  ];
390
- for (const configPath of paths) {
455
+ for (const { scope, configPath } of paths) {
391
456
  let raw;
392
457
  try {
393
458
  raw = readFileSync2(configPath, "utf-8");
@@ -411,6 +476,7 @@ var init_config = __esm({
411
476
  continue;
412
477
  }
413
478
  cfg[key] = valid;
479
+ configSources[key] = scope;
414
480
  }
415
481
  }
416
482
  };
@@ -491,13 +557,20 @@ var init_common = __esm({
491
557
  injectMarkedBlock = (message, fence, body) => {
492
558
  const sys = message[0];
493
559
  if (!sys || sys.role !== "system" || typeof sys.content !== "string") return;
494
- if (sys.content.includes(fence)) {
495
- sys.content = sys.content.split(fence)[0].trimEnd();
496
- }
497
- sys.content += `
560
+ const idx = sys.content.indexOf(fence);
561
+ if (idx === -1) {
562
+ sys.content += `
498
563
 
499
564
  ${fence}
500
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`);
501
574
  };
502
575
  decodeProcessOutput = (buf) => {
503
576
  const utf8 = buf.toString("utf8");
@@ -811,17 +884,120 @@ var init_deepseek = __esm({
811
884
  }
812
885
  });
813
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
+
814
976
  // ../core/src/llm/model.ts
815
- var activeProvider, chatWithModelWithSummary, classifyToolRisk, isContextLengthError2, isTransientApiError;
977
+ import { readFileSync as readFileSync3 } from "fs";
978
+ var resolveInitialProvider, activeProvider, chatWithModelWithSummary, classifyToolRisk;
816
979
  var init_model = __esm({
817
980
  "../core/src/llm/model.ts"() {
818
981
  "use strict";
819
982
  init_deepseek();
820
- activeProvider = deepseekProvider;
821
- chatWithModelWithSummary = deepseekProvider.summarize;
822
- classifyToolRisk = deepseekProvider.classifyRisk;
823
- isContextLengthError2 = deepseekProvider.isContextLengthError;
824
- 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);
825
1001
  }
826
1002
  });
827
1003
 
@@ -1139,6 +1315,7 @@ var init_store = __esm({
1139
1315
  } catch {
1140
1316
  continue;
1141
1317
  }
1318
+ if (typeof msg?.dscEvent === "string") continue;
1142
1319
  messageCount++;
1143
1320
  if (!preview && msg?.role === "user" && typeof msg.content === "string") {
1144
1321
  const t = msg.content.replace(/\s+/g, " ").trim();
@@ -1193,6 +1370,87 @@ var init_store = __esm({
1193
1370
  }
1194
1371
  });
1195
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
+
1196
1454
  // ../core/src/hooks/types.ts
1197
1455
  var INTERCEPTABLE_EVENTS, TOOL_EVENTS, ALL_EVENTS, DEFAULT_TIMEOUT_BY_EVENT;
1198
1456
  var init_types = __esm({
@@ -1340,12 +1598,13 @@ var init_shellExecutor = __esm({
1340
1598
  });
1341
1599
 
1342
1600
  // ../core/src/hooks/registry.ts
1343
- 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;
1344
1602
  var init_registry = __esm({
1345
1603
  "../core/src/hooks/registry.ts"() {
1346
1604
  "use strict";
1347
1605
  init_types();
1348
1606
  init_shellExecutor();
1607
+ init_config();
1349
1608
  rules = [];
1350
1609
  matches = (matcher, toolName) => {
1351
1610
  if (typeof matcher === "string") return matcher === "*" || matcher === toolName;
@@ -1368,6 +1627,7 @@ var init_registry = __esm({
1368
1627
  };
1369
1628
  return rules.map((r) => ({ event: r.event, matcher: strMatcher(r.matcher), source: r.source, onError: r.onError }));
1370
1629
  };
1630
+ isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
1371
1631
  dispatch = async (event, ctx) => {
1372
1632
  const interceptable = INTERCEPTABLE_EVENTS.has(event);
1373
1633
  const isToolEvent = TOOL_EVENTS.has(event);
@@ -1395,14 +1655,29 @@ var init_registry = __esm({
1395
1655
  )
1396
1656
  ));
1397
1657
  const additions2 = [];
1398
- for (const r of results) if (r?.contextAdditions) additions2.push(...r.contextAdditions);
1399
- 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 };
1400
1666
  }
1401
1667
  const additions = [];
1668
+ let argsOverride = void 0;
1402
1669
  for (const rule of matched) {
1403
1670
  try {
1404
1671
  const res = await rule.run(ctx);
1405
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
+ }
1406
1681
  if (res && res.deny) {
1407
1682
  return { deny: true, reason: res.reason, contextAdditions: additions.length > 0 ? additions : void 0 };
1408
1683
  }
@@ -1415,7 +1690,7 @@ var init_registry = __esm({
1415
1690
  console.warn(`\u26A0\uFE0F [hook:${event}] \u6267\u884C\u5F02\u5E38\uFF0C\u6309\u653E\u884C\u5904\u7406: ${msg}`);
1416
1691
  }
1417
1692
  }
1418
- return pack(additions);
1693
+ return { ...pack(additions), argsOverride };
1419
1694
  };
1420
1695
  runPreHooks = async (toolName, args, ctx) => {
1421
1696
  return dispatch("PreToolUse", {
@@ -1431,7 +1706,7 @@ var init_registry = __esm({
1431
1706
  truncateForHook = (s) => s.length <= HOOK_RESULT_MAX ? s : s.slice(0, HOOK_RESULT_MAX) + `
1432
1707
  \u2026[hook \u89C6\u56FE\u622A\u65AD\uFF0C\u5171 ${s.length} \u5B57\u7B26]`;
1433
1708
  runPostHooks = async (toolName, args, result, ctx) => {
1434
- await dispatch("PostToolUse", {
1709
+ return dispatch("PostToolUse", {
1435
1710
  sessionId: ctx.sessionId,
1436
1711
  cwd: ctx.cwd,
1437
1712
  env: ctx.env,
@@ -1475,6 +1750,7 @@ var init_truncate = __esm({
1475
1750
  init_model();
1476
1751
  init_contextCore();
1477
1752
  init_store();
1753
+ init_transcript();
1478
1754
  init_hooks();
1479
1755
  ANSI_ESCAPE = /\u001b\[[\d;?]*[ -/]*[@-~]|\u001b\][^\u0007]*(?:\u0007|\u001b\\)/g;
1480
1756
  getFileAccessRoots = () => {
@@ -1630,6 +1906,11 @@ ${line}` : line;
1630
1906
  consecutiveFailures: 0,
1631
1907
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1632
1908
  });
1909
+ await appendEvent(event.sessionId, {
1910
+ dscEvent: "compaction",
1911
+ archivedMessageCount: store.archivedMessageCount,
1912
+ summary: summaryMsg.content
1913
+ });
1633
1914
  } else if (keep > 1) {
1634
1915
  keep--;
1635
1916
  continue;
@@ -1644,6 +1925,11 @@ ${line}` : line;
1644
1925
  consecutiveFailures: 0,
1645
1926
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1646
1927
  });
1928
+ await appendEvent(event.sessionId, {
1929
+ dscEvent: "compaction",
1930
+ archivedMessageCount: store.archivedMessageCount,
1931
+ summary: summaryMsg.content
1932
+ });
1647
1933
  } else {
1648
1934
  break;
1649
1935
  }
@@ -1750,7 +2036,7 @@ ${line}` : line;
1750
2036
  });
1751
2037
 
1752
2038
  // ../core/src/tool/permissions.ts
1753
- import fs5 from "fs/promises";
2039
+ import fs6 from "fs/promises";
1754
2040
  import path8 from "path";
1755
2041
  var PRIMARY_ARG, rules2, trustedProject, globCache, globToRegex, DANGER_COMMAND_TOOLS, compileRule, readPermissionConfig, toolNameMatches, ruleMatches, initPermissions, checkPermission, listPermissionRules, buildScopedAllowRule, resolveSettingsPath, addPermissionRule;
1756
2042
  var init_permissions = __esm({
@@ -1821,7 +2107,7 @@ var init_permissions = __esm({
1821
2107
  for (const configPath of paths) {
1822
2108
  let raw;
1823
2109
  try {
1824
- raw = await fs5.readFile(configPath, "utf-8");
2110
+ raw = await fs6.readFile(configPath, "utf-8");
1825
2111
  } catch {
1826
2112
  continue;
1827
2113
  }
@@ -1918,7 +2204,7 @@ var init_permissions = __esm({
1918
2204
  const arr = Array.isArray(cfg.permissions[kind]) ? cfg.permissions[kind] : cfg.permissions[kind] = [];
1919
2205
  if (!arr.includes(ruleStr)) {
1920
2206
  arr.push(ruleStr);
1921
- await fs5.mkdir(path8.dirname(file), { recursive: true });
2207
+ await fs6.mkdir(path8.dirname(file), { recursive: true });
1922
2208
  await atomicWriteJSON(file, cfg);
1923
2209
  }
1924
2210
  if (!rules2[kind].some((r) => r.raw === compiled.raw)) rules2[kind].push(compiled);
@@ -1956,7 +2242,7 @@ var init_sessionRegistry = __esm({
1956
2242
  });
1957
2243
 
1958
2244
  // ../core/src/tool/guard.ts
1959
- import fs6 from "fs/promises";
2245
+ import fs7 from "fs/promises";
1960
2246
  import fsSync from "fs";
1961
2247
  import path9 from "path";
1962
2248
  import ignore from "ignore";
@@ -2134,11 +2420,11 @@ var init_guard = __esm({
2134
2420
  };
2135
2421
  scanIgnoreFilesRecursive = async (dirPath, base, engine) => {
2136
2422
  try {
2137
- const entries = await fs6.readdir(dirPath, { withFileTypes: true });
2423
+ const entries = await fs7.readdir(dirPath, { withFileTypes: true });
2138
2424
  for (const entry of entries) {
2139
2425
  if (entry.isFile() && (entry.name === ".gitignore" || entry.name === ".agentignore")) {
2140
2426
  const fullPath = path9.join(dirPath, entry.name);
2141
- const content = await fs6.readFile(fullPath, "utf-8");
2427
+ const content = await fs7.readFile(fullPath, "utf-8");
2142
2428
  const relDir = path9.relative(base, dirPath).replace(/\\/g, "/");
2143
2429
  const rules3 = content.split(/\r?\n/).map((line) => {
2144
2430
  const trimmed = line.trim();
@@ -3035,7 +3321,7 @@ ${capped || JSON.stringify(result)}`;
3035
3321
  });
3036
3322
 
3037
3323
  // ../core/src/tool/mcp/loader.ts
3038
- import fs7 from "fs/promises";
3324
+ import fs8 from "fs/promises";
3039
3325
  import path11 from "path";
3040
3326
  import { homedir } from "os";
3041
3327
  function sanitize(s) {
@@ -3045,7 +3331,7 @@ async function readMcpConfig() {
3045
3331
  const configPath = process.env.MCP_CONFIG || path11.join(appConfig.dataDir, "mcp.json");
3046
3332
  let raw;
3047
3333
  try {
3048
- raw = await fs7.readFile(configPath, "utf-8");
3334
+ raw = await fs8.readFile(configPath, "utf-8");
3049
3335
  } catch {
3050
3336
  return {};
3051
3337
  }
@@ -3136,9 +3422,14 @@ async function loadMcpTools() {
3136
3422
  }
3137
3423
  async function initMcpTools(into) {
3138
3424
  const mcpTools = await loadMcpTools();
3139
- 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) {
3140
3431
  into.push(...mcpTools);
3141
- 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`);
3142
3433
  }
3143
3434
  if (clients.some((c) => c.supportsResources)) {
3144
3435
  into.push(...mcpResourceTools);
@@ -3174,7 +3465,7 @@ async function listMcpClients() {
3174
3465
  }
3175
3466
  return out;
3176
3467
  }
3177
- var clients, expandPlaceholders, expandServerConfig, mcpResourceTools, mcpPromptTools;
3468
+ var clients, expandPlaceholders, expandServerConfig, mcpResourceTools, mcpPromptTools, resolveMcpPermissionName, mcpDispatcherTools;
3178
3469
  var init_loader = __esm({
3179
3470
  "../core/src/tool/mcp/loader.ts"() {
3180
3471
  "use strict";
@@ -3338,6 +3629,88 @@ ${text}`;
3338
3629
  }
3339
3630
  }
3340
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
+ ];
3341
3714
  }
3342
3715
  });
3343
3716
 
@@ -3373,68 +3746,44 @@ var init_httpExecutor = __esm({
3373
3746
  }
3374
3747
  });
3375
3748
 
3376
- // ../core/src/session/transcript.ts
3377
- import fs8 from "fs/promises";
3378
- var readMessages, appendMessage;
3379
- var init_transcript = __esm({
3380
- "../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"() {
3381
3753
  "use strict";
3382
- init_store();
3383
- init_common();
3384
- readMessages = async (sessionId) => {
3385
- await ensureSessionsDir(sessionId);
3386
- const p = getTranscriptPath(sessionId);
3387
- try {
3388
- const text = await fs8.readFile(p, "utf-8");
3389
- const lines = [];
3390
- for (const s of text.split("\n")) {
3391
- const line = s.trim();
3392
- if (line.length === 0) continue;
3393
- try {
3394
- lines.push(JSON.parse(line));
3395
- } catch {
3396
- console.warn(`\u26A0\uFE0F [transcript] \u8DF3\u8FC7\u65E0\u6CD5\u89E3\u6790\u7684\u635F\u574F\u884C: ${line.slice(0, 120)}`);
3397
- }
3398
- }
3399
- return lines;
3400
- } catch (err) {
3401
- if (err?.code === "ENOENT") return [];
3402
- 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;
3403
3766
  }
3767
+ q.push(text);
3768
+ sessionInboxes.set(sessionId, q);
3769
+ return true;
3404
3770
  };
3405
- appendMessage = async (entry) => {
3406
- try {
3407
- await ensureSessionsDir(entry.sessionId);
3408
- const { sessionId, ...rest } = entry;
3409
- const line = { id: createUUID(), ...rest };
3410
- const p = getTranscriptPath(sessionId);
3411
- const payload = JSON.stringify(line) + "\n";
3412
- if (payload.includes("\uFFFD")) {
3413
- const fffdCount = (payload.match(/\uFFFD/g) || []).length;
3414
- 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`);
3415
- }
3416
- const MAX_ATTEMPTS = 3;
3417
- for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
3418
- let beforeSize = 0;
3419
- try {
3420
- beforeSize = (await fs8.stat(p)).size;
3421
- } catch {
3422
- }
3423
- try {
3424
- await fs8.appendFile(p, payload, "utf-8");
3425
- return;
3426
- } catch (e) {
3427
- try {
3428
- await fs8.truncate(p, beforeSize);
3429
- } catch {
3430
- }
3431
- if (attempt === MAX_ATTEMPTS) throw e;
3432
- await new Promise((r) => setTimeout(r, 50 * attempt));
3433
- }
3434
- }
3435
- } catch (e) {
3436
- 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 });
3437
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`);
3785
+ }
3786
+ return texts.length;
3438
3787
  };
3439
3788
  }
3440
3789
  });
@@ -3561,6 +3910,9 @@ var init_planMode = __esm({
3561
3910
  "git_status",
3562
3911
  "git_log",
3563
3912
  "inspect_dependencies",
3913
+ "get_diagnostics",
3914
+ "goto_definition",
3915
+ // ★ P0-A 补漏:LSP 导航/诊断恰是调研阶段最需要的(原白名单漂移漏登记,schema 档与 runtime 档共用此表)
3564
3916
  "web_fetch",
3565
3917
  "web_search"
3566
3918
  // 只读研究类(虽为 DANGER 但不写本地状态;仍走各自审批)
@@ -3738,7 +4090,7 @@ var init_inject2 = __esm({
3738
4090
  init_registry4();
3739
4091
  AGENT_CATALOG_MARKER = "\u3010\u53EF\u7528\u5B50 Agent \u76EE\u5F55\u3011";
3740
4092
  AGENT_CATALOG_FENCE = "\u27E6DSC:AGENT_CATALOG\u27E7";
3741
- 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';
3742
4094
  injectAgentCatalog = (message) => {
3743
4095
  const catalog = getAgentCatalog();
3744
4096
  if (!catalog) return;
@@ -3894,6 +4246,7 @@ var prepareToolsAndInjections;
3894
4246
  var init_systemInjections = __esm({
3895
4247
  "../core/src/agent/systemInjections.ts"() {
3896
4248
  "use strict";
4249
+ init_config();
3897
4250
  init_planMode();
3898
4251
  init_toolFilter();
3899
4252
  init_truncate();
@@ -3905,7 +4258,7 @@ var init_systemInjections = __esm({
3905
4258
  init_common();
3906
4259
  prepareToolsAndInjections = async (message, options, events) => {
3907
4260
  const { toolSchemas, planMode, locale: locale2, outputStyle } = options;
3908
- const rawToolsPreEnv = planMode ? filterToolsForPlanMode(toolSchemas ?? []) : appendPlanControlTools(toolSchemas ?? []);
4261
+ const rawToolsPreEnv = planMode && appConfig.planEnforcement === "schema" ? filterToolsForPlanMode(toolSchemas ?? []) : appendPlanControlTools(toolSchemas ?? []);
3909
4262
  const validationCtx = {
3910
4263
  sessionId: options.sessionId,
3911
4264
  cwd: options.cwd ?? process.cwd(),
@@ -8811,6 +9164,9 @@ var init_toolExecution = __esm({
8811
9164
  init_backgroundTool();
8812
9165
  init_backup();
8813
9166
  init_autoPermission();
9167
+ init_loader();
9168
+ init_config();
9169
+ init_planMode();
8814
9170
  applyPrivacyMasking = (rules3, args, output) => {
8815
9171
  if (!rules3) return output;
8816
9172
  try {
@@ -8836,6 +9192,7 @@ var init_toolExecution = __esm({
8836
9192
  rawTools,
8837
9193
  events,
8838
9194
  permissionMode,
9195
+ planMode,
8839
9196
  onUIEvent,
8840
9197
  requestApproval: hostRequestApproval,
8841
9198
  requestQuestion: hostRequestQuestion,
@@ -8862,6 +9219,10 @@ var init_toolExecution = __esm({
8862
9219
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: note, resultForUser: note, ok: true, terminal: { kind: "exit_plan_mode", plan } };
8863
9220
  }
8864
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
+ }
8865
9226
  const reason = typeof calledArgs?.reason === "string" ? calledArgs.reason : "";
8866
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";
8867
9228
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: note, resultForUser: note, ok: true, terminal: { kind: "enter_plan_mode", reason } };
@@ -8870,10 +9231,16 @@ var init_toolExecution = __esm({
8870
9231
  const placeholder = "\uFF08\u5DF2\u4E2D\u6B62\uFF0C\u672A\u6267\u884C\uFF09";
8871
9232
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: placeholder, resultForUser: placeholder, ok: false, aborted: true };
8872
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
+ }
8873
9239
  const matchedTool = rawTools.find((t) => t.function.name === calledName);
8874
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 };
8875
9241
  let result = "";
8876
9242
  let explicitOk = null;
9243
+ let postHookOverride;
8877
9244
  if (parseFailed) {
8878
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)}`;
8879
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 } });
@@ -8881,6 +9248,23 @@ var init_toolExecution = __esm({
8881
9248
  const level = matchedTool.function.safetyLevel;
8882
9249
  let needApproval = level === "mutation" /* MUTATION */ || level === "danger" /* DANGER */;
8883
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
+ }
8884
9268
  const protectedPaths = calledName === "move_file" ? [calledArgs?.source, calledArgs?.destination] : isUndoTrigger(calledName) ? [calledArgs?.path] : [];
8885
9269
  const hitProtected = protectedPaths.find((p) => typeof p === "string" && isProtectedWrite(p, toolCtx.cwd));
8886
9270
  if (hitProtected) {
@@ -8889,7 +9273,7 @@ var init_toolExecution = __esm({
8889
9273
  }
8890
9274
  let perm = null;
8891
9275
  try {
8892
- perm = checkPermission(calledName, calledArgs);
9276
+ perm = checkPermission(resolveMcpPermissionName(calledName, calledArgs), calledArgs);
8893
9277
  if (perm === "deny") {
8894
9278
  denied = true;
8895
9279
  result = `\u274C [\u6743\u9650\u89C4\u5219]\uFF1A[${calledName}] \u88AB\u6743\u9650\u89C4\u5219 deny \u62D2\u7EDD\u3002`;
@@ -8915,7 +9299,7 @@ var init_toolExecution = __esm({
8915
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`;
8916
9300
  }
8917
9301
  if (needApproval && !denied) {
8918
- const auto = await runAutoCheck(calledName, calledArgs, toolCtx, permissionMode === "auto");
9302
+ const auto = await runAutoCheck(resolveMcpPermissionName(calledName, calledArgs), calledArgs, toolCtx, permissionMode === "auto");
8919
9303
  if (auto === "allow") {
8920
9304
  needApproval = false;
8921
9305
  } else if (auto === "deny") {
@@ -8946,7 +9330,7 @@ var init_toolExecution = __esm({
8946
9330
  }
8947
9331
  }
8948
9332
  }
8949
- if (!denied) {
9333
+ if (!denied && !preHooksAdvanced) {
8950
9334
  let veto;
8951
9335
  try {
8952
9336
  veto = await runPreHooks(calledName, calledArgs, toolCtx);
@@ -8990,9 +9374,12 @@ ${result}`;
8990
9374
  result = `\u5DE5\u5177\u6267\u884C\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`;
8991
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 } });
8992
9376
  }
8993
- 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) {
8994
9381
  console.warn(`\u26A0\uFE0F post-hook [${calledName}] \u5F02\u5E38\uFF08\u5DF2\u5FFD\u7565\uFF09:`, e?.message ?? e);
8995
- });
9382
+ }
8996
9383
  }
8997
9384
  } else {
8998
9385
  result = `\u9519\u8BEF\uFF1A\u672A\u77E5\u5DE5\u5177 "${calledName}" \u6216\u8BE5\u5DE5\u5177\u65E0\u53EF\u6267\u884C\u51FD\u6570`;
@@ -9013,6 +9400,10 @@ ${result}`;
9013
9400
  } catch {
9014
9401
  }
9015
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
+ }
9016
9407
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel, resultForUser, ok };
9017
9408
  };
9018
9409
  }
@@ -9536,6 +9927,18 @@ async function* runAgent(message, options) {
9536
9927
  let lastRealPromptTokens = persistedCalib.lastRealPromptTokens;
9537
9928
  let lastCachedTokens = persistedCalib.lastCachedTokens;
9538
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 });
9539
9942
  try {
9540
9943
  while (true) {
9541
9944
  round++;
@@ -9563,6 +9966,14 @@ async function* runAgent(message, options) {
9563
9966
  yield { type: "final", text: lastContent || "\uFF08\u5DF2\u4E2D\u6B62\uFF09" };
9564
9967
  return;
9565
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
+ }
9566
9977
  yield { type: "round.start", round };
9567
9978
  try {
9568
9979
  await ensureFitsWindow(
@@ -9629,6 +10040,7 @@ async function* runAgent(message, options) {
9629
10040
  lastRealPromptTokens = infResult.usage.prompt_tokens;
9630
10041
  lastCachedTokens = infResult.usage.cached_tokens;
9631
10042
  }
10043
+ addUsage(infResult.usage);
9632
10044
  const assistantMessage = infResult.assistantMessage;
9633
10045
  message.push(assistantMessage);
9634
10046
  await appendMessage({ ...assistantMessage, sessionId });
@@ -9637,6 +10049,7 @@ async function* runAgent(message, options) {
9637
10049
  }
9638
10050
  const tcCount = assistantMessage.tool_calls?.length ?? 0;
9639
10051
  if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
10052
+ await appendEvent(sessionId, { dscEvent: "round.end", runId, round, usage: infResult.usage });
9640
10053
  const finalText = (typeof assistantMessage.content === "string" ? assistantMessage.content : "") || "";
9641
10054
  const intercepted = nudges.interceptFinal(finalText, round);
9642
10055
  if (intercepted) continue;
@@ -9647,6 +10060,11 @@ async function* runAgent(message, options) {
9647
10060
  const repeatVerdict = breaker.check(assistantMessage.tool_calls, round, lastContent);
9648
10061
  if (repeatVerdict.tripped) {
9649
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
+ }
9650
10068
  yield { type: "final", text: repeatVerdict.text };
9651
10069
  return;
9652
10070
  }
@@ -9661,6 +10079,8 @@ async function* runAgent(message, options) {
9661
10079
  rawTools,
9662
10080
  events,
9663
10081
  permissionMode: options.permissionMode,
10082
+ planMode: !!options.planMode,
10083
+ // ★ P0-A runtime 档:计划期写工具在 processToolCall 执行层拒绝(工具表恒定不裁剪)
9664
10084
  onUIEvent: options.onUIEvent,
9665
10085
  requestApproval: options.requestApproval,
9666
10086
  requestQuestion: options.requestQuestion,
@@ -9678,6 +10098,7 @@ async function* runAgent(message, options) {
9678
10098
  yield { type: "final", text: scheduleResult.terminalText };
9679
10099
  return;
9680
10100
  }
10101
+ await appendEvent(sessionId, { dscEvent: "round.end", runId, round, usage: infResult.usage });
9681
10102
  }
9682
10103
  } catch (toolErr) {
9683
10104
  if (signal?.aborted) {
@@ -9691,6 +10112,14 @@ async function* runAgent(message, options) {
9691
10112
  \uFF08\u5DE5\u5177\u6267\u884C\u5F02\u5E38\uFF1A${errMsg}\uFF09` };
9692
10113
  return;
9693
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
+ }
9694
10123
  try {
9695
10124
  const reason = signal?.aborted ? "aborted" : stopReason;
9696
10125
  await dispatch("Stop", { sessionId, cwd, lastText: lastContent || "", reason });
@@ -9706,6 +10135,8 @@ var init_runAgent = __esm({
9706
10135
  "../core/src/agent/runAgent.ts"() {
9707
10136
  "use strict";
9708
10137
  init_transcript();
10138
+ init_inbox();
10139
+ init_common();
9709
10140
  init_contextCore();
9710
10141
  init_store();
9711
10142
  init_truncate();
@@ -9718,17 +10149,30 @@ var init_runAgent = __esm({
9718
10149
  }
9719
10150
  });
9720
10151
 
9721
- // ../core/src/session/content.ts
9722
- var repairOrphanToolCalls, decayOldToolResults, buildContextMessages;
9723
- var init_content = __esm({
9724
- "../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"() {
9725
10156
  "use strict";
9726
- init_contextCore();
9727
10157
  init_transcript();
9728
10158
  init_store();
9729
- init_config();
9730
- 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";
9731
10174
  const out = [];
10175
+ const repairedToolCallIds = [];
9732
10176
  for (let i = 0; i < msgs.length; i++) {
9733
10177
  const m = msgs[i];
9734
10178
  out.push(m);
@@ -9741,13 +10185,60 @@ var init_content = __esm({
9741
10185
  }
9742
10186
  for (const tc of m.tool_calls) {
9743
10187
  if (tc.id && !answered.has(tc.id)) {
9744
- 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);
9745
10190
  }
9746
10191
  }
9747
10192
  }
9748
10193
  }
9749
- return out;
10194
+ return { msgs: out, repairedToolCallIds, confirmedCrash };
9750
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 };
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();
9751
10242
  decayOldToolResults = (msgs) => {
9752
10243
  const units = groupUnits(msgs);
9753
10244
  const cutoffUnitIdx = Math.max(0, units.length - appConfig.KEEP_RECENT_UNITS);
@@ -9773,21 +10264,28 @@ var init_content = __esm({
9773
10264
  });
9774
10265
  };
9775
10266
  buildContextMessages = async (sessionId, currentUserMsg, systemPrompt) => {
9776
- const all = (await readMessages(sessionId)).map(cleanMsg);
10267
+ const lines = await readTranscriptLines(sessionId);
9777
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);
9778
10275
  const result = [
9779
10276
  { role: "system", content: systemPrompt },
9780
10277
  // 存储系统提示词
9781
- { role: "system", content: store.rollingSummary }
10278
+ { role: "system", content: recovery.compaction.rollingSummary }
9782
10279
  // 后续存储摘要使用
9783
10280
  ];
9784
- const archivedMessageCount = store.archivedMessageCount;
10281
+ const archivedMessageCount = recovery.compaction.archivedMessageCount;
9785
10282
  const messageAll = all.slice(archivedMessageCount);
9786
10283
  if (messageAll.length === 0) {
9787
10284
  result.push(currentUserMsg);
9788
10285
  return result;
9789
10286
  }
9790
- result.push(...decayOldToolResults(repairOrphanToolCalls(messageAll)));
10287
+ const repair = repairOrphanToolCalls(messageAll, recovery.interruption.kind === "crashed");
10288
+ result.push(...decayOldToolResults(repair.msgs));
9791
10289
  result.push(currentUserMsg);
9792
10290
  return result;
9793
10291
  };
@@ -9851,7 +10349,8 @@ var init_subagent = __esm({
9851
10349
  parentSystemPrompt
9852
10350
  ].join("\n");
9853
10351
  const allTools = getGlobalTools();
9854
- 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));
9855
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)}..."`);
9856
10355
  try {
9857
10356
  const subMessages = await buildContextMessages(subSessionId, { role: "user", content: task }, subSystem);
@@ -9917,7 +10416,7 @@ var init_subagent = __esm({
9917
10416
  // ../core/src/hooks/loader.ts
9918
10417
  import fs27 from "fs/promises";
9919
10418
  import path29 from "path";
9920
- var readHooksConfig, appendValidated, validateRule, parseAgentDecision, compileRule2, loadHooks, initHooks;
10419
+ var readHooksConfig, appendValidated, validateRule, parseStdoutDecision, parseAgentDecision, compileRule2, loadHooks, initHooks;
9921
10420
  var init_loader4 = __esm({
9922
10421
  "../core/src/hooks/loader.ts"() {
9923
10422
  "use strict";
@@ -10061,6 +10560,24 @@ var init_loader4 = __esm({
10061
10560
  requireApproval: raw.requireApproval === true ? true : void 0
10062
10561
  };
10063
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
+ };
10064
10581
  parseAgentDecision = (output) => {
10065
10582
  const lines = (output ?? "").split(/\r?\n/);
10066
10583
  for (let i = lines.length - 1; i >= 0; i--) {
@@ -10129,9 +10646,15 @@ var init_loader4 = __esm({
10129
10646
  }
10130
10647
  try {
10131
10648
  const parsed = JSON.parse(res.responseBody);
10132
- if (parsed && typeof parsed === "object" && parsed.deny === true) {
10133
- const reason = typeof parsed.reason === "string" ? parsed.reason : `hook http \u62D2\u7EDD\uFF08status ${res.status}\uFF09`;
10134
- 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;
10135
10658
  }
10136
10659
  } catch {
10137
10660
  }
@@ -10160,6 +10683,10 @@ var init_loader4 = __esm({
10160
10683
  const detail = res.stderr ? `\uFF1A${res.stderr.slice(0, 200)}` : "";
10161
10684
  return { deny: true, reason: `hook \u9000\u51FA\u7801 ${res.exitCode}${detail}` };
10162
10685
  }
10686
+ if (res.exitCode === 0) {
10687
+ const out = parseStdoutDecision(res.stdout);
10688
+ if (out) return out;
10689
+ }
10163
10690
  return { deny: false };
10164
10691
  }
10165
10692
  };
@@ -10394,9 +10921,9 @@ var init_loader6 = __esm({
10394
10921
  );
10395
10922
  for (const { item: m, file, source } of picked) {
10396
10923
  if (m.tools.length > 0) {
10397
- const unknown = m.tools.filter((t) => !known.has(t));
10924
+ const unknown = m.tools.filter((t) => !known.has(t) && !t.startsWith("mcp__"));
10398
10925
  if (unknown.length) console.warn(`\u26A0\uFE0F [agents] ${m.name} \u58F0\u660E\u4E86\u672A\u77E5\u5DE5\u5177 [${unknown.join(", ")}]\uFF0C\u5DF2\u5254\u9664`);
10399
- m.tools = m.tools.filter((t) => known.has(t));
10926
+ m.tools = m.tools.filter((t) => known.has(t) || t.startsWith("mcp__"));
10400
10927
  }
10401
10928
  if (source === "project") {
10402
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`);
@@ -11262,6 +11789,124 @@ ${lines.join("\n")}
11262
11789
  }
11263
11790
  });
11264
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
+
11265
11910
  // src/cliHost.ts
11266
11911
  var createCliRequestApproval, createCliRequestQuestion;
11267
11912
  var init_cliHost = __esm({
@@ -11346,13 +11991,47 @@ var init_util = __esm({
11346
11991
 
11347
11992
  // src/replay.ts
11348
11993
  var buildReplayRows;
11349
- var init_replay = __esm({
11994
+ var init_replay2 = __esm({
11350
11995
  "src/replay.ts"() {
11351
11996
  "use strict";
11352
- buildReplayRows = (msgs, nid) => {
11997
+ init_transcript();
11998
+ buildReplayRows = (lines, nid) => {
11353
11999
  const rows = [];
11354
12000
  const pending = /* @__PURE__ */ new Map();
11355
- 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;
11356
12035
  const role = m?.role;
11357
12036
  if (role === "user") {
11358
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("") : "";
@@ -11402,6 +12081,9 @@ var init_replay = __esm({
11402
12081
  rows[idx] = { ...r, result: r.result ?? "", ok: false, status: "done" };
11403
12082
  }
11404
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
+ }
11405
12087
  return rows;
11406
12088
  };
11407
12089
  }
@@ -11416,13 +12098,15 @@ var init_useChatState = __esm({
11416
12098
  init_chatProcessing();
11417
12099
  init_store();
11418
12100
  init_transcript();
12101
+ init_fork();
12102
+ init_inbox();
11419
12103
  init_contextCore();
11420
12104
  init_createModel();
11421
12105
  init_tool();
11422
12106
  init_cliHost();
11423
12107
  init_strings();
11424
12108
  init_util();
11425
- init_replay();
12109
+ init_replay2();
11426
12110
  FLUSH_MS = 120;
11427
12111
  ABORT_GRACE_MS = 8e3;
11428
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";
@@ -11440,6 +12124,7 @@ var init_useChatState = __esm({
11440
12124
  const [pendingQuestion, setPendingQuestion] = useState(null);
11441
12125
  const [pendingPlan, setPendingPlan] = useState(null);
11442
12126
  const [pendingSessions, setPendingSessions] = useState(null);
12127
+ const [pendingFork, setPendingFork] = useState(null);
11443
12128
  const nextId = useRef(1);
11444
12129
  const assistantStreamingId = useRef(null);
11445
12130
  const thinkingStreamingId = useRef(null);
@@ -11452,6 +12137,7 @@ var init_useChatState = __esm({
11452
12137
  const currentAcRef = useRef(null);
11453
12138
  const turnGenRef = useRef(0);
11454
12139
  const abortGuardRef = useRef(null);
12140
+ const queuedUnclaimedRef = useRef(0);
11455
12141
  const proposedPlanRef = useRef(null);
11456
12142
  const enterPlanReasonRef = useRef(null);
11457
12143
  const modelRef = useRef("");
@@ -11466,8 +12152,8 @@ var init_useChatState = __esm({
11466
12152
  if (!initialSessionId) return;
11467
12153
  void (async () => {
11468
12154
  try {
11469
- const msgs = await readMessages(initialSessionId);
11470
- const seeded = buildReplayRows(msgs, newRowId);
12155
+ const lines = await readTranscriptLines(initialSessionId);
12156
+ const seeded = buildReplayRows(lines, newRowId);
11471
12157
  if (seeded.length) setRows(seeded);
11472
12158
  } catch {
11473
12159
  }
@@ -11650,10 +12336,18 @@ var init_useChatState = __esm({
11650
12336
  }
11651
12337
  case "approval_request":
11652
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
+ }
11653
12347
  default:
11654
12348
  break;
11655
12349
  }
11656
- }, [closeStreaming, flush, scheduleFlush]);
12350
+ }, [closeStreaming, flush, scheduleFlush, pushInfo]);
11657
12351
  const askApproval = useCallback((detail, toolName) => new Promise((resolve) => setPendingApproval({ detail, toolName, resolve })), []);
11658
12352
  const resolveApproval = useCallback((v) => {
11659
12353
  setPendingApproval((prev) => {
@@ -11733,9 +12427,13 @@ var init_useChatState = __esm({
11733
12427
  clearTimeout(abortGuardRef.current);
11734
12428
  abortGuardRef.current = null;
11735
12429
  }
12430
+ if (queuedUnclaimedRef.current > 0) {
12431
+ pushInfo(S.inboxCarryover(queuedUnclaimedRef.current));
12432
+ queuedUnclaimedRef.current = 0;
12433
+ }
11736
12434
  }
11737
12435
  }
11738
- }, [askApproval, autoRequestApproval, flush, onTrace, pushEvent]);
12436
+ }, [askApproval, autoRequestApproval, flush, onTrace, pushEvent, pushInfo]);
11739
12437
  const approveAndImplement = useCallback(async (sid, plan) => {
11740
12438
  const res = await setPlan(plan);
11741
12439
  if (res.action === "accept") {
@@ -11753,6 +12451,15 @@ ${finalPlan}`, false, res.autoExecute);
11753
12451
  const plan = takeProposedPlan();
11754
12452
  if (plan != null) await approveAndImplement(sid, plan);
11755
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]);
11756
12463
  const submit = useCallback(async (content) => {
11757
12464
  const text = content.trim();
11758
12465
  if (!text || busy) return;
@@ -11813,8 +12520,8 @@ ${finalPlan}`, false, res.autoExecute);
11813
12520
  sessionIdRef.current = id;
11814
12521
  clearRows();
11815
12522
  try {
11816
- const msgs = await readMessages(id);
11817
- const seeded = buildReplayRows(msgs, newRowId);
12523
+ const lines = await readTranscriptLines(id);
12524
+ const seeded = buildReplayRows(lines, newRowId);
11818
12525
  if (seeded.length) setRows(seeded);
11819
12526
  } catch {
11820
12527
  }
@@ -11835,6 +12542,37 @@ ${finalPlan}`, false, res.autoExecute);
11835
12542
  const picked = await new Promise((resolve) => setPendingSessions({ sessions: sessions2, resolve }));
11836
12543
  if (picked) await loadSession(picked);
11837
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]);
11838
12576
  const setModelOverride = useCallback((m) => {
11839
12577
  modelRef.current = m;
11840
12578
  }, []);
@@ -11864,9 +12602,11 @@ ${finalPlan}`, false, res.autoExecute);
11864
12602
  pendingQuestion,
11865
12603
  pendingPlan,
11866
12604
  pendingSessions,
12605
+ pendingFork,
11867
12606
  sessionIdRef,
11868
12607
  // 动作
11869
12608
  submit,
12609
+ queueInput,
11870
12610
  abortCurrent,
11871
12611
  pushUser,
11872
12612
  pushInfo,
@@ -11889,7 +12629,9 @@ ${finalPlan}`, false, res.autoExecute);
11889
12629
  getOutputStyle: getOutputStyle2,
11890
12630
  openSessionPicker,
11891
12631
  resolveSession,
11892
- loadSession
12632
+ loadSession,
12633
+ openForkPicker,
12634
+ resolveFork
11893
12635
  };
11894
12636
  };
11895
12637
  }
@@ -12491,9 +13233,45 @@ var init_SessionPicker = __esm({
12491
13233
  }
12492
13234
  });
12493
13235
 
12494
- // src/components/SlashMenu.tsx
13236
+ // src/components/ForkPicker.tsx
12495
13237
  import { Box as Box11, Text as Text13 } from "ink";
12496
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";
12497
13275
  var MAX_ROWS, SlashMenu;
12498
13276
  var init_SlashMenu = __esm({
12499
13277
  "src/components/SlashMenu.tsx"() {
@@ -12505,22 +13283,22 @@ var init_SlashMenu = __esm({
12505
13283
  const more = entries.length > MAX_ROWS;
12506
13284
  const cmdW = Math.min(22, Math.max(8, ...visible.map((e) => e.name.length)) + 1);
12507
13285
  const descMax = Math.max(12, cols - cmdW - 6);
12508
- 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: [
12509
13287
  visible.map((e, i) => {
12510
13288
  const active = i === selectedIndex;
12511
13289
  const desc = e.description.length > descMax ? `${e.description.slice(0, descMax - 1)}\u2026` : e.description;
12512
13290
  const gap = Math.max(1, cmdW - e.name.length);
12513
- return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "row", children: [
12514
- /* @__PURE__ */ jsx13(Text13, { color: active ? THEME.coralBright : void 0, bold: true, children: active ? "\u276F " : " " }),
12515
- /* @__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: [
12516
13294
  "/",
12517
13295
  e.name
12518
13296
  ] }),
12519
- /* @__PURE__ */ jsx13(Text13, { color: THEME.grayDim, children: " ".repeat(gap) }),
12520
- /* @__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 })
12521
13299
  ] }, e.name);
12522
13300
  }),
12523
- more ? /* @__PURE__ */ jsxs11(Text13, { color: THEME.coralMuted, children: [
13301
+ more ? /* @__PURE__ */ jsxs12(Text14, { color: THEME.coralMuted, children: [
12524
13302
  "\u2026 \u8FD8\u6709 ",
12525
13303
  entries.length - MAX_ROWS,
12526
13304
  " \u6761"
@@ -12531,8 +13309,8 @@ var init_SlashMenu = __esm({
12531
13309
  });
12532
13310
 
12533
13311
  // src/components/StatusStrip.tsx
12534
- import { Box as Box12, Text as Text14 } from "ink";
12535
- 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";
12536
13314
  var StatusStrip;
12537
13315
  var init_StatusStrip = __esm({
12538
13316
  "src/components/StatusStrip.tsx"() {
@@ -12549,18 +13327,18 @@ var init_StatusStrip = __esm({
12549
13327
  `state: ${stateText}`,
12550
13328
  sessionShort ? `session: ${sessionShort}` : ""
12551
13329
  ].filter(Boolean);
12552
- return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", flexShrink: 0, children: [
12553
- /* @__PURE__ */ jsx14(Text14, { color: THEME.coralMuted, children: customLine && customLine.trim() ? customLine : bits.join(" \xB7 ") }),
12554
- /* @__PURE__ */ jsx14(Text14, { color: stateColor, children: "" }),
12555
- /* @__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 })
12556
13334
  ] });
12557
13335
  };
12558
13336
  }
12559
13337
  });
12560
13338
 
12561
13339
  // src/components/TopPanel.tsx
12562
- import { Box as Box13, Text as Text15 } from "ink";
12563
- 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";
12564
13342
  var ROBOT, INNER_H, TopPanel;
12565
13343
  var init_TopPanel = __esm({
12566
13344
  "src/components/TopPanel.tsx"() {
@@ -12580,36 +13358,36 @@ var init_TopPanel = __esm({
12580
13358
  const leftW = Math.floor(innerW * 0.33);
12581
13359
  const rightW = innerW - leftW - 1;
12582
13360
  const ruleLen = Math.max(8, rightW - 2);
12583
- 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)) });
12584
- return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", width: panelW, marginBottom: 1, children: [
12585
- /* @__PURE__ */ jsxs13(Text15, { children: [
12586
- /* @__PURE__ */ jsx15(Text15, { color: THEME.coralBright, bold: true, children: "* Welcome to " }),
12587
- /* @__PURE__ */ jsx15(Text15, { bold: true, color: THEME.white, children: "deep" }),
12588
- /* @__PURE__ */ jsx15(Text15, { bold: true, color: THEME.coralBright, children: "Seek" }),
12589
- /* @__PURE__ */ jsx15(Text15, { bold: true, color: THEME.white, children: "Code" }),
12590
- /* @__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: [
12591
13369
  " ",
12592
13370
  VERSION
12593
13371
  ] })
12594
13372
  ] }),
12595
- /* @__PURE__ */ jsx15(Text15, { color: THEME.grayDim, children: cwd }),
12596
- /* @__PURE__ */ jsxs13(Box13, { flexDirection: "row", borderStyle: "round", borderColor: THEME.coral, marginTop: 1, paddingX: 0, paddingY: 0, children: [
12597
- /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", width: leftW, height: INNER_H, alignItems: "center", justifyContent: "center", children: [
12598
- /* @__PURE__ */ jsx15(Text15, { color: THEME.white, bold: true, children: "Welcome back!" }),
12599
- ROBOT.map((l, i) => /* @__PURE__ */ jsx15(Text15, { color: THEME.coralBright, children: l }, i)),
12600
- /* @__PURE__ */ jsx15(Text15, { color: THEME.grayDim, children: "Ctrl+C \u9000\u51FA \xB7 Esc \u4E2D\u6B62" }),
12601
- /* @__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" })
12602
13380
  ] }),
12603
13381
  VDivider,
12604
- /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", width: rightW, height: INNER_H, justifyContent: "space-between", paddingX: 1, children: [
12605
- /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
12606
- /* @__PURE__ */ jsx15(Text15, { color: THEME.coralBright, bold: true, children: "Tips for getting started" }),
12607
- /* @__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" })
12608
13386
  ] }),
12609
- /* @__PURE__ */ jsx15(Text15, { color: THEME.grayDim, children: "\u2500".repeat(ruleLen) }),
12610
- /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
12611
- /* @__PURE__ */ jsx15(Text15, { color: THEME.coralBright, bold: true, children: "What's new" }),
12612
- /* @__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" })
12613
13391
  ] })
12614
13392
  ] })
12615
13393
  ] })
@@ -12619,7 +13397,7 @@ var init_TopPanel = __esm({
12619
13397
  });
12620
13398
 
12621
13399
  // src/inspect.ts
12622
- import fs37 from "fs";
13400
+ import fs38 from "fs";
12623
13401
  var CWD, fmt, readTrace, inspectUsage, inspectContext, inspectPermissions, inspectMcp, inspectHooks, inspectDebug;
12624
13402
  var init_inspect = __esm({
12625
13403
  "src/inspect.ts"() {
@@ -12637,7 +13415,7 @@ var init_inspect = __esm({
12637
13415
  if (!sessionId) return [];
12638
13416
  try {
12639
13417
  const p = await getTraceStorePath(sessionId);
12640
- const text = await fs37.promises.readFile(p, "utf8");
13418
+ const text = await fs38.promises.readFile(p, "utf8");
12641
13419
  const out = [];
12642
13420
  for (const line of text.split("\n")) {
12643
13421
  if (!line.trim()) continue;
@@ -12760,8 +13538,8 @@ __export(App_exports, {
12760
13538
  App: () => App
12761
13539
  });
12762
13540
  import { useCallback as useCallback2, useEffect as useEffect4, useMemo, useRef as useRef2, useState as useState4 } from "react";
12763
- import { Box as Box14, Static, Text as Text16, useApp, useInput as useInput2, useStdout } from "ink";
12764
- 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";
12765
13543
  var KNOWN_MODELS, CWD2, RowView, isDynamicRow, App;
12766
13544
  var init_App = __esm({
12767
13545
  "src/App.tsx"() {
@@ -12786,6 +13564,7 @@ var init_App = __esm({
12786
13564
  init_PlanModal();
12787
13565
  init_PlanEditor();
12788
13566
  init_SessionPicker();
13567
+ init_ForkPicker();
12789
13568
  init_SlashMenu();
12790
13569
  init_MultilineInput();
12791
13570
  init_StatusStrip();
@@ -12794,16 +13573,16 @@ var init_App = __esm({
12794
13573
  KNOWN_MODELS = ["deepseek-v4", "deepseek-v4-flash"];
12795
13574
  CWD2 = process.cwd();
12796
13575
  RowView = ({ row, wrapW, streamTail, showThinking }) => {
12797
- if (row.kind === "todos") return /* @__PURE__ */ jsx16(TodosPanel, { todos: row.todos, wrapW });
12798
- 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 });
12799
- 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 });
12800
13579
  if (row.kind === "user") {
12801
- return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", marginTop: 1, children: [
12802
- /* @__PURE__ */ jsx16(Text16, { color: THEME.grayDim, children: "\u2500".repeat(Math.max(8, wrapW)) }),
12803
- /* @__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 })
12804
13583
  ] });
12805
13584
  }
12806
- return /* @__PURE__ */ jsx16(MessageBlock, { row, wrapW, streamTail });
13585
+ return /* @__PURE__ */ jsx17(MessageBlock, { row, wrapW, streamTail });
12807
13586
  };
12808
13587
  isDynamicRow = (r) => r.kind === "assistant" && !!r.streaming || r.kind === "thinking" && !!r.streaming || r.kind === "tool" && r.status === "running" || r.kind === "todos" && !!r.active;
12809
13588
  App = ({ resumeSessionId, initialPlanMode, initialAutoMode, initialIncludeProject }) => {
@@ -12903,6 +13682,8 @@ var init_App = __esm({
12903
13682
  return S.cmdOutputStyle;
12904
13683
  case "sessions":
12905
13684
  return S.cmdSessions;
13685
+ case "fork":
13686
+ return S.cmdFork;
12906
13687
  case "usage":
12907
13688
  return S.cmdUsage;
12908
13689
  case "context":
@@ -12935,13 +13716,13 @@ var init_App = __esm({
12935
13716
  const q = input.slice(1).toLowerCase();
12936
13717
  return menuEntries.filter((c) => c.name.toLowerCase().startsWith(q));
12937
13718
  }, [input, menuEntries]);
12938
- 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;
12939
13720
  const slashVisible = !menuActive && input.startsWith("/") && filteredCommands.length > 0;
12940
13721
  const inputActive = !menuActive;
12941
13722
  useEffect4(() => {
12942
13723
  setSelectIdx(0);
12943
13724
  setPlanEditing(false);
12944
- }, [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]);
12945
13726
  useEffect4(() => {
12946
13727
  setQCursor(0);
12947
13728
  setQChecked(/* @__PURE__ */ new Set());
@@ -12960,6 +13741,9 @@ var init_App = __esm({
12960
13741
  case "/sessions":
12961
13742
  void state.openSessionPicker();
12962
13743
  return true;
13744
+ case "/fork":
13745
+ void state.openForkPicker();
13746
+ return true;
12963
13747
  case "/help":
12964
13748
  state.pushInfo(S.helpText(modelDisplay, state.getThinkingLevel(), getLocale()));
12965
13749
  return true;
@@ -13108,7 +13892,12 @@ ${listText}`);
13108
13892
  const v = inputRef.current.trim();
13109
13893
  if (!v) return;
13110
13894
  if (busyRef.current) {
13111
- 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
+ }
13112
13901
  return;
13113
13902
  }
13114
13903
  setInput("");
@@ -13190,6 +13979,16 @@ ${listText}`);
13190
13979
  } else if (key.escape || key.ctrl && ch === "g") state.resolveSession(null);
13191
13980
  return;
13192
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
+ }
13193
13992
  if (key.ctrl && ch === "t") {
13194
13993
  state.toggleShowThinking();
13195
13994
  return;
@@ -13230,19 +14029,19 @@ ${listText}`);
13230
14029
  }
13231
14030
  });
13232
14031
  const sessionShort = state.sessionIdRef.current ? truncateMiddle(state.sessionIdRef.current, 12) : "";
13233
- return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", width: cols, children: [
13234
- /* @__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) }),
13235
- /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", width: cols, children: [
13236
- menuActive ? null : /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", paddingX: 1, children: [
13237
- dynamicRows.map((row) => /* @__PURE__ */ jsx16(RowView, { row, wrapW, streamTail, showThinking: state.showThinkingText }, row.id)),
13238
- 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: [
13239
14038
  "\u25CF ",
13240
14039
  S.generating
13241
14040
  ] }) }) : null
13242
14041
  ] }),
13243
- /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", paddingX: 1, flexShrink: 0, children: [
13244
- state.pendingApproval ? /* @__PURE__ */ jsx16(ApprovalModal, { toolName: state.pendingApproval.toolName, detail: state.pendingApproval.detail, selectedIndex: selectIdx, wrapW }) : null,
13245
- 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(
13246
14045
  QuestionModal,
13247
14046
  {
13248
14047
  question: state.pendingQuestion.req.question,
@@ -13253,7 +14052,7 @@ ${listText}`);
13253
14052
  wrapW
13254
14053
  }
13255
14054
  ) : null,
13256
- state.pendingPlan ? planEditing ? /* @__PURE__ */ jsx16(
14055
+ state.pendingPlan ? planEditing ? /* @__PURE__ */ jsx17(
13257
14056
  PlanEditor,
13258
14057
  {
13259
14058
  value: planDraft,
@@ -13264,13 +14063,14 @@ ${listText}`);
13264
14063
  },
13265
14064
  onSubmit: confirmPlanEdit
13266
14065
  }
13267
- ) : /* @__PURE__ */ jsx16(PlanModal, { selectedIndex: selectIdx }) : null,
13268
- state.pendingSessions ? /* @__PURE__ */ jsx16(SessionPicker, { sessions: state.pendingSessions.sessions, selectedIndex: selectIdx, wrapW }) : null,
13269
- 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
13270
14070
  ] }),
13271
- /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", paddingX: 1, marginTop: 1, flexShrink: 0, children: [
13272
- /* @__PURE__ */ jsx16(Text16, { dimColor: true, color: THEME.gray, children: dimRule(cols) }),
13273
- /* @__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(
13274
14074
  MultilineInput,
13275
14075
  {
13276
14076
  value: input,
@@ -13288,7 +14088,7 @@ ${listText}`);
13288
14088
  }
13289
14089
  )
13290
14090
  ] }),
13291
- /* @__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 }) })
13292
14092
  ] })
13293
14093
  ] });
13294
14094
  };
@@ -13302,7 +14102,7 @@ __export(updateCheck_exports, {
13302
14102
  readLocalVersion: () => readLocalVersion,
13303
14103
  refreshUpdateCache: () => refreshUpdateCache
13304
14104
  });
13305
- import { readFileSync as readFileSync3, writeFileSync, mkdirSync } from "node:fs";
14105
+ import { readFileSync as readFileSync4, writeFileSync, mkdirSync } from "node:fs";
13306
14106
  import path39 from "node:path";
13307
14107
  import os4 from "os";
13308
14108
  import { fileURLToPath as fileURLToPath6 } from "node:url";
@@ -13318,7 +14118,7 @@ var init_updateCheck = __esm({
13318
14118
  readLocalVersion = () => {
13319
14119
  try {
13320
14120
  const here = path39.dirname(fileURLToPath6(import.meta.url));
13321
- return JSON.parse(readFileSync3(path39.join(here, "..", "package.json"), "utf8")).version ?? "0.0.0";
14121
+ return JSON.parse(readFileSync4(path39.join(here, "..", "package.json"), "utf8")).version ?? "0.0.0";
13322
14122
  } catch {
13323
14123
  return "0.0.0";
13324
14124
  }
@@ -13334,7 +14134,7 @@ var init_updateCheck = __esm({
13334
14134
  };
13335
14135
  readCache = () => {
13336
14136
  try {
13337
- const obj = JSON.parse(readFileSync3(cacheFile(), "utf8"));
14137
+ const obj = JSON.parse(readFileSync4(cacheFile(), "utf8"));
13338
14138
  return typeof obj.latestVersion === "string" && typeof obj.lastCheck === "number" ? obj : null;
13339
14139
  } catch {
13340
14140
  return null;
@@ -13422,7 +14222,7 @@ import { render } from "ink";
13422
14222
  import os5 from "os";
13423
14223
  import path40 from "path";
13424
14224
  import { emitKeypressEvents } from "node:readline";
13425
- import { jsx as jsx17 } from "react/jsx-runtime";
14225
+ import { jsx as jsx18 } from "react/jsx-runtime";
13426
14226
  var parseArgs = (argv) => {
13427
14227
  const out = {};
13428
14228
  for (let i = 0; i < argv.length; i++) {
@@ -13566,7 +14366,7 @@ var main = async () => {
13566
14366
  resumeId = await getMostRecentSessionId2() ?? void 0;
13567
14367
  }
13568
14368
  const { waitUntilExit } = render(
13569
- /* @__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 }),
13570
14370
  // exitOnCtrlC:false:Ctrl+C 交由 App useInput 处理(统一退出/中止语义);
13571
14371
  // patchConsole:false:避免 console 劫持与全屏重绘叠加闪屏。
13572
14372
  { exitOnCtrlC: false, patchConsole: false }