@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.
@@ -572,8 +572,8 @@ var import_config = require("dotenv/config");
572
572
 
573
573
  // src/server/runtime.ts
574
574
  init_cjs_shims();
575
- var fs27 = __toESM(require("fs"), 1);
576
- var path31 = __toESM(require("path"), 1);
575
+ var fs28 = __toESM(require("fs"), 1);
576
+ var path33 = __toESM(require("path"), 1);
577
577
 
578
578
  // src/server/store.ts
579
579
  init_cjs_shims();
@@ -1062,6 +1062,10 @@ function configDir() {
1062
1062
  const base = process.env.XDG_CONFIG_HOME || (IS_WINDOWS ? process.env.APPDATA : void 0) || path2.join(homeDir(), ".config");
1063
1063
  return path2.join(base, APP_DIR_NAME);
1064
1064
  }
1065
+ function stateDir() {
1066
+ const base = process.env.XDG_STATE_HOME || (IS_WINDOWS ? process.env.LOCALAPPDATA : void 0) || path2.join(homeDir(), ".local", "state");
1067
+ return path2.join(base, APP_DIR_NAME);
1068
+ }
1065
1069
 
1066
1070
  // ../shared/src/llm-presets.ts
1067
1071
  init_cjs_shims();
@@ -1625,6 +1629,7 @@ var Store = class {
1625
1629
 
1626
1630
  // src/server/session.ts
1627
1631
  init_cjs_shims();
1632
+ var path20 = __toESM(require("path"), 1);
1628
1633
  var import_async_hooks = require("async_hooks");
1629
1634
 
1630
1635
  // src/engine/TurnEngine.ts
@@ -1752,8 +1757,11 @@ function lookupContextWindow(modelId) {
1752
1757
  return DEFAULT_CONTEXT_WINDOW;
1753
1758
  }
1754
1759
  var KEEP_RECENT_FRACTION = 0.25;
1755
- var SPAN_TOOL_RESULT_CLIP = 400;
1756
- var SPAN_BUDGET_CHARS = 4e5;
1760
+ var SOFT_THRESHOLD_RATIO = 0.75;
1761
+ var ELIDE_KEEP_FRACTION = 0.15;
1762
+ var SUMMARY_MAX_TOKENS = 3e3;
1763
+ var SPAN_TOOL_RESULT_CLIP = 200;
1764
+ var SPAN_BUDGET_CHARS = 8e4;
1757
1765
  var USER_MESSAGE_CLIP = 600;
1758
1766
  var USER_MESSAGES_MAX = 40;
1759
1767
  var TRIM_FRACTION = 0.1;
@@ -1776,9 +1784,22 @@ function triggerTokens(contextWindow, thresholdPct = DEFAULT_THRESHOLD_PCT, capT
1776
1784
  }
1777
1785
  return base;
1778
1786
  }
1787
+ function softTriggerTokens(contextWindow, thresholdPct = DEFAULT_THRESHOLD_PCT, capTokens, softThresholdPct) {
1788
+ const hard = triggerTokens(contextWindow, thresholdPct, capTokens);
1789
+ if (softThresholdPct != null && Number.isFinite(softThresholdPct) && softThresholdPct > 0) {
1790
+ return Math.min(
1791
+ triggerTokens(contextWindow, softThresholdPct, capTokens),
1792
+ Math.floor(hard * 0.95)
1793
+ );
1794
+ }
1795
+ return Math.floor(hard * SOFT_THRESHOLD_RATIO);
1796
+ }
1779
1797
  function shouldCompact(signal, contextWindow, thresholdPct, capTokens) {
1780
1798
  return signal >= triggerTokens(contextWindow, thresholdPct, capTokens);
1781
1799
  }
1800
+ function elisionKeepChars(hardTriggerTokens, fraction = ELIDE_KEEP_FRACTION) {
1801
+ return Math.max(4e3, Math.floor(hardTriggerTokens * fraction * 4));
1802
+ }
1782
1803
  function turnStarts(messages, start) {
1783
1804
  const users = [];
1784
1805
  const assistants = [];
@@ -1928,10 +1949,11 @@ var SUMMARY_SYSTEM_PROMPT = `\u4F60\u6B63\u5728\u538B\u7F29\u4E00\u4E2A AI \u534
1928
1949
 
1929
1950
  \u89C4\u5219\uFF1A
1930
1951
  - \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
1952
+ - \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
1931
1953
  - \u8981\u5177\u4F53\uFF1A\u8DEF\u5F84\u3001\u540D\u79F0\u3001\u547D\u4EE4\u3001id\u2014\u2014\u800C\u4E0D\u662F\u6A21\u7CCA\u7684\u5F15\u7528\u3002
1932
1954
  - \u4EC5\u8F93\u51FA\u6458\u8981\u7AE0\u8282\uFF0C\u4E0D\u9644\u52A0\u524D\u8A00\u3002`;
1933
1955
  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";
1934
- function renderSpan(span, budgetChars = SPAN_BUDGET_CHARS) {
1956
+ function renderSpan(span, budgetChars2 = SPAN_BUDGET_CHARS) {
1935
1957
  const lines = [];
1936
1958
  for (const m of span) {
1937
1959
  const role = m.role;
@@ -1955,8 +1977,8 @@ function renderSpan(span, budgetChars = SPAN_BUDGET_CHARS) {
1955
1977
  if (text) lines.push(`[${role}] ${text}`);
1956
1978
  }
1957
1979
  let rendered = lines.join("\n");
1958
- if (rendered.length > budgetChars) {
1959
- rendered = "(\u2026oldest turns elided\u2026)\n" + rendered.slice(-budgetChars);
1980
+ if (rendered.length > budgetChars2) {
1981
+ rendered = "(\u2026oldest turns elided\u2026)\n" + rendered.slice(-budgetChars2);
1960
1982
  }
1961
1983
  return rendered;
1962
1984
  }
@@ -1970,9 +1992,17 @@ function summarizerMessages(span, priorSummary = "") {
1970
1992
  { role: "user", content: body }
1971
1993
  ];
1972
1994
  }
1995
+ function summarizerReasoningEffort() {
1996
+ const v = process.env.COMPACTION_REASONING_EFFORT?.toLowerCase();
1997
+ if (v === "low" || v === "medium" || v === "high") return v;
1998
+ return "low";
1999
+ }
1973
2000
  async function summarizeSpan(provider, model, span, priorSummary = "", opts) {
1974
2001
  const msgs = summarizerMessages(span, priorSummary);
1975
- const chunks = await provider.stream(model, msgs, [], opts);
2002
+ const chunks = await provider.stream(model, msgs, [], {
2003
+ ...opts,
2004
+ maxTokens: SUMMARY_MAX_TOKENS
2005
+ });
1976
2006
  let text = "";
1977
2007
  for await (const chunk of chunks) {
1978
2008
  if (chunk.content) text += chunk.content;
@@ -2083,6 +2113,166 @@ function applyToOutbound(messages, state) {
2083
2113
  head.push({ role: "user", content: compactedBlock(state) });
2084
2114
  return [...head, ...messages.slice(boundary)];
2085
2115
  }
2116
+ var ELIDE_EXEMPT_TOOLS = /* @__PURE__ */ new Set([
2117
+ "explore",
2118
+ "ask_user",
2119
+ "propose_plan",
2120
+ "request_directory",
2121
+ "todo_write"
2122
+ ]);
2123
+ function elidedToolContent(m) {
2124
+ const name = m.tool_call_name || "tool";
2125
+ const size = m.content.length;
2126
+ const saved = /full output saved to ([^\]]+)/.exec(m.content)?.[1]?.trim();
2127
+ 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]`;
2128
+ }
2129
+ function isPlaceholder(content) {
2130
+ return content.startsWith("[old tool result elided") || content.startsWith("[superseded by a later read_file");
2131
+ }
2132
+ var ONE_SHOT_TOOLS = /* @__PURE__ */ new Set(["git_diff"]);
2133
+ var ONE_SHOT_KEEP_RECENT = 1;
2134
+ function applyOneShotElision(messages, keepRecent = ONE_SHOT_KEEP_RECENT, tools = ONE_SHOT_TOOLS) {
2135
+ const out = [...messages];
2136
+ let seen = 0;
2137
+ let elided = 0;
2138
+ let freed = 0;
2139
+ for (let i = messages.length - 1; i >= 0; i--) {
2140
+ const m = messages[i];
2141
+ if (m.role !== "tool" || !tools.has(m.tool_call_name ?? "")) continue;
2142
+ if (isPlaceholder(m.content)) continue;
2143
+ seen++;
2144
+ if (seen <= keepRecent) continue;
2145
+ const placeholder = elidedToolContent(m);
2146
+ freed += m.content.length - placeholder.length;
2147
+ out[i] = { ...m, content: placeholder };
2148
+ elided++;
2149
+ }
2150
+ return elided > 0 ? { messages: out, elided, freedChars: freed } : { messages, elided: 0, freedChars: 0 };
2151
+ }
2152
+ function applyToolResultElision(messages, keepChars, exempt = ELIDE_EXEMPT_TOOLS) {
2153
+ let total = 0;
2154
+ for (const m of messages) {
2155
+ if (m.role === "tool" && !exempt.has(m.tool_call_name ?? "")) {
2156
+ total += m.content.length;
2157
+ }
2158
+ }
2159
+ if (total <= keepChars) return { messages, elided: 0, freedChars: 0 };
2160
+ const out = [...messages];
2161
+ let budget = 0;
2162
+ let elided = 0;
2163
+ let freed = 0;
2164
+ for (let i = messages.length - 1; i >= 0; i--) {
2165
+ const m = messages[i];
2166
+ if (m.role !== "tool") continue;
2167
+ if (exempt.has(m.tool_call_name ?? "")) continue;
2168
+ if (elided === 0 && budget === 0 && m.content.length > keepChars) {
2169
+ const kept = m.content.slice(0, keepChars) + "\n\u2026(truncated)";
2170
+ freed += m.content.length - kept.length;
2171
+ out[i] = { ...m, content: kept };
2172
+ budget = keepChars;
2173
+ continue;
2174
+ }
2175
+ if (budget + m.content.length <= keepChars) {
2176
+ budget += m.content.length;
2177
+ continue;
2178
+ }
2179
+ const placeholder = elidedToolContent(m);
2180
+ freed += m.content.length - placeholder.length;
2181
+ out[i] = { ...m, content: placeholder };
2182
+ elided++;
2183
+ }
2184
+ return { messages: out, elided, freedChars: freed };
2185
+ }
2186
+ function normalizeToolPath(p) {
2187
+ let s = p.trim().replace(/\\/g, "/");
2188
+ while (s.startsWith("./")) s = s.slice(2);
2189
+ return s.replace(/\/{2,}/g, "/");
2190
+ }
2191
+ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
2192
+ "write_file",
2193
+ "replace_in_file",
2194
+ "apply_patch",
2195
+ "apply_unified_diff",
2196
+ "run_shell"
2197
+ ]);
2198
+ function parseReadFileSpan(content) {
2199
+ const body = content.replace(/^\[tool output truncated:[^\n]*\]\n\n/, "");
2200
+ let path35;
2201
+ let start;
2202
+ let end;
2203
+ try {
2204
+ const obj = JSON.parse(body);
2205
+ path35 = obj.path;
2206
+ start = obj.start_line;
2207
+ end = obj.end_line;
2208
+ } catch {
2209
+ const str2 = (key) => new RegExp(`"${key}"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)"`).exec(body)?.[1];
2210
+ const num2 = (key) => {
2211
+ const m = new RegExp(`"${key}"\\s*:\\s*(\\d+)`).exec(body);
2212
+ return m ? Number(m[1]) : void 0;
2213
+ };
2214
+ path35 = str2("path");
2215
+ start = num2("start_line");
2216
+ end = num2("end_line");
2217
+ }
2218
+ if (typeof path35 !== "string" || !path35) return null;
2219
+ if (typeof start !== "number" || typeof end !== "number") return null;
2220
+ if (!Number.isFinite(start) || !Number.isFinite(end)) return null;
2221
+ return { path: path35, start, end };
2222
+ }
2223
+ function addSpan(spans, start, end) {
2224
+ spans.push([start, end]);
2225
+ spans.sort((a, b) => a[0] - b[0]);
2226
+ const merged = [];
2227
+ for (const cur of spans) {
2228
+ const last = merged[merged.length - 1];
2229
+ if (last && cur[0] <= last[1] + 1) {
2230
+ last[1] = Math.max(last[1], cur[1]);
2231
+ } else {
2232
+ merged.push([...cur]);
2233
+ }
2234
+ }
2235
+ spans.length = 0;
2236
+ spans.push(...merged);
2237
+ }
2238
+ function coversSpans(spans, start, end) {
2239
+ return spans.some(([a, b]) => a <= start && b >= end);
2240
+ }
2241
+ function applyReadFileDedup(messages) {
2242
+ let candidates = 0;
2243
+ for (const m of messages) {
2244
+ if (m.role === "tool" && m.tool_call_name === "read_file") candidates++;
2245
+ }
2246
+ if (candidates < 2) return { messages, elided: 0 };
2247
+ const out = [...messages];
2248
+ const covered = /* @__PURE__ */ new Map();
2249
+ let elided = 0;
2250
+ for (let i = messages.length - 1; i >= 0; i--) {
2251
+ const m = messages[i];
2252
+ if (m.role === "assistant") {
2253
+ if (m.tool_calls?.some((tc) => MUTATING_TOOLS.has(tc.function.name))) {
2254
+ covered.clear();
2255
+ }
2256
+ continue;
2257
+ }
2258
+ if (m.role !== "tool" || m.tool_call_name !== "read_file") continue;
2259
+ const span = parseReadFileSpan(m.content);
2260
+ if (!span) continue;
2261
+ const key = normalizeToolPath(span.path);
2262
+ const seen = covered.get(key) ?? [];
2263
+ if (coversSpans(seen, span.start, span.end)) {
2264
+ out[i] = {
2265
+ ...m,
2266
+ content: `[superseded by a later read_file of ${span.path} (lines ${span.start}-${span.end})]`
2267
+ };
2268
+ elided++;
2269
+ continue;
2270
+ }
2271
+ addSpan(seen, span.start, span.end);
2272
+ covered.set(key, seen);
2273
+ }
2274
+ return { messages: elided > 0 ? out : messages, elided };
2275
+ }
2086
2276
  var OVERFLOW_MARKERS = [
2087
2277
  "context_length_exceeded",
2088
2278
  "maximum context length",
@@ -2098,9 +2288,94 @@ function isContextOverflow(exc) {
2098
2288
  return OVERFLOW_MARKERS.some((m) => text.includes(m));
2099
2289
  }
2100
2290
 
2291
+ // src/engine/toolResultBudget.ts
2292
+ init_cjs_shims();
2293
+ var fs4 = __toESM(require("fs"), 1);
2294
+ var path5 = __toESM(require("path"), 1);
2295
+ var DEFAULT_TOOL_RESULT_MAX_CHARS = 24e3;
2296
+ var HEAD_RATIO = 0.6;
2297
+ function budgetChars() {
2298
+ const n = Number(process.env.TOOL_RESULT_MAX_CHARS);
2299
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : DEFAULT_TOOL_RESULT_MAX_CHARS;
2300
+ }
2301
+ function toolResultDir() {
2302
+ const env3 = process.env.TOOL_RESULT_DIR?.trim();
2303
+ return env3 || path5.join(stateDir(), "tool-results");
2304
+ }
2305
+ function clipText(text, budget) {
2306
+ if (text.length <= budget) return text;
2307
+ const room = Math.max(
2308
+ 0,
2309
+ budget - `
2310
+
2311
+ \u2026 [${text.length} characters omitted] \u2026
2312
+
2313
+ `.length
2314
+ );
2315
+ const headLen = Math.floor(room * HEAD_RATIO);
2316
+ const tailLen = room - headLen;
2317
+ const omitted = text.length - headLen - tailLen;
2318
+ return text.slice(0, headLen) + `
2319
+
2320
+ \u2026 [${omitted} characters omitted] \u2026
2321
+
2322
+ ` + text.slice(-tailLen);
2323
+ }
2324
+ function clipDeep(value, perField) {
2325
+ if (typeof value === "string") {
2326
+ return value.length <= perField ? value : clipText(value, perField);
2327
+ }
2328
+ if (Array.isArray(value)) {
2329
+ return value.map((v) => clipDeep(v, perField));
2330
+ }
2331
+ if (value && typeof value === "object") {
2332
+ const out = {};
2333
+ for (const [k, v] of Object.entries(value)) out[k] = clipDeep(v, perField);
2334
+ return out;
2335
+ }
2336
+ return value;
2337
+ }
2338
+ function saveRaw(tool, raw, dir) {
2339
+ try {
2340
+ fs4.mkdirSync(dir, { recursive: true });
2341
+ const safe = tool.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 40) || "tool";
2342
+ const name = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}-${safe}.txt`;
2343
+ const file = path5.join(dir, name);
2344
+ fs4.writeFileSync(file, raw, "utf-8");
2345
+ return file;
2346
+ } catch {
2347
+ return void 0;
2348
+ }
2349
+ }
2350
+ function applyToolResultBudget(tool, result, opts = {}) {
2351
+ const budget = opts.maxChars ?? budgetChars();
2352
+ const raw = typeof result === "string" ? result : JSON.stringify(result, null, 2) ?? String(result);
2353
+ if (raw.length <= budget) {
2354
+ return { content: raw, truncated: false };
2355
+ }
2356
+ let content = raw;
2357
+ if (typeof result === "object" && result !== null) {
2358
+ let perField = Math.floor(budget * 0.7);
2359
+ let clipped = JSON.stringify(clipDeep(result, perField), null, 2);
2360
+ for (let i = 0; i < 6 && clipped && clipped.length > budget && perField > 128; i++) {
2361
+ perField = Math.floor(perField / 2);
2362
+ clipped = JSON.stringify(clipDeep(result, perField), null, 2);
2363
+ }
2364
+ if (clipped && clipped.length <= budget) content = clipped;
2365
+ }
2366
+ if (content.length > budget) content = clipText(content, budget);
2367
+ const savedPath = saveRaw(tool, raw, opts.dir ?? toolResultDir());
2368
+ if (savedPath) {
2369
+ content = `[tool output truncated: ${raw.length} chars, full output saved to ${savedPath}]
2370
+
2371
+ ` + content;
2372
+ }
2373
+ return { content, truncated: true, savedPath };
2374
+ }
2375
+
2101
2376
  // src/engine/TurnEngine.ts
2102
2377
  var crypto3 = __toESM(require("crypto"), 1);
2103
- var fs4 = __toESM(require("fs"), 1);
2378
+ var fs5 = __toESM(require("fs"), 1);
2104
2379
  var _path = __toESM(require("path"), 1);
2105
2380
 
2106
2381
  // src/prompts/index.ts
@@ -2226,13 +2501,17 @@ var INTERACTIVE_TOOLS = /* @__PURE__ */ new Set([
2226
2501
  "propose_plan",
2227
2502
  "request_directory"
2228
2503
  ]);
2229
- function toolResultMessage(tc, result, taskId) {
2230
- const content = typeof result === "string" ? result : JSON.stringify(result, null, 2);
2504
+ var MAX_COMPACT_ROUNDS = 4;
2505
+ var OVERFLOW_TARGET_RATIO = 0.5;
2506
+ var OVERFLOW_TRIM_FRACTION = 0.5;
2507
+ var COMPACT_STALL_RETRY_DELTA = 20;
2508
+ function toolResultMessage(tc, result, taskId, toolResultDir2) {
2509
+ const budgeted = applyToolResultBudget(tc.name, result, { dir: toolResultDir2 });
2231
2510
  return {
2232
2511
  role: "tool",
2233
2512
  tool_call_id: tc.id,
2234
2513
  tool_call_name: tc.name,
2235
- content,
2514
+ content: budgeted.content,
2236
2515
  taskId
2237
2516
  };
2238
2517
  }
@@ -2343,12 +2622,22 @@ var TurnEngine = class _TurnEngine {
2343
2622
  // 上下文压缩
2344
2623
  compactionState = null;
2345
2624
  _lastContextTokens = null;
2625
+ /** 压缩无进展时的消息条数快照;-1 表示无冷却 */
2626
+ _compactStalledAt = -1;
2346
2627
  /** 显式配置的上下文窗口大小(token 数),未设置时为 undefined(走自动检测) */
2347
2628
  _contextWindow;
2348
2629
  /** 压缩触发阈值比例(默认 0.8) */
2349
2630
  _thresholdPct;
2631
+ /** 软阈值比例(undefined = 取硬阈值的 75%) */
2632
+ _softThresholdPct;
2633
+ /** 是否启用软阈值淘汰(COMPACTION_ELIDE=0 可关闭) */
2634
+ _elisionEnabled;
2635
+ /** 是否启用重复读取去重(COMPACTION_DEDUP=0 可关闭) */
2636
+ _dedupEnabled;
2350
2637
  /** 压缩触发阈值绝对上限(null = 不限制) */
2351
2638
  _capTokens;
2639
+ /** 超预算工具结果的落盘目录(undefined = 使用默认状态目录) */
2640
+ _toolResultDir;
2352
2641
  // 产出文件追踪(本轮 Turn 中成功写入的文件)
2353
2642
  _producedFiles = [];
2354
2643
  _turnStartTime = 0;
@@ -2400,7 +2689,15 @@ var TurnEngine = class _TurnEngine {
2400
2689
  this._hook = opts.hook;
2401
2690
  this._contextWindow = opts.contextWindow;
2402
2691
  this._thresholdPct = opts.thresholdPct ?? DEFAULT_THRESHOLD_PCT;
2692
+ this._softThresholdPct = opts.softThresholdPct;
2693
+ this._elisionEnabled = !/^(0|false|off|no)$/i.test(
2694
+ (process.env.COMPACTION_ELIDE ?? "").trim()
2695
+ );
2696
+ this._dedupEnabled = !/^(0|false|off|no)$/i.test(
2697
+ (process.env.COMPACTION_DEDUP ?? "").trim()
2698
+ );
2403
2699
  this._capTokens = opts.capTokens ?? null;
2700
+ this._toolResultDir = opts.toolResultDir;
2404
2701
  this._reasoningEffort = opts.reasoningEffort;
2405
2702
  if (this.messages.length === 0 || this.messages[0].role !== "system") {
2406
2703
  this.messages.unshift({ role: "system", content: this.instructions });
@@ -2456,6 +2753,7 @@ var TurnEngine = class _TurnEngine {
2456
2753
  this._cancel = false;
2457
2754
  this._producedFiles = [];
2458
2755
  this._turnStartTime = Date.now();
2756
+ this._compactStalledAt = -1;
2459
2757
  this._emitHook((h) => h.onTurnStart?.(userInput));
2460
2758
  yield {
2461
2759
  type: "turn_start" /* TURN_START */,
@@ -3172,7 +3470,9 @@ var TurnEngine = class _TurnEngine {
3172
3470
  for (let i = 0; i < concurrent.length; i++) {
3173
3471
  const tc = concurrent[i];
3174
3472
  const { result, status } = results[i];
3175
- this.messages.push(toolResultMessage(tc, result, this.taskId));
3473
+ this.messages.push(
3474
+ toolResultMessage(tc, result, this.taskId, this._toolResultDir)
3475
+ );
3176
3476
  this._trackProducedFile(
3177
3477
  tc,
3178
3478
  result,
@@ -3235,7 +3535,9 @@ var TurnEngine = class _TurnEngine {
3235
3535
  };
3236
3536
  status = "error";
3237
3537
  }
3238
- this.messages.push(toolResultMessage(tc, result, this.taskId));
3538
+ this.messages.push(
3539
+ toolResultMessage(tc, result, this.taskId, this._toolResultDir)
3540
+ );
3239
3541
  this._trackProducedFile(
3240
3542
  tc,
3241
3543
  result,
@@ -3270,13 +3572,41 @@ var TurnEngine = class _TurnEngine {
3270
3572
  * 过滤 notice 消息;tool 消息只保留必要字段;
3271
3573
  * 其余消息剥离 display-only sidecar 字段(source/_display/ts/
3272
3574
  * reasoning/usage),避免污染模型上下文。
3575
+ *
3576
+ * 达到软阈值时先淘汰旧工具结果(零 LLM 成本),再产出最终视图。
3273
3577
  */
3274
3578
  _outboundMessages() {
3275
- const _STRIP = /* @__PURE__ */ new Set(["source", "_display", "ts", "reasoning", "usage"]);
3276
- const sourceMessages = applyToOutbound(
3277
- this.messages,
3278
- this.compactionState
3579
+ const base = applyToOutbound(this.messages, this.compactionState);
3580
+ const deduped = this._dedupEnabled ? applyReadFileDedup(base).messages : base;
3581
+ let view = this._finalizeOutbound(deduped);
3582
+ if (!this._elisionEnabled) return view;
3583
+ if (estimateTokens(view) < this._softTriggerTokens()) {
3584
+ return view;
3585
+ }
3586
+ const oneShot = applyOneShotElision(deduped);
3587
+ if (oneShot.freedChars > 0) {
3588
+ view = this._finalizeOutbound(oneShot.messages);
3589
+ if (estimateTokens(view) < this._softTriggerTokens()) {
3590
+ return view;
3591
+ }
3592
+ }
3593
+ const keepChars = elisionKeepChars(
3594
+ triggerTokens(
3595
+ this._getContextWindow(),
3596
+ this._thresholdPct,
3597
+ this._capTokens
3598
+ )
3279
3599
  );
3600
+ const elided = applyToolResultElision(
3601
+ oneShot.messages,
3602
+ keepChars
3603
+ );
3604
+ if (elided.freedChars === 0) return view;
3605
+ return this._finalizeOutbound(elided.messages);
3606
+ }
3607
+ /** 过滤 notice + 剥离 display-only 字段,得到最终发送视图 */
3608
+ _finalizeOutbound(sourceMessages) {
3609
+ const _STRIP = /* @__PURE__ */ new Set(["source", "_display", "ts", "reasoning", "usage"]);
3280
3610
  return sourceMessages.filter((m) => m.role !== "notice").map((m) => {
3281
3611
  if (m.role === "tool") {
3282
3612
  return {
@@ -3294,6 +3624,15 @@ var TurnEngine = class _TurnEngine {
3294
3624
  return stripped;
3295
3625
  });
3296
3626
  }
3627
+ /** 软阈值(token 数):达到它只淘汰旧工具结果 */
3628
+ _softTriggerTokens() {
3629
+ return softTriggerTokens(
3630
+ this._getContextWindow(),
3631
+ this._thresholdPct,
3632
+ this._capTokens,
3633
+ this._softThresholdPct
3634
+ );
3635
+ }
3297
3636
  /** 通知消息:仅用于日志/展示,不发给模型 */
3298
3637
  _appendNotice(kind, text) {
3299
3638
  this.messages.push({
@@ -3335,18 +3674,36 @@ var TurnEngine = class _TurnEngine {
3335
3674
  return false;
3336
3675
  }
3337
3676
  // ===================== 自动压缩 =====================
3338
- /** 当前上下文 token 用量:优先采信 API 返回的精确数量,否则用 chars/4 估算 */
3677
+ /**
3678
+ * 当前上下文 token 用量。
3679
+ *
3680
+ * API 返回的 `usage.input` 是精确值,但反映的是「上一次请求」——工具结果
3681
+ * 与淘汰/压缩都会让它过时。因此同时估算当前 outbound 视图,取较大者:
3682
+ * 宁可早一点压缩,也不要漏判导致溢出。
3683
+ */
3339
3684
  _compactionTokens() {
3340
- if (this._lastContextTokens != null) {
3341
- return { tokens: this._lastContextTokens, estimated: false };
3685
+ const estimated = estimateTokens(this._outboundMessages());
3686
+ if (this._lastContextTokens == null) {
3687
+ return { tokens: estimated, estimated: true };
3342
3688
  }
3343
- return {
3344
- tokens: estimateTokens(this._outboundMessages()),
3345
- estimated: true
3346
- };
3689
+ if (estimated > this._lastContextTokens) {
3690
+ return { tokens: estimated, estimated: true };
3691
+ }
3692
+ return { tokens: this._lastContextTokens, estimated: false };
3693
+ }
3694
+ /** 压缩后重置精确计数:后续按估算值判断 */
3695
+ _resetContextTokens() {
3696
+ this._lastContextTokens = null;
3697
+ }
3698
+ /** 当前 outbound 视图的估算 token 数 */
3699
+ _estimateContextTokens() {
3700
+ return estimateTokens(this._outboundMessages());
3347
3701
  }
3348
3702
  /** 检查是否需要压缩(每轮 LLM 调用前) */
3349
3703
  _compactionDue() {
3704
+ if (this._compactStalledAt >= 0 && this.messages.length < this._compactStalledAt + COMPACT_STALL_RETRY_DELTA) {
3705
+ return false;
3706
+ }
3350
3707
  const { tokens } = this._compactionTokens();
3351
3708
  return shouldCompact(
3352
3709
  tokens,
@@ -3393,63 +3750,82 @@ var TurnEngine = class _TurnEngine {
3393
3750
  async _compactNow(reqId, force = false) {
3394
3751
  const contextWindow = this._getContextWindow();
3395
3752
  const messagesBefore = this._outboundMessages().length;
3396
- const keep = Math.floor(
3397
- KEEP_RECENT_FRACTION * triggerTokens(
3398
- contextWindow,
3399
- this._thresholdPct,
3400
- this._capTokens
3401
- )
3753
+ const trigger = triggerTokens(
3754
+ contextWindow,
3755
+ this._thresholdPct,
3756
+ this._capTokens
3402
3757
  );
3403
- const model = this.model;
3404
- let state = null;
3405
- let failed2 = false;
3406
- for (let attempt = 0; attempt < 2; attempt++) {
3407
- try {
3408
- const logTime = (/* @__PURE__ */ new Date()).toISOString();
3409
- state = await buildState(
3758
+ const target = Math.floor(
3759
+ trigger * (force ? OVERFLOW_TARGET_RATIO : 0.9)
3760
+ );
3761
+ const model = process.env.COMPACTION_MODEL?.trim() || this.model;
3762
+ const reasoningEffort = summarizerReasoningEffort();
3763
+ let keep = Math.floor(KEEP_RECENT_FRACTION * trigger);
3764
+ let method = null;
3765
+ let lastState = null;
3766
+ let progressed = false;
3767
+ for (let round = 0; round < MAX_COMPACT_ROUNDS; round++) {
3768
+ if (round > 0 && this._estimateContextTokens() <= target) break;
3769
+ let state = null;
3770
+ for (let attempt = 0; attempt < 2 && !state; attempt++) {
3771
+ try {
3772
+ const logTime = (/* @__PURE__ */ new Date()).toISOString();
3773
+ state = await buildState(
3774
+ this.messages,
3775
+ this.provider,
3776
+ model,
3777
+ keep,
3778
+ this.compactionState,
3779
+ { reqId, logTime, reasoningEffort }
3780
+ );
3781
+ } catch {
3782
+ }
3783
+ }
3784
+ if (state) {
3785
+ this.compactionState = state;
3786
+ lastState = state;
3787
+ method = "summarized";
3788
+ progressed = true;
3789
+ } else {
3790
+ const trimmed = trimState(
3410
3791
  this.messages,
3411
- this.provider,
3412
- model,
3413
- keep,
3414
3792
  this.compactionState,
3415
- { reqId, logTime, reasoningEffort: this._reasoningEffort }
3793
+ force ? OVERFLOW_TRIM_FRACTION : 0.25
3416
3794
  );
3417
- failed2 = false;
3418
- break;
3419
- } catch {
3420
- failed2 = true;
3421
- }
3422
- }
3423
- if (state) {
3424
- this.compactionState = state;
3425
- this._lastContextTokens = null;
3795
+ if (!trimmed) break;
3796
+ this.compactionState = trimmed;
3797
+ lastState = trimmed;
3798
+ method = "trimmed";
3799
+ progressed = true;
3800
+ }
3801
+ this._resetContextTokens();
3802
+ if (this._estimateContextTokens() <= trigger) break;
3803
+ keep = Math.max(Math.floor(trigger * 0.05), Math.floor(keep / 2));
3804
+ }
3805
+ if (!progressed || !lastState) return { notice: null, method: null };
3806
+ this._compactStalledAt = this._estimateContextTokens() >= trigger ? this.messages.length : -1;
3807
+ const messagesAfter = this._outboundMessages().length;
3808
+ const common = {
3809
+ boundaryIndex: lastState.boundaryIndex,
3810
+ messagesBefore,
3811
+ messagesAfter
3812
+ };
3813
+ if (method === "summarized") {
3426
3814
  return {
3427
3815
  notice: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29 \u2014 \u65E9\u671F\u8F6E\u6B21\u5DF2\u6458\u8981",
3428
3816
  method: "summarized",
3429
- modelUsed: state.modelUsed,
3430
- boundaryIndex: state.boundaryIndex,
3431
- summaryPreview: state.summaryText.slice(0, 200),
3432
- keptUserMessages: state.userMessages.length,
3433
- droppedUserMessages: state.userMessagesDropped,
3434
- messagesBefore,
3435
- messagesAfter: this._outboundMessages().length
3817
+ modelUsed: lastState.modelUsed,
3818
+ summaryPreview: lastState.summaryText.slice(0, 200),
3819
+ keptUserMessages: lastState.userMessages.length,
3820
+ droppedUserMessages: lastState.userMessagesDropped,
3821
+ ...common
3436
3822
  };
3437
3823
  }
3438
- if (failed2 || force) {
3439
- const trimmed = trimState(this.messages, this.compactionState);
3440
- if (trimmed) {
3441
- this.compactionState = trimmed;
3442
- this._lastContextTokens = null;
3443
- return {
3444
- notice: "\u4E0A\u4E0B\u6587\u5DF2\u88C1\u526A \u2014 \u6700\u65E7\u7684\u8F6E\u6B21\u5DF2\u4E22\u5F03\uFF08\u6458\u8981\u4E0D\u53EF\u7528\uFF09",
3445
- method: "trimmed",
3446
- boundaryIndex: trimmed.boundaryIndex,
3447
- messagesBefore,
3448
- messagesAfter: this._outboundMessages().length
3449
- };
3450
- }
3451
- }
3452
- return { notice: null, method: null };
3824
+ return {
3825
+ notice: "\u4E0A\u4E0B\u6587\u5DF2\u88C1\u526A \u2014 \u6700\u65E7\u7684\u8F6E\u6B21\u5DF2\u4E22\u5F03\uFF08\u6458\u8981\u4E0D\u53EF\u7528\uFF09",
3826
+ method: "trimmed",
3827
+ ...common
3828
+ };
3453
3829
  }
3454
3830
  /** 追踪文件写入工具成功产生的文件 */
3455
3831
  _trackProducedFile(tc, result, status) {
@@ -3459,9 +3835,9 @@ var TurnEngine = class _TurnEngine {
3459
3835
  return;
3460
3836
  }
3461
3837
  if (result && typeof result === "object") {
3462
- const path33 = result.path;
3463
- if (path33) {
3464
- this._producedFiles.push(path33);
3838
+ const path35 = result.path;
3839
+ if (path35) {
3840
+ this._producedFiles.push(path35);
3465
3841
  return;
3466
3842
  }
3467
3843
  const changed = result.changed_files;
@@ -3485,7 +3861,7 @@ var TurnEngine = class _TurnEngine {
3485
3861
  _fileExists(f, ws) {
3486
3862
  if (!ws && !_path.isAbsolute(f)) return true;
3487
3863
  try {
3488
- return fs4.existsSync(_path.isAbsolute(f) ? f : _path.join(ws, f));
3864
+ return fs5.existsSync(_path.isAbsolute(f) ? f : _path.join(ws, f));
3489
3865
  } catch {
3490
3866
  return false;
3491
3867
  }
@@ -3508,7 +3884,7 @@ var TurnEngine = class _TurnEngine {
3508
3884
  if (depth > 4 || results.length >= 50) return;
3509
3885
  let entries;
3510
3886
  try {
3511
- entries = fs4.readdirSync(dir, { withFileTypes: true });
3887
+ entries = fs5.readdirSync(dir, { withFileTypes: true });
3512
3888
  } catch {
3513
3889
  return;
3514
3890
  }
@@ -3523,7 +3899,7 @@ var TurnEngine = class _TurnEngine {
3523
3899
  const ext = dotIdx !== -1 ? e.name.slice(dotIdx).toLowerCase() : "";
3524
3900
  if (!_TurnEngine.SCAN_SUFFIXES.has(ext)) continue;
3525
3901
  try {
3526
- const st = fs4.statSync(full);
3902
+ const st = fs5.statSync(full);
3527
3903
  if (st.mtimeMs >= since) {
3528
3904
  results.push(
3529
3905
  _path.relative(_path.resolve(ws), _path.resolve(full))
@@ -4020,62 +4396,62 @@ init_cjs_shims();
4020
4396
 
4021
4397
  // src/tools/pathSafety.ts
4022
4398
  init_cjs_shims();
4023
- var fs6 = __toESM(require("fs"), 1);
4024
- var path6 = __toESM(require("path"), 1);
4399
+ var fs7 = __toESM(require("fs"), 1);
4400
+ var path7 = __toESM(require("path"), 1);
4025
4401
  function isInsideRelative(rel) {
4026
- return !path6.isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${path6.sep}`);
4402
+ return !path7.isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${path7.sep}`);
4027
4403
  }
4028
4404
  function isFilesystemRoot(p) {
4029
- return path6.parse(p).root === p;
4405
+ return path7.parse(p).root === p;
4030
4406
  }
4031
4407
  function resolveWithinRoot(workspace, target) {
4032
- const abs = path6.resolve(workspace, target);
4408
+ const abs = path7.resolve(workspace, target);
4033
4409
  let rootReal;
4034
4410
  try {
4035
- rootReal = fs6.realpathSync(workspace);
4411
+ rootReal = fs7.realpathSync(workspace);
4036
4412
  } catch {
4037
- const rootAbs = path6.resolve(workspace);
4038
- if (!isInsideRelative(path6.relative(rootAbs, abs))) return null;
4413
+ const rootAbs = path7.resolve(workspace);
4414
+ if (!isInsideRelative(path7.relative(rootAbs, abs))) return null;
4039
4415
  return { root: rootAbs, real: abs };
4040
4416
  }
4041
4417
  if (isFilesystemRoot(rootReal)) return null;
4042
4418
  let real;
4043
4419
  try {
4044
- real = fs6.realpathSync(abs);
4420
+ real = fs7.realpathSync(abs);
4045
4421
  } catch {
4046
4422
  let probe = abs;
4047
4423
  const tail = [];
4048
4424
  for (; ; ) {
4049
- const exists = fs6.existsSync(probe) || fs6.lstatSync(probe, { throwIfNoEntry: false }) !== void 0;
4425
+ const exists = fs7.existsSync(probe) || fs7.lstatSync(probe, { throwIfNoEntry: false }) !== void 0;
4050
4426
  if (exists) {
4051
4427
  try {
4052
- real = path6.join(fs6.realpathSync(probe), ...tail);
4428
+ real = path7.join(fs7.realpathSync(probe), ...tail);
4053
4429
  break;
4054
4430
  } catch {
4055
4431
  return null;
4056
4432
  }
4057
4433
  }
4058
- tail.unshift(path6.basename(probe));
4059
- const parent = path6.dirname(probe);
4434
+ tail.unshift(path7.basename(probe));
4435
+ const parent = path7.dirname(probe);
4060
4436
  if (parent === probe) return null;
4061
4437
  probe = parent;
4062
4438
  }
4063
4439
  }
4064
- if (!isInsideRelative(path6.relative(rootReal, real))) return null;
4440
+ if (!isInsideRelative(path7.relative(rootReal, real))) return null;
4065
4441
  return { root: rootReal, real };
4066
4442
  }
4067
4443
  function isWithinRoot(workspace, target) {
4068
4444
  return resolveWithinRoot(workspace, target) !== null;
4069
4445
  }
4070
4446
  function resolveEntryWithinRoot(workspace, target) {
4071
- const abs = path6.resolve(workspace, target);
4072
- const parent = resolveWithinRoot(workspace, path6.dirname(abs));
4447
+ const abs = path7.resolve(workspace, target);
4448
+ const parent = resolveWithinRoot(workspace, path7.dirname(abs));
4073
4449
  if (!parent) return null;
4074
- return { root: parent.root, real: path6.join(parent.real, path6.basename(abs)) };
4450
+ return { root: parent.root, real: path7.join(parent.real, path7.basename(abs)) };
4075
4451
  }
4076
4452
 
4077
4453
  // src/permissions/PermissionEngine.ts
4078
- var path7 = __toESM(require("path"), 1);
4454
+ var path8 = __toESM(require("path"), 1);
4079
4455
  var READ_ONLY_MODES = /* @__PURE__ */ new Set(["discuss" /* DISCUSS */, "plan" /* PLAN */]);
4080
4456
  var WRITE_TOOLS = /* @__PURE__ */ new Set([
4081
4457
  "write_file",
@@ -4125,7 +4501,7 @@ var PermissionEngine = class {
4125
4501
  /** 会话级「总是允许此命令」集合 */
4126
4502
  sessionAllowCommands;
4127
4503
  constructor(opts) {
4128
- this.workspaceRoot = path7.resolve(opts.workspaceRoot);
4504
+ this.workspaceRoot = path8.resolve(opts.workspaceRoot);
4129
4505
  this.mode = opts.mode;
4130
4506
  this.allowedCommands = Array.isArray(opts.allowedCommands) ? opts.allowedCommands : [];
4131
4507
  this.sessionAllowTools = /* @__PURE__ */ new Set();
@@ -4218,8 +4594,8 @@ var PermissionEngine = class {
4218
4594
 
4219
4595
  // src/providers/OpenAIProvider.ts
4220
4596
  init_cjs_shims();
4221
- var fs7 = __toESM(require("fs"), 1);
4222
- var path8 = __toESM(require("path"), 1);
4597
+ var fs8 = __toESM(require("fs"), 1);
4598
+ var path9 = __toESM(require("path"), 1);
4223
4599
  var import_openai = __toESM(require("openai"), 1);
4224
4600
 
4225
4601
  // src/terminal.ts
@@ -4357,7 +4733,7 @@ var OpenAIProvider = class {
4357
4733
  });
4358
4734
  if (opts.logDir) {
4359
4735
  this._logDir = opts.logDir;
4360
- fs7.mkdirSync(opts.logDir, { recursive: true });
4736
+ fs8.mkdirSync(opts.logDir, { recursive: true });
4361
4737
  }
4362
4738
  }
4363
4739
  // =====================================================================
@@ -4510,6 +4886,8 @@ var OpenAIProvider = class {
4510
4886
  tools: this._toResponsesTools(tools, model),
4511
4887
  // 推理强度:DeepSeek 不支持,跳过
4512
4888
  ...opts.reasoningEffort && !this._isDeepSeek(model) ? { reasoning: { effort: opts.reasoningEffort } } : {},
4889
+ // 限制输出长度(摘要器等轻量请求)
4890
+ ...opts.maxTokens ? { max_output_tokens: opts.maxTokens } : {},
4513
4891
  stream: true
4514
4892
  }, requestOptions);
4515
4893
  return this._responsesStream(reqId, startTime, rawStream, {
@@ -4608,6 +4986,8 @@ var OpenAIProvider = class {
4608
4986
  })),
4609
4987
  // 推理强度:DeepSeek 不支持,跳过
4610
4988
  ...opts.reasoningEffort && !this._isDeepSeek(model) ? { reasoning_effort: opts.reasoningEffort } : {},
4989
+ // 限制输出长度:o 系列 / gpt-5 推理模型仅支持 max_completion_tokens
4990
+ ...opts.maxTokens ? isReasoningModel(model) ? { max_completion_tokens: opts.maxTokens } : { max_tokens: opts.maxTokens } : {},
4611
4991
  stream: true,
4612
4992
  // 要求返回 Token 用量统计(在流结束前的一个 chunk 中携带)
4613
4993
  stream_options: { include_usage: true }
@@ -4721,8 +5101,8 @@ var OpenAIProvider = class {
4721
5101
  if (!this._logDir) return;
4722
5102
  try {
4723
5103
  const today = logTime?.split("T")[0];
4724
- const dir = path8.join(this._logDir, today);
4725
- fs7.mkdirSync(dir, { recursive: true });
5104
+ const dir = path9.join(this._logDir, today);
5105
+ fs8.mkdirSync(dir, { recursive: true });
4726
5106
  const entry = {
4727
5107
  reqId,
4728
5108
  ts: logTime,
@@ -4734,8 +5114,8 @@ var OpenAIProvider = class {
4734
5114
  toolNames: tools?.map((t) => t.function.name) ?? []
4735
5115
  }
4736
5116
  };
4737
- fs7.writeFileSync(
4738
- path8.join(dir, `${reqId}.json`),
5117
+ fs8.writeFileSync(
5118
+ path9.join(dir, `${reqId}.json`),
4739
5119
  JSON.stringify(entry, null, 2),
4740
5120
  "utf-8"
4741
5121
  );
@@ -4754,9 +5134,9 @@ var OpenAIProvider = class {
4754
5134
  if (!this._logDir) return;
4755
5135
  try {
4756
5136
  const today = logTime?.split("T")[0];
4757
- const filePath = path8.join(this._logDir, today, `${reqId}.json`);
4758
- if (!fs7.existsSync(filePath)) return;
4759
- const entry = JSON.parse(fs7.readFileSync(filePath, "utf-8"));
5137
+ const filePath = path9.join(this._logDir, today, `${reqId}.json`);
5138
+ if (!fs8.existsSync(filePath)) return;
5139
+ const entry = JSON.parse(fs8.readFileSync(filePath, "utf-8"));
4760
5140
  entry.elapsedMs = Date.now() - startTime;
4761
5141
  if (data.response) {
4762
5142
  entry.response = data.response;
@@ -4764,7 +5144,7 @@ var OpenAIProvider = class {
4764
5144
  if (data.error) {
4765
5145
  entry.error = data.error;
4766
5146
  }
4767
- fs7.writeFileSync(filePath, JSON.stringify(entry, null, 2), "utf-8");
5147
+ fs8.writeFileSync(filePath, JSON.stringify(entry, null, 2), "utf-8");
4768
5148
  } catch (e) {
4769
5149
  console.error(e);
4770
5150
  }
@@ -5122,12 +5502,12 @@ var OpenAIProvider = class {
5122
5502
 
5123
5503
  // src/tools/files.ts
5124
5504
  init_cjs_shims();
5125
- var fs8 = __toESM(require("fs"), 1);
5126
- var path9 = __toESM(require("path"), 1);
5505
+ var fs9 = __toESM(require("fs"), 1);
5506
+ var path10 = __toESM(require("path"), 1);
5127
5507
  var DEFAULT_MAX_LINES = 2e3;
5128
5508
  var MAX_LINE_CHARS = 500;
5129
5509
  function makeReadFile(workspace) {
5130
- const root = path9.resolve(workspace);
5510
+ const root = path10.resolve(workspace);
5131
5511
  return function read_file(args) {
5132
5512
  const filePath = String(args.path ?? "");
5133
5513
  const startLine = Math.max(1, Number(args.start_line ?? 1));
@@ -5137,13 +5517,13 @@ function makeReadFile(workspace) {
5137
5517
  return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
5138
5518
  }
5139
5519
  const target = resolved.real;
5140
- if (!fs8.existsSync(target) || !fs8.statSync(target).isFile()) {
5520
+ if (!fs9.existsSync(target) || !fs9.statSync(target).isFile()) {
5141
5521
  return { error: `\u4E0D\u662F\u6587\u4EF6: ${filePath}` };
5142
5522
  }
5143
5523
  const selected = [];
5144
5524
  let total = 0;
5145
5525
  try {
5146
- const content = fs8.readFileSync(target, "utf-8");
5526
+ const content = fs9.readFileSync(target, "utf-8");
5147
5527
  const lines = content.split("\n");
5148
5528
  total = lines.length;
5149
5529
  for (let i = startLine - 1; i < Math.min(startLine - 1 + maxLines, lines.length); i++) {
@@ -5158,7 +5538,7 @@ function makeReadFile(workspace) {
5158
5538
  }
5159
5539
  const end = startLine + selected.length - 1;
5160
5540
  const result = {
5161
- path: path9.relative(resolved.root, target),
5541
+ path: path10.relative(resolved.root, target),
5162
5542
  start_line: startLine,
5163
5543
  end_line: end,
5164
5544
  total_lines: total,
@@ -5171,7 +5551,7 @@ function makeReadFile(workspace) {
5171
5551
  };
5172
5552
  }
5173
5553
  function makeWriteFile(workspace) {
5174
- const root = path9.resolve(workspace);
5554
+ const root = path10.resolve(workspace);
5175
5555
  return function write_file(args) {
5176
5556
  const filePath = String(args.path ?? "");
5177
5557
  const content = String(args.content ?? "");
@@ -5181,34 +5561,34 @@ function makeWriteFile(workspace) {
5181
5561
  return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
5182
5562
  }
5183
5563
  const target = resolved.real;
5184
- if (fs8.existsSync(target) && fs8.statSync(target).isDirectory()) {
5564
+ if (fs9.existsSync(target) && fs9.statSync(target).isDirectory()) {
5185
5565
  return { error: "\u8DEF\u5F84\u662F\u76EE\u5F55\uFF0C\u4E0D\u662F\u6587\u4EF6" };
5186
5566
  }
5187
- if (fs8.existsSync(target) && !overwrite) {
5567
+ if (fs9.existsSync(target) && !overwrite) {
5188
5568
  return { error: "\u6587\u4EF6\u5DF2\u5B58\u5728 (overwrite=false)" };
5189
5569
  }
5190
5570
  try {
5191
- fs8.mkdirSync(path9.dirname(target), { recursive: true });
5192
- fs8.writeFileSync(target, content, "utf-8");
5571
+ fs9.mkdirSync(path10.dirname(target), { recursive: true });
5572
+ fs9.writeFileSync(target, content, "utf-8");
5193
5573
  } catch (exc) {
5194
5574
  return { error: `\u5199\u5165\u5931\u8D25: ${String(exc)}` };
5195
5575
  }
5196
- return path9.relative(resolved.root, target);
5576
+ return path10.relative(resolved.root, target);
5197
5577
  };
5198
5578
  }
5199
5579
  function makeListFiles(workspace) {
5200
- const root = path9.resolve(workspace);
5580
+ const root = path10.resolve(workspace);
5201
5581
  return function list_files(args) {
5202
5582
  const dirPath = String(args.path ?? ".");
5203
5583
  const maxResults = Math.min(Number(args.max_results ?? 100), 100);
5204
5584
  const resolved = resolveWithinRoot(root, dirPath);
5205
5585
  if (!resolved) return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
5206
5586
  const target = resolved.real;
5207
- if (!fs8.existsSync(target) || !fs8.statSync(target).isDirectory()) {
5587
+ if (!fs9.existsSync(target) || !fs9.statSync(target).isDirectory()) {
5208
5588
  return { error: `\u4E0D\u662F\u76EE\u5F55: ${dirPath}` };
5209
5589
  }
5210
5590
  try {
5211
- const entries = fs8.readdirSync(target, { withFileTypes: true });
5591
+ const entries = fs9.readdirSync(target, { withFileTypes: true });
5212
5592
  const results = [];
5213
5593
  for (const e of entries) {
5214
5594
  if (results.length >= maxResults) break;
@@ -5217,14 +5597,14 @@ function makeListFiles(workspace) {
5217
5597
  results.push(e.isDirectory() ? `${e.name}/` : e.name);
5218
5598
  }
5219
5599
  results.sort();
5220
- return { path: path9.relative(resolved.root, target), entries: results.slice(0, maxResults) };
5600
+ return { path: path10.relative(resolved.root, target), entries: results.slice(0, maxResults) };
5221
5601
  } catch (exc) {
5222
5602
  return { error: `\u5217\u51FA\u6587\u4EF6\u5931\u8D25: ${String(exc)}` };
5223
5603
  }
5224
5604
  };
5225
5605
  }
5226
5606
  function makeReplaceInFile(workspace) {
5227
- const root = path9.resolve(workspace);
5607
+ const root = path10.resolve(workspace);
5228
5608
  return function replace_in_file(args) {
5229
5609
  const filePath = String(args.path ?? "");
5230
5610
  const oldStr = String(args.old ?? "");
@@ -5234,19 +5614,19 @@ function makeReplaceInFile(workspace) {
5234
5614
  const resolved = resolveWithinRoot(root, filePath);
5235
5615
  if (!resolved) return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
5236
5616
  const target = resolved.real;
5237
- if (!fs8.existsSync(target) || !fs8.statSync(target).isFile()) {
5617
+ if (!fs9.existsSync(target) || !fs9.statSync(target).isFile()) {
5238
5618
  return { error: `\u4E0D\u662F\u6587\u4EF6: ${filePath}` };
5239
5619
  }
5240
5620
  try {
5241
- const content = fs8.readFileSync(target, "utf-8");
5621
+ const content = fs9.readFileSync(target, "utf-8");
5242
5622
  const count = content.split(oldStr).length - 1;
5243
5623
  if (count !== expectedReplacements) {
5244
5624
  return { error: `\u671F\u671B ${expectedReplacements} \u5904\u66FF\u6362\uFF0C\u5B9E\u9645\u627E\u5230 ${count} \u5904: ${filePath}` };
5245
5625
  }
5246
5626
  const updated = content.replaceAll(oldStr, newStr);
5247
- fs8.writeFileSync(target, updated, "utf-8");
5627
+ fs9.writeFileSync(target, updated, "utf-8");
5248
5628
  return {
5249
- path: path9.relative(resolved.root, target),
5629
+ path: path10.relative(resolved.root, target),
5250
5630
  replacements: count,
5251
5631
  chars_before: content.length,
5252
5632
  chars_after: updated.length
@@ -5347,8 +5727,8 @@ function registerFileTools(registry, workspace) {
5347
5727
 
5348
5728
  // src/tools/shell.ts
5349
5729
  init_cjs_shims();
5350
- var fs10 = __toESM(require("fs"), 1);
5351
- var path11 = __toESM(require("path"), 1);
5730
+ var fs11 = __toESM(require("fs"), 1);
5731
+ var path12 = __toESM(require("path"), 1);
5352
5732
 
5353
5733
  // src/tools/execAsync.ts
5354
5734
  init_cjs_shims();
@@ -5513,15 +5893,15 @@ function execAsync(file, argv, opts = {}) {
5513
5893
 
5514
5894
  // src/tools/shellRuntime.ts
5515
5895
  init_cjs_shims();
5516
- var fs9 = __toESM(require("fs"), 1);
5517
- var path10 = __toESM(require("path"), 1);
5896
+ var fs10 = __toESM(require("fs"), 1);
5897
+ var path11 = __toESM(require("path"), 1);
5518
5898
  var WINDOWS_SUFFIXES = [".exe", ".cmd", ".bat", ".com", ""];
5519
5899
  function pathApi(platform) {
5520
- return platform === "win32" ? path10.win32 : path10.posix;
5900
+ return platform === "win32" ? path11.win32 : path11.posix;
5521
5901
  }
5522
5902
  function defaultFileExists(p) {
5523
5903
  try {
5524
- return fs9.statSync(p).isFile();
5904
+ return fs10.statSync(p).isFile();
5525
5905
  } catch {
5526
5906
  return false;
5527
5907
  }
@@ -5552,7 +5932,7 @@ function kindFromBasename(base) {
5552
5932
  return "sh";
5553
5933
  }
5554
5934
  function busyboxArgs(file, command) {
5555
- const base = (file ? path10.win32.basename(file) : "busybox").toLowerCase().replace(/\.(exe|cmd|bat|com)$/, "");
5935
+ const base = (file ? path11.win32.basename(file) : "busybox").toLowerCase().replace(/\.(exe|cmd|bat|com)$/, "");
5556
5936
  return base === "busybox" ? ["sh", "-c", command] : ["-c", command];
5557
5937
  }
5558
5938
  function argsFor(kind, command) {
@@ -5819,7 +6199,7 @@ var MAX_STDERR = 5e3;
5819
6199
  var MAX_BUFFER = 1024 * 1024 * 5;
5820
6200
  var shellSessions = /* @__PURE__ */ new Map();
5821
6201
  function sessionFor(workspace) {
5822
- const key = path11.resolve(workspace);
6202
+ const key = path12.resolve(workspace);
5823
6203
  let s = shellSessions.get(key);
5824
6204
  if (!s) {
5825
6205
  s = { cwd: key };
@@ -5876,8 +6256,8 @@ function makeRunShell(workspace, spec, tasks, scope) {
5876
6256
  let toRun = command;
5877
6257
  if (target) {
5878
6258
  try {
5879
- const resolved = path11.resolve(session.cwd, target);
5880
- if (fs10.existsSync(resolved) && fs10.statSync(resolved).isDirectory()) {
6259
+ const resolved = path12.resolve(session.cwd, target);
6260
+ if (fs11.existsSync(resolved) && fs11.statSync(resolved).isDirectory()) {
5881
6261
  session.cwd = resolved;
5882
6262
  }
5883
6263
  } catch {
@@ -6012,8 +6392,8 @@ init_cjs_shims();
6012
6392
 
6013
6393
  // src/tools/nodeGrep.ts
6014
6394
  init_cjs_shims();
6015
- var fs11 = __toESM(require("fs"), 1);
6016
- var path12 = __toESM(require("path"), 1);
6395
+ var fs12 = __toESM(require("fs"), 1);
6396
+ var path13 = __toESM(require("path"), 1);
6017
6397
  var SKIP_DIRS = /* @__PURE__ */ new Set([
6018
6398
  ".git",
6019
6399
  "node_modules",
@@ -6038,27 +6418,27 @@ function nodeGrep(opts) {
6038
6418
  } catch (err) {
6039
6419
  throw new Error(`\u65E0\u6548\u7684\u6B63\u5219\u8868\u8FBE\u5F0F: ${opts.pattern}\uFF08${String(err)}\uFF09`);
6040
6420
  }
6041
- const target = path12.resolve(opts.workspace, opts.target);
6042
- if (!fs11.existsSync(target)) {
6421
+ const target = path13.resolve(opts.workspace, opts.target);
6422
+ if (!fs12.existsSync(target)) {
6043
6423
  throw new Error(`\u641C\u7D22\u8DEF\u5F84\u4E0D\u5B58\u5728: ${opts.target}`);
6044
6424
  }
6045
6425
  const matches = [];
6046
6426
  const scanFile = (file) => {
6047
6427
  let stat2;
6048
6428
  try {
6049
- stat2 = fs11.statSync(file);
6429
+ stat2 = fs12.statSync(file);
6050
6430
  } catch {
6051
6431
  return false;
6052
6432
  }
6053
6433
  if (!stat2.isFile() || stat2.size > maxFileBytes) return false;
6054
6434
  let buf;
6055
6435
  try {
6056
- buf = fs11.readFileSync(file);
6436
+ buf = fs12.readFileSync(file);
6057
6437
  } catch {
6058
6438
  return false;
6059
6439
  }
6060
6440
  if (looksBinary(buf)) return false;
6061
- const rel = path12.relative(opts.workspace, file) || path12.basename(file);
6441
+ const rel = path13.relative(opts.workspace, file) || path13.basename(file);
6062
6442
  const lines = buf.toString("utf-8").split("\n");
6063
6443
  for (let i = 0; i < lines.length; i++) {
6064
6444
  const raw = lines[i].replace(/\r$/, "");
@@ -6069,7 +6449,7 @@ function nodeGrep(opts) {
6069
6449
  }
6070
6450
  return false;
6071
6451
  };
6072
- const stat = fs11.statSync(target);
6452
+ const stat = fs12.statSync(target);
6073
6453
  if (stat.isFile()) {
6074
6454
  scanFile(target);
6075
6455
  return matches;
@@ -6079,12 +6459,12 @@ function nodeGrep(opts) {
6079
6459
  const dir = stack.pop();
6080
6460
  let entries;
6081
6461
  try {
6082
- entries = fs11.readdirSync(dir, { withFileTypes: true });
6462
+ entries = fs12.readdirSync(dir, { withFileTypes: true });
6083
6463
  } catch {
6084
6464
  continue;
6085
6465
  }
6086
6466
  for (const entry of entries) {
6087
- const full = path12.join(dir, entry.name);
6467
+ const full = path13.join(dir, entry.name);
6088
6468
  if (entry.isDirectory()) {
6089
6469
  if (!SKIP_DIRS.has(entry.name)) stack.push(full);
6090
6470
  } else if (entry.isFile()) {
@@ -6097,22 +6477,22 @@ function nodeGrep(opts) {
6097
6477
 
6098
6478
  // src/tools/ripgrep.ts
6099
6479
  init_cjs_shims();
6100
- var fs12 = __toESM(require("fs"), 1);
6480
+ var fs13 = __toESM(require("fs"), 1);
6101
6481
  var os2 = __toESM(require("os"), 1);
6102
- var path13 = __toESM(require("path"), 1);
6482
+ var path14 = __toESM(require("path"), 1);
6103
6483
  var import_node_module = require("module");
6104
6484
  var cached2;
6105
6485
  function extractBundledRipgrep() {
6106
6486
  try {
6107
- const req2 = typeof require === "function" ? require : (0, import_node_module.createRequire)(path13.join(process.cwd(), "__zqagent__.js"));
6487
+ const req2 = typeof require === "function" ? require : (0, import_node_module.createRequire)(path14.join(process.cwd(), "__zqagent__.js"));
6108
6488
  const sea = req2("node:sea");
6109
6489
  if (!sea?.isSea?.() || typeof sea.getAsset !== "function") return null;
6110
6490
  const asset = sea.getAsset("rg");
6111
6491
  if (!asset) return null;
6112
- const dir = fs12.mkdtempSync(path13.join(os2.tmpdir(), "zqagent-rg-"));
6113
- const file = path13.join(dir, process.platform === "win32" ? "rg.exe" : "rg");
6114
- fs12.writeFileSync(file, Buffer.from(asset));
6115
- fs12.chmodSync(file, 493);
6492
+ const dir = fs13.mkdtempSync(path14.join(os2.tmpdir(), "zqagent-rg-"));
6493
+ const file = path14.join(dir, process.platform === "win32" ? "rg.exe" : "rg");
6494
+ fs13.writeFileSync(file, Buffer.from(asset));
6495
+ fs13.chmodSync(file, 493);
6116
6496
  return file;
6117
6497
  } catch {
6118
6498
  return null;
@@ -6121,7 +6501,7 @@ function extractBundledRipgrep() {
6121
6501
  function resolveRipgrep() {
6122
6502
  if (cached2 !== void 0) return cached2;
6123
6503
  const explicit = (process.env.ZQAGENT_RG ?? "").trim();
6124
- if (explicit && fs12.existsSync(explicit)) {
6504
+ if (explicit && fs13.existsSync(explicit)) {
6125
6505
  cached2 = explicit;
6126
6506
  return cached2;
6127
6507
  }
@@ -6135,7 +6515,7 @@ function resolveRipgrep() {
6135
6515
  env: process.env,
6136
6516
  fileExists: (p) => {
6137
6517
  try {
6138
- return fs12.statSync(p).isFile();
6518
+ return fs13.statSync(p).isFile();
6139
6519
  } catch {
6140
6520
  return false;
6141
6521
  }
@@ -6684,8 +7064,8 @@ function registerWebTools(registry) {
6684
7064
 
6685
7065
  // src/tools/git.ts
6686
7066
  init_cjs_shims();
6687
- var fs13 = __toESM(require("fs"), 1);
6688
- var path14 = __toESM(require("path"), 1);
7067
+ var fs14 = __toESM(require("fs"), 1);
7068
+ var path15 = __toESM(require("path"), 1);
6689
7069
  async function runGit(repoDir, args, timeout = 15e3) {
6690
7070
  const res = await execAsync("git", ["-c", "core.quotepath=false", ...args], {
6691
7071
  cwd: repoDir,
@@ -6702,9 +7082,9 @@ async function runGit(repoDir, args, timeout = 15e3) {
6702
7082
  async function resolveRepoRoot(workspace, pathArg) {
6703
7083
  let dir = workspace;
6704
7084
  if (pathArg) {
6705
- const abs = path14.resolve(workspace, pathArg);
7085
+ const abs = path15.resolve(workspace, pathArg);
6706
7086
  try {
6707
- dir = fs13.statSync(abs).isDirectory() ? abs : path14.dirname(abs);
7087
+ dir = fs14.statSync(abs).isDirectory() ? abs : path15.dirname(abs);
6708
7088
  } catch {
6709
7089
  dir = abs;
6710
7090
  }
@@ -6717,24 +7097,24 @@ function repoDirArg(workspace, raw) {
6717
7097
  if (typeof raw !== "string") return void 0;
6718
7098
  const v = raw.trim();
6719
7099
  if (!v) return void 0;
6720
- const base = path14.resolve(workspace);
6721
- const abs = path14.resolve(base, v);
6722
- const rel = path14.relative(base, abs);
6723
- if (rel.startsWith("..") || path14.isAbsolute(rel)) return void 0;
7100
+ const base = path15.resolve(workspace);
7101
+ const abs = path15.resolve(base, v);
7102
+ const rel = path15.relative(base, abs);
7103
+ if (rel.startsWith("..") || path15.isAbsolute(rel)) return void 0;
6724
7104
  return abs;
6725
7105
  }
6726
7106
  function toPathspec(repoRoot, workspace, pathArg) {
6727
7107
  if (!pathArg) return null;
6728
- const abs = path14.resolve(workspace, pathArg);
6729
- let rel = path14.relative(repoRoot, abs);
7108
+ const abs = path15.resolve(workspace, pathArg);
7109
+ let rel = path15.relative(repoRoot, abs);
6730
7110
  if (rel === "") return ".";
6731
- if (rel.startsWith("..") || path14.isAbsolute(rel)) {
7111
+ if (rel.startsWith("..") || path15.isAbsolute(rel)) {
6732
7112
  try {
6733
- const realRepo = fs13.realpathSync(repoRoot);
6734
- const realAbs = fs13.realpathSync(abs);
6735
- const rel2 = path14.relative(realRepo, realAbs);
7113
+ const realRepo = fs14.realpathSync(repoRoot);
7114
+ const realAbs = fs14.realpathSync(abs);
7115
+ const rel2 = path15.relative(realRepo, realAbs);
6736
7116
  if (rel2 === "") return ".";
6737
- if (!rel2.startsWith("..") && !path14.isAbsolute(rel2)) return rel2;
7117
+ if (!rel2.startsWith("..") && !path15.isAbsolute(rel2)) return rel2;
6738
7118
  } catch {
6739
7119
  }
6740
7120
  return null;
@@ -7160,18 +7540,18 @@ var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".hg", ".s
7160
7540
  var SCAN_DEFAULT_DEPTH = 3;
7161
7541
  var SCAN_MAX_DIRS = 2e3;
7162
7542
  function readRepoHead(repoDir) {
7163
- const dotGit = path14.join(repoDir, ".git");
7543
+ const dotGit = path15.join(repoDir, ".git");
7164
7544
  try {
7165
7545
  let headFile;
7166
- if (fs13.statSync(dotGit).isDirectory()) {
7167
- headFile = path14.join(dotGit, "HEAD");
7546
+ if (fs14.statSync(dotGit).isDirectory()) {
7547
+ headFile = path15.join(dotGit, "HEAD");
7168
7548
  } else {
7169
- const raw = fs13.readFileSync(dotGit, "utf-8");
7549
+ const raw = fs14.readFileSync(dotGit, "utf-8");
7170
7550
  const m = /^gitdir:\s*(.+)$/m.exec(raw);
7171
7551
  if (!m) return {};
7172
- headFile = path14.join(path14.resolve(repoDir, m[1].trim()), "HEAD");
7552
+ headFile = path15.join(path15.resolve(repoDir, m[1].trim()), "HEAD");
7173
7553
  }
7174
- const head = fs13.readFileSync(headFile, "utf-8").trim();
7554
+ const head = fs14.readFileSync(headFile, "utf-8").trim();
7175
7555
  const prefix = "ref: refs/heads/";
7176
7556
  if (head.startsWith(prefix)) return { branch: head.slice(prefix.length), detached: false };
7177
7557
  if (/^[0-9a-f]{7,40}$/i.test(head)) return { branch: head.slice(0, 7), detached: true };
@@ -7180,7 +7560,7 @@ function readRepoHead(repoDir) {
7180
7560
  return {};
7181
7561
  }
7182
7562
  function scanGitRepos(workspace, depth = SCAN_DEFAULT_DEPTH) {
7183
- const root = path14.resolve(workspace);
7563
+ const root = path15.resolve(workspace);
7184
7564
  const maxDepth = Number.isFinite(depth) ? Math.min(Math.max(Math.floor(depth), 0), 6) : SCAN_DEFAULT_DEPTH;
7185
7565
  const repos = [];
7186
7566
  const stack = [{ dir: root, depth: 0 }];
@@ -7193,24 +7573,24 @@ function scanGitRepos(workspace, depth = SCAN_DEFAULT_DEPTH) {
7193
7573
  truncated = true;
7194
7574
  break;
7195
7575
  }
7196
- const rel = path14.relative(root, dir).split(path14.sep).join("/");
7197
- if (fs13.existsSync(path14.join(dir, ".git"))) {
7576
+ const rel = path15.relative(root, dir).split(path15.sep).join("/");
7577
+ if (fs14.existsSync(path15.join(dir, ".git"))) {
7198
7578
  repos.push({
7199
7579
  path: rel,
7200
- name: rel ? path14.basename(dir) : path14.basename(root),
7580
+ name: rel ? path15.basename(dir) : path15.basename(root),
7201
7581
  ...readRepoHead(dir)
7202
7582
  });
7203
7583
  }
7204
7584
  if (d >= maxDepth) continue;
7205
7585
  let entries;
7206
7586
  try {
7207
- entries = fs13.readdirSync(dir, { withFileTypes: true });
7587
+ entries = fs14.readdirSync(dir, { withFileTypes: true });
7208
7588
  } catch {
7209
7589
  continue;
7210
7590
  }
7211
7591
  for (const e of entries) {
7212
7592
  if (!e.isDirectory() || SCAN_SKIP_DIRS.has(e.name)) continue;
7213
- stack.push({ dir: path14.join(dir, e.name), depth: d + 1 });
7593
+ stack.push({ dir: path15.join(dir, e.name), depth: d + 1 });
7214
7594
  }
7215
7595
  }
7216
7596
  repos.sort(
@@ -7220,9 +7600,9 @@ function scanGitRepos(workspace, depth = SCAN_DEFAULT_DEPTH) {
7220
7600
  }
7221
7601
  function makeGitRepos(workspace) {
7222
7602
  return async function git_repos(args) {
7223
- const root = path14.resolve(workspace);
7603
+ const root = path15.resolve(workspace);
7224
7604
  try {
7225
- if (!fs13.statSync(root).isDirectory()) return { root, repos: [], error: "\u5DE5\u4F5C\u533A\u4E0D\u662F\u76EE\u5F55" };
7605
+ if (!fs14.statSync(root).isDirectory()) return { root, repos: [], error: "\u5DE5\u4F5C\u533A\u4E0D\u662F\u76EE\u5F55" };
7226
7606
  } catch {
7227
7607
  return { root, repos: [], error: "\u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728" };
7228
7608
  }
@@ -7234,10 +7614,10 @@ function makeGitRepos(workspace) {
7234
7614
 
7235
7615
  // src/tools/diff.ts
7236
7616
  init_cjs_shims();
7237
- var fs14 = __toESM(require("fs"), 1);
7238
- var path15 = __toESM(require("path"), 1);
7617
+ var fs15 = __toESM(require("fs"), 1);
7618
+ var path16 = __toESM(require("path"), 1);
7239
7619
  function makeApplyPatch(workspace) {
7240
- const root = path15.resolve(workspace);
7620
+ const root = path16.resolve(workspace);
7241
7621
  return function apply_patch(args) {
7242
7622
  const patch = String(args.patch ?? "");
7243
7623
  if (!patch.trim()) return { error: "\u8865\u4E01\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A" };
@@ -7276,12 +7656,12 @@ function makeApplyPatch(workspace) {
7276
7656
  contentLines.push(current.slice(1));
7277
7657
  i++;
7278
7658
  }
7279
- if (fs14.existsSync(target)) {
7659
+ if (fs15.existsSync(target)) {
7280
7660
  return { error: `\u6587\u4EF6\u5DF2\u5B58\u5728: ${filePath}` };
7281
7661
  }
7282
- fs14.mkdirSync(path15.dirname(target), { recursive: true });
7283
- fs14.writeFileSync(target, contentLines.join("\n") + "\n", "utf-8");
7284
- const rel = path15.relative(resolved.root, target);
7662
+ fs15.mkdirSync(path16.dirname(target), { recursive: true });
7663
+ fs15.writeFileSync(target, contentLines.join("\n") + "\n", "utf-8");
7664
+ const rel = path16.relative(resolved.root, target);
7285
7665
  changedFiles.push(rel);
7286
7666
  addedFiles.push(rel);
7287
7667
  fileCount++;
@@ -7294,11 +7674,11 @@ function makeApplyPatch(workspace) {
7294
7674
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
7295
7675
  }
7296
7676
  const target = entry.real;
7297
- if (!fs14.existsSync(target)) {
7677
+ if (!fs15.existsSync(target)) {
7298
7678
  return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
7299
7679
  }
7300
- fs14.unlinkSync(target);
7301
- deletedFiles.push(path15.relative(entry.root, target));
7680
+ fs15.unlinkSync(target);
7681
+ deletedFiles.push(path16.relative(entry.root, target));
7302
7682
  fileCount++;
7303
7683
  i++;
7304
7684
  continue;
@@ -7334,10 +7714,10 @@ function makeApplyPatch(workspace) {
7334
7714
  if (!chunkLines.length) {
7335
7715
  return { error: `\u8865\u4E01\u66F4\u65B0\u6CA1\u6709 hunks: ${filePath}` };
7336
7716
  }
7337
- if (!fs14.existsSync(target)) {
7717
+ if (!fs15.existsSync(target)) {
7338
7718
  return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
7339
7719
  }
7340
- const oldLines = fs14.readFileSync(target, "utf-8").split("\n");
7720
+ const oldLines = fs15.readFileSync(target, "utf-8").split("\n");
7341
7721
  const newLines = applyPatchHunks(oldLines, chunkLines, filePath);
7342
7722
  let finalResolved = resolved;
7343
7723
  if (moveTo) {
@@ -7348,20 +7728,20 @@ function makeApplyPatch(workspace) {
7348
7728
  finalResolved = moved;
7349
7729
  }
7350
7730
  const finalTarget = finalResolved.real;
7351
- if (moveTo && fs14.existsSync(finalTarget) && finalTarget !== target) {
7731
+ if (moveTo && fs15.existsSync(finalTarget) && finalTarget !== target) {
7352
7732
  return { error: `\u6587\u4EF6\u5DF2\u5B58\u5728: ${moveTo}` };
7353
7733
  }
7354
- fs14.mkdirSync(path15.dirname(finalTarget), { recursive: true });
7355
- fs14.writeFileSync(finalTarget, newLines.join("\n"), "utf-8");
7734
+ fs15.mkdirSync(path16.dirname(finalTarget), { recursive: true });
7735
+ fs15.writeFileSync(finalTarget, newLines.join("\n"), "utf-8");
7356
7736
  if (moveTo && finalTarget !== target) {
7357
7737
  const entry = resolveEntryWithinRoot(root, filePath);
7358
7738
  if (!entry) {
7359
7739
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
7360
7740
  }
7361
- fs14.unlinkSync(entry.real);
7362
- deletedFiles.push(path15.relative(resolved.root, target));
7741
+ fs15.unlinkSync(entry.real);
7742
+ deletedFiles.push(path16.relative(resolved.root, target));
7363
7743
  }
7364
- changedFiles.push(path15.relative(finalResolved.root, finalTarget));
7744
+ changedFiles.push(path16.relative(finalResolved.root, finalTarget));
7365
7745
  fileCount++;
7366
7746
  continue;
7367
7747
  }
@@ -7373,18 +7753,18 @@ function makeApplyPatch(workspace) {
7373
7753
  return { changed_files: changedFiles, added_files: addedFiles, deleted_files: deletedFiles, file_count: fileCount };
7374
7754
  };
7375
7755
  }
7376
- function applyPatchHunks(oldLines, chunks, path33) {
7756
+ function applyPatchHunks(oldLines, chunks, path35) {
7377
7757
  let newLines = [...oldLines];
7378
7758
  let cursor = 0;
7379
7759
  for (const chunk of chunks) {
7380
7760
  const oldBlock = chunk.filter((l) => l[0] === " " || l[0] === "-").map((l) => l.slice(1));
7381
7761
  const newBlock = chunk.filter((l) => l[0] === " " || l[0] === "+").map((l) => l.slice(1));
7382
7762
  if (!oldBlock.length) {
7383
- throw new Error(`\u8865\u4E01 hunk \u5FC5\u987B\u5305\u542B\u4E0A\u4E0B\u6587\u6216\u5220\u9664\u884C: ${path33}`);
7763
+ throw new Error(`\u8865\u4E01 hunk \u5FC5\u987B\u5305\u542B\u4E0A\u4E0B\u6587\u6216\u5220\u9664\u884C: ${path35}`);
7384
7764
  }
7385
7765
  const matchStart = findBlockMatch(newLines, oldBlock, cursor);
7386
7766
  if (matchStart === -1) {
7387
- throw new Error(`\u8865\u4E01\u4E0A\u4E0B\u6587\u4E0D\u5339\u914D: ${path33}`);
7767
+ throw new Error(`\u8865\u4E01\u4E0A\u4E0B\u6587\u4E0D\u5339\u914D: ${path35}`);
7388
7768
  }
7389
7769
  const matchEnd = matchStart + oldBlock.length;
7390
7770
  newLines.splice(matchStart, oldBlock.length, ...newBlock);
@@ -7401,7 +7781,7 @@ function findBlockMatch(lines, block, start) {
7401
7781
  return -1;
7402
7782
  }
7403
7783
  function makeApplyUnifiedDiff(workspace) {
7404
- const root = path15.resolve(workspace);
7784
+ const root = path16.resolve(workspace);
7405
7785
  return function apply_unified_diff(args) {
7406
7786
  const diff = String(args.diff ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
7407
7787
  if (!diff.trim()) return { error: "diff \u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A" };
@@ -7420,26 +7800,26 @@ function makeApplyUnifiedDiff(workspace) {
7420
7800
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${targetPath}` };
7421
7801
  }
7422
7802
  const target = resolved.real;
7423
- const oldLines = oldIsDevNull ? [] : fs14.existsSync(target) ? fs14.readFileSync(target, "utf-8").split("\n") : [];
7803
+ const oldLines = oldIsDevNull ? [] : fs15.existsSync(target) ? fs15.readFileSync(target, "utf-8").split("\n") : [];
7424
7804
  const newLines = applyDiffHunks(oldLines, patch.hunks, targetPath);
7425
7805
  hunkCount += patch.hunks.length;
7426
7806
  if (newIsDevNull) {
7427
- if (fs14.existsSync(target)) {
7428
- if (!fs14.statSync(target).isFile()) {
7807
+ if (fs15.existsSync(target)) {
7808
+ if (!fs15.statSync(target).isFile()) {
7429
7809
  return { error: `\u8DEF\u5F84\u4E0D\u662F\u6587\u4EF6: ${targetPath}` };
7430
7810
  }
7431
7811
  const entry = resolveEntryWithinRoot(root, targetPath);
7432
7812
  if (!entry) {
7433
7813
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${targetPath}` };
7434
7814
  }
7435
- fs14.unlinkSync(entry.real);
7815
+ fs15.unlinkSync(entry.real);
7436
7816
  }
7437
- deletedFiles.push(path15.relative(resolved.root, target));
7817
+ deletedFiles.push(path16.relative(resolved.root, target));
7438
7818
  continue;
7439
7819
  }
7440
- fs14.mkdirSync(path15.dirname(target), { recursive: true });
7441
- fs14.writeFileSync(target, newLines.join("\n"), "utf-8");
7442
- const rel = path15.relative(resolved.root, target);
7820
+ fs15.mkdirSync(path16.dirname(target), { recursive: true });
7821
+ fs15.writeFileSync(target, newLines.join("\n"), "utf-8");
7822
+ const rel = path16.relative(resolved.root, target);
7443
7823
  changedFiles.push(rel);
7444
7824
  if (oldIsDevNull) addedFiles.push(rel);
7445
7825
  }
@@ -7840,7 +8220,8 @@ function buildExplorerEngine(opts) {
7840
8220
  model: opts.model,
7841
8221
  instructions: EXPLORER_INSTRUCTIONS,
7842
8222
  maxIterations: parseMaxIterations(process.env.EXPLORE_MAX_ITERATIONS, 30),
7843
- hook: opts.hook
8223
+ hook: opts.hook,
8224
+ toolResultDir: opts.toolResultDir
7844
8225
  });
7845
8226
  }
7846
8227
  function makeExploreTool(opts) {
@@ -7852,7 +8233,8 @@ function makeExploreTool(opts) {
7852
8233
  provider: opts.provider,
7853
8234
  model: opts.model,
7854
8235
  readonlyRegistrars: opts.readonlyRegistrars,
7855
- hook: opts.hookFactory?.()
8236
+ hook: opts.hookFactory?.(),
8237
+ toolResultDir: opts.toolResultDir
7856
8238
  });
7857
8239
  let report = "";
7858
8240
  let status = "unknown";
@@ -7913,14 +8295,14 @@ function registerExploreTool(registry, opts) {
7913
8295
 
7914
8296
  // src/tools/media.ts
7915
8297
  init_cjs_shims();
7916
- var fs16 = __toESM(require("fs"), 1);
7917
- var path17 = __toESM(require("path"), 1);
8298
+ var fs17 = __toESM(require("fs"), 1);
8299
+ var path18 = __toESM(require("path"), 1);
7918
8300
 
7919
8301
  // src/tools/ffmpegRuntime.ts
7920
8302
  init_cjs_shims();
7921
- var fs15 = __toESM(require("fs"), 1);
8303
+ var fs16 = __toESM(require("fs"), 1);
7922
8304
  var os3 = __toESM(require("os"), 1);
7923
- var path16 = __toESM(require("path"), 1);
8305
+ var path17 = __toESM(require("path"), 1);
7924
8306
  var import_node_module2 = require("module");
7925
8307
  var import_node_crypto = require("crypto");
7926
8308
  var import_promises = require("stream/promises");
@@ -7943,29 +8325,29 @@ if (PROXY_ENV2) {
7943
8325
  }
7944
8326
  var DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
7945
8327
  function agentBinDir(workspace) {
7946
- return path16.join(path16.resolve(workspace), ".agent", "bin");
8328
+ return path17.join(path17.resolve(workspace), ".agent", "bin");
7947
8329
  }
7948
8330
  function exeName(name, platform) {
7949
8331
  return platform === "win32" ? `${name}.exe` : name;
7950
8332
  }
7951
8333
  function defaultFileExists2(p) {
7952
8334
  try {
7953
- return fs15.statSync(p).isFile();
8335
+ return fs16.statSync(p).isFile();
7954
8336
  } catch {
7955
8337
  return false;
7956
8338
  }
7957
8339
  }
7958
8340
  function extractBundled(name, platform) {
7959
8341
  try {
7960
- const req2 = typeof require === "function" ? require : (0, import_node_module2.createRequire)(path16.join(process.cwd(), "__zqagent__.js"));
8342
+ const req2 = typeof require === "function" ? require : (0, import_node_module2.createRequire)(path17.join(process.cwd(), "__zqagent__.js"));
7961
8343
  const sea = req2("node:sea");
7962
8344
  if (!sea?.isSea?.() || typeof sea.getAsset !== "function") return null;
7963
8345
  const asset = sea.getAsset(name);
7964
8346
  if (!asset) return null;
7965
- const dir = fs15.mkdtempSync(path16.join(os3.tmpdir(), `zqagent-${name}-`));
7966
- const file = path16.join(dir, exeName(name, platform));
7967
- fs15.writeFileSync(file, Buffer.from(asset));
7968
- fs15.chmodSync(file, 493);
8347
+ const dir = fs16.mkdtempSync(path17.join(os3.tmpdir(), `zqagent-${name}-`));
8348
+ const file = path17.join(dir, exeName(name, platform));
8349
+ fs16.writeFileSync(file, Buffer.from(asset));
8350
+ fs16.chmodSync(file, 493);
7969
8351
  return file;
7970
8352
  } catch {
7971
8353
  return null;
@@ -7975,7 +8357,7 @@ function resolveOne(name, workspace, o) {
7975
8357
  const envKey = name === "ffmpeg" ? ENV_FFMPEG : ENV_FFPROBE;
7976
8358
  const explicit = (o.env[envKey] ?? "").trim();
7977
8359
  if (explicit && o.fileExists(explicit)) return { path: explicit, source: "env" };
7978
- const local = path16.join(agentBinDir(workspace), exeName(name, o.platform));
8360
+ const local = path17.join(agentBinDir(workspace), exeName(name, o.platform));
7979
8361
  if (o.fileExists(local)) return { path: local, source: "agent-bin" };
7980
8362
  const bundled = extractBundled(name, o.platform);
7981
8363
  if (bundled) return { path: bundled, source: "bundled" };
@@ -7989,7 +8371,7 @@ function resolveOne(name, workspace, o) {
7989
8371
  var cache = /* @__PURE__ */ new Map();
7990
8372
  function resolveMedia(workspace, opts = {}) {
7991
8373
  const platform = opts.platform ?? process.platform;
7992
- const key = `${platform}:${path16.resolve(workspace)}`;
8374
+ const key = `${platform}:${path17.resolve(workspace)}`;
7993
8375
  const hit = cache.get(key);
7994
8376
  if (hit) return hit;
7995
8377
  const ctx = {
@@ -8095,7 +8477,7 @@ function parseChecksumFile(text, filename) {
8095
8477
  if (!m) continue;
8096
8478
  const hash = m[1].toLowerCase();
8097
8479
  const name = m[2]?.trim() ?? "";
8098
- if (!name || name === filename || path16.basename(name) === filename) return hash;
8480
+ if (!name || name === filename || path17.basename(name) === filename) return hash;
8099
8481
  }
8100
8482
  const single = lines[0]?.trim();
8101
8483
  if (lines.length === 1 && single && /^[0-9a-f]{32,128}$/i.test(single)) {
@@ -8105,7 +8487,7 @@ function parseChecksumFile(text, filename) {
8105
8487
  }
8106
8488
  async function computeChecksum(file, algo) {
8107
8489
  const hash = (0, import_node_crypto.createHash)(algo);
8108
- await (0, import_promises.pipeline)(fs15.createReadStream(file), hash);
8490
+ await (0, import_promises.pipeline)(fs16.createReadStream(file), hash);
8109
8491
  return hash.digest("hex");
8110
8492
  }
8111
8493
  function fetchInit() {
@@ -8120,7 +8502,7 @@ async function downloadToFile(url, dest) {
8120
8502
  const resp = await (0, import_node_fetch3.default)(url, fetchInit());
8121
8503
  if (!resp.ok) throw new Error(`HTTP ${resp.status} ${resp.statusText}`);
8122
8504
  if (!resp.body) throw new Error("\u54CD\u5E94\u6CA1\u6709\u5185\u5BB9");
8123
- await (0, import_promises.pipeline)(resp.body, fs15.createWriteStream(dest));
8505
+ await (0, import_promises.pipeline)(resp.body, fs16.createWriteStream(dest));
8124
8506
  }
8125
8507
  async function resolveComponentUrl(c) {
8126
8508
  if (c.url) return c.url;
@@ -8137,12 +8519,12 @@ function findBinaryInTree(dir, names) {
8137
8519
  const cur = stack.pop();
8138
8520
  let entries;
8139
8521
  try {
8140
- entries = fs15.readdirSync(cur, { withFileTypes: true });
8522
+ entries = fs16.readdirSync(cur, { withFileTypes: true });
8141
8523
  } catch {
8142
8524
  continue;
8143
8525
  }
8144
8526
  for (const e of entries) {
8145
- const full = path16.join(cur, e.name);
8527
+ const full = path17.join(cur, e.name);
8146
8528
  if (e.isDirectory()) {
8147
8529
  stack.push(full);
8148
8530
  } else if (names.includes(e.name)) {
@@ -8153,7 +8535,7 @@ function findBinaryInTree(dir, names) {
8153
8535
  return null;
8154
8536
  }
8155
8537
  async function extractArchive(archive, dest, kind, platform) {
8156
- fs15.mkdirSync(dest, { recursive: true });
8538
+ fs16.mkdirSync(dest, { recursive: true });
8157
8539
  let res;
8158
8540
  if (kind === "tar.xz") {
8159
8541
  res = await execAsync("tar", ["-xJf", archive, "-C", dest]);
@@ -8189,7 +8571,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
8189
8571
  error: `\u6682\u4E0D\u652F\u6301\u81EA\u52A8\u4E0B\u8F7D\u7684\u5E73\u53F0: ${platform}/${arch}`
8190
8572
  };
8191
8573
  }
8192
- const tmp = fs15.mkdtempSync(path16.join(os3.tmpdir(), "zqagent-ffmpeg-"));
8574
+ const tmp = fs16.mkdtempSync(path17.join(os3.tmpdir(), "zqagent-ffmpeg-"));
8193
8575
  const binDir = agentBinDir(workspace);
8194
8576
  const installed = {};
8195
8577
  try {
@@ -8203,7 +8585,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
8203
8585
  }
8204
8586
  let archive = fetched.get(url);
8205
8587
  if (!archive) {
8206
- const archivePath = path16.join(tmp, path16.basename(new URL(url).pathname) || `${c.name}.${c.archive}`);
8588
+ const archivePath = path17.join(tmp, path17.basename(new URL(url).pathname) || `${c.name}.${c.archive}`);
8207
8589
  log(`\u4E0B\u8F7D ${url}`);
8208
8590
  try {
8209
8591
  await downloadToFile(url, archivePath);
@@ -8213,7 +8595,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
8213
8595
  if (c.checksumUrl) {
8214
8596
  const algo = c.checksumAlgo ?? "sha256";
8215
8597
  try {
8216
- const expected = parseChecksumFile(await fetchText(c.checksumUrl), path16.basename(archivePath));
8598
+ const expected = parseChecksumFile(await fetchText(c.checksumUrl), path17.basename(archivePath));
8217
8599
  if (!expected) {
8218
8600
  notes.push(`\u672A\u80FD\u4ECE ${c.checksumUrl} \u89E3\u6790\u51FA\u6821\u9A8C\u548C\uFF0C\u5DF2\u8DF3\u8FC7\u6821\u9A8C`);
8219
8601
  } else {
@@ -8230,24 +8612,24 @@ async function downloadFfmpeg(workspace, opts = {}) {
8230
8612
  notes.push(`\u6821\u9A8C\u548C\u83B7\u53D6\u5931\u8D25 (${c.checksumUrl}): ${String(e instanceof Error ? e.message : e)}`);
8231
8613
  }
8232
8614
  } else {
8233
- notes.push(`${path16.basename(archivePath)} \u672A\u63D0\u4F9B\u6821\u9A8C\u548C\uFF08\u4EC5 HTTPS \u4F20\u8F93\u4FDD\u62A4\uFF09`);
8615
+ notes.push(`${path17.basename(archivePath)} \u672A\u63D0\u4F9B\u6821\u9A8C\u548C\uFF08\u4EC5 HTTPS \u4F20\u8F93\u4FDD\u62A4\uFF09`);
8234
8616
  }
8235
8617
  archive = { file: archivePath, kind: c.archive };
8236
8618
  fetched.set(url, archive);
8237
8619
  }
8238
- const extractDir = fs15.mkdtempSync(path16.join(tmp, "x-"));
8620
+ const extractDir = fs16.mkdtempSync(path17.join(tmp, "x-"));
8239
8621
  const err = await extractArchive(archive.file, extractDir, archive.kind, platform);
8240
8622
  if (err) return { ok: false, error: err };
8241
8623
  const found = findBinaryInTree(extractDir, [exeName(c.name, platform), c.name]);
8242
8624
  if (!found) {
8243
- return { ok: false, error: `\u5F52\u6863\u5185\u672A\u627E\u5230 ${c.name}: ${path16.basename(archive.file)}` };
8625
+ return { ok: false, error: `\u5F52\u6863\u5185\u672A\u627E\u5230 ${c.name}: ${path17.basename(archive.file)}` };
8244
8626
  }
8245
- fs15.mkdirSync(binDir, { recursive: true });
8246
- const target = path16.join(binDir, exeName(c.name, platform));
8627
+ fs16.mkdirSync(binDir, { recursive: true });
8628
+ const target = path17.join(binDir, exeName(c.name, platform));
8247
8629
  const staging = `${target}.tmp-${process.pid}`;
8248
- fs15.copyFileSync(found, staging);
8249
- fs15.chmodSync(staging, 493);
8250
- fs15.renameSync(staging, target);
8630
+ fs16.copyFileSync(found, staging);
8631
+ fs16.chmodSync(staging, 493);
8632
+ fs16.renameSync(staging, target);
8251
8633
  installed[c.name] = target;
8252
8634
  }
8253
8635
  resetFfmpegCache();
@@ -8256,7 +8638,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
8256
8638
  } catch (e) {
8257
8639
  return { ok: false, error: `\u5B89\u88C5\u5931\u8D25: ${String(e instanceof Error ? e.message : e)}` };
8258
8640
  } finally {
8259
- fs15.rmSync(tmp, { recursive: true, force: true });
8641
+ fs16.rmSync(tmp, { recursive: true, force: true });
8260
8642
  }
8261
8643
  }
8262
8644
  function canAutoDownload(platform, arch) {
@@ -8521,13 +8903,13 @@ function resolveTarget(workspace, value) {
8521
8903
  return { abs: resolved.real, root: resolved.root };
8522
8904
  }
8523
8905
  function relOut(workspace, abs) {
8524
- return path17.relative(workspace, abs) || abs;
8906
+ return path18.relative(workspace, abs) || abs;
8525
8907
  }
8526
8908
  function registerMediaTool(registry, workspace, opts = {}) {
8527
8909
  const platform = opts.platform ?? process.platform;
8528
8910
  const arch = process.arch;
8529
8911
  const media = async (args) => {
8530
- const root = path17.resolve(workspace);
8912
+ const root = path18.resolve(workspace);
8531
8913
  const op = str(args["op"]);
8532
8914
  if (!op) {
8533
8915
  return { error: `\u7F3A\u5C11\u53C2\u6570 op\u3002\u53EF\u9009\u503C\uFF1A${OPS.join(" / ")}` };
@@ -8572,7 +8954,7 @@ function registerMediaTool(registry, workspace, opts = {}) {
8572
8954
  if (!input) return { error: `\u64CD\u4F5C ${op} \u9700\u8981\u53C2\u6570 input\uFF08\u5DE5\u4F5C\u533A\u5185\u76F8\u5BF9\u8DEF\u5F84\uFF09` };
8573
8955
  const r = resolveTarget(root, input);
8574
8956
  if ("error" in r) return { error: r.error };
8575
- if (!fs16.existsSync(r.abs) || !fs16.statSync(r.abs).isFile()) {
8957
+ if (!fs17.existsSync(r.abs) || !fs17.statSync(r.abs).isFile()) {
8576
8958
  return { error: `\u8F93\u5165\u6587\u4EF6\u4E0D\u5B58\u5728: ${input}` };
8577
8959
  }
8578
8960
  inputAbs = r.abs;
@@ -8584,7 +8966,7 @@ function registerMediaTool(registry, workspace, opts = {}) {
8584
8966
  if (!output) return { error: `\u64CD\u4F5C ${op} \u9700\u8981\u53C2\u6570 output\uFF08\u5DE5\u4F5C\u533A\u5185\u76F8\u5BF9\u8DEF\u5F84\uFF09` };
8585
8967
  const r = resolveTarget(root, output);
8586
8968
  if ("error" in r) return { error: r.error };
8587
- fs16.mkdirSync(path17.dirname(r.abs), { recursive: true });
8969
+ fs17.mkdirSync(path18.dirname(r.abs), { recursive: true });
8588
8970
  outputAbs = r.abs;
8589
8971
  relRoot = r.root;
8590
8972
  }
@@ -8634,7 +9016,7 @@ function registerMediaTool(registry, workspace, opts = {}) {
8634
9016
  };
8635
9017
  if (outputAbs) {
8636
9018
  try {
8637
- result["size_bytes"] = fs16.statSync(outputAbs).size;
9019
+ result["size_bytes"] = fs17.statSync(outputAbs).size;
8638
9020
  } catch {
8639
9021
  }
8640
9022
  }
@@ -8825,8 +9207,8 @@ function resolveSessionProvider(store, tenant, providerId) {
8825
9207
 
8826
9208
  // src/server/usage.ts
8827
9209
  init_cjs_shims();
8828
- var fs17 = __toESM(require("fs"), 1);
8829
- var path18 = __toESM(require("path"), 1);
9210
+ var fs18 = __toESM(require("fs"), 1);
9211
+ var path19 = __toESM(require("path"), 1);
8830
9212
  var LEDGER_RETENTION_MONTHS = 12;
8831
9213
  var USAGE_SCHEMA = 1;
8832
9214
  var DEFAULT_PRICING = {
@@ -8907,15 +9289,15 @@ function retainedMonths(now = /* @__PURE__ */ new Date()) {
8907
9289
  return out;
8908
9290
  }
8909
9291
  function atomicWrite(file, content) {
8910
- fs17.mkdirSync(path18.dirname(file), { recursive: true });
9292
+ fs18.mkdirSync(path19.dirname(file), { recursive: true });
8911
9293
  const tmp = `${file}.tmp`;
8912
- fs17.writeFileSync(tmp, content, "utf-8");
8913
- fs17.renameSync(tmp, file);
9294
+ fs18.writeFileSync(tmp, content, "utf-8");
9295
+ fs18.renameSync(tmp, file);
8914
9296
  }
8915
9297
  var UsageLedger = class {
8916
9298
  constructor(root) {
8917
9299
  this.root = root;
8918
- this.dir = path18.join(root, "usage");
9300
+ this.dir = path19.join(root, "usage");
8919
9301
  }
8920
9302
  root;
8921
9303
  dir;
@@ -8928,18 +9310,18 @@ var UsageLedger = class {
8928
9310
  return this.dir;
8929
9311
  }
8930
9312
  ledgerFile(month) {
8931
- return path18.join(this.ledgerDir, `ledger-${month}.jsonl`);
9313
+ return path19.join(this.ledgerDir, `ledger-${month}.jsonl`);
8932
9314
  }
8933
9315
  get rollupFile() {
8934
- return path18.join(this.ledgerDir, "rollup.json");
9316
+ return path19.join(this.ledgerDir, "rollup.json");
8935
9317
  }
8936
9318
  get pricingFile() {
8937
- return path18.join(this.ledgerDir, "pricing.json");
9319
+ return path19.join(this.ledgerDir, "pricing.json");
8938
9320
  }
8939
9321
  // ===================== 初始化 =====================
8940
9322
  init(now = /* @__PURE__ */ new Date()) {
8941
9323
  if (this.inited) return;
8942
- fs17.mkdirSync(this.ledgerDir, { recursive: true });
9324
+ fs18.mkdirSync(this.ledgerDir, { recursive: true });
8943
9325
  this.pricing = this.loadPricing();
8944
9326
  this.rollup = this.loadRollup(now);
8945
9327
  this.pruneLedgers(now);
@@ -8947,7 +9329,7 @@ var UsageLedger = class {
8947
9329
  }
8948
9330
  loadPricing() {
8949
9331
  try {
8950
- const raw = fs17.readFileSync(this.pricingFile, "utf-8");
9332
+ const raw = fs18.readFileSync(this.pricingFile, "utf-8");
8951
9333
  const parsed = JSON.parse(raw);
8952
9334
  const vendors = {};
8953
9335
  for (const v of /* @__PURE__ */ new Set([
@@ -8976,7 +9358,7 @@ var UsageLedger = class {
8976
9358
  }
8977
9359
  loadRollup(now) {
8978
9360
  try {
8979
- const raw = fs17.readFileSync(this.rollupFile, "utf-8");
9361
+ const raw = fs18.readFileSync(this.rollupFile, "utf-8");
8980
9362
  const parsed = JSON.parse(raw);
8981
9363
  return {
8982
9364
  schema: USAGE_SCHEMA,
@@ -9003,10 +9385,10 @@ var UsageLedger = class {
9003
9385
  const keep = retainedMonths(now);
9004
9386
  let changed = false;
9005
9387
  try {
9006
- for (const f of fs17.readdirSync(this.ledgerDir)) {
9388
+ for (const f of fs18.readdirSync(this.ledgerDir)) {
9007
9389
  const m = /^ledger-(\d{4}-\d{2})\.jsonl$/.exec(f);
9008
9390
  if (m && !keep.has(m[1])) {
9009
- fs17.unlinkSync(path18.join(this.ledgerDir, f));
9391
+ fs18.unlinkSync(path19.join(this.ledgerDir, f));
9010
9392
  }
9011
9393
  }
9012
9394
  } catch {
@@ -9083,7 +9465,7 @@ var UsageLedger = class {
9083
9465
  };
9084
9466
  const line = JSON.stringify(rec) + "\n";
9085
9467
  try {
9086
- fs17.appendFileSync(this.ledgerFile(monthOf(rec.ts)), line);
9468
+ fs18.appendFileSync(this.ledgerFile(monthOf(rec.ts)), line);
9087
9469
  } catch (e) {
9088
9470
  console.error(`[usage] \u5199\u8D26\u672C\u5931\u8D25: ${String(e)}`);
9089
9471
  }
@@ -9255,10 +9637,10 @@ var UsageLedger = class {
9255
9637
  const rows = [];
9256
9638
  for (const month of months) {
9257
9639
  const file = this.ledgerFile(month);
9258
- if (!fs17.existsSync(file)) continue;
9640
+ if (!fs18.existsSync(file)) continue;
9259
9641
  let content = "";
9260
9642
  try {
9261
- content = fs17.readFileSync(file, "utf-8");
9643
+ content = fs18.readFileSync(file, "utf-8");
9262
9644
  } catch {
9263
9645
  continue;
9264
9646
  }
@@ -9367,10 +9749,10 @@ var UsageLedger = class {
9367
9749
  const seen = /* @__PURE__ */ new Set();
9368
9750
  for (const month of this.monthsInRange("", "")) {
9369
9751
  const file = this.ledgerFile(month);
9370
- if (!fs17.existsSync(file)) continue;
9752
+ if (!fs18.existsSync(file)) continue;
9371
9753
  let content = "";
9372
9754
  try {
9373
- content = fs17.readFileSync(file, "utf-8");
9755
+ content = fs18.readFileSync(file, "utf-8");
9374
9756
  } catch {
9375
9757
  continue;
9376
9758
  }
@@ -10678,7 +11060,10 @@ data: ${JSON.stringify(entry.data)}
10678
11060
  // 上下文窗口:环境变量显式覆盖 > 供应商 /models 提供的真实长度 > 引擎内置表
10679
11061
  contextWindow: parseOptionalPositiveNumber(process.env.CONTEXT_WINDOW) ?? modelContextWindow(eff.modelMeta, activeModel),
10680
11062
  thresholdPct: parseRatio(process.env.COMPACTION_THRESHOLD_PCT),
10681
- capTokens: parseOptionalPositiveNumber(process.env.COMPACTION_CAP_TOKENS) ?? null
11063
+ softThresholdPct: parseRatio(process.env.COMPACTION_SOFT_THRESHOLD_PCT),
11064
+ capTokens: parseOptionalPositiveNumber(process.env.COMPACTION_CAP_TOKENS) ?? null,
11065
+ // 超预算工具结果按租户/工作区隔离落盘(多租户共用一个状态目录会混内容)
11066
+ toolResultDir: path20.join(workspace, ".agent", "tool-results")
10682
11067
  });
10683
11068
  engine.setApprover(this._makeApprover(rt));
10684
11069
  const mcpConfigs = loadServerMcpConfigs(tenant);
@@ -10859,11 +11244,11 @@ var ApiError = class extends Error {
10859
11244
  // src/server/routes.ts
10860
11245
  init_cjs_shims();
10861
11246
  var import_http = require("http");
10862
- var path23 = __toESM(require("path"), 1);
11247
+ var path25 = __toESM(require("path"), 1);
10863
11248
 
10864
11249
  // src/server/projects.ts
10865
11250
  init_cjs_shims();
10866
- var path19 = __toESM(require("path"), 1);
11251
+ var path21 = __toESM(require("path"), 1);
10867
11252
  var DEFAULT_MODEL = () => process.env.OPENAI_MODEL || process.env.MODEL || "gpt-4o";
10868
11253
  function toProjectSummary(store, tenant) {
10869
11254
  return {
@@ -10996,9 +11381,9 @@ function createProject(store, input) {
10996
11381
  if (store.loadTenant(id)) {
10997
11382
  throw new ApiError(409, "project_exists", `\u9879\u76EE ${id} \u5DF2\u5B58\u5728`);
10998
11383
  }
10999
- const workspaceRoot = typeof raw.workspaceRoot === "string" && raw.workspaceRoot.trim() ? path19.resolve(raw.workspaceRoot.trim()) : void 0;
11384
+ const workspaceRoot = typeof raw.workspaceRoot === "string" && raw.workspaceRoot.trim() ? path21.resolve(raw.workspaceRoot.trim()) : void 0;
11000
11385
  const explicitName = typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : "";
11001
- const defaultName = workspaceRoot ? path19.basename(workspaceRoot) : id;
11386
+ const defaultName = workspaceRoot ? path21.basename(workspaceRoot) : id;
11002
11387
  const tenant = {
11003
11388
  id,
11004
11389
  name: explicitName || defaultName,
@@ -11028,7 +11413,7 @@ function buildNodeInfo(store, opts) {
11028
11413
  nodeId: opts.nodeId ?? ensureNodeId(store),
11029
11414
  platform: process.platform,
11030
11415
  mode: process.env.NODE_MODE ?? "standalone",
11031
- version: "0.2.4",
11416
+ version: "0.2.6",
11032
11417
  admin: opts.admin,
11033
11418
  projects: opts.projects
11034
11419
  };
@@ -11112,8 +11497,8 @@ function hubProxyEnabled(host) {
11112
11497
  const h = (host ?? "127.0.0.1").trim();
11113
11498
  return h === "127.0.0.1" || h === "localhost" || h === "::1";
11114
11499
  }
11115
- function isHubProxyPath(path33) {
11116
- return path33.startsWith(HUB_PROXY_PREFIX);
11500
+ function isHubProxyPath(path35) {
11501
+ return path35.startsWith(HUB_PROXY_PREFIX);
11117
11502
  }
11118
11503
  function decodeTarget(enc) {
11119
11504
  try {
@@ -11207,34 +11592,34 @@ async function handleHubProxy(req2, res) {
11207
11592
 
11208
11593
  // src/server/fsBrowser.ts
11209
11594
  init_cjs_shims();
11210
- var fs18 = __toESM(require("fs"), 1);
11595
+ var fs19 = __toESM(require("fs"), 1);
11211
11596
  var os4 = __toESM(require("os"), 1);
11212
- var path20 = __toESM(require("path"), 1);
11597
+ var path22 = __toESM(require("path"), 1);
11213
11598
  var MAX_ENTRIES = 2e3;
11214
11599
  var DEFAULT_SEARCH_LIMIT = 200;
11215
11600
  var MAX_SEARCH_LIMIT = 1e3;
11216
11601
  function listDirs(input) {
11217
11602
  const base = (input ?? "").trim() || os4.homedir();
11218
- const target = path20.resolve(base);
11603
+ const target = path22.resolve(base);
11219
11604
  let st;
11220
11605
  try {
11221
- st = fs18.statSync(target);
11606
+ st = fs19.statSync(target);
11222
11607
  } catch {
11223
11608
  throw new Error(`\u8DEF\u5F84\u4E0D\u5B58\u5728: ${target}`);
11224
11609
  }
11225
11610
  if (!st.isDirectory()) throw new Error(`\u4E0D\u662F\u76EE\u5F55: ${target}`);
11226
11611
  let dirents;
11227
11612
  try {
11228
- dirents = fs18.readdirSync(target, { withFileTypes: true });
11613
+ dirents = fs19.readdirSync(target, { withFileTypes: true });
11229
11614
  } catch {
11230
11615
  throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF08\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09: ${target}`);
11231
11616
  }
11232
11617
  const entries = dirents.map((d) => {
11233
- const full = path20.join(target, d.name);
11618
+ const full = path22.join(target, d.name);
11234
11619
  let isDir = d.isDirectory();
11235
11620
  if (d.isSymbolicLink()) {
11236
11621
  try {
11237
- isDir = fs18.statSync(full).isDirectory();
11622
+ isDir = fs19.statSync(full).isDirectory();
11238
11623
  } catch {
11239
11624
  }
11240
11625
  }
@@ -11243,7 +11628,7 @@ function listDirs(input) {
11243
11628
  entries.sort(
11244
11629
  (a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1
11245
11630
  );
11246
- const parentDir = path20.dirname(target);
11631
+ const parentDir = path22.dirname(target);
11247
11632
  return {
11248
11633
  path: target,
11249
11634
  parent: parentDir !== target ? parentDir : null,
@@ -11274,7 +11659,7 @@ var NAME_CACHE_MAX_DIRS = 4;
11274
11659
  var NAME_CACHE_MAX_ENTRIES = 5e4;
11275
11660
  var nameCache = /* @__PURE__ */ new Map();
11276
11661
  function invalidateDirNames(target) {
11277
- nameCache.delete(path20.resolve(target));
11662
+ nameCache.delete(path22.resolve(target));
11278
11663
  }
11279
11664
  async function loadDirNames(target) {
11280
11665
  const now = Date.now();
@@ -11286,7 +11671,7 @@ async function loadDirNames(target) {
11286
11671
  }
11287
11672
  let dirents;
11288
11673
  try {
11289
- dirents = await fs18.promises.readdir(target, { withFileTypes: true });
11674
+ dirents = await fs19.promises.readdir(target, { withFileTypes: true });
11290
11675
  } catch {
11291
11676
  throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF08\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09: ${target}`);
11292
11677
  }
@@ -11309,11 +11694,11 @@ async function loadDirNames(target) {
11309
11694
  return entries;
11310
11695
  }
11311
11696
  async function toDirEntry(dir, e) {
11312
- const full = path20.join(dir, e.name);
11697
+ const full = path22.join(dir, e.name);
11313
11698
  let isDir = e.isDir;
11314
11699
  if (e.isLink) {
11315
11700
  try {
11316
- isDir = (await fs18.promises.stat(full)).isDirectory();
11701
+ isDir = (await fs19.promises.stat(full)).isDirectory();
11317
11702
  } catch {
11318
11703
  }
11319
11704
  }
@@ -11321,10 +11706,10 @@ async function toDirEntry(dir, e) {
11321
11706
  }
11322
11707
  async function searchDirs(input, query, limit) {
11323
11708
  const base = (input ?? "").trim() || os4.homedir();
11324
- const target = path20.resolve(base);
11709
+ const target = path22.resolve(base);
11325
11710
  let st;
11326
11711
  try {
11327
- st = await fs18.promises.stat(target);
11712
+ st = await fs19.promises.stat(target);
11328
11713
  } catch {
11329
11714
  throw new Error(`\u8DEF\u5F84\u4E0D\u5B58\u5728: ${target}`);
11330
11715
  }
@@ -11354,7 +11739,7 @@ async function searchDirs(input, query, limit) {
11354
11739
  );
11355
11740
  const hit = scored.slice(0, max);
11356
11741
  const entries = await Promise.all(hit.map(({ entry }) => toDirEntry(target, entry)));
11357
- const parentDir = path20.dirname(target);
11742
+ const parentDir = path22.dirname(target);
11358
11743
  return {
11359
11744
  path: target,
11360
11745
  parent: parentDir !== target ? parentDir : null,
@@ -11365,52 +11750,52 @@ async function searchDirs(input, query, limit) {
11365
11750
  };
11366
11751
  }
11367
11752
  function makeDirIn(parentInput, name) {
11368
- const parent = path20.resolve((parentInput ?? "").trim() || os4.homedir());
11753
+ const parent = path22.resolve((parentInput ?? "").trim() || os4.homedir());
11369
11754
  const clean = (name ?? "").trim();
11370
11755
  if (!clean) throw new Error("\u76EE\u5F55\u540D\u4E0D\u80FD\u4E3A\u7A7A");
11371
11756
  if (clean === "." || clean === "..") throw new Error("\u76EE\u5F55\u540D\u975E\u6CD5");
11372
- const target = path20.resolve(parent, clean);
11373
- const rel = path20.relative(parent, target);
11374
- if (!rel || rel.startsWith("..") || path20.isAbsolute(rel)) {
11757
+ const target = path22.resolve(parent, clean);
11758
+ const rel = path22.relative(parent, target);
11759
+ if (!rel || rel.startsWith("..") || path22.isAbsolute(rel)) {
11375
11760
  throw new Error("\u76EE\u5F55\u540D\u4E0D\u80FD\u8D8A\u51FA\u5F53\u524D\u76EE\u5F55");
11376
11761
  }
11377
11762
  let st = null;
11378
11763
  try {
11379
- st = fs18.statSync(target);
11764
+ st = fs19.statSync(target);
11380
11765
  } catch {
11381
11766
  st = null;
11382
11767
  }
11383
11768
  if (st && !st.isDirectory()) throw new Error(`\u5DF2\u5B58\u5728\u540C\u540D\u6587\u4EF6: ${target}`);
11384
11769
  try {
11385
- fs18.mkdirSync(target, { recursive: true });
11770
+ fs19.mkdirSync(target, { recursive: true });
11386
11771
  } catch (e) {
11387
11772
  throw new Error(`\u521B\u5EFA\u76EE\u5F55\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
11388
11773
  }
11389
11774
  invalidateDirNames(parent);
11390
- return { name: path20.basename(target), path: target, type: "dir" };
11775
+ return { name: path22.basename(target), path: target, type: "dir" };
11391
11776
  }
11392
11777
 
11393
11778
  // src/server/projectFiles.ts
11394
11779
  init_cjs_shims();
11395
- var fs19 = __toESM(require("fs"), 1);
11396
- var path21 = __toESM(require("path"), 1);
11780
+ var fs20 = __toESM(require("fs"), 1);
11781
+ var path23 = __toESM(require("path"), 1);
11397
11782
  var MAX_FILE_BYTES = 2 * 1024 * 1024;
11398
11783
  var MAX_DOWNLOAD_BYTES = 16 * 1024 * 1024;
11399
11784
  var MAX_ENTRIES2 = 2e3;
11400
11785
  function toPosix(p) {
11401
- return p.split(path21.sep).join("/");
11786
+ return p.split(path23.sep).join("/");
11402
11787
  }
11403
11788
  function projectRoot(workspaceRoot) {
11404
11789
  const r = (workspaceRoot ?? "").trim();
11405
11790
  if (!r) {
11406
11791
  throw new Error("\u9879\u76EE\u672A\u8BBE\u7F6E\u5DE5\u4F5C\u533A\u76EE\u5F55\uFF08workspaceRoot\uFF09\uFF0C\u65E0\u6CD5\u6D4F\u89C8\u6587\u4EF6");
11407
11792
  }
11408
- const abs = path21.resolve(r);
11409
- if (!fs19.existsSync(abs)) {
11793
+ const abs = path23.resolve(r);
11794
+ if (!fs20.existsSync(abs)) {
11410
11795
  throw new Error(`\u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728: ${abs}`);
11411
11796
  }
11412
11797
  try {
11413
- return fs19.realpathSync(abs);
11798
+ return fs20.realpathSync(abs);
11414
11799
  } catch {
11415
11800
  return abs;
11416
11801
  }
@@ -11428,37 +11813,37 @@ function listProjectFiles(workspaceRoot, relPath) {
11428
11813
  const dir = resolveWithin(base, relPath ?? "");
11429
11814
  let st;
11430
11815
  try {
11431
- st = fs19.statSync(dir);
11816
+ st = fs20.statSync(dir);
11432
11817
  } catch {
11433
- throw new Error(`\u76EE\u5F55\u4E0D\u5B58\u5728: ${toPosix(path21.relative(base, dir)) || "/"}`);
11818
+ throw new Error(`\u76EE\u5F55\u4E0D\u5B58\u5728: ${toPosix(path23.relative(base, dir)) || "/"}`);
11434
11819
  }
11435
11820
  if (!st.isDirectory()) throw new Error("\u76EE\u6807\u4E0D\u662F\u76EE\u5F55");
11436
11821
  let dirents;
11437
11822
  try {
11438
- dirents = fs19.readdirSync(dir, { withFileTypes: true });
11823
+ dirents = fs20.readdirSync(dir, { withFileTypes: true });
11439
11824
  } catch {
11440
11825
  throw new Error("\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF08\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09");
11441
11826
  }
11442
11827
  const entries = dirents.map((d) => {
11443
- const full = path21.join(dir, d.name);
11828
+ const full = path23.join(dir, d.name);
11444
11829
  let isDir = d.isDirectory();
11445
11830
  if (d.isSymbolicLink()) {
11446
11831
  try {
11447
- isDir = fs19.statSync(full).isDirectory();
11832
+ isDir = fs20.statSync(full).isDirectory();
11448
11833
  } catch {
11449
11834
  }
11450
11835
  }
11451
11836
  let size = 0;
11452
11837
  let mtime = "";
11453
11838
  try {
11454
- const s = fs19.statSync(full);
11839
+ const s = fs20.statSync(full);
11455
11840
  size = s.size;
11456
11841
  mtime = s.mtime.toISOString();
11457
11842
  } catch {
11458
11843
  }
11459
11844
  return {
11460
11845
  name: d.name,
11461
- path: toPosix(path21.relative(base, full)),
11846
+ path: toPosix(path23.relative(base, full)),
11462
11847
  type: isDir ? "dir" : "file",
11463
11848
  size,
11464
11849
  mtime
@@ -11467,12 +11852,12 @@ function listProjectFiles(workspaceRoot, relPath) {
11467
11852
  entries.sort(
11468
11853
  (a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1
11469
11854
  );
11470
- const relDir = toPosix(path21.relative(base, dir));
11471
- const parentAbs = path21.dirname(dir);
11855
+ const relDir = toPosix(path23.relative(base, dir));
11856
+ const parentAbs = path23.dirname(dir);
11472
11857
  return {
11473
11858
  root: base,
11474
11859
  path: relDir,
11475
- parent: isWithinRoot(base, parentAbs) ? toPosix(path21.relative(base, parentAbs)) : null,
11860
+ parent: isWithinRoot(base, parentAbs) ? toPosix(path23.relative(base, parentAbs)) : null,
11476
11861
  entries: entries.slice(0, MAX_ENTRIES2)
11477
11862
  };
11478
11863
  }
@@ -11481,17 +11866,17 @@ function readProjectFile(workspaceRoot, relPath) {
11481
11866
  const file = resolveWithin(base, relPath ?? "");
11482
11867
  let st;
11483
11868
  try {
11484
- st = fs19.statSync(file);
11869
+ st = fs20.statSync(file);
11485
11870
  } catch {
11486
11871
  throw new Error("\u6587\u4EF6\u4E0D\u5B58\u5728");
11487
11872
  }
11488
11873
  if (!st.isFile()) throw new Error("\u76EE\u6807\u4E0D\u662F\u6587\u4EF6");
11489
- const buf = fs19.readFileSync(file);
11874
+ const buf = fs20.readFileSync(file);
11490
11875
  const binary = isBinary(buf);
11491
11876
  const truncated = buf.length > MAX_FILE_BYTES;
11492
11877
  const slice = truncated ? buf.subarray(0, MAX_FILE_BYTES) : buf;
11493
11878
  return {
11494
- path: toPosix(path21.relative(base, file)),
11879
+ path: toPosix(path23.relative(base, file)),
11495
11880
  size: st.size,
11496
11881
  binary,
11497
11882
  truncated,
@@ -11503,7 +11888,7 @@ function downloadProjectFile(workspaceRoot, relPath) {
11503
11888
  const file = resolveWithin(base, relPath ?? "");
11504
11889
  let st;
11505
11890
  try {
11506
- st = fs19.statSync(file);
11891
+ st = fs20.statSync(file);
11507
11892
  } catch {
11508
11893
  throw new Error("\u6587\u4EF6\u4E0D\u5B58\u5728");
11509
11894
  }
@@ -11511,10 +11896,10 @@ function downloadProjectFile(workspaceRoot, relPath) {
11511
11896
  if (st.size > MAX_DOWNLOAD_BYTES) {
11512
11897
  throw new Error(`\u6587\u4EF6\u8D85\u8FC7\u4E0B\u8F7D\u4E0A\u9650\uFF08${MAX_DOWNLOAD_BYTES / (1024 * 1024)}MB\uFF09`);
11513
11898
  }
11514
- const buf = fs19.readFileSync(file);
11899
+ const buf = fs20.readFileSync(file);
11515
11900
  return {
11516
- path: toPosix(path21.relative(base, file)),
11517
- name: path21.basename(file),
11901
+ path: toPosix(path23.relative(base, file)),
11902
+ name: path23.basename(file),
11518
11903
  size: buf.length,
11519
11904
  base64: buf.toString("base64")
11520
11905
  };
@@ -11536,12 +11921,12 @@ function writeProjectFile(workspaceRoot, relPath, content) {
11536
11921
  throw new Error("\u5185\u5BB9\u8D85\u8FC7 2MB \u4E0A\u9650\uFF0C\u65E0\u6CD5\u4FDD\u5B58");
11537
11922
  }
11538
11923
  try {
11539
- fs19.mkdirSync(path21.dirname(file), { recursive: true });
11540
- fs19.writeFileSync(file, buf);
11924
+ fs20.mkdirSync(path23.dirname(file), { recursive: true });
11925
+ fs20.writeFileSync(file, buf);
11541
11926
  } catch (e) {
11542
11927
  throw new Error(`\u5199\u5165\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
11543
11928
  }
11544
- return { path: toPosix(path21.relative(base, file)), size: buf.length };
11929
+ return { path: toPosix(path23.relative(base, file)), size: buf.length };
11545
11930
  }
11546
11931
  function createProjectDir(workspaceRoot, relPath) {
11547
11932
  const base = projectRoot(workspaceRoot);
@@ -11549,26 +11934,26 @@ function createProjectDir(workspaceRoot, relPath) {
11549
11934
  if (!rel) throw new Error("\u76EE\u5F55\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A");
11550
11935
  const dir = resolveWithin(base, rel);
11551
11936
  try {
11552
- fs19.mkdirSync(dir, { recursive: true });
11937
+ fs20.mkdirSync(dir, { recursive: true });
11553
11938
  } catch (e) {
11554
11939
  throw new Error(`\u521B\u5EFA\u76EE\u5F55\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
11555
11940
  }
11556
- return { path: toPosix(path21.relative(base, dir)) };
11941
+ return { path: toPosix(path23.relative(base, dir)) };
11557
11942
  }
11558
11943
  function createProjectFile(workspaceRoot, relPath, content = "") {
11559
11944
  const base = projectRoot(workspaceRoot);
11560
11945
  const rel = (relPath ?? "").trim();
11561
11946
  if (!rel) throw new Error("\u6587\u4EF6\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A");
11562
11947
  const file = resolveWithin(base, rel);
11563
- if (fs19.existsSync(file)) throw new Error("\u6587\u4EF6\u5DF2\u5B58\u5728");
11948
+ if (fs20.existsSync(file)) throw new Error("\u6587\u4EF6\u5DF2\u5B58\u5728");
11564
11949
  const buf = Buffer.from(String(content ?? ""), "utf-8");
11565
11950
  try {
11566
- fs19.mkdirSync(path21.dirname(file), { recursive: true });
11567
- fs19.writeFileSync(file, buf);
11951
+ fs20.mkdirSync(path23.dirname(file), { recursive: true });
11952
+ fs20.writeFileSync(file, buf);
11568
11953
  } catch (e) {
11569
11954
  throw new Error(`\u521B\u5EFA\u6587\u4EF6\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
11570
11955
  }
11571
- return { path: toPosix(path21.relative(base, file)), size: buf.length };
11956
+ return { path: toPosix(path23.relative(base, file)), size: buf.length };
11572
11957
  }
11573
11958
  function searchProjectFiles(workspaceRoot, query) {
11574
11959
  const base = projectRoot(workspaceRoot);
@@ -11591,7 +11976,7 @@ function searchProjectFiles(workspaceRoot, query) {
11591
11976
  if (out.length >= LIMIT) return;
11592
11977
  let dirents;
11593
11978
  try {
11594
- dirents = fs19.readdirSync(dir, { withFileTypes: true });
11979
+ dirents = fs20.readdirSync(dir, { withFileTypes: true });
11595
11980
  } catch {
11596
11981
  return;
11597
11982
  }
@@ -11602,7 +11987,7 @@ function searchProjectFiles(workspaceRoot, query) {
11602
11987
  let isDir = d.isDirectory();
11603
11988
  if (d.isSymbolicLink()) {
11604
11989
  try {
11605
- isDir = fs19.statSync(path21.join(dir, d.name)).isDirectory();
11990
+ isDir = fs20.statSync(path23.join(dir, d.name)).isDirectory();
11606
11991
  } catch {
11607
11992
  isDir = false;
11608
11993
  }
@@ -11610,7 +11995,7 @@ function searchProjectFiles(workspaceRoot, query) {
11610
11995
  if (d.name.toLowerCase().includes(q)) {
11611
11996
  out.push({ path: rel, type: isDir ? "dir" : "file" });
11612
11997
  }
11613
- if (isDir) walk(path21.join(dir, d.name), rel);
11998
+ if (isDir) walk(path23.join(dir, d.name), rel);
11614
11999
  }
11615
12000
  };
11616
12001
  walk(base, "");
@@ -11620,7 +12005,7 @@ function searchProjectFiles(workspaceRoot, query) {
11620
12005
  // src/server/projectOps.ts
11621
12006
  init_cjs_shims();
11622
12007
  var os5 = __toESM(require("os"), 1);
11623
- var path22 = __toESM(require("path"), 1);
12008
+ var path24 = __toESM(require("path"), 1);
11624
12009
  var MAX_STDOUT2 = 2e4;
11625
12010
  var MAX_STDERR2 = 1e4;
11626
12011
  var MAX_BUFFER3 = 1024 * 1024 * 5;
@@ -11634,7 +12019,7 @@ function resolveInvocation2(spec, prepared) {
11634
12019
  }
11635
12020
  function workRoot(workspaceRoot) {
11636
12021
  const r = (workspaceRoot ?? "").trim();
11637
- return r ? path22.resolve(r) : os5.homedir();
12022
+ return r ? path24.resolve(r) : os5.homedir();
11638
12023
  }
11639
12024
  async function runProjectCommand(workspaceRoot, command, timeoutSeconds) {
11640
12025
  const cmd = String(command ?? "");
@@ -11754,6 +12139,92 @@ async function projectGitPush(workspaceRoot, repoPath) {
11754
12139
  });
11755
12140
  }
11756
12141
 
12142
+ // src/server/gitCommitMessage.ts
12143
+ init_cjs_shims();
12144
+ var crypto5 = __toESM(require("crypto"), 1);
12145
+ var MAX_DIFF_CHARS = 12e3;
12146
+ var MAX_MESSAGE_CHARS = 200;
12147
+ var GEN_COMMIT_MESSAGE_INSTRUCTIONS = `\u4F60\u662F Git \u63D0\u4EA4\u8BF4\u660E\u751F\u6210\u52A9\u624B\u3002\u6839\u636E\u7ED9\u5B9A\u7684\u4EE3\u7801\u5DEE\u5F02\uFF0C\u751F\u6210\u4E00\u6761\u51C6\u786E\u3001\u7B80\u6D01\u7684\u63D0\u4EA4\u8BF4\u660E\u3002
12148
+
12149
+ \u8981\u6C42\uFF1A
12150
+ 1. \u4F7F\u7528 Conventional Commits \u98CE\u683C\uFF1Atype(scope): \u63CF\u8FF0\uFF08type \u5982 feat / fix / refactor / docs / test / chore / perf / build / ci\uFF09\uFF1B
12151
+ 2. scope \u53D6\u53D8\u66F4\u6700\u96C6\u4E2D\u7684\u6A21\u5757\u6216\u76EE\u5F55\uFF0C\u65E0\u6CD5\u5224\u65AD\u65F6\u7701\u7565\uFF1B
12152
+ 3. \u63CF\u8FF0\u4F7F\u7528\u4E2D\u6587\uFF0C\u6982\u62EC\u672C\u6B21\u53D8\u66F4\u7684\u76EE\u7684\u6216\u6548\u679C\uFF0C\u4E0D\u8981\u9010\u6587\u4EF6\u7F57\u5217\uFF1B
12153
+ 4. \u53EA\u8F93\u51FA\u63D0\u4EA4\u8BF4\u660E\u672C\u8EAB\uFF0C\u4E0D\u8981\u5F15\u53F7\u3001\u4EE3\u7801\u5757\u3001\u6362\u884C\u6216\u4EFB\u4F55\u89E3\u91CA\uFF1B
12154
+ 5. \u4E0D\u8D85\u8FC7 72 \u4E2A\u5B57\u7B26\u3002`;
12155
+ function genReqId2() {
12156
+ return crypto5.randomBytes(6).toString("base64url");
12157
+ }
12158
+ function cleanCommitMessage(raw) {
12159
+ let text = String(raw ?? "").trim();
12160
+ if (!text) return "";
12161
+ text = text.replace(/^```[^\n]*\n?/, "").replace(/\n?```$/, "").trim();
12162
+ const first = text.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0) ?? "";
12163
+ return first.replace(/^(commit message|提交说明|提交信息|message)\s*[::]\s*/i, "").replace(/^["'“”`]+|["'“”`]+$/g, "").replace(/\s+/g, " ").trim().slice(0, MAX_MESSAGE_CHARS);
12164
+ }
12165
+ async function collectDiff(workspaceRoot, repo) {
12166
+ const staged = await projectGitDiff(workspaceRoot, void 0, true, repo);
12167
+ if (!staged.error && String(staged.diff ?? "").trim()) {
12168
+ return { text: String(staged.diff), source: "staged" };
12169
+ }
12170
+ const worktree = await projectGitDiff(workspaceRoot, void 0, false, repo);
12171
+ if (!worktree.error && String(worktree.diff ?? "").trim()) {
12172
+ return { text: String(worktree.diff), source: "worktree" };
12173
+ }
12174
+ const status = await projectGitStatus(workspaceRoot, void 0, repo);
12175
+ if (status.error) throw new ApiError(400, "git_error", String(status.error));
12176
+ const entries = status.entries ?? [];
12177
+ if (entries.length === 0) throw new ApiError(400, "no_changes", "\u6CA1\u6709\u53EF\u63D0\u4EA4\u7684\u53D8\u66F4");
12178
+ return {
12179
+ text: entries.map((e) => `${e.status ?? ""} ${e.file ?? ""}`.trim()).join("\n"),
12180
+ source: "worktree"
12181
+ };
12182
+ }
12183
+ async function generateGitCommitMessage(opts) {
12184
+ const { store, tenant } = opts;
12185
+ const { text, source } = await collectDiff(opts.workspaceRoot, opts.repo);
12186
+ const truncated = text.length > MAX_DIFF_CHARS;
12187
+ const clipped = text.slice(0, MAX_DIFF_CHARS);
12188
+ const eff = resolveSessionProvider(store, tenant);
12189
+ const apiKey = eff.apiKey || process.env.OPENAI_API_KEY || "";
12190
+ const model = (eff.model || tenant.model || process.env.OPENAI_MODEL || process.env.MODEL || "").trim();
12191
+ if (!apiKey) {
12192
+ throw new ApiError(
12193
+ 400,
12194
+ "llm_not_configured",
12195
+ "\u5F53\u524D\u8282\u70B9\u672A\u914D\u7F6E\u53EF\u7528\u7684 LLM\uFF0C\u8BF7\u5148\u5728\u8282\u70B9\u914D\u7F6E\u4E2D\u6DFB\u52A0\u6A21\u578B\u4E0E API Key"
12196
+ );
12197
+ }
12198
+ if (!model) {
12199
+ throw new ApiError(
12200
+ 400,
12201
+ "llm_not_configured",
12202
+ "\u5F53\u524D\u8282\u70B9\u672A\u914D\u7F6E\u6A21\u578B\uFF0C\u8BF7\u5148\u5728\u8282\u70B9\u914D\u7F6E\u4E2D\u9009\u62E9\u6A21\u578B"
12203
+ );
12204
+ }
12205
+ const provider = new OpenAIProvider({ apiKey, baseURL: eff.baseURL });
12206
+ const raw = await provider.chat(
12207
+ model,
12208
+ [
12209
+ { role: "system", content: GEN_COMMIT_MESSAGE_INSTRUCTIONS },
12210
+ {
12211
+ role: "user",
12212
+ content: `\u4EE5\u4E0B\u662F\u672C\u6B21\u5F85\u63D0\u4EA4\u7684\u53D8\u66F4${source === "staged" ? "\uFF08\u5DF2\u6682\u5B58\uFF09" : "\uFF08\u5DE5\u4F5C\u533A\uFF0C\u5C1A\u672A\u6682\u5B58\uFF09"}${truncated ? "\uFF0C\u5185\u5BB9\u8FC7\u957F\u5DF2\u622A\u65AD" : ""}\uFF1A
12213
+
12214
+ ${clipped}`
12215
+ }
12216
+ ],
12217
+ {},
12218
+ { reqId: `llm_commit_${Date.now()}_${genReqId2()}`, logTime: (/* @__PURE__ */ new Date()).toISOString() },
12219
+ // 不设 maxTokens:推理模型的思考会占用输出配额,过小会导致 content 为空;
12220
+ // 提示词已约束只输出一行短说明。
12221
+ {}
12222
+ );
12223
+ const message = cleanCommitMessage(raw ?? "");
12224
+ if (!message) throw new ApiError(502, "llm_empty", "AI \u672A\u8FD4\u56DE\u6709\u6548\u7684\u63D0\u4EA4\u8BF4\u660E\uFF0C\u8BF7\u91CD\u8BD5");
12225
+ return message;
12226
+ }
12227
+
11757
12228
  // src/server/toolsCatalog.ts
11758
12229
  init_cjs_shims();
11759
12230
  function buildToolCatalog(workspace) {
@@ -11859,11 +12330,11 @@ function projectStats(manager, tenantId) {
11859
12330
 
11860
12331
  // src/server/nodeHost.ts
11861
12332
  init_cjs_shims();
11862
- var fs20 = __toESM(require("fs"), 1);
12333
+ var fs21 = __toESM(require("fs"), 1);
11863
12334
  var os6 = __toESM(require("os"), 1);
11864
12335
  function diskInfo(pathStr) {
11865
12336
  try {
11866
- const st = fs20.statfsSync(pathStr);
12337
+ const st = fs21.statfsSync(pathStr);
11867
12338
  return {
11868
12339
  path: pathStr,
11869
12340
  total: Number(st.blocks) * Number(st.bsize),
@@ -11935,7 +12406,7 @@ function buildNodeHost(store, manager, diskPath) {
11935
12406
  arch: process.arch,
11936
12407
  release: os6.release(),
11937
12408
  uptimeSec: Math.round(os6.uptime()),
11938
- version: "0.2.4",
12409
+ version: "0.2.6",
11939
12410
  mode: process.env.NODE_MODE ?? "standalone",
11940
12411
  cpu: {
11941
12412
  count: cpus2.length,
@@ -12510,7 +12981,7 @@ function createApp(store, manager, opts = {}) {
12510
12981
  applyProvidersPatch(tenant, raw);
12511
12982
  if (typeof raw.workspaceRoot === "string") {
12512
12983
  const ws = raw.workspaceRoot.trim();
12513
- if (ws) tenant.workspaceRoot = path23.resolve(ws);
12984
+ if (ws) tenant.workspaceRoot = path25.resolve(ws);
12514
12985
  else delete tenant.workspaceRoot;
12515
12986
  }
12516
12987
  if (typeof raw.name === "string" && raw.name.trim()) {
@@ -12919,6 +13390,25 @@ function createApp(store, manager, opts = {}) {
12919
13390
  }
12920
13391
  }
12921
13392
  },
13393
+ {
13394
+ method: "POST",
13395
+ pattern: ["git", "commit-message"],
13396
+ fn: async (_req, res, { tenant, body }) => {
13397
+ const raw = body ?? {};
13398
+ try {
13399
+ const message = await generateGitCommitMessage({
13400
+ store,
13401
+ tenant,
13402
+ workspaceRoot: tenant.workspaceRoot ?? null,
13403
+ repo: String(raw.repo ?? "") || void 0
13404
+ });
13405
+ ok(res, { message });
13406
+ } catch (e) {
13407
+ if (e instanceof ApiError) throw e;
13408
+ throw new ApiError(400, "bad_request", e instanceof Error ? e.message : String(e));
13409
+ }
13410
+ }
13411
+ },
12922
13412
  {
12923
13413
  method: "POST",
12924
13414
  pattern: ["git", "checkout"],
@@ -13442,10 +13932,10 @@ function createApp(store, manager, opts = {}) {
13442
13932
  function handle(req2, res) {
13443
13933
  const startedAt = Date.now();
13444
13934
  const url = new URL(req2.url ?? "/", "http://localhost");
13445
- const path33 = url.pathname;
13935
+ const path35 = url.pathname;
13446
13936
  const method = req2.method ?? "GET";
13447
13937
  console.log(
13448
- `[api] ${(/* @__PURE__ */ new Date()).toISOString()} \u2192 ${method} ${path33}${url.search}`
13938
+ `[api] ${(/* @__PURE__ */ new Date()).toISOString()} \u2192 ${method} ${path35}${url.search}`
13449
13939
  );
13450
13940
  let tenantId = null;
13451
13941
  let logged = false;
@@ -13453,7 +13943,7 @@ function createApp(store, manager, opts = {}) {
13453
13943
  if (logged) return;
13454
13944
  logged = true;
13455
13945
  console.log(
13456
- `[api] ${(/* @__PURE__ */ new Date()).toISOString()} \u2190 ${method} ${path33} ${extra} (${Date.now() - startedAt}ms) tenant=${tenantId ?? "-"}`
13946
+ `[api] ${(/* @__PURE__ */ new Date()).toISOString()} \u2190 ${method} ${path35} ${extra} (${Date.now() - startedAt}ms) tenant=${tenantId ?? "-"}`
13457
13947
  );
13458
13948
  };
13459
13949
  res.on("finish", () => logDone(`HTTP ${res.statusCode}`));
@@ -13464,16 +13954,16 @@ function createApp(store, manager, opts = {}) {
13464
13954
  res.end();
13465
13955
  return;
13466
13956
  }
13467
- if (req2.method === "GET" && path33 === "/healthz") {
13957
+ if (req2.method === "GET" && path35 === "/healthz") {
13468
13958
  json(res, 200, {
13469
13959
  status: "ok",
13470
13960
  // 版本让运维/探针能确认线上跑的是哪个构建(更新与回滚据此判断)
13471
- version: "0.2.4",
13961
+ version: "0.2.6",
13472
13962
  features: { mcp: hasServerMcpConfig() }
13473
13963
  });
13474
13964
  return;
13475
13965
  }
13476
- if (hubProxyEnabled(opts.host) && isHubProxyPath(path33)) {
13966
+ if (hubProxyEnabled(opts.host) && isHubProxyPath(path35)) {
13477
13967
  void handleHubProxy(req2, res).catch(() => {
13478
13968
  if (!res.headersSent) {
13479
13969
  res.writeHead(502, { "Content-Type": "application/json; charset=utf-8" });
@@ -13484,11 +13974,11 @@ function createApp(store, manager, opts = {}) {
13484
13974
  });
13485
13975
  return;
13486
13976
  }
13487
- if (!path33.startsWith(API_PREFIX)) {
13977
+ if (!path35.startsWith(API_PREFIX)) {
13488
13978
  json(res, 404, { error: { code: "not_found", message: "\u672A\u77E5\u8DEF\u5F84" } });
13489
13979
  return;
13490
13980
  }
13491
- const segs = path33.slice(API_PREFIX.length).split("/").filter(Boolean);
13981
+ const segs = path35.slice(API_PREFIX.length).split("/").filter(Boolean);
13492
13982
  const apiKey = extractApiKey(req2);
13493
13983
  if (!apiKey) {
13494
13984
  json(res, 401, {
@@ -13553,7 +14043,7 @@ function createApp(store, manager, opts = {}) {
13553
14043
  ).catch((err) => {
13554
14044
  const msg = err instanceof Error ? err.message : String(err);
13555
14045
  console.log(
13556
- `[api] ${(/* @__PURE__ */ new Date()).toISOString()} ! ${method} ${path33} ERROR: ${msg}`
14046
+ `[api] ${(/* @__PURE__ */ new Date()).toISOString()} ! ${method} ${path35} ERROR: ${msg}`
13557
14047
  );
13558
14048
  error(res, err);
13559
14049
  });
@@ -14068,6 +14558,17 @@ async function dispatchFleetCommand(deps, env3) {
14068
14558
  )
14069
14559
  );
14070
14560
  }
14561
+ case "git_commit_message": {
14562
+ const t = project();
14563
+ return ok2({
14564
+ message: await generateGitCommitMessage({
14565
+ store,
14566
+ tenant: t,
14567
+ workspaceRoot: t.workspaceRoot ?? null,
14568
+ repo: gitRepo(payload.repo)
14569
+ })
14570
+ });
14571
+ }
14071
14572
  case "git_checkout": {
14072
14573
  const t = project();
14073
14574
  return ok2(
@@ -14773,6 +15274,18 @@ var READONLY_CMD_TYPES = /* @__PURE__ */ new Set([
14773
15274
  "git_show"
14774
15275
  ]);
14775
15276
  var CMD_CACHE_TTL_MS = 1500;
15277
+ var STATE_CHANGING_EVENTS = /* @__PURE__ */ new Set([
15278
+ "turn_start",
15279
+ "turn_end",
15280
+ "permission_required",
15281
+ "ask_user_question",
15282
+ "plan_proposed",
15283
+ "directory_requested",
15284
+ "error",
15285
+ "interrupted",
15286
+ "queue_steering",
15287
+ "session_named"
15288
+ ]);
14776
15289
  var READONLY_CONCURRENCY = 4;
14777
15290
  var BATCH_MAX_SUBS = 64;
14778
15291
  var BATCH_SUB_TIMEOUT_MS = 7e3;
@@ -15008,6 +15521,20 @@ var FleetNodeBridge = class {
15008
15521
  this.writeEpoch += 1;
15009
15522
  this.cmdResultCache.clear();
15010
15523
  }
15524
+ /**
15525
+ * 会话快照缓存失效(事件/通知驱动的状态变化)。
15526
+ *
15527
+ * 与写操作不同,这些变化只影响会话相关查询;文件树 / Git 等重查询的缓存
15528
+ * 保持有效,避免多会话并行运行时把整个只读缓存反复清空。
15529
+ */
15530
+ _invalidateSessionCache() {
15531
+ this.writeEpoch += 1;
15532
+ for (const key of [...this.cmdResultCache.keys()]) {
15533
+ if (/^(get_state|get_events|list_sessions|get_session_states)\|/.test(key)) {
15534
+ this.cmdResultCache.delete(key);
15535
+ }
15536
+ }
15537
+ }
15011
15538
  close() {
15012
15539
  this.closing = true;
15013
15540
  if (this.usageTimer) {
@@ -15036,6 +15563,7 @@ var FleetNodeBridge = class {
15036
15563
  }
15037
15564
  /** 通知广播(SessionManager 注入的 notify 回调) */
15038
15565
  notify(tenantId, ev) {
15566
+ this._invalidateSessionCache();
15039
15567
  const payload = ev.kind === "approval_request" ? {
15040
15568
  ...ev,
15041
15569
  projectId: tenantId,
@@ -15048,6 +15576,7 @@ var FleetNodeBridge = class {
15048
15576
  }
15049
15577
  /** 事件桥接(SessionManager 注入的 onEvent 回调);发布按窗口批量合并 */
15050
15578
  onEvent(tenantId, sessionId, entry) {
15579
+ if (STATE_CHANGING_EVENTS.has(entry.type)) this._invalidateSessionCache();
15051
15580
  if (entry.type === "turn_end") this.stats.turns += 1;
15052
15581
  else if (entry.type === "tool_finished") this.stats.toolCalls += 1;
15053
15582
  else if (entry.type === "error") this.stats.errors += 1;
@@ -15132,7 +15661,7 @@ var FleetNodeBridge = class {
15132
15661
  label: this.opts.deps.store.loadNodeLabel() ?? void 0,
15133
15662
  platform: process.platform,
15134
15663
  mode: process.env.NODE_MODE ?? "standalone",
15135
- version: "0.2.4",
15664
+ version: "0.2.6",
15136
15665
  ip: primaryIp()
15137
15666
  };
15138
15667
  }
@@ -15166,7 +15695,10 @@ var FleetNodeBridge = class {
15166
15695
  sessionId: s.sessionId,
15167
15696
  name: s.name ?? null,
15168
15697
  status: s.status,
15169
- lastUserMessage: lastUserMessage(s)
15698
+ lastUserMessage: lastUserMessage(s),
15699
+ // 控制面的聚合邮件靠它判断「这台机器最近有没有在干活」;
15700
+ // 状态跃迁可能被 info 节流跳过,完成时间不会。
15701
+ lastCompletedAt: s.lastCompletedAt ?? null
15170
15702
  });
15171
15703
  }
15172
15704
  } catch {
@@ -15184,7 +15716,7 @@ var FleetNodeBridge = class {
15184
15716
  label: this.opts.deps.store.loadNodeLabel() ?? void 0,
15185
15717
  platform: process.platform,
15186
15718
  mode: process.env.NODE_MODE ?? "standalone",
15187
- version: "0.2.4",
15719
+ version: "0.2.6",
15188
15720
  ip: primaryIp(),
15189
15721
  projects,
15190
15722
  totals,
@@ -15953,8 +16485,8 @@ init_cjs_shims();
15953
16485
 
15954
16486
  // src/server/qq/bindings.ts
15955
16487
  init_cjs_shims();
15956
- var fs21 = __toESM(require("fs"), 1);
15957
- var path24 = __toESM(require("path"), 1);
16488
+ var fs22 = __toESM(require("fs"), 1);
16489
+ var path26 = __toESM(require("path"), 1);
15958
16490
  function conversationKey(target) {
15959
16491
  switch (target.kind) {
15960
16492
  case "c2c":
@@ -15993,12 +16525,12 @@ var QqBindingsStore = class _QqBindingsStore {
15993
16525
  cache = null;
15994
16526
  /** 基于 DATA_DIR 构造 */
15995
16527
  static in(dataDir) {
15996
- return new _QqBindingsStore(path24.join(dataDir, "qq", "bindings.json"));
16528
+ return new _QqBindingsStore(path26.join(dataDir, "qq", "bindings.json"));
15997
16529
  }
15998
16530
  load() {
15999
16531
  if (this.cache) return this.cache;
16000
16532
  try {
16001
- const raw = JSON.parse(fs21.readFileSync(this.file, "utf-8"));
16533
+ const raw = JSON.parse(fs22.readFileSync(this.file, "utf-8"));
16002
16534
  this.cache = {
16003
16535
  conversations: raw.conversations ?? {}
16004
16536
  };
@@ -16030,8 +16562,8 @@ var QqBindingsStore = class _QqBindingsStore {
16030
16562
  return null;
16031
16563
  }
16032
16564
  persist() {
16033
- fs21.mkdirSync(path24.dirname(this.file), { recursive: true });
16034
- fs21.writeFileSync(this.file, JSON.stringify(this.cache, null, 2), "utf-8");
16565
+ fs22.mkdirSync(path26.dirname(this.file), { recursive: true });
16566
+ fs22.writeFileSync(this.file, JSON.stringify(this.cache, null, 2), "utf-8");
16035
16567
  }
16036
16568
  };
16037
16569
 
@@ -16443,8 +16975,8 @@ ${lines.join("\n")}`, msgId);
16443
16975
 
16444
16976
  // src/server/qq/QqManager.ts
16445
16977
  init_cjs_shims();
16446
- var fs22 = __toESM(require("fs"), 1);
16447
- var path25 = __toESM(require("path"), 1);
16978
+ var fs23 = __toESM(require("fs"), 1);
16979
+ var path27 = __toESM(require("path"), 1);
16448
16980
  var QqManager = class {
16449
16981
  constructor(opts) {
16450
16982
  this.opts = opts;
@@ -16453,7 +16985,7 @@ var QqManager = class {
16453
16985
  adapter = null;
16454
16986
  config = null;
16455
16987
  get configFile() {
16456
- return path25.join(this.opts.dataDir, "qq", "config.json");
16988
+ return path27.join(this.opts.dataDir, "qq", "config.json");
16457
16989
  }
16458
16990
  /** 启动:读配置(env 优先,其次文件),有则建连接 */
16459
16991
  start() {
@@ -16491,7 +17023,7 @@ var QqManager = class {
16491
17023
  /** 清空配置:删除文件 + 断开 */
16492
17024
  clearConfig() {
16493
17025
  try {
16494
- fs22.rmSync(this.configFile, { force: true });
17026
+ fs23.rmSync(this.configFile, { force: true });
16495
17027
  } catch {
16496
17028
  }
16497
17029
  this.stop();
@@ -16533,7 +17065,7 @@ var QqManager = class {
16533
17065
  }
16534
17066
  _readConfigFile() {
16535
17067
  try {
16536
- const raw = JSON.parse(fs22.readFileSync(this.configFile, "utf-8"));
17068
+ const raw = JSON.parse(fs23.readFileSync(this.configFile, "utf-8"));
16537
17069
  if (raw.appId && raw.appSecret) {
16538
17070
  return {
16539
17071
  appId: raw.appId,
@@ -16546,18 +17078,18 @@ var QqManager = class {
16546
17078
  return null;
16547
17079
  }
16548
17080
  _persistConfig(cfg) {
16549
- fs22.mkdirSync(path25.dirname(this.configFile), { recursive: true });
16550
- fs22.writeFileSync(this.configFile, JSON.stringify(cfg, null, 2), "utf-8");
17081
+ fs23.mkdirSync(path27.dirname(this.configFile), { recursive: true });
17082
+ fs23.writeFileSync(this.configFile, JSON.stringify(cfg, null, 2), "utf-8");
16551
17083
  }
16552
17084
  };
16553
17085
 
16554
17086
  // src/server/qq/pairingCodes.ts
16555
17087
  init_cjs_shims();
16556
- var crypto5 = __toESM(require("crypto"), 1);
17088
+ var crypto6 = __toESM(require("crypto"), 1);
16557
17089
  var PAIRING_CODE_TTL_MS = 5 * 60 * 1e3;
16558
17090
  function randomCode(len = 8) {
16559
17091
  const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
16560
- const bytes = crypto5.randomBytes(len);
17092
+ const bytes = crypto6.randomBytes(len);
16561
17093
  let out = "";
16562
17094
  for (let i = 0; i < len; i++) {
16563
17095
  out += alphabet[bytes[i] % alphabet.length];
@@ -16599,13 +17131,13 @@ var baseUrlResolver = null;
16599
17131
  function resolveBase(baseUrl) {
16600
17132
  return baseUrlResolver ? baseUrlResolver(baseUrl) : baseUrl;
16601
17133
  }
16602
- async function req(baseUrl, token, path33, init = {}) {
17134
+ async function req(baseUrl, token, path35, init = {}) {
16603
17135
  const headers = {
16604
17136
  ...init.body ? { "Content-Type": "application/json" } : {},
16605
17137
  ...init.headers ?? {}
16606
17138
  };
16607
17139
  if (token) headers.Authorization = `Bearer ${token}`;
16608
- const res = await fetch(`${resolveBase(baseUrl).replace(/\/$/, "")}${path33}`, {
17140
+ const res = await fetch(`${resolveBase(baseUrl).replace(/\/$/, "")}${path35}`, {
16609
17141
  ...init,
16610
17142
  headers
16611
17143
  });
@@ -16647,17 +17179,17 @@ async function hubEnrollByAccount(baseUrl, input) {
16647
17179
 
16648
17180
  // src/update/controller.ts
16649
17181
  init_cjs_shims();
16650
- var fs26 = __toESM(require("fs"), 1);
16651
- var path30 = __toESM(require("path"), 1);
17182
+ var fs27 = __toESM(require("fs"), 1);
17183
+ var path32 = __toESM(require("path"), 1);
16652
17184
 
16653
17185
  // src/update/downloader.ts
16654
17186
  init_cjs_shims();
16655
17187
  var import_node_crypto5 = require("crypto");
16656
- var fs23 = __toESM(require("fs"), 1);
17188
+ var fs24 = __toESM(require("fs"), 1);
16657
17189
  var import_node_stream2 = require("stream");
16658
17190
  var import_promises2 = require("stream/promises");
16659
17191
  var import_node_child_process4 = require("child_process");
16660
- var path26 = __toESM(require("path"), 1);
17192
+ var path28 = __toESM(require("path"), 1);
16661
17193
 
16662
17194
  // src/update/manifest.ts
16663
17195
  init_cjs_shims();
@@ -16832,8 +17364,8 @@ async function fetchManifest(url, publicKey, opts = {}) {
16832
17364
  async function downloadBinary(bin, destPath, log = () => {
16833
17365
  }) {
16834
17366
  const partPath = `${destPath}.part`;
16835
- fs23.mkdirSync(path26.dirname(destPath), { recursive: true });
16836
- fs23.rmSync(partPath, { force: true });
17367
+ fs24.mkdirSync(path28.dirname(destPath), { recursive: true });
17368
+ fs24.rmSync(partPath, { force: true });
16837
17369
  let res;
16838
17370
  try {
16839
17371
  res = await fetch(bin.url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS2) });
@@ -16858,25 +17390,25 @@ async function downloadBinary(bin, destPath, log = () => {
16858
17390
  yield chunk;
16859
17391
  }
16860
17392
  },
16861
- fs23.createWriteStream(partPath, { mode: 493 })
17393
+ fs24.createWriteStream(partPath, { mode: 493 })
16862
17394
  );
16863
17395
  } catch (err) {
16864
- fs23.rmSync(partPath, { force: true });
17396
+ fs24.rmSync(partPath, { force: true });
16865
17397
  return { error: `\u4E0B\u8F7D\u4E2D\u65AD\uFF1A${err.message}` };
16866
17398
  }
16867
17399
  const actual = hash.digest("hex");
16868
17400
  if (actual !== bin.sha256) {
16869
- fs23.rmSync(partPath, { force: true });
17401
+ fs24.rmSync(partPath, { force: true });
16870
17402
  return { error: `SHA-256 \u4E0D\u5339\u914D\uFF08\u671F\u671B ${bin.sha256.slice(0, 12)}\u2026\uFF0C\u5B9E\u9645 ${actual.slice(0, 12)}\u2026\uFF09` };
16871
17403
  }
16872
17404
  if (expected && bytes !== expected) {
16873
- fs23.rmSync(partPath, { force: true });
17405
+ fs24.rmSync(partPath, { force: true });
16874
17406
  return { error: `\u4EA7\u7269\u5927\u5C0F\u4E0D\u5339\u914D\uFF08\u671F\u671B ${expected}\uFF0C\u5B9E\u9645 ${bytes}\uFF09` };
16875
17407
  }
16876
- fs23.rmSync(destPath, { force: true });
16877
- fs23.renameSync(partPath, destPath);
16878
- fs23.chmodSync(destPath, 493);
16879
- log(`\u5DF2\u4E0B\u8F7D ${path26.basename(destPath)}\uFF08${Math.round(bytes / 1024 / 1024)}MB\uFF0C\u6821\u9A8C\u901A\u8FC7\uFF09`);
17408
+ fs24.rmSync(destPath, { force: true });
17409
+ fs24.renameSync(partPath, destPath);
17410
+ fs24.chmodSync(destPath, 493);
17411
+ log(`\u5DF2\u4E0B\u8F7D ${path28.basename(destPath)}\uFF08${Math.round(bytes / 1024 / 1024)}MB\uFF0C\u6821\u9A8C\u901A\u8FC7\uFF09`);
16880
17412
  return { bytes };
16881
17413
  }
16882
17414
  function selfCheck(binPath, expectedVersion) {
@@ -16900,10 +17432,10 @@ function selfCheck(binPath, expectedVersion) {
16900
17432
 
16901
17433
  // src/update/platform.ts
16902
17434
  init_cjs_shims();
16903
- var path27 = __toESM(require("path"), 1);
17435
+ var path29 = __toESM(require("path"), 1);
16904
17436
  var import_node_module3 = require("module");
16905
17437
  function currentVersion() {
16906
- const injected = "0.2.4"?.trim();
17438
+ const injected = "0.2.6"?.trim();
16907
17439
  if (injected) return injected;
16908
17440
  try {
16909
17441
  const req2 = (0, import_node_module3.createRequire)(importMetaUrl);
@@ -16915,7 +17447,7 @@ function currentVersion() {
16915
17447
  }
16916
17448
  function isSeaRuntime() {
16917
17449
  if (process.env.ZQAGENT_SEA === "1") return true;
16918
- const base = path27.basename(process.execPath).toLowerCase().replace(/\.exe$/, "");
17450
+ const base = path29.basename(process.execPath).toLowerCase().replace(/\.exe$/, "");
16919
17451
  return base !== "node" && base !== "nodejs";
16920
17452
  }
16921
17453
  function serviceManager() {
@@ -16943,22 +17475,22 @@ function selfUpdateCapability(hasPublicKey) {
16943
17475
 
16944
17476
  // src/update/installer.ts
16945
17477
  init_cjs_shims();
16946
- var fs25 = __toESM(require("fs"), 1);
16947
- var path29 = __toESM(require("path"), 1);
17478
+ var fs26 = __toESM(require("fs"), 1);
17479
+ var path31 = __toESM(require("path"), 1);
16948
17480
 
16949
17481
  // src/update/state.ts
16950
17482
  init_cjs_shims();
16951
- var fs24 = __toESM(require("fs"), 1);
16952
- var path28 = __toESM(require("path"), 1);
17483
+ var fs25 = __toESM(require("fs"), 1);
17484
+ var path30 = __toESM(require("path"), 1);
16953
17485
  function updatesDir(dataDir) {
16954
- return path28.join(dataDir, "updates");
17486
+ return path30.join(dataDir, "updates");
16955
17487
  }
16956
17488
  function statePath(dataDir) {
16957
- return path28.join(updatesDir(dataDir), "state.json");
17489
+ return path30.join(updatesDir(dataDir), "state.json");
16958
17490
  }
16959
17491
  function loadState(dataDir) {
16960
17492
  try {
16961
- const raw = fs24.readFileSync(statePath(dataDir), "utf-8");
17493
+ const raw = fs25.readFileSync(statePath(dataDir), "utf-8");
16962
17494
  const parsed = JSON.parse(raw);
16963
17495
  if (typeof parsed === "object" && parsed !== null) return parsed;
16964
17496
  } catch {
@@ -16967,11 +17499,11 @@ function loadState(dataDir) {
16967
17499
  }
16968
17500
  function saveState(dataDir, state) {
16969
17501
  const dir = updatesDir(dataDir);
16970
- fs24.mkdirSync(dir, { recursive: true });
17502
+ fs25.mkdirSync(dir, { recursive: true });
16971
17503
  const file = statePath(dataDir);
16972
17504
  const tmp = `${file}.tmp`;
16973
- fs24.writeFileSync(tmp, JSON.stringify(state, null, 2), "utf-8");
16974
- fs24.renameSync(tmp, file);
17505
+ fs25.writeFileSync(tmp, JSON.stringify(state, null, 2), "utf-8");
17506
+ fs25.renameSync(tmp, file);
16975
17507
  }
16976
17508
  function patchState(dataDir, patch) {
16977
17509
  const next = { ...loadState(dataDir), ...patch };
@@ -16981,36 +17513,36 @@ function patchState(dataDir, patch) {
16981
17513
 
16982
17514
  // src/update/installer.ts
16983
17515
  function backupDir(dataDir) {
16984
- return path29.join(updatesDir(dataDir), "backup");
17516
+ return path31.join(updatesDir(dataDir), "backup");
16985
17517
  }
16986
17518
  function backupCurrent(dataDir, execPath, version) {
16987
17519
  const dir = backupDir(dataDir);
16988
- fs25.mkdirSync(dir, { recursive: true });
16989
- const target = path29.join(dir, `zqagent-${version}${process.platform === "win32" ? ".exe" : ""}`);
16990
- fs25.copyFileSync(execPath, target);
16991
- fs25.chmodSync(target, 493);
17520
+ fs26.mkdirSync(dir, { recursive: true });
17521
+ const target = path31.join(dir, `zqagent-${version}${process.platform === "win32" ? ".exe" : ""}`);
17522
+ fs26.copyFileSync(execPath, target);
17523
+ fs26.chmodSync(target, 493);
16992
17524
  return target;
16993
17525
  }
16994
17526
  function replaceExecutable(execPath, stagedPath) {
16995
17527
  if (process.platform === "win32") {
16996
17528
  const oldPath = `${execPath}.old`;
16997
- fs25.rmSync(oldPath, { force: true });
16998
- fs25.renameSync(execPath, oldPath);
16999
- fs25.copyFileSync(stagedPath, execPath);
17000
- fs25.chmodSync(execPath, 493);
17529
+ fs26.rmSync(oldPath, { force: true });
17530
+ fs26.renameSync(execPath, oldPath);
17531
+ fs26.copyFileSync(stagedPath, execPath);
17532
+ fs26.chmodSync(execPath, 493);
17001
17533
  return;
17002
17534
  }
17003
17535
  const tmp = `${execPath}.new`;
17004
- fs25.rmSync(tmp, { force: true });
17536
+ fs26.rmSync(tmp, { force: true });
17005
17537
  try {
17006
- fs25.copyFileSync(stagedPath, tmp);
17007
- fs25.chmodSync(tmp, 493);
17008
- fs25.renameSync(tmp, execPath);
17538
+ fs26.copyFileSync(stagedPath, tmp);
17539
+ fs26.chmodSync(tmp, 493);
17540
+ fs26.renameSync(tmp, execPath);
17009
17541
  } catch (err) {
17010
- fs25.rmSync(tmp, { force: true });
17542
+ fs26.rmSync(tmp, { force: true });
17011
17543
  throw err;
17012
17544
  }
17013
- fs25.rmSync(stagedPath, { force: true });
17545
+ fs26.rmSync(stagedPath, { force: true });
17014
17546
  }
17015
17547
  function installStaged(opts) {
17016
17548
  const log = opts.log ?? (() => {
@@ -17018,7 +17550,7 @@ function installStaged(opts) {
17018
17550
  const from = currentVersion();
17019
17551
  log(`\u6B63\u5728\u5B89\u88C5 v${opts.version}\uFF08\u5F53\u524D v${from}\uFF09`);
17020
17552
  const backupPath = backupCurrent(opts.dataDir, opts.execPath, from);
17021
- log(`\u5DF2\u5907\u4EFD\u5F53\u524D\u7248\u672C\u5230 ${path29.basename(backupPath)}`);
17553
+ log(`\u5DF2\u5907\u4EFD\u5F53\u524D\u7248\u672C\u5230 ${path31.basename(backupPath)}`);
17022
17554
  replaceExecutable(opts.execPath, opts.stagedPath);
17023
17555
  return patchState(opts.dataDir, {
17024
17556
  stagedVersion: void 0,
@@ -17037,18 +17569,18 @@ function rollback(dataDir, execPath, log = () => {
17037
17569
  }) {
17038
17570
  const state = readRollbackTarget(dataDir);
17039
17571
  if (!state?.backupPath || !state.backupVersion) return null;
17040
- if (!fs25.existsSync(state.backupPath)) return null;
17572
+ if (!fs26.existsSync(state.backupPath)) return null;
17041
17573
  log(`\u6B63\u5728\u56DE\u6EDA\u5230 v${state.backupVersion}`);
17042
17574
  if (process.platform === "win32") {
17043
- fs25.rmSync(`${execPath}.old`, { force: true });
17044
- fs25.renameSync(execPath, `${execPath}.old`);
17045
- fs25.copyFileSync(state.backupPath, execPath);
17046
- fs25.chmodSync(execPath, 493);
17575
+ fs26.rmSync(`${execPath}.old`, { force: true });
17576
+ fs26.renameSync(execPath, `${execPath}.old`);
17577
+ fs26.copyFileSync(state.backupPath, execPath);
17578
+ fs26.chmodSync(execPath, 493);
17047
17579
  } else {
17048
17580
  const tmp = `${execPath}.rollback`;
17049
- fs25.copyFileSync(state.backupPath, tmp);
17050
- fs25.chmodSync(tmp, 493);
17051
- fs25.renameSync(tmp, execPath);
17581
+ fs26.copyFileSync(state.backupPath, tmp);
17582
+ fs26.chmodSync(tmp, 493);
17583
+ fs26.renameSync(tmp, execPath);
17052
17584
  }
17053
17585
  patchState(dataDir, {
17054
17586
  pendingVersion: state.backupVersion,
@@ -17062,7 +17594,7 @@ function rollback(dataDir, execPath, log = () => {
17062
17594
  }
17063
17595
  function readRollbackTarget(dataDir) {
17064
17596
  try {
17065
- const raw = fs25.readFileSync(path29.join(updatesDir(dataDir), "state.json"), "utf-8");
17597
+ const raw = fs26.readFileSync(path31.join(updatesDir(dataDir), "state.json"), "utf-8");
17066
17598
  const s = JSON.parse(raw);
17067
17599
  if (s.backupPath && s.backupVersion) return s;
17068
17600
  } catch {
@@ -17145,7 +17677,7 @@ var UpdateController = class {
17145
17677
  reconcile() {
17146
17678
  const state = loadState(this.opts.dataDir);
17147
17679
  const current = this.currentVersion;
17148
- if (state.stagedPath && !fs26.existsSync(state.stagedPath)) {
17680
+ if (state.stagedPath && !fs27.existsSync(state.stagedPath)) {
17149
17681
  patchState(this.opts.dataDir, { stagedVersion: void 0, stagedPath: void 0 });
17150
17682
  }
17151
17683
  const patch = { runningVersion: current };
@@ -17160,7 +17692,7 @@ var UpdateController = class {
17160
17692
  }
17161
17693
  patchState(this.opts.dataDir, patch);
17162
17694
  if (process.platform === "win32") {
17163
- fs26.rmSync(`${process.execPath}.old`, { force: true });
17695
+ fs27.rmSync(`${process.execPath}.old`, { force: true });
17164
17696
  }
17165
17697
  }
17166
17698
  schedule(delayMs) {
@@ -17236,8 +17768,8 @@ var UpdateController = class {
17236
17768
  const bin = manifest.binaries[platformKey()];
17237
17769
  if (!bin) return `\u6E05\u5355\u6CA1\u6709\u5F53\u524D\u5E73\u53F0\uFF08${platformKey()}\uFF09\u7684\u4EA7\u7269`;
17238
17770
  let stagedPath = state.stagedPath;
17239
- if (!(state.stagedVersion === manifest.version && stagedPath && fs26.existsSync(stagedPath))) {
17240
- stagedPath = path30.join(
17771
+ if (!(state.stagedVersion === manifest.version && stagedPath && fs27.existsSync(stagedPath))) {
17772
+ stagedPath = path32.join(
17241
17773
  updatesDir(this.opts.dataDir),
17242
17774
  "staged",
17243
17775
  `zqagent-${manifest.version}${process.platform === "win32" ? ".exe" : ""}`
@@ -17249,7 +17781,7 @@ var UpdateController = class {
17249
17781
  }
17250
17782
  const selfTest = selfCheck(stagedPath, manifest.version);
17251
17783
  if (!selfTest.ok) {
17252
- fs26.rmSync(stagedPath, { force: true });
17784
+ fs27.rmSync(stagedPath, { force: true });
17253
17785
  this.recordFailure(manifest.version, selfTest.error ?? "\u81EA\u68C0\u5931\u8D25");
17254
17786
  return `\u5B89\u88C5\u5305\u81EA\u68C0\u5931\u8D25\uFF1A${selfTest.error}`;
17255
17787
  }
@@ -17390,7 +17922,7 @@ function pemFile(key) {
17390
17922
  const p = env2(key);
17391
17923
  if (!p) return void 0;
17392
17924
  try {
17393
- return fs27.readFileSync(p);
17925
+ return fs28.readFileSync(p);
17394
17926
  } catch (e) {
17395
17927
  console.warn(`[tls] \u8BFB\u53D6 ${key}=${p} \u5931\u8D25: ${e instanceof Error ? e.message : e}`);
17396
17928
  return void 0;
@@ -17412,7 +17944,7 @@ function seedDemoTenant(store) {
17412
17944
  apiKey: env2("OPENAI_API_KEY"),
17413
17945
  baseURL: env2("OPENAI_BASE_URL")
17414
17946
  },
17415
- workspaceRoot: workspace ? path31.resolve(workspace) : void 0,
17947
+ workspaceRoot: workspace ? path33.resolve(workspace) : void 0,
17416
17948
  allowedCommands: []
17417
17949
  };
17418
17950
  store.saveTenant(demo);
@@ -17466,9 +17998,9 @@ function syncTenantModels(store) {
17466
17998
  tenant.model = model;
17467
17999
  changed = true;
17468
18000
  }
17469
- if (workspace && tenant.workspaceRoot !== path31.resolve(workspace)) {
17470
- console.log(`[sync] \u79DF\u6237 ${id} workspaceRoot \u5DF2\u540C\u6B65: ${tenant.workspaceRoot ?? "(\u6C99\u7BB1)"} \u2192 ${path31.resolve(workspace)}`);
17471
- tenant.workspaceRoot = path31.resolve(workspace);
18001
+ if (workspace && tenant.workspaceRoot !== path33.resolve(workspace)) {
18002
+ console.log(`[sync] \u79DF\u6237 ${id} workspaceRoot \u5DF2\u540C\u6B65: ${tenant.workspaceRoot ?? "(\u6C99\u7BB1)"} \u2192 ${path33.resolve(workspace)}`);
18003
+ tenant.workspaceRoot = path33.resolve(workspace);
17472
18004
  changed = true;
17473
18005
  }
17474
18006
  if (apiKey && tenant.provider.apiKey !== apiKey) {
@@ -17500,7 +18032,7 @@ function exitWhenOrphaned() {
17500
18032
  timer.unref();
17501
18033
  }
17502
18034
  function startServer(opts = {}) {
17503
- const dataDir = path31.resolve(opts.dataDir ?? env2("DATA_DIR") ?? DEFAULT_DATA_DIR);
18035
+ const dataDir = path33.resolve(opts.dataDir ?? env2("DATA_DIR") ?? DEFAULT_DATA_DIR);
17504
18036
  const port = opts.port ?? Number(env2("PORT") ?? 8787);
17505
18037
  const host = opts.host ?? env2("HOST") ?? "127.0.0.1";
17506
18038
  exitWhenOrphaned();
@@ -17530,7 +18062,7 @@ function startServer(opts = {}) {
17530
18062
  }
17531
18063
  const adminKey = ensureAdminKey();
17532
18064
  const secretKey = env2("SECRET_KEY");
17533
- const secretBox = secretKey ? SecretBox.fromHex(secretKey) : SecretBox.fromFile(path31.join(dataDir, "secret.key"));
18065
+ const secretBox = secretKey ? SecretBox.fromHex(secretKey) : SecretBox.fromFile(path33.join(dataDir, "secret.key"));
17534
18066
  if (secretKey && !secretBox) {
17535
18067
  console.warn("[crypto] SECRET_KEY \u975E\u6CD5\uFF08\u9700 64 \u4F4D hex\uFF09\uFF0C\u901A\u77E5\u51ED\u636E\u5C06\u9000\u5316\u4E3A\u660E\u6587");
17536
18068
  }
@@ -17759,7 +18291,7 @@ function printReady(info, qq) {
17759
18291
  `zqagent server \u76D1\u542C http://${info.host}:${info.port}`,
17760
18292
  `\u6570\u636E\u76EE\u5F55: ${info.dataDir}`,
17761
18293
  `\u5BA1\u6279\u6302\u8D77\u8D85\u65F6: ${env2("APPROVAL_TIMEOUT_SECONDS") ?? 900}s\uFF08\u8D85\u65F6\u540E\u56DE\u6536\u5185\u5B58\u4EFB\u52A1\uFF0C\u53EF\u7ECF resume \u6062\u590D\uFF09`,
17762
- env2("ZQAGENT_LOG_ADMIN_KEY") === "0" ? `\u7BA1\u7406\u5458\u5BC6\u94A5: ${maskKey(info.adminKey)}\uFF08\u5DF2\u8131\u654F\uFF0C\u5B8C\u6574\u503C\u89C1 ${path31.join(info.dataDir, "admin.json")}\uFF09` : `\u7BA1\u7406\u5458\u5BC6\u94A5: ${info.adminKey}\uFF08\u63A7\u5236\u53F0\u591A\u9879\u76EE\u5165\u53E3\uFF1A/api/v1/projects\uFF1B\u9879\u76EE\u7EA7\u63A5\u53E3\u52A0 X-Project-Id \u5934\uFF09`
18294
+ env2("ZQAGENT_LOG_ADMIN_KEY") === "0" ? `\u7BA1\u7406\u5458\u5BC6\u94A5: ${maskKey(info.adminKey)}\uFF08\u5DF2\u8131\u654F\uFF0C\u5B8C\u6574\u503C\u89C1 ${path33.join(info.dataDir, "admin.json")}\uFF09` : `\u7BA1\u7406\u5458\u5BC6\u94A5: ${info.adminKey}\uFF08\u63A7\u5236\u53F0\u591A\u9879\u76EE\u5165\u53E3\uFF1A/api/v1/projects\uFF1B\u9879\u76EE\u7EA7\u63A5\u53E3\u52A0 X-Project-Id \u5934\uFF09`
17763
18295
  ];
17764
18296
  if (info.fleetNodeId) {
17765
18297
  lines.push(
@@ -17780,7 +18312,7 @@ function printReady(info, qq) {
17780
18312
 
17781
18313
  // src/cli/account-enroll.ts
17782
18314
  init_cjs_shims();
17783
- var path32 = __toESM(require("path"), 1);
18315
+ var path34 = __toESM(require("path"), 1);
17784
18316
  var readline = __toESM(require("readline/promises"), 1);
17785
18317
  var SKIP_ANSWERS = /* @__PURE__ */ new Set(["-", "skip", "none", "no"]);
17786
18318
  function resolveDefaultHub(hubUrl2, env3 = process.env) {
@@ -17792,7 +18324,7 @@ function parseHubAnswer(answer, fallback) {
17792
18324
  return (a || fallback).replace(/\/$/, "");
17793
18325
  }
17794
18326
  function resolveDataDir(dataDir) {
17795
- return path32.resolve(dataDir ?? process.env.DATA_DIR ?? DEFAULT_DATA_DIR);
18327
+ return path34.resolve(dataDir ?? process.env.DATA_DIR ?? DEFAULT_DATA_DIR);
17796
18328
  }
17797
18329
  async function maybeAccountEnroll(opts = {}) {
17798
18330
  if (!process.stdin.isTTY || !process.stdout.isTTY) return;