deepseeker-code 1.0.3 → 1.0.41

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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.mjs +1513 -396
  3. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -34,17 +34,19 @@ var init_strings = __esm({
34
34
  toolRunning: (name) => `\u23FA ${name} \u8FD0\u884C\u4E2D\u2026`,
35
35
  toolDone: (name, ok) => `\u23FA ${name} \xB7 ${ok ? "\u6210\u529F" : "\u5931\u8D25"}`,
36
36
  toolDenied: (name) => `\u{1F6AB} ${name} \u5DF2\u88AB\u62D2\u7EDD`,
37
- planTitle: "\u2705 \u5B9E\u73B0\u65B9\u6848\uFF08\u8BA1\u5212\u6A21\u5F0F\uFF09",
38
- planPrompt: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u786E\u8BA4\uFF08\u4FEE\u6539\u9879\u8FDB\u5165\u7F16\u8F91\uFF0CEnter \u63D0\u4EA4 / Esc \u53D6\u6D88\uFF09\u3002",
39
- planAcceptAuto: "\u63A5\u53D7\u5E76\u81EA\u52A8\u6267\u884C\uFF08\u5B9E\u73B0\u9636\u6BB5\u514D\u5BA1\u6279\uFF09",
40
- planAcceptManual: "\u63A5\u53D7\u5E76\u624B\u52A8\u6267\u884C\uFF08\u9010\u6B65\u5BA1\u6279\u6BCF\u4E2A\u5DE5\u5177\uFF09",
41
- planEdit: "\u4FEE\u6539\u65B9\u6848",
42
- planReject: "\u62D2\u7EDD\uFF0C\u56DE\u5230\u8F93\u5165\u6846",
37
+ planTitle: "\u51C6\u5907\u5F00\u59CB\u7F16\u7801\uFF1F",
38
+ planPrompt: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u786E\u8BA4\uFF08\u7F16\u8F91\u9879\u8FDB\u5165\u7F16\u8F91\u5668\uFF0CEnter \u63D0\u4EA4 / Esc \u53D6\u6D88\uFF09\u3002",
39
+ planAcceptAuto: "\u662F\uFF0C\u5E76\u81EA\u52A8\u63A5\u53D7\u7F16\u8F91\uFF08\u5B9E\u73B0\u9636\u6BB5\u514D\u5BA1\u6279\uFF09",
40
+ planAcceptManual: "\u662F\uFF0C\u5E76\u624B\u52A8\u5BA1\u6279\u7F16\u8F91\uFF08\u9010\u6B65\u5BA1\u6279\u6BCF\u4E2A\u5DE5\u5177\uFF09",
41
+ planEdit: "\u7F16\u8F91\u65B9\u6848",
42
+ planReject: "\u5426\uFF0C\u7EE7\u7EED\u89C4\u5212",
43
43
  planEditTitle: "\u270F\uFE0F \u7F16\u8F91\u65B9\u6848",
44
44
  planEditHint: "Enter \u6309\u6B64\u65B9\u6848\u6267\u884C \xB7 Esc \u53D6\u6D88\u56DE\u5230\u9009\u9879",
45
- planRejected: "\u270B \u5DF2\u62D2\u7EDD\u65B9\u6848\uFF0C\u672C\u8F6E\u672A\u6267\u884C\u3002",
45
+ planKeepPlanning: "\u270B \u5DF2\u7559\u5728\u8BA1\u5212\u6A21\u5F0F\uFF1A\u7EE7\u7EED\u8F93\u5165\u8865\u5145\u8981\u6C42\uFF0C\u6A21\u578B\u5C06\u4FEE\u8BA2\u65B9\u6848\u540E\u518D\u6B21\u63D0\u4EA4\uFF08/plan \u53EF\u9000\u51FA\uFF09\u3002",
46
46
  planEditEmpty: "\u65B9\u6848\u4E0D\u80FD\u4E3A\u7A7A",
47
47
  planAutoExecute: "\u26A1 \u5DF2\u8FDB\u5165\u81EA\u52A8\u6267\u884C\uFF1A\u5B9E\u73B0\u9636\u6BB5\u5DE5\u5177\u5C06\u514D\u5BA1\u6279\u76F4\u63A5\u8FD0\u884C\u3002",
48
+ inputBadgePlan: "plan mode on\uFF08\u53EA\u8BFB\u8C03\u7814 \u2192 \u65B9\u6848\u5BA1\u6279 \u2192 \u5B9E\u73B0\uFF09",
49
+ inputBadgeAuto: "auto-accept edits on",
48
50
  approvalTitle: "\u{1F510} \u64CD\u4F5C\u5BA1\u6279",
49
51
  approvalPrompt: "\u2191\u2193 \u9009\u62E9\u540E Enter\uFF08Esc \u62D2\u7EDD\uFF09\uFF1A\u5141\u8BB8\u672C\u6B21 / \u603B\u662F\u5141\u8BB8\uFF08\u5199\u6301\u4E45\u89C4\u5219\uFF09/ \u62D2\u7EDD\u3002",
50
52
  approvalAllow: "\u5141\u8BB8\u672C\u6B21",
@@ -52,6 +54,9 @@ var init_strings = __esm({
52
54
  approvalDeny: "\u62D2\u7EDD",
53
55
  errAborted: "\u5DF2\u4E2D\u6B62\u5F53\u524D\u8F6E\u3002",
54
56
  busyBlockSend: "\u23F3 \u6B63\u5728\u751F\u6210\uFF0C\u8BF7\u7B49\u5F85\u6216\u6309 Esc \u4E2D\u6B62\u540E\u518D\u53D1\u9001\u3002",
57
+ inboxQueued: "\u{1F4EE} \u5DF2\u6392\u961F\uFF0C\u5C06\u5728\u56DE\u5408\u8FB9\u754C\u9001\u8FBE\u6A21\u578B\uFF08Esc \u4ECD\u53EF\u4E2D\u6B62\uFF09\u3002",
58
+ inboxClaimed: (n) => `\u{1F4EC} \u5DF2\u9001\u8FBE\u6A21\u578B\uFF08${n} \u6761\u8865\u5145\u8F93\u5165\uFF09`,
59
+ 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
60
  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
61
  statusStreaming: "\u751F\u6210\u4E2D",
57
62
  statusIdle: "\u5C31\u7EEA",
@@ -98,6 +103,12 @@ ${dim} \u8FD0\u884C npm i -g deepseeker-code@latest \u66F4\u65B0${reset}
98
103
  sessionsPrompt: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u8F7D\u5165 \xB7 Esc \u53D6\u6D88",
99
104
  noHistory: "\uFF08\u6682\u65E0\u5386\u53F2\u4F1A\u8BDD\uFF09",
100
105
  sessionLoaded: (id) => `\u5DF2\u8F7D\u5165\u4F1A\u8BDD ${id}\uFF08\u7EE7\u7EED\u5BF9\u8BDD\u5C06\u7EED\u63A5\u6B64\u4F1A\u8BDD\uFF09`,
106
+ cmdFork: "\u4ECE\u5F53\u524D\u4F1A\u8BDD\u7684\u67D0\u8F6E\u56DE\u590D\u5904\u5206\u53C9\u51FA\u65B0\u4F1A\u8BDD",
107
+ forkTitle: "\u{1F33F} \u5206\u53C9\u5F53\u524D\u4F1A\u8BDD\uFF08\u2191\u2193 \u9009\u68C0\u67E5\u70B9 \xB7 Enter \u5206\u53C9\uFF09",
108
+ forkPrompt: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u5206\u53C9 \xB7 Esc \u53D6\u6D88",
109
+ forkEmpty: "\uFF08\u5F53\u524D\u4F1A\u8BDD\u6682\u65E0\u53EF\u5206\u53C9\u7684\u68C0\u67E5\u70B9\uFF09",
110
+ 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`,
111
+ forkFailed: (e) => `\u5206\u53C9\u5931\u8D25\uFF1A${e}`,
101
112
  relTime: (iso) => {
102
113
  const t = Date.parse(iso);
103
114
  if (!t) return "";
@@ -133,6 +144,7 @@ DeepSeeker-Code \u5C06\u5728\u6B64\u76EE\u5F55\u8BFB\u53D6\u3001\u7F16\u8F91\u54
133
144
  `/thinking [off|high|max] \u2014 \u5207\u6362\u601D\u8003\u7B49\u7EA7\uFF08\u5F53\u524D ${thinking}\uFF09`,
134
145
  `/lang [zh|en] \u2014 \u5207\u6362\u754C\u9762\u8BED\u8A00\uFF08\u5F53\u524D ${locale2}\uFF09`,
135
146
  "/sessions \u2014 \u9009\u62E9\u5E76\u8F7D\u5165\u5386\u53F2\u4F1A\u8BDD\uFF08\u7EED\u63A5\u5BF9\u8BDD\uFF09",
147
+ "/fork \u2014 \u4ECE\u5F53\u524D\u4F1A\u8BDD\u7684\u67D0\u8F6E\u56DE\u590D\u5904\u5206\u53C9\u51FA\u65B0\u4F1A\u8BDD",
136
148
  "/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
149
  "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
150
  ].join("\n"),
@@ -162,17 +174,19 @@ DeepSeeker-Code \u5C06\u5728\u6B64\u76EE\u5F55\u8BFB\u53D6\u3001\u7F16\u8F91\u54
162
174
  toolRunning: (name) => `\u23FA ${name} running\u2026`,
163
175
  toolDone: (name, ok) => `\u23FA ${name} \xB7 ${ok ? "ok" : "failed"}`,
164
176
  toolDenied: (name) => `\u{1F6AB} ${name} denied`,
165
- planTitle: "\u2705 Implementation plan (plan mode)",
177
+ planTitle: "Ready to code?",
166
178
  planPrompt: "\u2191\u2193 then Enter (edit opens the editor: Enter to save / Esc to cancel).",
167
- planAcceptAuto: "Accept & auto-run (skip impl approvals)",
168
- planAcceptManual: "Accept & step-by-step (approve each tool)",
179
+ planAcceptAuto: "Yes, and auto-accept edits (skip impl approvals)",
180
+ planAcceptManual: "Yes, and manually approve edits (step-by-step)",
169
181
  planEdit: "Edit plan",
170
- planReject: "Reject, back to prompt",
182
+ planReject: "No, keep planning",
171
183
  planEditTitle: "\u270F\uFE0F Edit plan",
172
184
  planEditHint: "Enter to implement with this plan \xB7 Esc back to options",
173
- planRejected: "\u270B Plan rejected, nothing executed this turn.",
185
+ planKeepPlanning: "\u270B Staying in plan mode: add feedback and the plan will be revised and resubmitted (/plan to exit).",
174
186
  planEditEmpty: "Plan cannot be empty",
175
187
  planAutoExecute: "\u26A1 Auto-run: implementation tools will execute without approval.",
188
+ inputBadgePlan: "plan mode on (read-only research \u2192 approve \u2192 implement)",
189
+ inputBadgeAuto: "auto-accept edits on",
176
190
  approvalTitle: "\u{1F510} Action approval",
177
191
  approvalPrompt: "\u2191\u2193 then Enter (Esc to deny): allow once / always allow (persist rule) / deny.",
178
192
  approvalAllow: "Allow once",
@@ -180,6 +194,9 @@ DeepSeeker-Code \u5C06\u5728\u6B64\u76EE\u5F55\u8BFB\u53D6\u3001\u7F16\u8F91\u54
180
194
  approvalDeny: "Deny",
181
195
  errAborted: "Aborted current turn.",
182
196
  busyBlockSend: "\u23F3 Still generating\u2014wait or press Esc to abort before sending.",
197
+ inboxQueued: "\u{1F4EE} Queued\u2014will reach the model at the next round boundary (Esc still aborts).",
198
+ inboxClaimed: (n) => `\u{1F4EC} Delivered to the model (${n} supplemental input${n > 1 ? "s" : ""})`,
199
+ 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
200
  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
201
  statusStreaming: "streaming",
185
202
  statusIdle: "ready",
@@ -226,6 +243,12 @@ ${dim} Run npm i -g deepseeker-code@latest to update${reset}
226
243
  sessionsPrompt: "\u2191\u2193 pick \xB7 Enter resume \xB7 Esc cancel",
227
244
  noHistory: "(no past sessions)",
228
245
  sessionLoaded: (id) => `Resumed session ${id} (new messages continue it)`,
246
+ cmdFork: "Fork the current session from a past round",
247
+ forkTitle: "\u{1F33F} Fork current session (\u2191\u2193 to pick \xB7 Enter to fork)",
248
+ forkPrompt: "\u2191\u2193 pick \xB7 Enter fork \xB7 Esc cancel",
249
+ forkEmpty: "(no checkpoints to fork from in this session)",
250
+ forkDone: (id, roundNo) => `Forked from round ${roundNo} \u2192 new session ${id} (loaded; new messages go to the fork, source untouched)`,
251
+ forkFailed: (e) => `Fork failed: ${e}`,
229
252
  relTime: (iso) => {
230
253
  const t = Date.parse(iso);
231
254
  if (!t) return "";
@@ -261,6 +284,7 @@ DeepSeeker-Code will read, edit, and execute files here.`,
261
284
  `/thinking [off|high|max] \u2014 Switch thinking level (current ${thinking})`,
262
285
  `/lang [zh|en] \u2014 Switch interface language (current ${locale2})`,
263
286
  "/sessions \u2014 Pick a past session to resume",
287
+ "/fork \u2014 Fork the current session from a past round",
264
288
  "/trust \u2014 Manage trusted dirs (project hooks/skills load only when trusted; CI uses --trust)",
265
289
  "Ctrl+C exit \xB7 Esc abort/clear input \xB7 Ctrl+G abort \xB7 Ctrl+T toggle thinking"
266
290
  ].join("\n"),
@@ -281,7 +305,7 @@ DeepSeeker-Code will read, edit, and execute files here.`,
281
305
  S = new Proxy({}, {
282
306
  get: (_t, k) => typeof k === "string" ? STRINGS[locale][k] : void 0
283
307
  });
284
- LOCAL_COMMAND_NAMES = ["help", "status", "plan", "auto", "model", "thinking", "lang", "output-style", "sessions", "usage", "context", "permissions", "mcp", "hooks", "trust", "debug", "clear", "exit"];
308
+ LOCAL_COMMAND_NAMES = ["help", "status", "plan", "auto", "model", "thinking", "lang", "output-style", "sessions", "fork", "usage", "context", "permissions", "mcp", "hooks", "trust", "debug", "clear", "exit"];
285
309
  }
286
310
  });
287
311
 
@@ -289,13 +313,15 @@ DeepSeeker-Code will read, edit, and execute files here.`,
289
313
  var config_exports = {};
290
314
  __export(config_exports, {
291
315
  ENGINE_OVERRIDE_KEYS: () => ENGINE_OVERRIDE_KEYS,
292
- appConfig: () => appConfig
316
+ ENV_SOURCES: () => ENV_SOURCES,
317
+ appConfig: () => appConfig,
318
+ configSources: () => configSources
293
319
  });
294
320
  import path2 from "path";
295
321
  import os2 from "os";
296
322
  import { readFileSync as readFileSync2 } from "fs";
297
323
  import { createHash } from "crypto";
298
- var _DataDir, appConfig, ENGINE_OVERRIDE_VALIDATORS, ENGINE_OVERRIDE_KEYS, applyEngineOverrides;
324
+ var _DataDir, appConfig, ENV_SOURCES, configSources, ENGINE_OVERRIDE_VALIDATORS, ENGINE_OVERRIDE_KEYS, applyEngineOverrides;
299
325
  var init_config = __esm({
300
326
  "../core/src/config/index.ts"() {
301
327
  "use strict";
@@ -370,8 +396,51 @@ var init_config = __esm({
370
396
  workflowMaxSteps: Number(process.env.DEEP_SEEK_WORKFLOW_MAX_STEPS) || 8,
371
397
  /** P0-3 run_workflow 单个子 agent 结果的字符预算:超出按头尾截断,避免单个巨型结果挤占聚合输出。
372
398
  * 最终聚合再受工具 maxOutputCharacters 兜底。 */
373
- workflowPerStepChars: 6e3
374
- };
399
+ workflowPerStepChars: 6e3,
400
+ /** ★ P0-A 计划模式强制位置:'runtime'(默认)= 工具表全会话恒定(两态统一 appendPlanControlTools),
401
+ * 计划期写工具由 processToolCall 执行层按 PLAN_ALLOWED_TOOLS 拒绝——保 DeepSeek 前缀缓存
402
+ * (tools 参数序列化在请求头部,计划模式裁表翻转 = 全会话历史 re-prefill 两次);
403
+ * 'schema' = 旧路径(计划期裁成只读白名单子表)。env DEEP_SEEK_PLAN_ENFORCEMENT=schema 回退。 */
404
+ planEnforcement: process.env.DEEP_SEEK_PLAN_ENFORCEMENT === "schema" ? "schema" : "runtime",
405
+ /** ★ P1-MCP 工具暴露模式:'dispatcher'(默认)= 只注入恒定 schema 的 mcp_list_tools + mcp_call
406
+ * (目录按需查、审批/权限用合成名 mcp__<server>__<tool> 匹配既有规则;工具表不随 MCP server
407
+ * 工具数增长,L1 恒定);'schemas' = 旧路径(每个 MCP 工具独立 schema 常驻)。
408
+ * env DEEP_SEEK_MCP_EXPOSE_MODE=schemas 回退。 */
409
+ mcpExposeMode: process.env.DEEP_SEEK_MCP_EXPOSE_MODE === "schemas" ? "schemas" : "dispatcher",
410
+ /** ★ 事件日志化:transcript 追加 turn/step 边界事件行(run.start/round.end/run.end/compaction/
411
+ * run.abandoned,带 dscEvent 标记,readMessages 自动过滤)——崩溃恢复从盲扫启发式升级为事件确认,
412
+ * compaction 事件携带归档计数+摘要文本使 transcript 自包含(分叉/审计不再押 state.json 单点)。
413
+ * 关闭时 appendEvent 全 no-op、恢复走纯内存启发式(= 改造前行为)。env DEEP_SEEK_TRANSCRIPT_EVENTS=0 回退。 */
414
+ transcriptEvents: process.env.DEEP_SEEK_TRANSCRIPT_EVENTS !== "0",
415
+ /** ★ inbox steering(第二梯队 #2):agent 运行中用户补充输入进 per-session 队列,runAgent 回合边界
416
+ * claim 注入上下文并落盘 transcript(长任务中途补指示不再只能中止重跑)。
417
+ * 关闭时全链路 no-op:端点 501 / CLI 回退 busy 提示 / VSCode 不排队。env DEEP_SEEK_INBOX=0 回退。 */
418
+ inboxSteering: process.env.DEEP_SEEK_INBOX !== "0",
419
+ /** ★ hooks 改写型拦截点(第二梯队 #3):PreToolUse 可改 args(argsOverride,前置到全部安全门禁
420
+ * 之前生效——门禁/审批评估的是改写后参数)、PostToolUse 可改 resultForModel(仅模型视图,
421
+ * 用户视图 resultForUser 不变)。关闭时 PreToolUse 回退旧位(审批后、仅 deny),改写字段全忽略,
422
+ * 行为与改造前一致。env DEEP_SEEK_HOOK_REWRITE=0 回退。 */
423
+ hookRewrite: process.env.DEEP_SEEK_HOOK_REWRITE !== "0"
424
+ };
425
+ ENV_SOURCES = {
426
+ dataDir: "DEEPSEEKER_CODE_DATA_DIR",
427
+ searchProvider: "SEARCH_PROVIDER",
428
+ tavilyApiKey: "TAVILY_API_KEY",
429
+ webFetchAllowPrivate: "WEB_FETCH_ALLOW_PRIVATE",
430
+ parallelSafeTools: "DEEP_SEEK_PARALLEL_SAFE_TOOLS",
431
+ workflowConcurrency: "DEEP_SEEK_WORKFLOW_CONCURRENCY",
432
+ workflowMaxSteps: "DEEP_SEEK_WORKFLOW_MAX_STEPS",
433
+ planEnforcement: "DEEP_SEEK_PLAN_ENFORCEMENT",
434
+ mcpExposeMode: "DEEP_SEEK_MCP_EXPOSE_MODE",
435
+ transcriptEvents: "DEEP_SEEK_TRANSCRIPT_EVENTS",
436
+ inboxSteering: "DEEP_SEEK_INBOX",
437
+ hookRewrite: "DEEP_SEEK_HOOK_REWRITE"
438
+ };
439
+ configSources = {};
440
+ for (const [key, envName] of Object.entries(ENV_SOURCES)) {
441
+ const v = process.env[envName];
442
+ if (v !== void 0 && v !== "") configSources[key] = "env";
443
+ }
375
444
  ENGINE_OVERRIDE_VALIDATORS = {
376
445
  undoEnabled: (v) => typeof v === "boolean" ? v : void 0,
377
446
  undoBackupSensitive: (v) => v === "skip" || v === "deny" || v === "allow" ? v : void 0,
@@ -382,12 +451,12 @@ var init_config = __esm({
382
451
  ENGINE_OVERRIDE_KEYS = Object.keys(ENGINE_OVERRIDE_VALIDATORS);
383
452
  applyEngineOverrides = (cfg) => {
384
453
  const paths = [
385
- path2.join(cfg.dataDir, "settings.json"),
454
+ { scope: "settings.global", configPath: path2.join(cfg.dataDir, "settings.json") },
386
455
  // 全局用户级
387
- path2.join(process.cwd(), ".deepseeker-code", "settings.json")
456
+ { scope: "settings.project", configPath: path2.join(process.cwd(), ".deepseeker-code", "settings.json") }
388
457
  // 项目级(覆盖全局)
389
458
  ];
390
- for (const configPath of paths) {
459
+ for (const { scope, configPath } of paths) {
391
460
  let raw;
392
461
  try {
393
462
  raw = readFileSync2(configPath, "utf-8");
@@ -411,6 +480,7 @@ var init_config = __esm({
411
480
  continue;
412
481
  }
413
482
  cfg[key] = valid;
483
+ configSources[key] = scope;
414
484
  }
415
485
  }
416
486
  };
@@ -491,13 +561,20 @@ var init_common = __esm({
491
561
  injectMarkedBlock = (message, fence, body) => {
492
562
  const sys = message[0];
493
563
  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 += `
564
+ const idx = sys.content.indexOf(fence);
565
+ if (idx === -1) {
566
+ sys.content += `
498
567
 
499
568
  ${fence}
500
569
  ${body}`;
570
+ return;
571
+ }
572
+ const after = sys.content.slice(idx + fence.length);
573
+ const next = after.match(/\n\n⟦[^\n]*?⟧/);
574
+ const oldBody = next?.index !== void 0 ? after.slice(0, next.index) : after;
575
+ if (oldBody === `
576
+ ${body}`) return;
577
+ 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
578
  };
502
579
  decodeProcessOutput = (buf) => {
503
580
  const utf8 = buf.toString("utf8");
@@ -811,17 +888,120 @@ var init_deepseek = __esm({
811
888
  }
812
889
  });
813
890
 
891
+ // ../core/src/llm/providers/replay/index.ts
892
+ var createReplayProvider;
893
+ var init_replay = __esm({
894
+ "../core/src/llm/providers/replay/index.ts"() {
895
+ "use strict";
896
+ createReplayProvider = (script) => {
897
+ const turns = Array.isArray(script.turns) ? script.turns : [];
898
+ let cursor = 0;
899
+ let attemptCounts = [];
900
+ const calls = [];
901
+ const makeFaultError = (fault) => {
902
+ 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");
903
+ const e = new Error(msg);
904
+ e.replayFault = fault.type;
905
+ if (fault.type === "transient") e.status = 429;
906
+ return e;
907
+ };
908
+ const streamChat2 = async function* (messages, tools, opts) {
909
+ const turnIndex = cursor;
910
+ calls.push({ turnIndex, messages: [...messages], tools, opts });
911
+ const turn = turns[turnIndex];
912
+ if (!turn) return;
913
+ const attempt = attemptCounts[turnIndex] ?? 0;
914
+ attemptCounts[turnIndex] = attempt + 1;
915
+ const fault = turn.fault;
916
+ const shouldFault = !!fault && (fault.type === "fatal" || attempt < (fault.times ?? 1));
917
+ if (shouldFault) {
918
+ const f = fault;
919
+ const n = Math.min(f.afterChars ?? 0, (turn.content ?? "").length);
920
+ if (n > 0) yield { kind: "text", text: (turn.content ?? "").slice(0, n) };
921
+ throw makeFaultError(f);
922
+ }
923
+ if (turn.reasoning) yield { kind: "reasoning", text: turn.reasoning };
924
+ if (turn.content) yield { kind: "text", text: turn.content };
925
+ const tcs = turn.toolCalls ?? [];
926
+ for (let i = 0; i < tcs.length; i++) {
927
+ const tc = tcs[i];
928
+ yield { kind: "tool_call_delta", index: i, id: tc.id ?? `call_replay_${turnIndex}_${i}`, type: "function", nameDelta: tc.name };
929
+ yield { kind: "tool_call_delta", index: i, argumentsDelta: JSON.stringify(tc.args ?? {}) };
930
+ }
931
+ if (turn.usage) yield { kind: "usage", usage: turn.usage };
932
+ cursor++;
933
+ };
934
+ const summarize2 = async (_messages, _tools, _opts) => ({
935
+ id: "replay-summary",
936
+ object: "chat.completion",
937
+ created: 0,
938
+ // 确定性:固定值(回放产物不掺时间戳,diff 稳定)
939
+ model: "replay",
940
+ choices: [{
941
+ index: 0,
942
+ 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 },
943
+ finish_reason: "stop",
944
+ logprobs: null
945
+ }],
946
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
947
+ });
948
+ return {
949
+ id: "replay",
950
+ displayName: "Replay",
951
+ modelLabel: "Replay",
952
+ streamChat: streamChat2,
953
+ summarize: summarize2,
954
+ classifyRisk: async () => script.riskVerdict ?? "safe",
955
+ buildAssistantMessage: (parts) => {
956
+ const m = { role: "assistant", content: parts.content };
957
+ if (parts.reasoning) m.reasoning_content = parts.reasoning;
958
+ if (parts.toolCalls?.length) m.tool_calls = parts.toolCalls;
959
+ return m;
960
+ },
961
+ // 自识别回放注入的故障(streamInference 经 activeProvider 调用这两个分类器驱动重试通道)
962
+ isContextLengthError: (e) => e?.replayFault === "context_length",
963
+ isTransientError: (e) => e?.replayFault === "transient",
964
+ get calls() {
965
+ return calls;
966
+ },
967
+ get cursor() {
968
+ return cursor;
969
+ },
970
+ reset: () => {
971
+ cursor = 0;
972
+ attemptCounts = [];
973
+ calls.length = 0;
974
+ }
975
+ };
976
+ };
977
+ }
978
+ });
979
+
814
980
  // ../core/src/llm/model.ts
815
- var activeProvider, chatWithModelWithSummary, classifyToolRisk, isContextLengthError2, isTransientApiError;
981
+ import { readFileSync as readFileSync3 } from "fs";
982
+ var resolveInitialProvider, activeProvider, chatWithModelWithSummary, classifyToolRisk;
816
983
  var init_model = __esm({
817
984
  "../core/src/llm/model.ts"() {
818
985
  "use strict";
819
986
  init_deepseek();
820
- activeProvider = deepseekProvider;
821
- chatWithModelWithSummary = deepseekProvider.summarize;
822
- classifyToolRisk = deepseekProvider.classifyRisk;
823
- isContextLengthError2 = deepseekProvider.isContextLengthError;
824
- isTransientApiError = deepseekProvider.isTransientError;
987
+ init_replay();
988
+ resolveInitialProvider = () => {
989
+ if (process.env.DEEP_SEEK_PROVIDER !== "replay") return deepseekProvider;
990
+ const scriptPath = process.env.DEEP_SEEK_REPLAY_SCRIPT;
991
+ if (!scriptPath) {
992
+ 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");
993
+ }
994
+ let raw;
995
+ try {
996
+ raw = JSON.parse(readFileSync3(scriptPath, "utf-8"));
997
+ } catch (e) {
998
+ throw new Error(`\u56DE\u653E\u5267\u672C\u52A0\u8F7D\u5931\u8D25\uFF08${scriptPath}\uFF09: ${e?.message ?? e}`);
999
+ }
1000
+ return createReplayProvider(Array.isArray(raw) ? { turns: raw } : raw);
1001
+ };
1002
+ activeProvider = resolveInitialProvider();
1003
+ chatWithModelWithSummary = (messages, tools, opts) => activeProvider.summarize(messages, tools, opts);
1004
+ classifyToolRisk = (toolName, args, detail, signal) => activeProvider.classifyRisk(toolName, args, detail, signal);
825
1005
  }
826
1006
  });
827
1007
 
@@ -1139,6 +1319,7 @@ var init_store = __esm({
1139
1319
  } catch {
1140
1320
  continue;
1141
1321
  }
1322
+ if (typeof msg?.dscEvent === "string") continue;
1142
1323
  messageCount++;
1143
1324
  if (!preview && msg?.role === "user" && typeof msg.content === "string") {
1144
1325
  const t = msg.content.replace(/\s+/g, " ").trim();
@@ -1193,6 +1374,87 @@ var init_store = __esm({
1193
1374
  }
1194
1375
  });
1195
1376
 
1377
+ // ../core/src/session/transcript.ts
1378
+ import fs5 from "fs/promises";
1379
+ var isEventLine, readTranscriptLines, appendLineWithRetry, appendMessage, appendEvent;
1380
+ var init_transcript = __esm({
1381
+ "../core/src/session/transcript.ts"() {
1382
+ "use strict";
1383
+ init_store();
1384
+ init_common();
1385
+ init_config();
1386
+ isEventLine = (l) => typeof l?.dscEvent === "string";
1387
+ readTranscriptLines = async (sessionId) => {
1388
+ await ensureSessionsDir(sessionId);
1389
+ const p = getTranscriptPath(sessionId);
1390
+ try {
1391
+ const text = await fs5.readFile(p, "utf-8");
1392
+ const lines = [];
1393
+ for (const s of text.split("\n")) {
1394
+ const line = s.trim();
1395
+ if (line.length === 0) continue;
1396
+ try {
1397
+ lines.push(JSON.parse(line));
1398
+ } catch {
1399
+ console.warn(`\u26A0\uFE0F [transcript] \u8DF3\u8FC7\u65E0\u6CD5\u89E3\u6790\u7684\u635F\u574F\u884C: ${line.slice(0, 120)}`);
1400
+ }
1401
+ }
1402
+ return lines;
1403
+ } catch (err) {
1404
+ if (err?.code === "ENOENT") return [];
1405
+ throw err;
1406
+ }
1407
+ };
1408
+ appendLineWithRetry = async (p, payload) => {
1409
+ const MAX_ATTEMPTS = 3;
1410
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
1411
+ let beforeSize = 0;
1412
+ try {
1413
+ beforeSize = (await fs5.stat(p)).size;
1414
+ } catch {
1415
+ }
1416
+ try {
1417
+ await fs5.appendFile(p, payload, "utf-8");
1418
+ return;
1419
+ } catch (e) {
1420
+ try {
1421
+ await fs5.truncate(p, beforeSize);
1422
+ } catch {
1423
+ }
1424
+ if (attempt === MAX_ATTEMPTS) throw e;
1425
+ await new Promise((r) => setTimeout(r, 50 * attempt));
1426
+ }
1427
+ }
1428
+ };
1429
+ appendMessage = async (entry) => {
1430
+ try {
1431
+ await ensureSessionsDir(entry.sessionId);
1432
+ const { sessionId, ...rest } = entry;
1433
+ const line = { id: createUUID(), ...rest };
1434
+ const p = getTranscriptPath(sessionId);
1435
+ const payload = JSON.stringify(line) + "\n";
1436
+ if (payload.includes("\uFFFD")) {
1437
+ const fffdCount = (payload.match(/\uFFFD/g) || []).length;
1438
+ 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`);
1439
+ }
1440
+ await appendLineWithRetry(p, payload);
1441
+ } catch (e) {
1442
+ 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);
1443
+ }
1444
+ };
1445
+ appendEvent = async (sessionId, ev) => {
1446
+ if (!appConfig.transcriptEvents) return;
1447
+ try {
1448
+ await ensureSessionsDir(sessionId);
1449
+ const line = { id: createUUID(), ts: (/* @__PURE__ */ new Date()).toISOString(), ...ev };
1450
+ await appendLineWithRetry(getTranscriptPath(sessionId), JSON.stringify(line) + "\n");
1451
+ } catch (e) {
1452
+ 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);
1453
+ }
1454
+ };
1455
+ }
1456
+ });
1457
+
1196
1458
  // ../core/src/hooks/types.ts
1197
1459
  var INTERCEPTABLE_EVENTS, TOOL_EVENTS, ALL_EVENTS, DEFAULT_TIMEOUT_BY_EVENT;
1198
1460
  var init_types = __esm({
@@ -1340,12 +1602,13 @@ var init_shellExecutor = __esm({
1340
1602
  });
1341
1603
 
1342
1604
  // ../core/src/hooks/registry.ts
1343
- var rules, matches, registerHooks, listHooks, dispatch, runPreHooks, HOOK_RESULT_MAX, truncateForHook, runPostHooks;
1605
+ var rules, matches, registerHooks, listHooks, isPlainObject, dispatch, runPreHooks, HOOK_RESULT_MAX, truncateForHook, runPostHooks;
1344
1606
  var init_registry = __esm({
1345
1607
  "../core/src/hooks/registry.ts"() {
1346
1608
  "use strict";
1347
1609
  init_types();
1348
1610
  init_shellExecutor();
1611
+ init_config();
1349
1612
  rules = [];
1350
1613
  matches = (matcher, toolName) => {
1351
1614
  if (typeof matcher === "string") return matcher === "*" || matcher === toolName;
@@ -1368,6 +1631,7 @@ var init_registry = __esm({
1368
1631
  };
1369
1632
  return rules.map((r) => ({ event: r.event, matcher: strMatcher(r.matcher), source: r.source, onError: r.onError }));
1370
1633
  };
1634
+ isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
1371
1635
  dispatch = async (event, ctx) => {
1372
1636
  const interceptable = INTERCEPTABLE_EVENTS.has(event);
1373
1637
  const isToolEvent = TOOL_EVENTS.has(event);
@@ -1395,14 +1659,29 @@ var init_registry = __esm({
1395
1659
  )
1396
1660
  ));
1397
1661
  const additions2 = [];
1398
- for (const r of results) if (r?.contextAdditions) additions2.push(...r.contextAdditions);
1399
- return pack(additions2);
1662
+ let resultOverride;
1663
+ for (const r of results) {
1664
+ if (r?.contextAdditions) additions2.push(...r.contextAdditions);
1665
+ if (event === "PostToolUse" && appConfig.hookRewrite && typeof r?.resultOverride === "string" && r.resultOverride) {
1666
+ resultOverride = r.resultOverride;
1667
+ }
1668
+ }
1669
+ return { ...pack(additions2), resultOverride };
1400
1670
  }
1401
1671
  const additions = [];
1672
+ let argsOverride = void 0;
1402
1673
  for (const rule of matched) {
1403
1674
  try {
1404
1675
  const res = await rule.run(ctx);
1405
1676
  if (res?.contextAdditions) additions.push(...res.contextAdditions);
1677
+ if (event === "PreToolUse" && appConfig.hookRewrite && res?.argsOverride !== void 0) {
1678
+ if (isPlainObject(res.argsOverride)) {
1679
+ ctx.args = res.argsOverride;
1680
+ argsOverride = res.argsOverride;
1681
+ } else {
1682
+ console.warn(`\u26A0\uFE0F [hook:PreToolUse] argsOverride \u987B\u4E3A\u666E\u901A\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565`);
1683
+ }
1684
+ }
1406
1685
  if (res && res.deny) {
1407
1686
  return { deny: true, reason: res.reason, contextAdditions: additions.length > 0 ? additions : void 0 };
1408
1687
  }
@@ -1415,7 +1694,7 @@ var init_registry = __esm({
1415
1694
  console.warn(`\u26A0\uFE0F [hook:${event}] \u6267\u884C\u5F02\u5E38\uFF0C\u6309\u653E\u884C\u5904\u7406: ${msg}`);
1416
1695
  }
1417
1696
  }
1418
- return pack(additions);
1697
+ return { ...pack(additions), argsOverride };
1419
1698
  };
1420
1699
  runPreHooks = async (toolName, args, ctx) => {
1421
1700
  return dispatch("PreToolUse", {
@@ -1431,7 +1710,7 @@ var init_registry = __esm({
1431
1710
  truncateForHook = (s) => s.length <= HOOK_RESULT_MAX ? s : s.slice(0, HOOK_RESULT_MAX) + `
1432
1711
  \u2026[hook \u89C6\u56FE\u622A\u65AD\uFF0C\u5171 ${s.length} \u5B57\u7B26]`;
1433
1712
  runPostHooks = async (toolName, args, result, ctx) => {
1434
- await dispatch("PostToolUse", {
1713
+ return dispatch("PostToolUse", {
1435
1714
  sessionId: ctx.sessionId,
1436
1715
  cwd: ctx.cwd,
1437
1716
  env: ctx.env,
@@ -1475,6 +1754,7 @@ var init_truncate = __esm({
1475
1754
  init_model();
1476
1755
  init_contextCore();
1477
1756
  init_store();
1757
+ init_transcript();
1478
1758
  init_hooks();
1479
1759
  ANSI_ESCAPE = /\u001b\[[\d;?]*[ -/]*[@-~]|\u001b\][^\u0007]*(?:\u0007|\u001b\\)/g;
1480
1760
  getFileAccessRoots = () => {
@@ -1630,6 +1910,11 @@ ${line}` : line;
1630
1910
  consecutiveFailures: 0,
1631
1911
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1632
1912
  });
1913
+ await appendEvent(event.sessionId, {
1914
+ dscEvent: "compaction",
1915
+ archivedMessageCount: store.archivedMessageCount,
1916
+ summary: summaryMsg.content
1917
+ });
1633
1918
  } else if (keep > 1) {
1634
1919
  keep--;
1635
1920
  continue;
@@ -1644,6 +1929,11 @@ ${line}` : line;
1644
1929
  consecutiveFailures: 0,
1645
1930
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1646
1931
  });
1932
+ await appendEvent(event.sessionId, {
1933
+ dscEvent: "compaction",
1934
+ archivedMessageCount: store.archivedMessageCount,
1935
+ summary: summaryMsg.content
1936
+ });
1647
1937
  } else {
1648
1938
  break;
1649
1939
  }
@@ -1750,7 +2040,7 @@ ${line}` : line;
1750
2040
  });
1751
2041
 
1752
2042
  // ../core/src/tool/permissions.ts
1753
- import fs5 from "fs/promises";
2043
+ import fs6 from "fs/promises";
1754
2044
  import path8 from "path";
1755
2045
  var PRIMARY_ARG, rules2, trustedProject, globCache, globToRegex, DANGER_COMMAND_TOOLS, compileRule, readPermissionConfig, toolNameMatches, ruleMatches, initPermissions, checkPermission, listPermissionRules, buildScopedAllowRule, resolveSettingsPath, addPermissionRule;
1756
2046
  var init_permissions = __esm({
@@ -1821,7 +2111,7 @@ var init_permissions = __esm({
1821
2111
  for (const configPath of paths) {
1822
2112
  let raw;
1823
2113
  try {
1824
- raw = await fs5.readFile(configPath, "utf-8");
2114
+ raw = await fs6.readFile(configPath, "utf-8");
1825
2115
  } catch {
1826
2116
  continue;
1827
2117
  }
@@ -1918,7 +2208,7 @@ var init_permissions = __esm({
1918
2208
  const arr = Array.isArray(cfg.permissions[kind]) ? cfg.permissions[kind] : cfg.permissions[kind] = [];
1919
2209
  if (!arr.includes(ruleStr)) {
1920
2210
  arr.push(ruleStr);
1921
- await fs5.mkdir(path8.dirname(file), { recursive: true });
2211
+ await fs6.mkdir(path8.dirname(file), { recursive: true });
1922
2212
  await atomicWriteJSON(file, cfg);
1923
2213
  }
1924
2214
  if (!rules2[kind].some((r) => r.raw === compiled.raw)) rules2[kind].push(compiled);
@@ -1956,7 +2246,7 @@ var init_sessionRegistry = __esm({
1956
2246
  });
1957
2247
 
1958
2248
  // ../core/src/tool/guard.ts
1959
- import fs6 from "fs/promises";
2249
+ import fs7 from "fs/promises";
1960
2250
  import fsSync from "fs";
1961
2251
  import path9 from "path";
1962
2252
  import ignore from "ignore";
@@ -2111,9 +2401,14 @@ var init_guard = __esm({
2111
2401
  SENSITIVE_ENV_PATTERN = /(KEY|TOKEN|SECRET|PASSWORD|PASSPHRASE|CREDENTIAL|PRIVATE|AUTH)/i;
2112
2402
  scrubCommandEnv = (env = process.env) => {
2113
2403
  const next = {};
2404
+ const GIT_CONFIG_MEMBER = /^GIT_CONFIG_(KEY|VALUE)_\d+$/;
2405
+ const gitConfigFamilyHit = Object.keys(env).some(
2406
+ (k) => GIT_CONFIG_MEMBER.test(k) && (COMMAND_ENV_DENYLIST.includes(k) || SENSITIVE_ENV_PATTERN.test(k))
2407
+ );
2114
2408
  for (const [k, v] of Object.entries(env)) {
2115
2409
  if (COMMAND_ENV_DENYLIST.includes(k)) continue;
2116
2410
  if (SENSITIVE_ENV_PATTERN.test(k)) continue;
2411
+ if (gitConfigFamilyHit && (k === "GIT_CONFIG_COUNT" || GIT_CONFIG_MEMBER.test(k))) continue;
2117
2412
  next[k] = v;
2118
2413
  }
2119
2414
  return next;
@@ -2134,11 +2429,11 @@ var init_guard = __esm({
2134
2429
  };
2135
2430
  scanIgnoreFilesRecursive = async (dirPath, base, engine) => {
2136
2431
  try {
2137
- const entries = await fs6.readdir(dirPath, { withFileTypes: true });
2432
+ const entries = await fs7.readdir(dirPath, { withFileTypes: true });
2138
2433
  for (const entry of entries) {
2139
2434
  if (entry.isFile() && (entry.name === ".gitignore" || entry.name === ".agentignore")) {
2140
2435
  const fullPath = path9.join(dirPath, entry.name);
2141
- const content = await fs6.readFile(fullPath, "utf-8");
2436
+ const content = await fs7.readFile(fullPath, "utf-8");
2142
2437
  const relDir = path9.relative(base, dirPath).replace(/\\/g, "/");
2143
2438
  const rules3 = content.split(/\r?\n/).map((line) => {
2144
2439
  const trimmed = line.trim();
@@ -3035,7 +3330,7 @@ ${capped || JSON.stringify(result)}`;
3035
3330
  });
3036
3331
 
3037
3332
  // ../core/src/tool/mcp/loader.ts
3038
- import fs7 from "fs/promises";
3333
+ import fs8 from "fs/promises";
3039
3334
  import path11 from "path";
3040
3335
  import { homedir } from "os";
3041
3336
  function sanitize(s) {
@@ -3045,7 +3340,7 @@ async function readMcpConfig() {
3045
3340
  const configPath = process.env.MCP_CONFIG || path11.join(appConfig.dataDir, "mcp.json");
3046
3341
  let raw;
3047
3342
  try {
3048
- raw = await fs7.readFile(configPath, "utf-8");
3343
+ raw = await fs8.readFile(configPath, "utf-8");
3049
3344
  } catch {
3050
3345
  return {};
3051
3346
  }
@@ -3136,9 +3431,14 @@ async function loadMcpTools() {
3136
3431
  }
3137
3432
  async function initMcpTools(into) {
3138
3433
  const mcpTools = await loadMcpTools();
3139
- if (mcpTools.length > 0) {
3434
+ if (appConfig.mcpExposeMode === "dispatcher") {
3435
+ if (clients.length > 0) {
3436
+ into.push(...mcpDispatcherTools);
3437
+ 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`);
3438
+ }
3439
+ } else if (mcpTools.length > 0) {
3140
3440
  into.push(...mcpTools);
3141
- console.log(`\u{1F50C} [MCP] \u5171\u6CE8\u5165 ${mcpTools.length} \u4E2A MCP \u5DE5\u5177`);
3441
+ console.log(`\u{1F50C} [MCP] \u5171\u6CE8\u5165 ${mcpTools.length} \u4E2A MCP \u5DE5\u5177 schema\uFF08schemas \u6A21\u5F0F\uFF09`);
3142
3442
  }
3143
3443
  if (clients.some((c) => c.supportsResources)) {
3144
3444
  into.push(...mcpResourceTools);
@@ -3174,7 +3474,7 @@ async function listMcpClients() {
3174
3474
  }
3175
3475
  return out;
3176
3476
  }
3177
- var clients, expandPlaceholders, expandServerConfig, mcpResourceTools, mcpPromptTools;
3477
+ var clients, expandPlaceholders, expandServerConfig, mcpResourceTools, mcpPromptTools, resolveMcpPermissionName, mcpDispatcherTools;
3178
3478
  var init_loader = __esm({
3179
3479
  "../core/src/tool/mcp/loader.ts"() {
3180
3480
  "use strict";
@@ -3338,6 +3638,88 @@ ${text}`;
3338
3638
  }
3339
3639
  }
3340
3640
  ];
3641
+ resolveMcpPermissionName = (calledName, args) => {
3642
+ if (calledName !== "mcp_call") return calledName;
3643
+ const server = typeof args?.server === "string" ? sanitize(args.server) : "";
3644
+ const tool = typeof args?.tool === "string" ? sanitize(args.tool) : "";
3645
+ return server && tool ? `mcp__${server}__${tool}` : calledName;
3646
+ };
3647
+ mcpDispatcherTools = [
3648
+ {
3649
+ type: "function",
3650
+ function: {
3651
+ name: "mcp_list_tools",
3652
+ 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",
3653
+ parameters: {
3654
+ type: "object",
3655
+ properties: {
3656
+ server: { type: "string", description: "\u4EC5\u5217\u51FA\u8BE5 server \u7684\u5DE5\u5177\uFF08\u7F3A\u7701\u5217\u51FA\u5168\u90E8\u5DF2\u8FDE\u63A5 server\uFF09" }
3657
+ }
3658
+ },
3659
+ safetyLevel: "safe" /* SAFE */,
3660
+ isSync: true,
3661
+ async execute(args) {
3662
+ const server = args?.server;
3663
+ const lines = [];
3664
+ for (const c of clients) {
3665
+ if (server && c.serverName !== server) continue;
3666
+ try {
3667
+ const ts = await c.listTools();
3668
+ for (const t of ts) {
3669
+ const props = t?.inputSchema?.properties && typeof t.inputSchema.properties === "object" ? Object.keys(t.inputSchema.properties) : [];
3670
+ const required = Array.isArray(t?.inputSchema?.required) ? t.inputSchema.required : [];
3671
+ const argList = props.map((p) => required.includes(p) ? `${p}*` : p).join(", ");
3672
+ lines.push(`- [${c.serverName}] ${t.name}${t.description ? `\uFF1A${String(t.description).split("\n")[0]}` : ""}${argList ? `\uFF08\u53C2\u6570\uFF1A${argList}\uFF09` : ""}`);
3673
+ }
3674
+ } catch (e) {
3675
+ console.warn(`\u26A0\uFE0F [MCP] listTools "${c.serverName}" \u5931\u8D25\uFF08\u5DF2\u8DF3\u8FC7\uFF09: ${e?.message ?? e}`);
3676
+ }
3677
+ }
3678
+ if (lines.length === 0) return server ? `(server "${server}" \u672A\u8FDE\u63A5\u6216\u65E0\u5DE5\u5177)` : "(\u65E0\u5DF2\u8FDE\u63A5\u7684 MCP server)";
3679
+ return `[MCP tools]
3680
+ ${lines.join("\n")}`;
3681
+ }
3682
+ }
3683
+ },
3684
+ {
3685
+ type: "function",
3686
+ function: {
3687
+ name: "mcp_call",
3688
+ 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',
3689
+ parameters: {
3690
+ type: "object",
3691
+ properties: {
3692
+ server: { type: "string", description: "MCP server \u540D\uFF08\u89C1 mcp_list_tools \u8F93\u51FA\uFF09" },
3693
+ tool: { type: "string", description: "\u5DE5\u5177\u540D\uFF08\u89C1 mcp_list_tools \u8F93\u51FA\uFF0C\u7528\u539F\u540D\uFF09" },
3694
+ args: { type: "string", description: '\u5DE5\u5177\u53C2\u6570\u7684 JSON \u5B57\u7B26\u4E32\uFF0C\u5982 {"query":"..."}\uFF1B\u65E0\u53C2\u6570\u4F20 "{}"' }
3695
+ },
3696
+ required: ["server", "tool"]
3697
+ },
3698
+ safetyLevel: "danger" /* DANGER */,
3699
+ isSync: true,
3700
+ requireApproval: (args) => `\u26A0\uFE0F\u3010MCP \u5DE5\u5177\u5BA1\u6279\u3011
3701
+ \u670D\u52A1: ${args?.server} / \u5DE5\u5177: ${args?.tool}
3702
+ \u53C2\u6570: ${args?.args}`,
3703
+ async execute(args) {
3704
+ const { server, tool } = args ?? {};
3705
+ if (!server || !tool) return "\u274C [mcp_call] \u7F3A\u5C11 server \u6216 tool\uFF08\u5148\u7528 mcp_list_tools \u67E5\u76EE\u5F55\uFF09\u3002";
3706
+ let parsed = {};
3707
+ if (typeof args.args === "string" && args.args.trim() && args.args.trim() !== "{}") {
3708
+ try {
3709
+ parsed = JSON.parse(args.args);
3710
+ } catch {
3711
+ 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`;
3712
+ }
3713
+ }
3714
+ const c = findClient(server);
3715
+ 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`;
3716
+ const result = await c.callTool(tool, parsed);
3717
+ return `[MCP ${server}/${tool}]
3718
+ ${result}`;
3719
+ }
3720
+ }
3721
+ }
3722
+ ];
3341
3723
  }
3342
3724
  });
3343
3725
 
@@ -3373,68 +3755,44 @@ var init_httpExecutor = __esm({
3373
3755
  }
3374
3756
  });
3375
3757
 
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"() {
3758
+ // ../core/src/agent/inbox.ts
3759
+ var INBOX_MAX_ITEMS, sessionInboxes, pushSessionInbox, claimSessionInbox, flushLeftoverToTranscript;
3760
+ var init_inbox = __esm({
3761
+ "../core/src/agent/inbox.ts"() {
3381
3762
  "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;
3763
+ init_transcript();
3764
+ init_config();
3765
+ INBOX_MAX_ITEMS = 50;
3766
+ sessionInboxes = /* @__PURE__ */ new Map();
3767
+ pushSessionInbox = (sessionId, content) => {
3768
+ if (!appConfig.inboxSteering) return false;
3769
+ const text = (content ?? "").trim();
3770
+ if (!text) return false;
3771
+ const q = sessionInboxes.get(sessionId) ?? [];
3772
+ if (q.length >= INBOX_MAX_ITEMS) {
3773
+ console.warn(`[inbox] \u4F1A\u8BDD ${sessionId} \u961F\u5217\u5DF2\u6EE1\uFF08${INBOX_MAX_ITEMS}\uFF09\uFF0C\u4E22\u5F03\u65B0\u8F93\u5165`);
3774
+ return false;
3403
3775
  }
3776
+ q.push(text);
3777
+ sessionInboxes.set(sessionId, q);
3778
+ return true;
3404
3779
  };
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);
3780
+ claimSessionInbox = (sessionId) => {
3781
+ if (!appConfig.inboxSteering) return [];
3782
+ const q = sessionInboxes.get(sessionId);
3783
+ if (!q || q.length === 0) return [];
3784
+ sessionInboxes.delete(sessionId);
3785
+ return q;
3786
+ };
3787
+ flushLeftoverToTranscript = async (sessionId) => {
3788
+ const texts = claimSessionInbox(sessionId);
3789
+ for (const t of texts) {
3790
+ await appendMessage({ sessionId, role: "user", content: t });
3437
3791
  }
3792
+ if (texts.length > 0) {
3793
+ 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`);
3794
+ }
3795
+ return texts.length;
3438
3796
  };
3439
3797
  }
3440
3798
  });
@@ -3550,7 +3908,6 @@ var init_planMode = __esm({
3550
3908
  "../core/src/agent/planMode.ts"() {
3551
3909
  "use strict";
3552
3910
  PLAN_ALLOWED_TOOLS = /* @__PURE__ */ new Set([
3553
- "getTime",
3554
3911
  "read_file",
3555
3912
  "list_dir",
3556
3913
  "view_symbol_outline",
@@ -3561,6 +3918,9 @@ var init_planMode = __esm({
3561
3918
  "git_status",
3562
3919
  "git_log",
3563
3920
  "inspect_dependencies",
3921
+ "get_diagnostics",
3922
+ "goto_definition",
3923
+ // ★ P0-A 补漏:LSP 导航/诊断恰是调研阶段最需要的(原白名单漂移漏登记,schema 档与 runtime 档共用此表)
3564
3924
  "web_fetch",
3565
3925
  "web_search"
3566
3926
  // 只读研究类(虽为 DANGER 但不写本地状态;仍走各自审批)
@@ -3738,7 +4098,7 @@ var init_inject2 = __esm({
3738
4098
  init_registry4();
3739
4099
  AGENT_CATALOG_MARKER = "\u3010\u53EF\u7528\u5B50 Agent \u76EE\u5F55\u3011";
3740
4100
  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';
4101
+ 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
4102
  injectAgentCatalog = (message) => {
3743
4103
  const catalog = getAgentCatalog();
3744
4104
  if (!catalog) return;
@@ -3894,6 +4254,7 @@ var prepareToolsAndInjections;
3894
4254
  var init_systemInjections = __esm({
3895
4255
  "../core/src/agent/systemInjections.ts"() {
3896
4256
  "use strict";
4257
+ init_config();
3897
4258
  init_planMode();
3898
4259
  init_toolFilter();
3899
4260
  init_truncate();
@@ -3905,7 +4266,7 @@ var init_systemInjections = __esm({
3905
4266
  init_common();
3906
4267
  prepareToolsAndInjections = async (message, options, events) => {
3907
4268
  const { toolSchemas, planMode, locale: locale2, outputStyle } = options;
3908
- const rawToolsPreEnv = planMode ? filterToolsForPlanMode(toolSchemas ?? []) : appendPlanControlTools(toolSchemas ?? []);
4269
+ const rawToolsPreEnv = planMode && appConfig.planEnforcement === "schema" ? filterToolsForPlanMode(toolSchemas ?? []) : appendPlanControlTools(toolSchemas ?? []);
3909
4270
  const validationCtx = {
3910
4271
  sessionId: options.sessionId,
3911
4272
  cwd: options.cwd ?? process.cwd(),
@@ -3940,36 +4301,6 @@ var init_systemInjections = __esm({
3940
4301
  }
3941
4302
  });
3942
4303
 
3943
- // ../core/src/tool/registry/system.ts
3944
- var systemTools;
3945
- var init_system = __esm({
3946
- "../core/src/tool/registry/system.ts"() {
3947
- "use strict";
3948
- init_type();
3949
- systemTools = [
3950
- {
3951
- type: "function",
3952
- function: {
3953
- name: "getTime",
3954
- description: "\u8FD4\u56DE\u5F53\u524D\u670D\u52A1\u5668\u7684\u65E5\u671F\u4E0E\u65F6\u95F4\uFF08\u672C\u5730\u65F6\u533A\uFF09",
3955
- parameters: {
3956
- type: "object",
3957
- properties: {
3958
- // Q-7:location 改为可选——旧版 required 却完全不消费,迫使模型填无意义城市名。
3959
- location: { type: "string", description: "\u57CE\u5E02\u540D\u79F0\uFF08\u53EF\u9009\uFF0C\u4EC5\u4F5C\u8BB0\u5F55\uFF1B\u670D\u52A1\u5668\u59CB\u7EC8\u8FD4\u56DE\u5176\u672C\u5730\u65F6\u95F4\uFF09" }
3960
- }
3961
- },
3962
- safetyLevel: "safe" /* SAFE */,
3963
- isSync: true,
3964
- async execute() {
3965
- return (/* @__PURE__ */ new Date()).toLocaleString("zh-CN", { dateStyle: "medium", timeStyle: "medium" });
3966
- }
3967
- }
3968
- }
3969
- ];
3970
- }
3971
- });
3972
-
3973
4304
  // ../core/src/tool/registry/agent.ts
3974
4305
  var createAgentTools;
3975
4306
  var init_agent = __esm({
@@ -4564,7 +4895,7 @@ import fs12 from "fs/promises";
4564
4895
  import path15 from "path";
4565
4896
  import { createReadStream } from "fs";
4566
4897
  import * as readline from "readline";
4567
- var isSensitiveReadTarget, stripReadFileLineNumbers, maskSecretsInContent, makeTmpPath, ATOMIC_TMP_RE, sweepStaleAtomicTmp, assertReadable, stripEdgeBlankLines, matchLineBlockWith, diagnoseOldStr, reindentToBase, fsTools;
4898
+ var isSensitiveReadTarget, stripReadFileLineNumbers, maskSecretsInContent, makeTmpPath, ATOMIC_TMP_RE, sweepStaleAtomicTmp, assertReadable, stripEdgeBlankLines, matchLineBlockWith, diagnoseOldStr, reindentToBase, alignIndentsByAnchor, applyOneEditToContent, fsTools;
4568
4899
  var init_fs = __esm({
4569
4900
  "../core/src/tool/registry/fs.ts"() {
4570
4901
  "use strict";
@@ -4652,7 +4983,7 @@ var init_fs = __esm({
4652
4983
  while (lines.length > 1 && lines[lines.length - 1].trim() === "") lines.pop();
4653
4984
  return lines.join("\n");
4654
4985
  };
4655
- matchLineBlockWith = (content, oldBlock, newBlock, replaceAll, norm) => {
4986
+ matchLineBlockWith = (content, oldBlock, newBlock, replaceAll, norm, anchorAlign) => {
4656
4987
  const contentLines = content.split("\n");
4657
4988
  const oldLines = oldBlock.split("\n");
4658
4989
  if (oldLines.length === 0 || oldLines.length > contentLines.length) {
@@ -4681,7 +5012,11 @@ var init_fs = __esm({
4681
5012
  break;
4682
5013
  }
4683
5014
  }
4684
- const aligned = baseIndent ? reindentToBase(newBlock, baseIndent) : newBlock;
5015
+ let aligned = baseIndent ? reindentToBase(newBlock, baseIndent) : newBlock;
5016
+ if (anchorAlign) {
5017
+ const anchored = alignIndentsByAnchor(newBlock, oldLines, contentLines, start);
5018
+ if (anchored !== null) aligned = anchored;
5019
+ }
4685
5020
  out.splice(start, oldLines.length, ...aligned.split("\n"));
4686
5021
  }
4687
5022
  return { ok: true, content: out.join("\n"), count: hits.length };
@@ -4716,6 +5051,92 @@ var init_fs = __esm({
4716
5051
  return baseIndent + (l.startsWith(common) ? l.slice(common.length) : l);
4717
5052
  }).join("\n");
4718
5053
  };
5054
+ alignIndentsByAnchor = (newBlock, oldLines, contentLines, start) => {
5055
+ const stripLead = (l) => l.replace(/^[ \t]+/, "");
5056
+ const out = [];
5057
+ let cursor = 0;
5058
+ let lastIndent = null;
5059
+ let anchors = 0;
5060
+ for (const line of newBlock.split("\n")) {
5061
+ if (line.trim() === "") {
5062
+ out.push(line);
5063
+ continue;
5064
+ }
5065
+ const bare = stripLead(line);
5066
+ let idx = -1;
5067
+ for (let i = cursor; i < oldLines.length; i++) {
5068
+ if (oldLines[i].trim() !== "" && stripLead(oldLines[i]) === bare) {
5069
+ idx = i;
5070
+ break;
5071
+ }
5072
+ }
5073
+ if (idx === -1) {
5074
+ out.push(lastIndent === null ? line : lastIndent + bare);
5075
+ continue;
5076
+ }
5077
+ cursor = idx + 1;
5078
+ lastIndent = contentLines[start + idx].match(/^[ \t]*/)?.[0] ?? "";
5079
+ anchors++;
5080
+ out.push(lastIndent + bare);
5081
+ }
5082
+ return anchors > 0 ? out.join("\n") : null;
5083
+ };
5084
+ applyOneEditToContent = (normalizedContent, oldStr, newStr, replaceAll) => {
5085
+ const normalizedNew = newStr.replace(/\r\n/g, "\n");
5086
+ const normalizedOldRaw = oldStr.replace(/\r\n/g, "\n");
5087
+ const lnStripped = stripReadFileLineNumbers(normalizedOldRaw);
5088
+ const candidates = [
5089
+ { old: normalizedOldRaw, note: "" },
5090
+ { old: stripEdgeBlankLines(normalizedOldRaw), note: "\uFF08\u5DF2\u5BB9\u9519\u9996\u5C3E\u7A7A\u884C\uFF09" }
5091
+ ];
5092
+ if (lnStripped !== normalizedOldRaw) candidates.push({ old: lnStripped, note: "\uFF08\u5DF2\u5265\u79BB read_file \u884C\u53F7\u524D\u7F00\uFF09" });
5093
+ let normalizedOld = null;
5094
+ let note = "";
5095
+ for (const c of candidates) {
5096
+ if (c.old.length > 0 && normalizedContent.includes(c.old)) {
5097
+ normalizedOld = c.old;
5098
+ note = c.note;
5099
+ break;
5100
+ }
5101
+ }
5102
+ if (normalizedOld == null) {
5103
+ const base = lnStripped !== normalizedOldRaw ? lnStripped : normalizedOldRaw;
5104
+ const norms = [
5105
+ // 行尾空白归一:old_str 前导缩进与文件一致(可信)→ 只对齐基线(anchor=false)
5106
+ { norm: (l) => l.replace(/[ \t]+$/, ""), tag: "\u9010\u884C\u5C3E\u7A7A\u767D\u5BB9\u9519", anchor: false },
5107
+ // 全空白归一:模型前导缩进已证明不符(不可信)→ 逐行锚定文件真实缩进(anchor=true)
5108
+ { norm: (l) => l.trim(), tag: "\u9010\u884C\u5168\u7A7A\u767D\u5BB9\u9519\uFF08\u524D\u5BFC Tab/\u7A7A\u683C\uFF09", anchor: true }
5109
+ ];
5110
+ for (const { norm, tag, anchor } of norms) {
5111
+ const lm = matchLineBlockWith(normalizedContent, base, normalizedNew, replaceAll, norm, anchor);
5112
+ if (lm.ok) {
5113
+ return {
5114
+ ok: true,
5115
+ content: lm.content,
5116
+ summary: replaceAll ? `\u5DF2\u6279\u91CF\u66FF\u6362\u5168\u90E8 ${lm.count} \u5904\u5339\u914D\uFF08${tag}\uFF09\u3002` : `\u5DF2\u6210\u529F\u5B8C\u6210\u5C40\u90E8\u91CD\u6784\uFF08${tag}\uFF09\u3002`
5117
+ };
5118
+ }
5119
+ if (lm.reason === "conflict") {
5120
+ return { ok: false, error: `\u274C [\u4EE3\u7801\u4FEE\u8865\u5931\u8D25]\uFF1A\u4EE3\u7801\u51B2\u7A81\uFF01old_str\uFF08${tag}\u5F52\u4E00\u540E\uFF09\u5728\u5168\u6587\u4E2D\u4E0D\u552F\u4E00\uFF08\u5171 ${lm.count} \u5904\uFF09\u3002\u8BF7\u591A\u5305\u88F9\u51E0\u884C\u4E0A\u4E0B\u6587\uFF0C\u6216\u663E\u5F0F\u8BBE replace_all=true \u6279\u91CF\u66FF\u6362\u3002` };
5121
+ }
5122
+ }
5123
+ const diag = diagnoseOldStr(normalizedContent, normalizedOldRaw);
5124
+ return { ok: false, error: `\u274C [\u4EE3\u7801\u4FEE\u8865\u5931\u8D25]\uFF1A\u672A\u80FD\u5728\u6587\u4EF6\u4E2D\u627E\u5230\u6307\u5B9A\u7684 old_str \u65E7\u4EE3\u7801\u5757\u3002${diag}\u5E38\u89C1\u539F\u56E0\uFF1A\u2460 \u8BEF\u5E26\u4E86 read_file \u7684\u300C<\u884C\u53F7>: \u300D\u524D\u7F00\uFF1B\u2461 \u7F29\u8FDB\uFF08Tab/\u7A7A\u683C\uFF09\u4E0D\u4E00\u81F4\uFF1B\u2462 \u884C\u5C3E\u591A\u4F59\u7A7A\u683C\uFF1B\u2463 \u6587\u4EF6\u5DF2\u88AB\u6539\u52A8/old_str \u975E\u8FDE\u7EED\u6574\u6BB5\u3002\u8BF7\u7528 read_file \u91CD\u65B0\u6838\u5BF9\u8BE5\u5904\u7247\u6BB5\uFF08\u53BB\u6389\u300C<\u884C\u53F7>: \u300D\u524D\u7F00\u3001\u4FDD\u7559\u539F\u59CB Tab/\u7A7A\u683C\u7F29\u8FDB\uFF09\u540E\u91CD\u8BD5\u3002\u26A0\uFE0F \u4E25\u7981\u6539\u7528 run_command \u8C03\u7528 python/node/sed/awk \u7B49\u811A\u672C\u7ED5\u8FC7\u672C\u5DE5\u5177\u4FEE\u6539\u6587\u4EF6\u3002` };
5125
+ }
5126
+ const matchCount = normalizedContent.split(normalizedOld).length - 1;
5127
+ if (!replaceAll && matchCount > 1) {
5128
+ return { ok: false, error: `\u274C [\u4EE3\u7801\u4FEE\u8865\u5931\u8D25]\uFF1A\u4EE3\u7801\u51B2\u7A81\uFF01old_str \u5728\u5168\u6587\u4E2D\u4E0D\u552F\u4E00\uFF08\u5171\u53D1\u73B0\u4E86 ${matchCount} \u5904\uFF09\u3002\u8BF7\u5411\u4E0A\u6216\u5411\u4E0B\u591A\u5305\u88F9\u51E0\u884C\u4E0A\u4E0B\u6587\u518D\u63D0\u8BF7\u4FEE\u6539\uFF0C\u6216\u663E\u5F0F\u8BBE replace_all=true \u6279\u91CF\u66FF\u6362\u3002` };
5129
+ }
5130
+ const oldFirstNonBlank = normalizedOld.split("\n").find((l) => l.trim() !== "") ?? "";
5131
+ const baseIndent = oldFirstNonBlank.match(/^[ \t]*/)?.[0] ?? "";
5132
+ const alignedNew = baseIndent ? reindentToBase(normalizedNew, baseIndent) : normalizedNew;
5133
+ const updatedContent = replaceAll ? normalizedContent.split(normalizedOld).join(alignedNew) : normalizedContent.replace(normalizedOld, () => alignedNew);
5134
+ return {
5135
+ ok: true,
5136
+ content: updatedContent,
5137
+ summary: replaceAll ? `\u5DF2\u6279\u91CF\u66FF\u6362\u5168\u90E8 ${matchCount} \u5904\u5339\u914D\u3002${note}` : `\u5DF2\u6210\u529F\u5B8C\u6210\u552F\u4E00\u6027\u5C40\u90E8\u91CD\u6784\u3002${note}`
5138
+ };
5139
+ };
4719
5140
  fsTools = [
4720
5141
  {
4721
5142
  type: "function",
@@ -4847,78 +5268,65 @@ ${treeResult}`;
4847
5268
  type: "function",
4848
5269
  function: {
4849
5270
  name: "edit_file",
4850
- description: "\u9488\u5BF9\u6307\u5B9A\u7684\u6587\u4EF6\u8FDB\u884C\u5C40\u90E8\u7CBE\u51C6\u4FEE\u6539\u3002old_str \u5FC5\u987B\u4E0E\u6587\u4EF6\u4E2D\u7684\u539F\u59CB\u4EE3\u7801\u9010\u5B57\u7B26\u4E00\u81F4\uFF1A\u8BF7\u53BB\u6389 read_file \u8FD4\u56DE\u7684\u300C<\u884C\u53F7>: \u300D\u524D\u7F00\uFF0C\u5E76\u4FDD\u7559\u539F\u6709\u7F29\u8FDB\uFF08Tab/\u7A7A\u683C\uFF09\u4E0E\u884C\u5C3E\u7A7A\u767D\uFF1B\u9ED8\u8BA4\u8981\u6C42\u5728\u5168\u6587\u4E2D\u552F\u4E00\uFF0C\u82E5\u8BBE replace_all=true \u5219\u66FF\u6362\u5168\u90E8\u5339\u914D\u5904\uFF08\u9002\u5408\u6279\u91CF\u91CD\u547D\u540D/\u7EDF\u4E00\u6539\u5199\uFF09\u3002",
5271
+ description: "\u9488\u5BF9\u6307\u5B9A\u7684\u6587\u4EF6\u8FDB\u884C\u5C40\u90E8\u7CBE\u51C6\u4FEE\u6539\u3002\u2605 \u540C\u4E00\u6587\u4EF6\u6709\u591A\u5904\u8981\u6539\u65F6\uFF0C\u52A1\u5FC5\u4E00\u6B21\u8C03\u7528\u4F20 edits \u6570\u7EC4\u5168\u90E8\u5B8C\u6210\uFF08\u6309\u5E8F\u5E94\u7528\u3001\u6574\u4F53\u539F\u5B50\uFF1A\u4EFB\u4E00\u5904\u5931\u8D25\u5219\u5168\u90E8\u4E0D\u5199\u5165\uFF09\uFF0C\u52FF\u9010\u5904\u591A\u6B21\u8C03\u7528\u3002old_str \u5FC5\u987B\u4E0E\u6587\u4EF6\u4E2D\u7684\u539F\u59CB\u4EE3\u7801\u9010\u5B57\u7B26\u4E00\u81F4\uFF1A\u8BF7\u53BB\u6389 read_file \u8FD4\u56DE\u7684\u300C<\u884C\u53F7>: \u300D\u524D\u7F00\uFF0C\u5E76\u4FDD\u7559\u539F\u6709\u7F29\u8FDB\uFF08Tab/\u7A7A\u683C\uFF09\u4E0E\u884C\u5C3E\u7A7A\u767D\uFF1B\u9ED8\u8BA4\u8981\u6C42\u5728\u5168\u6587\u4E2D\u552F\u4E00\uFF0C\u82E5\u8BBE replace_all=true \u5219\u66FF\u6362\u5168\u90E8\u5339\u914D\u5904\uFF08\u9002\u5408\u6279\u91CF\u91CD\u547D\u540D/\u7EDF\u4E00\u6539\u5199\uFF09\u3002",
4851
5272
  parameters: {
4852
5273
  type: "object",
4853
5274
  properties: {
4854
5275
  path: { type: "string", description: "\u51C6\u5907\u4FEE\u6539\u7684\u6587\u4EF6\u76F8\u5BF9\u8DEF\u5F84" },
4855
- old_str: { type: "string", description: "\u6587\u4EF6\u4E2D\u73B0\u6709\u7684\u5B8C\u6574\u65E7\u4EE3\u7801\u5757" },
4856
- new_str: { type: "string", description: "\u51C6\u5907\u66FF\u6362\u8FDB\u53BB\u7684\u65B0\u4EE3\u7801\u5757" },
4857
- replace_all: { type: "boolean", description: "\u662F\u5426\u66FF\u6362\u5168\u6587\u6240\u6709\u5339\u914D\u5904\uFF08\u9ED8\u8BA4 false \u4EC5\u66FF\u6362\u552F\u4E00\u5339\u914D\uFF1B\u6279\u91CF\u91CD\u547D\u540D/\u7EDF\u4E00\u6539\u5199\u65F6\u8BBE true\uFF09" }
5276
+ old_str: { type: "string", description: "\u6587\u4EF6\u4E2D\u73B0\u6709\u7684\u5B8C\u6574\u65E7\u4EE3\u7801\u5757\uFF08\u5355\u5904\u4FEE\u6539\u7528\uFF1B\u591A\u5904\u4FEE\u6539\u8BF7\u6539\u7528 edits \u6570\u7EC4\u4E00\u6B21\u5B8C\u6210\uFF09" },
5277
+ new_str: { type: "string", description: "\u51C6\u5907\u66FF\u6362\u8FDB\u53BB\u7684\u65B0\u4EE3\u7801\u5757\uFF08\u5355\u5904\u4FEE\u6539\u7528\uFF09" },
5278
+ replace_all: { type: "boolean", description: "\u662F\u5426\u66FF\u6362\u5168\u6587\u6240\u6709\u5339\u914D\u5904\uFF08\u9ED8\u8BA4 false \u4EC5\u66FF\u6362\u552F\u4E00\u5339\u914D\uFF1B\u6279\u91CF\u91CD\u547D\u540D/\u7EDF\u4E00\u6539\u5199\u65F6\u8BBE true\uFF09\u3002\u9876\u5C42\u53C2\u6570\u4EC5\u4F5C\u7528\u4E8E\u5355\u6761 old_str/new_str\uFF1Bedits \u5185\u6BCF\u6761\u53EF\u72EC\u7ACB\u8BBE\u7F6E" },
5279
+ edits: {
5280
+ type: "array",
5281
+ description: "\u2605 \u540C\u4E00\u6587\u4EF6\u591A\u5904\u4FEE\u6539\u65F6\u4F18\u5148\u4F7F\u7528\uFF1A\u4E00\u6B21\u8C03\u7528\u6309\u5E8F\u5B8C\u6210\u5168\u90E8\u66FF\u6362\uFF0C\u7701\u53BB\u9010\u5904\u5F80\u8FD4\u3002\u7B2C N \u6761\u7684 old_str \u987B\u57FA\u4E8E\u524D N-1 \u6761\u5E94\u7528\u540E\u7684\u6587\u4EF6\u5185\u5BB9\u6784\u9020\uFF1B\u4EFB\u4E00\u6761\u672A\u547D\u4E2D\u5219\u6574\u4F53\u4E0D\u5199\u5165\uFF08\u539F\u5B50\uFF09\uFF0C\u4FEE\u6B63\u8BE5\u6761\u540E\u6574\u7EC4\u91CD\u8BD5",
5282
+ items: {
5283
+ type: "object",
5284
+ properties: {
5285
+ old_str: { type: "string", description: "\u6587\u4EF6\u4E2D\u73B0\u6709\u7684\u5B8C\u6574\u65E7\u4EE3\u7801\u5757" },
5286
+ new_str: { type: "string", description: "\u51C6\u5907\u66FF\u6362\u8FDB\u53BB\u7684\u65B0\u4EE3\u7801\u5757" },
5287
+ replace_all: { type: "boolean", description: "\u8BE5\u6761\u662F\u5426\u66FF\u6362\u5168\u6587\u6240\u6709\u5339\u914D\u5904\uFF08\u9ED8\u8BA4 false\uFF09" }
5288
+ },
5289
+ required: ["old_str", "new_str"]
5290
+ }
5291
+ }
4858
5292
  },
4859
- required: ["path", "old_str", "new_str"]
5293
+ required: ["path"]
4860
5294
  },
4861
5295
  safetyLevel: "mutation" /* MUTATION */,
4862
5296
  isSync: true,
4863
- requireApproval: (args) => `\u7533\u8BF7\u4FEE\u6539\u6587\u4EF6 [${args.path}]${args.replace_all ? "\uFF08\u{1F525} \u6279\u91CF\u66FF\u6362\u5168\u90E8\u5339\u914D\u5904\uFF09" : ""}
5297
+ requireApproval: (args) => {
5298
+ const list = args.edits && args.edits.length > 0 ? args.edits : [{ old_str: args.old_str ?? "", new_str: args.new_str ?? "", replace_all: args.replace_all }];
5299
+ const patches = list.map((e, i) => `\u2014\u2014 \u7B2C ${i + 1} \u5904${e.replace_all ? "\uFF08\u{1F525} \u6279\u91CF\u66FF\u6362\u5168\u90E8\u5339\u914D\u5904\uFF09" : ""} \u2014\u2014
4864
5300
  \u3010\u51CF\u5C11\u3011:
4865
- ${args.old_str}
5301
+ ${e.old_str}
4866
5302
  \u3010\u589E\u52A0\u3011:
4867
- ${args.new_str}`,
5303
+ ${e.new_str}`).join("\n\n");
5304
+ return `\u7533\u8BF7\u4FEE\u6539\u6587\u4EF6 [${args.path}]\uFF08\u5171 ${list.length} \u5904\u4FEE\u6539\uFF0C\u4EFB\u4E00\u5904\u5931\u8D25\u5C06\u6574\u4F53\u4E0D\u5199\u5165\uFF09
5305
+ ${patches}`;
5306
+ },
4868
5307
  async execute(args) {
4869
5308
  try {
4870
- const absPath = resolveSafePath(args.path);
4871
- const rawContent = await fs12.readFile(absPath, "utf-8");
4872
- const isCRLF = rawContent.includes("\r\n");
4873
- const normalizedContent = rawContent.replace(/\r\n/g, "\n");
4874
- const normalizedNew = args.new_str.replace(/\r\n/g, "\n");
4875
- const normalizedOldRaw = args.old_str.replace(/\r\n/g, "\n");
4876
- const lnStripped = stripReadFileLineNumbers(normalizedOldRaw);
4877
- const candidates = [
4878
- { old: normalizedOldRaw, note: "" },
4879
- { old: stripEdgeBlankLines(normalizedOldRaw), note: "\uFF08\u5DF2\u5BB9\u9519\u9996\u5C3E\u7A7A\u884C\uFF09" }
4880
- ];
4881
- if (lnStripped !== normalizedOldRaw) candidates.push({ old: lnStripped, note: "\uFF08\u5DF2\u5265\u79BB read_file \u884C\u53F7\u524D\u7F00\uFF09" });
4882
- let normalizedOld = null;
4883
- let note = "";
4884
- for (const c of candidates) {
4885
- if (c.old.length > 0 && normalizedContent.includes(c.old)) {
4886
- normalizedOld = c.old;
4887
- note = c.note;
4888
- break;
4889
- }
5309
+ const editList = args.edits && args.edits.length > 0 ? args.edits : [{ old_str: args.old_str ?? "", new_str: args.new_str ?? "", replace_all: args.replace_all }];
5310
+ if (editList.length === 0 || editList.some((e) => typeof e.old_str !== "string" || e.old_str.length === 0)) {
5311
+ return `\u274C [\u53C2\u6570\u7F3A\u5931]\uFF1A\u8BF7\u4F20\u5165 edits \u6570\u7EC4\uFF08\u6BCF\u6761\u542B old_str/new_str\uFF09\u4E00\u6B21\u5B8C\u6210\u591A\u5904\u4FEE\u6539\uFF0C\u6216\u9876\u5C42 old_str + new_str \u4FEE\u6539\u5355\u5904\u3002old_str \u4E0D\u80FD\u4E3A\u7A7A\u3002`;
4890
5312
  }
4891
- if (normalizedOld == null) {
4892
- const base = lnStripped !== normalizedOldRaw ? lnStripped : normalizedOldRaw;
4893
- const norms = [
4894
- { norm: (l) => l.replace(/[ \t]+$/, ""), tag: "\u9010\u884C\u5C3E\u7A7A\u767D\u5BB9\u9519" },
4895
- { norm: (l) => l.trim(), tag: "\u9010\u884C\u5168\u7A7A\u767D\u5BB9\u9519\uFF08\u524D\u5BFC Tab/\u7A7A\u683C\uFF09" }
4896
- ];
4897
- for (const { norm, tag } of norms) {
4898
- const lm = matchLineBlockWith(normalizedContent, base, normalizedNew, !!args.replace_all, norm);
4899
- if (lm.ok) {
4900
- assertWithinWorkspace(absPath);
4901
- await fs12.writeFile(absPath, isCRLF ? lm.content.replace(/\n/g, "\r\n") : lm.content, "utf-8");
4902
- return args.replace_all ? `\u2705 [\u4EE3\u7801\u4FEE\u8865\u6210\u529F]\uFF1A\u6587\u4EF6 [${args.path}] \u5DF2\u6279\u91CF\u66FF\u6362\u5168\u90E8 ${lm.count} \u5904\u5339\u914D\uFF08${tag}\uFF09\u3002` : `\u2705 [\u4EE3\u7801\u4FEE\u8865\u6210\u529F]\uFF1A\u6587\u4EF6 [${args.path}] \u5DF2\u6210\u529F\u5B8C\u6210\u5C40\u90E8\u91CD\u6784\uFF08${tag}\uFF09\u3002`;
4903
- }
4904
- if (lm.reason === "conflict") {
4905
- return `\u274C [\u4EE3\u7801\u4FEE\u8865\u5931\u8D25]\uFF1A\u4EE3\u7801\u51B2\u7A81\uFF01old_str\uFF08${tag}\u5F52\u4E00\u540E\uFF09\u5728\u5168\u6587\u4E2D\u4E0D\u552F\u4E00\uFF08\u5171 ${lm.count} \u5904\uFF09\u3002\u8BF7\u591A\u5305\u88F9\u51E0\u884C\u4E0A\u4E0B\u6587\uFF0C\u6216\u663E\u5F0F\u8BBE replace_all=true \u6279\u91CF\u66FF\u6362\u3002`;
4906
- }
5313
+ const absPath = resolveSafePath(args.path);
5314
+ const rawContent = await fs12.readFile(absPath, "utf-8");
5315
+ const isCRLF = rawContent.includes("\r\n");
5316
+ let content = rawContent.replace(/\r\n/g, "\n");
5317
+ const summaries = [];
5318
+ for (let i = 0; i < editList.length; i++) {
5319
+ const r = applyOneEditToContent(content, editList[i].old_str, editList[i].new_str ?? "", !!editList[i].replace_all);
5320
+ if (!r.ok) {
5321
+ return editList.length === 1 ? r.error : `\u274C [\u4EE3\u7801\u4FEE\u8865\u5931\u8D25]\uFF1Aedits \u7B2C ${i + 1}/${editList.length} \u6761\u672A\u547D\u4E2D\uFF0C\u5DF2\u6574\u4F53\u653E\u5F03\uFF08\u6587\u4EF6\u672A\u505A\u4EFB\u4F55\u6539\u52A8\uFF0C\u53EF\u4FEE\u6B63\u8BE5\u6761\u540E\u6574\u7EC4\u91CD\u8BD5\uFF09\u3002${r.error.replace(/^❌ \[代码修补失败\]:/, "")}`;
4907
5322
  }
4908
- const diag = diagnoseOldStr(normalizedContent, normalizedOldRaw);
4909
- return `\u274C [\u4EE3\u7801\u4FEE\u8865\u5931\u8D25]\uFF1A\u672A\u80FD\u5728\u6587\u4EF6\u4E2D\u627E\u5230\u6307\u5B9A\u7684 old_str \u65E7\u4EE3\u7801\u5757\u3002${diag}\u5E38\u89C1\u539F\u56E0\uFF1A\u2460 \u8BEF\u5E26\u4E86 read_file \u7684\u300C<\u884C\u53F7>: \u300D\u524D\u7F00\uFF1B\u2461 \u7F29\u8FDB\uFF08Tab/\u7A7A\u683C\uFF09\u4E0D\u4E00\u81F4\uFF1B\u2462 \u884C\u5C3E\u591A\u4F59\u7A7A\u683C\uFF1B\u2463 \u6587\u4EF6\u5DF2\u88AB\u6539\u52A8/old_str \u975E\u8FDE\u7EED\u6574\u6BB5\u3002\u8BF7\u7528 read_file \u91CD\u65B0\u6838\u5BF9\u5E94\u8D34\u7247\u6BB5\u3002\u26A0\uFE0F \u4E25\u7981\u6539\u7528 run_command \u8C03\u7528 python/node/sed/awk \u7B49\u811A\u672C\u7ED5\u8FC7\u672C\u5DE5\u5177\u4FEE\u6539\u6587\u4EF6\u2014\u2014\u8BF7\u7528 read_file \u91CD\u65B0\u8BFB\u53D6\u76EE\u6807\u7247\u6BB5\uFF08\u53BB\u6389\u300C<\u884C\u53F7>: \u300D\u524D\u7F00\u3001\u4FDD\u7559\u539F\u59CB Tab/\u7A7A\u683C\u7F29\u8FDB\uFF09\uFF0C\u518D\u6B21\u8C03\u7528 edit_file \u91CD\u8BD5\u3002`;
4910
- }
4911
- const matchCount = normalizedContent.split(normalizedOld).length - 1;
4912
- if (!args.replace_all && matchCount > 1) {
4913
- return `\u274C [\u4EE3\u7801\u4FEE\u8865\u5931\u8D25]\uFF1A\u4EE3\u7801\u51B2\u7A81\uFF01old_str \u5728\u5168\u6587\u4E2D\u4E0D\u552F\u4E00\uFF08\u5171\u53D1\u73B0\u4E86 ${matchCount} \u5904\uFF09\u3002\u8BF7\u5411\u4E0A\u6216\u5411\u4E0B\u591A\u5305\u88F9\u51E0\u884C\u4E0A\u4E0B\u6587\u518D\u63D0\u8BF7\u4FEE\u6539\uFF0C\u6216\u663E\u5F0F\u8BBE replace_all=true \u6279\u91CF\u66FF\u6362\u3002`;
5323
+ content = r.content;
5324
+ summaries.push(editList.length === 1 ? r.summary : ` ${i + 1}. ${r.summary}`);
4914
5325
  }
4915
- const oldFirstNonBlank = normalizedOld.split("\n").find((l) => l.trim() !== "") ?? "";
4916
- const baseIndent = oldFirstNonBlank.match(/^[ \t]*/)?.[0] ?? "";
4917
- const alignedNew = baseIndent ? reindentToBase(normalizedNew, baseIndent) : normalizedNew;
4918
- const updatedContent = args.replace_all ? normalizedContent.split(normalizedOld).join(alignedNew) : normalizedContent.replace(normalizedOld, () => alignedNew);
4919
5326
  assertWithinWorkspace(absPath);
4920
- await fs12.writeFile(absPath, isCRLF ? updatedContent.replace(/\n/g, "\r\n") : updatedContent, "utf-8");
4921
- return args.replace_all ? `\u2705 [\u4EE3\u7801\u4FEE\u8865\u6210\u529F]\uFF1A\u6587\u4EF6 [${args.path}] \u5DF2\u6279\u91CF\u66FF\u6362\u5168\u90E8 ${matchCount} \u5904\u5339\u914D\u3002${note}` : `\u2705 [\u4EE3\u7801\u4FEE\u8865\u6210\u529F]\uFF1A\u6587\u4EF6 [${args.path}] \u5DF2\u6210\u529F\u5B8C\u6210\u552F\u4E00\u6027\u5C40\u90E8\u91CD\u6784\u3002${note}`;
5327
+ await fs12.writeFile(absPath, isCRLF ? content.replace(/\n/g, "\r\n") : content, "utf-8");
5328
+ return editList.length === 1 ? `\u2705 [\u4EE3\u7801\u4FEE\u8865\u6210\u529F]\uFF1A\u6587\u4EF6 [${args.path}] ${summaries[0]}` : `\u2705 [\u4EE3\u7801\u4FEE\u8865\u6210\u529F]\uFF1A\u6587\u4EF6 [${args.path}] \u5DF2\u6309\u5E8F\u5B8C\u6210 ${editList.length} \u5904\u4FEE\u6539\uFF08\u5355\u6B21\u539F\u5B50\u5199\u5165\uFF09\uFF1A
5329
+ ${summaries.join("\n")}`;
4922
5330
  } catch (error) {
4923
5331
  return `\u64CD\u4F5C\u5931\u8D25: ${error.message}`;
4924
5332
  }
@@ -5226,13 +5634,14 @@ var init_search = __esm({
5226
5634
  type: "function",
5227
5635
  function: {
5228
5636
  name: "search_grep",
5229
- description: "\u5728\u5DE5\u4F5C\u533A\u7684\u6240\u6709\u6587\u4EF6\u4E2D\u68C0\u7D22\u5339\u914D\u7684\u4EE3\u7801\u884C\uFF0C\u6BCF\u4E2A\u547D\u4E2D\u5E26\u524D\u540E\u5404 2 \u884C\u4E0A\u4E0B\u6587\uFF08\u53EF\u76F4\u63A5\u5224\u65AD\u7528\u6CD5\u3001\u514D\u7D27\u8DDF\u4E00\u6B21 read_file\uFF09\uFF1B\u9ED8\u8BA4\u6309\u5B57\u9762\u91CF\u5173\u952E\u8BCD\u5339\u914D\uFF0C\u5982\u9700\u6B63\u5219\u4F20 is_regex=true\u3002\u591A\u6839\u5DE5\u4F5C\u533A\u4E0B\u7528 path \u9650\u5B9A\u641C\u7D22\u76EE\u5F55\uFF1A\u53EF\u4F20\u9879\u76EE\u76EE\u5F55\u540D\uFF08\u5982 'frontend'\uFF09\u81EA\u52A8\u5339\u914D\u5DE5\u4F5C\u533A\u6839\uFF0C\u6216\u4F20\u7EDD\u5BF9\u8DEF\u5F84 / \u76F8\u5BF9\u9ED8\u8BA4\u6839\u7684 ../\u5144\u5F1F\u76EE\u5F55\u3002\u2605 \u5F53\u7528\u6237\u5728\u5BF9\u8BDD\u91CC\u6307\u660E\u4E86\u67D0\u4E2A\u9879\u76EE\u65F6\uFF0C\u52A1\u5FC5\u628A\u8BE5\u9879\u76EE\u4F5C\u4E3A path \u4F20\u5165\u2014\u2014\u5426\u5219\u53EA\u4F1A\u641C\u7D22 IDE \u5934\u90E8\u6240\u793A\u7684\u6D3B\u52A8\u6839\uFF0C\u5E38\u4E0E\u7528\u6237\u610F\u56FE\u4E0D\u7B26\u3002",
5637
+ description: "\u5728\u5DE5\u4F5C\u533A\u7684\u6240\u6709\u6587\u4EF6\u4E2D\u68C0\u7D22\u5339\u914D\u7684\u4EE3\u7801\u884C\uFF0C\u6BCF\u4E2A\u547D\u4E2D\u9ED8\u8BA4\u5E26\u524D\u540E\u5404 2 \u884C\u4E0A\u4E0B\u6587\uFF08\u53EF\u76F4\u63A5\u5224\u65AD\u7528\u6CD5\u3001\u514D\u7D27\u8DDF\u4E00\u6B21 read_file\uFF1B\u6784\u9020 edit_file \u7684 old_str \u524D\u53EF\u7528 context \u53C2\u6570\u8C03\u5927\u4E00\u6B21\u62FF\u8DB3\uFF09\uFF1B\u9ED8\u8BA4\u6309\u5B57\u9762\u91CF\u5173\u952E\u8BCD\u5339\u914D\uFF0C\u5982\u9700\u6B63\u5219\u4F20 is_regex=true\u3002\u2605 \u68C0\u7D22\u591A\u4E2A\u5173\u952E\u8BCD\u65F6\uFF0C\u7528 is_regex=true \u4EE5 | \u5408\u5E76\uFF08\u5982 'uploadVisible|uploadTip'\uFF09\u4E00\u6B21\u67E5\u5B8C\uFF0C\u52FF\u9010\u4E2A\u5173\u952E\u8BCD\u591A\u6B21\u8C03\u7528\u3002path \u53EF\u9650\u5B9A\u5230\u76EE\u5F55\u6216\u5177\u4F53\u6587\u4EF6\uFF08\u5728\u5355\u4E2A\u5927\u6587\u4EF6\u5185\u591A\u5904\u5B9A\u4F4D\u65F6\u4F20\u6587\u4EF6\u8DEF\u5F84\u6700\u9AD8\u6548\uFF09\u3002\u591A\u6839\u5DE5\u4F5C\u533A\u4E0B\uFF1A\u53EF\u4F20\u9879\u76EE\u76EE\u5F55\u540D\uFF08\u5982 'frontend'\uFF09\u81EA\u52A8\u5339\u914D\u5DE5\u4F5C\u533A\u6839\uFF0C\u6216\u4F20\u7EDD\u5BF9\u8DEF\u5F84 / \u76F8\u5BF9\u9ED8\u8BA4\u6839\u7684 ../\u5144\u5F1F\u76EE\u5F55\u3002\u2605 \u5F53\u7528\u6237\u5728\u5BF9\u8BDD\u91CC\u6307\u660E\u4E86\u67D0\u4E2A\u9879\u76EE\u65F6\uFF0C\u52A1\u5FC5\u628A\u8BE5\u9879\u76EE\u4F5C\u4E3A path \u4F20\u5165\u2014\u2014\u5426\u5219\u53EA\u4F1A\u641C\u7D22 IDE \u5934\u90E8\u6240\u793A\u7684\u6D3B\u52A8\u6839\uFF0C\u5E38\u4E0E\u7528\u6237\u610F\u56FE\u4E0D\u7B26\u3002",
5230
5638
  parameters: {
5231
5639
  type: "object",
5232
5640
  properties: {
5233
- query: { type: "string", description: "\u68C0\u7D22\u5185\u5BB9\u3002\u9ED8\u8BA4\u4E3A\u5B57\u9762\u91CF\u5173\u952E\u8BCD\uFF08\u5982 'function runAgent'\uFF09\uFF1Bis_regex=true \u65F6\u6309\u6B63\u5219\u89E3\u6790\uFF08\u5982 'function\\s+runAgent'\uFF09\u3002" },
5641
+ query: { type: "string", description: "\u68C0\u7D22\u5185\u5BB9\u3002\u9ED8\u8BA4\u4E3A\u5B57\u9762\u91CF\u5173\u952E\u8BCD\uFF08\u5982 'function runAgent'\uFF09\uFF1Bis_regex=true \u65F6\u6309\u6B63\u5219\u89E3\u6790\uFF08\u5982 'function\\s+runAgent'\uFF0C\u591A\u5173\u952E\u8BCD\u7528 | \u5408\u5E76\uFF09" },
5234
5642
  is_regex: { type: "boolean", description: "\u662F\u5426\u5C06 query \u4F5C\u4E3A\u6B63\u5219\u8868\u8FBE\u5F0F\u89E3\u6790\uFF0C\u9ED8\u8BA4 false\uFF08\u5B57\u9762\u91CF\u5339\u914D\uFF0C\u81EA\u52A8\u8F6C\u4E49\u7279\u6B8A\u5B57\u7B26\uFF09" },
5235
- path: { type: "string", description: "\u9650\u5B9A\u641C\u7D22\u7684\u76EE\u5F55\uFF08\u53EF\u9009\uFF09\u3002\u9ED8\u8BA4\u641C\u7D22 IDE \u5934\u90E8\u6240\u793A\u6D3B\u52A8\u6839\u3002\u591A\u6839\u573A\u666F\u4E0B\uFF1A\u4F20\u9879\u76EE\u76EE\u5F55\u540D\uFF08\u5982 'frontend'\uFF09\u81EA\u52A8\u5339\u914D\u5DE5\u4F5C\u533A\u6839\uFF1B\u6216\u4F20\u7EDD\u5BF9\u8DEF\u5F84 / \u76F8\u5BF9\u9ED8\u8BA4\u6839\u7684 ../<\u5144\u5F1F\u76EE\u5F55>\uFF1B\u5747\u7ECF\u6C99\u7BB1\u6821\u9A8C\u3002\u7528\u6237\u63D0\u5230\u5177\u4F53\u9879\u76EE\u65F6\u5FC5\u586B\u3002" }
5643
+ path: { type: "string", description: "\u9650\u5B9A\u641C\u7D22\u7684\u76EE\u5F55\u6216\u5177\u4F53\u6587\u4EF6\uFF08\u53EF\u9009\uFF09\u3002\u4F20\u5177\u4F53\u6587\u4EF6\u8DEF\u5F84\u65F6\u76F8\u5F53\u4E8E\u8BE5\u6587\u4EF6\u5185\u5168\u6587\u68C0\u7D22\uFF08\u5927\u6587\u4EF6\u591A\u5904\u5B9A\u4F4D\u9996\u9009\uFF09\u3002\u9ED8\u8BA4\u641C\u7D22 IDE \u5934\u90E8\u6240\u793A\u6D3B\u52A8\u6839\u3002\u591A\u6839\u573A\u666F\u4E0B\uFF1A\u4F20\u9879\u76EE\u76EE\u5F55\u540D\uFF08\u5982 'frontend'\uFF09\u81EA\u52A8\u5339\u914D\u5DE5\u4F5C\u533A\u6839\uFF1B\u6216\u4F20\u7EDD\u5BF9\u8DEF\u5F84 / \u76F8\u5BF9\u9ED8\u8BA4\u6839\u7684 ../<\u5144\u5F1F\u76EE\u5F55>\uFF1B\u5747\u7ECF\u6C99\u7BB1\u6821\u9A8C\u3002\u7528\u6237\u63D0\u5230\u5177\u4F53\u9879\u76EE\u65F6\u5FC5\u586B\u3002" },
5644
+ context: { type: "number", description: "\u6BCF\u4E2A\u547D\u4E2D\u9644\u5E26\u7684\u524D\u540E\u4E0A\u4E0B\u6587\u884C\u6570\uFF080-10\uFF0C\u9ED8\u8BA4 2\uFF09\u3002\u9700\u8981\u62FF\u8DB3\u4E00\u6BB5\u5B8C\u6574\u4EE3\u7801\u4F5C edit_file \u7684 old_str \u65F6\u53EF\u8C03\u5927\uFF08\u5982 5-8\uFF09\uFF0C\u514D\u5F97\u4E8C\u6B21\u68C0\u7D22" }
5236
5645
  },
5237
5646
  required: ["query"]
5238
5647
  },
@@ -5261,6 +5670,7 @@ var init_search = __esm({
5261
5670
  return resolveReadablePath(args.path);
5262
5671
  };
5263
5672
  const searchRoot = resolveSearchRoot().replace(/\\/g, "/");
5673
+ const contextLines = Math.min(Math.max(args.context ?? 2, 0), 10);
5264
5674
  const rgArgs = [
5265
5675
  "--threads",
5266
5676
  "1",
@@ -5271,8 +5681,8 @@ var init_search = __esm({
5271
5681
  "--color",
5272
5682
  "never",
5273
5683
  "-C",
5274
- "2",
5275
- // ★ 每个命中带前后各 2 行上下文:模型一次看懂用法,免去紧跟的 read_file 往返(减调用次数的关键)
5684
+ String(contextLines),
5685
+ // ★ 每个命中带前后上下文:模型一次看懂用法,免去紧跟的 read_file 往返(减调用次数的关键)
5276
5686
  "--max-count",
5277
5687
  "10",
5278
5688
  "--glob",
@@ -5575,48 +5985,90 @@ ${tail}`,
5575
5985
  // ../core/src/tool/registry/glob.ts
5576
5986
  import fs14 from "fs/promises";
5577
5987
  import path17 from "path";
5578
- var globToRegex2, globTools;
5988
+ var findMatchingBrace, splitTopLevel, globToRegex2, globTools;
5579
5989
  var init_glob = __esm({
5580
5990
  "../core/src/tool/registry/glob.ts"() {
5581
5991
  "use strict";
5582
5992
  init_type();
5583
5993
  init_guard();
5994
+ findMatchingBrace = (s, start) => {
5995
+ let depth = 0;
5996
+ for (let i = start; i < s.length; i++) {
5997
+ if (s[i] === "{") depth++;
5998
+ else if (s[i] === "}") {
5999
+ depth--;
6000
+ if (depth === 0) return i;
6001
+ }
6002
+ }
6003
+ return -1;
6004
+ };
6005
+ splitTopLevel = (s) => {
6006
+ const parts = [];
6007
+ let depth = 0, cur = "";
6008
+ for (const ch of s) {
6009
+ if (ch === "{") depth++;
6010
+ else if (ch === "}") depth--;
6011
+ if (ch === "," && depth === 0) {
6012
+ parts.push(cur);
6013
+ cur = "";
6014
+ continue;
6015
+ }
6016
+ cur += ch;
6017
+ }
6018
+ parts.push(cur);
6019
+ return parts;
6020
+ };
5584
6021
  globToRegex2 = (pattern) => {
5585
- let re = "";
5586
- let i = 0;
5587
- while (i < pattern.length) {
5588
- const c = pattern[i];
5589
- if (c === "*" && pattern[i + 1] === "*") {
5590
- if (pattern[i + 2] === "/") {
5591
- re += "(?:.*/)?";
5592
- i += 3;
6022
+ const walk = (s) => {
6023
+ let re = "";
6024
+ let i = 0;
6025
+ while (i < s.length) {
6026
+ const c = s[i];
6027
+ if (c === "{") {
6028
+ const close = findMatchingBrace(s, i);
6029
+ if (close === -1) {
6030
+ re += "\\{";
6031
+ i++;
6032
+ continue;
6033
+ }
6034
+ const alts = splitTopLevel(s.slice(i + 1, close));
6035
+ re += `(?:${alts.map((a) => walk(a)).join("|")})`;
6036
+ i = close + 1;
6037
+ continue;
6038
+ }
6039
+ if (c === "*" && s[i + 1] === "*") {
6040
+ if (s[i + 2] === "/") {
6041
+ re += "(?:.*/)?";
6042
+ i += 3;
6043
+ } else {
6044
+ re += ".*";
6045
+ i += 2;
6046
+ }
6047
+ } else if (c === "*") {
6048
+ re += "[^/]*";
6049
+ i++;
6050
+ } else if (c === "?") {
6051
+ re += ".";
6052
+ i++;
5593
6053
  } else {
5594
- re += ".*";
5595
- i += 2;
6054
+ re += /[.+^$}()|[\]\\]/.test(c) ? "\\" + c : c;
6055
+ i++;
5596
6056
  }
5597
- } else if (c === "*") {
5598
- re += "[^/]*";
5599
- i++;
5600
- } else if (c === "?") {
5601
- re += ".";
5602
- i++;
5603
- } else {
5604
- re += /[.+^${}()|[\]\\]/.test(c) ? "\\" + c : c;
5605
- i++;
5606
6057
  }
5607
- }
5608
- return new RegExp(`^${re}$`);
6058
+ return re;
6059
+ };
6060
+ return new RegExp(`^${walk(pattern)}$`);
5609
6061
  };
5610
6062
  globTools = [
5611
6063
  {
5612
6064
  type: "function",
5613
6065
  function: {
5614
6066
  name: "glob",
5615
- description: "\u6309\u6587\u4EF6\u540D glob \u6A21\u5F0F\u5FEB\u901F\u67E5\u627E\u6587\u4EF6\uFF08\u5982 '**/*.ts'\u3001'src/**/test*'\u3001'**/*.test.ts'\uFF09\u3002\u4E0E search_grep\uFF08\u6309\u5185\u5BB9\u68C0\u7D22\uFF09\u4E92\u8865\u3002\u81EA\u52A8\u5957\u7528 .gitignore / \u901A\u7528\u9ED1\u540D\u5355\u8FC7\u6EE4\u3002",
6067
+ description: "\u6309\u6587\u4EF6\u540D glob \u6A21\u5F0F\u5FEB\u901F\u67E5\u627E\u6587\u4EF6\uFF08\u5982 '**/*.ts'\u3001'src/**/test*'\u3001'**/*.test.ts'\uFF09\u3002\u2605 \u652F\u6301\u82B1\u62EC\u53F7\u591A\u9009\uFF1A'**/*.{ts,tsx,vue}'\u3001'src/{api,components}/**' \u4E00\u6B21\u67E5\u591A\u7C7B\u578B/\u591A\u76EE\u5F55\uFF0C\u52FF\u5206\u591A\u6B21\u8C03\u7528\u3002\u4E0E search_grep\uFF08\u6309\u5185\u5BB9\u68C0\u7D22\uFF09\u4E92\u8865\u3002\u81EA\u52A8\u5957\u7528 .gitignore / \u901A\u7528\u9ED1\u540D\u5355\u8FC7\u6EE4\u3002",
5616
6068
  parameters: {
5617
6069
  type: "object",
5618
6070
  properties: {
5619
- pattern: { type: "string", description: "glob \u6A21\u5F0F\uFF0C\u5982 '**/*.ts' \u6216 'src/**/*.test.ts'" },
6071
+ pattern: { type: "string", description: "glob \u6A21\u5F0F\uFF0C\u652F\u6301 * / ** / ? \u4E0E\u82B1\u62EC\u53F7\u591A\u9009 {a,b}\uFF08\u5982 '**/*.{ts,vue}'\u3001'src/{api,components}/**'\uFF09" },
5620
6072
  path: { type: "string", description: "\u9650\u5B9A\u641C\u7D22\u7684\u8D77\u59CB\u76EE\u5F55\uFF08\u76F8\u5BF9\u8DEF\u5F84\uFF0C\u53EF\u9009\uFF0C\u9ED8\u8BA4\u5DE5\u4F5C\u533A\u6839\uFF09" }
5621
6073
  },
5622
6074
  required: ["pattern"]
@@ -6530,7 +6982,7 @@ var init_http = __esm({
6530
6982
  // ../core/src/tool/registry/typescript.ts
6531
6983
  import fs16 from "fs/promises";
6532
6984
  import path19 from "path";
6533
- var MAX_FILE_BYTES, toDisplayPath, precheck, MAX_DIAG_ENTRIES, typescriptTools;
6985
+ var MAX_FILE_BYTES, toDisplayPath, precheck, MAX_DIAG_ENTRIES, MAX_DIAG_FILES, diagnoseOneFile, typescriptTools;
6534
6986
  var init_typescript = __esm({
6535
6987
  "../core/src/tool/registry/typescript.ts"() {
6536
6988
  "use strict";
@@ -6554,74 +7006,108 @@ var init_typescript = __esm({
6554
7006
  return null;
6555
7007
  };
6556
7008
  MAX_DIAG_ENTRIES = 300;
7009
+ MAX_DIAG_FILES = 20;
7010
+ diagnoseOneFile = async (TS, displayPath, checkJs) => {
7011
+ try {
7012
+ const absPath = realpathNative(resolveReadablePath(displayPath));
7013
+ const block = await precheck(displayPath, absPath);
7014
+ if (block) return { ok: false, text: block };
7015
+ const { ls } = await getLanguageService(TS, absPath, { checkJs });
7016
+ const syntactic = ls.getSyntacticDiagnostics(absPath);
7017
+ const semantic = ls.getSemanticDiagnostics(absPath);
7018
+ const all = [...syntactic, ...semantic];
7019
+ if (all.length === 0) return { ok: true, errors: 0, warnings: 0, text: `\u2713 No diagnostics (0 errors, 0 warnings) for ${displayPath}` };
7020
+ const entries = [];
7021
+ let errors = 0, warnings = 0;
7022
+ for (const d of all) {
7023
+ const cat = d.category;
7024
+ let label;
7025
+ if (cat === TS.DiagnosticCategory.Error) {
7026
+ label = "error";
7027
+ errors++;
7028
+ } else if (cat === TS.DiagnosticCategory.Warning) {
7029
+ label = "warning";
7030
+ warnings++;
7031
+ } else if (cat === TS.DiagnosticCategory.Suggestion) label = "suggestion";
7032
+ else label = "info";
7033
+ let line = 0, column = 0;
7034
+ if (d.file && typeof d.start === "number") {
7035
+ ({ line, column } = posToLineCol(TS, d.file, d.start));
7036
+ }
7037
+ entries.push({
7038
+ line,
7039
+ column,
7040
+ label,
7041
+ code: typeof d.code === "number" ? `TS${d.code}` : "",
7042
+ msg: TS.flattenDiagnosticMessageText(d.messageText, "\n")
7043
+ });
7044
+ }
7045
+ entries.sort((a, b) => a.line - b.line || a.column - b.column);
7046
+ const overflow = entries.length > MAX_DIAG_ENTRIES ? entries.length - MAX_DIAG_ENTRIES : 0;
7047
+ const shown = overflow ? entries.slice(0, MAX_DIAG_ENTRIES) : entries;
7048
+ const body = shown.map(
7049
+ (e) => `${displayPath}:${e.line}:${e.column} ${e.label}${e.code ? " " + e.code : ""} ${e.msg}`
7050
+ ).join("\n");
7051
+ const tail = overflow ? `
7052
+ \u2026(\u53E6\u6709 ${overflow} \u6761\u8BCA\u65AD\u672A\u663E\u793A\uFF0C\u8BF7\u7F29\u5C0F\u8303\u56F4\u6216\u7528 run_command \u8DD1 tsc \u770B\u5168\u91CF)\u2026` : "";
7053
+ return { ok: true, errors, warnings, text: `[Diagnostics: ${displayPath}] ${errors} error(s), ${warnings} warning(s)
7054
+ ${body}${tail}` };
7055
+ } catch (e) {
7056
+ return { ok: false, text: `\u274C \u7C7B\u578B\u8BCA\u65AD\u5931\u8D25 [${displayPath}]: ${e?.message ?? e}` };
7057
+ }
7058
+ };
6557
7059
  typescriptTools = [
6558
7060
  {
6559
7061
  type: "function",
6560
7062
  function: {
6561
7063
  name: "get_diagnostics",
6562
- description: "\u5BF9\u5355\u4E2A TS/JS \u6587\u4EF6\u53D6 TypeScript \u8BED\u6CD5 + \u8BED\u4E49\u8BCA\u65AD\uFF08\u7C7B\u578B\u9519\u8BEF\u3001\u672A\u7528\u53D8\u91CF\u3001\u4E0D\u53EF\u8FBE\u4EE3\u7801\u7B49\uFF09\uFF0C\u8F93\u51FA\u5E26\u884C\u53F7/\u5217/\u4E25\u91CD\u5EA6/TS \u9519\u8BEF\u7801\uFF0C\u683C\u5F0F\u5BF9\u9F50 tsc\u3002\u7528\u4E8E\u7CBE\u51C6\u5B9A\u4F4D\u7F16\u8BD1/\u7C7B\u578B\u95EE\u9898\uFF0C\u66FF\u4EE3\u624B\u52A8\u8DD1 tsc \u518D\u89E3\u6790\u8F93\u51FA\u3002\u652F\u6301 .ts/.tsx/.js/.jsx/.mjs/.cjs\uFF1B.vue SFC \u4E0D\u652F\u6301\uFF08\u9700 vue-tsc\uFF09\u3002\u9879\u76EE\u7EA7\u5168\u91CF\u8BCA\u65AD\u8BF7\u7528 run_command \u8DD1 `tsc --noEmit`\u3002\u4F9D\u8D56 typescript \u6A21\u5757\uFF08VSCode \u6269\u5C55\u5185\u53EF\u7528\uFF1B\u7F3A\u5931\u5219\u672C\u5DE5\u5177\u81EA\u52A8\u9690\u85CF\uFF09\u3002",
7064
+ description: "\u5BF9\u4E00\u4E2A\u6216\u591A\u4E2A TS/JS \u6587\u4EF6\u53D6 TypeScript \u8BED\u6CD5 + \u8BED\u4E49\u8BCA\u65AD\uFF08\u7C7B\u578B\u9519\u8BEF\u3001\u672A\u7528\u53D8\u91CF\u3001\u4E0D\u53EF\u8FBE\u4EE3\u7801\u7B49\uFF09\uFF0C\u8F93\u51FA\u5E26\u884C\u53F7/\u5217/\u4E25\u91CD\u5EA6/TS \u9519\u8BEF\u7801\uFF0C\u683C\u5F0F\u5BF9\u9F50 tsc\u3002\u7528\u4E8E\u7CBE\u51C6\u5B9A\u4F4D\u7F16\u8BD1/\u7C7B\u578B\u95EE\u9898\uFF0C\u66FF\u4EE3\u624B\u52A8\u8DD1 tsc \u518D\u89E3\u6790\u8F93\u51FA\u3002\u2605 \u6539\u5B8C\u591A\u4E2A\u6587\u4EF6\u540E\u9A8C\u8BC1\uFF1A\u4F20 paths \u6570\u7EC4\u4E00\u6B21\u8BCA\u65AD\u5168\u90E8\uFF08\u6700\u591A 20 \u4E2A\uFF09\uFF0C\u52FF\u9010\u6587\u4EF6\u591A\u6B21\u8C03\u7528\u3002\u652F\u6301 .ts/.tsx/.js/.jsx/.mjs/.cjs\uFF1B.vue SFC \u4E0D\u652F\u6301\uFF08\u9700 vue-tsc\uFF09\u3002\u9879\u76EE\u7EA7\u5168\u91CF\u8BCA\u65AD\u8BF7\u7528 run_command \u8DD1 `tsc --noEmit`\u3002\u4F9D\u8D56 typescript \u6A21\u5757\uFF08VSCode \u6269\u5C55\u5185\u53EF\u7528\uFF1B\u7F3A\u5931\u5219\u672C\u5DE5\u5177\u81EA\u52A8\u9690\u85CF\uFF09\u3002",
6563
7065
  parameters: {
6564
7066
  type: "object",
6565
7067
  properties: {
6566
- path: { type: "string", description: "\u76EE\u6807\u6587\u4EF6\u76F8\u5BF9\u8DEF\u5F84\uFF08\u5982 'src/services/user.ts'\uFF09" },
7068
+ path: { type: "string", description: "\u76EE\u6807\u6587\u4EF6\u76F8\u5BF9\u8DEF\u5F84\uFF08\u5982 'src/services/user.ts'\uFF09\u3002\u5355\u6587\u4EF6\u8BCA\u65AD\u7528\uFF1B\u6279\u91CF\u8BF7\u6539\u7528 paths" },
7069
+ paths: { type: "array", items: { type: "string" }, description: "\u2605 \u6279\u91CF\u8BCA\u65AD\uFF1A\u591A\u4E2A\u6587\u4EF6\u4E00\u6B21\u9A8C\u8BC1\uFF08\u5355\u6B21\u6700\u591A 20 \u4E2A\uFF0C\u8D85\u51FA\u4F1A\u62D2\u7EDD\uFF09\u3002\u4F20\u4E86 paths \u5219\u5FFD\u7565 path" },
6567
7070
  check_js: { type: "boolean", description: "\u5BF9 .js/.jsx \u6587\u4EF6\u5F00\u542F\u8BED\u4E49\u68C0\u67E5\uFF08\u9ED8\u8BA4\u9075 tsconfig \u7684 checkJs\uFF1B\u76EE\u6807\u4E3A JS \u4E14\u60F3\u8981\u7C7B\u578B\u8BCA\u65AD\u65F6\u6309\u9700\u7F6E true\uFF09" }
6568
7071
  },
6569
- required: ["path"]
7072
+ required: []
6570
7073
  },
6571
7074
  safetyLevel: "safe" /* SAFE */,
6572
7075
  isSync: true,
6573
- maxOutputCharacters: 24e3,
7076
+ maxOutputCharacters: 48e3,
7077
+ // ★ 批量多文件后上调(原单文件 24K;中心 truncateToolResult 仍是最终兜底)
6574
7078
  privacyMaskingRules: maskSecretsInContent,
6575
7079
  validateEnvironment: async () => !!await getTs(),
6576
7080
  async execute(args, _ctx) {
6577
7081
  try {
6578
7082
  const TS = await getTs();
6579
7083
  if (!TS) return `\u26A0\uFE0F [\u7C7B\u578B\u8BCA\u65AD\u4E0D\u53EF\u7528]\uFF1Atypescript \u6A21\u5757\u672A\u52A0\u8F7D\uFF08VSCode \u6269\u5C55\u5185\u53EF\u7528\uFF1BCLI \u73AF\u5883\u672A\u5FC5\u5B89\u88C5 typescript\uFF09\u3002\u53EF\u6539\u7528 run_command \u8DD1 \`tsc --noEmit\`\u3002`;
6580
- const absPath = realpathNative(resolveReadablePath(args.path));
6581
- const block = await precheck(args.path, absPath);
6582
- if (block) return block;
6583
- const { ls } = await getLanguageService(TS, absPath, { checkJs: args.check_js });
6584
- const syntactic = ls.getSyntacticDiagnostics(absPath);
6585
- const semantic = ls.getSemanticDiagnostics(absPath);
6586
- const all = [...syntactic, ...semantic];
6587
- if (all.length === 0) return `\u2713 No diagnostics (0 errors, 0 warnings) for ${args.path}`;
6588
- const entries = [];
6589
- let errors = 0, warnings = 0;
6590
- for (const d of all) {
6591
- const cat = d.category;
6592
- let label;
6593
- if (cat === TS.DiagnosticCategory.Error) {
6594
- label = "error";
6595
- errors++;
6596
- } else if (cat === TS.DiagnosticCategory.Warning) {
6597
- label = "warning";
6598
- warnings++;
6599
- } else if (cat === TS.DiagnosticCategory.Suggestion) label = "suggestion";
6600
- else label = "info";
6601
- let line = 0, column = 0;
6602
- if (d.file && typeof d.start === "number") {
6603
- ({ line, column } = posToLineCol(TS, d.file, d.start));
7084
+ const targets = args.paths && args.paths.length > 0 ? args.paths : args.path ? [args.path] : [];
7085
+ if (targets.length === 0) return `\u274C [\u53C2\u6570\u7F3A\u5931]\uFF1A\u8BF7\u4F20 path\uFF08\u5355\u6587\u4EF6\uFF09\u6216 paths\uFF08\u6279\u91CF\uFF0C\u6539\u5B8C\u591A\u4E2A\u6587\u4EF6\u540E\u4E00\u6B21\u9A8C\u8BC1\uFF09\u3002`;
7086
+ if (targets.length > MAX_DIAG_FILES) return `\u274C [\u53C2\u6570\u8D85\u9650]\uFF1A\u5355\u6B21\u6279\u91CF\u8BCA\u65AD\u6700\u591A ${MAX_DIAG_FILES} \u4E2A\u6587\u4EF6\uFF08\u6536\u5230 ${targets.length} \u4E2A\uFF09\u3002\u8BF7\u5206\u6279\u8C03\u7528\uFF0C\u6216\u7528 run_command \u8DD1 \`tsc --noEmit\` \u505A\u9879\u76EE\u7EA7\u5168\u91CF\u8BCA\u65AD\u3002`;
7087
+ const results = [];
7088
+ for (const p of targets) results.push(await diagnoseOneFile(TS, p, args.check_js));
7089
+ if (results.length === 1) return results[0].text;
7090
+ let totalErr = 0, totalWarn = 0, clean = 0, failed = 0;
7091
+ const sections = [];
7092
+ for (let i = 0; i < results.length; i++) {
7093
+ const r = results[i];
7094
+ if (!r.ok) {
7095
+ failed++;
7096
+ sections.push(`\u2014\u2014 ${targets[i]}\uFF08\u8BCA\u65AD\u672A\u4EA7\u51FA\uFF09\u2014\u2014
7097
+ ${r.text}`);
7098
+ continue;
6604
7099
  }
6605
- entries.push({
6606
- line,
6607
- column,
6608
- label,
6609
- code: typeof d.code === "number" ? `TS${d.code}` : "",
6610
- msg: TS.flattenDiagnosticMessageText(d.messageText, "\n")
6611
- });
7100
+ totalErr += r.errors;
7101
+ totalWarn += r.warnings;
7102
+ if (r.errors === 0 && r.warnings === 0) clean++;
7103
+ sections.push(`\u2014\u2014 ${targets[i]} \u2014\u2014
7104
+ ${r.text}`);
6612
7105
  }
6613
- entries.sort((a, b) => a.line - b.line || a.column - b.column);
6614
- const overflow = entries.length > MAX_DIAG_ENTRIES ? entries.length - MAX_DIAG_ENTRIES : 0;
6615
- const shown = overflow ? entries.slice(0, MAX_DIAG_ENTRIES) : entries;
6616
- const body = shown.map(
6617
- (e) => `${args.path}:${e.line}:${e.column} ${e.label}${e.code ? " " + e.code : ""} ${e.msg}`
6618
- ).join("\n");
6619
- const tail = overflow ? `
6620
- \u2026(\u53E6\u6709 ${overflow} \u6761\u8BCA\u65AD\u672A\u663E\u793A\uFF0C\u8BF7\u7F29\u5C0F\u8303\u56F4\u6216\u7528 run_command \u8DD1 tsc \u770B\u5168\u91CF)\u2026` : "";
6621
- return `[Diagnostics: ${args.path}] ${errors} error(s), ${warnings} warning(s)
6622
- ${body}${tail}`;
7106
+ return `[\u6279\u91CF\u8BCA\u65AD: ${targets.length} \u4E2A\u6587\u4EF6] \u5171 ${totalErr} error(s), ${totalWarn} warning(s)\uFF1B${clean} \u4E2A\u6587\u4EF6\u5E72\u51C0${failed ? `\uFF0C${failed} \u4E2A\u8BCA\u65AD\u5931\u8D25` : ""}
7107
+
7108
+ ${sections.join("\n\n")}`;
6623
7109
  } catch (e) {
6624
- return `\u274C \u7C7B\u578B\u8BCA\u65AD\u5931\u8D25 [${args.path}]: ${e?.message ?? e}`;
7110
+ return `\u274C \u7C7B\u578B\u8BCA\u65AD\u5931\u8D25: ${e?.message ?? e}`;
6625
7111
  }
6626
7112
  }
6627
7113
  }
@@ -8465,7 +8951,6 @@ var agentTools, agentSubTools, workflowTools;
8465
8951
  var init_tool = __esm({
8466
8952
  "../core/src/tool/index.ts"() {
8467
8953
  "use strict";
8468
- init_system();
8469
8954
  init_agent();
8470
8955
  init_workflow();
8471
8956
  init_fs();
@@ -8491,7 +8976,6 @@ var init_tool = __esm({
8491
8976
  agentSubTools = createAgentTools(() => agentTools);
8492
8977
  workflowTools = createWorkflowTools(() => agentTools);
8493
8978
  agentTools.push(
8494
- ...systemTools,
8495
8979
  ...agentSubTools,
8496
8980
  ...workflowTools,
8497
8981
  ...fsTools,
@@ -8811,6 +9295,9 @@ var init_toolExecution = __esm({
8811
9295
  init_backgroundTool();
8812
9296
  init_backup();
8813
9297
  init_autoPermission();
9298
+ init_loader();
9299
+ init_config();
9300
+ init_planMode();
8814
9301
  applyPrivacyMasking = (rules3, args, output) => {
8815
9302
  if (!rules3) return output;
8816
9303
  try {
@@ -8836,6 +9323,7 @@ var init_toolExecution = __esm({
8836
9323
  rawTools,
8837
9324
  events,
8838
9325
  permissionMode,
9326
+ planMode,
8839
9327
  onUIEvent,
8840
9328
  requestApproval: hostRequestApproval,
8841
9329
  requestQuestion: hostRequestQuestion,
@@ -8862,6 +9350,10 @@ var init_toolExecution = __esm({
8862
9350
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: note, resultForUser: note, ok: true, terminal: { kind: "exit_plan_mode", plan } };
8863
9351
  }
8864
9352
  if (calledName === "enter_plan_mode") {
9353
+ if (planMode) {
9354
+ 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";
9355
+ return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: note2, resultForUser: note2, ok: true };
9356
+ }
8865
9357
  const reason = typeof calledArgs?.reason === "string" ? calledArgs.reason : "";
8866
9358
  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
9359
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: note, resultForUser: note, ok: true, terminal: { kind: "enter_plan_mode", reason } };
@@ -8870,10 +9362,16 @@ var init_toolExecution = __esm({
8870
9362
  const placeholder = "\uFF08\u5DF2\u4E2D\u6B62\uFF0C\u672A\u6267\u884C\uFF09";
8871
9363
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: placeholder, resultForUser: placeholder, ok: false, aborted: true };
8872
9364
  }
9365
+ if (!parseFailed && planMode && appConfig.planEnforcement === "runtime" && calledName !== "enter_plan_mode" && calledName !== "exit_plan_mode" && !PLAN_ALLOWED_TOOLS.has(calledName)) {
9366
+ 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`;
9367
+ 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 } });
9368
+ return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: note, resultForUser: note, ok: false };
9369
+ }
8873
9370
  const matchedTool = rawTools.find((t) => t.function.name === calledName);
8874
9371
  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
9372
  let result = "";
8876
9373
  let explicitOk = null;
9374
+ let postHookOverride;
8877
9375
  if (parseFailed) {
8878
9376
  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
9377
  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 +9379,23 @@ var init_toolExecution = __esm({
8881
9379
  const level = matchedTool.function.safetyLevel;
8882
9380
  let needApproval = level === "mutation" /* MUTATION */ || level === "danger" /* DANGER */;
8883
9381
  let denied = false;
9382
+ let preHooksAdvanced = false;
9383
+ if (appConfig.hookRewrite) {
9384
+ preHooksAdvanced = true;
9385
+ let veto;
9386
+ try {
9387
+ veto = await runPreHooks(calledName, calledArgs, toolCtx);
9388
+ } catch (e) {
9389
+ 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` };
9390
+ }
9391
+ if (veto.deny) {
9392
+ denied = true;
9393
+ result = veto.reason || `\u274C [Hook \u62E6\u622A]\uFF1Apre-hook \u62D2\u7EDD\u4E86 [${calledName}] \u7684\u6267\u884C\u3002`;
9394
+ } else if (veto.argsOverride !== void 0 && veto.argsOverride !== null && typeof veto.argsOverride === "object" && !Array.isArray(veto.argsOverride)) {
9395
+ console.log(`\u270F\uFE0F [Pre-hook] [${calledName}] args \u5DF2\u88AB hook \u6539\u5199`);
9396
+ calledArgs = veto.argsOverride;
9397
+ }
9398
+ }
8884
9399
  const protectedPaths = calledName === "move_file" ? [calledArgs?.source, calledArgs?.destination] : isUndoTrigger(calledName) ? [calledArgs?.path] : [];
8885
9400
  const hitProtected = protectedPaths.find((p) => typeof p === "string" && isProtectedWrite(p, toolCtx.cwd));
8886
9401
  if (hitProtected) {
@@ -8889,7 +9404,7 @@ var init_toolExecution = __esm({
8889
9404
  }
8890
9405
  let perm = null;
8891
9406
  try {
8892
- perm = checkPermission(calledName, calledArgs);
9407
+ perm = checkPermission(resolveMcpPermissionName(calledName, calledArgs), calledArgs);
8893
9408
  if (perm === "deny") {
8894
9409
  denied = true;
8895
9410
  result = `\u274C [\u6743\u9650\u89C4\u5219]\uFF1A[${calledName}] \u88AB\u6743\u9650\u89C4\u5219 deny \u62D2\u7EDD\u3002`;
@@ -8915,7 +9430,7 @@ var init_toolExecution = __esm({
8915
9430
  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
9431
  }
8917
9432
  if (needApproval && !denied) {
8918
- const auto = await runAutoCheck(calledName, calledArgs, toolCtx, permissionMode === "auto");
9433
+ const auto = await runAutoCheck(resolveMcpPermissionName(calledName, calledArgs), calledArgs, toolCtx, permissionMode === "auto");
8919
9434
  if (auto === "allow") {
8920
9435
  needApproval = false;
8921
9436
  } else if (auto === "deny") {
@@ -8946,7 +9461,7 @@ var init_toolExecution = __esm({
8946
9461
  }
8947
9462
  }
8948
9463
  }
8949
- if (!denied) {
9464
+ if (!denied && !preHooksAdvanced) {
8950
9465
  let veto;
8951
9466
  try {
8952
9467
  veto = await runPreHooks(calledName, calledArgs, toolCtx);
@@ -8990,9 +9505,12 @@ ${result}`;
8990
9505
  result = `\u5DE5\u5177\u6267\u884C\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`;
8991
9506
  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
9507
  }
8993
- await runPostHooks(calledName, calledArgs, result, toolCtx).catch((e) => {
9508
+ try {
9509
+ const post = await runPostHooks(calledName, calledArgs, result, toolCtx);
9510
+ postHookOverride = post?.resultOverride;
9511
+ } catch (e) {
8994
9512
  console.warn(`\u26A0\uFE0F post-hook [${calledName}] \u5F02\u5E38\uFF08\u5DF2\u5FFD\u7565\uFF09:`, e?.message ?? e);
8995
- });
9513
+ }
8996
9514
  }
8997
9515
  } else {
8998
9516
  result = `\u9519\u8BEF\uFF1A\u672A\u77E5\u5DE5\u5177 "${calledName}" \u6216\u8BE5\u5DE5\u5177\u65E0\u53EF\u6267\u884C\u51FD\u6570`;
@@ -9013,6 +9531,10 @@ ${result}`;
9013
9531
  } catch {
9014
9532
  }
9015
9533
  }
9534
+ if (postHookOverride !== void 0) {
9535
+ console.log(`\u270F\uFE0F [Post-hook] [${calledName}] \u6A21\u578B\u89C6\u56FE\u7ED3\u679C\u5DF2\u88AB hook \u6539\u5199`);
9536
+ resultForModel = truncateToolResult(postHookOverride, matchedTool?.function?.maxOutputCharacters);
9537
+ }
9016
9538
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel, resultForUser, ok };
9017
9539
  };
9018
9540
  }
@@ -9536,6 +10058,18 @@ async function* runAgent(message, options) {
9536
10058
  let lastRealPromptTokens = persistedCalib.lastRealPromptTokens;
9537
10059
  let lastCachedTokens = persistedCalib.lastCachedTokens;
9538
10060
  const breaker = createRepeatBreaker({ sessionId, depth, llmDecisionSource, startTime, events });
10061
+ const runId = createUUID();
10062
+ let usageSum;
10063
+ const addUsage = (u) => {
10064
+ if (!u) return;
10065
+ usageSum = {
10066
+ prompt_tokens: (usageSum?.prompt_tokens ?? 0) + (u.prompt_tokens ?? 0),
10067
+ completion_tokens: (usageSum?.completion_tokens ?? 0) + (u.completion_tokens ?? 0),
10068
+ total_tokens: (usageSum?.total_tokens ?? 0) + (u.total_tokens ?? 0),
10069
+ cached_tokens: (usageSum?.cached_tokens ?? 0) + (u.cached_tokens ?? 0)
10070
+ };
10071
+ };
10072
+ await appendEvent(sessionId, { dscEvent: "run.start", runId, depth });
9539
10073
  try {
9540
10074
  while (true) {
9541
10075
  round++;
@@ -9563,6 +10097,14 @@ async function* runAgent(message, options) {
9563
10097
  yield { type: "final", text: lastContent || "\uFF08\u5DF2\u4E2D\u6B62\uFF09" };
9564
10098
  return;
9565
10099
  }
10100
+ const inboxTexts = claimSessionInbox(sessionId);
10101
+ if (inboxTexts.length > 0) {
10102
+ for (const t of inboxTexts) {
10103
+ message.push({ role: "user", content: t });
10104
+ await appendMessage({ sessionId, role: "user", content: t });
10105
+ }
10106
+ yield { type: "inbox.claimed", texts: inboxTexts };
10107
+ }
9566
10108
  yield { type: "round.start", round };
9567
10109
  try {
9568
10110
  await ensureFitsWindow(
@@ -9629,6 +10171,7 @@ async function* runAgent(message, options) {
9629
10171
  lastRealPromptTokens = infResult.usage.prompt_tokens;
9630
10172
  lastCachedTokens = infResult.usage.cached_tokens;
9631
10173
  }
10174
+ addUsage(infResult.usage);
9632
10175
  const assistantMessage = infResult.assistantMessage;
9633
10176
  message.push(assistantMessage);
9634
10177
  await appendMessage({ ...assistantMessage, sessionId });
@@ -9637,6 +10180,7 @@ async function* runAgent(message, options) {
9637
10180
  }
9638
10181
  const tcCount = assistantMessage.tool_calls?.length ?? 0;
9639
10182
  if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
10183
+ await appendEvent(sessionId, { dscEvent: "round.end", runId, round, usage: infResult.usage });
9640
10184
  const finalText = (typeof assistantMessage.content === "string" ? assistantMessage.content : "") || "";
9641
10185
  const intercepted = nudges.interceptFinal(finalText, round);
9642
10186
  if (intercepted) continue;
@@ -9647,6 +10191,11 @@ async function* runAgent(message, options) {
9647
10191
  const repeatVerdict = breaker.check(assistantMessage.tool_calls, round, lastContent);
9648
10192
  if (repeatVerdict.tripped) {
9649
10193
  stopReason = "repeat";
10194
+ for (const tc of assistantMessage.tool_calls) {
10195
+ const ph = "\uFF08\u91CD\u590D\u8C03\u7528\u7194\u65AD\uFF0C\u672A\u6267\u884C\uFF09";
10196
+ message.push({ role: "tool", tool_call_id: tc.id, content: ph });
10197
+ await appendMessage({ sessionId, role: "tool", tool_call_id: tc.id, content: ph });
10198
+ }
9650
10199
  yield { type: "final", text: repeatVerdict.text };
9651
10200
  return;
9652
10201
  }
@@ -9661,6 +10210,8 @@ async function* runAgent(message, options) {
9661
10210
  rawTools,
9662
10211
  events,
9663
10212
  permissionMode: options.permissionMode,
10213
+ planMode: !!options.planMode,
10214
+ // ★ P0-A runtime 档:计划期写工具在 processToolCall 执行层拒绝(工具表恒定不裁剪)
9664
10215
  onUIEvent: options.onUIEvent,
9665
10216
  requestApproval: options.requestApproval,
9666
10217
  requestQuestion: options.requestQuestion,
@@ -9678,6 +10229,7 @@ async function* runAgent(message, options) {
9678
10229
  yield { type: "final", text: scheduleResult.terminalText };
9679
10230
  return;
9680
10231
  }
10232
+ await appendEvent(sessionId, { dscEvent: "round.end", runId, round, usage: infResult.usage });
9681
10233
  }
9682
10234
  } catch (toolErr) {
9683
10235
  if (signal?.aborted) {
@@ -9691,6 +10243,14 @@ async function* runAgent(message, options) {
9691
10243
  \uFF08\u5DE5\u5177\u6267\u884C\u5F02\u5E38\uFF1A${errMsg}\uFF09` };
9692
10244
  return;
9693
10245
  } finally {
10246
+ try {
10247
+ await flushLeftoverToTranscript(sessionId);
10248
+ } catch {
10249
+ }
10250
+ try {
10251
+ await appendEvent(sessionId, { dscEvent: "run.end", runId, stopReason: signal?.aborted ? "aborted" : stopReason, rounds: round, usage: usageSum });
10252
+ } catch {
10253
+ }
9694
10254
  try {
9695
10255
  const reason = signal?.aborted ? "aborted" : stopReason;
9696
10256
  await dispatch("Stop", { sessionId, cwd, lastText: lastContent || "", reason });
@@ -9706,6 +10266,8 @@ var init_runAgent = __esm({
9706
10266
  "../core/src/agent/runAgent.ts"() {
9707
10267
  "use strict";
9708
10268
  init_transcript();
10269
+ init_inbox();
10270
+ init_common();
9709
10271
  init_contextCore();
9710
10272
  init_store();
9711
10273
  init_truncate();
@@ -9718,17 +10280,30 @@ var init_runAgent = __esm({
9718
10280
  }
9719
10281
  });
9720
10282
 
9721
- // ../core/src/session/content.ts
9722
- var repairOrphanToolCalls, decayOldToolResults, buildContextMessages;
9723
- var init_content = __esm({
9724
- "../core/src/session/content.ts"() {
10283
+ // ../core/src/session/recovery.ts
10284
+ var detectInterruption, repairOrphanToolCalls, reconcileCompaction, recoverSession;
10285
+ var init_recovery = __esm({
10286
+ "../core/src/session/recovery.ts"() {
9725
10287
  "use strict";
9726
- init_contextCore();
9727
10288
  init_transcript();
9728
10289
  init_store();
9729
- init_config();
9730
- repairOrphanToolCalls = (msgs) => {
10290
+ detectInterruption = (lines) => {
10291
+ let sawEvents = false;
10292
+ let lastStart;
10293
+ const closed = /* @__PURE__ */ new Set();
10294
+ for (const l of lines) {
10295
+ if (!isEventLine(l)) continue;
10296
+ sawEvents = true;
10297
+ if (l.dscEvent === "run.start") lastStart = l;
10298
+ else if (l.dscEvent === "run.end" || l.dscEvent === "run.abandoned") closed.add(l.runId);
10299
+ }
10300
+ if (!sawEvents || !lastStart) return { kind: "legacy" };
10301
+ return closed.has(lastStart.runId) ? { kind: "clean" } : { kind: "crashed", runId: lastStart.runId };
10302
+ };
10303
+ repairOrphanToolCalls = (msgs, confirmedCrash) => {
10304
+ 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
10305
  const out = [];
10306
+ const repairedToolCallIds = [];
9732
10307
  for (let i = 0; i < msgs.length; i++) {
9733
10308
  const m = msgs[i];
9734
10309
  out.push(m);
@@ -9741,13 +10316,60 @@ var init_content = __esm({
9741
10316
  }
9742
10317
  for (const tc of m.tool_calls) {
9743
10318
  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" });
10319
+ out.push({ role: "tool", tool_call_id: tc.id, content: placeholder });
10320
+ repairedToolCallIds.push(tc.id);
9745
10321
  }
9746
10322
  }
9747
10323
  }
9748
10324
  }
9749
- return out;
10325
+ return { msgs: out, repairedToolCallIds, confirmedCrash };
10326
+ };
10327
+ reconcileCompaction = (state, lines) => {
10328
+ let last;
10329
+ for (const l of lines) {
10330
+ if (isEventLine(l) && l.dscEvent === "compaction") last = l;
10331
+ }
10332
+ if (!last) return { archivedMessageCount: state.archivedMessageCount, rollingSummary: state.rollingSummary, desync: "legacy" };
10333
+ let desync = "none";
10334
+ if (last.archivedMessageCount !== state.archivedMessageCount) {
10335
+ desync = state.archivedMessageCount > last.archivedMessageCount ? "state-ahead" : "event-ahead";
10336
+ console.warn(
10337
+ `\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`
10338
+ );
10339
+ }
10340
+ return { archivedMessageCount: state.archivedMessageCount, rollingSummary: state.rollingSummary, desync };
9750
10341
  };
10342
+ recoverSession = async (sessionId) => {
10343
+ const lines = await readTranscriptLines(sessionId);
10344
+ const interruption = detectInterruption(lines);
10345
+ if (interruption.kind === "crashed") {
10346
+ await appendEvent(sessionId, {
10347
+ dscEvent: "run.abandoned",
10348
+ runId: interruption.runId,
10349
+ reason: "run.end missing: interrupted by process exit/kill"
10350
+ });
10351
+ 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`);
10352
+ }
10353
+ const state = await getRollingState(sessionId);
10354
+ const compaction = reconcileCompaction(
10355
+ { archivedMessageCount: state.archivedMessageCount, rollingSummary: state.rollingSummary },
10356
+ lines
10357
+ );
10358
+ return { interruption, compaction };
10359
+ };
10360
+ }
10361
+ });
10362
+
10363
+ // ../core/src/session/content.ts
10364
+ var decayOldToolResults, buildContextMessages;
10365
+ var init_content = __esm({
10366
+ "../core/src/session/content.ts"() {
10367
+ "use strict";
10368
+ init_contextCore();
10369
+ init_transcript();
10370
+ init_store();
10371
+ init_config();
10372
+ init_recovery();
9751
10373
  decayOldToolResults = (msgs) => {
9752
10374
  const units = groupUnits(msgs);
9753
10375
  const cutoffUnitIdx = Math.max(0, units.length - appConfig.KEEP_RECENT_UNITS);
@@ -9773,21 +10395,28 @@ var init_content = __esm({
9773
10395
  });
9774
10396
  };
9775
10397
  buildContextMessages = async (sessionId, currentUserMsg, systemPrompt) => {
9776
- const all = (await readMessages(sessionId)).map(cleanMsg);
10398
+ const lines = await readTranscriptLines(sessionId);
9777
10399
  const store = await getRollingState(sessionId);
10400
+ const eventAware = appConfig.transcriptEvents || lines.some(isEventLine);
10401
+ const recovery = eventAware ? await recoverSession(sessionId) : {
10402
+ interruption: { kind: "legacy" },
10403
+ compaction: { archivedMessageCount: store.archivedMessageCount, rollingSummary: store.rollingSummary, desync: "legacy" }
10404
+ };
10405
+ const all = lines.filter((l) => !isEventLine(l)).map(cleanMsg);
9778
10406
  const result = [
9779
10407
  { role: "system", content: systemPrompt },
9780
10408
  // 存储系统提示词
9781
- { role: "system", content: store.rollingSummary }
10409
+ { role: "system", content: recovery.compaction.rollingSummary }
9782
10410
  // 后续存储摘要使用
9783
10411
  ];
9784
- const archivedMessageCount = store.archivedMessageCount;
10412
+ const archivedMessageCount = recovery.compaction.archivedMessageCount;
9785
10413
  const messageAll = all.slice(archivedMessageCount);
9786
10414
  if (messageAll.length === 0) {
9787
10415
  result.push(currentUserMsg);
9788
10416
  return result;
9789
10417
  }
9790
- result.push(...decayOldToolResults(repairOrphanToolCalls(messageAll)));
10418
+ const repair = repairOrphanToolCalls(messageAll, recovery.interruption.kind === "crashed");
10419
+ result.push(...decayOldToolResults(repair.msgs));
9791
10420
  result.push(currentUserMsg);
9792
10421
  return result;
9793
10422
  };
@@ -9851,7 +10480,8 @@ var init_subagent = __esm({
9851
10480
  parentSystemPrompt
9852
10481
  ].join("\n");
9853
10482
  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));
10483
+ const wantsMcp = manifest?.tools.some((t) => t === "mcp_call" || t === "mcp_list_tools" || t.startsWith("mcp__")) ?? false;
10484
+ 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
10485
  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
10486
  try {
9857
10487
  const subMessages = await buildContextMessages(subSessionId, { role: "user", content: task }, subSystem);
@@ -9917,7 +10547,7 @@ var init_subagent = __esm({
9917
10547
  // ../core/src/hooks/loader.ts
9918
10548
  import fs27 from "fs/promises";
9919
10549
  import path29 from "path";
9920
- var readHooksConfig, appendValidated, validateRule, parseAgentDecision, compileRule2, loadHooks, initHooks;
10550
+ var readHooksConfig, appendValidated, validateRule, parseStdoutDecision, parseAgentDecision, compileRule2, loadHooks, initHooks;
9921
10551
  var init_loader4 = __esm({
9922
10552
  "../core/src/hooks/loader.ts"() {
9923
10553
  "use strict";
@@ -10061,6 +10691,24 @@ var init_loader4 = __esm({
10061
10691
  requireApproval: raw.requireApproval === true ? true : void 0
10062
10692
  };
10063
10693
  };
10694
+ parseStdoutDecision = (stdout) => {
10695
+ const s = (stdout ?? "").trim();
10696
+ if (!s || !s.startsWith("{")) return void 0;
10697
+ try {
10698
+ const parsed = JSON.parse(s);
10699
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
10700
+ const out = {};
10701
+ if (parsed.deny === true) {
10702
+ out.deny = true;
10703
+ if (typeof parsed.reason === "string" && parsed.reason) out.reason = parsed.reason;
10704
+ }
10705
+ if (parsed.argsOverride !== void 0) out.argsOverride = parsed.argsOverride;
10706
+ if (typeof parsed.resultOverride === "string" && parsed.resultOverride) out.resultOverride = parsed.resultOverride;
10707
+ return out.deny !== void 0 || out.argsOverride !== void 0 || out.resultOverride !== void 0 ? out : void 0;
10708
+ } catch {
10709
+ return void 0;
10710
+ }
10711
+ };
10064
10712
  parseAgentDecision = (output) => {
10065
10713
  const lines = (output ?? "").split(/\r?\n/);
10066
10714
  for (let i = lines.length - 1; i >= 0; i--) {
@@ -10129,9 +10777,15 @@ var init_loader4 = __esm({
10129
10777
  }
10130
10778
  try {
10131
10779
  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 };
10780
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
10781
+ if (parsed.deny === true) {
10782
+ const reason = typeof parsed.reason === "string" ? parsed.reason : `hook http \u62D2\u7EDD\uFF08status ${res.status}\uFF09`;
10783
+ return { deny: true, reason };
10784
+ }
10785
+ const out = {};
10786
+ if (parsed.argsOverride !== void 0) out.argsOverride = parsed.argsOverride;
10787
+ if (typeof parsed.resultOverride === "string" && parsed.resultOverride) out.resultOverride = parsed.resultOverride;
10788
+ if (out.argsOverride !== void 0 || out.resultOverride !== void 0) return out;
10135
10789
  }
10136
10790
  } catch {
10137
10791
  }
@@ -10160,6 +10814,10 @@ var init_loader4 = __esm({
10160
10814
  const detail = res.stderr ? `\uFF1A${res.stderr.slice(0, 200)}` : "";
10161
10815
  return { deny: true, reason: `hook \u9000\u51FA\u7801 ${res.exitCode}${detail}` };
10162
10816
  }
10817
+ if (res.exitCode === 0) {
10818
+ const out = parseStdoutDecision(res.stdout);
10819
+ if (out) return out;
10820
+ }
10163
10821
  return { deny: false };
10164
10822
  }
10165
10823
  };
@@ -10394,9 +11052,9 @@ var init_loader6 = __esm({
10394
11052
  );
10395
11053
  for (const { item: m, file, source } of picked) {
10396
11054
  if (m.tools.length > 0) {
10397
- const unknown = m.tools.filter((t) => !known.has(t));
11055
+ const unknown = m.tools.filter((t) => !known.has(t) && !t.startsWith("mcp__"));
10398
11056
  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));
11057
+ m.tools = m.tools.filter((t) => known.has(t) || t.startsWith("mcp__"));
10400
11058
  }
10401
11059
  if (source === "project") {
10402
11060
  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 +11920,124 @@ ${lines.join("\n")}
11262
11920
  }
11263
11921
  });
11264
11922
 
11923
+ // ../core/src/session/fork.ts
11924
+ import fs37 from "fs/promises";
11925
+ var listForkAnchors, derivePrefix, forkSession;
11926
+ var init_fork = __esm({
11927
+ "../core/src/session/fork.ts"() {
11928
+ "use strict";
11929
+ init_common();
11930
+ init_transcript();
11931
+ init_store();
11932
+ listForkAnchors = (lines) => {
11933
+ const anchors = [];
11934
+ let lastUser = "";
11935
+ for (const l of lines) {
11936
+ if (isEventLine(l)) continue;
11937
+ const row = l;
11938
+ if (row?.role === "user") {
11939
+ lastUser = typeof row.content === "string" ? row.content : "";
11940
+ } else if (row?.role === "assistant") {
11941
+ const content = typeof row.content === "string" ? row.content : "";
11942
+ const names = Array.isArray(row.tool_calls) ? row.tool_calls.map((tc) => tc?.function?.name ?? "").filter(Boolean).join(", ") : "";
11943
+ anchors.push({
11944
+ lineId: row.id,
11945
+ roundNo: anchors.length + 1,
11946
+ userPreview: lastUser,
11947
+ assistantPreview: content || (names ? `\u{1F527} ${names}` : "(\u7A7A\u56DE\u590D)")
11948
+ });
11949
+ }
11950
+ }
11951
+ return anchors;
11952
+ };
11953
+ derivePrefix = (lines, upToLineId) => {
11954
+ let endIdx = lines.length - 1;
11955
+ if (upToLineId !== void 0) {
11956
+ const idx = lines.findIndex((l) => l?.id === upToLineId);
11957
+ if (idx < 0) throw new Error(`upToLineId\uFF08${upToLineId}\uFF09\u5728\u6E90\u4F1A\u8BDD\u8F6C\u5F55\u4E2D\u672A\u627E\u5230`);
11958
+ endIdx = idx;
11959
+ } else {
11960
+ let lastEnd = -1;
11961
+ let lastStart = -1;
11962
+ lines.forEach((l, i) => {
11963
+ if (!isEventLine(l)) return;
11964
+ if (l.dscEvent === "run.end" || l.dscEvent === "run.abandoned") lastEnd = i;
11965
+ else if (l.dscEvent === "run.start") lastStart = i;
11966
+ });
11967
+ if (lastEnd >= 0) endIdx = lastEnd;
11968
+ else if (lastStart >= 0) endIdx = lastStart - 1;
11969
+ }
11970
+ const prefix = endIdx < 0 ? [] : lines.slice(0, endIdx + 1);
11971
+ let archivedMessageCount = 0;
11972
+ let rollingSummary = "";
11973
+ let lastStartRunId;
11974
+ const closed = /* @__PURE__ */ new Set();
11975
+ for (const l of prefix) {
11976
+ if (!isEventLine(l)) continue;
11977
+ if (l.dscEvent === "compaction") {
11978
+ archivedMessageCount = l.archivedMessageCount;
11979
+ rollingSummary = l.summary;
11980
+ } else if (l.dscEvent === "run.start") {
11981
+ lastStartRunId = l.runId;
11982
+ } else if (l.dscEvent === "run.end" || l.dscEvent === "run.abandoned") {
11983
+ closed.add(l.runId);
11984
+ }
11985
+ }
11986
+ const unclosedRunId = lastStartRunId && !closed.has(lastStartRunId) ? lastStartRunId : void 0;
11987
+ return { lines: prefix, archivedMessageCount, rollingSummary, unclosedRunId };
11988
+ };
11989
+ forkSession = async (sourceId, upToLineId) => {
11990
+ assertSafeSessionId(sourceId);
11991
+ const lines = await readTranscriptLines(sourceId);
11992
+ if (lines.length === 0) throw new Error("\u6E90\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u8F6C\u5F55\u4E3A\u7A7A");
11993
+ const prefix = derivePrefix(lines, upToLineId);
11994
+ const newId = createUUID();
11995
+ await ensureSessionsDir(newId);
11996
+ const rawText = await fs37.readFile(getTranscriptPath(sourceId), "utf-8");
11997
+ const kept = [];
11998
+ let good = 0;
11999
+ for (const s of rawText.split("\n")) {
12000
+ if (good >= prefix.lines.length) break;
12001
+ kept.push(s);
12002
+ const t = s.trim();
12003
+ if (!t) continue;
12004
+ try {
12005
+ JSON.parse(t);
12006
+ good++;
12007
+ } catch {
12008
+ }
12009
+ }
12010
+ let payload = kept.join("\n");
12011
+ if (payload.length > 0 && !payload.endsWith("\n")) payload += "\n";
12012
+ await fs37.writeFile(getTranscriptPath(newId), payload, "utf-8");
12013
+ const srcState = await readStore(sourceId);
12014
+ const srcRolling = await getRollingState(sourceId);
12015
+ const legacy = !lines.some(isEventLine);
12016
+ const wholeFile = prefix.lines.length === lines.length;
12017
+ const archivedMessageCount = legacy && wholeFile ? srcRolling.archivedMessageCount : prefix.archivedMessageCount;
12018
+ const rollingSummary = legacy && wholeFile ? srcRolling.rollingSummary : prefix.rollingSummary;
12019
+ const srcTitle = typeof srcState?.title === "string" && srcState.title ? srcState.title : "\u4F1A\u8BDD";
12020
+ await writeStore(newId, {
12021
+ sessionId: newId,
12022
+ createAt: (/* @__PURE__ */ new Date()).toISOString(),
12023
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
12024
+ archivedMessageCount,
12025
+ rollingSummary,
12026
+ consecutiveFailures: 0,
12027
+ title: `${srcTitle}(\u5206\u53C9)`.slice(0, 200),
12028
+ // ★ 校准数据透传:fork 首轮压缩判定不从 1.4 重来(纯收益,语义同源会话续接)
12029
+ calibRatio: srcRolling.calibRatio,
12030
+ lastRealPromptTokens: srcRolling.lastRealPromptTokens,
12031
+ lastCachedTokens: srcRolling.lastCachedTokens
12032
+ });
12033
+ if (prefix.unclosedRunId) {
12034
+ await appendEvent(newId, { dscEvent: "run.abandoned", runId: prefix.unclosedRunId, reason: "forked from interrupted run" });
12035
+ }
12036
+ return { sessionId: newId, copiedLines: prefix.lines.length, archivedMessageCount };
12037
+ };
12038
+ }
12039
+ });
12040
+
11265
12041
  // src/cliHost.ts
11266
12042
  var createCliRequestApproval, createCliRequestQuestion;
11267
12043
  var init_cliHost = __esm({
@@ -11346,13 +12122,47 @@ var init_util = __esm({
11346
12122
 
11347
12123
  // src/replay.ts
11348
12124
  var buildReplayRows;
11349
- var init_replay = __esm({
12125
+ var init_replay2 = __esm({
11350
12126
  "src/replay.ts"() {
11351
12127
  "use strict";
11352
- buildReplayRows = (msgs, nid) => {
12128
+ init_transcript();
12129
+ buildReplayRows = (lines, nid) => {
11353
12130
  const rows = [];
11354
12131
  const pending = /* @__PURE__ */ new Map();
11355
- for (const m of msgs) {
12132
+ let lastStartRunId;
12133
+ const closedRuns = /* @__PURE__ */ new Set();
12134
+ const attachUsage = (u) => {
12135
+ for (let i = rows.length - 1; i >= 0; i--) {
12136
+ const r = rows[i];
12137
+ if (r.kind === "assistant") {
12138
+ rows[i] = {
12139
+ ...r,
12140
+ usage: {
12141
+ ...r.usage ?? {},
12142
+ prompt_tokens: u.prompt_tokens,
12143
+ completion_tokens: u.completion_tokens,
12144
+ total_tokens: u.total_tokens,
12145
+ prompt_cache_hit_tokens: u.cached_tokens
12146
+ }
12147
+ };
12148
+ return;
12149
+ }
12150
+ }
12151
+ };
12152
+ for (const line of lines) {
12153
+ if (isEventLine(line)) {
12154
+ if (line.dscEvent === "run.start") lastStartRunId = line.runId;
12155
+ else if (line.dscEvent === "run.end" || line.dscEvent === "run.abandoned") closedRuns.add(line.runId);
12156
+ if ((line.dscEvent === "round.end" || line.dscEvent === "run.end") && line.usage && (line.usage.prompt_tokens != null || line.usage.total_tokens != null)) {
12157
+ const last = [...rows].reverse().find((r) => r.kind === "assistant");
12158
+ if (line.dscEvent === "round.end" || !last?.usage) attachUsage(line.usage);
12159
+ }
12160
+ if (line.dscEvent === "run.abandoned") {
12161
+ rows.push({ id: nid(), kind: "info", text: "\u26A0\uFE0F \u6B64\u56DE\u5408\u88AB\u4E2D\u65AD\uFF0C\u672A\u5B8C\u6210" });
12162
+ }
12163
+ continue;
12164
+ }
12165
+ const m = line;
11356
12166
  const role = m?.role;
11357
12167
  if (role === "user") {
11358
12168
  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 +12212,9 @@ var init_replay = __esm({
11402
12212
  rows[idx] = { ...r, result: r.result ?? "", ok: false, status: "done" };
11403
12213
  }
11404
12214
  }
12215
+ if (lastStartRunId && !closedRuns.has(lastStartRunId)) {
12216
+ rows.push({ id: nid(), kind: "info", text: "\u26A0\uFE0F \u6B64\u56DE\u5408\u88AB\u4E2D\u65AD\uFF0C\u672A\u5B8C\u6210" });
12217
+ }
11405
12218
  return rows;
11406
12219
  };
11407
12220
  }
@@ -11416,13 +12229,15 @@ var init_useChatState = __esm({
11416
12229
  init_chatProcessing();
11417
12230
  init_store();
11418
12231
  init_transcript();
12232
+ init_fork();
12233
+ init_inbox();
11419
12234
  init_contextCore();
11420
12235
  init_createModel();
11421
12236
  init_tool();
11422
12237
  init_cliHost();
11423
12238
  init_strings();
11424
12239
  init_util();
11425
- init_replay();
12240
+ init_replay2();
11426
12241
  FLUSH_MS = 120;
11427
12242
  ABORT_GRACE_MS = 8e3;
11428
12243
  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 +12255,7 @@ var init_useChatState = __esm({
11440
12255
  const [pendingQuestion, setPendingQuestion] = useState(null);
11441
12256
  const [pendingPlan, setPendingPlan] = useState(null);
11442
12257
  const [pendingSessions, setPendingSessions] = useState(null);
12258
+ const [pendingFork, setPendingFork] = useState(null);
11443
12259
  const nextId = useRef(1);
11444
12260
  const assistantStreamingId = useRef(null);
11445
12261
  const thinkingStreamingId = useRef(null);
@@ -11452,6 +12268,7 @@ var init_useChatState = __esm({
11452
12268
  const currentAcRef = useRef(null);
11453
12269
  const turnGenRef = useRef(0);
11454
12270
  const abortGuardRef = useRef(null);
12271
+ const queuedUnclaimedRef = useRef(0);
11455
12272
  const proposedPlanRef = useRef(null);
11456
12273
  const enterPlanReasonRef = useRef(null);
11457
12274
  const modelRef = useRef("");
@@ -11466,8 +12283,8 @@ var init_useChatState = __esm({
11466
12283
  if (!initialSessionId) return;
11467
12284
  void (async () => {
11468
12285
  try {
11469
- const msgs = await readMessages(initialSessionId);
11470
- const seeded = buildReplayRows(msgs, newRowId);
12286
+ const lines = await readTranscriptLines(initialSessionId);
12287
+ const seeded = buildReplayRows(lines, newRowId);
11471
12288
  if (seeded.length) setRows(seeded);
11472
12289
  } catch {
11473
12290
  }
@@ -11650,10 +12467,18 @@ var init_useChatState = __esm({
11650
12467
  }
11651
12468
  case "approval_request":
11652
12469
  break;
12470
+ case "inbox.claimed": {
12471
+ const n = Array.isArray(obj.texts) ? obj.texts.length : 0;
12472
+ if (n > 0) {
12473
+ queuedUnclaimedRef.current = Math.max(0, queuedUnclaimedRef.current - n);
12474
+ pushInfo(S.inboxClaimed(n));
12475
+ }
12476
+ break;
12477
+ }
11653
12478
  default:
11654
12479
  break;
11655
12480
  }
11656
- }, [closeStreaming, flush, scheduleFlush]);
12481
+ }, [closeStreaming, flush, scheduleFlush, pushInfo]);
11657
12482
  const askApproval = useCallback((detail, toolName) => new Promise((resolve) => setPendingApproval({ detail, toolName, resolve })), []);
11658
12483
  const resolveApproval = useCallback((v) => {
11659
12484
  setPendingApproval((prev) => {
@@ -11733,9 +12558,13 @@ var init_useChatState = __esm({
11733
12558
  clearTimeout(abortGuardRef.current);
11734
12559
  abortGuardRef.current = null;
11735
12560
  }
12561
+ if (queuedUnclaimedRef.current > 0) {
12562
+ pushInfo(S.inboxCarryover(queuedUnclaimedRef.current));
12563
+ queuedUnclaimedRef.current = 0;
12564
+ }
11736
12565
  }
11737
12566
  }
11738
- }, [askApproval, autoRequestApproval, flush, onTrace, pushEvent]);
12567
+ }, [askApproval, autoRequestApproval, flush, onTrace, pushEvent, pushInfo]);
11739
12568
  const approveAndImplement = useCallback(async (sid, plan) => {
11740
12569
  const res = await setPlan(plan);
11741
12570
  if (res.action === "accept") {
@@ -11744,15 +12573,27 @@ var init_useChatState = __esm({
11744
12573
  await runOnce(sid, `\uFF08\u7528\u6237\u5DF2\u6279\u51C6\u4EE5\u4E0B\u65B9\u6848\uFF0C\u8BF7\u4E25\u683C\u6309\u65B9\u6848\u5F00\u59CB\u5B9E\u73B0\uFF09\uFF1A
11745
12574
 
11746
12575
  ${finalPlan}`, false, res.autoExecute);
11747
- } else {
11748
- pushInfo(S.planRejected);
12576
+ return true;
11749
12577
  }
12578
+ planModeRef.current = true;
12579
+ pushInfo(S.planKeepPlanning);
12580
+ return false;
11750
12581
  }, [runOnce, setPlan, pushInfo]);
11751
12582
  const runPlanStage = useCallback(async (sid, researchPrompt) => {
11752
12583
  await runOnce(sid, researchPrompt, true);
11753
12584
  const plan = takeProposedPlan();
11754
- if (plan != null) await approveAndImplement(sid, plan);
12585
+ if (plan == null) return false;
12586
+ return await approveAndImplement(sid, plan);
11755
12587
  }, [runOnce, takeProposedPlan, approveAndImplement]);
12588
+ const queueInput = useCallback((content) => {
12589
+ const text = content.trim();
12590
+ const sid = sessionIdRef.current;
12591
+ if (!text || !sid || !pushSessionInbox(sid, text)) return false;
12592
+ pushUser(text);
12593
+ pushInfo(S.inboxQueued);
12594
+ queuedUnclaimedRef.current++;
12595
+ return true;
12596
+ }, [pushUser, pushInfo]);
11756
12597
  const submit = useCallback(async (content) => {
11757
12598
  const text = content.trim();
11758
12599
  if (!text || busy) return;
@@ -11765,7 +12606,8 @@ ${finalPlan}`, false, res.autoExecute);
11765
12606
  const sid = sessionIdRef.current;
11766
12607
  pushUser(text);
11767
12608
  if (planModeRef.current) {
11768
- await runPlanStage(sid, text);
12609
+ const kept = await runPlanStage(sid, text);
12610
+ if (kept) planModeRef.current = false;
11769
12611
  } else {
11770
12612
  await runOnce(sid, text, false);
11771
12613
  const proposedPlan = takeProposedPlan();
@@ -11776,8 +12618,8 @@ ${finalPlan}`, false, res.autoExecute);
11776
12618
  if (enterReason != null) {
11777
12619
  pushInfo(`\u{1F4CB} \u6A21\u578B\u8BF7\u6C42\u8FDB\u5165\u8BA1\u5212\u6A21\u5F0F${enterReason ? `\uFF1A${enterReason}` : ""}\uFF0C\u5DF2\u5207\u6362\u2026`);
11778
12620
  planModeRef.current = true;
11779
- await runPlanStage(sid, ENTER_PLAN_RESEARCH_PROMPT);
11780
- planModeRef.current = false;
12621
+ const kept = await runPlanStage(sid, ENTER_PLAN_RESEARCH_PROMPT);
12622
+ if (kept) planModeRef.current = false;
11781
12623
  }
11782
12624
  }
11783
12625
  }
@@ -11813,8 +12655,8 @@ ${finalPlan}`, false, res.autoExecute);
11813
12655
  sessionIdRef.current = id;
11814
12656
  clearRows();
11815
12657
  try {
11816
- const msgs = await readMessages(id);
11817
- const seeded = buildReplayRows(msgs, newRowId);
12658
+ const lines = await readTranscriptLines(id);
12659
+ const seeded = buildReplayRows(lines, newRowId);
11818
12660
  if (seeded.length) setRows(seeded);
11819
12661
  } catch {
11820
12662
  }
@@ -11835,6 +12677,37 @@ ${finalPlan}`, false, res.autoExecute);
11835
12677
  const picked = await new Promise((resolve) => setPendingSessions({ sessions: sessions2, resolve }));
11836
12678
  if (picked) await loadSession(picked);
11837
12679
  }, [pushInfo, loadSession]);
12680
+ const resolveFork = useCallback((anchor) => {
12681
+ setPendingFork((prev) => {
12682
+ prev?.resolve(anchor);
12683
+ return null;
12684
+ });
12685
+ }, []);
12686
+ const openForkPicker = useCallback(async () => {
12687
+ const sid = sessionIdRef.current;
12688
+ if (!sid) {
12689
+ pushInfo(S.forkEmpty);
12690
+ return;
12691
+ }
12692
+ let anchors = [];
12693
+ try {
12694
+ anchors = listForkAnchors(await readTranscriptLines(sid));
12695
+ } catch {
12696
+ }
12697
+ if (anchors.length === 0) {
12698
+ pushInfo(S.forkEmpty);
12699
+ return;
12700
+ }
12701
+ const picked = await new Promise((resolve) => setPendingFork({ anchors, resolve }));
12702
+ if (!picked) return;
12703
+ try {
12704
+ const r = await forkSession(sid, picked.lineId);
12705
+ await loadSession(r.sessionId);
12706
+ pushInfo(S.forkDone(truncateMiddle(r.sessionId, 12), picked.roundNo));
12707
+ } catch (e) {
12708
+ pushInfo(S.forkFailed(e?.message ?? String(e)));
12709
+ }
12710
+ }, [pushInfo, loadSession]);
11838
12711
  const setModelOverride = useCallback((m) => {
11839
12712
  modelRef.current = m;
11840
12713
  }, []);
@@ -11864,9 +12737,11 @@ ${finalPlan}`, false, res.autoExecute);
11864
12737
  pendingQuestion,
11865
12738
  pendingPlan,
11866
12739
  pendingSessions,
12740
+ pendingFork,
11867
12741
  sessionIdRef,
11868
12742
  // 动作
11869
12743
  submit,
12744
+ queueInput,
11870
12745
  abortCurrent,
11871
12746
  pushUser,
11872
12747
  pushInfo,
@@ -11889,7 +12764,9 @@ ${finalPlan}`, false, res.autoExecute);
11889
12764
  getOutputStyle: getOutputStyle2,
11890
12765
  openSessionPicker,
11891
12766
  resolveSession,
11892
- loadSession
12767
+ loadSession,
12768
+ openForkPicker,
12769
+ resolveFork
11893
12770
  };
11894
12771
  };
11895
12772
  }
@@ -12006,6 +12883,8 @@ var init_MessageBlock = __esm({
12006
12883
  return { bullet: `${m[2]} `, rest: m[3] };
12007
12884
  };
12008
12885
  AssistantLine = ({ line }) => {
12886
+ const heading = line.match(/^#{1,6}\s+(.*)$/);
12887
+ if (heading) return /* @__PURE__ */ jsx3(Text3, { bold: true, color: "#ffffff", children: heading[1] });
12009
12888
  const list = listPrefix(line);
12010
12889
  if (list) {
12011
12890
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
@@ -12155,6 +13034,149 @@ var init_ThinkingBlock = __esm({
12155
13034
  }
12156
13035
  });
12157
13036
 
13037
+ // src/lineDiff.ts
13038
+ var splitLines, lineDiff, collapseContext;
13039
+ var init_lineDiff = __esm({
13040
+ "src/lineDiff.ts"() {
13041
+ "use strict";
13042
+ splitLines = (s) => {
13043
+ const t = String(s ?? "").replace(/\r\n/g, "\n");
13044
+ return t === "" ? [] : t.split("\n");
13045
+ };
13046
+ lineDiff = (oldStr, newStr) => {
13047
+ const a = splitLines(oldStr);
13048
+ const b = splitLines(newStr);
13049
+ let s = 0;
13050
+ while (s < a.length && s < b.length && a[s] === b[s]) s++;
13051
+ let e = 0;
13052
+ while (e < a.length - s && e < b.length - s && a[a.length - 1 - e] === b[b.length - 1 - e]) e++;
13053
+ const m1 = a.slice(s, a.length - e);
13054
+ const m2 = b.slice(s, b.length - e);
13055
+ const rows = [];
13056
+ for (let i = 0; i < s; i++) rows.push({ t: "ctx", s: a[i] });
13057
+ const n = m1.length, m = m2.length;
13058
+ if (n === 0 && m === 0) {
13059
+ } else if (n * m > 4e6) {
13060
+ for (const l of m1) rows.push({ t: "del", s: l });
13061
+ for (const l of m2) rows.push({ t: "add", s: l });
13062
+ } else {
13063
+ const w = m + 1;
13064
+ const dp = new Uint32Array((n + 1) * w);
13065
+ for (let i2 = n - 1; i2 >= 0; i2--) {
13066
+ for (let j2 = m - 1; j2 >= 0; j2--) {
13067
+ dp[i2 * w + j2] = m1[i2] === m2[j2] ? dp[(i2 + 1) * w + j2 + 1] + 1 : Math.max(dp[(i2 + 1) * w + j2], dp[i2 * w + j2 + 1]);
13068
+ }
13069
+ }
13070
+ let i = 0, j = 0;
13071
+ while (i < n && j < m) {
13072
+ if (m1[i] === m2[j]) {
13073
+ rows.push({ t: "ctx", s: m1[i] });
13074
+ i++;
13075
+ j++;
13076
+ } else if (dp[(i + 1) * w + j] >= dp[i * w + j + 1]) {
13077
+ rows.push({ t: "del", s: m1[i] });
13078
+ i++;
13079
+ } else {
13080
+ rows.push({ t: "add", s: m2[j] });
13081
+ j++;
13082
+ }
13083
+ }
13084
+ while (i < n) {
13085
+ rows.push({ t: "del", s: m1[i] });
13086
+ i++;
13087
+ }
13088
+ while (j < m) {
13089
+ rows.push({ t: "add", s: m2[j] });
13090
+ j++;
13091
+ }
13092
+ }
13093
+ for (let k = 0; k < e; k++) rows.push({ t: "ctx", s: a[a.length - e + k] });
13094
+ return rows;
13095
+ };
13096
+ collapseContext = (rows, keep = 2) => {
13097
+ const out = [];
13098
+ let i = 0;
13099
+ while (i < rows.length) {
13100
+ if (rows[i].t !== "ctx") {
13101
+ out.push(rows[i]);
13102
+ i++;
13103
+ continue;
13104
+ }
13105
+ let j = i;
13106
+ while (j < rows.length && rows[j].t === "ctx") j++;
13107
+ const run = j - i;
13108
+ if (run > keep * 2 + 1) {
13109
+ for (let k = 0; k < keep; k++) out.push(rows[i + k]);
13110
+ out.push({ t: "ellip", n: run - keep * 2 });
13111
+ for (let k = run - keep; k < run; k++) out.push(rows[i + k]);
13112
+ } else {
13113
+ for (let k = 0; k < run; k++) out.push(rows[i + k]);
13114
+ }
13115
+ i = j;
13116
+ }
13117
+ return out;
13118
+ };
13119
+ }
13120
+ });
13121
+
13122
+ // src/diffView.ts
13123
+ var EDIT_TOOL_NAMES, extractEditPairs, pairsToDisplayRows, parseApprovalDiff;
13124
+ var init_diffView = __esm({
13125
+ "src/diffView.ts"() {
13126
+ "use strict";
13127
+ init_lineDiff();
13128
+ EDIT_TOOL_NAMES = ["edit_file", "create_file", "write_file"];
13129
+ extractEditPairs = (toolName, args) => {
13130
+ if (args == null || typeof args !== "object") return null;
13131
+ const o = args;
13132
+ if (toolName === "edit_file") {
13133
+ const list = Array.isArray(o.edits) && o.edits.length > 0 ? o.edits : [o];
13134
+ const pairs = list.filter((e) => typeof e.old_str === "string" && e.old_str.length > 0);
13135
+ if (pairs.length === 0) return null;
13136
+ return pairs.map((e, i) => ({
13137
+ label: pairs.length > 1 ? `\u7B2C ${i + 1} \u5904${e.replace_all ? "\uFF08\u6279\u91CF\u66FF\u6362\u5168\u90E8\u5339\u914D\uFF09" : ""}` : "",
13138
+ old: e.old_str,
13139
+ new: typeof e.new_str === "string" ? e.new_str : ""
13140
+ }));
13141
+ }
13142
+ if (toolName === "create_file" || toolName === "write_file") {
13143
+ return typeof o.content === "string" && o.content.length > 0 ? [{ label: "", old: "", new: o.content }] : null;
13144
+ }
13145
+ return null;
13146
+ };
13147
+ pairsToDisplayRows = (pairs, keep = 2) => {
13148
+ const out = [];
13149
+ let changed = false;
13150
+ for (const p of pairs) {
13151
+ const collapsed = collapseContext(lineDiff(p.old, p.new), keep);
13152
+ if (!collapsed.some((r) => r.t !== "ctx" && r.t !== "ellip")) continue;
13153
+ changed = true;
13154
+ if (p.label) {
13155
+ if (out.length > 0) out.push({ t: "ellip", n: 0 });
13156
+ out.push({ t: "ctx", s: `\u2014\u2014 ${p.label} \u2014\u2014` });
13157
+ }
13158
+ out.push(...collapsed);
13159
+ }
13160
+ return changed ? out : null;
13161
+ };
13162
+ parseApprovalDiff = (detail, toolName) => {
13163
+ if (toolName !== "edit_file" || !detail.includes("\u3010\u51CF\u5C11\u3011:")) return null;
13164
+ const parts = detail.split(/\n(?=—— 第 )/);
13165
+ const sections = [];
13166
+ for (const part of parts.slice(1)) {
13167
+ const label = (part.match(/^—— (第 \d+ 处[^\n]*)——/)?.[1] ?? "").trim();
13168
+ const rIdx = part.indexOf("\u3010\u51CF\u5C11\u3011:");
13169
+ const aIdx = part.indexOf("\u3010\u589E\u52A0\u3011:");
13170
+ if (rIdx < 0 || aIdx < 0 || aIdx < rIdx) continue;
13171
+ const old = part.slice(rIdx + "\u3010\u51CF\u5C11\u3011:".length, aIdx).replace(/^\n+|\n+$/g, "");
13172
+ const neu = part.slice(aIdx + "\u3010\u589E\u52A0\u3011:".length).replace(/^\n+|\n+$/g, "");
13173
+ sections.push({ label, old, new: neu });
13174
+ }
13175
+ return sections.length > 0 ? { header: parts[0], sections } : null;
13176
+ };
13177
+ }
13178
+ });
13179
+
12158
13180
  // src/components/ToolCard.tsx
12159
13181
  import { Box as Box3, Text as Text5 } from "ink";
12160
13182
  import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -12164,6 +13186,7 @@ var init_ToolCard = __esm({
12164
13186
  "use strict";
12165
13187
  init_theme();
12166
13188
  init_util();
13189
+ init_diffView();
12167
13190
  ToolCard = ({ toolName, args, result, ok, status, progress, wrapW }) => {
12168
13191
  const tool = findTool(toolName);
12169
13192
  const level = tool?.function?.safetyLevel;
@@ -12180,6 +13203,15 @@ var init_ToolCard = __esm({
12180
13203
  const last = progress.replace(/\r/g, "").split("\n").map((s) => s.trim()).filter(Boolean).pop() ?? "";
12181
13204
  return truncateMiddle(last, Math.max(20, wrapW - 6));
12182
13205
  })();
13206
+ const MAX_DIFF_LINES2 = 14;
13207
+ const diffAll = (() => {
13208
+ if (status !== "done" || !ok) return null;
13209
+ if (!EDIT_TOOL_NAMES.includes(toolName)) return null;
13210
+ const pairs = extractEditPairs(toolName, args);
13211
+ return pairs ? pairsToDisplayRows(pairs) : null;
13212
+ })();
13213
+ const diffRows = diffAll ? diffAll.slice(0, MAX_DIFF_LINES2) : null;
13214
+ const diffHidden = diffAll ? Math.max(0, diffAll.length - MAX_DIFF_LINES2) : 0;
12183
13215
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 0.3, marginBottom: 0.3, children: [
12184
13216
  /* @__PURE__ */ jsxs3(Box3, { flexDirection: "row", flexShrink: 0, children: [
12185
13217
  /* @__PURE__ */ jsx5(Text5, { color: markerColor, bold: true, children: "\u23FA " }),
@@ -12193,6 +13225,14 @@ var init_ToolCard = __esm({
12193
13225
  ] }, i)) : status === "running" ? /* @__PURE__ */ jsxs3(Box3, { flexDirection: "row", flexShrink: 0, children: [
12194
13226
  /* @__PURE__ */ jsx5(Text5, { color: THEME.grayDim, children: " \u23BF " }),
12195
13227
  /* @__PURE__ */ jsx5(Text5, { color: THEME.gray, children: runningLine || "\u8FD0\u884C\u4E2D\u2026" })
13228
+ ] }) : null,
13229
+ diffRows && diffRows.length > 0 ? diffRows.map((r, i) => /* @__PURE__ */ jsxs3(Box3, { flexDirection: "row", flexShrink: 0, children: [
13230
+ /* @__PURE__ */ jsx5(Text5, { color: THEME.grayDim, children: " " }),
13231
+ r.t === "ellip" ? /* @__PURE__ */ jsx5(Text5, { color: THEME.grayDim, children: r.n > 0 ? ` \u22EF ${r.n} \u884C\u672A\u53D8` : " \u22EF" }) : /* @__PURE__ */ jsx5(Text5, { color: r.t === "del" ? THEME.danger : r.t === "add" ? THEME.ok : THEME.grayDim, children: `${r.t === "del" ? "- " : r.t === "add" ? "+ " : " "}${truncateMiddle(r.s, Math.max(20, wrapW - 8))}` })
13232
+ ] }, `diff-${i}`)) : null,
13233
+ diffHidden > 0 ? /* @__PURE__ */ jsxs3(Box3, { flexDirection: "row", flexShrink: 0, children: [
13234
+ /* @__PURE__ */ jsx5(Text5, { color: THEME.grayDim, children: " " }),
13235
+ /* @__PURE__ */ jsx5(Text5, { color: THEME.grayDim, children: ` \u2026\uFF08\u53E6\u6709 ${diffHidden} \u884C\u6539\u52A8\u672A\u663E\u793A\uFF09` })
12196
13236
  ] }) : null
12197
13237
  ] });
12198
13238
  };
@@ -12236,28 +13276,36 @@ var init_TodosPanel = __esm({
12236
13276
 
12237
13277
  // src/components/ApprovalModal.tsx
12238
13278
  import { Box as Box5, Text as Text7 } from "ink";
12239
- import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
12240
- var OPTIONS, ApprovalModal;
13279
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
13280
+ var OPTIONS, MAX_DIFF_LINES, ApprovalModal;
12241
13281
  var init_ApprovalModal = __esm({
12242
13282
  "src/components/ApprovalModal.tsx"() {
12243
13283
  "use strict";
12244
13284
  init_theme();
12245
13285
  init_strings();
12246
13286
  init_util();
13287
+ init_diffView();
12247
13288
  OPTIONS = [
12248
13289
  { label: S.approvalAllow, value: "allow-once", color: THEME.ok },
12249
13290
  { label: S.approvalAllowAlways, value: "allow-always", color: THEME.warn },
12250
13291
  { label: S.approvalDeny, value: "deny", color: THEME.danger }
12251
13292
  ];
13293
+ MAX_DIFF_LINES = 18;
12252
13294
  ApprovalModal = ({ toolName, detail, selectedIndex, wrapW }) => {
12253
13295
  const contentW = Math.max(16, wrapW);
13296
+ const parsed = parseApprovalDiff(detail, toolName);
13297
+ const diffRows = parsed ? pairsToDisplayRows(parsed.sections) : null;
12254
13298
  return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "round", borderColor: THEME.warn, paddingX: 1, paddingY: 1, marginTop: 1, children: [
12255
13299
  /* @__PURE__ */ jsxs5(Text7, { color: THEME.warn, bold: true, children: [
12256
13300
  S.approvalTitle,
12257
13301
  " \xB7 ",
12258
13302
  toolName
12259
13303
  ] }),
12260
- /* @__PURE__ */ jsx7(Box5, { flexDirection: "column", marginTop: 0.5, marginBottom: 0.5, children: wrapText(detail || "(\u65E0\u8BF4\u660E)", contentW).map((line, j) => /* @__PURE__ */ jsx7(Text7, { color: THEME.white, children: line }, j)) }),
13304
+ /* @__PURE__ */ jsx7(Box5, { flexDirection: "column", marginTop: 0.5, marginBottom: 0.5, children: parsed && diffRows ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
13305
+ wrapText(parsed.header, contentW).map((line, j) => /* @__PURE__ */ jsx7(Text7, { color: THEME.white, children: line }, `h-${j}`)),
13306
+ diffRows.slice(0, MAX_DIFF_LINES).map((r, i) => /* @__PURE__ */ jsx7(Text7, { color: r.t === "del" ? THEME.danger : r.t === "add" ? THEME.ok : THEME.grayDim, children: r.t === "ellip" ? r.n > 0 ? ` \u22EF ${r.n} \u884C\u672A\u53D8` : " \u22EF" : `${r.t === "del" ? "- " : r.t === "add" ? "+ " : " "}${truncateMiddle(r.s, Math.max(20, contentW - 2))}` }, `d-${i}`)),
13307
+ diffRows.length > MAX_DIFF_LINES ? /* @__PURE__ */ jsx7(Text7, { color: THEME.grayDim, children: ` \u2026\uFF08\u53E6\u6709 ${diffRows.length - MAX_DIFF_LINES} \u884C\u672A\u663E\u793A\uFF09` }) : null
13308
+ ] }) : wrapText(detail || "(\u65E0\u8BF4\u660E)", contentW).map((line, j) => /* @__PURE__ */ jsx7(Text7, { color: THEME.white, children: line }, j)) }),
12261
13309
  /* @__PURE__ */ jsx7(Text7, { color: THEME.grayDim, children: S.approvalPrompt }),
12262
13310
  OPTIONS.map((opt, i) => {
12263
13311
  const active = i === selectedIndex;
@@ -12491,9 +13539,45 @@ var init_SessionPicker = __esm({
12491
13539
  }
12492
13540
  });
12493
13541
 
12494
- // src/components/SlashMenu.tsx
13542
+ // src/components/ForkPicker.tsx
12495
13543
  import { Box as Box11, Text as Text13 } from "ink";
12496
13544
  import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
13545
+ var PAGE_SIZE2, ForkPicker;
13546
+ var init_ForkPicker = __esm({
13547
+ "src/components/ForkPicker.tsx"() {
13548
+ "use strict";
13549
+ init_theme();
13550
+ init_strings();
13551
+ init_util();
13552
+ PAGE_SIZE2 = 8;
13553
+ ForkPicker = ({ anchors, selectedIndex, wrapW }) => {
13554
+ const start = Math.floor(selectedIndex / PAGE_SIZE2) * PAGE_SIZE2;
13555
+ const visible = anchors.slice(start, start + PAGE_SIZE2);
13556
+ const contentW = Math.max(24, wrapW);
13557
+ const userW = Math.max(10, Math.floor(contentW * 0.45));
13558
+ const replyW = Math.max(10, contentW - userW - 4);
13559
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", borderStyle: "round", borderColor: THEME.blue, paddingX: 1, paddingY: 1, marginTop: 1, children: [
13560
+ /* @__PURE__ */ jsx13(Text13, { color: THEME.blue, bold: true, children: S.forkTitle }),
13561
+ /* @__PURE__ */ jsx13(Text13, { color: THEME.grayDim, children: S.forkPrompt }),
13562
+ /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", marginTop: 0.5, children: visible.map((a, i) => {
13563
+ const active = start + i === selectedIndex;
13564
+ const user = truncateMiddle(a.userPreview || "(\u65E0\u63D0\u95EE)", userW);
13565
+ const reply = truncateMiddle(a.assistantPreview, replyW);
13566
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "row", children: [
13567
+ /* @__PURE__ */ jsx13(Text13, { color: active ? THEME.coralBright : void 0, bold: true, children: active ? "\u203A " : " " }),
13568
+ /* @__PURE__ */ jsx13(Text13, { color: active ? THEME.white : THEME.gray, bold: active, children: `#${a.roundNo} ${user}` }),
13569
+ /* @__PURE__ */ jsx13(Text13, { color: THEME.grayDim, children: ` \u2192 ${reply}` })
13570
+ ] }, a.lineId);
13571
+ }) }),
13572
+ 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
13573
+ ] });
13574
+ };
13575
+ }
13576
+ });
13577
+
13578
+ // src/components/SlashMenu.tsx
13579
+ import { Box as Box12, Text as Text14 } from "ink";
13580
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
12497
13581
  var MAX_ROWS, SlashMenu;
12498
13582
  var init_SlashMenu = __esm({
12499
13583
  "src/components/SlashMenu.tsx"() {
@@ -12505,22 +13589,22 @@ var init_SlashMenu = __esm({
12505
13589
  const more = entries.length > MAX_ROWS;
12506
13590
  const cmdW = Math.min(22, Math.max(8, ...visible.map((e) => e.name.length)) + 1);
12507
13591
  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: [
13592
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", borderStyle: "round", borderColor: THEME.coral, paddingX: 1, paddingY: 0, marginTop: 1, children: [
12509
13593
  visible.map((e, i) => {
12510
13594
  const active = i === selectedIndex;
12511
13595
  const desc = e.description.length > descMax ? `${e.description.slice(0, descMax - 1)}\u2026` : e.description;
12512
13596
  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: [
13597
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "row", children: [
13598
+ /* @__PURE__ */ jsx14(Text14, { color: active ? THEME.coralBright : void 0, bold: true, children: active ? "\u276F " : " " }),
13599
+ /* @__PURE__ */ jsxs12(Text14, { color: active ? THEME.white : THEME.gray, bold: active, children: [
12516
13600
  "/",
12517
13601
  e.name
12518
13602
  ] }),
12519
- /* @__PURE__ */ jsx13(Text13, { color: THEME.grayDim, children: " ".repeat(gap) }),
12520
- /* @__PURE__ */ jsx13(Text13, { color: active ? THEME.gray : THEME.grayDim, children: desc })
13603
+ /* @__PURE__ */ jsx14(Text14, { color: THEME.grayDim, children: " ".repeat(gap) }),
13604
+ /* @__PURE__ */ jsx14(Text14, { color: active ? THEME.gray : THEME.grayDim, children: desc })
12521
13605
  ] }, e.name);
12522
13606
  }),
12523
- more ? /* @__PURE__ */ jsxs11(Text13, { color: THEME.coralMuted, children: [
13607
+ more ? /* @__PURE__ */ jsxs12(Text14, { color: THEME.coralMuted, children: [
12524
13608
  "\u2026 \u8FD8\u6709 ",
12525
13609
  entries.length - MAX_ROWS,
12526
13610
  " \u6761"
@@ -12531,8 +13615,8 @@ var init_SlashMenu = __esm({
12531
13615
  });
12532
13616
 
12533
13617
  // 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";
13618
+ import { Box as Box13, Text as Text15 } from "ink";
13619
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
12536
13620
  var StatusStrip;
12537
13621
  var init_StatusStrip = __esm({
12538
13622
  "src/components/StatusStrip.tsx"() {
@@ -12549,18 +13633,18 @@ var init_StatusStrip = __esm({
12549
13633
  `state: ${stateText}`,
12550
13634
  sessionShort ? `session: ${sessionShort}` : ""
12551
13635
  ].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 })
13636
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexShrink: 0, children: [
13637
+ /* @__PURE__ */ jsx15(Text15, { color: THEME.coralMuted, children: customLine && customLine.trim() ? customLine : bits.join(" \xB7 ") }),
13638
+ /* @__PURE__ */ jsx15(Text15, { color: stateColor, children: "" }),
13639
+ /* @__PURE__ */ jsx15(Text15, { color: THEME.grayDim, children: S.cmdHint })
12556
13640
  ] });
12557
13641
  };
12558
13642
  }
12559
13643
  });
12560
13644
 
12561
13645
  // 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";
13646
+ import { Box as Box14, Text as Text16 } from "ink";
13647
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
12564
13648
  var ROBOT, INNER_H, TopPanel;
12565
13649
  var init_TopPanel = __esm({
12566
13650
  "src/components/TopPanel.tsx"() {
@@ -12580,36 +13664,36 @@ var init_TopPanel = __esm({
12580
13664
  const leftW = Math.floor(innerW * 0.33);
12581
13665
  const rightW = innerW - leftW - 1;
12582
13666
  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: [
13667
+ 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)) });
13668
+ return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", width: panelW, marginBottom: 1, children: [
13669
+ /* @__PURE__ */ jsxs14(Text16, { children: [
13670
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.coralBright, bold: true, children: "* Welcome to " }),
13671
+ /* @__PURE__ */ jsx16(Text16, { bold: true, color: THEME.white, children: "deep" }),
13672
+ /* @__PURE__ */ jsx16(Text16, { bold: true, color: THEME.coralBright, children: "Seek" }),
13673
+ /* @__PURE__ */ jsx16(Text16, { bold: true, color: THEME.white, children: "Code" }),
13674
+ /* @__PURE__ */ jsxs14(Text16, { color: THEME.coralMuted, children: [
12591
13675
  " ",
12592
13676
  VERSION
12593
13677
  ] })
12594
13678
  ] }),
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" })
13679
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.grayDim, children: cwd }),
13680
+ /* @__PURE__ */ jsxs14(Box14, { flexDirection: "row", borderStyle: "round", borderColor: THEME.coral, marginTop: 1, paddingX: 0, paddingY: 0, children: [
13681
+ /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", width: leftW, height: INNER_H, alignItems: "center", justifyContent: "center", children: [
13682
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.white, bold: true, children: "Welcome back!" }),
13683
+ ROBOT.map((l, i) => /* @__PURE__ */ jsx16(Text16, { color: THEME.coralBright, children: l }, i)),
13684
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.grayDim, children: "Ctrl+C \u9000\u51FA \xB7 Esc \u4E2D\u6B62" }),
13685
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.grayDim, children: "\u8F93\u5165 / \u67E5\u770B\u547D\u4EE4" })
12602
13686
  ] }),
12603
13687
  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" })
13688
+ /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", width: rightW, height: INNER_H, justifyContent: "space-between", paddingX: 1, children: [
13689
+ /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
13690
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.coralBright, bold: true, children: "Tips for getting started" }),
13691
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.white, children: "\u8F93\u5165\u9700\u6C42\u5F00\u59CB\uFF0C\u8F93\u5165 / \u5524\u51FA\u547D\u4EE4\u3002" })
12608
13692
  ] }),
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" })
13693
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.grayDim, children: "\u2500".repeat(ruleLen) }),
13694
+ /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
13695
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.coralBright, bold: true, children: "What's new" }),
13696
+ /* @__PURE__ */ jsx16(Text16, { color: THEME.white, children: "\u5DF2\u5BF9\u63A5\u672C\u5730 Tool Call \u667A\u80FD\u51FD\u6570\u8C03\u7528\u3002" })
12613
13697
  ] })
12614
13698
  ] })
12615
13699
  ] })
@@ -12619,7 +13703,7 @@ var init_TopPanel = __esm({
12619
13703
  });
12620
13704
 
12621
13705
  // src/inspect.ts
12622
- import fs37 from "fs";
13706
+ import fs38 from "fs";
12623
13707
  var CWD, fmt, readTrace, inspectUsage, inspectContext, inspectPermissions, inspectMcp, inspectHooks, inspectDebug;
12624
13708
  var init_inspect = __esm({
12625
13709
  "src/inspect.ts"() {
@@ -12637,7 +13721,7 @@ var init_inspect = __esm({
12637
13721
  if (!sessionId) return [];
12638
13722
  try {
12639
13723
  const p = await getTraceStorePath(sessionId);
12640
- const text = await fs37.promises.readFile(p, "utf8");
13724
+ const text = await fs38.promises.readFile(p, "utf8");
12641
13725
  const out = [];
12642
13726
  for (const line of text.split("\n")) {
12643
13727
  if (!line.trim()) continue;
@@ -12760,8 +13844,8 @@ __export(App_exports, {
12760
13844
  App: () => App
12761
13845
  });
12762
13846
  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";
13847
+ import { Box as Box15, Static, Text as Text17, useApp, useInput as useInput2, useStdout } from "ink";
13848
+ import { Fragment as Fragment3, jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
12765
13849
  var KNOWN_MODELS, CWD2, RowView, isDynamicRow, App;
12766
13850
  var init_App = __esm({
12767
13851
  "src/App.tsx"() {
@@ -12786,6 +13870,7 @@ var init_App = __esm({
12786
13870
  init_PlanModal();
12787
13871
  init_PlanEditor();
12788
13872
  init_SessionPicker();
13873
+ init_ForkPicker();
12789
13874
  init_SlashMenu();
12790
13875
  init_MultilineInput();
12791
13876
  init_StatusStrip();
@@ -12794,16 +13879,16 @@ var init_App = __esm({
12794
13879
  KNOWN_MODELS = ["deepseek-v4", "deepseek-v4-flash"];
12795
13880
  CWD2 = process.cwd();
12796
13881
  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 });
13882
+ if (row.kind === "todos") return /* @__PURE__ */ jsx17(TodosPanel, { todos: row.todos, wrapW });
13883
+ 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 });
13884
+ 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
13885
  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 })
13886
+ return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", marginTop: 1, children: [
13887
+ /* @__PURE__ */ jsx17(Text17, { color: THEME.grayDim, children: "\u2500".repeat(Math.max(8, wrapW)) }),
13888
+ /* @__PURE__ */ jsx17(MessageBlock, { row, wrapW, streamTail })
12804
13889
  ] });
12805
13890
  }
12806
- return /* @__PURE__ */ jsx16(MessageBlock, { row, wrapW, streamTail });
13891
+ return /* @__PURE__ */ jsx17(MessageBlock, { row, wrapW, streamTail });
12807
13892
  };
12808
13893
  isDynamicRow = (r) => r.kind === "assistant" && !!r.streaming || r.kind === "thinking" && !!r.streaming || r.kind === "tool" && r.status === "running" || r.kind === "todos" && !!r.active;
12809
13894
  App = ({ resumeSessionId, initialPlanMode, initialAutoMode, initialIncludeProject }) => {
@@ -12903,6 +13988,8 @@ var init_App = __esm({
12903
13988
  return S.cmdOutputStyle;
12904
13989
  case "sessions":
12905
13990
  return S.cmdSessions;
13991
+ case "fork":
13992
+ return S.cmdFork;
12906
13993
  case "usage":
12907
13994
  return S.cmdUsage;
12908
13995
  case "context":
@@ -12935,13 +14022,13 @@ var init_App = __esm({
12935
14022
  const q = input.slice(1).toLowerCase();
12936
14023
  return menuEntries.filter((c) => c.name.toLowerCase().startsWith(q));
12937
14024
  }, [input, menuEntries]);
12938
- const menuActive = state.pendingApproval != null || state.pendingQuestion != null || state.pendingPlan != null || state.pendingSessions != null;
14025
+ const menuActive = state.pendingApproval != null || state.pendingQuestion != null || state.pendingPlan != null || state.pendingSessions != null || state.pendingFork != null;
12939
14026
  const slashVisible = !menuActive && input.startsWith("/") && filteredCommands.length > 0;
12940
14027
  const inputActive = !menuActive;
12941
14028
  useEffect4(() => {
12942
14029
  setSelectIdx(0);
12943
14030
  setPlanEditing(false);
12944
- }, [state.pendingApproval, state.pendingQuestion, state.pendingPlan, state.pendingSessions, slashVisible, filteredCommands.length]);
14031
+ }, [state.pendingApproval, state.pendingQuestion, state.pendingPlan, state.pendingSessions, state.pendingFork, slashVisible, filteredCommands.length]);
12945
14032
  useEffect4(() => {
12946
14033
  setQCursor(0);
12947
14034
  setQChecked(/* @__PURE__ */ new Set());
@@ -12960,6 +14047,9 @@ var init_App = __esm({
12960
14047
  case "/sessions":
12961
14048
  void state.openSessionPicker();
12962
14049
  return true;
14050
+ case "/fork":
14051
+ void state.openForkPicker();
14052
+ return true;
12963
14053
  case "/help":
12964
14054
  state.pushInfo(S.helpText(modelDisplay, state.getThinkingLevel(), getLocale()));
12965
14055
  return true;
@@ -13108,7 +14198,12 @@ ${listText}`);
13108
14198
  const v = inputRef.current.trim();
13109
14199
  if (!v) return;
13110
14200
  if (busyRef.current) {
13111
- state.pushEvent({ type: "error", message: S.busyBlockSend });
14201
+ if (!v.startsWith("/") && state.queueInput(v)) {
14202
+ setInput("");
14203
+ setCursor(0);
14204
+ } else {
14205
+ state.pushEvent({ type: "error", message: S.busyBlockSend });
14206
+ }
13112
14207
  return;
13113
14208
  }
13114
14209
  setInput("");
@@ -13190,6 +14285,16 @@ ${listText}`);
13190
14285
  } else if (key.escape || key.ctrl && ch === "g") state.resolveSession(null);
13191
14286
  return;
13192
14287
  }
14288
+ if (state.pendingFork) {
14289
+ const list = state.pendingFork.anchors;
14290
+ if (key.upArrow) setSelectIdx((i) => (i - 1 + list.length) % list.length);
14291
+ else if (key.downArrow) setSelectIdx((i) => (i + 1) % list.length);
14292
+ else if (key.return) {
14293
+ const sel = list[Math.min(selectIdxRef.current, list.length - 1)];
14294
+ state.resolveFork(sel ?? null);
14295
+ } else if (key.escape || key.ctrl && ch === "g") state.resolveFork(null);
14296
+ return;
14297
+ }
13193
14298
  if (key.ctrl && ch === "t") {
13194
14299
  state.toggleShowThinking();
13195
14300
  return;
@@ -13230,19 +14335,19 @@ ${listText}`);
13230
14335
  }
13231
14336
  });
13232
14337
  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: [
14338
+ return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", width: cols, children: [
14339
+ /* @__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) }),
14340
+ /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", width: cols, children: [
14341
+ menuActive ? null : /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", paddingX: 1, children: [
14342
+ dynamicRows.map((row) => /* @__PURE__ */ jsx17(RowView, { row, wrapW, streamTail, showThinking: state.showThinkingText }, row.id)),
14343
+ state.busy ? /* @__PURE__ */ jsx17(Box15, { marginTop: 0.5, children: /* @__PURE__ */ jsxs15(Text17, { color: THEME.coralBright, children: [
13239
14344
  "\u25CF ",
13240
14345
  S.generating
13241
14346
  ] }) }) : null
13242
14347
  ] }),
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(
14348
+ /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", paddingX: 1, flexShrink: 0, children: [
14349
+ state.pendingApproval ? /* @__PURE__ */ jsx17(ApprovalModal, { toolName: state.pendingApproval.toolName, detail: state.pendingApproval.detail, selectedIndex: selectIdx, wrapW }) : null,
14350
+ state.pendingQuestion ? /* @__PURE__ */ jsx17(
13246
14351
  QuestionModal,
13247
14352
  {
13248
14353
  question: state.pendingQuestion.req.question,
@@ -13253,7 +14358,7 @@ ${listText}`);
13253
14358
  wrapW
13254
14359
  }
13255
14360
  ) : null,
13256
- state.pendingPlan ? planEditing ? /* @__PURE__ */ jsx16(
14361
+ state.pendingPlan ? planEditing ? /* @__PURE__ */ jsx17(
13257
14362
  PlanEditor,
13258
14363
  {
13259
14364
  value: planDraft,
@@ -13264,13 +14369,15 @@ ${listText}`);
13264
14369
  },
13265
14370
  onSubmit: confirmPlanEdit
13266
14371
  }
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
14372
+ ) : /* @__PURE__ */ jsx17(PlanModal, { selectedIndex: selectIdx }) : null,
14373
+ state.pendingSessions ? /* @__PURE__ */ jsx17(SessionPicker, { sessions: state.pendingSessions.sessions, selectedIndex: selectIdx, wrapW }) : null,
14374
+ state.pendingFork ? /* @__PURE__ */ jsx17(ForkPicker, { anchors: state.pendingFork.anchors, selectedIndex: selectIdx, wrapW }) : null,
14375
+ slashVisible ? /* @__PURE__ */ jsx17(SlashMenu, { entries: filteredCommands, selectedIndex: selectIdx, cols }) : null
13270
14376
  ] }),
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(
14377
+ /* @__PURE__ */ jsx17(Box15, { flexDirection: "column", paddingX: 1, marginTop: 1, flexShrink: 0, children: (() => {
14378
+ const planOn = state.getPlanMode();
14379
+ const autoOn = !planOn && state.getAutoMode();
14380
+ const inputEl = /* @__PURE__ */ jsx17(
13274
14381
  MultilineInput,
13275
14382
  {
13276
14383
  value: input,
@@ -13286,9 +14393,19 @@ ${listText}`);
13286
14393
  suppressSubmit: slashVisible,
13287
14394
  placeholder: S.placeholder
13288
14395
  }
13289
- )
13290
- ] }),
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 }) })
14396
+ );
14397
+ if (planOn || autoOn) {
14398
+ return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", borderStyle: "round", borderColor: planOn ? THEME.thinking : THEME.warn, paddingX: 1, children: [
14399
+ /* @__PURE__ */ jsx17(Text17, { color: planOn ? THEME.thinking : THEME.warn, children: planOn ? S.inputBadgePlan : S.inputBadgeAuto }),
14400
+ inputEl
14401
+ ] });
14402
+ }
14403
+ return /* @__PURE__ */ jsxs15(Fragment3, { children: [
14404
+ /* @__PURE__ */ jsx17(Text17, { dimColor: true, color: THEME.gray, children: dimRule(cols) }),
14405
+ inputEl
14406
+ ] });
14407
+ })() }),
14408
+ /* @__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
14409
  ] })
13293
14410
  ] });
13294
14411
  };
@@ -13302,7 +14419,7 @@ __export(updateCheck_exports, {
13302
14419
  readLocalVersion: () => readLocalVersion,
13303
14420
  refreshUpdateCache: () => refreshUpdateCache
13304
14421
  });
13305
- import { readFileSync as readFileSync3, writeFileSync, mkdirSync } from "node:fs";
14422
+ import { readFileSync as readFileSync4, writeFileSync, mkdirSync } from "node:fs";
13306
14423
  import path39 from "node:path";
13307
14424
  import os4 from "os";
13308
14425
  import { fileURLToPath as fileURLToPath6 } from "node:url";
@@ -13318,7 +14435,7 @@ var init_updateCheck = __esm({
13318
14435
  readLocalVersion = () => {
13319
14436
  try {
13320
14437
  const here = path39.dirname(fileURLToPath6(import.meta.url));
13321
- return JSON.parse(readFileSync3(path39.join(here, "..", "package.json"), "utf8")).version ?? "0.0.0";
14438
+ return JSON.parse(readFileSync4(path39.join(here, "..", "package.json"), "utf8")).version ?? "0.0.0";
13322
14439
  } catch {
13323
14440
  return "0.0.0";
13324
14441
  }
@@ -13334,7 +14451,7 @@ var init_updateCheck = __esm({
13334
14451
  };
13335
14452
  readCache = () => {
13336
14453
  try {
13337
- const obj = JSON.parse(readFileSync3(cacheFile(), "utf8"));
14454
+ const obj = JSON.parse(readFileSync4(cacheFile(), "utf8"));
13338
14455
  return typeof obj.latestVersion === "string" && typeof obj.lastCheck === "number" ? obj : null;
13339
14456
  } catch {
13340
14457
  return null;
@@ -13422,7 +14539,7 @@ import { render } from "ink";
13422
14539
  import os5 from "os";
13423
14540
  import path40 from "path";
13424
14541
  import { emitKeypressEvents } from "node:readline";
13425
- import { jsx as jsx17 } from "react/jsx-runtime";
14542
+ import { jsx as jsx18 } from "react/jsx-runtime";
13426
14543
  var parseArgs = (argv) => {
13427
14544
  const out = {};
13428
14545
  for (let i = 0; i < argv.length; i++) {
@@ -13566,7 +14683,7 @@ var main = async () => {
13566
14683
  resumeId = await getMostRecentSessionId2() ?? void 0;
13567
14684
  }
13568
14685
  const { waitUntilExit } = render(
13569
- /* @__PURE__ */ jsx17(App2, { resumeSessionId: resumeId, initialPlanMode: args.plan, initialAutoMode: args.auto, initialIncludeProject: includeProject }),
14686
+ /* @__PURE__ */ jsx18(App2, { resumeSessionId: resumeId, initialPlanMode: args.plan, initialAutoMode: args.auto, initialIncludeProject: includeProject }),
13570
14687
  // exitOnCtrlC:false:Ctrl+C 交由 App useInput 处理(统一退出/中止语义);
13571
14688
  // patchConsole:false:避免 console 劫持与全屏重绘叠加闪屏。
13572
14689
  { exitOnCtrlC: false, patchConsole: false }