deepseeker-code 1.0.46 → 1.0.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +4 -1
  2. package/dist/cli.mjs +672 -198
  3. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -423,6 +423,12 @@ var init_config = __esm({
423
423
  /** 后台工具(isSync:false)兜底超时(ms):超时强制收尾释放互斥锁,防 generator 卡死导致锁永久泄漏。
424
424
  * abort 仍是主取消通道,此值仅作最后防线;默认 30min 远超合理后台任务时长,正常任务不受影响。 */
425
425
  MAX_BACKGROUND_TOOL_MS: 30 * 60 * 1e3,
426
+ /** ★ run_command 运行时看门狗(自动转后台阈值 ms):前台流式超过此时长仍未退出 → 进程自动收编进
427
+ * 后台任务注册表并返回 task_id(get_background_output / stop_background_task 接管),不再阻塞主循环、
428
+ * 也不杀进程。与内部空闲看门狗(RUN_COMMAND_IDLE_TIMEOUT_MS,默认 60s)共用降级路径。
429
+ * env RUN_COMMAND_AUTO_BG_MS 覆盖。勿把 IDLE 调到高于 DEEP_SEEK_STREAM_IDLE_TIMEOUT_MS
430
+ * (空闲分支到期会产出降级消息喂流,不会饿死 collectToolResult 的 idle 熔断)。 */
431
+ runCommandAutoBgMs: Number(process.env.RUN_COMMAND_AUTO_BG_MS) || 12e4,
426
432
  userWorkspaceDir: (() => {
427
433
  let cwd = process.cwd().replace(/\\/g, "/");
428
434
  if (/^[a-z]:/i.test(cwd)) {
@@ -506,6 +512,7 @@ var init_config = __esm({
506
512
  parallelSafeTools: "DEEP_SEEK_PARALLEL_SAFE_TOOLS",
507
513
  workflowConcurrency: "DEEP_SEEK_WORKFLOW_CONCURRENCY",
508
514
  workflowMaxSteps: "DEEP_SEEK_WORKFLOW_MAX_STEPS",
515
+ runCommandAutoBgMs: "RUN_COMMAND_AUTO_BG_MS",
509
516
  planEnforcement: "DEEP_SEEK_PLAN_ENFORCEMENT",
510
517
  mcpExposeMode: "DEEP_SEEK_MCP_EXPOSE_MODE",
511
518
  transcriptEvents: "DEEP_SEEK_TRANSCRIPT_EVENTS",
@@ -973,11 +980,96 @@ var init_deepseek = __esm({
973
980
  }
974
981
  });
975
982
 
983
+ // ../core/src/session/contentParts.ts
984
+ var MAX_IMAGE_BYTES, msgText, imageUrlsOf, hasImagePart, isVisionEnabled, estimateImageTokens, toWireUserContent, replaceImageParts, collapseToText, degradeImagesForAux;
985
+ var init_contentParts = __esm({
986
+ "../core/src/session/contentParts.ts"() {
987
+ "use strict";
988
+ MAX_IMAGE_BYTES = 8 * 1024 * 1024;
989
+ msgText = (content) => {
990
+ if (typeof content === "string") return content;
991
+ if (Array.isArray(content)) {
992
+ return content.filter((p) => typeof p?.text === "string").map((p) => p.text).join(" ");
993
+ }
994
+ return "";
995
+ };
996
+ imageUrlsOf = (content) => {
997
+ if (!Array.isArray(content)) return [];
998
+ return content.filter((p) => p?.type === "image_url" && typeof p?.image_url?.url === "string").map((p) => p.image_url.url);
999
+ };
1000
+ hasImagePart = (content) => imageUrlsOf(content).length > 0;
1001
+ isVisionEnabled = () => {
1002
+ const v = process.env.DEEP_SEEK_VISION;
1003
+ return v === "1" || v === "true";
1004
+ };
1005
+ estimateImageTokens = () => {
1006
+ const n = Number(process.env.DEEP_SEEK_IMAGE_TOKENS);
1007
+ return Number.isFinite(n) && n >= 100 ? Math.floor(n) : 1500;
1008
+ };
1009
+ toWireUserContent = (text, attachments) => {
1010
+ if (!Array.isArray(attachments) || attachments.length === 0) return text;
1011
+ const accepted = [];
1012
+ const skipped = [];
1013
+ for (const a of attachments) {
1014
+ const name = typeof a?.name === "string" && a.name.trim() ? a.name : "image";
1015
+ const b64 = typeof a?.base64 === "string" ? a.base64 : "";
1016
+ if (!b64) {
1017
+ skipped.push(`\u{1F5BC} \u56FE\u7247 ${name} \u5DF2\u5FFD\u7565\uFF1A\u7A7A\u6570\u636E`);
1018
+ continue;
1019
+ }
1020
+ if (!/^image\//.test(a?.mime || "")) {
1021
+ skipped.push(`\u{1F5BC} \u56FE\u7247 ${name} \u5DF2\u5FFD\u7565\uFF1A\u975E\u56FE\u7247\u7C7B\u578B`);
1022
+ continue;
1023
+ }
1024
+ if (b64.length * 3 / 4 > MAX_IMAGE_BYTES) {
1025
+ skipped.push(`\u{1F5BC} \u56FE\u7247 ${name} \u5DF2\u5FFD\u7565\uFF1A\u8D85\u8FC7 ${Math.round(MAX_IMAGE_BYTES / 1024 / 1024)}MB \u4E0A\u9650`);
1026
+ continue;
1027
+ }
1028
+ accepted.push({ name, url: `data:${a.mime};base64,${b64}` });
1029
+ }
1030
+ const labels = accepted.map((p) => `\u{1F5BC} [\u56FE\u7247: ${p.name}]`);
1031
+ const footer = [...skipped, ...labels];
1032
+ const finalText = footer.length ? `${text}
1033
+
1034
+ ${footer.join("\n")}` : text;
1035
+ if (accepted.length === 0) return finalText;
1036
+ const parts = [
1037
+ { type: "text", text: finalText },
1038
+ ...accepted.map((p) => ({ type: "image_url", image_url: { url: p.url } }))
1039
+ ];
1040
+ return parts;
1041
+ };
1042
+ replaceImageParts = (m, note) => {
1043
+ if (!m || !Array.isArray(m.content)) return m;
1044
+ let touched = false;
1045
+ const parts = m.content.map((p) => {
1046
+ if (p?.type === "image_url") {
1047
+ touched = true;
1048
+ return { type: "text", text: note };
1049
+ }
1050
+ return p;
1051
+ });
1052
+ if (!touched) return m;
1053
+ return { ...m, content: parts };
1054
+ };
1055
+ collapseToText = (m, imageNote) => {
1056
+ if (!m || !Array.isArray(m.content)) return m;
1057
+ const text = msgText(m.content);
1058
+ if (imageNote && hasImagePart(m.content)) {
1059
+ return { ...m, content: text ? `${text} ${imageNote}` : imageNote };
1060
+ }
1061
+ return { ...m, content: text };
1062
+ };
1063
+ degradeImagesForAux = (m) => collapseToText(m, "[\u56FE\u7247\u5DF2\u5F52\u6863]");
1064
+ }
1065
+ });
1066
+
976
1067
  // ../core/src/llm/providers/replay/index.ts
977
1068
  var createReplayProvider;
978
1069
  var init_replay = __esm({
979
1070
  "../core/src/llm/providers/replay/index.ts"() {
980
1071
  "use strict";
1072
+ init_contentParts();
981
1073
  createReplayProvider = (script) => {
982
1074
  const turns = Array.isArray(script.turns) ? script.turns : [];
983
1075
  let cursor = 0;
@@ -1095,6 +1187,7 @@ var isCjkCodePoint, estimateTextTokens, estimateTokens, cleanMsg, groupUnits, sp
1095
1187
  var init_contextCore = __esm({
1096
1188
  "../core/src/session/contextCore.ts"() {
1097
1189
  "use strict";
1190
+ init_contentParts();
1098
1191
  isCjkCodePoint = (c) => c >= 19968 && c <= 40959 || // CJK 统一表意文字(基本区)
1099
1192
  c >= 13312 && c <= 19903 || // CJK 扩展 A
1100
1193
  c >= 12352 && c <= 12543 || // 日文假名(平假名 + 片假名)
@@ -1117,8 +1210,14 @@ var init_contextCore = __esm({
1117
1210
  estimateTokens = (messagesArr) => {
1118
1211
  return messagesArr.reduce((total, m) => {
1119
1212
  let pureText = "";
1213
+ let imageCount = 0;
1120
1214
  if (m.content) {
1121
- pureText += typeof m.content === "string" ? m.content : JSON.stringify(m.content);
1215
+ if (typeof m.content === "string") {
1216
+ pureText += m.content;
1217
+ } else if (Array.isArray(m.content)) {
1218
+ pureText += msgText(m.content);
1219
+ imageCount += imageUrlsOf(m.content).length;
1220
+ }
1122
1221
  }
1123
1222
  if (m.tool_calls && Array.isArray(m.tool_calls)) {
1124
1223
  for (const call of m.tool_calls) {
@@ -1134,12 +1233,12 @@ var init_contextCore = __esm({
1134
1233
  }
1135
1234
  }
1136
1235
  const isStructured = m.role === "tool" || Array.isArray(m.tool_calls) && m.tool_calls.length > 0;
1137
- const tokens = estimateTextTokens(pureText, isStructured ? 4 : 4.8) + 4;
1236
+ const tokens = estimateTextTokens(pureText, isStructured ? 4 : 4.8) + imageCount * estimateImageTokens() + 4;
1138
1237
  return total + (isNaN(tokens) ? 0 : tokens);
1139
1238
  }, 0);
1140
1239
  };
1141
1240
  cleanMsg = (m) => {
1142
- const { id, sessionId, ...rest } = m;
1241
+ const { id, sessionId, ts, ...rest } = m;
1143
1242
  return rest;
1144
1243
  };
1145
1244
  groupUnits = (messagesArr) => {
@@ -1242,6 +1341,7 @@ var init_store = __esm({
1242
1341
  "use strict";
1243
1342
  init_config();
1244
1343
  init_common();
1344
+ init_contentParts();
1245
1345
  getSessionsDirPath = (mainSessionId) => {
1246
1346
  assertSafeSessionId(mainSessionId);
1247
1347
  const fileName = getFileName(mainSessionId);
@@ -1406,8 +1506,8 @@ var init_store = __esm({
1406
1506
  }
1407
1507
  if (typeof msg?.dscEvent === "string") continue;
1408
1508
  messageCount++;
1409
- if (!preview && msg?.role === "user" && typeof msg.content === "string") {
1410
- const t = msg.content.replace(/\s+/g, " ").trim();
1509
+ if (!preview && msg?.role === "user" && msg.content != null) {
1510
+ const t = (msgText(msg.content) || "\uFF08\u8BE5\u6761\u6D88\u606F\u5E26\u56FE\u7247\u9644\u4EF6\uFF09").replace(/\s+/g, " ").trim();
1411
1511
  if (t.includes("\uFFFD")) {
1412
1512
  const cleaned = t.replace(/\uFFFD+/g, "").trim();
1413
1513
  preview = cleaned ? `\uFF08\u542B\u4E71\u7801\u7247\u6BB5\uFF09${cleaned}` : "\uFF08\u8BE5\u6761\u6D88\u606F\u5185\u5BB9\u4E3A\u4E71\u7801\uFF0C\u65E0\u6CD5\u6B63\u5E38\u663E\u793A\uFF09";
@@ -1515,7 +1615,7 @@ var init_transcript = __esm({
1515
1615
  try {
1516
1616
  await ensureSessionsDir(entry.sessionId);
1517
1617
  const { sessionId, ...rest } = entry;
1518
- const line = { id: createUUID(), ...rest };
1618
+ const line = { id: createUUID(), ts: (/* @__PURE__ */ new Date()).toISOString(), ...rest };
1519
1619
  const p = getTranscriptPath(sessionId);
1520
1620
  const payload = JSON.stringify(line) + "\n";
1521
1621
  if (payload.includes("\uFFFD")) {
@@ -1831,13 +1931,14 @@ function microcompactTextContent(raw) {
1831
1931
  function isAsyncGenerator(x) {
1832
1932
  return x != null && typeof x[Symbol.asyncIterator] === "function";
1833
1933
  }
1834
- var ANSI_ESCAPE, getFileAccessRoots, relativizeWorkspacePathsInText, truncateToolResult, truncateApprovalDetail, compactBatch, compactToLine, ensureSummarySlot, SUMMARY_SELF_COMPACT_THRESHOLD, ensureFitsWindow, collectToolResult;
1934
+ var ANSI_ESCAPE, getFileAccessRoots, relativizeWorkspacePathsInText, truncateToolResult, truncateApprovalDetail, compactBatch, ARCHIVE_INDEX_MAX_ENTRIES, ARCHIVE_INDEX_BATCH_CAP, extractArchiveEntities, parseSummarySlot, mergeSummarySlot, compactSlotNarrative, compactToLine, ensureSummarySlot, SUMMARY_SELF_COMPACT_THRESHOLD, ensureFitsWindow, collectToolResult;
1835
1935
  var init_truncate = __esm({
1836
1936
  "../core/src/agent/truncate.ts"() {
1837
1937
  "use strict";
1838
1938
  init_config();
1839
1939
  init_model();
1840
1940
  init_contextCore();
1941
+ init_contentParts();
1841
1942
  init_store();
1842
1943
  init_transcript();
1843
1944
  init_hooks();
@@ -1878,7 +1979,7 @@ var init_truncate = __esm({
1878
1979
  }
1879
1980
  return out;
1880
1981
  };
1881
- truncateToolResult = (result, maxChars) => {
1982
+ truncateToolResult = (result, maxChars, sidecarNote) => {
1882
1983
  const finalMaxChars = maxChars ?? appConfig.MAX_TOOL_RESULT_CHARS;
1883
1984
  const newResult = microcompactTextContent(result);
1884
1985
  if (!newResult || newResult.length <= finalMaxChars) return newResult;
@@ -1887,7 +1988,8 @@ var init_truncate = __esm({
1887
1988
  const tail = newResult.slice(-half);
1888
1989
  const totalLines = newResult.split("\n").length;
1889
1990
  const omitted = Math.max(0, totalLines - head.split("\n").length - tail.split("\n").length);
1890
- return [head, "", `\u2026(\u5DF2\u7701\u7565\u4E2D\u95F4\u7EA6 ${omitted} \u884C\uFF0C\u5171 ${totalLines} \u884C)\u2026`, "", tail].join("\n");
1991
+ const note = sidecarNote ? `\uFF1B${sidecarNote}` : "";
1992
+ return [head, "", `\u2026(\u5DF2\u7701\u7565\u4E2D\u95F4\u7EA6 ${omitted} \u884C\uFF0C\u5171 ${totalLines} \u884C${note})\u2026`, "", tail].join("\n");
1891
1993
  };
1892
1994
  truncateApprovalDetail = (detail, maxChars = 2e3) => {
1893
1995
  if (!detail || detail.length <= maxChars) return detail;
@@ -1903,12 +2005,74 @@ ${tail}`;
1903
2005
  };
1904
2006
  compactBatch = async (batch, signal) => {
1905
2007
  const resp = await chatWithModelWithSummary(
1906
- [...batch, { role: "user", content: "\u7528\u4E00\u884C\u8BDD\u6982\u62EC\u4EE5\u4E0A\u5BF9\u8BDD\u4E0E\u5DE5\u5177\u8C03\u7528\uFF1A\u4EFB\u52A1\u76EE\u6807\u3001\u5173\u952E\u51B3\u7B56\u3001\u52A8\u8FC7\u7684\u6587\u4EF6\u3001\u5F53\u524D\u8FDB\u5EA6\u3002\u4E0D\u8981\u8C03\u7528\u5DE5\u5177\u3002" }],
2008
+ // ★ 辅助模型是 text-only:送前把 image part 降级为占位符——base64 绝不能进摘要批(烧钱且无意义)
2009
+ [...batch.map(degradeImagesForAux), { role: "user", content: '\u7528\u4E00\u884C\u8BDD\u6982\u62EC\u4EE5\u4E0A\u5BF9\u8BDD\u4E0E\u5DE5\u5177\u8C03\u7528\uFF1A\u4EFB\u52A1\u76EE\u6807\u3001\u5173\u952E\u51B3\u7B56\u3001\u52A8\u8FC7\u7684\u6587\u4EF6\u3001\u5F53\u524D\u8FDB\u5EA6\u3002\u5FC5\u987B\u539F\u6837\u4FDD\u7559\u5173\u952E\u5B9E\u4F53\u540D\uFF08\u6587\u4EF6\u8DEF\u5F84\u3001\u51FD\u6570/\u7C7B\u540D\u3001\u62A5\u9519\u5173\u952E\u8BCD\uFF09\u4EE5\u4FBF\u540E\u7EED\u68C0\u7D22\u3002\u5BF9\u8BB0\u4E0D\u51C6\u7684\u7EC6\u8282\u4E0D\u8981\u81C6\u6D4B\uFF0C\u6807\u6CE8"(\u7EC6\u8282\u5DF2\u5F52\u6863)"\u5373\u53EF\u3002\u4E0D\u8981\u8C03\u7528\u5DE5\u5177\u3002' }],
1907
2010
  [],
1908
2011
  { signal }
1909
2012
  );
1910
2013
  return `- ${resp.choices[0].message.content || ""}`;
1911
2014
  };
2015
+ ARCHIVE_INDEX_MAX_ENTRIES = 120;
2016
+ ARCHIVE_INDEX_BATCH_CAP = 60;
2017
+ extractArchiveEntities = (batch) => {
2018
+ let text = "";
2019
+ for (const m of batch) {
2020
+ if (m.content) text += ` ${msgText(m.content)}`;
2021
+ const calls = m.tool_calls;
2022
+ if (Array.isArray(calls)) for (const c of calls) text += ` ${c?.function?.name ?? ""} ${c?.function?.arguments ?? ""}`;
2023
+ }
2024
+ const found = [];
2025
+ const seen = /* @__PURE__ */ new Set();
2026
+ const push = (raw) => {
2027
+ const s = raw.trim();
2028
+ if (!s || s.length < 3 || s.length > 60 || seen.has(s)) return;
2029
+ seen.add(s);
2030
+ found.push(s);
2031
+ };
2032
+ for (const m of text.match(/(?:[A-Za-z]:)?[\w.\-]+(?:[/\\][\w.\-]+)+\.[A-Za-z0-9]{1,6}/g) ?? []) push(m);
2033
+ for (const m of text.match(/`([^`\n]{2,48})`/g) ?? []) push(m.slice(1, -1));
2034
+ return found.slice(0, ARCHIVE_INDEX_BATCH_CAP);
2035
+ };
2036
+ parseSummarySlot = (content) => {
2037
+ const raw = content ?? "";
2038
+ const idxMark = raw.indexOf("\u27E6DSC:ARCHIVE-INDEX\u27E7");
2039
+ const notesMark = raw.indexOf("\u27E6DSC:ARCHIVE-NOTES\u27E7");
2040
+ if (idxMark === -1 || notesMark === -1 || notesMark < idxMark) return { index: [], notes: raw };
2041
+ const indexText = raw.slice(idxMark + "\u27E6DSC:ARCHIVE-INDEX\u27E7".length, notesMark);
2042
+ const entityText = indexText.includes("\n") ? indexText.slice(indexText.indexOf("\n") + 1) : "";
2043
+ const notes = raw.slice(notesMark + "\u27E6DSC:ARCHIVE-NOTES\u27E7".length).replace(/^\n+/, "");
2044
+ const index = entityText.split("|").map((s) => s.trim()).filter(Boolean);
2045
+ return { index, notes };
2046
+ };
2047
+ mergeSummarySlot = (oldContent, noteLine, newEntities) => {
2048
+ const { index, notes } = parseSummarySlot(oldContent);
2049
+ const merged = [];
2050
+ const seen = /* @__PURE__ */ new Set();
2051
+ for (const e of [...newEntities, ...index]) {
2052
+ if (!e || seen.has(e)) continue;
2053
+ seen.add(e);
2054
+ merged.push(e);
2055
+ if (merged.length >= ARCHIVE_INDEX_MAX_ENTRIES) break;
2056
+ }
2057
+ const newNotes = notes ? `${notes}
2058
+ ${noteLine}` : noteLine;
2059
+ return [
2060
+ "\u27E6DSC:ARCHIVE-INDEX\u27E7 \u7CBE\u786E\u7EC6\u8282\u53EF\u7528 recall \u5DE5\u5177\u68C0\u7D22\u672C\u4F1A\u8BDD\u5168\u91CF\u5386\u53F2\uFF1B\u4EE5\u4E0B\u4E3A\u5F52\u6863\u5B9E\u4F53\u7D22\u5F15\uFF08\u68C0\u7D22\u5173\u952E\u8BCD\u7EBF\u7D22\uFF09\uFF1A",
2061
+ merged.join(" | "),
2062
+ "\u27E6DSC:ARCHIVE-NOTES\u27E7",
2063
+ newNotes
2064
+ ].join("\n");
2065
+ };
2066
+ compactSlotNarrative = async (slotContent, modelWindow, signal) => {
2067
+ const { index } = parseSummarySlot(slotContent);
2068
+ const compactedNotes = await compactToLine([{ role: "system", content: slotContent }], modelWindow, signal);
2069
+ return [
2070
+ "\u27E6DSC:ARCHIVE-INDEX\u27E7 \u7CBE\u786E\u7EC6\u8282\u53EF\u7528 recall \u5DE5\u5177\u68C0\u7D22\u672C\u4F1A\u8BDD\u5168\u91CF\u5386\u53F2\uFF1B\u4EE5\u4E0B\u4E3A\u5F52\u6863\u5B9E\u4F53\u7D22\u5F15\uFF08\u68C0\u7D22\u5173\u952E\u8BCD\u7EBF\u7D22\uFF09\uFF1A",
2071
+ index.join(" | "),
2072
+ "\u27E6DSC:ARCHIVE-NOTES\u27E7",
2073
+ compactedNotes
2074
+ ].join("\n");
2075
+ };
1912
2076
  compactToLine = async (toCompact, _modelWindow, signal) => {
1913
2077
  const MAX_BATCH_TOKENS = 16e3;
1914
2078
  const units = groupUnits(toCompact);
@@ -1963,11 +2127,9 @@ ${tail}`;
1963
2127
  try {
1964
2128
  if (toCompact.length > 0) {
1965
2129
  const line = await compactToLine(toCompact, event.modelWindow, event.signal);
1966
- const old = summaryMsg?.content || "";
1967
- summaryMsg.content = old ? `${old}
1968
- ${line}` : line;
2130
+ summaryMsg.content = mergeSummarySlot(summaryMsg?.content || "", line, extractArchiveEntities(toCompact));
1969
2131
  if (estimateTokens([summaryMsg]) > SUMMARY_SELF_COMPACT_THRESHOLD) {
1970
- summaryMsg.content = await compactToLine([summaryMsg], event.modelWindow, event.signal);
2132
+ summaryMsg.content = await compactSlotNarrative(summaryMsg.content, event.modelWindow, event.signal);
1971
2133
  }
1972
2134
  const endTime = performance.now();
1973
2135
  event.messageArr.length = 0;
@@ -2004,7 +2166,7 @@ ${line}` : line;
2004
2166
  keep--;
2005
2167
  continue;
2006
2168
  } else if (summaryMsg?.content) {
2007
- summaryMsg.content = await compactToLine([summaryMsg], event.modelWindow, event.signal);
2169
+ summaryMsg.content = await compactSlotNarrative(summaryMsg.content, event.modelWindow, event.signal);
2008
2170
  event.messageArr.length = 0;
2009
2171
  event.messageArr.push(systemMsg, summaryMsg, ...keepRecent);
2010
2172
  const store = await getRollingState(event.sessionId);
@@ -2642,7 +2804,7 @@ async function killBackgroundTasksUnder(dirPath) {
2642
2804
  }
2643
2805
  return killed;
2644
2806
  }
2645
- var _winShellCache, resolveWinShell, MAX_BUFFER_CHARS, registry, backgroundTools, killAllBackgroundTasks;
2807
+ var _winShellCache, resolveWinShell, MAX_BUFFER_CHARS, registry, createBgTask, attachTaskLifecycle, adoptRunningProcess, backgroundTools, killAllBackgroundTasks;
2646
2808
  var init_background = __esm({
2647
2809
  "../core/src/tool/registry/background.ts"() {
2648
2810
  "use strict";
@@ -2683,12 +2845,110 @@ var init_background = __esm({
2683
2845
  };
2684
2846
  MAX_BUFFER_CHARS = 1e5;
2685
2847
  registry = /* @__PURE__ */ new Map();
2848
+ createBgTask = (proc, command, cwd, sessionId) => {
2849
+ const task = {
2850
+ taskId: createUUID(),
2851
+ command,
2852
+ cwd,
2853
+ sessionId: sessionId ?? "",
2854
+ proc,
2855
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
2856
+ status: "running",
2857
+ exitCode: null,
2858
+ outputBuffer: ""
2859
+ };
2860
+ registry.set(task.taskId, task);
2861
+ return task;
2862
+ };
2863
+ attachTaskLifecycle = (task, proc, opts = {}) => {
2864
+ return new Promise((resolve) => {
2865
+ let finished = false;
2866
+ const finish = (apply) => {
2867
+ if (finished) return;
2868
+ finished = true;
2869
+ apply();
2870
+ setTimeout(() => registry.delete(task.taskId), 6e4);
2871
+ opts.signal?.removeEventListener("abort", onAbort);
2872
+ try {
2873
+ proc?.stdout?.removeAllListeners?.();
2874
+ } catch {
2875
+ }
2876
+ try {
2877
+ proc?.stderr?.removeAllListeners?.();
2878
+ } catch {
2879
+ }
2880
+ try {
2881
+ proc?.removeAllListeners?.();
2882
+ } catch {
2883
+ }
2884
+ resolve();
2885
+ };
2886
+ const onAbort = () => {
2887
+ killTree(proc).finally(() => {
2888
+ finish(() => {
2889
+ if (task.status === "running") task.status = "killed";
2890
+ task.exitCode = task.exitCode ?? -1;
2891
+ appendOutput(task, `
2892
+ [\u7528\u6237\u4E2D\u6B62\uFF0C\u8FDB\u7A0B\u6811\u5DF2\u7EC8\u6B62]`);
2893
+ });
2894
+ });
2895
+ };
2896
+ opts.signal?.addEventListener("abort", onAbort, { once: true });
2897
+ proc.on("close", (code) => {
2898
+ finish(() => {
2899
+ if (opts.codec) {
2900
+ try {
2901
+ appendOutput(task, opts.codec.flush());
2902
+ } catch {
2903
+ }
2904
+ }
2905
+ task.status = task.status === "killed" ? "killed" : "exited";
2906
+ task.exitCode ??= code ?? 0;
2907
+ appendOutput(task, `
2908
+ [\u8FDB\u7A0B\u7ED3\u675F exit=${code}]`);
2909
+ });
2910
+ });
2911
+ proc.on("error", (e) => {
2912
+ finish(() => {
2913
+ if (task.status === "running") task.status = "exited";
2914
+ task.exitCode = task.exitCode ?? -1;
2915
+ appendOutput(task, `
2916
+ [spawn error: ${e.message}]`);
2917
+ });
2918
+ });
2919
+ });
2920
+ };
2921
+ adoptRunningProcess = (proc, meta, seedOutput, codec) => {
2922
+ const task = createBgTask(proc, meta.command, meta.cwd, meta.sessionId);
2923
+ if (seedOutput) appendOutput(task, seedOutput);
2924
+ try {
2925
+ proc?.stdout?.removeAllListeners?.("data");
2926
+ } catch {
2927
+ }
2928
+ try {
2929
+ proc?.stderr?.removeAllListeners?.("data");
2930
+ } catch {
2931
+ }
2932
+ try {
2933
+ proc?.removeAllListeners?.("close");
2934
+ } catch {
2935
+ }
2936
+ try {
2937
+ proc?.removeAllListeners?.("error");
2938
+ } catch {
2939
+ }
2940
+ const onData = (d) => appendOutput(task, codec.decode(d));
2941
+ proc.stdout?.on("data", onData);
2942
+ proc.stderr?.on("data", onData);
2943
+ void attachTaskLifecycle(task, proc, { signal: meta.signal, codec });
2944
+ return task.taskId;
2945
+ };
2686
2946
  backgroundTools = [
2687
2947
  {
2688
2948
  type: "function",
2689
2949
  function: {
2690
2950
  name: "run_in_background",
2691
- description: "\u5728\u540E\u53F0\u542F\u52A8\u4E00\u4E2A\u957F\u8FDE\u63A5/\u5E38\u9A7B shell \u547D\u4EE4\uFF08\u5982 dev server\u3001watch\u3001tail -f\uFF09\uFF0C\u7ACB\u5373\u8FD4\u56DE task_id\uFF0C\u4E0D\u963B\u585E\u540E\u7EED\u63A8\u7406\u3002\u5E38\u7528\u4E8E\u5148\u8D77\u670D\u52A1\u518D\u7EE7\u7EED\u5E72\u522B\u7684\u6D3B\u3002\u7528 get_background_output \u67E5\u65E5\u5FD7\u4E0E\u72B6\u6001\uFF0Cstop_background_task \u7EC8\u6B62\u3002",
2951
+ description: "\u5728\u540E\u53F0\u542F\u52A8\u4E00\u4E2A\u957F\u65F6 shell \u547D\u4EE4\u5E76\u7ACB\u5373\u8FD4\u56DE task_id\uFF0C\u4E0D\u963B\u585E\u540E\u7EED\u63A8\u7406\u3002\u9002\u7528\u4E24\u7C7B\u573A\u666F\uFF1A\u2460 \u957F\u8FDE\u63A5/\u5E38\u9A7B\u8FDB\u7A0B\uFF08dev server\u3001watch\u3001tail -f\uFF09\u2014\u2014\u5148\u8D77\u670D\u52A1\u518D\u7EE7\u7EED\u5E72\u522B\u7684\u6D3B\uFF1B\u2461 \u9884\u8BA1\u8FD0\u884C\u6570\u5206\u949F\u4EE5\u4E0A\u3001\u65E0\u9700\u5B9E\u65F6\u76EF\u770B\u7684\u6709\u9650\u957F\u4EFB\u52A1\uFF08\u5168\u91CF\u6D4B\u8BD5 / \u5927\u6784\u5EFA / \u5927\u4F53\u79EF\u5B89\u88C5\u2014\u2014\u8DD1\u5B8C\u7528 get_background_output \u5E26 wait_seconds \u7B49\u7ED3\u679C\uFF0C\u4E0D\u8981\u7528\u524D\u53F0 run_command \u5E72\u7B49\uFF09\u3002\u7528 get_background_output \u67E5\u65E5\u5FD7\u4E0E\u72B6\u6001\uFF0Cstop_background_task \u7EC8\u6B62\u3002",
2692
2952
  parameters: {
2693
2953
  type: "object",
2694
2954
  properties: {
@@ -2738,75 +2998,25 @@ var init_background = __esm({
2738
2998
  yield `\u274C [\u540E\u53F0\u542F\u52A8\u5931\u8D25]\uFF1A${e.message}`;
2739
2999
  return;
2740
3000
  }
2741
- const taskId = createUUID();
2742
- const task = {
2743
- taskId,
2744
- command: args.command,
2745
- cwd,
2746
- sessionId: ctx?.sessionId ?? "",
2747
- proc,
2748
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
2749
- status: "running",
2750
- exitCode: null,
2751
- outputBuffer: ""
3001
+ const codec = {
3002
+ decode: decodeChunk,
3003
+ // flush:close 时收尾两个 stream 解码器的尾字节(GBK/UTF-8 残留,不 flush 丢最后一个字符)
3004
+ flush: () => {
3005
+ let s = "";
3006
+ if (decoder) s += decoder.decode();
3007
+ s += pendingUtf8.decode();
3008
+ return s;
3009
+ }
2752
3010
  };
2753
- registry.set(taskId, task);
2754
- proc.stdout?.on("data", (d) => appendOutput(task, decodeChunk(d)));
2755
- proc.stderr?.on("data", (d) => appendOutput(task, decodeChunk(d)));
3011
+ const task = createBgTask(proc, args.command, cwd, ctx?.sessionId);
3012
+ const taskId = task.taskId;
3013
+ proc.stdout?.on("data", (d) => appendOutput(task, codec.decode(d)));
3014
+ proc.stderr?.on("data", (d) => appendOutput(task, codec.decode(d)));
2756
3015
  yield `\u2705 [\u540E\u53F0\u4EFB\u52A1\u5DF2\u542F\u52A8]
2757
3016
  task_id: ${taskId}
2758
3017
  \u547D\u4EE4: ${args.command}
2759
3018
  \u7528 get_background_output(task_id="${taskId}") \u67E5\u65E5\u5FD7\uFF0Cstop_background_task(task_id="${taskId}") \u7EC8\u6B62\u3002`;
2760
- await new Promise((resolve) => {
2761
- let finished = false;
2762
- const finish = (apply) => {
2763
- if (finished) return;
2764
- finished = true;
2765
- apply();
2766
- setTimeout(() => registry.delete(taskId), 6e4);
2767
- ctx?.abortSignal?.removeEventListener("abort", onAbort);
2768
- try {
2769
- proc?.stdout?.removeAllListeners?.();
2770
- } catch {
2771
- }
2772
- try {
2773
- proc?.stderr?.removeAllListeners?.();
2774
- } catch {
2775
- }
2776
- try {
2777
- proc?.removeAllListeners?.();
2778
- } catch {
2779
- }
2780
- resolve();
2781
- };
2782
- const onAbort = () => {
2783
- killTree(proc).finally(() => {
2784
- finish(() => {
2785
- if (task.status === "running") task.status = "killed";
2786
- task.exitCode = task.exitCode ?? -1;
2787
- appendOutput(task, `
2788
- [\u7528\u6237\u4E2D\u6B62\uFF0C\u8FDB\u7A0B\u6811\u5DF2\u7EC8\u6B62]`);
2789
- });
2790
- });
2791
- };
2792
- ctx?.abortSignal?.addEventListener("abort", onAbort, { once: true });
2793
- proc.on("close", (code) => {
2794
- finish(() => {
2795
- task.status = task.status === "killed" ? "killed" : "exited";
2796
- task.exitCode ??= code ?? 0;
2797
- appendOutput(task, `
2798
- [\u8FDB\u7A0B\u7ED3\u675F exit=${code}]`);
2799
- });
2800
- });
2801
- proc.on("error", (e) => {
2802
- finish(() => {
2803
- if (task.status === "running") task.status = "exited";
2804
- task.exitCode = task.exitCode ?? -1;
2805
- appendOutput(task, `
2806
- [spawn error: ${e.message}]`);
2807
- });
2808
- });
2809
- });
3019
+ await attachTaskLifecycle(task, proc, { signal: ctx?.abortSignal, codec });
2810
3020
  }
2811
3021
  }
2812
3022
  },
@@ -2818,7 +3028,7 @@ task_id: ${taskId}
2818
3028
  parameters: {
2819
3029
  type: "object",
2820
3030
  properties: {
2821
- task_id: { type: "string", description: "run_in_background \u8FD4\u56DE\u7684 task_id" },
3031
+ task_id: { type: "string", description: "\u540E\u53F0\u4EFB\u52A1\u7684 task_id\uFF08run_in_background \u8FD4\u56DE\uFF0C\u6216 run_command \u8D85\u65F6\u81EA\u52A8\u8F6C\u540E\u53F0\u8FD4\u56DE\uFF09" },
2822
3032
  tail_lines: { type: "number", description: "\u53EA\u8FD4\u56DE\u6700\u540E N \u884C\u65E5\u5FD7\uFF08\u9ED8\u8BA4 50\uFF0C\u907F\u514D\u4E00\u6B21\u704C\u5165\u8FC7\u591A\uFF09" },
2823
3033
  wait_seconds: { type: "number", description: "\u963B\u585E\u7B49\u5F85\u4EFB\u52A1\u9000\u51FA\uFF08\u6216\u5230\u8FBE\u6B64\u65F6\u957F\uFF09\u540E\u518D\u8FD4\u56DE\uFF0C\u4E0A\u9650 120 \u79D2\uFF1B\u4EFB\u52A1\u5DF2\u5728\u8DD1\u4E14\u9700\u8981\u7B49\u7ED3\u679C\u65F6\u4F7F\u7528\uFF0C\u4E0D\u9700\u8981\u7B49\u5C31\u7701\u7565" }
2824
3034
  },
@@ -2859,7 +3069,7 @@ task_id: ${taskId}
2859
3069
  parameters: {
2860
3070
  type: "object",
2861
3071
  properties: {
2862
- task_id: { type: "string", description: "run_in_background \u8FD4\u56DE\u7684 task_id" }
3072
+ task_id: { type: "string", description: "\u540E\u53F0\u4EFB\u52A1\u7684 task_id\uFF08run_in_background \u8FD4\u56DE\uFF0C\u6216 run_command \u8D85\u65F6\u81EA\u52A8\u8F6C\u540E\u53F0\u8FD4\u56DE\uFF09" }
2863
3073
  },
2864
3074
  required: ["task_id"]
2865
3075
  },
@@ -4016,6 +4226,8 @@ var init_planMode = __esm({
4016
4226
  "get_diagnostics",
4017
4227
  "goto_definition",
4018
4228
  // ★ P0-A 补漏:LSP 导航/诊断恰是调研阶段最需要的(原白名单漂移漏登记,schema 档与 runtime 档共用此表)
4229
+ "recall",
4230
+ // 只读检索本会话历史(长调研中早前轮次可能已被压缩归档,调研阶段恰是 recall 主场景)
4019
4231
  "web_fetch",
4020
4232
  "web_search"
4021
4233
  // 只读研究类(虽为 DANGER 但不写本地状态;仍走各自审批)
@@ -5908,6 +6120,7 @@ var init_command = __esm({
5908
6120
  init_type();
5909
6121
  init_guard();
5910
6122
  init_background();
6123
+ init_config();
5911
6124
  EXIT_SENTINEL = (code) => `
5912
6125
  \u27E6DSC_EXIT:${code}\u27E7`;
5913
6126
  EXIT_SENTINEL_RE = /⟦DSC_EXIT:(-?\d+)⟧/g;
@@ -5917,7 +6130,7 @@ var init_command = __esm({
5917
6130
  type: "function",
5918
6131
  function: {
5919
6132
  name: "run_command",
5920
- description: "\u5728\u5DE5\u4F5C\u533A\u6267\u884C shell \u547D\u4EE4\uFF08\u8DD1\u6D4B\u8BD5 / \u6784\u5EFA / git \u7B49\uFF09\uFF0C\u6D41\u5F0F\u8FD4\u56DE stdout/stderr \u5E76\u9644\u5E26\u9000\u51FA\u7801\u3002\u5C5E\u4E8E\u9AD8\u5371\u64CD\u4F5C\uFF0C\u6BCF\u6B21\u6267\u884C\u90FD\u9700\u8981\u7528\u6237\u5BA1\u6279\u3002",
6133
+ description: "\u5728\u5DE5\u4F5C\u533A\u6267\u884C shell \u547D\u4EE4\uFF08\u77ED\u5E73\u5FEB\u7684\u6D4B\u8BD5 / \u6784\u5EFA / git \u7B49\uFF09\uFF0C\u6D41\u5F0F\u8FD4\u56DE stdout/stderr \u5E76\u9644\u5E26\u9000\u51FA\u7801\u3002\u5C5E\u4E8E\u9AD8\u5371\u64CD\u4F5C\uFF0C\u6BCF\u6B21\u6267\u884C\u90FD\u9700\u8981\u7528\u6237\u5BA1\u6279\u3002\u9884\u8BA1\u8FD0\u884C\u6570\u5206\u949F\u4EE5\u4E0A\u7684\u957F\u4EFB\u52A1\uFF08\u5168\u91CF\u6D4B\u8BD5 / \u5927\u6784\u5EFA / \u5927\u4F53\u79EF\u5B89\u88C5\uFF09\u8BF7\u6539\u7528 run_in_background \u540E\u53F0\u6267\u884C\uFF1B\u82E5\u672C\u5DE5\u5177\u6267\u884C\u8FC7\u4E45\u4ECD\u672A\u7ED3\u675F\uFF0C\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u628A\u8FDB\u7A0B\u8F6C\u5165\u540E\u53F0\u5E76\u8FD4\u56DE task_id\uFF08\u7528 get_background_output \u67E5\u7ED3\u679C\u3001stop_background_task \u7EC8\u6B62\uFF09\u3002",
5921
6134
  parameters: {
5922
6135
  type: "object",
5923
6136
  properties: {
@@ -5962,6 +6175,10 @@ ${tail}`,
5962
6175
  const cwd = args.cwd ? resolveSafePath(args.cwd) : getActiveWorkspaceRoot();
5963
6176
  const maxChars = RUN_COMMAND_MAX_CHARS;
5964
6177
  let totalYieldedChars = 0;
6178
+ let fullOutput = "";
6179
+ let degraded = false;
6180
+ let degradeDue = false;
6181
+ let degradeReason = null;
5965
6182
  const isWin = process.platform === "win32";
5966
6183
  if (ctx?.abortSignal?.aborted) {
5967
6184
  yield `\u274C [\u5DF2\u4E2D\u6B62]\uFF1A\u547D\u4EE4 [${args.command}] \u672A\u6267\u884C\uFF08\u7528\u6237\u5DF2\u4E2D\u65AD\uFF09\u3002`;
@@ -6016,6 +6233,15 @@ ${tail}`,
6016
6233
  }
6017
6234
  return (decoder ?? pendingUtf8).decode(buf, { stream: true });
6018
6235
  };
6236
+ const codec = {
6237
+ decode: decodeChunk,
6238
+ flush: () => {
6239
+ let s = "";
6240
+ if (decoder) s += decoder.decode();
6241
+ s += pendingUtf8.decode();
6242
+ return s;
6243
+ }
6244
+ };
6019
6245
  proc.stdout?.on("data", (d) => {
6020
6246
  queue.push(decodeChunk(d));
6021
6247
  notifyNewData();
@@ -6042,11 +6268,22 @@ ${tail}`,
6042
6268
  void killTree(proc);
6043
6269
  };
6044
6270
  ctx?.abortSignal?.addEventListener("abort", onAbort);
6271
+ const IDLE_TIMEOUT_MS = Number(process.env.RUN_COMMAND_IDLE_TIMEOUT_MS) || 6e4;
6272
+ const autoBgMs = Math.max(1, appConfig.runCommandAutoBgMs);
6273
+ const runtimeTimer = setTimeout(() => {
6274
+ degradeDue = true;
6275
+ notifyNewData();
6276
+ }, autoBgMs);
6277
+ runtimeTimer.unref?.();
6045
6278
  let overLimit = false;
6046
6279
  try {
6047
- while ((!settled || queue.length > 0) && !overLimit) {
6280
+ while ((!settled || queue.length > 0) && !overLimit && !degraded) {
6281
+ if (degradeDue && !settled) {
6282
+ degraded = true;
6283
+ degradeReason = "runtime";
6284
+ break;
6285
+ }
6048
6286
  if (queue.length === 0) {
6049
- const IDLE_TIMEOUT_MS = 6e4;
6050
6287
  let timedOut = false;
6051
6288
  await new Promise((r) => {
6052
6289
  resolveWaiter = r;
@@ -6055,20 +6292,26 @@ ${tail}`,
6055
6292
  r();
6056
6293
  }, IDLE_TIMEOUT_MS);
6057
6294
  });
6295
+ if (degradeDue && !settled) {
6296
+ degraded = true;
6297
+ degradeReason = "runtime";
6298
+ break;
6299
+ }
6058
6300
  if (timedOut && !settled && queue.length === 0) {
6059
- yield `
6060
-
6061
- \u23F1\uFE0F [\u770B\u95E8\u72D7]\uFF1A\u547D\u4EE4\u8FDE\u7EED ${IDLE_TIMEOUT_MS / 1e3}s \u65E0\u8F93\u51FA\u4E14\u672A\u9000\u51FA\uFF0C\u5224\u5B9A\u4E3A\u5E38\u9A7B\u8FDB\u7A0B\uFF08\u5982 dev server / tail -f\uFF09\u3002\u5DF2\u4E3B\u52A8\u7EC8\u6B62\u4EE5\u91CA\u653E\u4E3B\u5FAA\u73AF\u2014\u2014\u5E38\u9A7B\u547D\u4EE4\u8BF7\u6539\u7528 run_in_background\u3002
6062
- `;
6063
- onAbort();
6064
- settled = true;
6065
- overLimit = true;
6301
+ degraded = true;
6302
+ degradeReason = "idle";
6066
6303
  break;
6067
6304
  }
6068
6305
  }
6069
6306
  while (queue.length > 0) {
6307
+ if (degradeDue && !settled) {
6308
+ degraded = true;
6309
+ degradeReason = "runtime";
6310
+ break;
6311
+ }
6070
6312
  const chunk = queue.shift();
6071
6313
  totalYieldedChars += chunk.length;
6314
+ fullOutput += chunk;
6072
6315
  if (totalYieldedChars > maxChars) {
6073
6316
  yield `
6074
6317
 
@@ -6082,14 +6325,30 @@ ${tail}`,
6082
6325
  yield chunk;
6083
6326
  }
6084
6327
  }
6085
- if (overLimit) {
6328
+ if (degraded) {
6329
+ const taskId = adoptRunningProcess(
6330
+ proc,
6331
+ { command: args.command, cwd, sessionId: ctx?.sessionId, signal: ctx?.abortSignal },
6332
+ fullOutput + queue.join(""),
6333
+ codec
6334
+ );
6335
+ const why = degradeReason === "idle" ? `\u8FDE\u7EED ${Math.round(IDLE_TIMEOUT_MS / 1e3)}s \u65E0\u8F93\u51FA\u4E14\u672A\u9000\u51FA` : `\u5DF2\u6301\u7EED\u8FD0\u884C\u8D85\u8FC7 ${Math.round(autoBgMs / 1e3)}s`;
6336
+ yield `
6337
+
6338
+ \u23F3 [\u81EA\u52A8\u8F6C\u540E\u53F0]\uFF1A\u547D\u4EE4 [${args.command}] ${why}\uFF0C\u5DF2\u81EA\u52A8\u8F6C\u4E3A\u540E\u53F0\u4EFB\u52A1\uFF08\u524D\u53F0\u6D41\u5F0F\u7ED3\u675F\uFF0C\u672A\u6D88\u8D39\u7684\u5269\u4F59\u8F93\u51FA\u5DF2\u5E76\u5165\u540E\u53F0\u7F13\u51B2\uFF09\u3002
6339
+ task_id: ${taskId}
6340
+ \u26A0\uFE0F \u8FDB\u7A0B\u4ECD\u5728\u8FD0\u884C\uFF0C\u9000\u51FA\u7801\u672A\u77E5\uFF08\u4E0D\u5224\u5B9A\u6210\u529F/\u5931\u8D25\uFF09\u2014\u2014\u7528 get_background_output(task_id="${taskId}", wait_seconds=\u2026) \u7B49\u5F85/\u67E5\u770B\u7ED3\u679C\uFF1B\u82E5\u786E\u8BA4\u65E0\u9700\u7EE7\u7EED\uFF0C\u7528 stop_background_task(task_id="${taskId}") \u7EC8\u6B62\u3002
6341
+ `;
6342
+ } else if (overLimit) {
6086
6343
  yield EXIT_SENTINEL(-1);
6087
6344
  } else if (exitCode !== null) {
6088
6345
  yield EXIT_SENTINEL(exitCode);
6089
6346
  }
6090
6347
  } finally {
6091
6348
  if (idleTimer) clearTimeout(idleTimer);
6349
+ clearTimeout(runtimeTimer);
6092
6350
  ctx?.abortSignal?.removeEventListener("abort", onAbort);
6351
+ if (!settled && !degraded) void killTree(proc);
6093
6352
  }
6094
6353
  }
6095
6354
  }
@@ -8824,6 +9083,182 @@ ${lines.join("\n")}`;
8824
9083
  }
8825
9084
  });
8826
9085
 
9086
+ // ../core/src/tool/registry/recall.ts
9087
+ import fs26 from "fs/promises";
9088
+ import path29 from "path";
9089
+ var escapeRegExp2, lineText, primaryPathArg, annotateStaleness, snippetAround, recallTools;
9090
+ var init_recall = __esm({
9091
+ "../core/src/tool/registry/recall.ts"() {
9092
+ "use strict";
9093
+ init_type();
9094
+ init_guard();
9095
+ init_transcript();
9096
+ init_store();
9097
+ escapeRegExp2 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9098
+ lineText = (l) => {
9099
+ let t = "";
9100
+ if (typeof l.content === "string") t += l.content;
9101
+ else if (Array.isArray(l.content)) t += l.content.map((p) => typeof p?.text === "string" ? p.text : "").join(" ");
9102
+ if (Array.isArray(l.tool_calls)) {
9103
+ for (const c of l.tool_calls) {
9104
+ if (c?.function?.name === "recall") continue;
9105
+ t += ` ${c?.function?.name ?? ""} ${c?.function?.arguments ?? ""}`;
9106
+ }
9107
+ }
9108
+ return t;
9109
+ };
9110
+ primaryPathArg = (args) => {
9111
+ for (const k of ["path", "file_path", "source"]) {
9112
+ if (typeof args?.[k] === "string" && args[k]) return args[k];
9113
+ }
9114
+ return void 0;
9115
+ };
9116
+ annotateStaleness = async (args, observedAt) => {
9117
+ const p = primaryPathArg(args);
9118
+ if (!p) return "\u5FEB\u7167\uFF08\u65E0\u8DEF\u5F84\u5B9E\u4F53\uFF0C\u672A\u7ECF\u91CD\u9A8C\u2014\u2014\u636E\u4EE5\u884C\u52A8\u524D\u8BF7\u91CD\u8DD1\u9A8C\u8BC1\uFF09";
9119
+ const abs = path29.isAbsolute(p) ? p : path29.resolve(getActiveWorkspaceRoot(), p);
9120
+ try {
9121
+ const st = await fs26.stat(abs);
9122
+ if (observedAt && st.mtime.toISOString() > observedAt) {
9123
+ return `\u26A0\uFE0F \u89C2\u5BDF\u540E\u8BE5\u6587\u4EF6\u5DF2\u88AB\u4FEE\u6539\uFF08${p}\uFF09\u2014\u2014\u5FC5\u987B\u91CD\u65B0 read_file \u540E\u624D\u80FD\u636E\u4EE5\u4FEE\u6539`;
9124
+ }
9125
+ return `\u2713 \u6587\u4EF6\u672A\u53D8\uFF08${p}\uFF09`;
9126
+ } catch {
9127
+ return `\u26A0\uFE0F \u8BE5\u6587\u4EF6\u5DF2\u4E0D\u5B58\u5728\uFF08${p}\uFF09`;
9128
+ }
9129
+ };
9130
+ snippetAround = (text, re, maxChars) => {
9131
+ const m = re.exec(text);
9132
+ const start = Math.max(0, (m?.index ?? 0) - 120);
9133
+ const slice = text.slice(start, start + maxChars);
9134
+ const headMark = start > 0 ? "\u2026" : "";
9135
+ const tailMark = start + maxChars < text.length ? "\u2026\uFF08\u7247\u6BB5\u622A\u65AD\uFF0C\u5B8C\u6574\u5185\u5BB9\u53EF\u7528 with_full\uFF09" : "";
9136
+ return `${headMark}${slice}${tailMark}`;
9137
+ };
9138
+ recallTools = [
9139
+ {
9140
+ type: "function",
9141
+ function: {
9142
+ name: "recall",
9143
+ description: "\u68C0\u7D22\u3010\u672C\u4F1A\u8BDD\u3011\u5168\u91CF\u5386\u53F2\u8F6C\u5F55\uFF08transcript\uFF09\uFF0C\u5305\u62EC\u5DF2\u88AB\u4E0A\u4E0B\u6587\u538B\u7F29\u5F52\u6863\u7684\u539F\u59CB\u6D88\u606F\u4E0E\u5DE5\u5177\u7ED3\u679C\u3002\u4F7F\u7528\u65F6\u673A\uFF1A\u6EDA\u52A8\u6458\u8981\uFF08\u27E6DSC:ARCHIVE-INDEX\u27E7/\u27E6DSC:ARCHIVE-NOTES\u27E7\uFF09\u4E0D\u8DB3\u4EE5\u56DE\u7B54\u7EC6\u8282\u65F6\u2014\u2014\u5982\u5F52\u6863\u524D\u7684\u6587\u4EF6\u5185\u5BB9\u3001\u62A5\u9519\u539F\u6587\u3001\u7528\u6237\u65E9\u524D\u7684\u8981\u6C42\u2014\u2014\u5148\u7528\u672C\u5DE5\u5177\u68C0\u7D22\uFF0C\u4E0D\u8981\u51ED\u6458\u8981\u81C6\u6D4B\uFF0C\u4E5F\u4E0D\u8981\u91CD\u8DD1\u5DE5\u5177\u3002\u2605 \u9996\u67E5\u7B56\u7565\uFF1Aquery \u5FC5\u987B\u7528\u5177\u4F53\u5B9E\u4F53\u8BCD\u2014\u2014\u62A5\u9519\u5173\u952E\u8BCD\uFF08\u5982 TypeError\uFF09\u3001\u6587\u4EF6\u8DEF\u5F84\u3001\u51FD\u6570/\u7B26\u53F7\u540D\uFF08\u6458\u8981\u5B9E\u4F53\u7D22\u5F15\u91CC\u51FA\u73B0\u8FC7\u4EC0\u4E48\u5C31\u7528\u4EC0\u4E48\uFF09\uFF1B\u4E0D\u8981\u7528\u5DE5\u5177\u540D\uFF08read_file \u7B49\uFF09\u6216\u6CDB\u8BCD\uFF08\u4EE3\u7801/\u6587\u4EF6/\u95EE\u9898\uFF09\u4F5C query\uFF0C\u4F1A\u547D\u4E2D\u5927\u91CF\u65E0\u5173\u5DE5\u5177\u884C\u3002\u4E00\u6B21\u6CA1\u4E2D\u5C31\u6362\u66F4\u5177\u4F53\u7684\u8BCD\u6216\u7528 is_regex \u4EE5 | \u5408\u5E76\u591A\u4E2A\u5019\u9009\u8BCD\uFF0C\u901A\u5E38 1-2 \u6B21\u5185\u547D\u4E2D\u3002\u8FD4\u56DE\u6309\u65F6\u95F4\u65E7\u2192\u65B0\uFF08\u4F18\u5148\u5F52\u6863\u533A\uFF09\uFF1B\u6587\u4EF6\u7C7B\u5DE5\u5177\u7ED3\u679C\u9644\u5B9E\u65F6\u6821\u9A8C\uFF1A\u26A0\uFE0F \u5DF2\u4FEE\u6539/\u5DF2\u4E0D\u5B58\u5728 \u7684\u5FC5\u987B\u91CD\u65B0 read_file \u9A8C\u8BC1\u540E\u624D\u80FD\u636E\u4EE5\u4FEE\u6539\uFF0C\u2713 \u672A\u53D8 \u65B9\u53EF\u76F4\u63A5\u5F15\u7528\u3002\u7ED3\u679C\u66FE\u88AB\u622A\u65AD\u7684\u4F1A\u6807\u6CE8 with_full \u53EF\u53D6\u5B58\u6863\u5168\u6587\u3002\u6458\u8981\u5DF2\u591F\u7528\u65F6\u52FF\u8C03\u7528\uFF08\u7701 token\uFF09\u3002",
9144
+ parameters: {
9145
+ type: "object",
9146
+ properties: {
9147
+ query: { type: "string", description: "\u68C0\u7D22\u5173\u952E\u8BCD\u3002\u9ED8\u8BA4\u5B57\u9762\u91CF\u5339\u914D\uFF1Bis_regex=true \u65F6\u6309\u6B63\u5219\u89E3\u6790\uFF08\u591A\u5173\u952E\u8BCD\u7528 | \u5408\u5E76\uFF09" },
9148
+ is_regex: { type: "boolean", description: "query \u662F\u5426\u6309\u6B63\u5219\u89E3\u6790\uFF0C\u9ED8\u8BA4 false" },
9149
+ run_id: { type: "string", description: "\u9650\u5B9A\u67D0\u4E2A run\uFF08\u4E00\u6B21\u7528\u6237\u8F93\u5165\u7684\u56DE\u5408\uFF09\u5185\u68C0\u7D22\uFF1A\u4F20\u547D\u4E2D\u5934\u5C55\u793A\u7684 runId \u524D\u7F00\uFF08\u53EF\u9009\uFF09" },
9150
+ limit: { type: "number", description: "\u6700\u5927\u547D\u4E2D\u6761\u6570\uFF08\u9ED8\u8BA4 6\uFF0C\u4E0A\u9650 12\uFF09" },
9151
+ with_full: { type: "string", description: "\u53D6\u56DE\u67D0\u4E2A\u5DE5\u5177\u7ED3\u679C\u7684\u5B58\u6863\u5168\u6587\uFF08\u66FE\u88AB\u622A\u65AD\u7684\u8D85\u957F\u7ED3\u679C\uFF09\uFF1A\u4F20\u5176 tool_call_id\uFF08\u622A\u65AD\u6807\u8BB0\u884C\u4E2D\u7ED9\u51FA\uFF09\u3002\u4E0E query \u4E92\u65A5" },
9152
+ full_offset: { type: "number", description: "with_full \u5206\u9875\u504F\u79FB\uFF08\u5B57\u7B26\uFF09\uFF0C\u9ED8\u8BA4 0\uFF1B\u8FD4\u56DE\u5C3E\u6807\u63D0\u793A\u4E0B\u4E00\u9875\u504F\u79FB" }
9153
+ },
9154
+ required: []
9155
+ },
9156
+ safetyLevel: "safe" /* SAFE */,
9157
+ isSync: true,
9158
+ // ★ 检索结果预算与 web 等工具对齐 16000 口径;命中数 × 单片段双重限流防 context thrashing
9159
+ maxOutputCharacters: 16e3,
9160
+ async execute(args, ctx) {
9161
+ if (!ctx?.sessionId) return "\u274C [recall] \u7F3A\u5C11\u4F1A\u8BDD\u4E0A\u4E0B\u6587\uFF08sessionId\uFF09\uFF0C\u65E0\u6CD5\u68C0\u7D22\u3002";
9162
+ try {
9163
+ if (args.with_full) {
9164
+ const safeId = String(args.with_full).replace(/[^A-Za-z0-9_-]/g, "");
9165
+ if (!safeId) return "\u274C [recall] with_full \u542B\u975E\u6CD5\u5B57\u7B26\u3002";
9166
+ const sidecar = path29.join(getSessionsDirPath(ctx.sessionId), "tool-outputs", `${safeId}.txt`);
9167
+ let full = "";
9168
+ try {
9169
+ full = await fs26.readFile(sidecar, "utf-8");
9170
+ } catch {
9171
+ return `\u274C [recall] \u672A\u627E\u5230\u8BE5 id \u7684\u5B58\u6863\u539F\u6587\uFF08\u53EF\u80FD\u672A\u66FE\u89E6\u53D1\u622A\u65AD\u5B58\u6863\u3001\u6216\u4F1A\u8BDD\u5DF2\u6E05\u7406\uFF09\uFF1A${args.with_full}`;
9172
+ }
9173
+ const CHUNK = 14e3;
9174
+ const off = Math.max(0, Math.floor(args.full_offset ?? 0));
9175
+ const slice = full.slice(off, off + CHUNK);
9176
+ const more = off + CHUNK < full.length ? `
9177
+
9178
+ [\u672A\u5B8C\uFF0C\u5171 ${full.length} \u5B57\u7B26\u2014\u2014\u7EE7\u7EED\u4F20 full_offset=${off + CHUNK} \u53D6\u4E0B\u4E00\u6BB5]` : "\n\n[\u5168\u6587\u5B8C]";
9179
+ return `[\u5B58\u6863\u539F\u6587 ${args.with_full} \xB7 \u7B2C ${off}-${Math.min(off + CHUNK, full.length)} / ${full.length} \u5B57\u7B26]
9180
+
9181
+ ${slice}${more}`;
9182
+ }
9183
+ const cleanQuery = (args.query || "").trim();
9184
+ if (!cleanQuery) return "\u274C [recall] \u68C0\u7D22\u5173\u952E\u8BCD\u4E0D\u80FD\u4E3A\u7A7A\uFF08\u6216\u6539\u7528 with_full \u53D6\u5B58\u6863\u5168\u6587\uFF09\u3002";
9185
+ const re = new RegExp(args.is_regex ? cleanQuery : escapeRegExp2(cleanQuery), "i");
9186
+ const lines = await readTranscriptLines(ctx.sessionId);
9187
+ const toolCallInfo = /* @__PURE__ */ new Map();
9188
+ for (const l of lines) {
9189
+ if (isEventLine(l) || !Array.isArray(l.tool_calls)) continue;
9190
+ for (const c of l.tool_calls) {
9191
+ let a = {};
9192
+ try {
9193
+ a = JSON.parse(c?.function?.arguments || "{}");
9194
+ } catch {
9195
+ }
9196
+ toolCallInfo.set(c.id, { name: c?.function?.name ?? "", args: a, ts: l.ts });
9197
+ }
9198
+ }
9199
+ const limit = Math.min(Math.max(Math.floor(args.limit ?? 6), 1), 12);
9200
+ const runFilter = args.run_id?.trim();
9201
+ const blocks = [];
9202
+ let runN = 0;
9203
+ let runId = "";
9204
+ for (const l of lines) {
9205
+ if (isEventLine(l)) {
9206
+ if (l.dscEvent === "run.start") {
9207
+ runN++;
9208
+ runId = String(l.runId ?? "");
9209
+ }
9210
+ continue;
9211
+ }
9212
+ const role = l.role;
9213
+ if (role === "system") continue;
9214
+ if (runFilter && !(runId && runId.startsWith(runFilter))) continue;
9215
+ let info;
9216
+ if (role === "tool") {
9217
+ info = toolCallInfo.get(l.tool_call_id);
9218
+ if (info?.name === "recall") continue;
9219
+ }
9220
+ const text = lineText(l);
9221
+ if (!text || !re.test(text)) continue;
9222
+ const ts = l.ts;
9223
+ const roleDesc = role === "tool" ? `${info?.name ?? "tool"} \u8C03\u7528\u7ED3\u679C` : role === "assistant" ? "assistant\uFF08\u6587\u672C/\u5DE5\u5177\u8C03\u7528\u53C2\u6570\uFF09" : "\u7528\u6237\u6D88\u606F";
9224
+ let header = `\u2500\u2500 \u547D\u4E2D${blocks.length + 1} \xB7 run#${runN}${runId ? `(${runId.slice(0, 8)})` : ""} \xB7 ${roleDesc} \xB7 ${ts ?? "\u65F6\u95F4\u672A\u77E5"} \u2500\u2500`;
9225
+ if (role === "tool") {
9226
+ header += `
9227
+ \u6821\u9A8C\uFF1A${await annotateStaleness(info?.args, ts)}`;
9228
+ if (typeof l.content === "string" && l.content.includes("\u5B8C\u6574\u539F\u6587\u5DF2\u5B58\u6863")) {
9229
+ header += `
9230
+ \u2702\uFE0F \u8BE5\u7ED3\u679C\u66FE\u88AB\u622A\u65AD\uFF0C\u4F20 with_full="${l.tool_call_id}" \u53EF\u53D6\u5B58\u6863\u5168\u6587`;
9231
+ }
9232
+ }
9233
+ blocks.push(`${header}
9234
+ ${snippetAround(text, re, 700)}`);
9235
+ if (blocks.length >= limit) break;
9236
+ }
9237
+ if (blocks.length === 0) {
9238
+ return [
9239
+ `\u672A\u547D\u4E2D "${args.query}"\u3002\u5EFA\u8BAE\uFF1A`,
9240
+ "\xB7 \u6362\u7528\u6458\u8981\u69FD \u27E6DSC:ARCHIVE-INDEX\u27E7 \u4E2D\u51FA\u73B0\u8FC7\u7684\u5B9E\u4F53\u540D\uFF08\u6587\u4EF6\u8DEF\u5F84/\u51FD\u6570\u540D/\u62A5\u9519\u5173\u952E\u8BCD\uFF09",
9241
+ "\xB7 \u591A\u5173\u952E\u8BCD\u7528 is_regex=true \u4EE5 | \u5408\u5E76\u6269\u5927\u5339\u914D",
9242
+ "\xB7 \u8BB0\u5F97\u5F52\u6863\u533A\u5728\u6458\u8981\u69FD\u4E4B\u524D\u7684\u66F4\u65E9\u8F6E\u6B21\u2014\u2014\u7528\u66F4\u65E9\u9636\u6BB5\u51FA\u73B0\u8FC7\u7684\u8BCD"
9243
+ ].join("\n");
9244
+ }
9245
+ return [
9246
+ `[recall] \u672C\u4F1A\u8BDD\u5168\u91CF\u8F6C\u5F55\u547D\u4E2D ${blocks.length} \u6761\uFF08\u6309\u65F6\u95F4\u65E7\u2192\u65B0\uFF0C\u542B\u5DF2\u5F52\u6863\u6D88\u606F\uFF09\uFF1A`,
9247
+ "",
9248
+ blocks.join("\n\n"),
9249
+ "",
9250
+ "\u63D0\u793A\uFF1A\u26A0\uFE0F \u6807\u6CE8\u7684\u6587\u4EF6\u5FC5\u987B\u91CD\u65B0 read_file \u9A8C\u8BC1\u540E\u624D\u80FD\u636E\u4EE5\u4FEE\u6539\uFF1B\u672C\u5DE5\u5177\u7ED3\u679C\u4EC5\u4F5C\u5386\u53F2\u7EBF\u7D22\uFF0C\u4E0D\u66FF\u4EE3\u5F53\u524D\u6587\u4EF6\u72B6\u6001\u3002"
9251
+ ].join("\n");
9252
+ } catch (e) {
9253
+ return `\u274C [recall] \u68C0\u7D22\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`;
9254
+ }
9255
+ }
9256
+ }
9257
+ }
9258
+ ];
9259
+ }
9260
+ });
9261
+
8827
9262
  // ../core/src/tool/registry/spreadsheet.ts
8828
9263
  var getExcelJs, colLetter, cellToText, esc, spreadsheetTools;
8829
9264
  var init_spreadsheet = __esm({
@@ -8940,7 +9375,7 @@ var init_spreadsheet = __esm({
8940
9375
  });
8941
9376
 
8942
9377
  // ../core/src/tool/registry/document.ts
8943
- import fs26 from "fs/promises";
9378
+ import fs27 from "fs/promises";
8944
9379
  var getMammoth, getUnpdf, documentTools;
8945
9380
  var init_document = __esm({
8946
9381
  "../core/src/tool/registry/document.ts"() {
@@ -9030,7 +9465,7 @@ var init_document = __esm({
9030
9465
  }
9031
9466
  const start = args.start_char ? Math.max(0, Math.floor(args.start_char)) : 0;
9032
9467
  const maxChars = Math.min(args.max_chars ? Math.max(1, Math.floor(args.max_chars)) : 3e4, 5e4);
9033
- const buf = await fs26.readFile(absPath);
9468
+ const buf = await fs27.readFile(absPath);
9034
9469
  const unpdf = await getUnpdf();
9035
9470
  const pdf = await unpdf.getDocumentProxy(new Uint8Array(buf));
9036
9471
  const { text, totalPages } = await unpdf.extractText(pdf, { mergePages: true });
@@ -9085,6 +9520,7 @@ var init_tool = __esm({
9085
9520
  init_worktree();
9086
9521
  init_notebook();
9087
9522
  init_memory();
9523
+ init_recall();
9088
9524
  init_spreadsheet();
9089
9525
  init_document();
9090
9526
  init_type();
@@ -9110,6 +9546,7 @@ var init_tool = __esm({
9110
9546
  ...worktreeTools,
9111
9547
  ...notebookTools,
9112
9548
  ...memoryTools,
9549
+ ...recallTools,
9113
9550
  ...spreadsheetTools,
9114
9551
  ...documentTools
9115
9552
  // read_docx / read_pdf(Word / PDF 文档读取,补 read_file 读不了的二进制文档)
@@ -9233,7 +9670,7 @@ var init_backgroundTool = __esm({
9233
9670
  });
9234
9671
 
9235
9672
  // ../core/src/tool/autoPermission.ts
9236
- import path29 from "path";
9673
+ import path30 from "path";
9237
9674
  var AUTO_SCOPE, AUTO_DEFAULT_EXTRA, AUTO_AGGRESSIVE_EXTRA, isMcpTool, BUILTIN_AUTO_DENY, matchBuiltinDeny, COMMAND_DENY, matchCommandDeny, hasShellMetachars, READONLY_HEADS, hasWriteModifier, SENSITIVE_FILE_ARG_RE, hasSensitiveFileArg, isReadOnlyCommand, isWithinWorkspace, runAutoCheck;
9238
9675
  var init_autoPermission = __esm({
9239
9676
  "../core/src/tool/autoPermission.ts"() {
@@ -9368,9 +9805,9 @@ var init_autoPermission = __esm({
9368
9805
  isWithinWorkspace = (p, cwd) => {
9369
9806
  if (!p || !cwd) return false;
9370
9807
  try {
9371
- const abs = path29.isAbsolute(p) ? p : path29.resolve(cwd, p);
9372
- const rel = path29.relative(cwd, abs);
9373
- return rel === "" || !rel.startsWith("..") && !path29.isAbsolute(rel);
9808
+ const abs = path30.isAbsolute(p) ? p : path30.resolve(cwd, p);
9809
+ const rel = path30.relative(cwd, abs);
9810
+ return rel === "" || !rel.startsWith("..") && !path30.isAbsolute(rel);
9374
9811
  } catch {
9375
9812
  return false;
9376
9813
  }
@@ -9398,6 +9835,8 @@ var init_autoPermission = __esm({
9398
9835
  });
9399
9836
 
9400
9837
  // ../core/src/agent/toolExecution.ts
9838
+ import fs28 from "fs/promises";
9839
+ import path31 from "path";
9401
9840
  var applyPrivacyMasking, processToolCall;
9402
9841
  var init_toolExecution = __esm({
9403
9842
  "../core/src/agent/toolExecution.ts"() {
@@ -9413,6 +9852,7 @@ var init_toolExecution = __esm({
9413
9852
  init_autoPermission();
9414
9853
  init_loader();
9415
9854
  init_config();
9855
+ init_store();
9416
9856
  init_planMode();
9417
9857
  applyPrivacyMasking = (rules3, args, output) => {
9418
9858
  if (!rules3) return output;
@@ -9634,7 +10074,20 @@ ${result}`;
9634
10074
  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 } });
9635
10075
  }
9636
10076
  result = applyPrivacyMasking(matchedTool?.function?.privacyMaskingRules, calledArgs, result);
9637
- result = truncateToolResult(result, matchedTool?.function?.maxOutputCharacters);
10077
+ let sidecarNote;
10078
+ const truncBudget = matchedTool?.function?.maxOutputCharacters ?? appConfig.MAX_TOOL_RESULT_CHARS;
10079
+ if (result.length > truncBudget) {
10080
+ try {
10081
+ const sidecarDir = path31.join(getSessionsDirPath(sessionId), "tool-outputs");
10082
+ await fs28.mkdir(sidecarDir, { recursive: true });
10083
+ const safeId = String(toolCall.id).replace(/[^A-Za-z0-9_-]/g, "");
10084
+ await fs28.writeFile(path31.join(sidecarDir, `${safeId}.txt`), result, "utf-8");
10085
+ sidecarNote = `\u5B8C\u6574\u539F\u6587\u5DF2\u5B58\u6863\uFF0Crecall \u5DE5\u5177\u4F20 with_full="${toolCall.id}" \u53EF\u53D6\u56DE`;
10086
+ } catch (e) {
10087
+ console.warn(`\u26A0\uFE0F [sidecar] \u5DE5\u5177\u539F\u6587\u5B58\u6863\u5931\u8D25\uFF08\u5DF2\u964D\u7EA7\u4E3A\u666E\u901A\u622A\u65AD\u63D0\u793A\uFF09:`, e instanceof Error ? e.message : e);
10088
+ }
10089
+ }
10090
+ result = truncateToolResult(result, matchedTool?.function?.maxOutputCharacters, sidecarNote);
9638
10091
  const FAILED_PREFIXES = ["\u5DE5\u5177\u6267\u884C\u5931\u8D25", "\u53C2\u6570\u89E3\u6790\u5931\u8D25", "\u274C", "\u3010\u7CFB\u7EDF\u5224\u5B9A", "\u{1F512}", "\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25", "\u9879\u76EE\u6811\u626B\u63CF\u5931\u8D25", "\u7B26\u53F7\u5927\u7EB2\u5206\u6790\u5931\u8D25", "\u64CD\u4F5C\u5931\u8D25:"];
9639
10092
  const ok = explicitOk ?? !FAILED_PREFIXES.some((p) => result.startsWith(p));
9640
10093
  let resultForModel = result;
@@ -9777,6 +10230,7 @@ var init_streamInference = __esm({
9777
10230
  init_truncate();
9778
10231
  init_transcript();
9779
10232
  init_contextCore();
10233
+ init_contentParts();
9780
10234
  streamInference = async function* (ctx) {
9781
10235
  const {
9782
10236
  message,
@@ -9804,7 +10258,8 @@ var init_streamInference = __esm({
9804
10258
  eventType: "llm.request",
9805
10259
  metadata: { depth, decisionSource: userDecisionSource, ok: true, durationMs: performance.now() - startTime, round },
9806
10260
  usage: { prompt_tokens: estimateTokens(inferenceMessages) },
9807
- payload: { input: message[message.length - 1].content }
10261
+ payload: { input: msgText(message[message.length - 1].content) }
10262
+ // ★ 多模态:trace 只记文本视图
9808
10263
  });
9809
10264
  console.log(`\u{1F504} \u4EE3\u7406\u63A8\u7406\u7B2C ${round} \u8F6E\uFF08\u6D41\u5F0F\uFF09...`);
9810
10265
  let contentBuf = "";
@@ -9936,7 +10391,8 @@ var init_streamInference = __esm({
9936
10391
  prompt_cache_hit_tokens: lastUsage?.cached_tokens,
9937
10392
  prompt_cache_miss_tokens: (lastUsage?.prompt_tokens || 0) - (lastUsage?.cached_tokens || 0)
9938
10393
  },
9939
- payload: { input: message[message.length - 1].content }
10394
+ payload: { input: msgText(message[message.length - 1].content) }
10395
+ // ★ 多模态:trace 只记文本视图
9940
10396
  });
9941
10397
  console.log(`[DSC-DIAG] streamInference round ${round} \u5B8C\u6210: content=${contentBuf.length}chars reasoning=${reasoningBuf.length}chars toolCalls=${toolCallsBuf.size}`);
9942
10398
  return { kind: "completed", assistantMessage, usage: lastUsage };
@@ -9949,7 +10405,8 @@ var init_streamInference = __esm({
9949
10405
  sessionId,
9950
10406
  eventType: "llm.error",
9951
10407
  metadata: { depth, decisionSource: llmDecisionSource, ok: false, durationMs: performance.now() - startTime, attempt: round },
9952
- payload: { input: message[message.length - 1].content, output: err.message }
10408
+ payload: { input: msgText(message[message.length - 1].content), output: err.message }
10409
+ // ★ 多模态:文本视图
9953
10410
  });
9954
10411
  console.log(`[DSC-DIAG] streamInference round ${round} \u2550\u2550\u2550 ERROR: ${err.message}`);
9955
10412
  return { kind: "error", error: err };
@@ -10164,7 +10621,7 @@ async function* runAgent(message, options) {
10164
10621
  const firstPrompt = (() => {
10165
10622
  if (depth !== 0) return "";
10166
10623
  const m = message.find((x) => x?.role === "user");
10167
- return typeof m?.content === "string" ? m.content : "";
10624
+ return msgText(m?.content);
10168
10625
  })();
10169
10626
  const nudges = createNudgeScheduler({ firstPrompt, planMode: !!options.planMode, noEarlyFinal: !!options.noEarlyFinal });
10170
10627
  const userDecisionSource = depth > 0 ? "spawn_agent" : "user";
@@ -10292,7 +10749,7 @@ async function* runAgent(message, options) {
10292
10749
  const assistantMessage = infResult.assistantMessage;
10293
10750
  message.push(assistantMessage);
10294
10751
  await appendMessage({ ...assistantMessage, sessionId });
10295
- if (assistantMessage.content) {
10752
+ if (typeof assistantMessage.content === "string" && assistantMessage.content) {
10296
10753
  lastContent = assistantMessage.content;
10297
10754
  }
10298
10755
  if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
@@ -10385,6 +10842,7 @@ var init_runAgent = __esm({
10385
10842
  init_inbox();
10386
10843
  init_common();
10387
10844
  init_contextCore();
10845
+ init_contentParts();
10388
10846
  init_store();
10389
10847
  init_truncate();
10390
10848
  init_hooks();
@@ -10477,11 +10935,12 @@ var init_recovery = __esm({
10477
10935
  });
10478
10936
 
10479
10937
  // ../core/src/session/content.ts
10480
- var decayOldToolResults, buildContextMessages;
10938
+ var decayOldToolResults, NO_VISION_IMAGE_NOTE, enforceVisionGate, buildContextMessages;
10481
10939
  var init_content = __esm({
10482
10940
  "../core/src/session/content.ts"() {
10483
10941
  "use strict";
10484
10942
  init_contextCore();
10943
+ init_contentParts();
10485
10944
  init_transcript();
10486
10945
  init_store();
10487
10946
  init_config();
@@ -10491,15 +10950,20 @@ var init_content = __esm({
10491
10950
  const cutoffUnitIdx = Math.max(0, units.length - appConfig.KEEP_RECENT_UNITS);
10492
10951
  if (cutoffUnitIdx === 0) return msgs;
10493
10952
  const decayIds = /* @__PURE__ */ new Set();
10953
+ let hasOldImage = false;
10494
10954
  for (let u = 0; u < cutoffUnitIdx; u++) {
10495
10955
  for (const m of units[u]) {
10496
10956
  const mm = m;
10497
10957
  if (mm.role === "tool" && typeof mm.tool_call_id === "string") decayIds.add(mm.tool_call_id);
10958
+ if (hasImagePart(m.content)) hasOldImage = true;
10498
10959
  }
10499
10960
  }
10500
- if (decayIds.size === 0) return msgs;
10961
+ if (decayIds.size === 0 && !hasOldImage) return msgs;
10501
10962
  const keep = appConfig.BOUNDARY_TOOL_KEEP_CHARS;
10963
+ const oldRefs = /* @__PURE__ */ new Set();
10964
+ for (let u = 0; u < cutoffUnitIdx; u++) for (const m of units[u]) oldRefs.add(m);
10502
10965
  return msgs.map((m) => {
10966
+ if (!oldRefs.has(m)) return m;
10503
10967
  const mm = m;
10504
10968
  if (mm.role === "tool" && decayIds.has(mm.tool_call_id) && typeof mm.content === "string" && mm.content.length > keep) {
10505
10969
  const head = mm.content.slice(0, keep);
@@ -10507,9 +10971,11 @@ var init_content = __esm({
10507
10971
 
10508
10972
  [\u2026 \u8BE5\u5386\u53F2\u5DE5\u5177\u8F93\u51FA\u5DF2\u6298\u53E0\uFF08\u5171 ${mm.content.length} \u5B57\u7B26\uFF09\uFF0C\u5982\u9700\u7EC6\u8282\u8BF7\u91CD\u65B0\u8C03\u7528\u5DE5\u5177 \u2026]` };
10509
10973
  }
10510
- return m;
10974
+ return replaceImageParts(mm, "[\u5386\u53F2\u56FE\u7247\u5DF2\u6298\u53E0\uFF1A\u539F\u56FE\u4ECD\u5728\u4F1A\u8BDD\u5F52\u6863\u4E2D\uFF0C\u5982\u9700\u518D\u6B21\u67E5\u770B\u8BF7\u91CD\u65B0\u63D0\u4F9B\u8BE5\u56FE\u7247]");
10511
10975
  });
10512
10976
  };
10977
+ NO_VISION_IMAGE_NOTE = "[\u56FE\u7247\u672A\u9001\u8FBE\uFF1A\u5F53\u524D\u672A\u5F00\u542F\u89C6\u89C9\u80FD\u529B\uFF08DEEP_SEEK_VISION\uFF09\uFF0C\u6A21\u578B\u770B\u4E0D\u5230\u8BE5\u56FE\uFF1B\u539F\u56FE\u4FDD\u7559\u5728\u4F1A\u8BDD\u5F52\u6863\u4E2D\uFF0C\u5982\u9700\u5206\u6790\u8BF7\u8BA9\u7528\u6237\u91CD\u65B0\u63D0\u4F9B]";
10978
+ enforceVisionGate = (msgs) => isVisionEnabled() ? msgs : msgs.map((m) => collapseToText(m, NO_VISION_IMAGE_NOTE));
10513
10979
  buildContextMessages = async (sessionId, currentUserMsg, systemPrompt) => {
10514
10980
  const lines = await readTranscriptLines(sessionId);
10515
10981
  const store = await getRollingState(sessionId);
@@ -10529,18 +10995,18 @@ var init_content = __esm({
10529
10995
  const messageAll = all.slice(archivedMessageCount);
10530
10996
  if (messageAll.length === 0) {
10531
10997
  result.push(currentUserMsg);
10532
- return result;
10998
+ return enforceVisionGate(result);
10533
10999
  }
10534
11000
  const repair = repairOrphanToolCalls(messageAll, recovery.interruption.kind === "crashed");
10535
11001
  result.push(...decayOldToolResults(repair.msgs));
10536
11002
  result.push(currentUserMsg);
10537
- return result;
11003
+ return enforceVisionGate(result);
10538
11004
  };
10539
11005
  }
10540
11006
  });
10541
11007
 
10542
11008
  // ../core/src/session/lineage.ts
10543
- import fs27 from "node:fs";
11009
+ import fs29 from "node:fs";
10544
11010
  var parentOfSubsession, stringAncestorsOf, readForkedFromSync, lineageClosureOf, ownerCanApprove;
10545
11011
  var init_lineage = __esm({
10546
11012
  "../core/src/session/lineage.ts"() {
@@ -10563,7 +11029,7 @@ var init_lineage = __esm({
10563
11029
  };
10564
11030
  readForkedFromSync = (sessionId) => {
10565
11031
  try {
10566
- const raw = fs27.readFileSync(getStatePath(sessionId), "utf-8");
11032
+ const raw = fs29.readFileSync(getStatePath(sessionId), "utf-8");
10567
11033
  const v = JSON.parse(raw)?.forkedFrom;
10568
11034
  return typeof v === "string" && v.length > 0 ? v : void 0;
10569
11035
  } catch {
@@ -10760,8 +11226,8 @@ var init_subagent = __esm({
10760
11226
  });
10761
11227
 
10762
11228
  // ../core/src/hooks/loader.ts
10763
- import fs28 from "fs/promises";
10764
- import path30 from "path";
11229
+ import fs30 from "fs/promises";
11230
+ import path32 from "path";
10765
11231
  var readHooksConfig, appendValidated, validateRule, parseStdoutDecision, parseAgentDecision, compileRule2, loadHooks, initHooks;
10766
11232
  var init_loader4 = __esm({
10767
11233
  "../core/src/hooks/loader.ts"() {
@@ -10776,15 +11242,15 @@ var init_loader4 = __esm({
10776
11242
  readHooksConfig = async (includeProject) => {
10777
11243
  const merged = {};
10778
11244
  const paths = [
10779
- path30.join(appConfig.dataDir, "settings.json"),
11245
+ path32.join(appConfig.dataDir, "settings.json"),
10780
11246
  // 全局用户级
10781
- ...includeProject ? [path30.join(process.cwd(), ".deepseeker-code", "settings.json")] : []
11247
+ ...includeProject ? [path32.join(process.cwd(), ".deepseeker-code", "settings.json")] : []
10782
11248
  // 项目级(叠加);未信任时省略
10783
11249
  ];
10784
11250
  for (const configPath of paths) {
10785
11251
  let raw;
10786
11252
  try {
10787
- raw = await fs28.readFile(configPath, "utf-8");
11253
+ raw = await fs30.readFile(configPath, "utf-8");
10788
11254
  } catch {
10789
11255
  continue;
10790
11256
  }
@@ -11114,8 +11580,8 @@ ${m.context}`;
11114
11580
  });
11115
11581
 
11116
11582
  // ../core/src/skills/loader.ts
11117
- import fs29 from "fs/promises";
11118
- import path31 from "path";
11583
+ import fs31 from "fs/promises";
11584
+ import path33 from "path";
11119
11585
  import { fileURLToPath as fileURLToPath2 } from "url";
11120
11586
  var BUILTIN_DIR, SOURCES2, MAX_SKILL_BODY_BYTES, splitList, parseSkillAt, loadSkills, initSkills;
11121
11587
  var init_loader5 = __esm({
@@ -11126,18 +11592,18 @@ var init_loader5 = __esm({
11126
11592
  init_registry3();
11127
11593
  init_skill();
11128
11594
  init_registry2();
11129
- BUILTIN_DIR = path31.join(path31.dirname(fileURLToPath2(import.meta.url)), "builtin");
11595
+ BUILTIN_DIR = path33.join(path33.dirname(fileURLToPath2(import.meta.url)), "builtin");
11130
11596
  SOURCES2 = [
11131
11597
  { dir: BUILTIN_DIR, source: "builtin" },
11132
- { dir: path31.join(appConfig.dataDir, "skills"), source: "global" },
11133
- { dir: path31.join(process.cwd(), ".deepseeker-code", "skills"), source: "project" }
11598
+ { dir: path33.join(appConfig.dataDir, "skills"), source: "global" },
11599
+ { dir: path33.join(process.cwd(), ".deepseeker-code", "skills"), source: "project" }
11134
11600
  ];
11135
11601
  MAX_SKILL_BODY_BYTES = 64 * 1024;
11136
11602
  splitList = (raw) => (raw ?? "").split(",").map((s) => s.trim()).filter(Boolean);
11137
11603
  parseSkillAt = async (skillFile, dir, source) => {
11138
11604
  let raw;
11139
11605
  try {
11140
- raw = await fs29.readFile(skillFile, "utf-8");
11606
+ raw = await fs31.readFile(skillFile, "utf-8");
11141
11607
  } catch {
11142
11608
  return null;
11143
11609
  }
@@ -11176,7 +11642,7 @@ var init_loader5 = __esm({
11176
11642
  loadSkills = async (includeProject) => {
11177
11643
  const picked = await scanSources(
11178
11644
  filterSources(SOURCES2, includeProject),
11179
- (e, dir) => e.isDirectory() ? path31.join(dir, e.name, "SKILL.md") : void 0,
11645
+ (e, dir) => e.isDirectory() ? path33.join(dir, e.name, "SKILL.md") : void 0,
11180
11646
  parseSkillAt
11181
11647
  );
11182
11648
  for (const { item } of picked) registerSkill(item);
@@ -11207,8 +11673,8 @@ var init_loader5 = __esm({
11207
11673
  });
11208
11674
 
11209
11675
  // ../core/src/agents/loader.ts
11210
- import fs30 from "fs/promises";
11211
- import path32 from "path";
11676
+ import fs32 from "fs/promises";
11677
+ import path34 from "path";
11212
11678
  import { fileURLToPath as fileURLToPath3 } from "url";
11213
11679
  var BUILTIN_DIR2, SOURCES3, parseAgentAt, initAgents;
11214
11680
  var init_loader6 = __esm({
@@ -11218,16 +11684,16 @@ var init_loader6 = __esm({
11218
11684
  init_frontmatter();
11219
11685
  init_registry4();
11220
11686
  init_registry2();
11221
- BUILTIN_DIR2 = path32.join(path32.dirname(fileURLToPath3(import.meta.url)), "builtin");
11687
+ BUILTIN_DIR2 = path34.join(path34.dirname(fileURLToPath3(import.meta.url)), "builtin");
11222
11688
  SOURCES3 = [
11223
11689
  { dir: BUILTIN_DIR2, source: "builtin" },
11224
- { dir: path32.join(appConfig.dataDir, "agents"), source: "global" },
11225
- { dir: path32.join(process.cwd(), ".deepseeker-code", "agents"), source: "project" }
11690
+ { dir: path34.join(appConfig.dataDir, "agents"), source: "global" },
11691
+ { dir: path34.join(process.cwd(), ".deepseeker-code", "agents"), source: "project" }
11226
11692
  ];
11227
11693
  parseAgentAt = async (file, dir, source) => {
11228
11694
  let raw;
11229
11695
  try {
11230
- raw = await fs30.readFile(file, "utf-8");
11696
+ raw = await fs32.readFile(file, "utf-8");
11231
11697
  } catch {
11232
11698
  return null;
11233
11699
  }
@@ -11262,7 +11728,7 @@ var init_loader6 = __esm({
11262
11728
  const known = new Set(into.map((t) => t.function.name));
11263
11729
  const picked = await scanSources(
11264
11730
  filterSources(SOURCES3, includeProject),
11265
- (e, dir) => e.isFile() && e.name.endsWith(".agent.md") ? path32.join(dir, e.name) : void 0,
11731
+ (e, dir) => e.isFile() && e.name.endsWith(".agent.md") ? path34.join(dir, e.name) : void 0,
11266
11732
  parseAgentAt
11267
11733
  );
11268
11734
  for (const { item: m, file, source } of picked) {
@@ -11299,8 +11765,8 @@ var init_registry7 = __esm({
11299
11765
  });
11300
11766
 
11301
11767
  // ../core/src/commands/loader.ts
11302
- import fs31 from "fs/promises";
11303
- import path33 from "path";
11768
+ import fs33 from "fs/promises";
11769
+ import path35 from "path";
11304
11770
  import { fileURLToPath as fileURLToPath4 } from "url";
11305
11771
  var BUILTIN_DIR3, SOURCES4, MAX_COMMAND_BODY_BYTES, parseCommandAt, loadCommands, initCommands;
11306
11772
  var init_loader7 = __esm({
@@ -11310,17 +11776,17 @@ var init_loader7 = __esm({
11310
11776
  init_frontmatter();
11311
11777
  init_registry7();
11312
11778
  init_registry2();
11313
- BUILTIN_DIR3 = path33.join(path33.dirname(fileURLToPath4(import.meta.url)), "builtin");
11779
+ BUILTIN_DIR3 = path35.join(path35.dirname(fileURLToPath4(import.meta.url)), "builtin");
11314
11780
  SOURCES4 = [
11315
11781
  { dir: BUILTIN_DIR3, source: "builtin" },
11316
- { dir: path33.join(appConfig.dataDir, "commands"), source: "global" },
11317
- { dir: path33.join(process.cwd(), ".deepseeker-code", "commands"), source: "project" }
11782
+ { dir: path35.join(appConfig.dataDir, "commands"), source: "global" },
11783
+ { dir: path35.join(process.cwd(), ".deepseeker-code", "commands"), source: "project" }
11318
11784
  ];
11319
11785
  MAX_COMMAND_BODY_BYTES = 64 * 1024;
11320
11786
  parseCommandAt = async (file, _dir, source) => {
11321
11787
  let raw;
11322
11788
  try {
11323
- raw = await fs31.readFile(file, "utf-8");
11789
+ raw = await fs33.readFile(file, "utf-8");
11324
11790
  } catch {
11325
11791
  return null;
11326
11792
  }
@@ -11331,7 +11797,7 @@ var init_loader7 = __esm({
11331
11797
  }
11332
11798
  const name = parsed.frontmatter.name;
11333
11799
  const description = parsed.frontmatter.description;
11334
- const fallbackName = path33.basename(file, ".md");
11800
+ const fallbackName = path35.basename(file, ".md");
11335
11801
  const finalName = name || fallbackName;
11336
11802
  if (!description) {
11337
11803
  console.warn(`\u26A0\uFE0F [commands] \u7F3A\u5C11 description\uFF08${file}\uFF09\uFF0C\u5DF2\u8DF3\u8FC7`);
@@ -11357,7 +11823,7 @@ var init_loader7 = __esm({
11357
11823
  loadCommands = async (includeProject) => {
11358
11824
  const picked = await scanSources(
11359
11825
  filterSources(SOURCES4, includeProject),
11360
- (e, dir) => e.isFile() && e.name.endsWith(".md") ? path33.join(dir, e.name) : void 0,
11826
+ (e, dir) => e.isFile() && e.name.endsWith(".md") ? path35.join(dir, e.name) : void 0,
11361
11827
  parseCommandAt
11362
11828
  );
11363
11829
  for (const { item } of picked) registerCommand(item);
@@ -11375,8 +11841,8 @@ var init_loader7 = __esm({
11375
11841
  });
11376
11842
 
11377
11843
  // ../core/src/outputStyles/loader.ts
11378
- import fs32 from "fs/promises";
11379
- import path34 from "path";
11844
+ import fs34 from "fs/promises";
11845
+ import path36 from "path";
11380
11846
  import { fileURLToPath as fileURLToPath5 } from "url";
11381
11847
  var BUILTIN_DIR4, MAX_STYLE_BODY_BYTES, SOURCES5, parseStyleAt, loadOutputStyles, initOutputStyles;
11382
11848
  var init_loader8 = __esm({
@@ -11386,17 +11852,17 @@ var init_loader8 = __esm({
11386
11852
  init_frontmatter();
11387
11853
  init_registry5();
11388
11854
  init_registry2();
11389
- BUILTIN_DIR4 = path34.join(path34.dirname(fileURLToPath5(import.meta.url)), "builtin");
11855
+ BUILTIN_DIR4 = path36.join(path36.dirname(fileURLToPath5(import.meta.url)), "builtin");
11390
11856
  MAX_STYLE_BODY_BYTES = 64 * 1024;
11391
11857
  SOURCES5 = [
11392
11858
  { dir: BUILTIN_DIR4, source: "builtin" },
11393
- { dir: path34.join(appConfig.dataDir, "output-styles"), source: "global" },
11394
- { dir: path34.join(process.cwd(), ".deepseeker-code", "output-styles"), source: "project" }
11859
+ { dir: path36.join(appConfig.dataDir, "output-styles"), source: "global" },
11860
+ { dir: path36.join(process.cwd(), ".deepseeker-code", "output-styles"), source: "project" }
11395
11861
  ];
11396
11862
  parseStyleAt = async (file, _dir, source) => {
11397
11863
  let raw;
11398
11864
  try {
11399
- raw = await fs32.readFile(file, "utf-8");
11865
+ raw = await fs34.readFile(file, "utf-8");
11400
11866
  } catch {
11401
11867
  return null;
11402
11868
  }
@@ -11428,7 +11894,7 @@ var init_loader8 = __esm({
11428
11894
  loadOutputStyles = async (includeProject) => {
11429
11895
  const picked = await scanSources(
11430
11896
  filterSources(SOURCES5, includeProject),
11431
- (e, dir) => e.isFile() && e.name.endsWith(".md") ? path34.join(dir, e.name) : void 0,
11897
+ (e, dir) => e.isFile() && e.name.endsWith(".md") ? path36.join(dir, e.name) : void 0,
11432
11898
  parseStyleAt
11433
11899
  );
11434
11900
  for (const { item } of picked) registerOutputStyle(item);
@@ -11509,8 +11975,8 @@ var init_bootstrap = __esm({
11509
11975
  });
11510
11976
 
11511
11977
  // ../core/src/statusLine/config.ts
11512
- import fs33 from "fs/promises";
11513
- import path35 from "path";
11978
+ import fs35 from "fs/promises";
11979
+ import path37 from "path";
11514
11980
  var parseBlock, readStatusLineConfig;
11515
11981
  var init_config2 = __esm({
11516
11982
  "../core/src/statusLine/config.ts"() {
@@ -11526,16 +11992,16 @@ var init_config2 = __esm({
11526
11992
  };
11527
11993
  readStatusLineConfig = async (includeProject) => {
11528
11994
  const paths = [
11529
- path35.join(appConfig.dataDir, "settings.json"),
11995
+ path37.join(appConfig.dataDir, "settings.json"),
11530
11996
  // 全局用户级
11531
- ...includeProject ? [path35.join(process.cwd(), ".deepseeker-code", "settings.json")] : []
11997
+ ...includeProject ? [path37.join(process.cwd(), ".deepseeker-code", "settings.json")] : []
11532
11998
  // 项目级(覆盖);未信任时省略
11533
11999
  ];
11534
12000
  let result = null;
11535
12001
  for (const configPath of paths) {
11536
12002
  let raw;
11537
12003
  try {
11538
- raw = await fs33.readFile(configPath, "utf-8");
12004
+ raw = await fs35.readFile(configPath, "utf-8");
11539
12005
  } catch {
11540
12006
  continue;
11541
12007
  }
@@ -11555,8 +12021,8 @@ var init_config2 = __esm({
11555
12021
 
11556
12022
  // ../core/src/statusLine/runner.ts
11557
12023
  import { spawn as spawn5 } from "child_process";
11558
- import fs34 from "fs";
11559
- import path36 from "path";
12024
+ import fs36 from "fs";
12025
+ import path38 from "path";
11560
12026
  import { fileURLToPath as fileURLToPath6 } from "url";
11561
12027
  var STATUSLINE_TIMEOUT_MS, cachedVersion, runStatusLine;
11562
12028
  var init_runner = __esm({
@@ -11565,16 +12031,16 @@ var init_runner = __esm({
11565
12031
  init_guard();
11566
12032
  STATUSLINE_TIMEOUT_MS = 3e3;
11567
12033
  try {
11568
- const here = path36.dirname(fileURLToPath6(import.meta.url));
12034
+ const here = path38.dirname(fileURLToPath6(import.meta.url));
11569
12035
  const candidates = [
11570
- path36.join(here, "../../../package.json"),
12036
+ path38.join(here, "../../../package.json"),
11571
12037
  // src/core/package.json
11572
- path36.join(here, "../../../../package.json")
12038
+ path38.join(here, "../../../../package.json")
11573
12039
  // 仓库根 package.json
11574
12040
  ];
11575
12041
  for (const p of candidates) {
11576
12042
  try {
11577
- const pkg = JSON.parse(fs34.readFileSync(p, "utf-8"));
12043
+ const pkg = JSON.parse(fs36.readFileSync(p, "utf-8"));
11578
12044
  if (typeof pkg.version === "string" && pkg.version) {
11579
12045
  cachedVersion = pkg.version;
11580
12046
  break;
@@ -11633,8 +12099,8 @@ var init_createModel = __esm({
11633
12099
  });
11634
12100
 
11635
12101
  // ../core/src/observability/store.ts
11636
- import path37 from "path";
11637
- import fs35 from "fs/promises";
12102
+ import path39 from "path";
12103
+ import fs37 from "fs/promises";
11638
12104
  var getTraceDirPath, getTracePath, tracePathCache, ensuredTraceDirs, ensureTraceDir, getGlobalClockPath, getTraceStorePath;
11639
12105
  var init_store3 = __esm({
11640
12106
  "../core/src/observability/store.ts"() {
@@ -11644,21 +12110,21 @@ var init_store3 = __esm({
11644
12110
  init_common();
11645
12111
  getTraceDirPath = (mainTraceId) => {
11646
12112
  assertSafeSessionId(mainTraceId, "traceId");
11647
- return path37.join(appConfig.dataDir, "trace", appConfig.userWorkspaceDir, getFileName(mainTraceId));
12113
+ return path39.join(appConfig.dataDir, "trace", appConfig.userWorkspaceDir, getFileName(mainTraceId));
11648
12114
  };
11649
12115
  getTracePath = () => {
11650
- return path37.join(appConfig.dataDir, "trace");
12116
+ return path39.join(appConfig.dataDir, "trace");
11651
12117
  };
11652
12118
  tracePathCache = /* @__PURE__ */ new Map();
11653
12119
  ensuredTraceDirs = /* @__PURE__ */ new Set();
11654
12120
  ensureTraceDir = async (mainTraceId) => {
11655
12121
  const dir = getTraceDirPath(mainTraceId);
11656
12122
  if (ensuredTraceDirs.has(dir)) return;
11657
- await fs35.mkdir(dir, { recursive: true });
12123
+ await fs37.mkdir(dir, { recursive: true });
11658
12124
  ensuredTraceDirs.add(dir);
11659
12125
  };
11660
12126
  getGlobalClockPath = () => {
11661
- return path37.join(getTracePath(), "trace-cleanup-clock.json");
12127
+ return path39.join(getTracePath(), "trace-cleanup-clock.json");
11662
12128
  };
11663
12129
  getTraceStorePath = async (mainTraceId) => {
11664
12130
  assertSafeSessionId(mainTraceId, "traceId");
@@ -11667,7 +12133,7 @@ var init_store3 = __esm({
11667
12133
  if (cached2) return cached2;
11668
12134
  await ensureTraceDir(rootId);
11669
12135
  const dir = getTraceDirPath(rootId);
11670
- const files = await fs35.readdir(dir);
12136
+ const files = await fs37.readdir(dir);
11671
12137
  const existedFile = files.find((f) => f.endsWith(`__${rootId}.jsonl`));
11672
12138
  let fileName = "";
11673
12139
  if (existedFile) {
@@ -11676,7 +12142,7 @@ var init_store3 = __esm({
11676
12142
  const todayStr = getTodayDateString();
11677
12143
  fileName = `trace-${todayStr}__${rootId}.jsonl`;
11678
12144
  }
11679
- const resolved = path37.join(dir, fileName);
12145
+ const resolved = path39.join(dir, fileName);
11680
12146
  tracePathCache.set(rootId, resolved);
11681
12147
  return resolved;
11682
12148
  };
@@ -11684,8 +12150,8 @@ var init_store3 = __esm({
11684
12150
  });
11685
12151
 
11686
12152
  // ../core/src/observability/trace.ts
11687
- import fs36 from "fs/promises";
11688
- import path38 from "path";
12153
+ import fs38 from "fs/promises";
12154
+ import path40 from "path";
11689
12155
  async function emitTrace(base) {
11690
12156
  const event = {
11691
12157
  sessionId: base.sessionId,
@@ -11707,7 +12173,7 @@ async function emitTrace(base) {
11707
12173
  async function appendTraceEvent(event) {
11708
12174
  const p = await getTraceStorePath(event.sessionId);
11709
12175
  const line = JSON.stringify(event) + "\n";
11710
- await fs36.appendFile(p, line, "utf-8");
12176
+ await fs38.appendFile(p, line, "utf-8");
11711
12177
  if (workspaceTraceBytes < 0) {
11712
12178
  workspaceTraceBytes = await getDirBytes(getTracePath());
11713
12179
  }
@@ -11739,8 +12205,8 @@ async function maybeCleanupAfterAppend(sessionId) {
11739
12205
  lastSizeCleanupAt2 = now;
11740
12206
  diskLastCleanupAt2 = now;
11741
12207
  const clockPath = getGlobalClockPath();
11742
- await fs36.mkdir(path38.dirname(clockPath), { recursive: true });
11743
- await fs36.writeFile(clockPath, JSON.stringify({ lastCleanupAt: now, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), "utf-8");
12208
+ await fs38.mkdir(path40.dirname(clockPath), { recursive: true });
12209
+ await fs38.writeFile(clockPath, JSON.stringify({ lastCleanupAt: now, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), "utf-8");
11744
12210
  if (deletedCount > 0) {
11745
12211
  console.log(`\u2728 [Trace\u8FD0\u7EF4] \u5171\u9500\u6BC1 ${deletedCount} \u4E2A\u4F1A\u8BDD\u65E5\u5FD7\uFF08\u8F6F\u89C4\u5219\u8FC7\u671F\u6E05\u7406 + \u5BB9\u91CF\u786C\u9A71\u9010\uFF09\uFF0C\u78C1\u76D8\u6C34\u4F4D\u56DE\u843D\u3002`);
11746
12212
  }
@@ -11760,22 +12226,22 @@ async function cleanupOldTraceFiles() {
11760
12226
  let deleted = 0;
11761
12227
  let relativePaths;
11762
12228
  try {
11763
- relativePaths = await fs36.readdir(dir, { recursive: true });
12229
+ relativePaths = await fs38.readdir(dir, { recursive: true });
11764
12230
  } catch {
11765
12231
  return 0;
11766
12232
  }
11767
12233
  const re = /^trace-(\d{4}-\d{2}-\d{2})__([\w-]+)\.jsonl$/;
11768
12234
  for (const relativePath of relativePaths) {
11769
- const fileName = path38.basename(relativePath);
12235
+ const fileName = path40.basename(relativePath);
11770
12236
  const m = fileName.match(re);
11771
12237
  if (!m) continue;
11772
12238
  const fileBornDay = /* @__PURE__ */ new Date(`${m[1]}T00:00:00`);
11773
12239
  if (fileBornDay >= cutoff) continue;
11774
- const fullPath = path38.join(dir, relativePath);
12240
+ const fullPath = path40.join(dir, relativePath);
11775
12241
  try {
11776
- const st = await fs36.stat(fullPath);
12242
+ const st = await fs38.stat(fullPath);
11777
12243
  if (now - st.mtimeMs < ACTIVE_GRACE_MS2) continue;
11778
- await fs36.unlink(fullPath);
12244
+ await fs38.unlink(fullPath);
11779
12245
  deleted += 1;
11780
12246
  } catch {
11781
12247
  }
@@ -11786,18 +12252,18 @@ async function evictOldestForSize2(targetBytes) {
11786
12252
  const dir = getTracePath();
11787
12253
  let relativePaths;
11788
12254
  try {
11789
- relativePaths = await fs36.readdir(dir, { recursive: true });
12255
+ relativePaths = await fs38.readdir(dir, { recursive: true });
11790
12256
  } catch {
11791
12257
  return 0;
11792
12258
  }
11793
12259
  const re = /^trace-(\d{4}-\d{2}-\d{2})__([\w-]+)\.jsonl$/;
11794
12260
  const candidates = [];
11795
12261
  for (const relativePath of relativePaths) {
11796
- const fileName = path38.basename(relativePath);
12262
+ const fileName = path40.basename(relativePath);
11797
12263
  if (!re.test(fileName)) continue;
11798
12264
  try {
11799
- const st = await fs36.stat(path38.join(dir, relativePath));
11800
- candidates.push({ mtime: st.mtimeMs, fullPath: path38.join(dir, relativePath), size: st.size });
12265
+ const st = await fs38.stat(path40.join(dir, relativePath));
12266
+ candidates.push({ mtime: st.mtimeMs, fullPath: path40.join(dir, relativePath), size: st.size });
11801
12267
  } catch {
11802
12268
  }
11803
12269
  }
@@ -11807,7 +12273,7 @@ async function evictOldestForSize2(targetBytes) {
11807
12273
  for (const c of candidates) {
11808
12274
  if (totalBytes <= targetBytes) break;
11809
12275
  try {
11810
- await fs36.unlink(c.fullPath);
12276
+ await fs38.unlink(c.fullPath);
11811
12277
  totalBytes -= c.size;
11812
12278
  deleted += 1;
11813
12279
  } catch {
@@ -11839,7 +12305,7 @@ var init_trace = __esm({
11839
12305
  if (clockLoaded2) return;
11840
12306
  clockLoaded2 = true;
11841
12307
  try {
11842
- const rawClock = await fs36.readFile(getGlobalClockPath(), "utf-8");
12308
+ const rawClock = await fs38.readFile(getGlobalClockPath(), "utf-8");
11843
12309
  diskLastCleanupAt2 = JSON.parse(rawClock).lastCleanupAt || 0;
11844
12310
  } catch {
11845
12311
  diskLastCleanupAt2 = 0;
@@ -11985,8 +12451,8 @@ ${PLAN_MODE_AUTO_ENTER_HINT}
11985
12451
  });
11986
12452
 
11987
12453
  // ../core/src/serve/chatProcessing.ts
11988
- import path39 from "path";
11989
- import fs37 from "fs";
12454
+ import path41 from "path";
12455
+ import fs39 from "fs";
11990
12456
  var handleUnifiedChat;
11991
12457
  var init_chatProcessing = __esm({
11992
12458
  "../core/src/serve/chatProcessing.ts"() {
@@ -11998,6 +12464,7 @@ var init_chatProcessing = __esm({
11998
12464
  init_store();
11999
12465
  init_transcript();
12000
12466
  init_content();
12467
+ init_contentParts();
12001
12468
  init_trace();
12002
12469
  init_webHost();
12003
12470
  init_registry();
@@ -12040,23 +12507,29 @@ ${inject}`;
12040
12507
  if (roots.length > 1) {
12041
12508
  let activeReal = getActiveWorkspaceRoot();
12042
12509
  try {
12043
- activeReal = fs37.realpathSync(activeReal);
12510
+ activeReal = fs39.realpathSync(activeReal);
12044
12511
  } catch {
12045
12512
  }
12046
- const lines = roots.map((r) => `- ${path39.basename(r) || r}: ${r}${r === activeReal ? "\uFF08\u9ED8\u8BA4\u6839\uFF1A\u76F8\u5BF9\u8DEF\u5F84/\u547D\u4EE4\u57FA\u51C6\uFF09" : "\uFF08\u53EF\u76F4\u63A5\u8BFB\u5199\uFF09"}`);
12513
+ const lines = roots.map((r) => `- ${path41.basename(r) || r}: ${r}${r === activeReal ? "\uFF08\u9ED8\u8BA4\u6839\uFF1A\u76F8\u5BF9\u8DEF\u5F84/\u547D\u4EE4\u57FA\u51C6\uFF09" : "\uFF08\u53EF\u76F4\u63A5\u8BFB\u5199\uFF09"}`);
12047
12514
  sysPrompt = sysPrompt + `
12048
12515
 
12049
12516
  \u3010\u5DE5\u4F5C\u533A\uFF08\u591A\u9879\u76EE\uFF09\u3011
12050
12517
  \u65E0\u9700\u5207\u6362\u9879\u76EE\uFF0C\u4EE5\u4E0B\u6240\u6709\u6839\u90FD\u53EF\u76F4\u63A5\u7528\u7EDD\u5BF9\u8DEF\u5F84\u8BBF\u95EE\uFF1B\u8DE8\u9879\u76EE\u7528\u7EDD\u5BF9\u8DEF\u5F84\u6216\u76F8\u5BF9\u9ED8\u8BA4\u6839\u7684 ../<\u5144\u5F1F\u76EE\u5F55>:
12051
12518
  ${lines.join("\n")}
12052
- \u2605 \u8DEF\u5F84\u951A\u5B9A\u94C1\u5F8B\uFF1A\u5DE5\u5177\u7F3A\u7701\u53EA\u4F5C\u7528\u4E8E\u9ED8\u8BA4\u6839\uFF08${path39.basename(activeReal) || activeReal} = IDE \u5934\u90E8\u6240\u793A\u6839\uFF09\u3002\u5F53\u7528\u6237\u5728\u5BF9\u8BDD\u4E2D\u6307\u660E\u4E86\u67D0\u4E2A\u9879\u76EE\uFF08\u5982\u300C\u5728 A \u9879\u76EE\u300D\u300C\u6539\u524D\u7AEF\u7684 xxx\u300D\u300C\u641C\u540E\u7AEF\u300D\uFF09\uFF0C\u8BE5\u8F6E\u6240\u6709\u5E26 path \u53C2\u6570\u7684\u5DE5\u5177\u8C03\u7528\uFF08read_file / edit_file / search_grep / glob / list_dir / read_xlsx / read_docx \u7B49\uFF09\u5FC5\u987B\u628A\u5BF9\u5E94\u9879\u76EE\u6839\u4F5C\u4E3A path \u4F20\u5165\uFF08\u7EDD\u5BF9\u8DEF\u5F84\u3001../<\u5144\u5F1F\u76EE\u5F55>\u3001\u6216\u9879\u76EE\u76EE\u5F55\u540D\uFF09\uFF0C\u5426\u5219\u53EA\u4F1A\u8BFB\u5199/\u641C\u7D22\u9ED8\u8BA4\u6839\uFF0C\u4E0E\u7528\u6237\u610F\u56FE\u4E0D\u7B26\u3002
12053
- \u6CE8\u610F\uFF1A\u9879\u76EE\u7EA7\u914D\u7F6E\uFF08.deepseeker-code/\uFF09\u4EC5\u5728\u9ED8\u8BA4\u6839 ${path39.basename(activeReal) || activeReal} \u751F\u6548\u3002`;
12519
+ \u2605 \u8DEF\u5F84\u951A\u5B9A\u94C1\u5F8B\uFF1A\u5DE5\u5177\u7F3A\u7701\u53EA\u4F5C\u7528\u4E8E\u9ED8\u8BA4\u6839\uFF08${path41.basename(activeReal) || activeReal} = IDE \u5934\u90E8\u6240\u793A\u6839\uFF09\u3002\u5F53\u7528\u6237\u5728\u5BF9\u8BDD\u4E2D\u6307\u660E\u4E86\u67D0\u4E2A\u9879\u76EE\uFF08\u5982\u300C\u5728 A \u9879\u76EE\u300D\u300C\u6539\u524D\u7AEF\u7684 xxx\u300D\u300C\u641C\u540E\u7AEF\u300D\uFF09\uFF0C\u8BE5\u8F6E\u6240\u6709\u5E26 path \u53C2\u6570\u7684\u5DE5\u5177\u8C03\u7528\uFF08read_file / edit_file / search_grep / glob / list_dir / read_xlsx / read_docx \u7B49\uFF09\u5FC5\u987B\u628A\u5BF9\u5E94\u9879\u76EE\u6839\u4F5C\u4E3A path \u4F20\u5165\uFF08\u7EDD\u5BF9\u8DEF\u5F84\u3001../<\u5144\u5F1F\u76EE\u5F55>\u3001\u6216\u9879\u76EE\u76EE\u5F55\u540D\uFF09\uFF0C\u5426\u5219\u53EA\u4F1A\u8BFB\u5199/\u641C\u7D22\u9ED8\u8BA4\u6839\uFF0C\u4E0E\u7528\u6237\u610F\u56FE\u4E0D\u7B26\u3002
12520
+ \u6CE8\u610F\uFF1A\u9879\u76EE\u7EA7\u914D\u7F6E\uFF08.deepseeker-code/\uFF09\u4EC5\u5728\u9ED8\u8BA4\u6839 ${path41.basename(activeReal) || activeReal} \u751F\u6548\u3002`;
12054
12521
  }
12055
12522
  let replyText = "";
12523
+ let userContent = inbound.content;
12524
+ if (Array.isArray(inbound.attachments) && inbound.attachments.length > 0) {
12525
+ userContent = isVisionEnabled() ? toWireUserContent(inbound.content, inbound.attachments) : `${inbound.content}
12526
+
12527
+ \u{1F5BC} \u6536\u5230\u56FE\u7247\u9644\u4EF6\uFF08${inbound.attachments.map((a) => a?.name || "image").join("\u3001")}\uFF09\uFF1A\u5F53\u524D\u6A21\u578B\u672A\u5F00\u542F\u89C6\u89C9\u80FD\u529B\uFF08DEEP_SEEK_VISION\uFF09\uFF0C\u5DF2\u5FFD\u7565\u56FE\u7247\u5185\u5BB9\u3002`;
12528
+ }
12056
12529
  try {
12057
12530
  const fullMessages = await buildContextMessages(
12058
12531
  sessionId,
12059
- { role: "user", content: inbound.content },
12532
+ { role: "user", content: userContent },
12060
12533
  sysPrompt
12061
12534
  );
12062
12535
  await emitTrace({
@@ -12069,7 +12542,7 @@ ${lines.join("\n")}
12069
12542
  });
12070
12543
  void sweepStaleAtomicTmp().catch(() => {
12071
12544
  });
12072
- await appendMessage({ sessionId, role: "user", content: inbound.content });
12545
+ await appendMessage({ sessionId, role: "user", content: userContent });
12073
12546
  let streamedAnyText = false;
12074
12547
  const onUIEvent = opts?.onUIEvent ?? ((evt) => sseWrite?.(evt));
12075
12548
  const requestApproval2 = opts?.requestApproval ?? createWebRequestApproval((evt) => sseWrite?.(evt), abortSignal);
@@ -12137,12 +12610,13 @@ ${lines.join("\n")}
12137
12610
  });
12138
12611
 
12139
12612
  // ../core/src/session/fork.ts
12140
- import fs38 from "fs/promises";
12613
+ import fs40 from "fs/promises";
12141
12614
  var listForkAnchors, derivePrefix, forkSession;
12142
12615
  var init_fork = __esm({
12143
12616
  "../core/src/session/fork.ts"() {
12144
12617
  "use strict";
12145
12618
  init_common();
12619
+ init_contentParts();
12146
12620
  init_transcript();
12147
12621
  init_store();
12148
12622
  listForkAnchors = (lines) => {
@@ -12152,7 +12626,7 @@ var init_fork = __esm({
12152
12626
  if (isEventLine(l)) continue;
12153
12627
  const row = l;
12154
12628
  if (row?.role === "user") {
12155
- lastUser = typeof row.content === "string" ? row.content : "";
12629
+ lastUser = msgText(row.content) || (hasImagePart(row.content) ? "\u{1F5BC} [\u56FE\u7247\u6D88\u606F]" : "");
12156
12630
  } else if (row?.role === "assistant") {
12157
12631
  const content = typeof row.content === "string" ? row.content : "";
12158
12632
  const names = Array.isArray(row.tool_calls) ? row.tool_calls.map((tc) => tc?.function?.name ?? "").filter(Boolean).join(", ") : "";
@@ -12209,7 +12683,7 @@ var init_fork = __esm({
12209
12683
  const prefix = derivePrefix(lines, upToLineId);
12210
12684
  const newId = createUUID();
12211
12685
  await ensureSessionsDir(newId);
12212
- const rawText = await fs38.readFile(getTranscriptPath(sourceId), "utf-8");
12686
+ const rawText = await fs40.readFile(getTranscriptPath(sourceId), "utf-8");
12213
12687
  const kept = [];
12214
12688
  let good = 0;
12215
12689
  for (const s of rawText.split("\n")) {
@@ -12225,7 +12699,7 @@ var init_fork = __esm({
12225
12699
  }
12226
12700
  let payload = kept.join("\n");
12227
12701
  if (payload.length > 0 && !payload.endsWith("\n")) payload += "\n";
12228
- await fs38.writeFile(getTranscriptPath(newId), payload, "utf-8");
12702
+ await fs40.writeFile(getTranscriptPath(newId), payload, "utf-8");
12229
12703
  const srcState = await readStore(sourceId);
12230
12704
  const srcRolling = await getRollingState(sourceId);
12231
12705
  const legacy = !lines.some(isEventLine);
@@ -13924,7 +14398,7 @@ var init_TopPanel = __esm({
13924
14398
  });
13925
14399
 
13926
14400
  // ../core/src/observability/inspect.ts
13927
- import fs39 from "fs";
14401
+ import fs41 from "fs";
13928
14402
  var CWD, fmt, readTrace, inspectUsage, inspectContext, inspectPermissions, inspectMcp, inspectHooks, inspectDebug;
13929
14403
  var init_inspect = __esm({
13930
14404
  "../core/src/observability/inspect.ts"() {
@@ -13942,7 +14416,7 @@ var init_inspect = __esm({
13942
14416
  if (!sessionId) return [];
13943
14417
  try {
13944
14418
  const p = await getTraceStorePath(sessionId);
13945
- const text = await fs39.promises.readFile(p, "utf8");
14419
+ const text = await fs41.promises.readFile(p, "utf8");
13946
14420
  const out = [];
13947
14421
  for (const line of text.split("\n")) {
13948
14422
  if (!line.trim()) continue;
@@ -14701,7 +15175,7 @@ init_updateCheck();
14701
15175
  init_trust();
14702
15176
  import { render } from "ink";
14703
15177
  import os5 from "os";
14704
- import path40 from "path";
15178
+ import path42 from "path";
14705
15179
  import { emitKeypressEvents } from "node:readline";
14706
15180
  import { jsx as jsx18 } from "react/jsx-runtime";
14707
15181
  var parseArgs = (argv) => {
@@ -14834,7 +15308,7 @@ var main = async () => {
14834
15308
  process.stdout.write("\x1B[2J\x1B[3J\x1B[1;1H");
14835
15309
  const cwdIsHome = () => {
14836
15310
  try {
14837
- return path40.resolve(cwd).toLowerCase() === path40.resolve(os5.homedir()).toLowerCase();
15311
+ return path42.resolve(cwd).toLowerCase() === path42.resolve(os5.homedir()).toLowerCase();
14838
15312
  } catch {
14839
15313
  return false;
14840
15314
  }