@zqagent/core 0.2.4 → 0.2.6

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/index.cjs CHANGED
@@ -471,8 +471,11 @@ function formatTokens(tokens) {
471
471
  return `${tokens}`;
472
472
  }
473
473
  var KEEP_RECENT_FRACTION = 0.25;
474
- var SPAN_TOOL_RESULT_CLIP = 400;
475
- var SPAN_BUDGET_CHARS = 4e5;
474
+ var SOFT_THRESHOLD_RATIO = 0.75;
475
+ var ELIDE_KEEP_FRACTION = 0.15;
476
+ var SUMMARY_MAX_TOKENS = 3e3;
477
+ var SPAN_TOOL_RESULT_CLIP = 200;
478
+ var SPAN_BUDGET_CHARS = 8e4;
476
479
  var USER_MESSAGE_CLIP = 600;
477
480
  var USER_MESSAGES_MAX = 40;
478
481
  var TRIM_FRACTION = 0.1;
@@ -495,9 +498,22 @@ function triggerTokens(contextWindow, thresholdPct = DEFAULT_THRESHOLD_PCT, capT
495
498
  }
496
499
  return base;
497
500
  }
501
+ function softTriggerTokens(contextWindow, thresholdPct = DEFAULT_THRESHOLD_PCT, capTokens, softThresholdPct) {
502
+ const hard = triggerTokens(contextWindow, thresholdPct, capTokens);
503
+ if (softThresholdPct != null && Number.isFinite(softThresholdPct) && softThresholdPct > 0) {
504
+ return Math.min(
505
+ triggerTokens(contextWindow, softThresholdPct, capTokens),
506
+ Math.floor(hard * 0.95)
507
+ );
508
+ }
509
+ return Math.floor(hard * SOFT_THRESHOLD_RATIO);
510
+ }
498
511
  function shouldCompact(signal, contextWindow, thresholdPct, capTokens) {
499
512
  return signal >= triggerTokens(contextWindow, thresholdPct, capTokens);
500
513
  }
514
+ function elisionKeepChars(hardTriggerTokens, fraction = ELIDE_KEEP_FRACTION) {
515
+ return Math.max(4e3, Math.floor(hardTriggerTokens * fraction * 4));
516
+ }
501
517
  function turnStarts(messages, start) {
502
518
  const users = [];
503
519
  const assistants = [];
@@ -647,10 +663,11 @@ var SUMMARY_SYSTEM_PROMPT = `\u4F60\u6B63\u5728\u538B\u7F29\u4E00\u4E2A AI \u534
647
663
 
648
664
  \u89C4\u5219\uFF1A
649
665
  - \u4E0D\u8981\u5C06\u5B8C\u6574\u7684\u6587\u4EF6\u5185\u5BB9\u5F53\u4F5C\u771F\u7406\u3002\u53EA\u8BB0\u5F55\u67D0\u4E2A\u6587\u4EF6\u88AB\u8BFB\u53D6/\u7F16\u8F91\u8FC7\u8FD9\u4E00\u4E8B\u5B9E\uFF1B\u534F\u4F5C\u8005\u5982\u679C\u9700\u8981\u518D\u6B21\u83B7\u53D6\u5185\u5BB9\u4F1A\u91CD\u65B0\u8BFB\u53D6\u3002\u9648\u65E7\u7684\u6587\u4EF6\u8BB0\u5FC6\u6BD4\u6CA1\u6709\u8BB0\u5FC6\u66F4\u7CDF\u7CD5\u3002
666
+ - \u82E5\u63D0\u4F9B\u4E86\u4E0A\u4E00\u7248\u6458\u8981\uFF1A\u53EA\u628A\u5B83\u4ECD\u7136\u6709\u6548\u7684\u7ED3\u8BBA\u5E76\u5165\u5BF9\u5E94\u7AE0\u8282\uFF0C\u4E0D\u8981\u6574\u6BB5\u590D\u8FF0\u6216\u9010\u6761\u8F6C\u6284\u3002
650
667
  - \u8981\u5177\u4F53\uFF1A\u8DEF\u5F84\u3001\u540D\u79F0\u3001\u547D\u4EE4\u3001id\u2014\u2014\u800C\u4E0D\u662F\u6A21\u7CCA\u7684\u5F15\u7528\u3002
651
668
  - \u4EC5\u8F93\u51FA\u6458\u8981\u7AE0\u8282\uFF0C\u4E0D\u9644\u52A0\u524D\u8A00\u3002`;
652
669
  var CONTINUATION_CONTRACT = "\u4ECE\u4F60\u4E4B\u524D\u4E2D\u65AD\u7684\u5730\u65B9\u7EE7\u7EED\uFF1A\u5B8C\u5168\u6309\u7167\u63CF\u8FF0\u63A5\u7EED\u5F53\u524D\u5DE5\u4F5C\u548C\u4E0B\u4E00\u6B65\u3002\u4E0D\u8981\u91CD\u65B0\u8BE2\u95EE\u5DF2\u56DE\u7B54\u7684\u95EE\u9898\uFF0C\u4E0D\u8981\u56DE\u987E\uFF0C\u4E0D\u8981\u63D0\u53CA\u4E0A\u4E0B\u6587\u5DF2\u88AB\u538B\u7F29\u3002\u5982\u679C\u4F60\u9700\u8981\u4E0A\u8FF0\u63D0\u5230\u7684\u6587\u4EF6\u7684\u5185\u5BB9\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6\u5B83\u3002";
653
- function renderSpan(span, budgetChars = SPAN_BUDGET_CHARS) {
670
+ function renderSpan(span, budgetChars2 = SPAN_BUDGET_CHARS) {
654
671
  const lines = [];
655
672
  for (const m of span) {
656
673
  const role = m.role;
@@ -674,8 +691,8 @@ function renderSpan(span, budgetChars = SPAN_BUDGET_CHARS) {
674
691
  if (text) lines.push(`[${role}] ${text}`);
675
692
  }
676
693
  let rendered = lines.join("\n");
677
- if (rendered.length > budgetChars) {
678
- rendered = "(\u2026oldest turns elided\u2026)\n" + rendered.slice(-budgetChars);
694
+ if (rendered.length > budgetChars2) {
695
+ rendered = "(\u2026oldest turns elided\u2026)\n" + rendered.slice(-budgetChars2);
679
696
  }
680
697
  return rendered;
681
698
  }
@@ -689,9 +706,17 @@ function summarizerMessages(span, priorSummary = "") {
689
706
  { role: "user", content: body }
690
707
  ];
691
708
  }
709
+ function summarizerReasoningEffort() {
710
+ const v = process.env.COMPACTION_REASONING_EFFORT?.toLowerCase();
711
+ if (v === "low" || v === "medium" || v === "high") return v;
712
+ return "low";
713
+ }
692
714
  async function summarizeSpan(provider, model, span, priorSummary = "", opts) {
693
715
  const msgs = summarizerMessages(span, priorSummary);
694
- const chunks = await provider.stream(model, msgs, [], opts);
716
+ const chunks = await provider.stream(model, msgs, [], {
717
+ ...opts,
718
+ maxTokens: SUMMARY_MAX_TOKENS
719
+ });
695
720
  let text = "";
696
721
  for await (const chunk of chunks) {
697
722
  if (chunk.content) text += chunk.content;
@@ -802,6 +827,166 @@ function applyToOutbound(messages, state) {
802
827
  head.push({ role: "user", content: compactedBlock(state) });
803
828
  return [...head, ...messages.slice(boundary)];
804
829
  }
830
+ var ELIDE_EXEMPT_TOOLS = /* @__PURE__ */ new Set([
831
+ "explore",
832
+ "ask_user",
833
+ "propose_plan",
834
+ "request_directory",
835
+ "todo_write"
836
+ ]);
837
+ function elidedToolContent(m) {
838
+ const name = m.tool_call_name || "tool";
839
+ const size = m.content.length;
840
+ const saved = /full output saved to ([^\]]+)/.exec(m.content)?.[1]?.trim();
841
+ return saved ? `[old tool result elided: ${name}, ${size} chars; full output saved to ${saved}]` : `[old tool result elided: ${name}, ${size} chars; re-run the tool if its content is still needed]`;
842
+ }
843
+ function isPlaceholder(content) {
844
+ return content.startsWith("[old tool result elided") || content.startsWith("[superseded by a later read_file");
845
+ }
846
+ var ONE_SHOT_TOOLS = /* @__PURE__ */ new Set(["git_diff"]);
847
+ var ONE_SHOT_KEEP_RECENT = 1;
848
+ function applyOneShotElision(messages, keepRecent = ONE_SHOT_KEEP_RECENT, tools = ONE_SHOT_TOOLS) {
849
+ const out = [...messages];
850
+ let seen = 0;
851
+ let elided = 0;
852
+ let freed = 0;
853
+ for (let i = messages.length - 1; i >= 0; i--) {
854
+ const m = messages[i];
855
+ if (m.role !== "tool" || !tools.has(m.tool_call_name ?? "")) continue;
856
+ if (isPlaceholder(m.content)) continue;
857
+ seen++;
858
+ if (seen <= keepRecent) continue;
859
+ const placeholder = elidedToolContent(m);
860
+ freed += m.content.length - placeholder.length;
861
+ out[i] = { ...m, content: placeholder };
862
+ elided++;
863
+ }
864
+ return elided > 0 ? { messages: out, elided, freedChars: freed } : { messages, elided: 0, freedChars: 0 };
865
+ }
866
+ function applyToolResultElision(messages, keepChars, exempt = ELIDE_EXEMPT_TOOLS) {
867
+ let total = 0;
868
+ for (const m of messages) {
869
+ if (m.role === "tool" && !exempt.has(m.tool_call_name ?? "")) {
870
+ total += m.content.length;
871
+ }
872
+ }
873
+ if (total <= keepChars) return { messages, elided: 0, freedChars: 0 };
874
+ const out = [...messages];
875
+ let budget = 0;
876
+ let elided = 0;
877
+ let freed = 0;
878
+ for (let i = messages.length - 1; i >= 0; i--) {
879
+ const m = messages[i];
880
+ if (m.role !== "tool") continue;
881
+ if (exempt.has(m.tool_call_name ?? "")) continue;
882
+ if (elided === 0 && budget === 0 && m.content.length > keepChars) {
883
+ const kept = m.content.slice(0, keepChars) + "\n\u2026(truncated)";
884
+ freed += m.content.length - kept.length;
885
+ out[i] = { ...m, content: kept };
886
+ budget = keepChars;
887
+ continue;
888
+ }
889
+ if (budget + m.content.length <= keepChars) {
890
+ budget += m.content.length;
891
+ continue;
892
+ }
893
+ const placeholder = elidedToolContent(m);
894
+ freed += m.content.length - placeholder.length;
895
+ out[i] = { ...m, content: placeholder };
896
+ elided++;
897
+ }
898
+ return { messages: out, elided, freedChars: freed };
899
+ }
900
+ function normalizeToolPath(p) {
901
+ let s = p.trim().replace(/\\/g, "/");
902
+ while (s.startsWith("./")) s = s.slice(2);
903
+ return s.replace(/\/{2,}/g, "/");
904
+ }
905
+ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
906
+ "write_file",
907
+ "replace_in_file",
908
+ "apply_patch",
909
+ "apply_unified_diff",
910
+ "run_shell"
911
+ ]);
912
+ function parseReadFileSpan(content) {
913
+ const body = content.replace(/^\[tool output truncated:[^\n]*\]\n\n/, "");
914
+ let path16;
915
+ let start;
916
+ let end;
917
+ try {
918
+ const obj = JSON.parse(body);
919
+ path16 = obj.path;
920
+ start = obj.start_line;
921
+ end = obj.end_line;
922
+ } catch {
923
+ const str2 = (key) => new RegExp(`"${key}"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)"`).exec(body)?.[1];
924
+ const num = (key) => {
925
+ const m = new RegExp(`"${key}"\\s*:\\s*(\\d+)`).exec(body);
926
+ return m ? Number(m[1]) : void 0;
927
+ };
928
+ path16 = str2("path");
929
+ start = num("start_line");
930
+ end = num("end_line");
931
+ }
932
+ if (typeof path16 !== "string" || !path16) return null;
933
+ if (typeof start !== "number" || typeof end !== "number") return null;
934
+ if (!Number.isFinite(start) || !Number.isFinite(end)) return null;
935
+ return { path: path16, start, end };
936
+ }
937
+ function addSpan(spans, start, end) {
938
+ spans.push([start, end]);
939
+ spans.sort((a, b) => a[0] - b[0]);
940
+ const merged = [];
941
+ for (const cur of spans) {
942
+ const last = merged[merged.length - 1];
943
+ if (last && cur[0] <= last[1] + 1) {
944
+ last[1] = Math.max(last[1], cur[1]);
945
+ } else {
946
+ merged.push([...cur]);
947
+ }
948
+ }
949
+ spans.length = 0;
950
+ spans.push(...merged);
951
+ }
952
+ function coversSpans(spans, start, end) {
953
+ return spans.some(([a, b]) => a <= start && b >= end);
954
+ }
955
+ function applyReadFileDedup(messages) {
956
+ let candidates = 0;
957
+ for (const m of messages) {
958
+ if (m.role === "tool" && m.tool_call_name === "read_file") candidates++;
959
+ }
960
+ if (candidates < 2) return { messages, elided: 0 };
961
+ const out = [...messages];
962
+ const covered = /* @__PURE__ */ new Map();
963
+ let elided = 0;
964
+ for (let i = messages.length - 1; i >= 0; i--) {
965
+ const m = messages[i];
966
+ if (m.role === "assistant") {
967
+ if (m.tool_calls?.some((tc) => MUTATING_TOOLS.has(tc.function.name))) {
968
+ covered.clear();
969
+ }
970
+ continue;
971
+ }
972
+ if (m.role !== "tool" || m.tool_call_name !== "read_file") continue;
973
+ const span = parseReadFileSpan(m.content);
974
+ if (!span) continue;
975
+ const key = normalizeToolPath(span.path);
976
+ const seen = covered.get(key) ?? [];
977
+ if (coversSpans(seen, span.start, span.end)) {
978
+ out[i] = {
979
+ ...m,
980
+ content: `[superseded by a later read_file of ${span.path} (lines ${span.start}-${span.end})]`
981
+ };
982
+ elided++;
983
+ continue;
984
+ }
985
+ addSpan(seen, span.start, span.end);
986
+ covered.set(key, seen);
987
+ }
988
+ return { messages: elided > 0 ? out : messages, elided };
989
+ }
805
990
  var OVERFLOW_MARKERS = [
806
991
  "context_length_exceeded",
807
992
  "maximum context length",
@@ -817,9 +1002,108 @@ function isContextOverflow(exc) {
817
1002
  return OVERFLOW_MARKERS.some((m) => text.includes(m));
818
1003
  }
819
1004
 
1005
+ // src/engine/toolResultBudget.ts
1006
+ var fs = __toESM(require("fs"), 1);
1007
+ var path2 = __toESM(require("path"), 1);
1008
+
1009
+ // src/paths.ts
1010
+ var os = __toESM(require("os"), 1);
1011
+ var path = __toESM(require("path"), 1);
1012
+ var APP_DIR_NAME = "zqagent";
1013
+ var IS_WINDOWS = process.platform === "win32";
1014
+ function homeDir() {
1015
+ return process.env.HOME || process.env.USERPROFILE || os.homedir() || os.tmpdir();
1016
+ }
1017
+ function stateDir() {
1018
+ const base = process.env.XDG_STATE_HOME || (IS_WINDOWS ? process.env.LOCALAPPDATA : void 0) || path.join(homeDir(), ".local", "state");
1019
+ return path.join(base, APP_DIR_NAME);
1020
+ }
1021
+
1022
+ // src/engine/toolResultBudget.ts
1023
+ var DEFAULT_TOOL_RESULT_MAX_CHARS = 24e3;
1024
+ var HEAD_RATIO = 0.6;
1025
+ function budgetChars() {
1026
+ const n = Number(process.env.TOOL_RESULT_MAX_CHARS);
1027
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : DEFAULT_TOOL_RESULT_MAX_CHARS;
1028
+ }
1029
+ function toolResultDir() {
1030
+ const env = process.env.TOOL_RESULT_DIR?.trim();
1031
+ return env || path2.join(stateDir(), "tool-results");
1032
+ }
1033
+ function clipText(text, budget) {
1034
+ if (text.length <= budget) return text;
1035
+ const room = Math.max(
1036
+ 0,
1037
+ budget - `
1038
+
1039
+ \u2026 [${text.length} characters omitted] \u2026
1040
+
1041
+ `.length
1042
+ );
1043
+ const headLen = Math.floor(room * HEAD_RATIO);
1044
+ const tailLen = room - headLen;
1045
+ const omitted = text.length - headLen - tailLen;
1046
+ return text.slice(0, headLen) + `
1047
+
1048
+ \u2026 [${omitted} characters omitted] \u2026
1049
+
1050
+ ` + text.slice(-tailLen);
1051
+ }
1052
+ function clipDeep(value, perField) {
1053
+ if (typeof value === "string") {
1054
+ return value.length <= perField ? value : clipText(value, perField);
1055
+ }
1056
+ if (Array.isArray(value)) {
1057
+ return value.map((v) => clipDeep(v, perField));
1058
+ }
1059
+ if (value && typeof value === "object") {
1060
+ const out = {};
1061
+ for (const [k, v] of Object.entries(value)) out[k] = clipDeep(v, perField);
1062
+ return out;
1063
+ }
1064
+ return value;
1065
+ }
1066
+ function saveRaw(tool, raw, dir) {
1067
+ try {
1068
+ fs.mkdirSync(dir, { recursive: true });
1069
+ const safe = tool.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 40) || "tool";
1070
+ const name = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}-${safe}.txt`;
1071
+ const file = path2.join(dir, name);
1072
+ fs.writeFileSync(file, raw, "utf-8");
1073
+ return file;
1074
+ } catch {
1075
+ return void 0;
1076
+ }
1077
+ }
1078
+ function applyToolResultBudget(tool, result, opts = {}) {
1079
+ const budget = opts.maxChars ?? budgetChars();
1080
+ const raw = typeof result === "string" ? result : JSON.stringify(result, null, 2) ?? String(result);
1081
+ if (raw.length <= budget) {
1082
+ return { content: raw, truncated: false };
1083
+ }
1084
+ let content = raw;
1085
+ if (typeof result === "object" && result !== null) {
1086
+ let perField = Math.floor(budget * 0.7);
1087
+ let clipped = JSON.stringify(clipDeep(result, perField), null, 2);
1088
+ for (let i = 0; i < 6 && clipped && clipped.length > budget && perField > 128; i++) {
1089
+ perField = Math.floor(perField / 2);
1090
+ clipped = JSON.stringify(clipDeep(result, perField), null, 2);
1091
+ }
1092
+ if (clipped && clipped.length <= budget) content = clipped;
1093
+ }
1094
+ if (content.length > budget) content = clipText(content, budget);
1095
+ const savedPath = saveRaw(tool, raw, opts.dir ?? toolResultDir());
1096
+ if (savedPath) {
1097
+ content = `[tool output truncated: ${raw.length} chars, full output saved to ${savedPath}]
1098
+
1099
+ ` + content;
1100
+ }
1101
+ return { content, truncated: true, savedPath };
1102
+ }
1103
+
820
1104
  // src/engine/TurnEngine.ts
821
1105
  var crypto = __toESM(require("crypto"), 1);
822
- var fs = __toESM(require("fs"), 1);
1106
+ var fs2 = __toESM(require("fs"), 1);
823
1107
  var _path = __toESM(require("path"), 1);
824
1108
 
825
1109
  // src/prompts/index.ts
@@ -946,13 +1230,17 @@ var INTERACTIVE_TOOLS = /* @__PURE__ */ new Set([
946
1230
  "propose_plan",
947
1231
  "request_directory"
948
1232
  ]);
949
- function toolResultMessage(tc, result, taskId) {
950
- const content = typeof result === "string" ? result : JSON.stringify(result, null, 2);
1233
+ var MAX_COMPACT_ROUNDS = 4;
1234
+ var OVERFLOW_TARGET_RATIO = 0.5;
1235
+ var OVERFLOW_TRIM_FRACTION = 0.5;
1236
+ var COMPACT_STALL_RETRY_DELTA = 20;
1237
+ function toolResultMessage(tc, result, taskId, toolResultDir2) {
1238
+ const budgeted = applyToolResultBudget(tc.name, result, { dir: toolResultDir2 });
951
1239
  return {
952
1240
  role: "tool",
953
1241
  tool_call_id: tc.id,
954
1242
  tool_call_name: tc.name,
955
- content,
1243
+ content: budgeted.content,
956
1244
  taskId
957
1245
  };
958
1246
  }
@@ -1063,12 +1351,22 @@ var TurnEngine = class _TurnEngine {
1063
1351
  // 上下文压缩
1064
1352
  compactionState = null;
1065
1353
  _lastContextTokens = null;
1354
+ /** 压缩无进展时的消息条数快照;-1 表示无冷却 */
1355
+ _compactStalledAt = -1;
1066
1356
  /** 显式配置的上下文窗口大小(token 数),未设置时为 undefined(走自动检测) */
1067
1357
  _contextWindow;
1068
1358
  /** 压缩触发阈值比例(默认 0.8) */
1069
1359
  _thresholdPct;
1360
+ /** 软阈值比例(undefined = 取硬阈值的 75%) */
1361
+ _softThresholdPct;
1362
+ /** 是否启用软阈值淘汰(COMPACTION_ELIDE=0 可关闭) */
1363
+ _elisionEnabled;
1364
+ /** 是否启用重复读取去重(COMPACTION_DEDUP=0 可关闭) */
1365
+ _dedupEnabled;
1070
1366
  /** 压缩触发阈值绝对上限(null = 不限制) */
1071
1367
  _capTokens;
1368
+ /** 超预算工具结果的落盘目录(undefined = 使用默认状态目录) */
1369
+ _toolResultDir;
1072
1370
  // 产出文件追踪(本轮 Turn 中成功写入的文件)
1073
1371
  _producedFiles = [];
1074
1372
  _turnStartTime = 0;
@@ -1120,7 +1418,15 @@ var TurnEngine = class _TurnEngine {
1120
1418
  this._hook = opts.hook;
1121
1419
  this._contextWindow = opts.contextWindow;
1122
1420
  this._thresholdPct = opts.thresholdPct ?? DEFAULT_THRESHOLD_PCT;
1421
+ this._softThresholdPct = opts.softThresholdPct;
1422
+ this._elisionEnabled = !/^(0|false|off|no)$/i.test(
1423
+ (process.env.COMPACTION_ELIDE ?? "").trim()
1424
+ );
1425
+ this._dedupEnabled = !/^(0|false|off|no)$/i.test(
1426
+ (process.env.COMPACTION_DEDUP ?? "").trim()
1427
+ );
1123
1428
  this._capTokens = opts.capTokens ?? null;
1429
+ this._toolResultDir = opts.toolResultDir;
1124
1430
  this._reasoningEffort = opts.reasoningEffort;
1125
1431
  if (this.messages.length === 0 || this.messages[0].role !== "system") {
1126
1432
  this.messages.unshift({ role: "system", content: this.instructions });
@@ -1176,6 +1482,7 @@ var TurnEngine = class _TurnEngine {
1176
1482
  this._cancel = false;
1177
1483
  this._producedFiles = [];
1178
1484
  this._turnStartTime = Date.now();
1485
+ this._compactStalledAt = -1;
1179
1486
  this._emitHook((h) => h.onTurnStart?.(userInput));
1180
1487
  yield {
1181
1488
  type: "turn_start" /* TURN_START */,
@@ -1892,7 +2199,9 @@ var TurnEngine = class _TurnEngine {
1892
2199
  for (let i = 0; i < concurrent.length; i++) {
1893
2200
  const tc = concurrent[i];
1894
2201
  const { result, status } = results[i];
1895
- this.messages.push(toolResultMessage(tc, result, this.taskId));
2202
+ this.messages.push(
2203
+ toolResultMessage(tc, result, this.taskId, this._toolResultDir)
2204
+ );
1896
2205
  this._trackProducedFile(
1897
2206
  tc,
1898
2207
  result,
@@ -1955,7 +2264,9 @@ var TurnEngine = class _TurnEngine {
1955
2264
  };
1956
2265
  status = "error";
1957
2266
  }
1958
- this.messages.push(toolResultMessage(tc, result, this.taskId));
2267
+ this.messages.push(
2268
+ toolResultMessage(tc, result, this.taskId, this._toolResultDir)
2269
+ );
1959
2270
  this._trackProducedFile(
1960
2271
  tc,
1961
2272
  result,
@@ -1990,13 +2301,41 @@ var TurnEngine = class _TurnEngine {
1990
2301
  * 过滤 notice 消息;tool 消息只保留必要字段;
1991
2302
  * 其余消息剥离 display-only sidecar 字段(source/_display/ts/
1992
2303
  * reasoning/usage),避免污染模型上下文。
2304
+ *
2305
+ * 达到软阈值时先淘汰旧工具结果(零 LLM 成本),再产出最终视图。
1993
2306
  */
1994
2307
  _outboundMessages() {
1995
- const _STRIP = /* @__PURE__ */ new Set(["source", "_display", "ts", "reasoning", "usage"]);
1996
- const sourceMessages = applyToOutbound(
1997
- this.messages,
1998
- this.compactionState
2308
+ const base = applyToOutbound(this.messages, this.compactionState);
2309
+ const deduped = this._dedupEnabled ? applyReadFileDedup(base).messages : base;
2310
+ let view = this._finalizeOutbound(deduped);
2311
+ if (!this._elisionEnabled) return view;
2312
+ if (estimateTokens(view) < this._softTriggerTokens()) {
2313
+ return view;
2314
+ }
2315
+ const oneShot = applyOneShotElision(deduped);
2316
+ if (oneShot.freedChars > 0) {
2317
+ view = this._finalizeOutbound(oneShot.messages);
2318
+ if (estimateTokens(view) < this._softTriggerTokens()) {
2319
+ return view;
2320
+ }
2321
+ }
2322
+ const keepChars = elisionKeepChars(
2323
+ triggerTokens(
2324
+ this._getContextWindow(),
2325
+ this._thresholdPct,
2326
+ this._capTokens
2327
+ )
2328
+ );
2329
+ const elided = applyToolResultElision(
2330
+ oneShot.messages,
2331
+ keepChars
1999
2332
  );
2333
+ if (elided.freedChars === 0) return view;
2334
+ return this._finalizeOutbound(elided.messages);
2335
+ }
2336
+ /** 过滤 notice + 剥离 display-only 字段,得到最终发送视图 */
2337
+ _finalizeOutbound(sourceMessages) {
2338
+ const _STRIP = /* @__PURE__ */ new Set(["source", "_display", "ts", "reasoning", "usage"]);
2000
2339
  return sourceMessages.filter((m) => m.role !== "notice").map((m) => {
2001
2340
  if (m.role === "tool") {
2002
2341
  return {
@@ -2014,6 +2353,15 @@ var TurnEngine = class _TurnEngine {
2014
2353
  return stripped;
2015
2354
  });
2016
2355
  }
2356
+ /** 软阈值(token 数):达到它只淘汰旧工具结果 */
2357
+ _softTriggerTokens() {
2358
+ return softTriggerTokens(
2359
+ this._getContextWindow(),
2360
+ this._thresholdPct,
2361
+ this._capTokens,
2362
+ this._softThresholdPct
2363
+ );
2364
+ }
2017
2365
  /** 通知消息:仅用于日志/展示,不发给模型 */
2018
2366
  _appendNotice(kind, text) {
2019
2367
  this.messages.push({
@@ -2055,18 +2403,36 @@ var TurnEngine = class _TurnEngine {
2055
2403
  return false;
2056
2404
  }
2057
2405
  // ===================== 自动压缩 =====================
2058
- /** 当前上下文 token 用量:优先采信 API 返回的精确数量,否则用 chars/4 估算 */
2406
+ /**
2407
+ * 当前上下文 token 用量。
2408
+ *
2409
+ * API 返回的 `usage.input` 是精确值,但反映的是「上一次请求」——工具结果
2410
+ * 与淘汰/压缩都会让它过时。因此同时估算当前 outbound 视图,取较大者:
2411
+ * 宁可早一点压缩,也不要漏判导致溢出。
2412
+ */
2059
2413
  _compactionTokens() {
2060
- if (this._lastContextTokens != null) {
2061
- return { tokens: this._lastContextTokens, estimated: false };
2414
+ const estimated = estimateTokens(this._outboundMessages());
2415
+ if (this._lastContextTokens == null) {
2416
+ return { tokens: estimated, estimated: true };
2062
2417
  }
2063
- return {
2064
- tokens: estimateTokens(this._outboundMessages()),
2065
- estimated: true
2066
- };
2418
+ if (estimated > this._lastContextTokens) {
2419
+ return { tokens: estimated, estimated: true };
2420
+ }
2421
+ return { tokens: this._lastContextTokens, estimated: false };
2422
+ }
2423
+ /** 压缩后重置精确计数:后续按估算值判断 */
2424
+ _resetContextTokens() {
2425
+ this._lastContextTokens = null;
2426
+ }
2427
+ /** 当前 outbound 视图的估算 token 数 */
2428
+ _estimateContextTokens() {
2429
+ return estimateTokens(this._outboundMessages());
2067
2430
  }
2068
2431
  /** 检查是否需要压缩(每轮 LLM 调用前) */
2069
2432
  _compactionDue() {
2433
+ if (this._compactStalledAt >= 0 && this.messages.length < this._compactStalledAt + COMPACT_STALL_RETRY_DELTA) {
2434
+ return false;
2435
+ }
2070
2436
  const { tokens } = this._compactionTokens();
2071
2437
  return shouldCompact(
2072
2438
  tokens,
@@ -2113,63 +2479,82 @@ var TurnEngine = class _TurnEngine {
2113
2479
  async _compactNow(reqId, force = false) {
2114
2480
  const contextWindow = this._getContextWindow();
2115
2481
  const messagesBefore = this._outboundMessages().length;
2116
- const keep = Math.floor(
2117
- KEEP_RECENT_FRACTION * triggerTokens(
2118
- contextWindow,
2119
- this._thresholdPct,
2120
- this._capTokens
2121
- )
2482
+ const trigger = triggerTokens(
2483
+ contextWindow,
2484
+ this._thresholdPct,
2485
+ this._capTokens
2122
2486
  );
2123
- const model = this.model;
2124
- let state = null;
2125
- let failed = false;
2126
- for (let attempt = 0; attempt < 2; attempt++) {
2127
- try {
2128
- const logTime = (/* @__PURE__ */ new Date()).toISOString();
2129
- state = await buildState(
2487
+ const target = Math.floor(
2488
+ trigger * (force ? OVERFLOW_TARGET_RATIO : 0.9)
2489
+ );
2490
+ const model = process.env.COMPACTION_MODEL?.trim() || this.model;
2491
+ const reasoningEffort = summarizerReasoningEffort();
2492
+ let keep = Math.floor(KEEP_RECENT_FRACTION * trigger);
2493
+ let method = null;
2494
+ let lastState = null;
2495
+ let progressed = false;
2496
+ for (let round = 0; round < MAX_COMPACT_ROUNDS; round++) {
2497
+ if (round > 0 && this._estimateContextTokens() <= target) break;
2498
+ let state = null;
2499
+ for (let attempt = 0; attempt < 2 && !state; attempt++) {
2500
+ try {
2501
+ const logTime = (/* @__PURE__ */ new Date()).toISOString();
2502
+ state = await buildState(
2503
+ this.messages,
2504
+ this.provider,
2505
+ model,
2506
+ keep,
2507
+ this.compactionState,
2508
+ { reqId, logTime, reasoningEffort }
2509
+ );
2510
+ } catch {
2511
+ }
2512
+ }
2513
+ if (state) {
2514
+ this.compactionState = state;
2515
+ lastState = state;
2516
+ method = "summarized";
2517
+ progressed = true;
2518
+ } else {
2519
+ const trimmed = trimState(
2130
2520
  this.messages,
2131
- this.provider,
2132
- model,
2133
- keep,
2134
2521
  this.compactionState,
2135
- { reqId, logTime, reasoningEffort: this._reasoningEffort }
2522
+ force ? OVERFLOW_TRIM_FRACTION : 0.25
2136
2523
  );
2137
- failed = false;
2138
- break;
2139
- } catch {
2140
- failed = true;
2141
- }
2142
- }
2143
- if (state) {
2144
- this.compactionState = state;
2145
- this._lastContextTokens = null;
2524
+ if (!trimmed) break;
2525
+ this.compactionState = trimmed;
2526
+ lastState = trimmed;
2527
+ method = "trimmed";
2528
+ progressed = true;
2529
+ }
2530
+ this._resetContextTokens();
2531
+ if (this._estimateContextTokens() <= trigger) break;
2532
+ keep = Math.max(Math.floor(trigger * 0.05), Math.floor(keep / 2));
2533
+ }
2534
+ if (!progressed || !lastState) return { notice: null, method: null };
2535
+ this._compactStalledAt = this._estimateContextTokens() >= trigger ? this.messages.length : -1;
2536
+ const messagesAfter = this._outboundMessages().length;
2537
+ const common = {
2538
+ boundaryIndex: lastState.boundaryIndex,
2539
+ messagesBefore,
2540
+ messagesAfter
2541
+ };
2542
+ if (method === "summarized") {
2146
2543
  return {
2147
2544
  notice: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29 \u2014 \u65E9\u671F\u8F6E\u6B21\u5DF2\u6458\u8981",
2148
2545
  method: "summarized",
2149
- modelUsed: state.modelUsed,
2150
- boundaryIndex: state.boundaryIndex,
2151
- summaryPreview: state.summaryText.slice(0, 200),
2152
- keptUserMessages: state.userMessages.length,
2153
- droppedUserMessages: state.userMessagesDropped,
2154
- messagesBefore,
2155
- messagesAfter: this._outboundMessages().length
2546
+ modelUsed: lastState.modelUsed,
2547
+ summaryPreview: lastState.summaryText.slice(0, 200),
2548
+ keptUserMessages: lastState.userMessages.length,
2549
+ droppedUserMessages: lastState.userMessagesDropped,
2550
+ ...common
2156
2551
  };
2157
2552
  }
2158
- if (failed || force) {
2159
- const trimmed = trimState(this.messages, this.compactionState);
2160
- if (trimmed) {
2161
- this.compactionState = trimmed;
2162
- this._lastContextTokens = null;
2163
- return {
2164
- notice: "\u4E0A\u4E0B\u6587\u5DF2\u88C1\u526A \u2014 \u6700\u65E7\u7684\u8F6E\u6B21\u5DF2\u4E22\u5F03\uFF08\u6458\u8981\u4E0D\u53EF\u7528\uFF09",
2165
- method: "trimmed",
2166
- boundaryIndex: trimmed.boundaryIndex,
2167
- messagesBefore,
2168
- messagesAfter: this._outboundMessages().length
2169
- };
2170
- }
2171
- }
2172
- return { notice: null, method: null };
2553
+ return {
2554
+ notice: "\u4E0A\u4E0B\u6587\u5DF2\u88C1\u526A \u2014 \u6700\u65E7\u7684\u8F6E\u6B21\u5DF2\u4E22\u5F03\uFF08\u6458\u8981\u4E0D\u53EF\u7528\uFF09",
2555
+ method: "trimmed",
2556
+ ...common
2557
+ };
2173
2558
  }
2174
2559
  /** 追踪文件写入工具成功产生的文件 */
2175
2560
  _trackProducedFile(tc, result, status) {
@@ -2179,9 +2564,9 @@ var TurnEngine = class _TurnEngine {
2179
2564
  return;
2180
2565
  }
2181
2566
  if (result && typeof result === "object") {
2182
- const path14 = result.path;
2183
- if (path14) {
2184
- this._producedFiles.push(path14);
2567
+ const path16 = result.path;
2568
+ if (path16) {
2569
+ this._producedFiles.push(path16);
2185
2570
  return;
2186
2571
  }
2187
2572
  const changed = result.changed_files;
@@ -2205,7 +2590,7 @@ var TurnEngine = class _TurnEngine {
2205
2590
  _fileExists(f, ws) {
2206
2591
  if (!ws && !_path.isAbsolute(f)) return true;
2207
2592
  try {
2208
- return fs.existsSync(_path.isAbsolute(f) ? f : _path.join(ws, f));
2593
+ return fs2.existsSync(_path.isAbsolute(f) ? f : _path.join(ws, f));
2209
2594
  } catch {
2210
2595
  return false;
2211
2596
  }
@@ -2228,7 +2613,7 @@ var TurnEngine = class _TurnEngine {
2228
2613
  if (depth > 4 || results.length >= 50) return;
2229
2614
  let entries;
2230
2615
  try {
2231
- entries = fs.readdirSync(dir, { withFileTypes: true });
2616
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
2232
2617
  } catch {
2233
2618
  return;
2234
2619
  }
@@ -2243,7 +2628,7 @@ var TurnEngine = class _TurnEngine {
2243
2628
  const ext = dotIdx !== -1 ? e.name.slice(dotIdx).toLowerCase() : "";
2244
2629
  if (!_TurnEngine.SCAN_SUFFIXES.has(ext)) continue;
2245
2630
  try {
2246
- const st = fs.statSync(full);
2631
+ const st = fs2.statSync(full);
2247
2632
  if (st.mtimeMs >= since) {
2248
2633
  results.push(
2249
2634
  _path.relative(_path.resolve(ws), _path.resolve(full))
@@ -2264,8 +2649,8 @@ var TurnEngine = class _TurnEngine {
2264
2649
  };
2265
2650
 
2266
2651
  // src/providers/OpenAIProvider.ts
2267
- var fs2 = __toESM(require("fs"), 1);
2268
- var path = __toESM(require("path"), 1);
2652
+ var fs3 = __toESM(require("fs"), 1);
2653
+ var path3 = __toESM(require("path"), 1);
2269
2654
  var import_openai = __toESM(require("openai"), 1);
2270
2655
 
2271
2656
  // src/terminal.ts
@@ -2443,7 +2828,7 @@ var OpenAIProvider = class {
2443
2828
  });
2444
2829
  if (opts.logDir) {
2445
2830
  this._logDir = opts.logDir;
2446
- fs2.mkdirSync(opts.logDir, { recursive: true });
2831
+ fs3.mkdirSync(opts.logDir, { recursive: true });
2447
2832
  }
2448
2833
  }
2449
2834
  // =====================================================================
@@ -2596,6 +2981,8 @@ var OpenAIProvider = class {
2596
2981
  tools: this._toResponsesTools(tools, model),
2597
2982
  // 推理强度:DeepSeek 不支持,跳过
2598
2983
  ...opts.reasoningEffort && !this._isDeepSeek(model) ? { reasoning: { effort: opts.reasoningEffort } } : {},
2984
+ // 限制输出长度(摘要器等轻量请求)
2985
+ ...opts.maxTokens ? { max_output_tokens: opts.maxTokens } : {},
2599
2986
  stream: true
2600
2987
  }, requestOptions);
2601
2988
  return this._responsesStream(reqId, startTime, rawStream, {
@@ -2694,6 +3081,8 @@ var OpenAIProvider = class {
2694
3081
  })),
2695
3082
  // 推理强度:DeepSeek 不支持,跳过
2696
3083
  ...opts.reasoningEffort && !this._isDeepSeek(model) ? { reasoning_effort: opts.reasoningEffort } : {},
3084
+ // 限制输出长度:o 系列 / gpt-5 推理模型仅支持 max_completion_tokens
3085
+ ...opts.maxTokens ? isReasoningModel(model) ? { max_completion_tokens: opts.maxTokens } : { max_tokens: opts.maxTokens } : {},
2697
3086
  stream: true,
2698
3087
  // 要求返回 Token 用量统计(在流结束前的一个 chunk 中携带)
2699
3088
  stream_options: { include_usage: true }
@@ -2807,8 +3196,8 @@ var OpenAIProvider = class {
2807
3196
  if (!this._logDir) return;
2808
3197
  try {
2809
3198
  const today = logTime?.split("T")[0];
2810
- const dir = path.join(this._logDir, today);
2811
- fs2.mkdirSync(dir, { recursive: true });
3199
+ const dir = path3.join(this._logDir, today);
3200
+ fs3.mkdirSync(dir, { recursive: true });
2812
3201
  const entry = {
2813
3202
  reqId,
2814
3203
  ts: logTime,
@@ -2820,8 +3209,8 @@ var OpenAIProvider = class {
2820
3209
  toolNames: tools?.map((t) => t.function.name) ?? []
2821
3210
  }
2822
3211
  };
2823
- fs2.writeFileSync(
2824
- path.join(dir, `${reqId}.json`),
3212
+ fs3.writeFileSync(
3213
+ path3.join(dir, `${reqId}.json`),
2825
3214
  JSON.stringify(entry, null, 2),
2826
3215
  "utf-8"
2827
3216
  );
@@ -2840,9 +3229,9 @@ var OpenAIProvider = class {
2840
3229
  if (!this._logDir) return;
2841
3230
  try {
2842
3231
  const today = logTime?.split("T")[0];
2843
- const filePath = path.join(this._logDir, today, `${reqId}.json`);
2844
- if (!fs2.existsSync(filePath)) return;
2845
- const entry = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
3232
+ const filePath = path3.join(this._logDir, today, `${reqId}.json`);
3233
+ if (!fs3.existsSync(filePath)) return;
3234
+ const entry = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
2846
3235
  entry.elapsedMs = Date.now() - startTime;
2847
3236
  if (data.response) {
2848
3237
  entry.response = data.response;
@@ -2850,7 +3239,7 @@ var OpenAIProvider = class {
2850
3239
  if (data.error) {
2851
3240
  entry.error = data.error;
2852
3241
  }
2853
- fs2.writeFileSync(filePath, JSON.stringify(entry, null, 2), "utf-8");
3242
+ fs3.writeFileSync(filePath, JSON.stringify(entry, null, 2), "utf-8");
2854
3243
  } catch (e) {
2855
3244
  console.error(e);
2856
3245
  }
@@ -3315,62 +3704,62 @@ var ToolRegistry = class {
3315
3704
  };
3316
3705
 
3317
3706
  // src/tools/pathSafety.ts
3318
- var fs3 = __toESM(require("fs"), 1);
3319
- var path2 = __toESM(require("path"), 1);
3707
+ var fs4 = __toESM(require("fs"), 1);
3708
+ var path4 = __toESM(require("path"), 1);
3320
3709
  function isInsideRelative(rel) {
3321
- return !path2.isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${path2.sep}`);
3710
+ return !path4.isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${path4.sep}`);
3322
3711
  }
3323
3712
  function isFilesystemRoot(p) {
3324
- return path2.parse(p).root === p;
3713
+ return path4.parse(p).root === p;
3325
3714
  }
3326
3715
  function resolveWithinRoot(workspace, target) {
3327
- const abs = path2.resolve(workspace, target);
3716
+ const abs = path4.resolve(workspace, target);
3328
3717
  let rootReal;
3329
3718
  try {
3330
- rootReal = fs3.realpathSync(workspace);
3719
+ rootReal = fs4.realpathSync(workspace);
3331
3720
  } catch {
3332
- const rootAbs = path2.resolve(workspace);
3333
- if (!isInsideRelative(path2.relative(rootAbs, abs))) return null;
3721
+ const rootAbs = path4.resolve(workspace);
3722
+ if (!isInsideRelative(path4.relative(rootAbs, abs))) return null;
3334
3723
  return { root: rootAbs, real: abs };
3335
3724
  }
3336
3725
  if (isFilesystemRoot(rootReal)) return null;
3337
3726
  let real;
3338
3727
  try {
3339
- real = fs3.realpathSync(abs);
3728
+ real = fs4.realpathSync(abs);
3340
3729
  } catch {
3341
3730
  let probe = abs;
3342
3731
  const tail = [];
3343
3732
  for (; ; ) {
3344
- const exists = fs3.existsSync(probe) || fs3.lstatSync(probe, { throwIfNoEntry: false }) !== void 0;
3733
+ const exists = fs4.existsSync(probe) || fs4.lstatSync(probe, { throwIfNoEntry: false }) !== void 0;
3345
3734
  if (exists) {
3346
3735
  try {
3347
- real = path2.join(fs3.realpathSync(probe), ...tail);
3736
+ real = path4.join(fs4.realpathSync(probe), ...tail);
3348
3737
  break;
3349
3738
  } catch {
3350
3739
  return null;
3351
3740
  }
3352
3741
  }
3353
- tail.unshift(path2.basename(probe));
3354
- const parent = path2.dirname(probe);
3742
+ tail.unshift(path4.basename(probe));
3743
+ const parent = path4.dirname(probe);
3355
3744
  if (parent === probe) return null;
3356
3745
  probe = parent;
3357
3746
  }
3358
3747
  }
3359
- if (!isInsideRelative(path2.relative(rootReal, real))) return null;
3748
+ if (!isInsideRelative(path4.relative(rootReal, real))) return null;
3360
3749
  return { root: rootReal, real };
3361
3750
  }
3362
3751
  function isWithinRoot(workspace, target) {
3363
3752
  return resolveWithinRoot(workspace, target) !== null;
3364
3753
  }
3365
3754
  function resolveEntryWithinRoot(workspace, target) {
3366
- const abs = path2.resolve(workspace, target);
3367
- const parent = resolveWithinRoot(workspace, path2.dirname(abs));
3755
+ const abs = path4.resolve(workspace, target);
3756
+ const parent = resolveWithinRoot(workspace, path4.dirname(abs));
3368
3757
  if (!parent) return null;
3369
- return { root: parent.root, real: path2.join(parent.real, path2.basename(abs)) };
3758
+ return { root: parent.root, real: path4.join(parent.real, path4.basename(abs)) };
3370
3759
  }
3371
3760
 
3372
3761
  // src/permissions/PermissionEngine.ts
3373
- var path3 = __toESM(require("path"), 1);
3762
+ var path5 = __toESM(require("path"), 1);
3374
3763
  var Mode = /* @__PURE__ */ ((Mode2) => {
3375
3764
  Mode2["DISCUSS"] = "discuss";
3376
3765
  Mode2["PLAN"] = "plan";
@@ -3427,7 +3816,7 @@ var PermissionEngine = class {
3427
3816
  /** 会话级「总是允许此命令」集合 */
3428
3817
  sessionAllowCommands;
3429
3818
  constructor(opts) {
3430
- this.workspaceRoot = path3.resolve(opts.workspaceRoot);
3819
+ this.workspaceRoot = path5.resolve(opts.workspaceRoot);
3431
3820
  this.mode = opts.mode;
3432
3821
  this.allowedCommands = Array.isArray(opts.allowedCommands) ? opts.allowedCommands : [];
3433
3822
  this.sessionAllowTools = /* @__PURE__ */ new Set();
@@ -4342,14 +4731,14 @@ function approvalToHtmlTable(input, maxLen = APPROVAL_VALUE_MAX) {
4342
4731
  }
4343
4732
 
4344
4733
  // src/engine/FileLogHook.ts
4345
- var fs4 = __toESM(require("fs"), 1);
4346
- var path4 = __toESM(require("path"), 1);
4734
+ var fs5 = __toESM(require("fs"), 1);
4735
+ var path6 = __toESM(require("path"), 1);
4347
4736
  var FileLogHook = class {
4348
- constructor(sessionId, stateDir) {
4737
+ constructor(sessionId, stateDir2) {
4349
4738
  this.sessionId = sessionId;
4350
- fs4.mkdirSync(stateDir, { recursive: true });
4351
- this._logPath = path4.join(stateDir, `${sessionId}.jsonl`);
4352
- this._statePath = path4.join(stateDir, `${sessionId}_state.json`);
4739
+ fs5.mkdirSync(stateDir2, { recursive: true });
4740
+ this._logPath = path6.join(stateDir2, `${sessionId}.jsonl`);
4741
+ this._statePath = path6.join(stateDir2, `${sessionId}_state.json`);
4353
4742
  }
4354
4743
  sessionId;
4355
4744
  _logPath;
@@ -4443,13 +4832,13 @@ var FileLogHook = class {
4443
4832
  ...meta?.nameSource !== void 0 ? { nameSource: meta.nameSource } : {},
4444
4833
  ...meta?.taskCount !== void 0 ? { taskCount: meta.taskCount } : {}
4445
4834
  };
4446
- fs4.writeFileSync(this._statePath, JSON.stringify(state, null, 2), "utf-8");
4835
+ fs5.writeFileSync(this._statePath, JSON.stringify(state, null, 2), "utf-8");
4447
4836
  }
4448
4837
  /** 读取保存的引擎状态 */
4449
- static loadState(sessionId, stateDir) {
4450
- const p = path4.join(stateDir, `${sessionId}_state.json`);
4838
+ static loadState(sessionId, stateDir2) {
4839
+ const p = path6.join(stateDir2, `${sessionId}_state.json`);
4451
4840
  try {
4452
- const raw = fs4.readFileSync(p, "utf-8");
4841
+ const raw = fs5.readFileSync(p, "utf-8");
4453
4842
  return JSON.parse(raw);
4454
4843
  } catch (e) {
4455
4844
  console.error(e);
@@ -4457,10 +4846,10 @@ var FileLogHook = class {
4457
4846
  }
4458
4847
  }
4459
4848
  /** 读取事件日志 */
4460
- static loadEvents(sessionId, stateDir) {
4461
- const p = path4.join(stateDir, `${sessionId}.jsonl`);
4849
+ static loadEvents(sessionId, stateDir2) {
4850
+ const p = path6.join(stateDir2, `${sessionId}.jsonl`);
4462
4851
  try {
4463
- const raw = fs4.readFileSync(p, "utf-8");
4852
+ const raw = fs5.readFileSync(p, "utf-8");
4464
4853
  return raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
4465
4854
  } catch (e) {
4466
4855
  console.error(e);
@@ -4471,7 +4860,7 @@ var FileLogHook = class {
4471
4860
  _append(entry) {
4472
4861
  entry["ts"] = (/* @__PURE__ */ new Date()).toISOString();
4473
4862
  try {
4474
- fs4.appendFileSync(this._logPath, JSON.stringify(entry) + "\n", "utf-8");
4863
+ fs5.appendFileSync(this._logPath, JSON.stringify(entry) + "\n", "utf-8");
4475
4864
  } catch (e) {
4476
4865
  console.error(e);
4477
4866
  }
@@ -4895,12 +5284,12 @@ var McpManager = class {
4895
5284
  };
4896
5285
 
4897
5286
  // src/tools/files.ts
4898
- var fs5 = __toESM(require("fs"), 1);
4899
- var path5 = __toESM(require("path"), 1);
5287
+ var fs6 = __toESM(require("fs"), 1);
5288
+ var path7 = __toESM(require("path"), 1);
4900
5289
  var DEFAULT_MAX_LINES = 2e3;
4901
5290
  var MAX_LINE_CHARS = 500;
4902
5291
  function makeReadFile(workspace) {
4903
- const root = path5.resolve(workspace);
5292
+ const root = path7.resolve(workspace);
4904
5293
  return function read_file(args) {
4905
5294
  const filePath = String(args.path ?? "");
4906
5295
  const startLine = Math.max(1, Number(args.start_line ?? 1));
@@ -4910,13 +5299,13 @@ function makeReadFile(workspace) {
4910
5299
  return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
4911
5300
  }
4912
5301
  const target = resolved.real;
4913
- if (!fs5.existsSync(target) || !fs5.statSync(target).isFile()) {
5302
+ if (!fs6.existsSync(target) || !fs6.statSync(target).isFile()) {
4914
5303
  return { error: `\u4E0D\u662F\u6587\u4EF6: ${filePath}` };
4915
5304
  }
4916
5305
  const selected = [];
4917
5306
  let total = 0;
4918
5307
  try {
4919
- const content = fs5.readFileSync(target, "utf-8");
5308
+ const content = fs6.readFileSync(target, "utf-8");
4920
5309
  const lines = content.split("\n");
4921
5310
  total = lines.length;
4922
5311
  for (let i = startLine - 1; i < Math.min(startLine - 1 + maxLines, lines.length); i++) {
@@ -4931,7 +5320,7 @@ function makeReadFile(workspace) {
4931
5320
  }
4932
5321
  const end = startLine + selected.length - 1;
4933
5322
  const result = {
4934
- path: path5.relative(resolved.root, target),
5323
+ path: path7.relative(resolved.root, target),
4935
5324
  start_line: startLine,
4936
5325
  end_line: end,
4937
5326
  total_lines: total,
@@ -4944,7 +5333,7 @@ function makeReadFile(workspace) {
4944
5333
  };
4945
5334
  }
4946
5335
  function makeWriteFile(workspace) {
4947
- const root = path5.resolve(workspace);
5336
+ const root = path7.resolve(workspace);
4948
5337
  return function write_file(args) {
4949
5338
  const filePath = String(args.path ?? "");
4950
5339
  const content = String(args.content ?? "");
@@ -4954,34 +5343,34 @@ function makeWriteFile(workspace) {
4954
5343
  return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
4955
5344
  }
4956
5345
  const target = resolved.real;
4957
- if (fs5.existsSync(target) && fs5.statSync(target).isDirectory()) {
5346
+ if (fs6.existsSync(target) && fs6.statSync(target).isDirectory()) {
4958
5347
  return { error: "\u8DEF\u5F84\u662F\u76EE\u5F55\uFF0C\u4E0D\u662F\u6587\u4EF6" };
4959
5348
  }
4960
- if (fs5.existsSync(target) && !overwrite) {
5349
+ if (fs6.existsSync(target) && !overwrite) {
4961
5350
  return { error: "\u6587\u4EF6\u5DF2\u5B58\u5728 (overwrite=false)" };
4962
5351
  }
4963
5352
  try {
4964
- fs5.mkdirSync(path5.dirname(target), { recursive: true });
4965
- fs5.writeFileSync(target, content, "utf-8");
5353
+ fs6.mkdirSync(path7.dirname(target), { recursive: true });
5354
+ fs6.writeFileSync(target, content, "utf-8");
4966
5355
  } catch (exc) {
4967
5356
  return { error: `\u5199\u5165\u5931\u8D25: ${String(exc)}` };
4968
5357
  }
4969
- return path5.relative(resolved.root, target);
5358
+ return path7.relative(resolved.root, target);
4970
5359
  };
4971
5360
  }
4972
5361
  function makeListFiles(workspace) {
4973
- const root = path5.resolve(workspace);
5362
+ const root = path7.resolve(workspace);
4974
5363
  return function list_files(args) {
4975
5364
  const dirPath = String(args.path ?? ".");
4976
5365
  const maxResults = Math.min(Number(args.max_results ?? 100), 100);
4977
5366
  const resolved = resolveWithinRoot(root, dirPath);
4978
5367
  if (!resolved) return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
4979
5368
  const target = resolved.real;
4980
- if (!fs5.existsSync(target) || !fs5.statSync(target).isDirectory()) {
5369
+ if (!fs6.existsSync(target) || !fs6.statSync(target).isDirectory()) {
4981
5370
  return { error: `\u4E0D\u662F\u76EE\u5F55: ${dirPath}` };
4982
5371
  }
4983
5372
  try {
4984
- const entries = fs5.readdirSync(target, { withFileTypes: true });
5373
+ const entries = fs6.readdirSync(target, { withFileTypes: true });
4985
5374
  const results = [];
4986
5375
  for (const e of entries) {
4987
5376
  if (results.length >= maxResults) break;
@@ -4990,14 +5379,14 @@ function makeListFiles(workspace) {
4990
5379
  results.push(e.isDirectory() ? `${e.name}/` : e.name);
4991
5380
  }
4992
5381
  results.sort();
4993
- return { path: path5.relative(resolved.root, target), entries: results.slice(0, maxResults) };
5382
+ return { path: path7.relative(resolved.root, target), entries: results.slice(0, maxResults) };
4994
5383
  } catch (exc) {
4995
5384
  return { error: `\u5217\u51FA\u6587\u4EF6\u5931\u8D25: ${String(exc)}` };
4996
5385
  }
4997
5386
  };
4998
5387
  }
4999
5388
  function makeReplaceInFile(workspace) {
5000
- const root = path5.resolve(workspace);
5389
+ const root = path7.resolve(workspace);
5001
5390
  return function replace_in_file(args) {
5002
5391
  const filePath = String(args.path ?? "");
5003
5392
  const oldStr = String(args.old ?? "");
@@ -5007,19 +5396,19 @@ function makeReplaceInFile(workspace) {
5007
5396
  const resolved = resolveWithinRoot(root, filePath);
5008
5397
  if (!resolved) return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
5009
5398
  const target = resolved.real;
5010
- if (!fs5.existsSync(target) || !fs5.statSync(target).isFile()) {
5399
+ if (!fs6.existsSync(target) || !fs6.statSync(target).isFile()) {
5011
5400
  return { error: `\u4E0D\u662F\u6587\u4EF6: ${filePath}` };
5012
5401
  }
5013
5402
  try {
5014
- const content = fs5.readFileSync(target, "utf-8");
5403
+ const content = fs6.readFileSync(target, "utf-8");
5015
5404
  const count = content.split(oldStr).length - 1;
5016
5405
  if (count !== expectedReplacements) {
5017
5406
  return { error: `\u671F\u671B ${expectedReplacements} \u5904\u66FF\u6362\uFF0C\u5B9E\u9645\u627E\u5230 ${count} \u5904: ${filePath}` };
5018
5407
  }
5019
5408
  const updated = content.replaceAll(oldStr, newStr);
5020
- fs5.writeFileSync(target, updated, "utf-8");
5409
+ fs6.writeFileSync(target, updated, "utf-8");
5021
5410
  return {
5022
- path: path5.relative(resolved.root, target),
5411
+ path: path7.relative(resolved.root, target),
5023
5412
  replacements: count,
5024
5413
  chars_before: content.length,
5025
5414
  chars_after: updated.length
@@ -5119,8 +5508,8 @@ function registerFileTools(registry, workspace) {
5119
5508
  }
5120
5509
 
5121
5510
  // src/tools/shell.ts
5122
- var fs7 = __toESM(require("fs"), 1);
5123
- var path7 = __toESM(require("path"), 1);
5511
+ var fs8 = __toESM(require("fs"), 1);
5512
+ var path9 = __toESM(require("path"), 1);
5124
5513
 
5125
5514
  // src/tools/execAsync.ts
5126
5515
  var import_node_child_process2 = require("child_process");
@@ -5282,15 +5671,15 @@ function execAsync(file, argv, opts = {}) {
5282
5671
  }
5283
5672
 
5284
5673
  // src/tools/shellRuntime.ts
5285
- var fs6 = __toESM(require("fs"), 1);
5286
- var path6 = __toESM(require("path"), 1);
5674
+ var fs7 = __toESM(require("fs"), 1);
5675
+ var path8 = __toESM(require("path"), 1);
5287
5676
  var WINDOWS_SUFFIXES = [".exe", ".cmd", ".bat", ".com", ""];
5288
5677
  function pathApi(platform) {
5289
- return platform === "win32" ? path6.win32 : path6.posix;
5678
+ return platform === "win32" ? path8.win32 : path8.posix;
5290
5679
  }
5291
5680
  function defaultFileExists(p) {
5292
5681
  try {
5293
- return fs6.statSync(p).isFile();
5682
+ return fs7.statSync(p).isFile();
5294
5683
  } catch {
5295
5684
  return false;
5296
5685
  }
@@ -5321,7 +5710,7 @@ function kindFromBasename(base) {
5321
5710
  return "sh";
5322
5711
  }
5323
5712
  function busyboxArgs(file, command) {
5324
- const base = (file ? path6.win32.basename(file) : "busybox").toLowerCase().replace(/\.(exe|cmd|bat|com)$/, "");
5713
+ const base = (file ? path8.win32.basename(file) : "busybox").toLowerCase().replace(/\.(exe|cmd|bat|com)$/, "");
5325
5714
  return base === "busybox" ? ["sh", "-c", command] : ["-c", command];
5326
5715
  }
5327
5716
  function argsFor(kind, command) {
@@ -5587,7 +5976,7 @@ var MAX_STDERR = 5e3;
5587
5976
  var MAX_BUFFER = 1024 * 1024 * 5;
5588
5977
  var shellSessions = /* @__PURE__ */ new Map();
5589
5978
  function sessionFor(workspace) {
5590
- const key = path7.resolve(workspace);
5979
+ const key = path9.resolve(workspace);
5591
5980
  let s = shellSessions.get(key);
5592
5981
  if (!s) {
5593
5982
  s = { cwd: key };
@@ -5644,8 +6033,8 @@ function makeRunShell(workspace, spec, tasks, scope) {
5644
6033
  let toRun = command;
5645
6034
  if (target) {
5646
6035
  try {
5647
- const resolved = path7.resolve(session.cwd, target);
5648
- if (fs7.existsSync(resolved) && fs7.statSync(resolved).isDirectory()) {
6036
+ const resolved = path9.resolve(session.cwd, target);
6037
+ if (fs8.existsSync(resolved) && fs8.statSync(resolved).isDirectory()) {
5649
6038
  session.cwd = resolved;
5650
6039
  }
5651
6040
  } catch {
@@ -5770,8 +6159,8 @@ function registerShellTool(registry, workspace, scope) {
5770
6159
  }
5771
6160
 
5772
6161
  // src/tools/nodeGrep.ts
5773
- var fs8 = __toESM(require("fs"), 1);
5774
- var path8 = __toESM(require("path"), 1);
6162
+ var fs9 = __toESM(require("fs"), 1);
6163
+ var path10 = __toESM(require("path"), 1);
5775
6164
  var SKIP_DIRS = /* @__PURE__ */ new Set([
5776
6165
  ".git",
5777
6166
  "node_modules",
@@ -5796,27 +6185,27 @@ function nodeGrep(opts) {
5796
6185
  } catch (err) {
5797
6186
  throw new Error(`\u65E0\u6548\u7684\u6B63\u5219\u8868\u8FBE\u5F0F: ${opts.pattern}\uFF08${String(err)}\uFF09`);
5798
6187
  }
5799
- const target = path8.resolve(opts.workspace, opts.target);
5800
- if (!fs8.existsSync(target)) {
6188
+ const target = path10.resolve(opts.workspace, opts.target);
6189
+ if (!fs9.existsSync(target)) {
5801
6190
  throw new Error(`\u641C\u7D22\u8DEF\u5F84\u4E0D\u5B58\u5728: ${opts.target}`);
5802
6191
  }
5803
6192
  const matches = [];
5804
6193
  const scanFile = (file) => {
5805
6194
  let stat2;
5806
6195
  try {
5807
- stat2 = fs8.statSync(file);
6196
+ stat2 = fs9.statSync(file);
5808
6197
  } catch {
5809
6198
  return false;
5810
6199
  }
5811
6200
  if (!stat2.isFile() || stat2.size > maxFileBytes) return false;
5812
6201
  let buf;
5813
6202
  try {
5814
- buf = fs8.readFileSync(file);
6203
+ buf = fs9.readFileSync(file);
5815
6204
  } catch {
5816
6205
  return false;
5817
6206
  }
5818
6207
  if (looksBinary(buf)) return false;
5819
- const rel = path8.relative(opts.workspace, file) || path8.basename(file);
6208
+ const rel = path10.relative(opts.workspace, file) || path10.basename(file);
5820
6209
  const lines = buf.toString("utf-8").split("\n");
5821
6210
  for (let i = 0; i < lines.length; i++) {
5822
6211
  const raw = lines[i].replace(/\r$/, "");
@@ -5827,7 +6216,7 @@ function nodeGrep(opts) {
5827
6216
  }
5828
6217
  return false;
5829
6218
  };
5830
- const stat = fs8.statSync(target);
6219
+ const stat = fs9.statSync(target);
5831
6220
  if (stat.isFile()) {
5832
6221
  scanFile(target);
5833
6222
  return matches;
@@ -5837,12 +6226,12 @@ function nodeGrep(opts) {
5837
6226
  const dir = stack.pop();
5838
6227
  let entries;
5839
6228
  try {
5840
- entries = fs8.readdirSync(dir, { withFileTypes: true });
6229
+ entries = fs9.readdirSync(dir, { withFileTypes: true });
5841
6230
  } catch {
5842
6231
  continue;
5843
6232
  }
5844
6233
  for (const entry of entries) {
5845
- const full = path8.join(dir, entry.name);
6234
+ const full = path10.join(dir, entry.name);
5846
6235
  if (entry.isDirectory()) {
5847
6236
  if (!SKIP_DIRS.has(entry.name)) stack.push(full);
5848
6237
  } else if (entry.isFile()) {
@@ -5854,22 +6243,22 @@ function nodeGrep(opts) {
5854
6243
  }
5855
6244
 
5856
6245
  // src/tools/ripgrep.ts
5857
- var fs9 = __toESM(require("fs"), 1);
5858
- var os = __toESM(require("os"), 1);
5859
- var path9 = __toESM(require("path"), 1);
6246
+ var fs10 = __toESM(require("fs"), 1);
6247
+ var os2 = __toESM(require("os"), 1);
6248
+ var path11 = __toESM(require("path"), 1);
5860
6249
  var import_node_module = require("module");
5861
6250
  var cached2;
5862
6251
  function extractBundledRipgrep() {
5863
6252
  try {
5864
- const req = typeof require === "function" ? require : (0, import_node_module.createRequire)(path9.join(process.cwd(), "__zqagent__.js"));
6253
+ const req = typeof require === "function" ? require : (0, import_node_module.createRequire)(path11.join(process.cwd(), "__zqagent__.js"));
5865
6254
  const sea = req("node:sea");
5866
6255
  if (!sea?.isSea?.() || typeof sea.getAsset !== "function") return null;
5867
6256
  const asset = sea.getAsset("rg");
5868
6257
  if (!asset) return null;
5869
- const dir = fs9.mkdtempSync(path9.join(os.tmpdir(), "zqagent-rg-"));
5870
- const file = path9.join(dir, process.platform === "win32" ? "rg.exe" : "rg");
5871
- fs9.writeFileSync(file, Buffer.from(asset));
5872
- fs9.chmodSync(file, 493);
6258
+ const dir = fs10.mkdtempSync(path11.join(os2.tmpdir(), "zqagent-rg-"));
6259
+ const file = path11.join(dir, process.platform === "win32" ? "rg.exe" : "rg");
6260
+ fs10.writeFileSync(file, Buffer.from(asset));
6261
+ fs10.chmodSync(file, 493);
5873
6262
  return file;
5874
6263
  } catch {
5875
6264
  return null;
@@ -5878,7 +6267,7 @@ function extractBundledRipgrep() {
5878
6267
  function resolveRipgrep() {
5879
6268
  if (cached2 !== void 0) return cached2;
5880
6269
  const explicit = (process.env.ZQAGENT_RG ?? "").trim();
5881
- if (explicit && fs9.existsSync(explicit)) {
6270
+ if (explicit && fs10.existsSync(explicit)) {
5882
6271
  cached2 = explicit;
5883
6272
  return cached2;
5884
6273
  }
@@ -5892,7 +6281,7 @@ function resolveRipgrep() {
5892
6281
  env: process.env,
5893
6282
  fileExists: (p) => {
5894
6283
  try {
5895
- return fs9.statSync(p).isFile();
6284
+ return fs10.statSync(p).isFile();
5896
6285
  } catch {
5897
6286
  return false;
5898
6287
  }
@@ -6439,8 +6828,8 @@ function registerWebTools(registry) {
6439
6828
  }
6440
6829
 
6441
6830
  // src/tools/git.ts
6442
- var fs10 = __toESM(require("fs"), 1);
6443
- var path10 = __toESM(require("path"), 1);
6831
+ var fs11 = __toESM(require("fs"), 1);
6832
+ var path12 = __toESM(require("path"), 1);
6444
6833
  async function runGit(repoDir, args, timeout = 15e3) {
6445
6834
  const res = await execAsync("git", ["-c", "core.quotepath=false", ...args], {
6446
6835
  cwd: repoDir,
@@ -6457,9 +6846,9 @@ async function runGit(repoDir, args, timeout = 15e3) {
6457
6846
  async function resolveRepoRoot(workspace, pathArg) {
6458
6847
  let dir = workspace;
6459
6848
  if (pathArg) {
6460
- const abs = path10.resolve(workspace, pathArg);
6849
+ const abs = path12.resolve(workspace, pathArg);
6461
6850
  try {
6462
- dir = fs10.statSync(abs).isDirectory() ? abs : path10.dirname(abs);
6851
+ dir = fs11.statSync(abs).isDirectory() ? abs : path12.dirname(abs);
6463
6852
  } catch {
6464
6853
  dir = abs;
6465
6854
  }
@@ -6472,24 +6861,24 @@ function repoDirArg(workspace, raw) {
6472
6861
  if (typeof raw !== "string") return void 0;
6473
6862
  const v = raw.trim();
6474
6863
  if (!v) return void 0;
6475
- const base = path10.resolve(workspace);
6476
- const abs = path10.resolve(base, v);
6477
- const rel = path10.relative(base, abs);
6478
- if (rel.startsWith("..") || path10.isAbsolute(rel)) return void 0;
6864
+ const base = path12.resolve(workspace);
6865
+ const abs = path12.resolve(base, v);
6866
+ const rel = path12.relative(base, abs);
6867
+ if (rel.startsWith("..") || path12.isAbsolute(rel)) return void 0;
6479
6868
  return abs;
6480
6869
  }
6481
6870
  function toPathspec(repoRoot, workspace, pathArg) {
6482
6871
  if (!pathArg) return null;
6483
- const abs = path10.resolve(workspace, pathArg);
6484
- let rel = path10.relative(repoRoot, abs);
6872
+ const abs = path12.resolve(workspace, pathArg);
6873
+ let rel = path12.relative(repoRoot, abs);
6485
6874
  if (rel === "") return ".";
6486
- if (rel.startsWith("..") || path10.isAbsolute(rel)) {
6875
+ if (rel.startsWith("..") || path12.isAbsolute(rel)) {
6487
6876
  try {
6488
- const realRepo = fs10.realpathSync(repoRoot);
6489
- const realAbs = fs10.realpathSync(abs);
6490
- const rel2 = path10.relative(realRepo, realAbs);
6877
+ const realRepo = fs11.realpathSync(repoRoot);
6878
+ const realAbs = fs11.realpathSync(abs);
6879
+ const rel2 = path12.relative(realRepo, realAbs);
6491
6880
  if (rel2 === "") return ".";
6492
- if (!rel2.startsWith("..") && !path10.isAbsolute(rel2)) return rel2;
6881
+ if (!rel2.startsWith("..") && !path12.isAbsolute(rel2)) return rel2;
6493
6882
  } catch {
6494
6883
  }
6495
6884
  return null;
@@ -6634,10 +7023,10 @@ function registerGitTools(registry, workspace) {
6634
7023
  }
6635
7024
 
6636
7025
  // src/tools/diff.ts
6637
- var fs11 = __toESM(require("fs"), 1);
6638
- var path11 = __toESM(require("path"), 1);
7026
+ var fs12 = __toESM(require("fs"), 1);
7027
+ var path13 = __toESM(require("path"), 1);
6639
7028
  function makeApplyPatch(workspace) {
6640
- const root = path11.resolve(workspace);
7029
+ const root = path13.resolve(workspace);
6641
7030
  return function apply_patch(args) {
6642
7031
  const patch = String(args.patch ?? "");
6643
7032
  if (!patch.trim()) return { error: "\u8865\u4E01\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A" };
@@ -6676,12 +7065,12 @@ function makeApplyPatch(workspace) {
6676
7065
  contentLines.push(current.slice(1));
6677
7066
  i++;
6678
7067
  }
6679
- if (fs11.existsSync(target)) {
7068
+ if (fs12.existsSync(target)) {
6680
7069
  return { error: `\u6587\u4EF6\u5DF2\u5B58\u5728: ${filePath}` };
6681
7070
  }
6682
- fs11.mkdirSync(path11.dirname(target), { recursive: true });
6683
- fs11.writeFileSync(target, contentLines.join("\n") + "\n", "utf-8");
6684
- const rel = path11.relative(resolved.root, target);
7071
+ fs12.mkdirSync(path13.dirname(target), { recursive: true });
7072
+ fs12.writeFileSync(target, contentLines.join("\n") + "\n", "utf-8");
7073
+ const rel = path13.relative(resolved.root, target);
6685
7074
  changedFiles.push(rel);
6686
7075
  addedFiles.push(rel);
6687
7076
  fileCount++;
@@ -6694,11 +7083,11 @@ function makeApplyPatch(workspace) {
6694
7083
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
6695
7084
  }
6696
7085
  const target = entry.real;
6697
- if (!fs11.existsSync(target)) {
7086
+ if (!fs12.existsSync(target)) {
6698
7087
  return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
6699
7088
  }
6700
- fs11.unlinkSync(target);
6701
- deletedFiles.push(path11.relative(entry.root, target));
7089
+ fs12.unlinkSync(target);
7090
+ deletedFiles.push(path13.relative(entry.root, target));
6702
7091
  fileCount++;
6703
7092
  i++;
6704
7093
  continue;
@@ -6734,10 +7123,10 @@ function makeApplyPatch(workspace) {
6734
7123
  if (!chunkLines.length) {
6735
7124
  return { error: `\u8865\u4E01\u66F4\u65B0\u6CA1\u6709 hunks: ${filePath}` };
6736
7125
  }
6737
- if (!fs11.existsSync(target)) {
7126
+ if (!fs12.existsSync(target)) {
6738
7127
  return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
6739
7128
  }
6740
- const oldLines = fs11.readFileSync(target, "utf-8").split("\n");
7129
+ const oldLines = fs12.readFileSync(target, "utf-8").split("\n");
6741
7130
  const newLines = applyPatchHunks(oldLines, chunkLines, filePath);
6742
7131
  let finalResolved = resolved;
6743
7132
  if (moveTo) {
@@ -6748,20 +7137,20 @@ function makeApplyPatch(workspace) {
6748
7137
  finalResolved = moved;
6749
7138
  }
6750
7139
  const finalTarget = finalResolved.real;
6751
- if (moveTo && fs11.existsSync(finalTarget) && finalTarget !== target) {
7140
+ if (moveTo && fs12.existsSync(finalTarget) && finalTarget !== target) {
6752
7141
  return { error: `\u6587\u4EF6\u5DF2\u5B58\u5728: ${moveTo}` };
6753
7142
  }
6754
- fs11.mkdirSync(path11.dirname(finalTarget), { recursive: true });
6755
- fs11.writeFileSync(finalTarget, newLines.join("\n"), "utf-8");
7143
+ fs12.mkdirSync(path13.dirname(finalTarget), { recursive: true });
7144
+ fs12.writeFileSync(finalTarget, newLines.join("\n"), "utf-8");
6756
7145
  if (moveTo && finalTarget !== target) {
6757
7146
  const entry = resolveEntryWithinRoot(root, filePath);
6758
7147
  if (!entry) {
6759
7148
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
6760
7149
  }
6761
- fs11.unlinkSync(entry.real);
6762
- deletedFiles.push(path11.relative(resolved.root, target));
7150
+ fs12.unlinkSync(entry.real);
7151
+ deletedFiles.push(path13.relative(resolved.root, target));
6763
7152
  }
6764
- changedFiles.push(path11.relative(finalResolved.root, finalTarget));
7153
+ changedFiles.push(path13.relative(finalResolved.root, finalTarget));
6765
7154
  fileCount++;
6766
7155
  continue;
6767
7156
  }
@@ -6773,18 +7162,18 @@ function makeApplyPatch(workspace) {
6773
7162
  return { changed_files: changedFiles, added_files: addedFiles, deleted_files: deletedFiles, file_count: fileCount };
6774
7163
  };
6775
7164
  }
6776
- function applyPatchHunks(oldLines, chunks, path14) {
7165
+ function applyPatchHunks(oldLines, chunks, path16) {
6777
7166
  let newLines = [...oldLines];
6778
7167
  let cursor = 0;
6779
7168
  for (const chunk of chunks) {
6780
7169
  const oldBlock = chunk.filter((l) => l[0] === " " || l[0] === "-").map((l) => l.slice(1));
6781
7170
  const newBlock = chunk.filter((l) => l[0] === " " || l[0] === "+").map((l) => l.slice(1));
6782
7171
  if (!oldBlock.length) {
6783
- throw new Error(`\u8865\u4E01 hunk \u5FC5\u987B\u5305\u542B\u4E0A\u4E0B\u6587\u6216\u5220\u9664\u884C: ${path14}`);
7172
+ throw new Error(`\u8865\u4E01 hunk \u5FC5\u987B\u5305\u542B\u4E0A\u4E0B\u6587\u6216\u5220\u9664\u884C: ${path16}`);
6784
7173
  }
6785
7174
  const matchStart = findBlockMatch(newLines, oldBlock, cursor);
6786
7175
  if (matchStart === -1) {
6787
- throw new Error(`\u8865\u4E01\u4E0A\u4E0B\u6587\u4E0D\u5339\u914D: ${path14}`);
7176
+ throw new Error(`\u8865\u4E01\u4E0A\u4E0B\u6587\u4E0D\u5339\u914D: ${path16}`);
6788
7177
  }
6789
7178
  const matchEnd = matchStart + oldBlock.length;
6790
7179
  newLines.splice(matchStart, oldBlock.length, ...newBlock);
@@ -6801,7 +7190,7 @@ function findBlockMatch(lines, block, start) {
6801
7190
  return -1;
6802
7191
  }
6803
7192
  function makeApplyUnifiedDiff(workspace) {
6804
- const root = path11.resolve(workspace);
7193
+ const root = path13.resolve(workspace);
6805
7194
  return function apply_unified_diff(args) {
6806
7195
  const diff = String(args.diff ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
6807
7196
  if (!diff.trim()) return { error: "diff \u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A" };
@@ -6820,26 +7209,26 @@ function makeApplyUnifiedDiff(workspace) {
6820
7209
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${targetPath}` };
6821
7210
  }
6822
7211
  const target = resolved.real;
6823
- const oldLines = oldIsDevNull ? [] : fs11.existsSync(target) ? fs11.readFileSync(target, "utf-8").split("\n") : [];
7212
+ const oldLines = oldIsDevNull ? [] : fs12.existsSync(target) ? fs12.readFileSync(target, "utf-8").split("\n") : [];
6824
7213
  const newLines = applyDiffHunks(oldLines, patch.hunks, targetPath);
6825
7214
  hunkCount += patch.hunks.length;
6826
7215
  if (newIsDevNull) {
6827
- if (fs11.existsSync(target)) {
6828
- if (!fs11.statSync(target).isFile()) {
7216
+ if (fs12.existsSync(target)) {
7217
+ if (!fs12.statSync(target).isFile()) {
6829
7218
  return { error: `\u8DEF\u5F84\u4E0D\u662F\u6587\u4EF6: ${targetPath}` };
6830
7219
  }
6831
7220
  const entry = resolveEntryWithinRoot(root, targetPath);
6832
7221
  if (!entry) {
6833
7222
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${targetPath}` };
6834
7223
  }
6835
- fs11.unlinkSync(entry.real);
7224
+ fs12.unlinkSync(entry.real);
6836
7225
  }
6837
- deletedFiles.push(path11.relative(resolved.root, target));
7226
+ deletedFiles.push(path13.relative(resolved.root, target));
6838
7227
  continue;
6839
7228
  }
6840
- fs11.mkdirSync(path11.dirname(target), { recursive: true });
6841
- fs11.writeFileSync(target, newLines.join("\n"), "utf-8");
6842
- const rel = path11.relative(resolved.root, target);
7229
+ fs12.mkdirSync(path13.dirname(target), { recursive: true });
7230
+ fs12.writeFileSync(target, newLines.join("\n"), "utf-8");
7231
+ const rel = path13.relative(resolved.root, target);
6843
7232
  changedFiles.push(rel);
6844
7233
  if (oldIsDevNull) addedFiles.push(rel);
6845
7234
  }
@@ -7084,7 +7473,8 @@ function buildExplorerEngine(opts) {
7084
7473
  model: opts.model,
7085
7474
  instructions: EXPLORER_INSTRUCTIONS,
7086
7475
  maxIterations: parseMaxIterations(process.env.EXPLORE_MAX_ITERATIONS, 30),
7087
- hook: opts.hook
7476
+ hook: opts.hook,
7477
+ toolResultDir: opts.toolResultDir
7088
7478
  });
7089
7479
  }
7090
7480
  function makeExploreTool(opts) {
@@ -7096,7 +7486,8 @@ function makeExploreTool(opts) {
7096
7486
  provider: opts.provider,
7097
7487
  model: opts.model,
7098
7488
  readonlyRegistrars: opts.readonlyRegistrars,
7099
- hook: opts.hookFactory?.()
7489
+ hook: opts.hookFactory?.(),
7490
+ toolResultDir: opts.toolResultDir
7100
7491
  });
7101
7492
  let report = "";
7102
7493
  let status = "unknown";
@@ -7156,13 +7547,13 @@ function registerExploreTool(registry, opts) {
7156
7547
  }
7157
7548
 
7158
7549
  // src/tools/media.ts
7159
- var fs13 = __toESM(require("fs"), 1);
7160
- var path13 = __toESM(require("path"), 1);
7550
+ var fs14 = __toESM(require("fs"), 1);
7551
+ var path15 = __toESM(require("path"), 1);
7161
7552
 
7162
7553
  // src/tools/ffmpegRuntime.ts
7163
- var fs12 = __toESM(require("fs"), 1);
7164
- var os2 = __toESM(require("os"), 1);
7165
- var path12 = __toESM(require("path"), 1);
7554
+ var fs13 = __toESM(require("fs"), 1);
7555
+ var os3 = __toESM(require("os"), 1);
7556
+ var path14 = __toESM(require("path"), 1);
7166
7557
  var import_node_module2 = require("module");
7167
7558
  var import_node_crypto = require("crypto");
7168
7559
  var import_promises = require("stream/promises");
@@ -7185,29 +7576,29 @@ if (PROXY_ENV2) {
7185
7576
  }
7186
7577
  var DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
7187
7578
  function agentBinDir(workspace) {
7188
- return path12.join(path12.resolve(workspace), ".agent", "bin");
7579
+ return path14.join(path14.resolve(workspace), ".agent", "bin");
7189
7580
  }
7190
7581
  function exeName(name, platform) {
7191
7582
  return platform === "win32" ? `${name}.exe` : name;
7192
7583
  }
7193
7584
  function defaultFileExists2(p) {
7194
7585
  try {
7195
- return fs12.statSync(p).isFile();
7586
+ return fs13.statSync(p).isFile();
7196
7587
  } catch {
7197
7588
  return false;
7198
7589
  }
7199
7590
  }
7200
7591
  function extractBundled(name, platform) {
7201
7592
  try {
7202
- const req = typeof require === "function" ? require : (0, import_node_module2.createRequire)(path12.join(process.cwd(), "__zqagent__.js"));
7593
+ const req = typeof require === "function" ? require : (0, import_node_module2.createRequire)(path14.join(process.cwd(), "__zqagent__.js"));
7203
7594
  const sea = req("node:sea");
7204
7595
  if (!sea?.isSea?.() || typeof sea.getAsset !== "function") return null;
7205
7596
  const asset = sea.getAsset(name);
7206
7597
  if (!asset) return null;
7207
- const dir = fs12.mkdtempSync(path12.join(os2.tmpdir(), `zqagent-${name}-`));
7208
- const file = path12.join(dir, exeName(name, platform));
7209
- fs12.writeFileSync(file, Buffer.from(asset));
7210
- fs12.chmodSync(file, 493);
7598
+ const dir = fs13.mkdtempSync(path14.join(os3.tmpdir(), `zqagent-${name}-`));
7599
+ const file = path14.join(dir, exeName(name, platform));
7600
+ fs13.writeFileSync(file, Buffer.from(asset));
7601
+ fs13.chmodSync(file, 493);
7211
7602
  return file;
7212
7603
  } catch {
7213
7604
  return null;
@@ -7217,7 +7608,7 @@ function resolveOne(name, workspace, o) {
7217
7608
  const envKey = name === "ffmpeg" ? ENV_FFMPEG : ENV_FFPROBE;
7218
7609
  const explicit = (o.env[envKey] ?? "").trim();
7219
7610
  if (explicit && o.fileExists(explicit)) return { path: explicit, source: "env" };
7220
- const local = path12.join(agentBinDir(workspace), exeName(name, o.platform));
7611
+ const local = path14.join(agentBinDir(workspace), exeName(name, o.platform));
7221
7612
  if (o.fileExists(local)) return { path: local, source: "agent-bin" };
7222
7613
  const bundled = extractBundled(name, o.platform);
7223
7614
  if (bundled) return { path: bundled, source: "bundled" };
@@ -7231,7 +7622,7 @@ function resolveOne(name, workspace, o) {
7231
7622
  var cache = /* @__PURE__ */ new Map();
7232
7623
  function resolveMedia(workspace, opts = {}) {
7233
7624
  const platform = opts.platform ?? process.platform;
7234
- const key = `${platform}:${path12.resolve(workspace)}`;
7625
+ const key = `${platform}:${path14.resolve(workspace)}`;
7235
7626
  const hit = cache.get(key);
7236
7627
  if (hit) return hit;
7237
7628
  const ctx = {
@@ -7337,7 +7728,7 @@ function parseChecksumFile(text, filename) {
7337
7728
  if (!m) continue;
7338
7729
  const hash = m[1].toLowerCase();
7339
7730
  const name = m[2]?.trim() ?? "";
7340
- if (!name || name === filename || path12.basename(name) === filename) return hash;
7731
+ if (!name || name === filename || path14.basename(name) === filename) return hash;
7341
7732
  }
7342
7733
  const single = lines[0]?.trim();
7343
7734
  if (lines.length === 1 && single && /^[0-9a-f]{32,128}$/i.test(single)) {
@@ -7347,7 +7738,7 @@ function parseChecksumFile(text, filename) {
7347
7738
  }
7348
7739
  async function computeChecksum(file, algo) {
7349
7740
  const hash = (0, import_node_crypto.createHash)(algo);
7350
- await (0, import_promises.pipeline)(fs12.createReadStream(file), hash);
7741
+ await (0, import_promises.pipeline)(fs13.createReadStream(file), hash);
7351
7742
  return hash.digest("hex");
7352
7743
  }
7353
7744
  function fetchInit() {
@@ -7362,7 +7753,7 @@ async function downloadToFile(url, dest) {
7362
7753
  const resp = await (0, import_node_fetch3.default)(url, fetchInit());
7363
7754
  if (!resp.ok) throw new Error(`HTTP ${resp.status} ${resp.statusText}`);
7364
7755
  if (!resp.body) throw new Error("\u54CD\u5E94\u6CA1\u6709\u5185\u5BB9");
7365
- await (0, import_promises.pipeline)(resp.body, fs12.createWriteStream(dest));
7756
+ await (0, import_promises.pipeline)(resp.body, fs13.createWriteStream(dest));
7366
7757
  }
7367
7758
  async function resolveComponentUrl(c) {
7368
7759
  if (c.url) return c.url;
@@ -7379,12 +7770,12 @@ function findBinaryInTree(dir, names) {
7379
7770
  const cur = stack.pop();
7380
7771
  let entries;
7381
7772
  try {
7382
- entries = fs12.readdirSync(cur, { withFileTypes: true });
7773
+ entries = fs13.readdirSync(cur, { withFileTypes: true });
7383
7774
  } catch {
7384
7775
  continue;
7385
7776
  }
7386
7777
  for (const e of entries) {
7387
- const full = path12.join(cur, e.name);
7778
+ const full = path14.join(cur, e.name);
7388
7779
  if (e.isDirectory()) {
7389
7780
  stack.push(full);
7390
7781
  } else if (names.includes(e.name)) {
@@ -7395,7 +7786,7 @@ function findBinaryInTree(dir, names) {
7395
7786
  return null;
7396
7787
  }
7397
7788
  async function extractArchive(archive, dest, kind, platform) {
7398
- fs12.mkdirSync(dest, { recursive: true });
7789
+ fs13.mkdirSync(dest, { recursive: true });
7399
7790
  let res;
7400
7791
  if (kind === "tar.xz") {
7401
7792
  res = await execAsync("tar", ["-xJf", archive, "-C", dest]);
@@ -7431,7 +7822,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
7431
7822
  error: `\u6682\u4E0D\u652F\u6301\u81EA\u52A8\u4E0B\u8F7D\u7684\u5E73\u53F0: ${platform}/${arch}`
7432
7823
  };
7433
7824
  }
7434
- const tmp = fs12.mkdtempSync(path12.join(os2.tmpdir(), "zqagent-ffmpeg-"));
7825
+ const tmp = fs13.mkdtempSync(path14.join(os3.tmpdir(), "zqagent-ffmpeg-"));
7435
7826
  const binDir = agentBinDir(workspace);
7436
7827
  const installed = {};
7437
7828
  try {
@@ -7445,7 +7836,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
7445
7836
  }
7446
7837
  let archive = fetched.get(url);
7447
7838
  if (!archive) {
7448
- const archivePath = path12.join(tmp, path12.basename(new URL(url).pathname) || `${c.name}.${c.archive}`);
7839
+ const archivePath = path14.join(tmp, path14.basename(new URL(url).pathname) || `${c.name}.${c.archive}`);
7449
7840
  log(`\u4E0B\u8F7D ${url}`);
7450
7841
  try {
7451
7842
  await downloadToFile(url, archivePath);
@@ -7455,7 +7846,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
7455
7846
  if (c.checksumUrl) {
7456
7847
  const algo = c.checksumAlgo ?? "sha256";
7457
7848
  try {
7458
- const expected = parseChecksumFile(await fetchText(c.checksumUrl), path12.basename(archivePath));
7849
+ const expected = parseChecksumFile(await fetchText(c.checksumUrl), path14.basename(archivePath));
7459
7850
  if (!expected) {
7460
7851
  notes.push(`\u672A\u80FD\u4ECE ${c.checksumUrl} \u89E3\u6790\u51FA\u6821\u9A8C\u548C\uFF0C\u5DF2\u8DF3\u8FC7\u6821\u9A8C`);
7461
7852
  } else {
@@ -7472,24 +7863,24 @@ async function downloadFfmpeg(workspace, opts = {}) {
7472
7863
  notes.push(`\u6821\u9A8C\u548C\u83B7\u53D6\u5931\u8D25 (${c.checksumUrl}): ${String(e instanceof Error ? e.message : e)}`);
7473
7864
  }
7474
7865
  } else {
7475
- notes.push(`${path12.basename(archivePath)} \u672A\u63D0\u4F9B\u6821\u9A8C\u548C\uFF08\u4EC5 HTTPS \u4F20\u8F93\u4FDD\u62A4\uFF09`);
7866
+ notes.push(`${path14.basename(archivePath)} \u672A\u63D0\u4F9B\u6821\u9A8C\u548C\uFF08\u4EC5 HTTPS \u4F20\u8F93\u4FDD\u62A4\uFF09`);
7476
7867
  }
7477
7868
  archive = { file: archivePath, kind: c.archive };
7478
7869
  fetched.set(url, archive);
7479
7870
  }
7480
- const extractDir = fs12.mkdtempSync(path12.join(tmp, "x-"));
7871
+ const extractDir = fs13.mkdtempSync(path14.join(tmp, "x-"));
7481
7872
  const err = await extractArchive(archive.file, extractDir, archive.kind, platform);
7482
7873
  if (err) return { ok: false, error: err };
7483
7874
  const found = findBinaryInTree(extractDir, [exeName(c.name, platform), c.name]);
7484
7875
  if (!found) {
7485
- return { ok: false, error: `\u5F52\u6863\u5185\u672A\u627E\u5230 ${c.name}: ${path12.basename(archive.file)}` };
7876
+ return { ok: false, error: `\u5F52\u6863\u5185\u672A\u627E\u5230 ${c.name}: ${path14.basename(archive.file)}` };
7486
7877
  }
7487
- fs12.mkdirSync(binDir, { recursive: true });
7488
- const target = path12.join(binDir, exeName(c.name, platform));
7878
+ fs13.mkdirSync(binDir, { recursive: true });
7879
+ const target = path14.join(binDir, exeName(c.name, platform));
7489
7880
  const staging = `${target}.tmp-${process.pid}`;
7490
- fs12.copyFileSync(found, staging);
7491
- fs12.chmodSync(staging, 493);
7492
- fs12.renameSync(staging, target);
7881
+ fs13.copyFileSync(found, staging);
7882
+ fs13.chmodSync(staging, 493);
7883
+ fs13.renameSync(staging, target);
7493
7884
  installed[c.name] = target;
7494
7885
  }
7495
7886
  resetFfmpegCache();
@@ -7498,7 +7889,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
7498
7889
  } catch (e) {
7499
7890
  return { ok: false, error: `\u5B89\u88C5\u5931\u8D25: ${String(e instanceof Error ? e.message : e)}` };
7500
7891
  } finally {
7501
- fs12.rmSync(tmp, { recursive: true, force: true });
7892
+ fs13.rmSync(tmp, { recursive: true, force: true });
7502
7893
  }
7503
7894
  }
7504
7895
  function canAutoDownload(platform, arch) {
@@ -7763,13 +8154,13 @@ function resolveTarget(workspace, value) {
7763
8154
  return { abs: resolved.real, root: resolved.root };
7764
8155
  }
7765
8156
  function relOut(workspace, abs) {
7766
- return path13.relative(workspace, abs) || abs;
8157
+ return path15.relative(workspace, abs) || abs;
7767
8158
  }
7768
8159
  function registerMediaTool(registry, workspace, opts = {}) {
7769
8160
  const platform = opts.platform ?? process.platform;
7770
8161
  const arch = process.arch;
7771
8162
  const media = async (args) => {
7772
- const root = path13.resolve(workspace);
8163
+ const root = path15.resolve(workspace);
7773
8164
  const op = str(args["op"]);
7774
8165
  if (!op) {
7775
8166
  return { error: `\u7F3A\u5C11\u53C2\u6570 op\u3002\u53EF\u9009\u503C\uFF1A${OPS.join(" / ")}` };
@@ -7814,7 +8205,7 @@ function registerMediaTool(registry, workspace, opts = {}) {
7814
8205
  if (!input) return { error: `\u64CD\u4F5C ${op} \u9700\u8981\u53C2\u6570 input\uFF08\u5DE5\u4F5C\u533A\u5185\u76F8\u5BF9\u8DEF\u5F84\uFF09` };
7815
8206
  const r = resolveTarget(root, input);
7816
8207
  if ("error" in r) return { error: r.error };
7817
- if (!fs13.existsSync(r.abs) || !fs13.statSync(r.abs).isFile()) {
8208
+ if (!fs14.existsSync(r.abs) || !fs14.statSync(r.abs).isFile()) {
7818
8209
  return { error: `\u8F93\u5165\u6587\u4EF6\u4E0D\u5B58\u5728: ${input}` };
7819
8210
  }
7820
8211
  inputAbs = r.abs;
@@ -7826,7 +8217,7 @@ function registerMediaTool(registry, workspace, opts = {}) {
7826
8217
  if (!output) return { error: `\u64CD\u4F5C ${op} \u9700\u8981\u53C2\u6570 output\uFF08\u5DE5\u4F5C\u533A\u5185\u76F8\u5BF9\u8DEF\u5F84\uFF09` };
7827
8218
  const r = resolveTarget(root, output);
7828
8219
  if ("error" in r) return { error: r.error };
7829
- fs13.mkdirSync(path13.dirname(r.abs), { recursive: true });
8220
+ fs14.mkdirSync(path15.dirname(r.abs), { recursive: true });
7830
8221
  outputAbs = r.abs;
7831
8222
  relRoot = r.root;
7832
8223
  }
@@ -7876,7 +8267,7 @@ function registerMediaTool(registry, workspace, opts = {}) {
7876
8267
  };
7877
8268
  if (outputAbs) {
7878
8269
  try {
7879
- result["size_bytes"] = fs13.statSync(outputAbs).size;
8270
+ result["size_bytes"] = fs14.statSync(outputAbs).size;
7880
8271
  } catch {
7881
8272
  }
7882
8273
  }
@@ -8116,7 +8507,9 @@ function createDefaultEngine(opts) {
8116
8507
  subagentHookFactory,
8117
8508
  contextWindow,
8118
8509
  thresholdPct,
8510
+ softThresholdPct,
8119
8511
  capTokens,
8512
+ toolResultDir: toolResultDir2,
8120
8513
  logDir
8121
8514
  } = opts;
8122
8515
  const envEffort = process.env.OPENAI_REASONING_EFFORT?.toLowerCase();
@@ -8139,7 +8532,8 @@ function createDefaultEngine(opts) {
8139
8532
  workspace,
8140
8533
  provider: effectiveProvider,
8141
8534
  model,
8142
- hookFactory: subagentHookFactory
8535
+ hookFactory: subagentHookFactory,
8536
+ toolResultDir: toolResultDir2
8143
8537
  });
8144
8538
  const permissions = new PermissionEngine({
8145
8539
  workspaceRoot: workspace,
@@ -8161,7 +8555,9 @@ function createDefaultEngine(opts) {
8161
8555
  hook,
8162
8556
  contextWindow,
8163
8557
  thresholdPct,
8164
- capTokens
8558
+ softThresholdPct,
8559
+ capTokens,
8560
+ toolResultDir: toolResultDir2
8165
8561
  });
8166
8562
  return engine;
8167
8563
  }