deepseeker-code 1.0.53 → 1.0.54

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.
package/dist/cli.mjs CHANGED
@@ -102,7 +102,7 @@ var init_strings = __esm({
102
102
  "\u89E3\u91CA runAgent \u7684\u4E3B\u5FAA\u73AF\u903B\u8F91"
103
103
  ],
104
104
  placeholder: "\u53D1\u9001\u6D88\u606F\uFF08Shift+Enter \u6362\u884C\uFF0CEnter \u53D1\u9001\uFF09\u2026",
105
- generating: "\u751F\u6210\u4E2D\u2026",
105
+ thinkVerbs: ["\u601D\u8003\u4E2D", "\u6784\u601D\u4E2D", "\u63A8\u6572\u4E2D", "\u659F\u914C\u4E2D", "\u915D\u917F\u4E2D", "\u7F16\u7EC7\u4E2D", "\u63D0\u70BC\u4E2D", "\u7075\u611F\u6D8C\u73B0\u4E2D"],
106
106
  round: (n) => `\u7B2C ${n} \u8F6E`,
107
107
  thinkingStreaming: "\u273B \u6DF1\u5EA6\u601D\u8003\u4E2D\u2026",
108
108
  thinkingCollapsed: (lines) => `\u273B \u601D\u8003\u8FC7\u7A0B\uFF08${lines} \u884C\uFF09\xB7 Ctrl+T \u5C55\u5F00`,
@@ -229,6 +229,7 @@ DeepSeeker-Code \u5C06\u5728\u6B64\u76EE\u5F55\u8BFB\u53D6\u3001\u7F16\u8F91\u54
229
229
  "/plan \u2014 \u5207\u6362\u8BA1\u5212\u6A21\u5F0F\uFF08\u53EA\u8BFB\u8C03\u7814 \u2192 \u65B9\u6848\u5BA1\u6279 \u2192 \u5B9E\u73B0\uFF09",
230
230
  `/model <\u6A21\u578Bid> \u2014 \u5207\u6362\u6A21\u578B\uFF08\u76F4\u8F93\uFF0C\u5F53\u524D ${model}\uFF09`,
231
231
  "/switch \u2014 \u5F39\u51FA\u5019\u9009\u6A21\u578B\u9009\u62E9\u5668",
232
+ "/archive \u2014 \u67E5\u770B\u6700\u8FD1\u4E00\u6B21\u538B\u7F29\u7684\u5F52\u6863\u6458\u8981",
232
233
  `/thinking [off|high|max] \u2014 \u5207\u6362\u601D\u8003\u7B49\u7EA7\uFF08\u5F53\u524D ${thinking}\uFF09`,
233
234
  `/lang [zh|en] \u2014 \u5207\u6362\u754C\u9762\u8BED\u8A00\uFF08\u5F53\u524D ${locale2}\uFF09`,
234
235
  "/sessions \u2014 \u9009\u62E9\u5E76\u8F7D\u5165\u5386\u53F2\u4F1A\u8BDD\uFF08\u7EED\u63A5\u5BF9\u8BDD\uFF09",
@@ -255,7 +256,7 @@ DeepSeeker-Code \u5C06\u5728\u6B64\u76EE\u5F55\u8BFB\u53D6\u3001\u7F16\u8F91\u54
255
256
  "Explain runAgent's main loop"
256
257
  ],
257
258
  placeholder: "Send a message (Shift+Enter newline, Enter to send)\u2026",
258
- generating: "Generating\u2026",
259
+ thinkVerbs: ["Thinking", "Ideating", "Pondering", "Brewing", "Forging", "Weaving", "Distilling", "Synthesizing"],
259
260
  round: (n) => `Round ${n}`,
260
261
  thinkingStreaming: "\u273B Thinking\u2026",
261
262
  thinkingCollapsed: (lines) => `\u273B Thoughts (${lines} lines) \xB7 Ctrl+T expand`,
@@ -382,6 +383,7 @@ DeepSeeker-Code will read, edit, and execute files here.`,
382
383
  "/plan \u2014 Toggle plan mode (read-only research \u2192 review \u2192 implement)",
383
384
  `/model <model-id> \u2014 Switch model (direct input; current ${model})`,
384
385
  "/switch \u2014 Open the model picker",
386
+ "/archive \u2014 Show the latest compaction summary",
385
387
  `/thinking [off|high|max] \u2014 Switch thinking level (current ${thinking})`,
386
388
  `/lang [zh|en] \u2014 Switch interface language (current ${locale2})`,
387
389
  "/sessions \u2014 Pick a past session to resume",
@@ -1289,6 +1291,14 @@ var init_replay = __esm({
1289
1291
  const f = fault;
1290
1292
  const n = Math.min(f.afterChars ?? 0, (turn.content ?? "").length);
1291
1293
  if (n > 0) yield { kind: "text", text: (turn.content ?? "").slice(0, n) };
1294
+ if (f.withToolCalls) {
1295
+ const fts = turn.toolCalls ?? [];
1296
+ for (let i = 0; i < fts.length; i++) {
1297
+ const tc = fts[i];
1298
+ yield { kind: "tool_call_delta", index: i, id: tc.id ?? `call_replay_${turnIndex}_${i}`, type: "function", nameDelta: tc.name };
1299
+ yield { kind: "tool_call_delta", index: i, argumentsDelta: JSON.stringify(tc.args ?? {}) };
1300
+ }
1301
+ }
1292
1302
  throw makeFaultError(f);
1293
1303
  }
1294
1304
  if (turn.reasoning) yield { kind: "reasoning", text: turn.reasoning };
@@ -1800,7 +1810,7 @@ var init_store = __esm({
1800
1810
 
1801
1811
  // ../core/src/session/transcript.ts
1802
1812
  import fs6 from "fs/promises";
1803
- var isEventLine, readTranscriptLines, appendSizeCache, appendLineWithRetry, appendMessage, appendEvent;
1813
+ var isEventLine, readTranscriptLines, appendSizeCache, appendLineWithRetry, appendMessage, appendMessages, appendEvent;
1804
1814
  var init_transcript = __esm({
1805
1815
  "../core/src/session/transcript.ts"() {
1806
1816
  "use strict";
@@ -1830,7 +1840,7 @@ var init_transcript = __esm({
1830
1840
  }
1831
1841
  };
1832
1842
  appendSizeCache = /* @__PURE__ */ new Map();
1833
- appendLineWithRetry = async (p, payload) => {
1843
+ appendLineWithRetry = async (p, payload, handle) => {
1834
1844
  const MAX_ATTEMPTS = 3;
1835
1845
  const payloadBytes = Buffer.byteLength(payload, "utf-8");
1836
1846
  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
@@ -1843,7 +1853,8 @@ var init_transcript = __esm({
1843
1853
  }
1844
1854
  }
1845
1855
  try {
1846
- await fs6.appendFile(p, payload, "utf-8");
1856
+ if (handle) await handle.appendFile(payload, "utf-8");
1857
+ else await fs6.appendFile(p, payload, "utf-8");
1847
1858
  appendSizeCache.set(p, beforeSize + payloadBytes);
1848
1859
  return;
1849
1860
  } catch (e) {
@@ -1873,6 +1884,35 @@ var init_transcript = __esm({
1873
1884
  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);
1874
1885
  }
1875
1886
  };
1887
+ appendMessages = async (sessionId, entries) => {
1888
+ if (entries.length === 0) return;
1889
+ if (entries.length === 1) return appendMessage(entries[0]);
1890
+ try {
1891
+ await ensureSessionsDir(sessionId);
1892
+ const p = getTranscriptPath(sessionId);
1893
+ const payloads = entries.map((entry) => {
1894
+ const { sessionId: _sid, ...rest } = entry;
1895
+ return JSON.stringify({ id: createUUID(), ts: (/* @__PURE__ */ new Date()).toISOString(), ...rest }) + "\n";
1896
+ });
1897
+ const FFFD = String.fromCharCode(65533);
1898
+ const fffdTotal = payloads.reduce((n, pl) => n + (pl.split(FFFD).length - 1), 0);
1899
+ if (fffdTotal > 0) console.warn(`[transcript] \u6279\u91CF\u6D88\u606F\u542B ${fffdTotal} \u4E2A\u4E71\u7801\u5B57\u7B26\uFF08U+FFFD\uFF09\uFF0C\u5DF2\u539F\u6837\u5199\u5165\u5386\u53F2\u3002\u6765\u6E90\u53EF\u80FD\u662F GBK \u5B50\u8FDB\u7A0B\u8F93\u51FA\u6216\u4E71\u7801\u7C98\u8D34\u3002`);
1900
+ let handle = null;
1901
+ let i = 0;
1902
+ try {
1903
+ handle = await fs6.open(p, "a");
1904
+ for (; i < payloads.length; i++) await appendLineWithRetry(p, payloads[i], handle);
1905
+ } catch (e) {
1906
+ console.warn(`[transcript] \u6279\u91CF\u843D\u76D8\u7B2C ${i + 1}/${payloads.length} \u6761\u5931\u8D25\uFF0C\u8BE5\u6761\u8D77\u56DE\u843D\u9010\u6761\u65E7\u8DEF\u5F84:`, e instanceof Error ? e.message : e);
1907
+ for (; i < entries.length; i++) await appendMessage(entries[i]);
1908
+ } finally {
1909
+ if (handle) await handle.close().catch(() => {
1910
+ });
1911
+ }
1912
+ } catch (e) {
1913
+ console.warn(`\u6279\u91CF\u6D88\u606F\u843D\u76D8\u5931\u8D25\uFF08\u5185\u5B58\u4E0E\u78C1\u76D8\u8F6C\u5F55\u53EF\u80FD\u4E0D\u4E00\u81F4\uFF09:`, e);
1914
+ }
1915
+ };
1876
1916
  appendEvent = async (sessionId, ev) => {
1877
1917
  if (!appConfig.transcriptEvents) return;
1878
1918
  try {
@@ -2629,8 +2669,16 @@ ${lines}
2629
2669
  if (batch.length > 0) batches.push(batch);
2630
2670
  return batches;
2631
2671
  };
2632
- compactBatches = async (batches, signal) => {
2633
- const lines = await Promise.all(batches.map((b, i) => compactBatch(b, signal, i === batches.length - 1)));
2672
+ compactBatches = async (batches, signal, onUIEvent) => {
2673
+ if (onUIEvent && batches.length > 0) onUIEvent({ type: "compact.progress", done: 0, total: batches.length });
2674
+ let done = 0;
2675
+ const lines = await Promise.all(batches.map(
2676
+ (b, i) => compactBatch(b, signal, i === batches.length - 1).then((line) => {
2677
+ done++;
2678
+ onUIEvent?.({ type: "compact.progress", done, total: batches.length });
2679
+ return line;
2680
+ })
2681
+ ));
2634
2682
  return lines.join("\n");
2635
2683
  };
2636
2684
  ensureSummarySlot = (messageArr) => {
@@ -2679,6 +2727,7 @@ ${lines}
2679
2727
  const summaryMsg = event.messageArr[1];
2680
2728
  let keep = event.keepRecentUnits;
2681
2729
  let lastSize = estReal(event.messageArr);
2730
+ const preCompactSize = Math.round(lastSize);
2682
2731
  const startTime = performance.now();
2683
2732
  let round = 0;
2684
2733
  while (lastSize > event.modelWindow * effectiveRatio) {
@@ -2692,7 +2741,7 @@ ${lines}
2692
2741
  if (toCompact.length > 0) {
2693
2742
  summaryMsg.content = mergeSummarySlot(summaryMsg?.content || "", "", extractArchiveEntities(toCompact));
2694
2743
  const batches = splitIntoBatches(toCompact);
2695
- summaryMsg.content = batches.length === 1 ? await synthesizeSlotNarrativeFromBatch(summaryMsg.content, batches[0], event.signal) : await synthesizeSlotNarrative(summaryMsg.content, await compactBatches(batches, event.signal), event.signal);
2744
+ summaryMsg.content = batches.length === 1 ? await synthesizeSlotNarrativeFromBatch(summaryMsg.content, batches[0], event.signal) : await synthesizeSlotNarrative(summaryMsg.content, await compactBatches(batches, event.signal, event.depth === 0 ? event.onUIEvent : void 0), event.signal);
2696
2745
  if (estimateTokens([summaryMsg]) > SUMMARY_SELF_COMPACT_THRESHOLD) {
2697
2746
  summaryMsg.content = await compactSlotNarrative(summaryMsg.content, event.modelWindow, event.signal);
2698
2747
  }
@@ -2775,6 +2824,25 @@ ${lines}
2775
2824
  }
2776
2825
  await dispatch("PostCompact", { sessionId: event.sessionId, depth: event.depth, tokensBefore: Math.round(lastSize), tokensAfter: Math.round(estReal(event.messageArr)) }).catch(() => {
2777
2826
  });
2827
+ if (event.onUIEvent && event.depth === 0) {
2828
+ const afterSize = Math.round(estReal(event.messageArr));
2829
+ if (preCompactSize - afterSize > 0) {
2830
+ let summary;
2831
+ try {
2832
+ const slotRaw = event.messageArr[1]?.content;
2833
+ summary = parseSummarySlot(typeof slotRaw === "string" ? slotRaw : "").notes.slice(0, 6e3) || void 0;
2834
+ } catch {
2835
+ }
2836
+ event.onUIEvent({
2837
+ type: "compact.done",
2838
+ tokensBefore: preCompactSize,
2839
+ tokensAfter: afterSize,
2840
+ durationMs: Math.round(performance.now() - startTime),
2841
+ trigger: "auto",
2842
+ summary
2843
+ });
2844
+ }
2845
+ }
2778
2846
  const hardLimitRatio = Math.min(event.compactRatio + 0.13, 0.95);
2779
2847
  const finalTokens = estReal(event.messageArr);
2780
2848
  if (finalTokens > event.modelWindow * hardLimitRatio) {
@@ -3270,6 +3338,48 @@ var init_guard = __esm({
3270
3338
  }
3271
3339
  });
3272
3340
 
3341
+ // ../core/src/agent/inbox.ts
3342
+ var INBOX_MAX_ITEMS, sessionInboxes, pushSessionInbox, claimSessionInbox, flushLeftoverToTranscript;
3343
+ var init_inbox = __esm({
3344
+ "../core/src/agent/inbox.ts"() {
3345
+ "use strict";
3346
+ init_transcript();
3347
+ init_config();
3348
+ INBOX_MAX_ITEMS = 50;
3349
+ sessionInboxes = /* @__PURE__ */ new Map();
3350
+ pushSessionInbox = (sessionId, content) => {
3351
+ if (!appConfig.inboxSteering) return false;
3352
+ const text = (content ?? "").trim();
3353
+ if (!text) return false;
3354
+ const q = sessionInboxes.get(sessionId) ?? [];
3355
+ if (q.length >= INBOX_MAX_ITEMS) {
3356
+ console.warn(`[inbox] \u4F1A\u8BDD ${sessionId} \u961F\u5217\u5DF2\u6EE1\uFF08${INBOX_MAX_ITEMS}\uFF09\uFF0C\u4E22\u5F03\u65B0\u8F93\u5165`);
3357
+ return false;
3358
+ }
3359
+ q.push(text);
3360
+ sessionInboxes.set(sessionId, q);
3361
+ return true;
3362
+ };
3363
+ claimSessionInbox = (sessionId) => {
3364
+ if (!appConfig.inboxSteering) return [];
3365
+ const q = sessionInboxes.get(sessionId);
3366
+ if (!q || q.length === 0) return [];
3367
+ sessionInboxes.delete(sessionId);
3368
+ return q;
3369
+ };
3370
+ flushLeftoverToTranscript = async (sessionId) => {
3371
+ const texts = claimSessionInbox(sessionId);
3372
+ for (const t of texts) {
3373
+ await appendMessage({ sessionId, role: "user", content: t });
3374
+ }
3375
+ if (texts.length > 0) {
3376
+ 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`);
3377
+ }
3378
+ return texts.length;
3379
+ };
3380
+ }
3381
+ });
3382
+
3273
3383
  // ../core/src/tool/registry/background.ts
3274
3384
  import { spawn as spawn2, execFileSync } from "child_process";
3275
3385
  import fsSync2 from "fs";
@@ -3314,13 +3424,14 @@ async function killBackgroundTasksUnder(dirPath) {
3314
3424
  }
3315
3425
  return killed;
3316
3426
  }
3317
- var _winShellCache, resolveWinShell, MAX_BUFFER_CHARS, registry, createBgTask, attachTaskLifecycle, adoptRunningProcess, backgroundTools, killAllBackgroundTasks;
3427
+ var _winShellCache, resolveWinShell, MAX_BUFFER_CHARS, registry, createBgTask, attachTaskLifecycle, makeTaskExitNotifier, adoptRunningProcess, backgroundTools, killAllBackgroundTasks;
3318
3428
  var init_background = __esm({
3319
3429
  "../core/src/tool/registry/background.ts"() {
3320
3430
  "use strict";
3321
3431
  init_type();
3322
3432
  init_guard();
3323
3433
  init_common();
3434
+ init_inbox();
3324
3435
  resolveWinShell = () => {
3325
3436
  if (process.platform !== "win32") return null;
3326
3437
  if (_winShellCache !== void 0) return _winShellCache;
@@ -3377,6 +3488,17 @@ var init_background = __esm({
3377
3488
  if (finished) return;
3378
3489
  finished = true;
3379
3490
  apply();
3491
+ try {
3492
+ opts.onExit?.({
3493
+ taskId: task.taskId,
3494
+ command: task.command,
3495
+ sessionId: task.sessionId,
3496
+ status: task.status === "killed" ? "killed" : "exited",
3497
+ exitCode: task.exitCode,
3498
+ ok: task.status === "exited" && task.exitCode === 0
3499
+ });
3500
+ } catch {
3501
+ }
3380
3502
  setTimeout(() => registry.delete(task.taskId), 6e4);
3381
3503
  opts.signal?.removeEventListener("abort", onAbort);
3382
3504
  try {
@@ -3428,6 +3550,20 @@ var init_background = __esm({
3428
3550
  });
3429
3551
  });
3430
3552
  };
3553
+ makeTaskExitNotifier = (ctx) => (info) => {
3554
+ const brief = info.command.length > 80 ? `${info.command.slice(0, 80)}\u2026` : info.command;
3555
+ try {
3556
+ ctx?.onUIEvent?.({ type: "task.exit", taskId: info.taskId, command: brief, status: info.status, exitCode: info.exitCode, ok: info.ok });
3557
+ } catch {
3558
+ }
3559
+ if (!info.ok && info.status === "exited" && info.sessionId) {
3560
+ try {
3561
+ pushSessionInbox(info.sessionId, `\u26A0\uFE0F \u540E\u53F0\u4EFB\u52A1\u5931\u8D25\uFF08exit=${info.exitCode ?? "\u672A\u77E5"}\uFF09\uFF1A${brief}
3562
+ \uFF08\u540E\u53F0\u547D\u4EE4\u975E\u96F6\u9000\u51FA\u3002\u8BF7\u7528 get_background_output(task_id="${info.taskId}") \u67E5\u770B\u65E5\u5FD7\uFF0C\u5E76\u51B3\u5B9A\u662F\u5426\u4FEE\u590D\u91CD\u8DD1\u3002\uFF09`);
3563
+ } catch {
3564
+ }
3565
+ }
3566
+ };
3431
3567
  adoptRunningProcess = (proc, meta, seedOutput, codec) => {
3432
3568
  const task = createBgTask(proc, meta.command, meta.cwd, meta.sessionId);
3433
3569
  if (seedOutput) appendOutput(task, seedOutput);
@@ -3450,7 +3586,7 @@ var init_background = __esm({
3450
3586
  const onData = (d) => appendOutput(task, codec.decode(d));
3451
3587
  proc.stdout?.on("data", onData);
3452
3588
  proc.stderr?.on("data", onData);
3453
- void attachTaskLifecycle(task, proc, { signal: meta.signal, codec });
3589
+ void attachTaskLifecycle(task, proc, { signal: meta.signal, codec, onExit: meta.onExit });
3454
3590
  return task.taskId;
3455
3591
  };
3456
3592
  backgroundTools = [
@@ -3528,7 +3664,7 @@ var init_background = __esm({
3528
3664
  task_id: ${taskId}
3529
3665
  \u547D\u4EE4: ${args.command}
3530
3666
  \u7528 get_background_output(task_id="${taskId}") \u67E5\u65E5\u5FD7\uFF0Cstop_background_task(task_id="${taskId}") \u7EC8\u6B62\u3002`;
3531
- await attachTaskLifecycle(task, proc, { signal: ctx?.abortSignal, codec });
3667
+ await attachTaskLifecycle(task, proc, { signal: ctx?.abortSignal, codec, onExit: makeTaskExitNotifier(ctx) });
3532
3668
  }
3533
3669
  }
3534
3670
  },
@@ -4601,48 +4737,6 @@ var init_httpExecutor = __esm({
4601
4737
  }
4602
4738
  });
4603
4739
 
4604
- // ../core/src/agent/inbox.ts
4605
- var INBOX_MAX_ITEMS, sessionInboxes, pushSessionInbox, claimSessionInbox, flushLeftoverToTranscript;
4606
- var init_inbox = __esm({
4607
- "../core/src/agent/inbox.ts"() {
4608
- "use strict";
4609
- init_transcript();
4610
- init_config();
4611
- INBOX_MAX_ITEMS = 50;
4612
- sessionInboxes = /* @__PURE__ */ new Map();
4613
- pushSessionInbox = (sessionId, content) => {
4614
- if (!appConfig.inboxSteering) return false;
4615
- const text = (content ?? "").trim();
4616
- if (!text) return false;
4617
- const q = sessionInboxes.get(sessionId) ?? [];
4618
- if (q.length >= INBOX_MAX_ITEMS) {
4619
- console.warn(`[inbox] \u4F1A\u8BDD ${sessionId} \u961F\u5217\u5DF2\u6EE1\uFF08${INBOX_MAX_ITEMS}\uFF09\uFF0C\u4E22\u5F03\u65B0\u8F93\u5165`);
4620
- return false;
4621
- }
4622
- q.push(text);
4623
- sessionInboxes.set(sessionId, q);
4624
- return true;
4625
- };
4626
- claimSessionInbox = (sessionId) => {
4627
- if (!appConfig.inboxSteering) return [];
4628
- const q = sessionInboxes.get(sessionId);
4629
- if (!q || q.length === 0) return [];
4630
- sessionInboxes.delete(sessionId);
4631
- return q;
4632
- };
4633
- flushLeftoverToTranscript = async (sessionId) => {
4634
- const texts = claimSessionInbox(sessionId);
4635
- for (const t of texts) {
4636
- await appendMessage({ sessionId, role: "user", content: t });
4637
- }
4638
- if (texts.length > 0) {
4639
- 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`);
4640
- }
4641
- return texts.length;
4642
- };
4643
- }
4644
- });
4645
-
4646
4740
  // ../core/src/agent/repeatBreaker.ts
4647
4741
  var createRepeatBreaker;
4648
4742
  var init_repeatBreaker = __esm({
@@ -5123,7 +5217,9 @@ var init_systemInjections = __esm({
5123
5217
  events,
5124
5218
  onUIEvent: options.onUIEvent,
5125
5219
  requestApproval: options.requestApproval,
5126
- requestQuestion: options.requestQuestion
5220
+ requestQuestion: options.requestQuestion,
5221
+ ideAction: options.ideAction
5222
+ // ★ ide_* 三工具 validateEnvironment 据此判定自隐藏(CLI/HTTP 宿主无钩子 → 不暴露)
5127
5223
  };
5128
5224
  const rawTools = await filterByEnvironment(rawToolsPreEnv, validationCtx);
5129
5225
  const cleanedEntries = rawTools.map((t) => cleanToolSchema(t));
@@ -6130,7 +6226,7 @@ var init_fs = __esm({
6130
6226
  // ★ 内容级脱敏(defense-in-depth):黑名单外的代码文件也可能内联硬编码密钥,
6131
6227
  // 在 verifyResult 之后、回灌云端模型之前由 runAgent 调用,仅影响"发给模型的视图"。
6132
6228
  privacyMaskingRules: maskSecretsInContent,
6133
- async execute(args) {
6229
+ async execute(args, toolCtx) {
6134
6230
  try {
6135
6231
  const absPath = resolveReadablePath(args.path);
6136
6232
  const lowerExt = absPath.toLowerCase();
@@ -6164,7 +6260,22 @@ var init_fs = __esm({
6164
6260
  if (requestedLines.length === 0) {
6165
6261
  return `[File: ${args.path}] \u8BF7\u6C42\u7684\u884C\u533A\u95F4 ${start}-${end} \u65E0\u5185\u5BB9\uFF08\u6587\u4EF6\u603B\u884C\u6570\u7EA6 ${currentLineNum}\uFF09\u3002`;
6166
6262
  }
6167
- return `[File: ${args.path} | Lines ${start}-${realEnd}]
6263
+ let readStamp = "";
6264
+ if (toolCtx?.readTracker) {
6265
+ try {
6266
+ const st = await fs13.stat(absPath);
6267
+ const prev = toolCtx.readTracker.get(absPath);
6268
+ const count = (prev?.count ?? 0) + 1;
6269
+ const changed = prev !== void 0 && prev.mtimeMs !== st.mtimeMs;
6270
+ toolCtx.readTracker.set(absPath, { mtimeMs: st.mtimeMs, count });
6271
+ if (count > 1) {
6272
+ readStamp = `[\u7B2C ${count} \u6B21\u8BFB\u53D6\uFF08\u672C run\uFF09\xB7 \u5185\u5BB9\u81EA\u4E0A\u6B21\u8BFB\u53D6${changed ? "\u5DF2\u53D8\u66F4" : "\u672A\u53D8\u66F4"}]
6273
+ `;
6274
+ }
6275
+ } catch {
6276
+ }
6277
+ }
6278
+ return `${readStamp}[File: ${args.path} | Lines ${start}-${realEnd}]
6168
6279
  ${formattedCode}${hasMore ? `
6169
6280
 
6170
6281
  [... \u540E\u9762\u8FD8\u6709\u4EE3\u7801\u5DF2\u88AB\u9690\u85CF\uFF0C\u4F60\u53EF\u4EE5\u8C03\u6574 start_line \u7EE7\u7EED\u5206\u7247\u8BFB\u53D6\u3002]` : ""}`;
@@ -7051,7 +7162,7 @@ ${tail}`,
7051
7162
  if (degraded) {
7052
7163
  const taskId = adoptRunningProcess(
7053
7164
  proc,
7054
- { command: args.command, cwd, sessionId: ctx?.sessionId, signal: ctx?.abortSignal },
7165
+ { command: args.command, cwd, sessionId: ctx?.sessionId, signal: ctx?.abortSignal, onExit: makeTaskExitNotifier(ctx) },
7055
7166
  fullOutput + queue.join(""),
7056
7167
  codec
7057
7168
  );
@@ -7980,7 +8091,7 @@ ${body}${truncNote}`;
7980
8091
  });
7981
8092
 
7982
8093
  // ../core/src/tool/registry/http.ts
7983
- var HTTP_TIMEOUT_MS, DEFAULT_MAX_CHARS2, USER_AGENT2, ALLOWED_METHODS, httpTools;
8094
+ var HTTP_TIMEOUT_MS, DEFAULT_MAX_CHARS2, USER_AGENT2, ALLOWED_METHODS, READY_PROBE_TIMEOUT_MS, READY_POLL_INTERVAL_MS, READY_DEFAULT_TIMEOUT_S, READY_MAX_TIMEOUT_S, READY_CLUE_CHARS, isLoopbackHost, httpTools;
7984
8095
  var init_http = __esm({
7985
8096
  "../core/src/tool/registry/http.ts"() {
7986
8097
  "use strict";
@@ -7990,6 +8101,15 @@ var init_http = __esm({
7990
8101
  DEFAULT_MAX_CHARS2 = 16e3;
7991
8102
  USER_AGENT2 = "DeepSeeker-Code-Agent/1.0 (+http_request tool)";
7992
8103
  ALLOWED_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
8104
+ READY_PROBE_TIMEOUT_MS = 3e3;
8105
+ READY_POLL_INTERVAL_MS = 1e3;
8106
+ READY_DEFAULT_TIMEOUT_S = 60;
8107
+ READY_MAX_TIMEOUT_S = 180;
8108
+ READY_CLUE_CHARS = 200;
8109
+ isLoopbackHost = (host) => {
8110
+ const h = host.toLowerCase().replace(/^\[|\]$/g, "");
8111
+ return h === "localhost" || h.endsWith(".localhost") || h === "::1" || h === "::" || h === "0.0.0.0" || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h);
8112
+ };
7993
8113
  httpTools = [
7994
8114
  {
7995
8115
  type: "function",
@@ -8086,6 +8206,85 @@ var init_http = __esm({
8086
8206
  }
8087
8207
  }
8088
8208
  }
8209
+ },
8210
+ {
8211
+ type: "function",
8212
+ function: {
8213
+ name: "wait_http_ready",
8214
+ description: [
8215
+ "\u963B\u585E\u7B49\u5F85\u672C\u5730\u670D\u52A1\u5C31\u7EEA\uFF1A1s \u95F4\u9694\u8F6E\u8BE2 URL \u76F4\u5230\u670D\u52A1\u54CD\u5E94\uFF08run-stack \u7F16\u6392\u7528\u2014\u2014\u300C\u8D77\u6CA1\u8D77\u6765\u300D\u4ECE\u731C\u65E5\u5FD7\u53D8\u786E\u5B9A\u6027\u5224\u5B9A\uFF09\u3002",
8216
+ "\u4EC5\u9650 loopback \u5730\u5740\uFF08localhost/127.x/[::1]\uFF09\uFF0C\u5176\u4ED6\u5730\u5740\u76F4\u63A5\u62D2\u7EDD\u2014\u2014\u5916\u90E8 URL \u8BF7\u7528 http_request\uFF08\u8D70\u5BA1\u6279\uFF09\u3002",
8217
+ "expect_status \u7F3A\u7701=\u6536\u5230\u4EFB\u610F HTTP \u54CD\u5E94\u5373\u7B97\u5C31\u7EEA\uFF08\u7AEF\u53E3\u5DF2\u670D\u52A1\uFF0C404/503 \u4E5F\u7B97\uFF1B\u72B6\u6001\u7801\u4F1A\u56DE\u6267\uFF09\uFF1B\u6307\u5B9A\u4E86\u5219\u7CBE\u786E\u5339\u914D\u8BE5\u72B6\u6001\u7801\u624D\u901A\u8FC7\u3002",
8218
+ "\u8D85\u65F6\u65F6\u56DE\u6267\u5E26\u6700\u540E\u4E00\u6B21\u72B6\u6001\u7801\u4E0E body \u6458\u8981\uFF0C\u53EF\u5F53\u6392\u969C\u7EBF\u7D22\u3002\u2605 SAFE \u514D\u5BA1\u3001\u53EF\u957F\u963B\u585E\uFF0C\u66FF\u4EE3\u9010\u6B21 http_request \u63A2\u6D4B\u3002"
8219
+ ].join(" "),
8220
+ parameters: {
8221
+ type: "object",
8222
+ properties: {
8223
+ url: { type: "string", description: "\u7B49\u5F85\u7684\u672C\u5730 URL\uFF08\u5982 http://localhost:5173 \u6216\u5065\u5EB7\u7AEF\u70B9 http://localhost:8080/actuator/health\uFF09" },
8224
+ expect_status: { type: "number", description: "\u8981\u6C42\u7684 HTTP \u72B6\u6001\u7801\uFF08\u53EF\u9009\uFF0C\u7F3A\u7701\u4EFB\u610F\u54CD\u5E94\u5373\u5C31\u7EEA\uFF09" },
8225
+ timeout_seconds: { type: "number", description: `\u6700\u957F\u7B49\u5F85\u79D2\u6570\uFF08\u9ED8\u8BA4 ${READY_DEFAULT_TIMEOUT_S}\uFF0C\u4E0A\u9650 ${READY_MAX_TIMEOUT_S}\uFF09` }
8226
+ },
8227
+ required: ["url"]
8228
+ },
8229
+ safetyLevel: "safe" /* SAFE */,
8230
+ // loopback-only 硬闸(见文件头安全红线)+ GET 语义探测,免审
8231
+ isSync: true,
8232
+ maxOutputCharacters: 2e3,
8233
+ async execute(args, ctx) {
8234
+ let parsed;
8235
+ try {
8236
+ parsed = new URL(String(args?.url ?? ""));
8237
+ } catch {
8238
+ return toolFailure(`[wait_http_ready] URL \u4E0D\u5408\u6CD5\uFF1A${args?.url}`);
8239
+ }
8240
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
8241
+ return toolFailure(`[wait_http_ready] \u4EC5\u5141\u8BB8 http/https\uFF0C\u62D2\u7EDD ${parsed.protocol}`);
8242
+ }
8243
+ if (!isLoopbackHost(parsed.hostname)) {
8244
+ return toolFailure(`[wait_http_ready] \u4EC5\u652F\u6301\u672C\u5730\u5730\u5740\uFF08localhost/127.x/[::1]\uFF09\uFF0C\u62D2\u7EDD ${parsed.hostname}\u3002\u5916\u90E8 URL \u8BF7\u7528 http_request\uFF08DANGER \u5BA1\u6279\uFF09\u53D1\u8D77\u3002`, "permission");
8245
+ }
8246
+ const timeoutS = Math.min(Math.max(args?.timeout_seconds && args.timeout_seconds > 0 ? args.timeout_seconds : READY_DEFAULT_TIMEOUT_S, 1), READY_MAX_TIMEOUT_S);
8247
+ const startedAt = Date.now();
8248
+ const deadline = startedAt + timeoutS * 1e3;
8249
+ const elapsedS = () => Math.round((Date.now() - startedAt) / 1e3);
8250
+ let lastLine = "\u5C1A\u672A\u53D1\u8D77\u63A2\u6D4B";
8251
+ let lastClue = "";
8252
+ let attempts = 0;
8253
+ let nextProgressAt = startedAt + 5e3;
8254
+ while (Date.now() < deadline) {
8255
+ if (ctx?.abortSignal?.aborted) return `\u23F9\uFE0F [wait_http_ready \u5DF2\u4E2D\u6B62]\uFF1A\u7528\u6237\u4E2D\u65AD\uFF08\u5DF2\u7B49\u5F85 ${elapsedS()}s\uFF09`;
8256
+ attempts++;
8257
+ let status = null;
8258
+ try {
8259
+ const res = await safeFetchFollow(
8260
+ parsed.href,
8261
+ { "User-Agent": USER_AGENT2, "Accept": "*/*" },
8262
+ [AbortSignal.timeout(READY_PROBE_TIMEOUT_MS)],
8263
+ true,
8264
+ { method: "GET" }
8265
+ );
8266
+ status = res.status;
8267
+ if (args?.expect_status === void 0 || status === args.expect_status) {
8268
+ return `\u2705 \u670D\u52A1\u5C31\u7EEA ${parsed.href}\uFF08HTTP ${status}${res.statusText ? " " + res.statusText : ""}\uFF0C\u7B2C ${attempts} \u6B21\u63A2\u6D4B\uFF0C\u8017\u65F6 ${elapsedS()}s\uFF09`;
8269
+ }
8270
+ lastLine = `HTTP ${status} ${res.statusText}\uFF08\u671F\u671B ${args.expect_status}\uFF0C\u7EE7\u7EED\u7B49\uFF09`;
8271
+ try {
8272
+ lastClue = (await readBodyCapped(res, READY_CLUE_CHARS * 4)).text.slice(0, READY_CLUE_CHARS);
8273
+ } catch {
8274
+ }
8275
+ } catch (e) {
8276
+ lastLine = e?.name === "TimeoutError" ? "\u63A2\u6D4B\u8D85\u65F6\uFF08\u670D\u52A1\u672A\u54CD\u5E94\uFF09" : `\u8FDE\u63A5\u5931\u8D25\uFF1A${e?.message ?? e}`;
8277
+ }
8278
+ if (ctx?.emitProgress && Date.now() >= nextProgressAt) {
8279
+ ctx.emitProgress(`\u4ECD\u5728\u7B49\u5F85 ${parsed.href}\uFF08\u5269\u4F59 ${Math.max(Math.ceil((deadline - Date.now()) / 1e3), 0)}s\uFF09\uFF1A${lastLine}`);
8280
+ nextProgressAt = Date.now() + 5e3;
8281
+ }
8282
+ await new Promise((r) => setTimeout(r, READY_POLL_INTERVAL_MS));
8283
+ }
8284
+ return toolFailure(`[wait_http_ready \u8D85\u65F6]\uFF1A${timeoutS}s \u5185\u672A\u5C31\u7EEA\uFF08\u5171 ${attempts} \u6B21\u63A2\u6D4B\uFF09\u3002\u6700\u540E\u72B6\u6001\uFF1A${lastLine}${lastClue ? `
8285
+ \u54CD\u5E94 body \u6458\u8981\uFF1A${lastClue}` : ""}`);
8286
+ }
8287
+ }
8089
8288
  }
8090
8289
  ];
8091
8290
  }
@@ -9349,6 +9548,7 @@ var init_ask = __esm({
9349
9548
  description: [
9350
9549
  "\u5411\u7528\u6237\u63D0\u51FA\u591A\u9009\u95EE\u9898\u5E76\u7B49\u5F85\u4F5C\u7B54\uFF1A\u6280\u672F\u65B9\u6848\u4E8C\u9009\u4E00\u3001\u8DEF\u5F84\u53D6\u820D\u3001\u8303\u56F4\u786E\u8BA4\u7B49\u9700\u7528\u6237\u62CD\u677F\u7684\u51B3\u7B56\u6216\u6B67\u4E49\u6F84\u6E05\u2014\u2014\u52FF\u7528\u4E8E\u80FD\u4ECE\u4EE3\u7801/\u4E0A\u4E0B\u6587\u81EA\u884C\u786E\u5B9A\u7684\u95EE\u9898\u3002",
9351
9550
  "2-4 \u4E2A\u9009\u9879\uFF08label + \u53EF\u9009 description\uFF09\uFF1BmultiSelect=true \u591A\u9009\uFF08\u9ED8\u8BA4\u5355\u9009\uFF09\u3002\u7528\u6237\u53D6\u6D88\u8FD4\u56DE\u7A7A\u9009\u62E9\uFF08\u6539\u7528\u9ED8\u8BA4\u6216\u91CD\u65B0\u63D0\u95EE\uFF09\u3002",
9551
+ "\u5BBF\u4E3B\u6A21\u6001\u6052\u6709\u300COther\u300D\u81EA\u7531\u8F93\u5165\u6863\uFF1A\u9700\u8981\u7528\u6237\u63D0\u4F9B\u9009\u9879\u4E4B\u5916\u7684\u8D44\u6599\uFF08token\u3001\u8DEF\u5F84\u3001\u94FE\u63A5\u7B49\uFF09\u65F6\uFF0C\u628A\u5B83\u8BBE\u8BA1\u6210\u4E00\u4E2A\u9009\u9879\u5373\u53EF\uFF0C\u7528\u6237\u4F1A\u7ECF Other \u7C98\u8D34\u6587\u672C\u56DE\u4F20\uFF08\u7ED3\u679C\u542B\u300C\u8865\u5145\u8F93\u5165\u300D\u5B57\u6837\uFF09\u3002",
9352
9552
  "\u4EC5\u4EA4\u4E92\u5F0F\u73AF\u5883\u53EF\u7528\uFF1B\u7ED3\u679C\u63D0\u793A\u300C\u4E0D\u652F\u6301\u300D\u65F6\u6539\u7528\u7EAF\u6587\u672C\u63D0\u95EE\u3002"
9353
9553
  ].join("\n"),
9354
9554
  parameters: {
@@ -9395,8 +9595,15 @@ var init_ask = __esm({
9395
9595
  return toolFailure(`[\u63D0\u95EE\u5F02\u5E38]\uFF1A${e?.message ?? e}\u3002\u8BF7\u6539\u7528\u7EAF\u6587\u672C\u5411\u7528\u6237\u63D0\u95EE\u3002`);
9396
9596
  }
9397
9597
  if (!answer.selected || answer.selected.length === 0) {
9598
+ const free2 = (answer.freeText ?? "").trim();
9599
+ if (free2) return `\u7528\u6237\u7ECF\u300COther\u300D\u8865\u5145\u8F93\u5165\uFF1A\u300C${free2}\u300D`;
9398
9600
  return "\uFF08\u7528\u6237\u53D6\u6D88\u4E86\u672C\u6B21\u63D0\u95EE\uFF0C\u672A\u4F5C\u9009\u62E9\u3002\u53EF\u6309\u9ED8\u8BA4\u7EE7\u7EED\uFF0C\u6216\u91CD\u65B0\u7EC4\u7EC7\u95EE\u9898\u518D\u6B21\u8BE2\u95EE\u3002\uFF09";
9399
9601
  }
9602
+ const free = (answer.freeText ?? "").trim();
9603
+ if (free) {
9604
+ const selPart = answer.selected.map((s) => `\u300C${s}\u300D`).join("\u3001");
9605
+ return `\u7528\u6237\u9009\u62E9\u4E86 ${selPart}\uFF0C\u5E76\u7ECF\u300COther\u300D\u8865\u5145\u8F93\u5165\uFF1A\u300C${free}\u300D`;
9606
+ }
9400
9607
  return args?.multiSelect ? `\u7528\u6237\u9009\u62E9\u4E86 ${answer.selected.length} \u9879\uFF1A${answer.selected.map((s) => `\u300C${s}\u300D`).join("\u3001")}` : `\u7528\u6237\u9009\u62E9\uFF1A\u300C${answer.selected[0]}\u300D`;
9401
9608
  }
9402
9609
  }
@@ -10440,6 +10647,123 @@ var init_document = __esm({
10440
10647
  }
10441
10648
  });
10442
10649
 
10650
+ // ../core/src/tool/registry/ide.ts
10651
+ var requireIdeHost, IDE_UNAVAILABLE, formatDiagnostics, ideResultToText, ideTools;
10652
+ var init_ide = __esm({
10653
+ "../core/src/tool/registry/ide.ts"() {
10654
+ "use strict";
10655
+ init_type();
10656
+ requireIdeHost = (ctx) => !!ctx?.ideAction;
10657
+ IDE_UNAVAILABLE = toolFailure(
10658
+ "[IDE \u6865\u4E0D\u53EF\u7528]\uFF1A\u5F53\u524D\u5BBF\u4E3B\u672A\u63A5\u5165 IDE\u3002\u8BCA\u65AD\u8BF7\u6539\u7528 run_command \u8DD1\u5BF9\u5E94\u5DE5\u5177\u94FE\uFF08tsc/vue-tsc/mvn compile \u7B49\uFF09\uFF1B\u6253\u5F00\u6587\u4EF6\u8BF7\u5728\u56DE\u590D\u4E2D\u76F4\u63A5\u7ED9\u51FA \u6587\u4EF6\u8DEF\u5F84:\u884C\u53F7\u3002\u672C\u5DE5\u5177\u4EC5\u5728 VSCode \u5BBF\u4E3B\u53EF\u7528\u3002",
10659
+ "permission"
10660
+ );
10661
+ formatDiagnostics = (items, truncated) => {
10662
+ const count = (s) => items.filter((i) => i.severity === s).length;
10663
+ const sources = [...new Set(items.map((i) => i.source).filter(Boolean))].join("/");
10664
+ const head = `[IDE \u8BCA\u65AD] error\xD7${count("error")} warning\xD7${count("warning")} info\xD7${count("info")}${sources ? `\uFF08\u6765\u6E90: ${sources}\uFF09` : ""}${truncated ? `\uFF1B\u53E6\u6709 ${truncated} \u6761\u672A\u663E\u793A` : ""}`;
10665
+ const body = items.map((i) => `${i.file}:${i.line}:${i.column} ${i.severity}${i.code != null && i.code !== "" ? " " + i.code : ""} ${i.message}${i.source ? ` (${i.source})` : ""}`).join("\n");
10666
+ const hint = items.length === 0 ? "\n\uFF08\u8BCA\u65AD\u4E3A\u7A7A\uFF1A\u82E5\u76EE\u6807\u6587\u4EF6\u53EF\u80FD\u672A\u5728 IDE \u4E2D\u52A0\u8F7D\uFF0C\u5148 ide_open_file \u6253\u5F00\u518D\u53D6\u4E00\u6B21\uFF09" : "\n\u4FEE\u590D\u540E\u53EF\u518D\u6B21\u8C03\u7528\u786E\u8BA4\u6E05\u96F6\u3002.vue \u7B49\u8BCA\u65AD\u6765\u81EA IDE \u8BED\u8A00\u670D\u52A1\u5668\uFF08\u5982 Volar\uFF09\uFF0C\u4E0E run_command \u8DD1 vue-tsc \u7684\u53E3\u5F84\u4E00\u81F4\u3002";
10667
+ return `${head}
10668
+ ${body}${hint}`;
10669
+ };
10670
+ ideResultToText = (r) => {
10671
+ if (!r.ok) return toolFailure(`[IDE \u52A8\u4F5C\u5931\u8D25]\uFF1A${r.message}`);
10672
+ if (r.action === "diagnostics") return formatDiagnostics(r.items, r.truncated);
10673
+ return `\u2705 ${r.message}`;
10674
+ };
10675
+ ideTools = [
10676
+ {
10677
+ type: "function",
10678
+ function: {
10679
+ name: "ide_open_file",
10680
+ planAllowed: true,
10681
+ validateEnvironment: requireIdeHost,
10682
+ description: "\u5728\u7528\u6237\u7684 IDE \u7F16\u8F91\u5668\u91CC\u6253\u5F00\u6587\u4EF6\u5E76\u8DF3\u8F6C\u5230\u6307\u5B9A\u884C/\u5217\uFF08\u5982\u6539\u5B8C\u4EE3\u7801\u540E\u8BA9\u7528\u6237\u76F4\u63A5\u770B\u5230\u4FEE\u6539\u5904\uFF09\u3002\u4EC5 VSCode \u5BBF\u4E3B\u53EF\u7528\uFF1B\u5176\u4ED6\u73AF\u5883\u672C\u5DE5\u5177\u4E0D\u4F1A\u51FA\u73B0\uFF0C\u51FA\u73B0\u5373\u4EE3\u8868\u53EF\u7528\u3002\u2605 \u53EA\u8BFB\u5B89\u5168\uFF0C\u53EF\u4E0E\u5176\u5B83\u53EA\u8BFB\u5DE5\u5177\u5728\u540C\u4E00\u6761\u6D88\u606F\u91CC\u5E76\u884C\u8C03\u7528\u3002",
10683
+ parameters: {
10684
+ type: "object",
10685
+ properties: {
10686
+ path: { type: "string", description: "\u76EE\u6807\u6587\u4EF6\u8DEF\u5F84\uFF08\u76F8\u5BF9\u5DE5\u4F5C\u533A\u6839\u6216\u7EDD\u5BF9\u8DEF\u5F84\uFF09" },
10687
+ line: { type: "number", description: "\u8DF3\u8F6C\u884C\u53F7\uFF081-based\uFF0C\u53EF\u9009\uFF09" },
10688
+ column: { type: "number", description: "\u8DF3\u8F6C\u5217\u53F7\uFF081-based\uFF0C\u53EF\u9009\uFF0C\u7F3A\u7701\u884C\u9996\uFF09" }
10689
+ },
10690
+ required: ["path"]
10691
+ },
10692
+ safetyLevel: "safe" /* SAFE */,
10693
+ isSync: true,
10694
+ async execute(args, ctx) {
10695
+ const p = (args?.path ?? "").trim();
10696
+ if (!p) return toolFailure("[\u6253\u5F00\u5931\u8D25]\uFF1Apath \u4E0D\u80FD\u4E3A\u7A7A\u3002", "syntax");
10697
+ if (!ctx?.ideAction) return IDE_UNAVAILABLE;
10698
+ try {
10699
+ const r = await ctx.ideAction({ action: "open", path: p, line: args?.line, column: args?.column });
10700
+ return ideResultToText(r);
10701
+ } catch (e) {
10702
+ return toolFailure(`[IDE \u5F02\u5E38]\uFF1A${e?.message ?? e}`);
10703
+ }
10704
+ }
10705
+ }
10706
+ },
10707
+ {
10708
+ type: "function",
10709
+ function: {
10710
+ name: "ide_diagnostics",
10711
+ planAllowed: true,
10712
+ validateEnvironment: requireIdeHost,
10713
+ maxOutputCharacters: 8e3,
10714
+ description: "\u8BFB\u53D6 IDE \u5F53\u524D\u771F\u5B9E\u8BCA\u65AD\uFF08\u9519\u8BEF\u6CE2\u6D6A\u7EBF\uFF09\uFF0C\u652F\u6301\u6240\u6709\u5DF2\u88C5\u8BED\u8A00\u670D\u52A1\u5668\u7684\u8BED\u8A00\u2014\u2014\u542B .vue\uFF08Volar\uFF09/Java \u7B49\uFF1Bget_diagnostics \u53EA\u8986\u76D6 TS/JS\uFF0C\u4E24\u8005\u4E92\u8865\u3002path \u7F3A\u7701=\u6574\u4E2A\u5DE5\u4F5C\u533A\uFF08\u6309 error \u4F18\u5148\u6392\u5E8F\uFF09\uFF1Bseverity \u7F3A\u7701 error\u3002\u2605 \u4FEE\u590D .vue \u62A5\u9519\u3001\u9A8C\u8BC1\u6539\u52A8\u540E IDE \u662F\u5426\u8FD8\u4EAE\u7EA2\uFF0C\u7528\u5B83\uFF1B\u6587\u4EF6\u53EF\u80FD\u672A\u52A0\u8F7D\u65F6\u5148 ide_open_file \u6253\u5F00\u518D\u53D6\u3002\u2605 \u53EA\u8BFB\u5B89\u5168\uFF0C\u53EF\u5E76\u884C\u8C03\u7528\u3002",
10715
+ parameters: {
10716
+ type: "object",
10717
+ properties: {
10718
+ path: { type: "string", description: "\u76EE\u6807\u6587\u4EF6\u8DEF\u5F84\uFF08\u53EF\u9009\uFF0C\u7F3A\u7701=\u5168\u5DE5\u4F5C\u533A\u8BCA\u65AD\uFF09" },
10719
+ severity: { type: "string", enum: ["error", "warning", "all"], description: "\u6700\u4F4E\u4E25\u91CD\u5EA6\uFF08\u7F3A\u7701 error\uFF1B\u6392\u67E5\u8B66\u544A\u65F6\u7528 warning/all\uFF09" }
10720
+ },
10721
+ required: []
10722
+ },
10723
+ safetyLevel: "safe" /* SAFE */,
10724
+ isSync: true,
10725
+ async execute(args, ctx) {
10726
+ if (!ctx?.ideAction) return IDE_UNAVAILABLE;
10727
+ try {
10728
+ const r = await ctx.ideAction({ action: "diagnostics", path: args?.path?.trim() || void 0, severity: args?.severity });
10729
+ return ideResultToText(r);
10730
+ } catch (e) {
10731
+ return toolFailure(`[IDE \u5F02\u5E38]\uFF1A${e?.message ?? e}`);
10732
+ }
10733
+ }
10734
+ }
10735
+ },
10736
+ {
10737
+ type: "function",
10738
+ function: {
10739
+ name: "ide_run_task",
10740
+ validateEnvironment: requireIdeHost,
10741
+ description: "\u89E6\u53D1 VSCode \u4EFB\u52A1\uFF08.vscode/tasks.json \u91CC\u5B9A\u4E49\u7684 build/test/watch \u7B49\uFF09\uFF0C\u5728 IDE \u7EC8\u7AEF\u9762\u677F\u542F\u52A8\u3002\u2605 \u89E6\u53D1\u5373\u8FD4\u56DE\u3001\u4E0D\u7B49\u5F85\u5B8C\u6210\uFF08watch \u7C7B\u4EFB\u52A1\u6C38\u4E0D\u7ED3\u675F\uFF09\uFF1B\u8981\u770B\u7A0B\u5E8F\u5316\u8F93\u51FA\u8BF7\u6539\u7528 run_command\u3002name \u7F3A\u7701=\u53EA\u5217\u51FA\u53EF\u7528\u4EFB\u52A1\u540D\u4E0D\u6267\u884C\uFF08SAFE \u63A2\u6D4B\uFF09\u3002\u4EC5 VSCode \u5BBF\u4E3B\u53EF\u7528\u3002",
10742
+ parameters: {
10743
+ type: "object",
10744
+ properties: {
10745
+ name: { type: "string", description: "\u4EFB\u52A1\u540D\uFF08\u7F3A\u7701\u5219\u4EC5\u5217\u51FA\u53EF\u7528\u4EFB\u52A1\uFF09" }
10746
+ },
10747
+ required: []
10748
+ },
10749
+ safetyLevel: "danger" /* DANGER */,
10750
+ isSync: true,
10751
+ requireApproval: (args) => args?.name ? `\u5728 IDE \u4E2D\u89E6\u53D1\u4EFB\u52A1\u300C${args.name}\u300D\uFF1F\u4EFB\u52A1\u5C06\u5728 VSCode \u7EC8\u7AEF\u9762\u677F\u6267\u884C\u3002` : "\u8BFB\u53D6 IDE \u53EF\u7528\u4EFB\u52A1\u5217\u8868\uFF1F",
10752
+ async execute(args, ctx) {
10753
+ if (!ctx?.ideAction) return IDE_UNAVAILABLE;
10754
+ try {
10755
+ const r = await ctx.ideAction({ action: "task", name: args?.name?.trim() || void 0 });
10756
+ return ideResultToText(r);
10757
+ } catch (e) {
10758
+ return toolFailure(`[IDE \u5F02\u5E38]\uFF1A${e?.message ?? e}`);
10759
+ }
10760
+ }
10761
+ }
10762
+ }
10763
+ ];
10764
+ }
10765
+ });
10766
+
10443
10767
  // ../core/src/tool/index.ts
10444
10768
  var tool_exports = {};
10445
10769
  __export(tool_exports, {
@@ -10475,6 +10799,7 @@ var init_tool = __esm({
10475
10799
  init_recall();
10476
10800
  init_spreadsheet();
10477
10801
  init_document();
10802
+ init_ide();
10478
10803
  init_type();
10479
10804
  agentTools = [];
10480
10805
  agentSubTools = createAgentTools(() => agentTools);
@@ -10500,8 +10825,9 @@ var init_tool = __esm({
10500
10825
  ...memoryTools,
10501
10826
  ...recallTools,
10502
10827
  ...spreadsheetTools,
10503
- ...documentTools
10504
- // read_docx / read_pdf(Word / PDF 文档读取,补 read_file 读不了的二进制文档)
10828
+ ...documentTools,
10829
+ ...ideTools
10830
+ // IDE 桥(ide_open_file / ide_diagnostics / ide_run_task;仅 VSCode 宿主注入钩子,否则 validateEnvironment 自隐藏)
10505
10831
  );
10506
10832
  }
10507
10833
  });
@@ -10801,7 +11127,7 @@ var init_autoPermission = __esm({
10801
11127
 
10802
11128
  // ../core/src/agent/toolExecution.ts
10803
11129
  import path34 from "path";
10804
- var applyPrivacyMasking, processToolCall;
11130
+ var applyPrivacyMasking, scaleTruncBudget, processToolCall;
10805
11131
  var init_toolExecution = __esm({
10806
11132
  "../core/src/agent/toolExecution.ts"() {
10807
11133
  "use strict";
@@ -10833,6 +11159,14 @@ var init_toolExecution = __esm({
10833
11159
  return output;
10834
11160
  }
10835
11161
  };
11162
+ scaleTruncBudget = (base, fill) => {
11163
+ if (!Number.isFinite(base) || base <= 0) return base;
11164
+ if (fill === void 0 || !Number.isFinite(fill) || fill <= 0.5) return base;
11165
+ const floor = Math.min(base, 2e3);
11166
+ if (fill > 0.85) return Math.max(floor, Math.floor(base * 0.35));
11167
+ const factor = 1 - (fill - 0.5) / 0.35 * 0.6;
11168
+ return Math.max(floor, Math.floor(base * factor));
11169
+ };
10836
11170
  processToolCall = async (toolCall, ctx) => {
10837
11171
  const {
10838
11172
  sessionId,
@@ -10848,10 +11182,12 @@ var init_toolExecution = __esm({
10848
11182
  onUIEvent,
10849
11183
  requestApproval: hostRequestApproval,
10850
11184
  requestQuestion: hostRequestQuestion,
11185
+ ideAction,
10851
11186
  keepRecentUnits,
10852
11187
  compactRatio,
10853
11188
  modelWindow,
10854
- parentSystemPrompt
11189
+ parentSystemPrompt,
11190
+ readTracker
10855
11191
  } = ctx;
10856
11192
  let calledName = "";
10857
11193
  let calledArgs = {};
@@ -10889,7 +11225,7 @@ var init_toolExecution = __esm({
10889
11225
  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 } });
10890
11226
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel: note, resultForUser: note, ok: false };
10891
11227
  }
10892
- 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 };
11228
+ const toolCtx = { sessionId, cwd: getActiveWorkspaceRoot(), abortSignal: signal, depth, keepRecentUnits, compactRatio, modelWindow, parentSystemPrompt, events, onUIEvent, requestApproval: hostRequestApproval, requestQuestion: hostRequestQuestion, ideAction, emitProgress: (m) => onUIEvent?.({ type: "tool.progress", toolsId: toolCall.id, toolName: calledName, message: m }), permissionMode, readTracker };
10893
11229
  let result = "";
10894
11230
  let resultStatus = "success";
10895
11231
  let errorCategory;
@@ -11082,9 +11418,10 @@ ${result}`;
11082
11418
  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 } });
11083
11419
  }
11084
11420
  result = applyPrivacyMasking(matchedTool?.function?.privacyMaskingRules, calledArgs, result);
11085
- const truncBudget = matchedTool?.function?.maxOutputCharacters ?? appConfig.MAX_TOOL_RESULT_CHARS;
11421
+ const fill = ctx.contextTokensEst !== void 0 && modelWindow > 0 ? ctx.contextTokensEst / modelWindow : void 0;
11422
+ const truncBudget = scaleTruncBudget(matchedTool?.function?.maxOutputCharacters ?? appConfig.MAX_TOOL_RESULT_CHARS, fill);
11086
11423
  const sidecarNote = result.length > truncBudget ? await writeSidecarArchive(sessionId, String(toolCall.id), result) : void 0;
11087
- result = truncateToolResult(result, matchedTool?.function.maxOutputCharacters, sidecarNote);
11424
+ result = truncateToolResult(result, truncBudget, sidecarNote);
11088
11425
  const ok = resultStatus === "success";
11089
11426
  let resultForModel = result;
11090
11427
  let resultForUser = result;
@@ -11098,9 +11435,9 @@ ${result}`;
11098
11435
  }
11099
11436
  if (postHookOverride !== void 0) {
11100
11437
  console.log(`\u270F\uFE0F [Post-hook] [${calledName}] \u6A21\u578B\u89C6\u56FE\u7ED3\u679C\u5DF2\u88AB hook \u6539\u5199`);
11101
- const overrideBudget = matchedTool?.function?.maxOutputCharacters ?? appConfig.MAX_TOOL_RESULT_CHARS;
11438
+ const overrideBudget = scaleTruncBudget(matchedTool?.function?.maxOutputCharacters ?? appConfig.MAX_TOOL_RESULT_CHARS, fill);
11102
11439
  const overrideNote = postHookOverride.length > overrideBudget ? await writeSidecarArchive(sessionId, String(toolCall.id), postHookOverride) : void 0;
11103
- resultForModel = truncateToolResult(postHookOverride, matchedTool?.function?.maxOutputCharacters, overrideNote);
11440
+ resultForModel = truncateToolResult(postHookOverride, overrideBudget, overrideNote);
11104
11441
  }
11105
11442
  return { toolCallId: toolCall.id, calledName, calledArgs, resultForModel, resultForUser, ok, imageAttachments };
11106
11443
  };
@@ -11108,7 +11445,7 @@ ${result}`;
11108
11445
  });
11109
11446
 
11110
11447
  // ../core/src/agent/toolScheduling.ts
11111
- var scheduleToolCalls;
11448
+ var dedupeSameRoundCalls, scheduleToolCalls;
11112
11449
  var init_toolScheduling = __esm({
11113
11450
  "../core/src/agent/toolScheduling.ts"() {
11114
11451
  "use strict";
@@ -11118,6 +11455,22 @@ var init_toolScheduling = __esm({
11118
11455
  init_config();
11119
11456
  init_tool();
11120
11457
  init_permissions();
11458
+ dedupeSameRoundCalls = (tcs) => {
11459
+ const batch = [];
11460
+ const plan = [];
11461
+ const firstBySig = /* @__PURE__ */ new Map();
11462
+ for (const tc of tcs) {
11463
+ const sig = `${tc?.function?.name ?? ""}::${tc?.function?.arguments ?? ""}`;
11464
+ const first = firstBySig.get(sig);
11465
+ if (first) plan.push({ tc, dupOf: first });
11466
+ else {
11467
+ firstBySig.set(sig, tc);
11468
+ batch.push(tc);
11469
+ plan.push({ tc });
11470
+ }
11471
+ }
11472
+ return { batch, plan };
11473
+ };
11121
11474
  scheduleToolCalls = async function* (assistantMessage, message, ctx) {
11122
11475
  const { sessionId, signal, rawTools } = ctx;
11123
11476
  let abortedDuringTools = false;
@@ -11172,26 +11525,43 @@ var init_toolScheduling = __esm({
11172
11525
  const tc = tcs[idx];
11173
11526
  const { name: pname, args: pargs, parseFailed: pparseFailed } = parseTc(tc);
11174
11527
  if (canParallelize(pname, pargs, pparseFailed)) {
11175
- const batch = [];
11528
+ const seg = [];
11176
11529
  while (idx < tcs.length) {
11177
11530
  const btc = tcs[idx];
11178
11531
  const bp = parseTc(btc);
11179
11532
  if (!canParallelize(bp.name, bp.args, bp.parseFailed)) break;
11180
- batch.push(btc);
11533
+ seg.push(btc);
11181
11534
  idx++;
11182
11535
  }
11183
- for (const btc of batch) {
11184
- const bp = parseTc(btc);
11185
- yield { type: "tool.start", toolCallId: btc.id, toolName: bp.name, args: bp.args };
11536
+ const { batch, plan } = dedupeSameRoundCalls(seg);
11537
+ for (const p of plan) {
11538
+ const bp = parseTc(p.tc);
11539
+ yield { type: "tool.start", toolCallId: p.tc.id, toolName: bp.name, args: bp.args };
11186
11540
  }
11187
11541
  const outcomes = await Promise.all(batch.map((btc) => processToolCall(btc, ctx)));
11188
- for (const oc of outcomes) {
11542
+ const outcomeByTc = /* @__PURE__ */ new Map();
11543
+ batch.forEach((btc, i) => outcomeByTc.set(btc, outcomes[i]));
11544
+ const batchEntries = [];
11545
+ const dupSeq = /* @__PURE__ */ new Map();
11546
+ for (const p of plan) {
11547
+ let oc;
11548
+ if (p.dupOf) {
11549
+ const firstOc = outcomeByTc.get(p.dupOf);
11550
+ const n = (dupSeq.get(p.dupOf) ?? 1) + 1;
11551
+ dupSeq.set(p.dupOf, n);
11552
+ const note = `(\u540C\u8F6E\u91CD\u590D\u8C03\u7528 #${n}\uFF0C\u5DF2\u590D\u7528 #1 \u7684\u7ED3\u679C)
11553
+ `;
11554
+ oc = { ...firstOc, toolCallId: p.tc.id, resultForModel: note + firstOc.resultForModel, resultForUser: note + firstOc.resultForUser, imageAttachments: void 0 };
11555
+ } else {
11556
+ oc = outcomeByTc.get(p.tc);
11557
+ }
11189
11558
  yield { type: "tool.end", toolCallId: oc.toolCallId, toolName: oc.calledName, result: oc.resultForUser, ok: oc.ok };
11190
11559
  message.push({ role: "tool", tool_call_id: oc.toolCallId, content: oc.resultForModel });
11191
- await appendMessage({ sessionId, role: "tool", tool_call_id: oc.toolCallId, content: oc.resultForModel });
11560
+ batchEntries.push({ sessionId, role: "tool", tool_call_id: oc.toolCallId, content: oc.resultForModel });
11192
11561
  if (oc.imageAttachments?.length) pendingImages.push(...oc.imageAttachments);
11193
11562
  if (oc.aborted) abortedDuringTools = true;
11194
11563
  }
11564
+ await appendMessages(sessionId, batchEntries);
11195
11565
  } else {
11196
11566
  yield { type: "tool.start", toolCallId: tc.id, toolName: pname, args: pargs };
11197
11567
  const oc = await processToolCall(tc, ctx);
@@ -11368,9 +11738,10 @@ var init_streamInference = __esm({
11368
11738
  }
11369
11739
  );
11370
11740
  const noOutputYet = !contentBuf && !reasoningBuf && toolCallsBuf.size === 0;
11371
- if (isIdleTimeout && (noOutputYet || !hasCompleteToolCall) && streamAttempt < MAX_STREAM_RETRIES) {
11741
+ if (isIdleTimeout && streamAttempt < MAX_STREAM_RETRIES) {
11372
11742
  if (!noOutputYet) yield { type: "text.reset" };
11373
- console.warn(`\u26A0\uFE0F \u6D41\u5F0F stall\uFF08idle \u8D85\u65F6${noOutputYet ? "\uFF0C\u5C1A\u65E0\u8F93\u51FA" : "\uFF0Ctool_call \u672A\u6D41\u5B8C"}\uFF09\uFF0C\u7B2C ${streamAttempt + 1}/${MAX_STREAM_RETRIES} \u6B21\u91CD\u8BD5...`);
11743
+ const stallShape = noOutputYet ? "\uFF0C\u5C1A\u65E0\u8F93\u51FA" : hasCompleteToolCall ? "\uFF0C\u5DF2\u6709\u5B8C\u6574 tool_call\uFF08\u6D41\u5C3E\u90E8 stall\uFF09" : "\uFF0Ctool_call \u672A\u6D41\u5B8C";
11744
+ console.warn(`\u26A0\uFE0F \u6D41\u5F0F stall\uFF08idle \u8D85\u65F6${stallShape}\uFF09\uFF0C\u7B2C ${streamAttempt + 1}/${MAX_STREAM_RETRIES} \u6B21\u91CD\u8BD5...`);
11374
11745
  contentBuf = "";
11375
11746
  reasoningBuf = "";
11376
11747
  toolCallsBuf.clear();
@@ -11416,7 +11787,8 @@ var init_streamInference = __esm({
11416
11787
  events,
11417
11788
  depth,
11418
11789
  signal,
11419
- toolsTokens
11790
+ toolsTokens,
11791
+ onUIEvent: ctx.onUIEvent
11420
11792
  });
11421
11793
  inferenceMessages = withNudgeTail(message, nudgeMsg);
11422
11794
  compactedThisRound = true;
@@ -11498,7 +11870,7 @@ var init_streamInference = __esm({
11498
11870
  });
11499
11871
 
11500
11872
  // ../core/src/agent/agentNudges.ts
11501
- var NUDGE_EVERY, NUDGE_FENCE, PHANTOM_FENCE, PHANTOM_RETRY_MAX, EARLY_FINAL_FENCE, EARLY_FINAL_TURN_THRESHOLD, EARLY_FINAL_MAX, TOOL_DIGEST_FENCE, TOOL_DIGEST_MAX, TOOL_DIGEST_WINDOW, TOOL_DIGEST_TEXT_MAX_LEN, PLAN_FIRST_FENCE, REPEAT_FENCE, READ_REPEAT_THRESHOLD, GREP_REPEAT_THRESHOLD, REPEAT_NUDGE_MAX, TODO_INCOMPLETE_FENCE, TODO_INCOMPLETE_MAX, PHANTOM_TEXT, EARLY_FINAL_TEXT, TOOL_DIGEST_TEXT, NUDGE_TEXT, COMPLEX_VERBS, COMPLEX_OBJECTS, COMPLEX_MARKERS, QUERY_LEAD, looksComplex, PLAN_FIRST_TEXT, looksComplete, parseToolCall, normPath, normQuery, REPEAT_READ_TEXT, REPEAT_GREP_TEXT, TODO_INCOMPLETE_TEXT, createNudgeScheduler;
11873
+ var NUDGE_EVERY, NUDGE_FENCE, PHANTOM_FENCE, PHANTOM_RETRY_MAX, EARLY_FINAL_FENCE, EARLY_FINAL_TURN_THRESHOLD, EARLY_FINAL_MAX, TOOL_DIGEST_FENCE, TOOL_DIGEST_MAX, TOOL_DIGEST_WINDOW, TOOL_DIGEST_TEXT_MAX_LEN, PLAN_FIRST_FENCE, REPEAT_FENCE, READ_REPEAT_THRESHOLD, GREP_REPEAT_THRESHOLD, REPEAT_NUDGE_MAX, TODO_INCOMPLETE_FENCE, TODO_INCOMPLETE_MAX, PHANTOM_TEXT, EARLY_FINAL_TEXT, TOOL_DIGEST_TEXT, usageNoteText, NUDGE_TEXT, COMPLEX_VERBS, COMPLEX_OBJECTS, COMPLEX_MARKERS, QUERY_LEAD, looksComplex, PLAN_FIRST_TEXT, looksComplete, parseToolCall, normPath, normQuery, REPEAT_READ_TEXT, REPEAT_GREP_TEXT, TODO_INCOMPLETE_TEXT, createNudgeScheduler;
11502
11874
  var init_agentNudges = __esm({
11503
11875
  "../core/src/agent/agentNudges.ts"() {
11504
11876
  "use strict";
@@ -11523,7 +11895,11 @@ var init_agentNudges = __esm({
11523
11895
  PHANTOM_TEXT = "\u4F60\u7684\u4E0A\u4E00\u6761\u56DE\u590D\u6CA1\u6709\u4EFB\u4F55\u5185\u5BB9\u3001\u4E5F\u6CA1\u6709\u8C03\u7528\u4EFB\u4F55\u5DE5\u5177\uFF0C\u4F46\u4EFB\u52A1\u5C1A\u672A\u5B8C\u6210\u3002\u8BF7\u7EE7\u7EED\u63A8\u8FDB\uFF08\u8C03\u7528\u5DE5\u5177\u6216\u7ED9\u51FA\u5B9E\u8D28\u56DE\u7B54\uFF09\uFF1B\u82E5\u786E\u5B9E\u53D7\u963B\u3001\u9700\u8981\u7528\u6237\u51B3\u7B56\uFF0C\u7528 ask_question \u8BF4\u660E\u5177\u4F53\u963B\u585E\u70B9\u3002\u4E0D\u8981\u8FD4\u56DE\u7A7A\u56DE\u590D\u3002";
11524
11896
  EARLY_FINAL_TEXT = (round) => `\u4F60\u4EC5 ${round} \u8F6E\u5C31\u51C6\u5907\u6536\u5C3E\uFF0C\u4E14\u56DE\u7B54\u4E2D\u6CA1\u6709\u660E\u786E\u7684\u5B8C\u6210\u58F0\u660E\u3002\u8BF7\u81EA\u68C0\uFF1A\u82E5\u7528\u6237\u8F93\u5165\u672C\u5C31\u65E0\u9700\u5DE5\u5177\uFF08\u5BD2\u6684/\u95F2\u804A/\u7EAF\u95EE\u7B54\uFF09\u3001\u6216\u4F60\u5728\u7B49\u7528\u6237\u6F84\u6E05\u51B3\u7B56\uFF0C\u76F4\u63A5\u6B63\u5E38\u56DE\u5E94\u5373\u53EF\uFF0C\u5FFD\u7565\u672C\u81EA\u68C0\u3001\u4E0D\u8981\u8F93\u51FA\u4EFB\u4F55\u81EA\u68C0\u8BF4\u660E\uFF1B\u82E5\u786E\u6709\u4EFB\u52A1\u672A\u843D\u5730\uFF08\u6240\u9700\u4FE1\u606F\u672A\u83B7\u53D6\u9F50 / \u8BE5\u6539\u7684\u6587\u4EF6\u672A\u6539\u5B8C / \u672A\u9A8C\u8BC1\uFF09\uFF0C\u7ACB\u5373\u7EE7\u7EED\u8C03\u7528\u5DE5\u5177\u63A8\u8FDB\uFF0C\u4E0D\u8981\u8349\u7387\u603B\u7ED3\u6536\u5C3E\uFF1B\u82E5\u786E\u5DF2\u5168\u90E8\u5B8C\u6210\uFF0C\u8BF7\u660E\u786E\u56DE\u590D\u300C\u5DF2\u5B8C\u6210\u300D\u5E76\u7B80\u8FF0\u6210\u679C\u3002`;
11525
11897
  TOOL_DIGEST_TEXT = "\u4F60\u521A\u6267\u884C\u5B8C\u5DE5\u5177\u62FF\u5230\u7ED3\u679C\uFF0C\u5374\u672A\u57FA\u4E8E\u8BE5\u7ED3\u679C\u7ED9\u51FA\u5B9E\u8D28\u56DE\u5E94\u5C31\u51C6\u5907\u6536\u5C3E\u3002\u8BF7\u7ED3\u5408\u5DE5\u5177\u8FD4\u56DE\u7ED3\u679C\u7EE7\u7EED\u63A8\u8FDB\uFF1B\u82E5\u7ED3\u679C\u8868\u660E\u4EFB\u52A1\u5C1A\u672A\u5B8C\u6210\uFF08\u5982\u4ECD\u5728\u7F16\u8BD1/\u8FD0\u884C\u3001\u9700\u7EE7\u7EED\u8F6E\u8BE2\uFF09\uFF0C\u7ACB\u5373\u91C7\u53D6\u4E0B\u4E00\u6B65\u884C\u52A8\uFF0C\u4E0D\u8981\u7A7A\u624B\u6536\u5C3E\u3002\u82E5\u786E\u5DF2\u5168\u90E8\u5B8C\u6210\uFF0C\u8BF7\u660E\u786E\u56DE\u590D\u300C\u5DF2\u5B8C\u6210\u300D\u5E76\u7B80\u8FF0\u6210\u679C\u3002";
11526
- NUDGE_TEXT = (round) => `\u4F60\u5DF2\u6267\u884C\u7EA6 ${round} \u8F6E\u5DE5\u5177\u8C03\u7528\u3002\u8BF7\u81EA\u8BC4\uFF1A\u82E5\u4EFB\u52A1\u5DF2\u53EF\u5B8C\u6210\uFF0C\u7ACB\u5373\u7ED9\u51FA\u6700\u7EC8\u7B54\u6848\u3001\u4E0D\u518D\u8C03\u7528\u5DE5\u5177\uFF1B\u82E5\u786E\u9700\u66F4\u591A\u6B65\u9AA4\uFF0C\u7EE7\u7EED\uFF0C\u4F46\u786E\u4FDD\u6BCF\u6B65\u90FD\u5728\u5B9E\u8D28\u63A8\u8FDB\u4EFB\u52A1\u3001\u4E0D\u91CD\u590D\u68C0\u7D22\u3002`;
11898
+ usageNoteText = (usage) => {
11899
+ const pct = usage.windowTokens > 0 ? Math.min(100, Math.max(0, Math.round(usage.usedTokens / usage.windowTokens * 100))) : 0;
11900
+ return `\u5F53\u524D\u4E0A\u4E0B\u6587\u5DF2\u7528\u7EA6 ${pct}%\uFF08\u4F30\u7B97 \u2248${usage.usedTokens}/${usage.windowTokens} token\uFF09\u3002\u82E5\u5360\u6BD4\u5DF2\u9AD8\uFF08>70%\uFF09\uFF1A\u4F18\u5148\u6536\u655B\u6536\u5C3E\u3001\u51CF\u5C0F\u5355\u6B21\u8BFB\u53D6\u91CF\uFF08search_grep \u5B9A\u4F4D\u540E\u5C40\u90E8\u8BFB\u53D6\uFF09\uFF0C\u907F\u514D\u89E6\u53D1\u5F3A\u5236\u538B\u7F29\u3002`;
11901
+ };
11902
+ NUDGE_TEXT = (round, usage) => `\u4F60\u5DF2\u6267\u884C\u7EA6 ${round} \u8F6E\u5DE5\u5177\u8C03\u7528\u3002\u8BF7\u81EA\u8BC4\uFF1A\u82E5\u4EFB\u52A1\u5DF2\u53EF\u5B8C\u6210\uFF0C\u7ACB\u5373\u7ED9\u51FA\u6700\u7EC8\u7B54\u6848\u3001\u4E0D\u518D\u8C03\u7528\u5DE5\u5177\uFF1B\u82E5\u786E\u9700\u66F4\u591A\u6B65\u9AA4\uFF0C\u7EE7\u7EED\uFF0C\u4F46\u786E\u4FDD\u6BCF\u6B65\u90FD\u5728\u5B9E\u8D28\u63A8\u8FDB\u4EFB\u52A1\u3001\u4E0D\u91CD\u590D\u68C0\u7D22\u3002${usage ? usageNoteText(usage) : ""}`;
11527
11903
  COMPLEX_VERBS = /实现|新增|添加|重构|改造|迁移|重写|拆分|升级|开发|编写|构建|集成|支持|完善|implement|refactor|migrate|rewrite|rebuild|restructure|split|upgrade|integrate|develop/i;
11528
11904
  COMPLEX_OBJECTS = /功能|模块|系统|架构|流程|机制|组件|服务|页面|接口|能力|特性|面板|feature|module|system|architecture|pipeline|component|service|page|api\b|interface|panel|workflow|endpoint/i;
11529
11905
  COMPLEX_MARKERS = /多个文件|多文件|整体|全套|端到端|从零|重新设计|一整套|跨[^,。\s]{1,6}|multiple files|multi-file|end-to-end|from scratch|across\s+\S+/i;
@@ -11610,11 +11986,13 @@ ${PLAN_FIRST_TEXT}` };
11610
11986
  if (repeatRetrievalPending) {
11611
11987
  const m = repeatRetrievalPending;
11612
11988
  repeatRetrievalPending = null;
11613
- return m;
11989
+ const usage = opts.getContextUsage?.() ?? null;
11990
+ return usage ? { ...m, content: `${m.content}${usageNoteText(usage)}` } : m;
11614
11991
  }
11615
11992
  if (round > 1 && round % NUDGE_EVERY === 1) {
11993
+ const usage = opts.getContextUsage?.() ?? null;
11616
11994
  return { role: "system", content: `${NUDGE_FENCE}
11617
- ${NUDGE_TEXT(round)}` };
11995
+ ${NUDGE_TEXT(round, usage)}` };
11618
11996
  }
11619
11997
  return null;
11620
11998
  },
@@ -11839,7 +12217,13 @@ async function* runAgent(message, options) {
11839
12217
  const m = message.find((x) => x?.role === "user");
11840
12218
  return msgText(m?.content);
11841
12219
  })();
11842
- const nudges = createNudgeScheduler({ firstPrompt, planMode: !!options.planMode, noEarlyFinal: !!options.noEarlyFinal });
12220
+ const nudges = createNudgeScheduler({
12221
+ firstPrompt,
12222
+ planMode: !!options.planMode,
12223
+ noEarlyFinal: !!options.noEarlyFinal,
12224
+ getContextUsage: () => ({ usedTokens: Math.round(estimateTokens(message) * calibRatio + toolsTokens), windowTokens: modelWindow })
12225
+ });
12226
+ const readTracker = /* @__PURE__ */ new Map();
11843
12227
  const userDecisionSource = depth > 0 ? "spawn_agent" : "user";
11844
12228
  const llmDecisionSource = depth > 0 ? "llm_spawn_agent" : "llm";
11845
12229
  const startTime = performance.now();
@@ -11910,7 +12294,8 @@ async function* runAgent(message, options) {
11910
12294
  correctionRatio: calibRatio,
11911
12295
  lastRealPromptTokens,
11912
12296
  lastCachedTokens,
11913
- toolsTokens
12297
+ toolsTokens,
12298
+ onUIEvent: options.onUIEvent
11914
12299
  }
11915
12300
  );
11916
12301
  } catch (e) {
@@ -11943,7 +12328,8 @@ async function* runAgent(message, options) {
11943
12328
  keepRecentUnits,
11944
12329
  compactRatio,
11945
12330
  modelWindow,
11946
- toolsTokens
12331
+ toolsTokens,
12332
+ onUIEvent: options.onUIEvent
11947
12333
  });
11948
12334
  if (infResult.kind === "aborted") {
11949
12335
  yield { type: "final", text: infResult.partialText || lastContent || "\uFF08\u5DF2\u4E2D\u6B62\uFF09" };
@@ -11989,8 +12375,8 @@ async function* runAgent(message, options) {
11989
12375
  for (const tc of assistantMessage.tool_calls) {
11990
12376
  const ph = "\uFF08\u91CD\u590D\u8C03\u7528\u7194\u65AD\uFF0C\u672A\u6267\u884C\uFF09";
11991
12377
  message.push({ role: "tool", tool_call_id: tc.id, content: ph });
11992
- await appendMessage({ sessionId, role: "tool", tool_call_id: tc.id, content: ph });
11993
12378
  }
12379
+ await appendMessages(sessionId, assistantMessage.tool_calls.map((tc) => ({ sessionId, role: "tool", tool_call_id: tc.id, content: "\uFF08\u91CD\u590D\u8C03\u7528\u7194\u65AD\uFF0C\u672A\u6267\u884C\uFF09" })));
11994
12380
  yield { type: "final", text: repeatVerdict.text };
11995
12381
  return;
11996
12382
  }
@@ -12010,10 +12396,17 @@ async function* runAgent(message, options) {
12010
12396
  onUIEvent: options.onUIEvent,
12011
12397
  requestApproval: options.requestApproval,
12012
12398
  requestQuestion: options.requestQuestion,
12399
+ ideAction: options.ideAction,
12013
12400
  keepRecentUnits,
12014
12401
  compactRatio,
12015
12402
  modelWindow,
12016
- parentSystemPrompt
12403
+ parentSystemPrompt,
12404
+ // ★ P2-1:本时刻上下文用量估算(此时已含本轮 assistant,常数近似足够,不逐工具更新)——
12405
+ // processToolCall 据此收缩截断预算:最坏组合「上下文已 75% 满时跑大输出命令 → 16K 字符
12406
+ // 进上下文 → 下一轮立刻压缩」被釜底抽薪;口径与 ensureFitsWindow/nudge 闭包同公式。
12407
+ contextTokensEst: Math.round(estimateTokens(message) * calibRatio + toolsTokens),
12408
+ readTracker
12409
+ // ★ P3-8:run 级读取追踪(同一引用透传,跨轮累计)
12017
12410
  };
12018
12411
  const scheduleResult = yield* scheduleToolCalls(assistantMessage, message, toolCallCtx);
12019
12412
  if (scheduleResult.kind === "aborted") {
@@ -12153,7 +12546,7 @@ var init_lineage = __esm({
12153
12546
  });
12154
12547
 
12155
12548
  // ../core/src/agent/subagent.ts
12156
- var SUBAGENT_DENYLIST, activeSubagents, failText, RESUME_NOTE, runSubagent;
12549
+ var SUBAGENT_DENYLIST, activeSubagents, failText, RESUME_NOTE, createProgressThrottler, runSubagent;
12157
12550
  var init_subagent = __esm({
12158
12551
  "../core/src/agent/subagent.ts"() {
12159
12552
  "use strict";
@@ -12174,6 +12567,24 @@ var init_subagent = __esm({
12174
12567
  `\u4F60\u6B64\u524D\u7684\u5DE5\u4F5C\u5386\u53F2\u5DF2\u5B8C\u6574\u6062\u590D\u5230\u672C\u6B21\u4E0A\u4E0B\u6587\uFF08\u78C1\u76D8\u6700\u65B0\u72B6\u6001\uFF0C\u53EF\u80FD\u6BD4\u4F60\u4E0A\u6B21\u8BB0\u5FC6\u66F4\u65B0\uFF09\u3002`,
12175
12568
  `\u8BF7\u5148\u5BF9\u7167\u5386\u53F2\u7B80\u8981\u56DE\u987E\u5DF2\u5B8C\u6210 / \u672A\u5B8C\u6210\u90E8\u5206\uFF0C\u518D\u7EE7\u7EED\u6267\u884C\u65B0\u4EFB\u52A1\uFF1B\u6700\u7EC8\u6C47\u62A5\u4E5F\u8BF7\u5148\u6982\u8FF0\u6B64\u524D\u8FDB\u5C55\u3002`
12176
12569
  ].join("\n");
12570
+ createProgressThrottler = (onEmit, throttleMs = 1e3, now = Date.now) => {
12571
+ let acc = "";
12572
+ let lastAt = 0;
12573
+ return {
12574
+ push(delta) {
12575
+ acc += delta ?? "";
12576
+ const t = now();
12577
+ if (t - lastAt < throttleMs) return;
12578
+ lastAt = t;
12579
+ const lines = acc.split("\n");
12580
+ const complete = lines.slice(0, -1).filter((l) => l.trim().length > 0);
12581
+ let last = complete[complete.length - 1] ?? "";
12582
+ if (!last) return;
12583
+ if (last.length > 120) last = last.slice(0, 119) + "\u2026";
12584
+ onEmit(last);
12585
+ }
12586
+ };
12587
+ };
12177
12588
  runSubagent = async (spec, ctx, getGlobalTools) => {
12178
12589
  const { task, name, role, resumeSessionId } = spec;
12179
12590
  const resuming = typeof resumeSessionId === "string" && resumeSessionId.length > 0;
@@ -12255,6 +12666,7 @@ var init_subagent = __esm({
12255
12666
  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));
12256
12667
  console.log(`${resuming ? "\u{1F501} \u7EED\u8DD1" : "\u{1F423} \u6D3E\u751F"}\u5B50 Agent [\u6DF1\u5EA6: ${ctx.depth + 1}/${MAX_AGENT_DEPTH}][${subSessionId}]${manifest ? ` \u58F0\u660E\u5F0F=${manifest.name}` : ""} \u4EFB\u52A1: "${task.slice(0, 50)}..."`);
12257
12668
  activeSubagents.add(subSessionId);
12669
+ ctx.onUIEvent?.({ type: "subagent.count", running: activeSubagents.size, phase: "start", name: manifest?.name, depth: ctx.depth + 1 });
12258
12670
  try {
12259
12671
  const subMessages = await buildContextMessages(subSessionId, { role: "user", content: task }, subSystem, manifest?.model);
12260
12672
  await appendMessage({ sessionId: subSessionId, role: "user", content: task });
@@ -12276,6 +12688,8 @@ var init_subagent = __esm({
12276
12688
  // ★ 必须透传:否则子 agent 调用需审批工具时前端收不到弹窗,waitForUserApproval 永久挂起(死锁)
12277
12689
  requestApproval: ctx.requestApproval,
12278
12690
  // ★ 同步透传宿主审批钩子,子 agent 高危工具仍走同一审批通道
12691
+ ideAction: ctx.ideAction,
12692
+ // ★ IDE 桥透传:ide_diagnostics 等 SAFE 只读动作子 agent 也可用(缺省 undefined → 工具自隐藏)
12279
12693
  permissionMode: ctx.permissionMode,
12280
12694
  // ★ P1-6 透传:子 agent 工作区文件编辑也走 auto 分类器
12281
12695
  noEarlyFinal: true,
@@ -12286,6 +12700,7 @@ var init_subagent = __esm({
12286
12700
  const startCtx = { sessionId: subSessionId, parentSessionId: ctx.sessionId, task, depth: ctx.depth + 1, name: manifest?.name, cwd: ctx.cwd };
12287
12701
  await dispatch("SubagentStart", startCtx);
12288
12702
  let result;
12703
+ const progress = createProgressThrottler((line) => ctx.onUIEvent?.({ type: "subagent.progress", text: line }));
12289
12704
  try {
12290
12705
  for await (const e of runAgent(subMessages, subOptions)) {
12291
12706
  if (ctx.abortSignal?.aborted) {
@@ -12295,6 +12710,8 @@ var init_subagent = __esm({
12295
12710
  if (e.type === "final") {
12296
12711
  subResult = e.text;
12297
12712
  hasFinalResult = true;
12713
+ } else if (e.type === "text.delta") {
12714
+ progress.push(e.text ?? "");
12298
12715
  }
12299
12716
  }
12300
12717
  if (result === void 0) {
@@ -12306,6 +12723,10 @@ var init_subagent = __esm({
12306
12723
  } catch (streamError) {
12307
12724
  result = { ok: false, output: failText(`[\u5B50Agent\u5D29\u6E83]\uFF1A\u5B50 Agent \u5728\u8FED\u4EE3\u63A8\u7406\u4E3B\u5FAA\u73AF\u65F6\u906D\u9047\u5E95\u5C42\u5F02\u5E38: ${streamError.message}`), sessionId: subSessionId, manifestName: manifest?.name };
12308
12725
  } finally {
12726
+ try {
12727
+ ctx.onUIEvent?.({ type: "subagent.progress", text: "", done: true });
12728
+ } catch {
12729
+ }
12309
12730
  if (result) {
12310
12731
  await dispatch("SubagentStop", { ...startCtx, ok: result.ok, output: result.output.slice(0, 2e3) }).catch((e) => console.warn(`\u26A0\uFE0F SubagentStop hook \u5F02\u5E38\uFF08\u5DF2\u5FFD\u7565\uFF09: ${e?.message ?? e}`));
12311
12732
  }
@@ -12315,6 +12736,7 @@ var init_subagent = __esm({
12315
12736
  return { ok: false, output: failText(`[\u6D3E\u751F\u6267\u884C\u5931\u8D25]: ${error.message}`), sessionId: subSessionId, manifestName: manifest?.name };
12316
12737
  } finally {
12317
12738
  activeSubagents.delete(subSessionId);
12739
+ ctx.onUIEvent?.({ type: "subagent.count", running: activeSubagents.size, phase: "stop", name: manifest?.name, depth: ctx.depth + 1 });
12318
12740
  }
12319
12741
  };
12320
12742
  }
@@ -13618,7 +14040,7 @@ var init_systemPrompt = __esm({
13618
14040
 
13619
14041
  \u3010\u64CD\u4F5C\u786E\u8BA4\u7EA6\u5B9A\u3011
13620
14042
  - \u9047\u5230\u4F1A\u6539\u53D8\u72B6\u6001\u6216\u5177\u98CE\u9669\u7684\u5DE5\u5177\uFF08\u5199\u6587\u4EF6\u3001\u7F16\u8F91\u3001\u5220\u9664\u3001\u79FB\u52A8\u3001\u6267\u884C\u547D\u4EE4\u3001\u5BF9\u5916\u7F51\u7EDC\u8BF7\u6C42\u7B49\uFF09\uFF0C\u76F4\u63A5\u8C03\u7528\u8BE5\u5DE5\u5177\u5373\u53EF\uFF0C\u4E0D\u8981\u5148\u7528\u81EA\u7136\u8BED\u8A00\u5F81\u6C42\u786E\u8BA4\uFF08\u5982"\u6211\u53EF\u4EE5\u6267\u884C\u5417\uFF1F""\u662F\u5426\u7EE7\u7EED\uFF1F"\uFF09\u3002\u8FD9\u7C7B\u5DE5\u5177\u7531\u7CFB\u7EDF\u7EDF\u4E00\u62E6\u622A\u5E76\u5F39\u51FA\u5BA1\u6279\u754C\u9762\uFF0C\u7528\u6237\u4F1A\u4E00\u952E\u540C\u610F\u6216\u62D2\u7EDD\u2014\u2014\u4F60\u65E0\u9700\u4EE3\u66FF\u7CFB\u7EDF\u53D1\u95EE\uFF0C\u66F4\u4E0D\u8981\u505C\u4E0B\u7B49\u7528\u6237\u6253\u5B57\u786E\u8BA4\u3002
13621
- - \u4EC5\u5F53\u5B58\u5728\u591A\u79CD\u660E\u663E\u4E0D\u540C\u7684\u5B9E\u73B0\u65B9\u6848\u3001\u9700\u8981\u7528\u6237\u5728\u65B9\u5411\u4E0A\u62CD\u677F\u65F6\uFF0C\u8C03\u7528 ask_question \u5DE5\u5177\u63D0\u51FA 2-4 \u4E2A\u7ED3\u6784\u5316\u9009\u9879\u8BA9\u7528\u6237\u9009\u62E9\uFF08\u7EC8\u7AEF\u4F1A\u5F39\u51FA\u591A\u9009\u6846\uFF0C\u6BD4\u7EAF\u6587\u5B57\u66F4\u6E05\u6670\u9AD8\u6548\uFF0C\u7528\u6237\u9009\u62E9\u4F1A\u81EA\u52A8\u56DE\u4F20\u7ED9\u4F60\uFF09\uFF1B\u5BF9"\u67D0\u4E2A\u5177\u4F53\u64CD\u4F5C\u662F\u5426\u6267\u884C"\u4E00\u5F8B\u76F4\u63A5\u8C03\u7528\u5DE5\u5177\u8D70\u5BA1\u6279\uFF0C\u4E0D\u8981\u7528 ask_question \u53BB\u95EE\u80FD\u5426\u6267\u884C\u67D0\u4E2A\u64CD\u4F5C\u3002
14043
+ - \u4EC5\u5F53\u5B58\u5728\u591A\u79CD\u660E\u663E\u4E0D\u540C\u7684\u5B9E\u73B0\u65B9\u6848\u3001\u9700\u8981\u7528\u6237\u5728\u65B9\u5411\u4E0A\u62CD\u677F\u65F6\uFF0C\u8C03\u7528 ask_question \u5DE5\u5177\u63D0\u51FA 2-4 \u4E2A\u7ED3\u6784\u5316\u9009\u9879\u8BA9\u7528\u6237\u9009\u62E9\uFF08\u7EC8\u7AEF\u4F1A\u5F39\u51FA\u591A\u9009\u6846\uFF0C\u6BD4\u7EAF\u6587\u5B57\u66F4\u6E05\u6670\u9AD8\u6548\uFF0C\u7528\u6237\u9009\u62E9\u4F1A\u81EA\u52A8\u56DE\u4F20\u7ED9\u4F60\uFF1B\u63D0\u95EE\u6846\u6052\u6709\u300COther\u300D\u81EA\u7531\u8F93\u5165\u6863\uFF0C\u9700\u8981\u7528\u6237\u63D0\u4F9B\u9009\u9879\u4E4B\u5916\u7684\u8D44\u6599\u2014\u2014\u5982\u767B\u5F55 token\u3001\u6587\u4EF6\u8DEF\u5F84\u2014\u2014\u4E5F\u7528 ask_question \u63D0\u51FA\uFF0C\u7528\u6237\u4F1A\u7ECF Other \u7C98\u8D34\u539F\u6587\u56DE\u4F20\uFF09\uFF1B\u5BF9"\u67D0\u4E2A\u5177\u4F53\u64CD\u4F5C\u662F\u5426\u6267\u884C"\u4E00\u5F8B\u76F4\u63A5\u8C03\u7528\u5DE5\u5177\u8D70\u5BA1\u6279\uFF0C\u4E0D\u8981\u7528 ask_question \u53BB\u95EE\u80FD\u5426\u6267\u884C\u67D0\u4E2A\u64CD\u4F5C\u3002
13622
14044
 
13623
14045
  \u3010\u8BA1\u5212\u6A21\u5F0F\u3011
13624
14046
  ${PLAN_MODE_AUTO_ENTER_HINT}
@@ -13759,6 +14181,7 @@ ${envLines.join("\n")}`;
13759
14181
  onUIEvent,
13760
14182
  requestApproval: requestApproval2,
13761
14183
  requestQuestion: opts?.requestQuestion,
14184
+ ideAction: opts?.ideAction,
13762
14185
  // ★ CLI 宿主注入项:计划模式两阶段 / 模型覆盖 / 思考等级。serve 不传 → 均为 undefined,行为不变。
13763
14186
  planMode: opts?.planMode,
13764
14187
  permissionMode: opts?.permissionMode,
@@ -14211,6 +14634,7 @@ var init_useChatState = __esm({
14211
14634
  const [rows, setRows] = useState([]);
14212
14635
  const [busy, setBusy] = useState(false);
14213
14636
  const [aborting, setAborting] = useState(false);
14637
+ const [runningAgents, setRunningAgents] = useState(0);
14214
14638
  const [showThinkingText, setShowThinkingText] = useState(false);
14215
14639
  const [pendingApproval, setPendingApproval] = useState(null);
14216
14640
  const [pendingQuestion, setPendingQuestion] = useState(null);
@@ -14239,6 +14663,21 @@ var init_useChatState = __esm({
14239
14663
  const thinkingLevelRef = useRef(
14240
14664
  !MODEL_THINKING_ENABLED ? "off" : MODEL_REASONING_EFFORT === "max" ? "max" : "high"
14241
14665
  );
14666
+ const archivedSummaryRef = useRef("");
14667
+ const compactProgressIdRef = useRef(null);
14668
+ const dropCompactProgressRow = useCallback(() => {
14669
+ const pid = compactProgressIdRef.current;
14670
+ if (pid == null) return;
14671
+ compactProgressIdRef.current = null;
14672
+ setRows((prev) => prev.filter((r) => r.id !== pid));
14673
+ }, []);
14674
+ const subagentProgressIdRef = useRef(null);
14675
+ const dropSubagentProgressRow = useCallback(() => {
14676
+ const pid = subagentProgressIdRef.current;
14677
+ if (pid == null) return;
14678
+ subagentProgressIdRef.current = null;
14679
+ setRows((prev) => prev.filter((r) => r.id !== pid));
14680
+ }, []);
14242
14681
  const outputStyleRef = useRef(void 0);
14243
14682
  const lastUsageRef = useRef(null);
14244
14683
  useEffect(() => {
@@ -14374,6 +14813,69 @@ var init_useChatState = __esm({
14374
14813
  }
14375
14814
  break;
14376
14815
  }
14816
+ case "compact.progress": {
14817
+ flush();
14818
+ const done = obj.done ?? 0;
14819
+ const total = obj.total ?? 0;
14820
+ if (compactProgressIdRef.current == null) {
14821
+ const id = newRowId();
14822
+ compactProgressIdRef.current = id;
14823
+ setRows((prev) => [...prev, { id, kind: "compact-progress", done, total }]);
14824
+ } else {
14825
+ const pid = compactProgressIdRef.current;
14826
+ setRows((prev) => prev.map((r) => r.id === pid && r.kind === "compact-progress" ? { ...r, done, total } : r));
14827
+ }
14828
+ break;
14829
+ }
14830
+ case "compact.done": {
14831
+ flush();
14832
+ dropCompactProgressRow();
14833
+ archivedSummaryRef.current = obj.summary || "";
14834
+ setRows((prev) => [...prev, {
14835
+ id: newRowId(),
14836
+ kind: "compact",
14837
+ tokensBefore: obj.tokensBefore ?? 0,
14838
+ tokensAfter: obj.tokensAfter ?? 0,
14839
+ trigger: obj.trigger ?? "auto",
14840
+ hasSummary: !!obj.summary
14841
+ }]);
14842
+ break;
14843
+ }
14844
+ case "task.exit": {
14845
+ flush();
14846
+ const cmd = obj.command ?? "";
14847
+ const id = newRowId();
14848
+ if (obj.ok) {
14849
+ setRows((prev) => [...prev, { id, kind: "meta", text: `\u{1F4E7} \u540E\u53F0\u4EFB\u52A1\u5B8C\u6210\uFF08exit=0\uFF09\uFF1A${cmd}` }]);
14850
+ } else if (obj.status === "killed") {
14851
+ setRows((prev) => [...prev, { id, kind: "meta", text: `\u23F9 \u540E\u53F0\u4EFB\u52A1\u5DF2\u505C\u6B62\uFF1A${cmd}` }]);
14852
+ } else {
14853
+ setRows((prev) => [...prev, { id, kind: "system", text: `\u26A0\uFE0F \u540E\u53F0\u4EFB\u52A1\u5931\u8D25\uFF08exit=${obj.exitCode ?? "?"}\uFF09\uFF1A${cmd}` }]);
14854
+ }
14855
+ break;
14856
+ }
14857
+ case "subagent.count": {
14858
+ setRunningAgents(Math.max(0, obj.running ?? 0));
14859
+ break;
14860
+ }
14861
+ case "subagent.progress": {
14862
+ flush();
14863
+ if (obj.done) {
14864
+ dropSubagentProgressRow();
14865
+ break;
14866
+ }
14867
+ const ptext = String(obj.text ?? "").slice(0, 160);
14868
+ if (!ptext) break;
14869
+ if (subagentProgressIdRef.current == null) {
14870
+ const id = newRowId();
14871
+ subagentProgressIdRef.current = id;
14872
+ setRows((prev) => [...prev, { id, kind: "subagent-progress", text: ptext }]);
14873
+ } else {
14874
+ const pid = subagentProgressIdRef.current;
14875
+ setRows((prev) => prev.map((r) => r.id === pid && r.kind === "subagent-progress" ? { ...r, text: ptext } : r));
14876
+ }
14877
+ break;
14878
+ }
14377
14879
  case "round.start": {
14378
14880
  closeStreaming();
14379
14881
  break;
@@ -14393,6 +14895,9 @@ var init_useChatState = __esm({
14393
14895
  }
14394
14896
  case "final": {
14395
14897
  closeStreaming();
14898
+ dropCompactProgressRow();
14899
+ dropSubagentProgressRow();
14900
+ setRunningAgents(0);
14396
14901
  const ft = obj.text ?? "";
14397
14902
  if (ft) {
14398
14903
  const id = newRowId();
@@ -14402,6 +14907,9 @@ var init_useChatState = __esm({
14402
14907
  }
14403
14908
  case "error": {
14404
14909
  closeStreaming();
14910
+ dropCompactProgressRow();
14911
+ dropSubagentProgressRow();
14912
+ setRunningAgents(0);
14405
14913
  const id = newRowId();
14406
14914
  setRows((prev) => [...prev, { id, kind: "system", text: `\u274C ${obj.message ?? "\u672A\u77E5\u9519\u8BEF"}` }]);
14407
14915
  break;
@@ -14630,6 +15138,7 @@ ${r.output.trim() || "(\u65E0\u8F93\u51FA)"}`);
14630
15138
  const clearRows = useCallback(() => {
14631
15139
  flush();
14632
15140
  setRows([]);
15141
+ setRunningAgents(0);
14633
15142
  toolRowByCallId.current.clear();
14634
15143
  }, [flush]);
14635
15144
  const loadSession = useCallback(async (id) => {
@@ -14728,11 +15237,13 @@ ${r.output.trim() || "(\u65E0\u8F93\u51FA)"}`);
14728
15237
  outputStyleRef.current = name;
14729
15238
  }, []);
14730
15239
  const getOutputStyle2 = useCallback(() => outputStyleRef.current, []);
15240
+ const getArchivedSummary = useCallback(() => archivedSummaryRef.current, []);
14731
15241
  return {
14732
15242
  // 状态
14733
15243
  rows,
14734
15244
  busy,
14735
15245
  aborting,
15246
+ runningAgents,
14736
15247
  showThinkingText,
14737
15248
  pendingApproval,
14738
15249
  pendingQuestion,
@@ -14765,6 +15276,7 @@ ${r.output.trim() || "(\u65E0\u8F93\u51FA)"}`);
14765
15276
  getThinkingLevel,
14766
15277
  setOutputStyle,
14767
15278
  getOutputStyle: getOutputStyle2,
15279
+ getArchivedSummary,
14768
15280
  openSessionPicker,
14769
15281
  resolveSession,
14770
15282
  loadSession,
@@ -15003,6 +15515,33 @@ var init_MessageBlock = __esm({
15003
15515
  row.text
15004
15516
  ] }) });
15005
15517
  }
15518
+ if (row.kind === "compact") {
15519
+ const freed = Math.max(0, (row.tokensBefore || 0) - (row.tokensAfter || 0));
15520
+ const freedText = freed >= 1e3 ? `${Math.round(freed / 1e3)}k` : String(freed);
15521
+ return /* @__PURE__ */ jsx3(Box, { marginTop: 0.25, marginBottom: 0.25, children: /* @__PURE__ */ jsxs(Text3, { italic: true, color: THEME.grayDim, children: [
15522
+ "\u5DF2\u538B\u7F29\u4E0A\u4E0B\u6587 \xB7 ",
15523
+ row.trigger === "manual" ? "\u624B\u52A8" : "\u81EA\u52A8",
15524
+ " \xB7 \u91CA\u653E ",
15525
+ freedText,
15526
+ " tokens \xB7 \u4E0B\u6761\u6D88\u606F\u5C06\u91CD\u5EFA\u7F13\u5B58",
15527
+ row.hasSummary ? "\uFF08/archive \u770B\u6458\u8981\uFF09" : ""
15528
+ ] }) });
15529
+ }
15530
+ if (row.kind === "compact-progress") {
15531
+ return /* @__PURE__ */ jsx3(Box, { marginTop: 0.25, marginBottom: 0.25, children: /* @__PURE__ */ jsxs(Text3, { italic: true, color: THEME.grayDim, children: [
15532
+ "\u6B63\u5728\u538B\u7F29\u4E0A\u4E0B\u6587 ",
15533
+ row.done,
15534
+ "/",
15535
+ row.total,
15536
+ " \u6279\u2026\uFF08\u8F85\u52A9\u6A21\u578B\u6458\u8981\u4E2D\uFF0C\u8BF7\u7A0D\u5019\uFF09"
15537
+ ] }) });
15538
+ }
15539
+ if (row.kind === "subagent-progress") {
15540
+ return /* @__PURE__ */ jsx3(Box, { marginTop: 0.25, marginBottom: 0.25, children: /* @__PURE__ */ jsxs(Text3, { italic: true, color: THEME.grayDim, children: [
15541
+ "\u21B3 \u5B50agent ",
15542
+ row.text
15543
+ ] }) });
15544
+ }
15006
15545
  if (row.kind === "info") {
15007
15546
  const lines = wrapText(row.text, Math.max(8, wrapW));
15008
15547
  return /* @__PURE__ */ jsx3(Box, { flexDirection: "column", marginTop: 0.5, marginBottom: 0.5, children: lines.map((line, j) => /* @__PURE__ */ jsxs(Text3, { color: THEME.coralMuted, children: [
@@ -15413,79 +15952,9 @@ var init_ApprovalModal = __esm({
15413
15952
  }
15414
15953
  });
15415
15954
 
15416
- // src/components/QuestionModal.tsx
15417
- import { Box as Box6, Text as Text8 } from "ink";
15418
- import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
15419
- var QuestionModal;
15420
- var init_QuestionModal = __esm({
15421
- "src/components/QuestionModal.tsx"() {
15422
- "use strict";
15423
- init_theme();
15424
- init_util();
15425
- QuestionModal = ({ question, options, multiSelect, cursor, checked, wrapW }) => {
15426
- const contentW = Math.max(16, wrapW);
15427
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "round", borderColor: THEME.coralBright, paddingX: 1, paddingY: 1, marginTop: 1, children: [
15428
- /* @__PURE__ */ jsxs6(Text8, { color: THEME.coralBright, bold: true, children: [
15429
- "\u2753 ",
15430
- multiSelect ? "\u591A\u9009\u63D0\u95EE" : "\u63D0\u95EE"
15431
- ] }),
15432
- /* @__PURE__ */ jsx8(Box6, { flexDirection: "column", marginTop: 0.5, marginBottom: 0.5, children: wrapText(question || "(\u65E0\u95EE\u9898)", contentW).map((line, j) => /* @__PURE__ */ jsx8(Text8, { color: THEME.white, children: line }, j)) }),
15433
- options.map((opt, i) => {
15434
- const active = i === cursor;
15435
- const isChecked = checked.has(i);
15436
- const marker2 = multiSelect ? isChecked ? "[x] " : "[ ] " : "";
15437
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
15438
- /* @__PURE__ */ jsxs6(Box6, { flexDirection: "row", children: [
15439
- /* @__PURE__ */ jsx8(Text8, { color: active ? THEME.coralBright : void 0, bold: true, children: active ? "\u203A " : " " }),
15440
- /* @__PURE__ */ jsxs6(Text8, { color: active ? THEME.white : THEME.gray, bold: active, children: [
15441
- marker2,
15442
- opt.label
15443
- ] })
15444
- ] }),
15445
- opt.description ? /* @__PURE__ */ jsxs6(Text8, { color: THEME.grayDim, children: [
15446
- " ",
15447
- wrapText(opt.description, contentW - 4).map((l) => l).join(" ")
15448
- ] }) : null
15449
- ] }, i);
15450
- }),
15451
- /* @__PURE__ */ jsx8(Text8, { color: THEME.grayDim, children: multiSelect ? "\u2191\u2193 \u79FB\u52A8 \xB7 Space \u52FE\u9009 \xB7 Enter \u63D0\u4EA4 \xB7 Esc \u53D6\u6D88" : "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u786E\u8BA4 \xB7 Esc \u53D6\u6D88" })
15452
- ] });
15453
- };
15454
- }
15455
- });
15456
-
15457
- // src/components/PlanModal.tsx
15458
- import { Box as Box7, Text as Text9 } from "ink";
15459
- import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
15460
- var OPTIONS2, PlanModal;
15461
- var init_PlanModal = __esm({
15462
- "src/components/PlanModal.tsx"() {
15463
- "use strict";
15464
- init_theme();
15465
- init_strings();
15466
- OPTIONS2 = [
15467
- { label: S.planAcceptAuto, color: THEME.ok },
15468
- { label: S.planAcceptManual, color: THEME.warn },
15469
- { label: S.planEdit, color: THEME.blue },
15470
- { label: S.planReject, color: THEME.danger }
15471
- ];
15472
- PlanModal = ({ selectedIndex }) => /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "round", borderColor: THEME.blue, paddingX: 1, paddingY: 1, marginTop: 1, children: [
15473
- /* @__PURE__ */ jsx9(Text9, { color: THEME.blue, bold: true, children: S.planTitle }),
15474
- /* @__PURE__ */ jsx9(Text9, { color: THEME.grayDim, children: S.planPrompt }),
15475
- OPTIONS2.map((opt, i) => {
15476
- const active = i === selectedIndex;
15477
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "row", children: [
15478
- /* @__PURE__ */ jsx9(Text9, { color: active ? THEME.coralBright : void 0, bold: true, children: active ? "\u203A " : " " }),
15479
- /* @__PURE__ */ jsx9(Text9, { color: active ? THEME.white : opt.color, bold: active, children: opt.label })
15480
- ] }, opt.label);
15481
- })
15482
- ] });
15483
- }
15484
- });
15485
-
15486
15955
  // src/components/MultilineInput.tsx
15487
- import { Box as Box8, Text as Text10, useInput } from "ink";
15488
- import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
15956
+ import { Box as Box6, Text as Text8, useInput } from "ink";
15957
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
15489
15958
  var lineColOf, offsetOf, MultilineInput;
15490
15959
  var init_MultilineInput = __esm({
15491
15960
  "src/components/MultilineInput.tsx"() {
@@ -15546,30 +16015,112 @@ var init_MultilineInput = __esm({
15546
16015
  const [curLine, curCol] = lineColOf(value, cursor);
15547
16016
  const lines = value.length ? value.split("\n") : [""];
15548
16017
  const renderLine = (line, i, isActive) => {
15549
- if (!isActive) return /* @__PURE__ */ jsx10(Text10, { color: THEME.white, children: line || " " }, i);
16018
+ if (!isActive) return /* @__PURE__ */ jsx8(Text8, { color: THEME.white, children: line || " " }, i);
15550
16019
  const before = line.slice(0, curCol);
15551
16020
  const at = curCol < line.length ? line[curCol] : "";
15552
16021
  const after = curCol < line.length ? line.slice(curCol + 1) : "";
15553
- return /* @__PURE__ */ jsxs8(Text10, { children: [
15554
- /* @__PURE__ */ jsx10(Text10, { color: THEME.white, children: before }),
15555
- at ? /* @__PURE__ */ jsx10(Text10, { inverse: true, color: THEME.white, children: at }) : /* @__PURE__ */ jsx10(Text10, { color: THEME.coralBright, children: "\u2588" }),
15556
- /* @__PURE__ */ jsx10(Text10, { color: THEME.white, children: after })
16022
+ return /* @__PURE__ */ jsxs6(Text8, { children: [
16023
+ /* @__PURE__ */ jsx8(Text8, { color: THEME.white, children: before }),
16024
+ at ? /* @__PURE__ */ jsx8(Text8, { inverse: true, color: THEME.white, children: at }) : /* @__PURE__ */ jsx8(Text8, { color: THEME.coralBright, children: "\u2588" }),
16025
+ /* @__PURE__ */ jsx8(Text8, { color: THEME.white, children: after })
15557
16026
  ] }, i);
15558
16027
  };
15559
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "row", flexShrink: 0, children: [
15560
- /* @__PURE__ */ jsx10(Text10, { bold: true, color: THEME.coralBright, children: "> " }),
15561
- /* @__PURE__ */ jsx10(Box8, { flexGrow: 1, minWidth: 0, children: value === "" && placeholder ? /* @__PURE__ */ jsxs8(Text10, { children: [
15562
- /* @__PURE__ */ jsx10(Text10, { color: THEME.coralBright, children: "\u2588" }),
15563
- /* @__PURE__ */ jsxs8(Text10, { color: THEME.grayDim, children: [
16028
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "row", flexShrink: 0, children: [
16029
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: THEME.coralBright, children: "> " }),
16030
+ /* @__PURE__ */ jsx8(Box6, { flexGrow: 1, minWidth: 0, children: value === "" && placeholder ? /* @__PURE__ */ jsxs6(Text8, { children: [
16031
+ /* @__PURE__ */ jsx8(Text8, { color: THEME.coralBright, children: "\u2588" }),
16032
+ /* @__PURE__ */ jsxs6(Text8, { color: THEME.grayDim, children: [
15564
16033
  " ",
15565
16034
  placeholder
15566
16035
  ] })
15567
- ] }) : /* @__PURE__ */ jsx10(Box8, { flexDirection: "column", children: lines.map((line, i) => renderLine(line, i, i === curLine)) }) })
16036
+ ] }) : /* @__PURE__ */ jsx8(Box6, { flexDirection: "column", children: lines.map((line, i) => renderLine(line, i, i === curLine)) }) })
15568
16037
  ] });
15569
16038
  };
15570
16039
  }
15571
16040
  });
15572
16041
 
16042
+ // src/components/QuestionModal.tsx
16043
+ import { Box as Box7, Text as Text9 } from "ink";
16044
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
16045
+ var QuestionModal;
16046
+ var init_QuestionModal = __esm({
16047
+ "src/components/QuestionModal.tsx"() {
16048
+ "use strict";
16049
+ init_theme();
16050
+ init_util();
16051
+ init_MultilineInput();
16052
+ QuestionModal = ({ question, options, multiSelect, cursor, checked, wrapW, editing, draft, draftCursor, onDraftChange, onDraftSubmit }) => {
16053
+ const contentW = Math.max(16, wrapW);
16054
+ const onOther = cursor === options.length && !editing;
16055
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "round", borderColor: THEME.coralBright, paddingX: 1, paddingY: 1, marginTop: 1, children: [
16056
+ /* @__PURE__ */ jsxs7(Text9, { color: THEME.coralBright, bold: true, children: [
16057
+ "\u2753 ",
16058
+ multiSelect ? "\u591A\u9009\u63D0\u95EE" : "\u63D0\u95EE"
16059
+ ] }),
16060
+ /* @__PURE__ */ jsx9(Box7, { flexDirection: "column", marginTop: 0.5, marginBottom: 0.5, children: wrapText(question || "(\u65E0\u95EE\u9898)", contentW).map((line, j) => /* @__PURE__ */ jsx9(Text9, { color: THEME.white, children: line }, j)) }),
16061
+ options.map((opt, i) => {
16062
+ const active = i === cursor;
16063
+ const isChecked = checked.has(i);
16064
+ const marker2 = multiSelect ? isChecked ? "[x] " : "[ ] " : "";
16065
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
16066
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "row", children: [
16067
+ /* @__PURE__ */ jsx9(Text9, { color: active ? THEME.coralBright : void 0, bold: true, children: active ? "\u203A " : " " }),
16068
+ /* @__PURE__ */ jsxs7(Text9, { color: active ? THEME.white : THEME.gray, bold: active, children: [
16069
+ marker2,
16070
+ opt.label
16071
+ ] })
16072
+ ] }),
16073
+ opt.description ? /* @__PURE__ */ jsxs7(Text9, { color: THEME.grayDim, children: [
16074
+ " ",
16075
+ wrapText(opt.description, contentW - 4).map((l) => l).join(" ")
16076
+ ] }) : null
16077
+ ] }, i);
16078
+ }),
16079
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginTop: 0.5, children: [
16080
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "row", children: [
16081
+ /* @__PURE__ */ jsx9(Text9, { color: onOther ? THEME.coralBright : void 0, bold: true, children: onOther ? "\u203A " : " " }),
16082
+ /* @__PURE__ */ jsxs7(Text9, { color: editing || onOther ? THEME.white : THEME.gray, bold: onOther, children: [
16083
+ "\u270E Other",
16084
+ editing ? "\uFF08\u8F93\u5165\u540E Enter \u63D0\u4EA4\uFF09" : " \u2014 \u81EA\u7531\u8F93\u5165"
16085
+ ] })
16086
+ ] }),
16087
+ editing ? /* @__PURE__ */ jsx9(Box7, { flexDirection: "row", marginLeft: 2, marginTop: 0.5, children: /* @__PURE__ */ jsx9(MultilineInput, { value: draft, cursor: draftCursor, onChange: onDraftChange, onSubmit: onDraftSubmit, active: true, placeholder: "\u7C98\u8D34 token / \u8DEF\u5F84\u7B49\uFF0CEnter \u63D0\u4EA4" }) }) : null
16088
+ ] }),
16089
+ /* @__PURE__ */ jsx9(Text9, { color: THEME.grayDim, children: editing ? "Enter \u63D0\u4EA4 \xB7 Esc \u8FD4\u56DE\u9009\u9879" : multiSelect ? "\u2191\u2193 \u79FB\u52A8 \xB7 Space \u52FE\u9009 \xB7 Enter \u63D0\u4EA4 \xB7 Esc \u53D6\u6D88" : "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u786E\u8BA4 \xB7 Esc \u53D6\u6D88" })
16090
+ ] });
16091
+ };
16092
+ }
16093
+ });
16094
+
16095
+ // src/components/PlanModal.tsx
16096
+ import { Box as Box8, Text as Text10 } from "ink";
16097
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
16098
+ var OPTIONS2, PlanModal;
16099
+ var init_PlanModal = __esm({
16100
+ "src/components/PlanModal.tsx"() {
16101
+ "use strict";
16102
+ init_theme();
16103
+ init_strings();
16104
+ OPTIONS2 = [
16105
+ { label: S.planAcceptAuto, color: THEME.ok },
16106
+ { label: S.planAcceptManual, color: THEME.warn },
16107
+ { label: S.planEdit, color: THEME.blue },
16108
+ { label: S.planReject, color: THEME.danger }
16109
+ ];
16110
+ PlanModal = ({ selectedIndex }) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", borderStyle: "round", borderColor: THEME.blue, paddingX: 1, paddingY: 1, marginTop: 1, children: [
16111
+ /* @__PURE__ */ jsx10(Text10, { color: THEME.blue, bold: true, children: S.planTitle }),
16112
+ /* @__PURE__ */ jsx10(Text10, { color: THEME.grayDim, children: S.planPrompt }),
16113
+ OPTIONS2.map((opt, i) => {
16114
+ const active = i === selectedIndex;
16115
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "row", children: [
16116
+ /* @__PURE__ */ jsx10(Text10, { color: active ? THEME.coralBright : void 0, bold: true, children: active ? "\u203A " : " " }),
16117
+ /* @__PURE__ */ jsx10(Text10, { color: active ? THEME.white : opt.color, bold: active, children: opt.label })
16118
+ ] }, opt.label);
16119
+ })
16120
+ ] });
16121
+ }
16122
+ });
16123
+
15573
16124
  // src/components/PlanEditor.tsx
15574
16125
  import { Box as Box9, Text as Text11 } from "ink";
15575
16126
  import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
@@ -16047,7 +16598,7 @@ var init_App = __esm({
16047
16598
  }
16048
16599
  return /* @__PURE__ */ jsx18(MessageBlock, { row, wrapW, streamTail });
16049
16600
  };
16050
- isDynamicRow = (r) => r.kind === "assistant" && !!r.streaming || r.kind === "thinking" && !!r.streaming || r.kind === "tool" && r.status === "running" || r.kind === "todos" && !!r.active;
16601
+ isDynamicRow = (r) => r.kind === "assistant" && !!r.streaming || r.kind === "thinking" && !!r.streaming || r.kind === "tool" && r.status === "running" || r.kind === "todos" && !!r.active || r.kind === "compact-progress" || r.kind === "subagent-progress";
16051
16602
  SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
16052
16603
  App = ({ resumeSessionId, initialPlanMode, initialAutoMode, initialIncludeProject, engineReady }) => {
16053
16604
  const state = useChatState(resumeSessionId, initialPlanMode, initialAutoMode);
@@ -16076,8 +16627,13 @@ var init_App = __esm({
16076
16627
  const [planCursor, setPlanCursor] = useState4(0);
16077
16628
  const [qCursor, setQCursor] = useState4(0);
16078
16629
  const [qChecked, setQChecked] = useState4(/* @__PURE__ */ new Set());
16630
+ const [qEditing, setQEditing] = useState4(false);
16631
+ const [qDraft, setQDraft] = useState4("");
16632
+ const [qDraftCursor, setQDraftCursor] = useState4(0);
16079
16633
  const qCursorRef = useRef2(0);
16080
16634
  const qCheckedRef = useRef2(/* @__PURE__ */ new Set());
16635
+ const qEditingRef = useRef2(false);
16636
+ const qDraftRef = useRef2("");
16081
16637
  const inputRef = useRef2(input);
16082
16638
  const selectIdxRef = useRef2(selectIdx);
16083
16639
  const busyRef = useRef2(state.busy);
@@ -16090,6 +16646,8 @@ var init_App = __esm({
16090
16646
  planDraftRef.current = planDraft;
16091
16647
  qCursorRef.current = qCursor;
16092
16648
  qCheckedRef.current = qChecked;
16649
+ qEditingRef.current = qEditing;
16650
+ qDraftRef.current = qDraft;
16093
16651
  const statusLineCfgRef = useRef2(null);
16094
16652
  const statusLineRunRef = useRef2(false);
16095
16653
  const [statusLineText, setStatusLineText] = useState4("");
@@ -16231,6 +16789,9 @@ var init_App = __esm({
16231
16789
  useEffect4(() => {
16232
16790
  setQCursor(0);
16233
16791
  setQChecked(/* @__PURE__ */ new Set());
16792
+ setQEditing(false);
16793
+ setQDraft("");
16794
+ setQDraftCursor(0);
16234
16795
  }, [state.pendingQuestion]);
16235
16796
  useEffect4(() => {
16236
16797
  if (state.pendingModel) {
@@ -16307,6 +16868,12 @@ ${list}
16307
16868
  case "/debug":
16308
16869
  state.pushInfo(inspectDebug(state.sessionIdRef.current ?? ""));
16309
16870
  return true;
16871
+ case "/archive": {
16872
+ const s = state.getArchivedSummary();
16873
+ state.pushInfo(s ? `\u{1F4DC} \u6700\u8FD1\u4E00\u6B21\u538B\u7F29\u7684\u5F52\u6863\u6458\u8981\uFF1A
16874
+ ${s}` : "\u672C\u4F1A\u8BDD\u542F\u52A8\u4EE5\u6765\u5C1A\u672A\u53D1\u751F\u8FC7\u538B\u7F29\uFF08\u6458\u8981\u4EC5\u4FDD\u7559\u8FD0\u884C\u65F6\u6700\u8FD1\u4E00\u6B21\uFF09\u3002");
16875
+ return true;
16876
+ }
16310
16877
  case "/plan": {
16311
16878
  const on = !state.getPlanMode();
16312
16879
  state.setPlanMode(on);
@@ -16448,6 +17015,13 @@ ${listText}`);
16448
17015
  }
16449
17016
  state.resolvePlan({ action: "accept", plan: planDraftRef.current });
16450
17017
  };
17018
+ const confirmQuestionDraft = () => {
17019
+ const q = state.pendingQuestion?.req;
17020
+ const text = qDraftRef.current.trim();
17021
+ if (!q || !text) return;
17022
+ const sel = q.multiSelect ? [...qCheckedRef.current].sort((a, b) => a - b).map((i) => q.options[i]?.label).filter(Boolean) : [];
17023
+ state.resolveQuestion({ selected: sel, freeText: text });
17024
+ };
16451
17025
  useInput2((ch, key) => {
16452
17026
  if (key.ctrl && ch === "c") {
16453
17027
  exit();
@@ -16463,18 +17037,34 @@ ${listText}`);
16463
17037
  }
16464
17038
  if (state.pendingQuestion) {
16465
17039
  const q = state.pendingQuestion.req;
16466
- const n = q.options.length;
17040
+ const n = q.options.length + 1;
16467
17041
  const multi = !!q.multiSelect;
17042
+ if (qEditingRef.current) {
17043
+ if (key.escape || key.ctrl && ch === "g") {
17044
+ setQEditing(false);
17045
+ setQDraft("");
17046
+ setQDraftCursor(0);
17047
+ }
17048
+ return;
17049
+ }
16468
17050
  if (key.upArrow) setQCursor((i) => (i - 1 + n) % n);
16469
17051
  else if (key.downArrow) setQCursor((i) => (i + 1) % n);
16470
17052
  else if (multi && ch === " ") {
16471
- setQChecked((prev) => {
17053
+ if (qCursorRef.current >= q.options.length) {
17054
+ setQDraft("");
17055
+ setQDraftCursor(0);
17056
+ setQEditing(true);
17057
+ } else setQChecked((prev) => {
16472
17058
  const nx = new Set(prev);
16473
17059
  nx.has(qCursorRef.current) ? nx.delete(qCursorRef.current) : nx.add(qCursorRef.current);
16474
17060
  return nx;
16475
17061
  });
16476
17062
  } else if (key.return) {
16477
- if (multi) {
17063
+ if (qCursorRef.current >= q.options.length) {
17064
+ setQDraft("");
17065
+ setQDraftCursor(0);
17066
+ setQEditing(true);
17067
+ } else if (multi) {
16478
17068
  const sel = qCheckedRef.current.size > 0 ? [...qCheckedRef.current].sort((a, b) => a - b).map((i) => q.options[i]?.label).filter(Boolean) : [q.options[qCursorRef.current]?.label].filter(Boolean);
16479
17069
  state.resolveQuestion({ selected: sel });
16480
17070
  } else {
@@ -16577,7 +17167,15 @@ ${listText}`);
16577
17167
  /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", width: cols, children: [
16578
17168
  menuActive ? null : /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", paddingX: 1, children: [
16579
17169
  dynamicRows.map((row) => /* @__PURE__ */ jsx18(RowView, { row, wrapW, streamTail, showThinking: state.showThinkingText }, row.id)),
16580
- state.busy ? /* @__PURE__ */ jsx18(Box16, { marginTop: 0.5, children: /* @__PURE__ */ jsx18(Text18, { color: THEME.coralBright, children: `${SPINNER_FRAMES[busyClock.frame]} ${S.generating} (${busyClock.sec}s \xB7 ${S.escInterrupt})` }) }) : null
17170
+ state.busy ? /* @__PURE__ */ jsx18(Box16, { marginTop: 0.5, children: /* @__PURE__ */ jsxs16(Text18, { color: THEME.coralBright, children: [
17171
+ `${SPINNER_FRAMES[busyClock.frame]} ${S.thinkVerbs[Math.floor(busyClock.sec / 3) % S.thinkVerbs.length]}\u2026 (${busyClock.sec}s \xB7 ${S.escInterrupt})`,
17172
+ state.runningAgents > 0 ? /* @__PURE__ */ jsxs16(Text18, { color: "green", children: [
17173
+ " \xB7 \u25CF ",
17174
+ state.runningAgents,
17175
+ " agent",
17176
+ state.runningAgents > 1 ? "s" : ""
17177
+ ] }) : null
17178
+ ] }) }) : null
16581
17179
  ] }),
16582
17180
  /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", paddingX: 1, flexShrink: 0, children: [
16583
17181
  state.pendingApproval ? /* @__PURE__ */ jsx18(ApprovalModal, { toolName: state.pendingApproval.toolName, detail: state.pendingApproval.detail, selectedIndex: selectIdx, wrapW }) : null,
@@ -16589,7 +17187,15 @@ ${listText}`);
16589
17187
  multiSelect: !!state.pendingQuestion.req.multiSelect,
16590
17188
  cursor: qCursor,
16591
17189
  checked: qChecked,
16592
- wrapW
17190
+ wrapW,
17191
+ editing: qEditing,
17192
+ draft: qDraft,
17193
+ draftCursor: qDraftCursor,
17194
+ onDraftChange: (v, c) => {
17195
+ setQDraft(v);
17196
+ setQDraftCursor(c);
17197
+ },
17198
+ onDraftSubmit: confirmQuestionDraft
16593
17199
  }
16594
17200
  ) : null,
16595
17201
  state.pendingPlan ? planEditing ? /* @__PURE__ */ jsx18(