@zqagent/core 0.2.5 → 0.2.7

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.
@@ -555,8 +555,8 @@ import "dotenv/config";
555
555
 
556
556
  // src/server/runtime.ts
557
557
  init_esm_shims();
558
- import * as fs27 from "fs";
559
- import * as path32 from "path";
558
+ import * as fs29 from "fs";
559
+ import * as path34 from "path";
560
560
 
561
561
  // src/server/store.ts
562
562
  init_esm_shims();
@@ -1045,6 +1045,10 @@ function configDir() {
1045
1045
  const base = process.env.XDG_CONFIG_HOME || (IS_WINDOWS ? process.env.APPDATA : void 0) || path3.join(homeDir(), ".config");
1046
1046
  return path3.join(base, APP_DIR_NAME);
1047
1047
  }
1048
+ function stateDir() {
1049
+ const base = process.env.XDG_STATE_HOME || (IS_WINDOWS ? process.env.LOCALAPPDATA : void 0) || path3.join(homeDir(), ".local", "state");
1050
+ return path3.join(base, APP_DIR_NAME);
1051
+ }
1048
1052
 
1049
1053
  // ../shared/src/llm-presets.ts
1050
1054
  init_esm_shims();
@@ -1608,6 +1612,7 @@ var Store = class {
1608
1612
 
1609
1613
  // src/server/session.ts
1610
1614
  init_esm_shims();
1615
+ import * as path21 from "path";
1611
1616
  import { AsyncLocalStorage } from "async_hooks";
1612
1617
 
1613
1618
  // src/engine/TurnEngine.ts
@@ -1735,8 +1740,11 @@ function lookupContextWindow(modelId) {
1735
1740
  return DEFAULT_CONTEXT_WINDOW;
1736
1741
  }
1737
1742
  var KEEP_RECENT_FRACTION = 0.25;
1738
- var SPAN_TOOL_RESULT_CLIP = 400;
1739
- var SPAN_BUDGET_CHARS = 4e5;
1743
+ var SOFT_THRESHOLD_RATIO = 0.75;
1744
+ var ELIDE_KEEP_FRACTION = 0.15;
1745
+ var SUMMARY_MAX_TOKENS = 3e3;
1746
+ var SPAN_TOOL_RESULT_CLIP = 200;
1747
+ var SPAN_BUDGET_CHARS = 8e4;
1740
1748
  var USER_MESSAGE_CLIP = 600;
1741
1749
  var USER_MESSAGES_MAX = 40;
1742
1750
  var TRIM_FRACTION = 0.1;
@@ -1759,9 +1767,22 @@ function triggerTokens(contextWindow, thresholdPct = DEFAULT_THRESHOLD_PCT, capT
1759
1767
  }
1760
1768
  return base;
1761
1769
  }
1770
+ function softTriggerTokens(contextWindow, thresholdPct = DEFAULT_THRESHOLD_PCT, capTokens, softThresholdPct) {
1771
+ const hard = triggerTokens(contextWindow, thresholdPct, capTokens);
1772
+ if (softThresholdPct != null && Number.isFinite(softThresholdPct) && softThresholdPct > 0) {
1773
+ return Math.min(
1774
+ triggerTokens(contextWindow, softThresholdPct, capTokens),
1775
+ Math.floor(hard * 0.95)
1776
+ );
1777
+ }
1778
+ return Math.floor(hard * SOFT_THRESHOLD_RATIO);
1779
+ }
1762
1780
  function shouldCompact(signal, contextWindow, thresholdPct, capTokens) {
1763
1781
  return signal >= triggerTokens(contextWindow, thresholdPct, capTokens);
1764
1782
  }
1783
+ function elisionKeepChars(hardTriggerTokens, fraction = ELIDE_KEEP_FRACTION) {
1784
+ return Math.max(4e3, Math.floor(hardTriggerTokens * fraction * 4));
1785
+ }
1765
1786
  function turnStarts(messages, start) {
1766
1787
  const users = [];
1767
1788
  const assistants = [];
@@ -1911,10 +1932,11 @@ var SUMMARY_SYSTEM_PROMPT = `\u4F60\u6B63\u5728\u538B\u7F29\u4E00\u4E2A AI \u534
1911
1932
 
1912
1933
  \u89C4\u5219\uFF1A
1913
1934
  - \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
1935
+ - \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
1914
1936
  - \u8981\u5177\u4F53\uFF1A\u8DEF\u5F84\u3001\u540D\u79F0\u3001\u547D\u4EE4\u3001id\u2014\u2014\u800C\u4E0D\u662F\u6A21\u7CCA\u7684\u5F15\u7528\u3002
1915
1937
  - \u4EC5\u8F93\u51FA\u6458\u8981\u7AE0\u8282\uFF0C\u4E0D\u9644\u52A0\u524D\u8A00\u3002`;
1916
1938
  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";
1917
- function renderSpan(span, budgetChars = SPAN_BUDGET_CHARS) {
1939
+ function renderSpan(span, budgetChars2 = SPAN_BUDGET_CHARS) {
1918
1940
  const lines = [];
1919
1941
  for (const m of span) {
1920
1942
  const role = m.role;
@@ -1938,8 +1960,8 @@ function renderSpan(span, budgetChars = SPAN_BUDGET_CHARS) {
1938
1960
  if (text) lines.push(`[${role}] ${text}`);
1939
1961
  }
1940
1962
  let rendered = lines.join("\n");
1941
- if (rendered.length > budgetChars) {
1942
- rendered = "(\u2026oldest turns elided\u2026)\n" + rendered.slice(-budgetChars);
1963
+ if (rendered.length > budgetChars2) {
1964
+ rendered = "(\u2026oldest turns elided\u2026)\n" + rendered.slice(-budgetChars2);
1943
1965
  }
1944
1966
  return rendered;
1945
1967
  }
@@ -1953,9 +1975,17 @@ function summarizerMessages(span, priorSummary = "") {
1953
1975
  { role: "user", content: body }
1954
1976
  ];
1955
1977
  }
1978
+ function summarizerReasoningEffort() {
1979
+ const v = process.env.COMPACTION_REASONING_EFFORT?.toLowerCase();
1980
+ if (v === "low" || v === "medium" || v === "high") return v;
1981
+ return "low";
1982
+ }
1956
1983
  async function summarizeSpan(provider, model, span, priorSummary = "", opts) {
1957
1984
  const msgs = summarizerMessages(span, priorSummary);
1958
- const chunks = await provider.stream(model, msgs, [], opts);
1985
+ const chunks = await provider.stream(model, msgs, [], {
1986
+ ...opts,
1987
+ maxTokens: SUMMARY_MAX_TOKENS
1988
+ });
1959
1989
  let text = "";
1960
1990
  for await (const chunk of chunks) {
1961
1991
  if (chunk.content) text += chunk.content;
@@ -2066,6 +2096,166 @@ function applyToOutbound(messages, state) {
2066
2096
  head.push({ role: "user", content: compactedBlock(state) });
2067
2097
  return [...head, ...messages.slice(boundary)];
2068
2098
  }
2099
+ var ELIDE_EXEMPT_TOOLS = /* @__PURE__ */ new Set([
2100
+ "explore",
2101
+ "ask_user",
2102
+ "propose_plan",
2103
+ "request_directory",
2104
+ "todo_write"
2105
+ ]);
2106
+ function elidedToolContent(m) {
2107
+ const name = m.tool_call_name || "tool";
2108
+ const size = m.content.length;
2109
+ const saved = /full output saved to ([^\]]+)/.exec(m.content)?.[1]?.trim();
2110
+ 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]`;
2111
+ }
2112
+ function isPlaceholder(content) {
2113
+ return content.startsWith("[old tool result elided") || content.startsWith("[superseded by a later read_file");
2114
+ }
2115
+ var ONE_SHOT_TOOLS = /* @__PURE__ */ new Set(["git_diff"]);
2116
+ var ONE_SHOT_KEEP_RECENT = 1;
2117
+ function applyOneShotElision(messages, keepRecent = ONE_SHOT_KEEP_RECENT, tools = ONE_SHOT_TOOLS) {
2118
+ const out = [...messages];
2119
+ let seen = 0;
2120
+ let elided = 0;
2121
+ let freed = 0;
2122
+ for (let i = messages.length - 1; i >= 0; i--) {
2123
+ const m = messages[i];
2124
+ if (m.role !== "tool" || !tools.has(m.tool_call_name ?? "")) continue;
2125
+ if (isPlaceholder(m.content)) continue;
2126
+ seen++;
2127
+ if (seen <= keepRecent) continue;
2128
+ const placeholder = elidedToolContent(m);
2129
+ freed += m.content.length - placeholder.length;
2130
+ out[i] = { ...m, content: placeholder };
2131
+ elided++;
2132
+ }
2133
+ return elided > 0 ? { messages: out, elided, freedChars: freed } : { messages, elided: 0, freedChars: 0 };
2134
+ }
2135
+ function applyToolResultElision(messages, keepChars, exempt = ELIDE_EXEMPT_TOOLS) {
2136
+ let total = 0;
2137
+ for (const m of messages) {
2138
+ if (m.role === "tool" && !exempt.has(m.tool_call_name ?? "")) {
2139
+ total += m.content.length;
2140
+ }
2141
+ }
2142
+ if (total <= keepChars) return { messages, elided: 0, freedChars: 0 };
2143
+ const out = [...messages];
2144
+ let budget = 0;
2145
+ let elided = 0;
2146
+ let freed = 0;
2147
+ for (let i = messages.length - 1; i >= 0; i--) {
2148
+ const m = messages[i];
2149
+ if (m.role !== "tool") continue;
2150
+ if (exempt.has(m.tool_call_name ?? "")) continue;
2151
+ if (elided === 0 && budget === 0 && m.content.length > keepChars) {
2152
+ const kept = m.content.slice(0, keepChars) + "\n\u2026(truncated)";
2153
+ freed += m.content.length - kept.length;
2154
+ out[i] = { ...m, content: kept };
2155
+ budget = keepChars;
2156
+ continue;
2157
+ }
2158
+ if (budget + m.content.length <= keepChars) {
2159
+ budget += m.content.length;
2160
+ continue;
2161
+ }
2162
+ const placeholder = elidedToolContent(m);
2163
+ freed += m.content.length - placeholder.length;
2164
+ out[i] = { ...m, content: placeholder };
2165
+ elided++;
2166
+ }
2167
+ return { messages: out, elided, freedChars: freed };
2168
+ }
2169
+ function normalizeToolPath(p) {
2170
+ let s = p.trim().replace(/\\/g, "/");
2171
+ while (s.startsWith("./")) s = s.slice(2);
2172
+ return s.replace(/\/{2,}/g, "/");
2173
+ }
2174
+ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
2175
+ "write_file",
2176
+ "replace_in_file",
2177
+ "apply_patch",
2178
+ "apply_unified_diff",
2179
+ "run_shell"
2180
+ ]);
2181
+ function parseReadFileSpan(content) {
2182
+ const body = content.replace(/^\[tool output truncated:[^\n]*\]\n\n/, "");
2183
+ let path36;
2184
+ let start;
2185
+ let end;
2186
+ try {
2187
+ const obj = JSON.parse(body);
2188
+ path36 = obj.path;
2189
+ start = obj.start_line;
2190
+ end = obj.end_line;
2191
+ } catch {
2192
+ const str2 = (key) => new RegExp(`"${key}"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)"`).exec(body)?.[1];
2193
+ const num2 = (key) => {
2194
+ const m = new RegExp(`"${key}"\\s*:\\s*(\\d+)`).exec(body);
2195
+ return m ? Number(m[1]) : void 0;
2196
+ };
2197
+ path36 = str2("path");
2198
+ start = num2("start_line");
2199
+ end = num2("end_line");
2200
+ }
2201
+ if (typeof path36 !== "string" || !path36) return null;
2202
+ if (typeof start !== "number" || typeof end !== "number") return null;
2203
+ if (!Number.isFinite(start) || !Number.isFinite(end)) return null;
2204
+ return { path: path36, start, end };
2205
+ }
2206
+ function addSpan(spans, start, end) {
2207
+ spans.push([start, end]);
2208
+ spans.sort((a, b) => a[0] - b[0]);
2209
+ const merged = [];
2210
+ for (const cur of spans) {
2211
+ const last = merged[merged.length - 1];
2212
+ if (last && cur[0] <= last[1] + 1) {
2213
+ last[1] = Math.max(last[1], cur[1]);
2214
+ } else {
2215
+ merged.push([...cur]);
2216
+ }
2217
+ }
2218
+ spans.length = 0;
2219
+ spans.push(...merged);
2220
+ }
2221
+ function coversSpans(spans, start, end) {
2222
+ return spans.some(([a, b]) => a <= start && b >= end);
2223
+ }
2224
+ function applyReadFileDedup(messages) {
2225
+ let candidates = 0;
2226
+ for (const m of messages) {
2227
+ if (m.role === "tool" && m.tool_call_name === "read_file") candidates++;
2228
+ }
2229
+ if (candidates < 2) return { messages, elided: 0 };
2230
+ const out = [...messages];
2231
+ const covered = /* @__PURE__ */ new Map();
2232
+ let elided = 0;
2233
+ for (let i = messages.length - 1; i >= 0; i--) {
2234
+ const m = messages[i];
2235
+ if (m.role === "assistant") {
2236
+ if (m.tool_calls?.some((tc) => MUTATING_TOOLS.has(tc.function.name))) {
2237
+ covered.clear();
2238
+ }
2239
+ continue;
2240
+ }
2241
+ if (m.role !== "tool" || m.tool_call_name !== "read_file") continue;
2242
+ const span = parseReadFileSpan(m.content);
2243
+ if (!span) continue;
2244
+ const key = normalizeToolPath(span.path);
2245
+ const seen = covered.get(key) ?? [];
2246
+ if (coversSpans(seen, span.start, span.end)) {
2247
+ out[i] = {
2248
+ ...m,
2249
+ content: `[superseded by a later read_file of ${span.path} (lines ${span.start}-${span.end})]`
2250
+ };
2251
+ elided++;
2252
+ continue;
2253
+ }
2254
+ addSpan(seen, span.start, span.end);
2255
+ covered.set(key, seen);
2256
+ }
2257
+ return { messages: elided > 0 ? out : messages, elided };
2258
+ }
2069
2259
  var OVERFLOW_MARKERS = [
2070
2260
  "context_length_exceeded",
2071
2261
  "maximum context length",
@@ -2081,9 +2271,94 @@ function isContextOverflow(exc) {
2081
2271
  return OVERFLOW_MARKERS.some((m) => text.includes(m));
2082
2272
  }
2083
2273
 
2274
+ // src/engine/toolResultBudget.ts
2275
+ init_esm_shims();
2276
+ import * as fs4 from "fs";
2277
+ import * as path6 from "path";
2278
+ var DEFAULT_TOOL_RESULT_MAX_CHARS = 24e3;
2279
+ var HEAD_RATIO = 0.6;
2280
+ function budgetChars() {
2281
+ const n = Number(process.env.TOOL_RESULT_MAX_CHARS);
2282
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : DEFAULT_TOOL_RESULT_MAX_CHARS;
2283
+ }
2284
+ function toolResultDir() {
2285
+ const env3 = process.env.TOOL_RESULT_DIR?.trim();
2286
+ return env3 || path6.join(stateDir(), "tool-results");
2287
+ }
2288
+ function clipText(text, budget) {
2289
+ if (text.length <= budget) return text;
2290
+ const room = Math.max(
2291
+ 0,
2292
+ budget - `
2293
+
2294
+ \u2026 [${text.length} characters omitted] \u2026
2295
+
2296
+ `.length
2297
+ );
2298
+ const headLen = Math.floor(room * HEAD_RATIO);
2299
+ const tailLen = room - headLen;
2300
+ const omitted = text.length - headLen - tailLen;
2301
+ return text.slice(0, headLen) + `
2302
+
2303
+ \u2026 [${omitted} characters omitted] \u2026
2304
+
2305
+ ` + text.slice(-tailLen);
2306
+ }
2307
+ function clipDeep(value, perField) {
2308
+ if (typeof value === "string") {
2309
+ return value.length <= perField ? value : clipText(value, perField);
2310
+ }
2311
+ if (Array.isArray(value)) {
2312
+ return value.map((v) => clipDeep(v, perField));
2313
+ }
2314
+ if (value && typeof value === "object") {
2315
+ const out = {};
2316
+ for (const [k, v] of Object.entries(value)) out[k] = clipDeep(v, perField);
2317
+ return out;
2318
+ }
2319
+ return value;
2320
+ }
2321
+ function saveRaw(tool, raw, dir) {
2322
+ try {
2323
+ fs4.mkdirSync(dir, { recursive: true });
2324
+ const safe = tool.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 40) || "tool";
2325
+ const name = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}-${safe}.txt`;
2326
+ const file = path6.join(dir, name);
2327
+ fs4.writeFileSync(file, raw, "utf-8");
2328
+ return file;
2329
+ } catch {
2330
+ return void 0;
2331
+ }
2332
+ }
2333
+ function applyToolResultBudget(tool, result, opts = {}) {
2334
+ const budget = opts.maxChars ?? budgetChars();
2335
+ const raw = typeof result === "string" ? result : JSON.stringify(result, null, 2) ?? String(result);
2336
+ if (raw.length <= budget) {
2337
+ return { content: raw, truncated: false };
2338
+ }
2339
+ let content = raw;
2340
+ if (typeof result === "object" && result !== null) {
2341
+ let perField = Math.floor(budget * 0.7);
2342
+ let clipped = JSON.stringify(clipDeep(result, perField), null, 2);
2343
+ for (let i = 0; i < 6 && clipped && clipped.length > budget && perField > 128; i++) {
2344
+ perField = Math.floor(perField / 2);
2345
+ clipped = JSON.stringify(clipDeep(result, perField), null, 2);
2346
+ }
2347
+ if (clipped && clipped.length <= budget) content = clipped;
2348
+ }
2349
+ if (content.length > budget) content = clipText(content, budget);
2350
+ const savedPath = saveRaw(tool, raw, opts.dir ?? toolResultDir());
2351
+ if (savedPath) {
2352
+ content = `[tool output truncated: ${raw.length} chars, full output saved to ${savedPath}]
2353
+
2354
+ ` + content;
2355
+ }
2356
+ return { content, truncated: true, savedPath };
2357
+ }
2358
+
2084
2359
  // src/engine/TurnEngine.ts
2085
2360
  import * as crypto3 from "crypto";
2086
- import * as fs4 from "fs";
2361
+ import * as fs5 from "fs";
2087
2362
  import * as _path from "path";
2088
2363
 
2089
2364
  // src/prompts/index.ts
@@ -2209,13 +2484,17 @@ var INTERACTIVE_TOOLS = /* @__PURE__ */ new Set([
2209
2484
  "propose_plan",
2210
2485
  "request_directory"
2211
2486
  ]);
2212
- function toolResultMessage(tc, result, taskId) {
2213
- const content = typeof result === "string" ? result : JSON.stringify(result, null, 2);
2487
+ var MAX_COMPACT_ROUNDS = 4;
2488
+ var OVERFLOW_TARGET_RATIO = 0.5;
2489
+ var OVERFLOW_TRIM_FRACTION = 0.5;
2490
+ var COMPACT_STALL_RETRY_DELTA = 20;
2491
+ function toolResultMessage(tc, result, taskId, toolResultDir2) {
2492
+ const budgeted = applyToolResultBudget(tc.name, result, { dir: toolResultDir2 });
2214
2493
  return {
2215
2494
  role: "tool",
2216
2495
  tool_call_id: tc.id,
2217
2496
  tool_call_name: tc.name,
2218
- content,
2497
+ content: budgeted.content,
2219
2498
  taskId
2220
2499
  };
2221
2500
  }
@@ -2326,12 +2605,22 @@ var TurnEngine = class _TurnEngine {
2326
2605
  // 上下文压缩
2327
2606
  compactionState = null;
2328
2607
  _lastContextTokens = null;
2608
+ /** 压缩无进展时的消息条数快照;-1 表示无冷却 */
2609
+ _compactStalledAt = -1;
2329
2610
  /** 显式配置的上下文窗口大小(token 数),未设置时为 undefined(走自动检测) */
2330
2611
  _contextWindow;
2331
2612
  /** 压缩触发阈值比例(默认 0.8) */
2332
2613
  _thresholdPct;
2614
+ /** 软阈值比例(undefined = 取硬阈值的 75%) */
2615
+ _softThresholdPct;
2616
+ /** 是否启用软阈值淘汰(COMPACTION_ELIDE=0 可关闭) */
2617
+ _elisionEnabled;
2618
+ /** 是否启用重复读取去重(COMPACTION_DEDUP=0 可关闭) */
2619
+ _dedupEnabled;
2333
2620
  /** 压缩触发阈值绝对上限(null = 不限制) */
2334
2621
  _capTokens;
2622
+ /** 超预算工具结果的落盘目录(undefined = 使用默认状态目录) */
2623
+ _toolResultDir;
2335
2624
  // 产出文件追踪(本轮 Turn 中成功写入的文件)
2336
2625
  _producedFiles = [];
2337
2626
  _turnStartTime = 0;
@@ -2383,7 +2672,15 @@ var TurnEngine = class _TurnEngine {
2383
2672
  this._hook = opts.hook;
2384
2673
  this._contextWindow = opts.contextWindow;
2385
2674
  this._thresholdPct = opts.thresholdPct ?? DEFAULT_THRESHOLD_PCT;
2675
+ this._softThresholdPct = opts.softThresholdPct;
2676
+ this._elisionEnabled = !/^(0|false|off|no)$/i.test(
2677
+ (process.env.COMPACTION_ELIDE ?? "").trim()
2678
+ );
2679
+ this._dedupEnabled = !/^(0|false|off|no)$/i.test(
2680
+ (process.env.COMPACTION_DEDUP ?? "").trim()
2681
+ );
2386
2682
  this._capTokens = opts.capTokens ?? null;
2683
+ this._toolResultDir = opts.toolResultDir;
2387
2684
  this._reasoningEffort = opts.reasoningEffort;
2388
2685
  if (this.messages.length === 0 || this.messages[0].role !== "system") {
2389
2686
  this.messages.unshift({ role: "system", content: this.instructions });
@@ -2439,6 +2736,7 @@ var TurnEngine = class _TurnEngine {
2439
2736
  this._cancel = false;
2440
2737
  this._producedFiles = [];
2441
2738
  this._turnStartTime = Date.now();
2739
+ this._compactStalledAt = -1;
2442
2740
  this._emitHook((h) => h.onTurnStart?.(userInput));
2443
2741
  yield {
2444
2742
  type: "turn_start" /* TURN_START */,
@@ -3155,7 +3453,9 @@ var TurnEngine = class _TurnEngine {
3155
3453
  for (let i = 0; i < concurrent.length; i++) {
3156
3454
  const tc = concurrent[i];
3157
3455
  const { result, status } = results[i];
3158
- this.messages.push(toolResultMessage(tc, result, this.taskId));
3456
+ this.messages.push(
3457
+ toolResultMessage(tc, result, this.taskId, this._toolResultDir)
3458
+ );
3159
3459
  this._trackProducedFile(
3160
3460
  tc,
3161
3461
  result,
@@ -3218,7 +3518,9 @@ var TurnEngine = class _TurnEngine {
3218
3518
  };
3219
3519
  status = "error";
3220
3520
  }
3221
- this.messages.push(toolResultMessage(tc, result, this.taskId));
3521
+ this.messages.push(
3522
+ toolResultMessage(tc, result, this.taskId, this._toolResultDir)
3523
+ );
3222
3524
  this._trackProducedFile(
3223
3525
  tc,
3224
3526
  result,
@@ -3253,13 +3555,41 @@ var TurnEngine = class _TurnEngine {
3253
3555
  * 过滤 notice 消息;tool 消息只保留必要字段;
3254
3556
  * 其余消息剥离 display-only sidecar 字段(source/_display/ts/
3255
3557
  * reasoning/usage),避免污染模型上下文。
3558
+ *
3559
+ * 达到软阈值时先淘汰旧工具结果(零 LLM 成本),再产出最终视图。
3256
3560
  */
3257
3561
  _outboundMessages() {
3258
- const _STRIP = /* @__PURE__ */ new Set(["source", "_display", "ts", "reasoning", "usage"]);
3259
- const sourceMessages = applyToOutbound(
3260
- this.messages,
3261
- this.compactionState
3562
+ const base = applyToOutbound(this.messages, this.compactionState);
3563
+ const deduped = this._dedupEnabled ? applyReadFileDedup(base).messages : base;
3564
+ let view = this._finalizeOutbound(deduped);
3565
+ if (!this._elisionEnabled) return view;
3566
+ if (estimateTokens(view) < this._softTriggerTokens()) {
3567
+ return view;
3568
+ }
3569
+ const oneShot = applyOneShotElision(deduped);
3570
+ if (oneShot.freedChars > 0) {
3571
+ view = this._finalizeOutbound(oneShot.messages);
3572
+ if (estimateTokens(view) < this._softTriggerTokens()) {
3573
+ return view;
3574
+ }
3575
+ }
3576
+ const keepChars = elisionKeepChars(
3577
+ triggerTokens(
3578
+ this._getContextWindow(),
3579
+ this._thresholdPct,
3580
+ this._capTokens
3581
+ )
3582
+ );
3583
+ const elided = applyToolResultElision(
3584
+ oneShot.messages,
3585
+ keepChars
3262
3586
  );
3587
+ if (elided.freedChars === 0) return view;
3588
+ return this._finalizeOutbound(elided.messages);
3589
+ }
3590
+ /** 过滤 notice + 剥离 display-only 字段,得到最终发送视图 */
3591
+ _finalizeOutbound(sourceMessages) {
3592
+ const _STRIP = /* @__PURE__ */ new Set(["source", "_display", "ts", "reasoning", "usage"]);
3263
3593
  return sourceMessages.filter((m) => m.role !== "notice").map((m) => {
3264
3594
  if (m.role === "tool") {
3265
3595
  return {
@@ -3277,6 +3607,15 @@ var TurnEngine = class _TurnEngine {
3277
3607
  return stripped;
3278
3608
  });
3279
3609
  }
3610
+ /** 软阈值(token 数):达到它只淘汰旧工具结果 */
3611
+ _softTriggerTokens() {
3612
+ return softTriggerTokens(
3613
+ this._getContextWindow(),
3614
+ this._thresholdPct,
3615
+ this._capTokens,
3616
+ this._softThresholdPct
3617
+ );
3618
+ }
3280
3619
  /** 通知消息:仅用于日志/展示,不发给模型 */
3281
3620
  _appendNotice(kind, text) {
3282
3621
  this.messages.push({
@@ -3318,18 +3657,36 @@ var TurnEngine = class _TurnEngine {
3318
3657
  return false;
3319
3658
  }
3320
3659
  // ===================== 自动压缩 =====================
3321
- /** 当前上下文 token 用量:优先采信 API 返回的精确数量,否则用 chars/4 估算 */
3660
+ /**
3661
+ * 当前上下文 token 用量。
3662
+ *
3663
+ * API 返回的 `usage.input` 是精确值,但反映的是「上一次请求」——工具结果
3664
+ * 与淘汰/压缩都会让它过时。因此同时估算当前 outbound 视图,取较大者:
3665
+ * 宁可早一点压缩,也不要漏判导致溢出。
3666
+ */
3322
3667
  _compactionTokens() {
3323
- if (this._lastContextTokens != null) {
3324
- return { tokens: this._lastContextTokens, estimated: false };
3668
+ const estimated = estimateTokens(this._outboundMessages());
3669
+ if (this._lastContextTokens == null) {
3670
+ return { tokens: estimated, estimated: true };
3325
3671
  }
3326
- return {
3327
- tokens: estimateTokens(this._outboundMessages()),
3328
- estimated: true
3329
- };
3672
+ if (estimated > this._lastContextTokens) {
3673
+ return { tokens: estimated, estimated: true };
3674
+ }
3675
+ return { tokens: this._lastContextTokens, estimated: false };
3676
+ }
3677
+ /** 压缩后重置精确计数:后续按估算值判断 */
3678
+ _resetContextTokens() {
3679
+ this._lastContextTokens = null;
3680
+ }
3681
+ /** 当前 outbound 视图的估算 token 数 */
3682
+ _estimateContextTokens() {
3683
+ return estimateTokens(this._outboundMessages());
3330
3684
  }
3331
3685
  /** 检查是否需要压缩(每轮 LLM 调用前) */
3332
3686
  _compactionDue() {
3687
+ if (this._compactStalledAt >= 0 && this.messages.length < this._compactStalledAt + COMPACT_STALL_RETRY_DELTA) {
3688
+ return false;
3689
+ }
3333
3690
  const { tokens } = this._compactionTokens();
3334
3691
  return shouldCompact(
3335
3692
  tokens,
@@ -3376,63 +3733,82 @@ var TurnEngine = class _TurnEngine {
3376
3733
  async _compactNow(reqId, force = false) {
3377
3734
  const contextWindow = this._getContextWindow();
3378
3735
  const messagesBefore = this._outboundMessages().length;
3379
- const keep = Math.floor(
3380
- KEEP_RECENT_FRACTION * triggerTokens(
3381
- contextWindow,
3382
- this._thresholdPct,
3383
- this._capTokens
3384
- )
3736
+ const trigger = triggerTokens(
3737
+ contextWindow,
3738
+ this._thresholdPct,
3739
+ this._capTokens
3385
3740
  );
3386
- const model = this.model;
3387
- let state = null;
3388
- let failed2 = false;
3389
- for (let attempt = 0; attempt < 2; attempt++) {
3390
- try {
3391
- const logTime = (/* @__PURE__ */ new Date()).toISOString();
3392
- state = await buildState(
3741
+ const target = Math.floor(
3742
+ trigger * (force ? OVERFLOW_TARGET_RATIO : 0.9)
3743
+ );
3744
+ const model = process.env.COMPACTION_MODEL?.trim() || this.model;
3745
+ const reasoningEffort = summarizerReasoningEffort();
3746
+ let keep = Math.floor(KEEP_RECENT_FRACTION * trigger);
3747
+ let method = null;
3748
+ let lastState = null;
3749
+ let progressed = false;
3750
+ for (let round = 0; round < MAX_COMPACT_ROUNDS; round++) {
3751
+ if (round > 0 && this._estimateContextTokens() <= target) break;
3752
+ let state = null;
3753
+ for (let attempt = 0; attempt < 2 && !state; attempt++) {
3754
+ try {
3755
+ const logTime = (/* @__PURE__ */ new Date()).toISOString();
3756
+ state = await buildState(
3757
+ this.messages,
3758
+ this.provider,
3759
+ model,
3760
+ keep,
3761
+ this.compactionState,
3762
+ { reqId, logTime, reasoningEffort }
3763
+ );
3764
+ } catch {
3765
+ }
3766
+ }
3767
+ if (state) {
3768
+ this.compactionState = state;
3769
+ lastState = state;
3770
+ method = "summarized";
3771
+ progressed = true;
3772
+ } else {
3773
+ const trimmed = trimState(
3393
3774
  this.messages,
3394
- this.provider,
3395
- model,
3396
- keep,
3397
3775
  this.compactionState,
3398
- { reqId, logTime, reasoningEffort: this._reasoningEffort }
3776
+ force ? OVERFLOW_TRIM_FRACTION : 0.25
3399
3777
  );
3400
- failed2 = false;
3401
- break;
3402
- } catch {
3403
- failed2 = true;
3404
- }
3405
- }
3406
- if (state) {
3407
- this.compactionState = state;
3408
- this._lastContextTokens = null;
3778
+ if (!trimmed) break;
3779
+ this.compactionState = trimmed;
3780
+ lastState = trimmed;
3781
+ method = "trimmed";
3782
+ progressed = true;
3783
+ }
3784
+ this._resetContextTokens();
3785
+ if (this._estimateContextTokens() <= trigger) break;
3786
+ keep = Math.max(Math.floor(trigger * 0.05), Math.floor(keep / 2));
3787
+ }
3788
+ if (!progressed || !lastState) return { notice: null, method: null };
3789
+ this._compactStalledAt = this._estimateContextTokens() >= trigger ? this.messages.length : -1;
3790
+ const messagesAfter = this._outboundMessages().length;
3791
+ const common = {
3792
+ boundaryIndex: lastState.boundaryIndex,
3793
+ messagesBefore,
3794
+ messagesAfter
3795
+ };
3796
+ if (method === "summarized") {
3409
3797
  return {
3410
3798
  notice: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29 \u2014 \u65E9\u671F\u8F6E\u6B21\u5DF2\u6458\u8981",
3411
3799
  method: "summarized",
3412
- modelUsed: state.modelUsed,
3413
- boundaryIndex: state.boundaryIndex,
3414
- summaryPreview: state.summaryText.slice(0, 200),
3415
- keptUserMessages: state.userMessages.length,
3416
- droppedUserMessages: state.userMessagesDropped,
3417
- messagesBefore,
3418
- messagesAfter: this._outboundMessages().length
3800
+ modelUsed: lastState.modelUsed,
3801
+ summaryPreview: lastState.summaryText.slice(0, 200),
3802
+ keptUserMessages: lastState.userMessages.length,
3803
+ droppedUserMessages: lastState.userMessagesDropped,
3804
+ ...common
3419
3805
  };
3420
3806
  }
3421
- if (failed2 || force) {
3422
- const trimmed = trimState(this.messages, this.compactionState);
3423
- if (trimmed) {
3424
- this.compactionState = trimmed;
3425
- this._lastContextTokens = null;
3426
- return {
3427
- notice: "\u4E0A\u4E0B\u6587\u5DF2\u88C1\u526A \u2014 \u6700\u65E7\u7684\u8F6E\u6B21\u5DF2\u4E22\u5F03\uFF08\u6458\u8981\u4E0D\u53EF\u7528\uFF09",
3428
- method: "trimmed",
3429
- boundaryIndex: trimmed.boundaryIndex,
3430
- messagesBefore,
3431
- messagesAfter: this._outboundMessages().length
3432
- };
3433
- }
3434
- }
3435
- return { notice: null, method: null };
3807
+ return {
3808
+ notice: "\u4E0A\u4E0B\u6587\u5DF2\u88C1\u526A \u2014 \u6700\u65E7\u7684\u8F6E\u6B21\u5DF2\u4E22\u5F03\uFF08\u6458\u8981\u4E0D\u53EF\u7528\uFF09",
3809
+ method: "trimmed",
3810
+ ...common
3811
+ };
3436
3812
  }
3437
3813
  /** 追踪文件写入工具成功产生的文件 */
3438
3814
  _trackProducedFile(tc, result, status) {
@@ -3442,9 +3818,9 @@ var TurnEngine = class _TurnEngine {
3442
3818
  return;
3443
3819
  }
3444
3820
  if (result && typeof result === "object") {
3445
- const path34 = result.path;
3446
- if (path34) {
3447
- this._producedFiles.push(path34);
3821
+ const path36 = result.path;
3822
+ if (path36) {
3823
+ this._producedFiles.push(path36);
3448
3824
  return;
3449
3825
  }
3450
3826
  const changed = result.changed_files;
@@ -3468,7 +3844,7 @@ var TurnEngine = class _TurnEngine {
3468
3844
  _fileExists(f, ws) {
3469
3845
  if (!ws && !_path.isAbsolute(f)) return true;
3470
3846
  try {
3471
- return fs4.existsSync(_path.isAbsolute(f) ? f : _path.join(ws, f));
3847
+ return fs5.existsSync(_path.isAbsolute(f) ? f : _path.join(ws, f));
3472
3848
  } catch {
3473
3849
  return false;
3474
3850
  }
@@ -3491,7 +3867,7 @@ var TurnEngine = class _TurnEngine {
3491
3867
  if (depth > 4 || results.length >= 50) return;
3492
3868
  let entries;
3493
3869
  try {
3494
- entries = fs4.readdirSync(dir, { withFileTypes: true });
3870
+ entries = fs5.readdirSync(dir, { withFileTypes: true });
3495
3871
  } catch {
3496
3872
  return;
3497
3873
  }
@@ -3506,7 +3882,7 @@ var TurnEngine = class _TurnEngine {
3506
3882
  const ext = dotIdx !== -1 ? e.name.slice(dotIdx).toLowerCase() : "";
3507
3883
  if (!_TurnEngine.SCAN_SUFFIXES.has(ext)) continue;
3508
3884
  try {
3509
- const st = fs4.statSync(full);
3885
+ const st = fs5.statSync(full);
3510
3886
  if (st.mtimeMs >= since) {
3511
3887
  results.push(
3512
3888
  _path.relative(_path.resolve(ws), _path.resolve(full))
@@ -3963,16 +4339,16 @@ var McpManager = class {
3963
4339
 
3964
4340
  // src/server/mcpConfig.ts
3965
4341
  init_esm_shims();
3966
- import fs5 from "fs";
3967
- import path6 from "path";
4342
+ import fs6 from "fs";
4343
+ import path7 from "path";
3968
4344
  function isValidMcpConfig(v) {
3969
4345
  return !!v && typeof v === "object" && typeof v.name === "string" && (v.type === "stdio" || v.type === "http");
3970
4346
  }
3971
4347
  function readConfigFile(file) {
3972
4348
  if (!file) return [];
3973
4349
  try {
3974
- if (!fs5.existsSync(file)) return [];
3975
- const raw = JSON.parse(fs5.readFileSync(file, "utf8"));
4350
+ if (!fs6.existsSync(file)) return [];
4351
+ const raw = JSON.parse(fs6.readFileSync(file, "utf8"));
3976
4352
  const list = Array.isArray(raw) ? raw : raw && typeof raw === "object" && Array.isArray(raw.servers) ? raw.servers : [];
3977
4353
  return list.filter(isValidMcpConfig);
3978
4354
  } catch (err) {
@@ -3984,7 +4360,7 @@ function hasServerMcpConfig() {
3984
4360
  if (readConfigFile(process.env.MCP_CONFIG_PATH).length > 0) return true;
3985
4361
  const workspace = process.env.WORKSPACE;
3986
4362
  if (!workspace) return false;
3987
- return readConfigFile(path6.join(workspace, ".agent", "mcp.json")).length > 0;
4363
+ return readConfigFile(path7.join(workspace, ".agent", "mcp.json")).length > 0;
3988
4364
  }
3989
4365
  function loadServerMcpConfigs(tenant) {
3990
4366
  const fromEnv = readConfigFile(process.env.MCP_CONFIG_PATH);
@@ -3994,7 +4370,7 @@ function loadServerMcpConfigs(tenant) {
3994
4370
  }
3995
4371
  const workspace = tenant.workspaceRoot ?? process.env.WORKSPACE;
3996
4372
  if (workspace)
3997
- return readConfigFile(path6.join(workspace, ".agent", "mcp.json"));
4373
+ return readConfigFile(path7.join(workspace, ".agent", "mcp.json"));
3998
4374
  return [];
3999
4375
  }
4000
4376
 
@@ -4003,62 +4379,62 @@ init_esm_shims();
4003
4379
 
4004
4380
  // src/tools/pathSafety.ts
4005
4381
  init_esm_shims();
4006
- import * as fs6 from "fs";
4007
- import * as path7 from "path";
4382
+ import * as fs7 from "fs";
4383
+ import * as path8 from "path";
4008
4384
  function isInsideRelative(rel) {
4009
- return !path7.isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${path7.sep}`);
4385
+ return !path8.isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${path8.sep}`);
4010
4386
  }
4011
4387
  function isFilesystemRoot(p) {
4012
- return path7.parse(p).root === p;
4388
+ return path8.parse(p).root === p;
4013
4389
  }
4014
4390
  function resolveWithinRoot(workspace, target) {
4015
- const abs = path7.resolve(workspace, target);
4391
+ const abs = path8.resolve(workspace, target);
4016
4392
  let rootReal;
4017
4393
  try {
4018
- rootReal = fs6.realpathSync(workspace);
4394
+ rootReal = fs7.realpathSync(workspace);
4019
4395
  } catch {
4020
- const rootAbs = path7.resolve(workspace);
4021
- if (!isInsideRelative(path7.relative(rootAbs, abs))) return null;
4396
+ const rootAbs = path8.resolve(workspace);
4397
+ if (!isInsideRelative(path8.relative(rootAbs, abs))) return null;
4022
4398
  return { root: rootAbs, real: abs };
4023
4399
  }
4024
4400
  if (isFilesystemRoot(rootReal)) return null;
4025
4401
  let real;
4026
4402
  try {
4027
- real = fs6.realpathSync(abs);
4403
+ real = fs7.realpathSync(abs);
4028
4404
  } catch {
4029
4405
  let probe = abs;
4030
4406
  const tail = [];
4031
4407
  for (; ; ) {
4032
- const exists = fs6.existsSync(probe) || fs6.lstatSync(probe, { throwIfNoEntry: false }) !== void 0;
4408
+ const exists = fs7.existsSync(probe) || fs7.lstatSync(probe, { throwIfNoEntry: false }) !== void 0;
4033
4409
  if (exists) {
4034
4410
  try {
4035
- real = path7.join(fs6.realpathSync(probe), ...tail);
4411
+ real = path8.join(fs7.realpathSync(probe), ...tail);
4036
4412
  break;
4037
4413
  } catch {
4038
4414
  return null;
4039
4415
  }
4040
4416
  }
4041
- tail.unshift(path7.basename(probe));
4042
- const parent = path7.dirname(probe);
4417
+ tail.unshift(path8.basename(probe));
4418
+ const parent = path8.dirname(probe);
4043
4419
  if (parent === probe) return null;
4044
4420
  probe = parent;
4045
4421
  }
4046
4422
  }
4047
- if (!isInsideRelative(path7.relative(rootReal, real))) return null;
4423
+ if (!isInsideRelative(path8.relative(rootReal, real))) return null;
4048
4424
  return { root: rootReal, real };
4049
4425
  }
4050
4426
  function isWithinRoot(workspace, target) {
4051
4427
  return resolveWithinRoot(workspace, target) !== null;
4052
4428
  }
4053
4429
  function resolveEntryWithinRoot(workspace, target) {
4054
- const abs = path7.resolve(workspace, target);
4055
- const parent = resolveWithinRoot(workspace, path7.dirname(abs));
4430
+ const abs = path8.resolve(workspace, target);
4431
+ const parent = resolveWithinRoot(workspace, path8.dirname(abs));
4056
4432
  if (!parent) return null;
4057
- return { root: parent.root, real: path7.join(parent.real, path7.basename(abs)) };
4433
+ return { root: parent.root, real: path8.join(parent.real, path8.basename(abs)) };
4058
4434
  }
4059
4435
 
4060
4436
  // src/permissions/PermissionEngine.ts
4061
- import * as path8 from "path";
4437
+ import * as path9 from "path";
4062
4438
  var READ_ONLY_MODES = /* @__PURE__ */ new Set(["discuss" /* DISCUSS */, "plan" /* PLAN */]);
4063
4439
  var WRITE_TOOLS = /* @__PURE__ */ new Set([
4064
4440
  "write_file",
@@ -4108,7 +4484,7 @@ var PermissionEngine = class {
4108
4484
  /** 会话级「总是允许此命令」集合 */
4109
4485
  sessionAllowCommands;
4110
4486
  constructor(opts) {
4111
- this.workspaceRoot = path8.resolve(opts.workspaceRoot);
4487
+ this.workspaceRoot = path9.resolve(opts.workspaceRoot);
4112
4488
  this.mode = opts.mode;
4113
4489
  this.allowedCommands = Array.isArray(opts.allowedCommands) ? opts.allowedCommands : [];
4114
4490
  this.sessionAllowTools = /* @__PURE__ */ new Set();
@@ -4201,8 +4577,8 @@ var PermissionEngine = class {
4201
4577
 
4202
4578
  // src/providers/OpenAIProvider.ts
4203
4579
  init_esm_shims();
4204
- import * as fs7 from "fs";
4205
- import * as path9 from "path";
4580
+ import * as fs8 from "fs";
4581
+ import * as path10 from "path";
4206
4582
  import OpenAI from "openai";
4207
4583
 
4208
4584
  // src/terminal.ts
@@ -4340,7 +4716,7 @@ var OpenAIProvider = class {
4340
4716
  });
4341
4717
  if (opts.logDir) {
4342
4718
  this._logDir = opts.logDir;
4343
- fs7.mkdirSync(opts.logDir, { recursive: true });
4719
+ fs8.mkdirSync(opts.logDir, { recursive: true });
4344
4720
  }
4345
4721
  }
4346
4722
  // =====================================================================
@@ -4493,6 +4869,8 @@ var OpenAIProvider = class {
4493
4869
  tools: this._toResponsesTools(tools, model),
4494
4870
  // 推理强度:DeepSeek 不支持,跳过
4495
4871
  ...opts.reasoningEffort && !this._isDeepSeek(model) ? { reasoning: { effort: opts.reasoningEffort } } : {},
4872
+ // 限制输出长度(摘要器等轻量请求)
4873
+ ...opts.maxTokens ? { max_output_tokens: opts.maxTokens } : {},
4496
4874
  stream: true
4497
4875
  }, requestOptions);
4498
4876
  return this._responsesStream(reqId, startTime, rawStream, {
@@ -4591,6 +4969,8 @@ var OpenAIProvider = class {
4591
4969
  })),
4592
4970
  // 推理强度:DeepSeek 不支持,跳过
4593
4971
  ...opts.reasoningEffort && !this._isDeepSeek(model) ? { reasoning_effort: opts.reasoningEffort } : {},
4972
+ // 限制输出长度:o 系列 / gpt-5 推理模型仅支持 max_completion_tokens
4973
+ ...opts.maxTokens ? isReasoningModel(model) ? { max_completion_tokens: opts.maxTokens } : { max_tokens: opts.maxTokens } : {},
4594
4974
  stream: true,
4595
4975
  // 要求返回 Token 用量统计(在流结束前的一个 chunk 中携带)
4596
4976
  stream_options: { include_usage: true }
@@ -4704,8 +5084,8 @@ var OpenAIProvider = class {
4704
5084
  if (!this._logDir) return;
4705
5085
  try {
4706
5086
  const today = logTime?.split("T")[0];
4707
- const dir = path9.join(this._logDir, today);
4708
- fs7.mkdirSync(dir, { recursive: true });
5087
+ const dir = path10.join(this._logDir, today);
5088
+ fs8.mkdirSync(dir, { recursive: true });
4709
5089
  const entry = {
4710
5090
  reqId,
4711
5091
  ts: logTime,
@@ -4717,8 +5097,8 @@ var OpenAIProvider = class {
4717
5097
  toolNames: tools?.map((t) => t.function.name) ?? []
4718
5098
  }
4719
5099
  };
4720
- fs7.writeFileSync(
4721
- path9.join(dir, `${reqId}.json`),
5100
+ fs8.writeFileSync(
5101
+ path10.join(dir, `${reqId}.json`),
4722
5102
  JSON.stringify(entry, null, 2),
4723
5103
  "utf-8"
4724
5104
  );
@@ -4737,9 +5117,9 @@ var OpenAIProvider = class {
4737
5117
  if (!this._logDir) return;
4738
5118
  try {
4739
5119
  const today = logTime?.split("T")[0];
4740
- const filePath = path9.join(this._logDir, today, `${reqId}.json`);
4741
- if (!fs7.existsSync(filePath)) return;
4742
- const entry = JSON.parse(fs7.readFileSync(filePath, "utf-8"));
5120
+ const filePath = path10.join(this._logDir, today, `${reqId}.json`);
5121
+ if (!fs8.existsSync(filePath)) return;
5122
+ const entry = JSON.parse(fs8.readFileSync(filePath, "utf-8"));
4743
5123
  entry.elapsedMs = Date.now() - startTime;
4744
5124
  if (data.response) {
4745
5125
  entry.response = data.response;
@@ -4747,7 +5127,7 @@ var OpenAIProvider = class {
4747
5127
  if (data.error) {
4748
5128
  entry.error = data.error;
4749
5129
  }
4750
- fs7.writeFileSync(filePath, JSON.stringify(entry, null, 2), "utf-8");
5130
+ fs8.writeFileSync(filePath, JSON.stringify(entry, null, 2), "utf-8");
4751
5131
  } catch (e) {
4752
5132
  console.error(e);
4753
5133
  }
@@ -5105,12 +5485,12 @@ var OpenAIProvider = class {
5105
5485
 
5106
5486
  // src/tools/files.ts
5107
5487
  init_esm_shims();
5108
- import * as fs8 from "fs";
5109
- import * as path10 from "path";
5488
+ import * as fs9 from "fs";
5489
+ import * as path11 from "path";
5110
5490
  var DEFAULT_MAX_LINES = 2e3;
5111
5491
  var MAX_LINE_CHARS = 500;
5112
5492
  function makeReadFile(workspace) {
5113
- const root = path10.resolve(workspace);
5493
+ const root = path11.resolve(workspace);
5114
5494
  return function read_file(args) {
5115
5495
  const filePath = String(args.path ?? "");
5116
5496
  const startLine = Math.max(1, Number(args.start_line ?? 1));
@@ -5120,13 +5500,13 @@ function makeReadFile(workspace) {
5120
5500
  return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
5121
5501
  }
5122
5502
  const target = resolved.real;
5123
- if (!fs8.existsSync(target) || !fs8.statSync(target).isFile()) {
5503
+ if (!fs9.existsSync(target) || !fs9.statSync(target).isFile()) {
5124
5504
  return { error: `\u4E0D\u662F\u6587\u4EF6: ${filePath}` };
5125
5505
  }
5126
5506
  const selected = [];
5127
5507
  let total = 0;
5128
5508
  try {
5129
- const content = fs8.readFileSync(target, "utf-8");
5509
+ const content = fs9.readFileSync(target, "utf-8");
5130
5510
  const lines = content.split("\n");
5131
5511
  total = lines.length;
5132
5512
  for (let i = startLine - 1; i < Math.min(startLine - 1 + maxLines, lines.length); i++) {
@@ -5141,7 +5521,7 @@ function makeReadFile(workspace) {
5141
5521
  }
5142
5522
  const end = startLine + selected.length - 1;
5143
5523
  const result = {
5144
- path: path10.relative(resolved.root, target),
5524
+ path: path11.relative(resolved.root, target),
5145
5525
  start_line: startLine,
5146
5526
  end_line: end,
5147
5527
  total_lines: total,
@@ -5154,7 +5534,7 @@ function makeReadFile(workspace) {
5154
5534
  };
5155
5535
  }
5156
5536
  function makeWriteFile(workspace) {
5157
- const root = path10.resolve(workspace);
5537
+ const root = path11.resolve(workspace);
5158
5538
  return function write_file(args) {
5159
5539
  const filePath = String(args.path ?? "");
5160
5540
  const content = String(args.content ?? "");
@@ -5164,34 +5544,34 @@ function makeWriteFile(workspace) {
5164
5544
  return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
5165
5545
  }
5166
5546
  const target = resolved.real;
5167
- if (fs8.existsSync(target) && fs8.statSync(target).isDirectory()) {
5547
+ if (fs9.existsSync(target) && fs9.statSync(target).isDirectory()) {
5168
5548
  return { error: "\u8DEF\u5F84\u662F\u76EE\u5F55\uFF0C\u4E0D\u662F\u6587\u4EF6" };
5169
5549
  }
5170
- if (fs8.existsSync(target) && !overwrite) {
5550
+ if (fs9.existsSync(target) && !overwrite) {
5171
5551
  return { error: "\u6587\u4EF6\u5DF2\u5B58\u5728 (overwrite=false)" };
5172
5552
  }
5173
5553
  try {
5174
- fs8.mkdirSync(path10.dirname(target), { recursive: true });
5175
- fs8.writeFileSync(target, content, "utf-8");
5554
+ fs9.mkdirSync(path11.dirname(target), { recursive: true });
5555
+ fs9.writeFileSync(target, content, "utf-8");
5176
5556
  } catch (exc) {
5177
5557
  return { error: `\u5199\u5165\u5931\u8D25: ${String(exc)}` };
5178
5558
  }
5179
- return path10.relative(resolved.root, target);
5559
+ return path11.relative(resolved.root, target);
5180
5560
  };
5181
5561
  }
5182
5562
  function makeListFiles(workspace) {
5183
- const root = path10.resolve(workspace);
5563
+ const root = path11.resolve(workspace);
5184
5564
  return function list_files(args) {
5185
5565
  const dirPath = String(args.path ?? ".");
5186
5566
  const maxResults = Math.min(Number(args.max_results ?? 100), 100);
5187
5567
  const resolved = resolveWithinRoot(root, dirPath);
5188
5568
  if (!resolved) return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
5189
5569
  const target = resolved.real;
5190
- if (!fs8.existsSync(target) || !fs8.statSync(target).isDirectory()) {
5570
+ if (!fs9.existsSync(target) || !fs9.statSync(target).isDirectory()) {
5191
5571
  return { error: `\u4E0D\u662F\u76EE\u5F55: ${dirPath}` };
5192
5572
  }
5193
5573
  try {
5194
- const entries = fs8.readdirSync(target, { withFileTypes: true });
5574
+ const entries = fs9.readdirSync(target, { withFileTypes: true });
5195
5575
  const results = [];
5196
5576
  for (const e of entries) {
5197
5577
  if (results.length >= maxResults) break;
@@ -5200,14 +5580,14 @@ function makeListFiles(workspace) {
5200
5580
  results.push(e.isDirectory() ? `${e.name}/` : e.name);
5201
5581
  }
5202
5582
  results.sort();
5203
- return { path: path10.relative(resolved.root, target), entries: results.slice(0, maxResults) };
5583
+ return { path: path11.relative(resolved.root, target), entries: results.slice(0, maxResults) };
5204
5584
  } catch (exc) {
5205
5585
  return { error: `\u5217\u51FA\u6587\u4EF6\u5931\u8D25: ${String(exc)}` };
5206
5586
  }
5207
5587
  };
5208
5588
  }
5209
5589
  function makeReplaceInFile(workspace) {
5210
- const root = path10.resolve(workspace);
5590
+ const root = path11.resolve(workspace);
5211
5591
  return function replace_in_file(args) {
5212
5592
  const filePath = String(args.path ?? "");
5213
5593
  const oldStr = String(args.old ?? "");
@@ -5217,19 +5597,19 @@ function makeReplaceInFile(workspace) {
5217
5597
  const resolved = resolveWithinRoot(root, filePath);
5218
5598
  if (!resolved) return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
5219
5599
  const target = resolved.real;
5220
- if (!fs8.existsSync(target) || !fs8.statSync(target).isFile()) {
5600
+ if (!fs9.existsSync(target) || !fs9.statSync(target).isFile()) {
5221
5601
  return { error: `\u4E0D\u662F\u6587\u4EF6: ${filePath}` };
5222
5602
  }
5223
5603
  try {
5224
- const content = fs8.readFileSync(target, "utf-8");
5604
+ const content = fs9.readFileSync(target, "utf-8");
5225
5605
  const count = content.split(oldStr).length - 1;
5226
5606
  if (count !== expectedReplacements) {
5227
5607
  return { error: `\u671F\u671B ${expectedReplacements} \u5904\u66FF\u6362\uFF0C\u5B9E\u9645\u627E\u5230 ${count} \u5904: ${filePath}` };
5228
5608
  }
5229
5609
  const updated = content.replaceAll(oldStr, newStr);
5230
- fs8.writeFileSync(target, updated, "utf-8");
5610
+ fs9.writeFileSync(target, updated, "utf-8");
5231
5611
  return {
5232
- path: path10.relative(resolved.root, target),
5612
+ path: path11.relative(resolved.root, target),
5233
5613
  replacements: count,
5234
5614
  chars_before: content.length,
5235
5615
  chars_after: updated.length
@@ -5330,8 +5710,8 @@ function registerFileTools(registry, workspace) {
5330
5710
 
5331
5711
  // src/tools/shell.ts
5332
5712
  init_esm_shims();
5333
- import * as fs10 from "fs";
5334
- import * as path12 from "path";
5713
+ import * as fs11 from "fs";
5714
+ import * as path13 from "path";
5335
5715
 
5336
5716
  // src/tools/execAsync.ts
5337
5717
  init_esm_shims();
@@ -5496,15 +5876,15 @@ function execAsync(file, argv, opts = {}) {
5496
5876
 
5497
5877
  // src/tools/shellRuntime.ts
5498
5878
  init_esm_shims();
5499
- import * as fs9 from "fs";
5500
- import * as path11 from "path";
5879
+ import * as fs10 from "fs";
5880
+ import * as path12 from "path";
5501
5881
  var WINDOWS_SUFFIXES = [".exe", ".cmd", ".bat", ".com", ""];
5502
5882
  function pathApi(platform) {
5503
- return platform === "win32" ? path11.win32 : path11.posix;
5883
+ return platform === "win32" ? path12.win32 : path12.posix;
5504
5884
  }
5505
5885
  function defaultFileExists(p) {
5506
5886
  try {
5507
- return fs9.statSync(p).isFile();
5887
+ return fs10.statSync(p).isFile();
5508
5888
  } catch {
5509
5889
  return false;
5510
5890
  }
@@ -5535,7 +5915,7 @@ function kindFromBasename(base) {
5535
5915
  return "sh";
5536
5916
  }
5537
5917
  function busyboxArgs(file, command) {
5538
- const base = (file ? path11.win32.basename(file) : "busybox").toLowerCase().replace(/\.(exe|cmd|bat|com)$/, "");
5918
+ const base = (file ? path12.win32.basename(file) : "busybox").toLowerCase().replace(/\.(exe|cmd|bat|com)$/, "");
5539
5919
  return base === "busybox" ? ["sh", "-c", command] : ["-c", command];
5540
5920
  }
5541
5921
  function argsFor(kind, command) {
@@ -5802,7 +6182,7 @@ var MAX_STDERR = 5e3;
5802
6182
  var MAX_BUFFER = 1024 * 1024 * 5;
5803
6183
  var shellSessions = /* @__PURE__ */ new Map();
5804
6184
  function sessionFor(workspace) {
5805
- const key = path12.resolve(workspace);
6185
+ const key = path13.resolve(workspace);
5806
6186
  let s = shellSessions.get(key);
5807
6187
  if (!s) {
5808
6188
  s = { cwd: key };
@@ -5859,8 +6239,8 @@ function makeRunShell(workspace, spec, tasks, scope) {
5859
6239
  let toRun = command;
5860
6240
  if (target) {
5861
6241
  try {
5862
- const resolved = path12.resolve(session.cwd, target);
5863
- if (fs10.existsSync(resolved) && fs10.statSync(resolved).isDirectory()) {
6242
+ const resolved = path13.resolve(session.cwd, target);
6243
+ if (fs11.existsSync(resolved) && fs11.statSync(resolved).isDirectory()) {
5864
6244
  session.cwd = resolved;
5865
6245
  }
5866
6246
  } catch {
@@ -5995,8 +6375,8 @@ init_esm_shims();
5995
6375
 
5996
6376
  // src/tools/nodeGrep.ts
5997
6377
  init_esm_shims();
5998
- import * as fs11 from "fs";
5999
- import * as path13 from "path";
6378
+ import * as fs12 from "fs";
6379
+ import * as path14 from "path";
6000
6380
  var SKIP_DIRS = /* @__PURE__ */ new Set([
6001
6381
  ".git",
6002
6382
  "node_modules",
@@ -6021,27 +6401,27 @@ function nodeGrep(opts) {
6021
6401
  } catch (err) {
6022
6402
  throw new Error(`\u65E0\u6548\u7684\u6B63\u5219\u8868\u8FBE\u5F0F: ${opts.pattern}\uFF08${String(err)}\uFF09`);
6023
6403
  }
6024
- const target = path13.resolve(opts.workspace, opts.target);
6025
- if (!fs11.existsSync(target)) {
6404
+ const target = path14.resolve(opts.workspace, opts.target);
6405
+ if (!fs12.existsSync(target)) {
6026
6406
  throw new Error(`\u641C\u7D22\u8DEF\u5F84\u4E0D\u5B58\u5728: ${opts.target}`);
6027
6407
  }
6028
6408
  const matches = [];
6029
6409
  const scanFile = (file) => {
6030
6410
  let stat2;
6031
6411
  try {
6032
- stat2 = fs11.statSync(file);
6412
+ stat2 = fs12.statSync(file);
6033
6413
  } catch {
6034
6414
  return false;
6035
6415
  }
6036
6416
  if (!stat2.isFile() || stat2.size > maxFileBytes) return false;
6037
6417
  let buf;
6038
6418
  try {
6039
- buf = fs11.readFileSync(file);
6419
+ buf = fs12.readFileSync(file);
6040
6420
  } catch {
6041
6421
  return false;
6042
6422
  }
6043
6423
  if (looksBinary(buf)) return false;
6044
- const rel = path13.relative(opts.workspace, file) || path13.basename(file);
6424
+ const rel = path14.relative(opts.workspace, file) || path14.basename(file);
6045
6425
  const lines = buf.toString("utf-8").split("\n");
6046
6426
  for (let i = 0; i < lines.length; i++) {
6047
6427
  const raw = lines[i].replace(/\r$/, "");
@@ -6052,7 +6432,7 @@ function nodeGrep(opts) {
6052
6432
  }
6053
6433
  return false;
6054
6434
  };
6055
- const stat = fs11.statSync(target);
6435
+ const stat = fs12.statSync(target);
6056
6436
  if (stat.isFile()) {
6057
6437
  scanFile(target);
6058
6438
  return matches;
@@ -6062,12 +6442,12 @@ function nodeGrep(opts) {
6062
6442
  const dir = stack.pop();
6063
6443
  let entries;
6064
6444
  try {
6065
- entries = fs11.readdirSync(dir, { withFileTypes: true });
6445
+ entries = fs12.readdirSync(dir, { withFileTypes: true });
6066
6446
  } catch {
6067
6447
  continue;
6068
6448
  }
6069
6449
  for (const entry of entries) {
6070
- const full = path13.join(dir, entry.name);
6450
+ const full = path14.join(dir, entry.name);
6071
6451
  if (entry.isDirectory()) {
6072
6452
  if (!SKIP_DIRS.has(entry.name)) stack.push(full);
6073
6453
  } else if (entry.isFile()) {
@@ -6080,22 +6460,22 @@ function nodeGrep(opts) {
6080
6460
 
6081
6461
  // src/tools/ripgrep.ts
6082
6462
  init_esm_shims();
6083
- import * as fs12 from "fs";
6463
+ import * as fs13 from "fs";
6084
6464
  import * as os2 from "os";
6085
- import * as path14 from "path";
6465
+ import * as path15 from "path";
6086
6466
  import { createRequire } from "module";
6087
6467
  var cached2;
6088
6468
  function extractBundledRipgrep() {
6089
6469
  try {
6090
- const req2 = typeof __require === "function" ? __require : createRequire(path14.join(process.cwd(), "__zqagent__.js"));
6470
+ const req2 = typeof __require === "function" ? __require : createRequire(path15.join(process.cwd(), "__zqagent__.js"));
6091
6471
  const sea = req2("node:sea");
6092
6472
  if (!sea?.isSea?.() || typeof sea.getAsset !== "function") return null;
6093
6473
  const asset = sea.getAsset("rg");
6094
6474
  if (!asset) return null;
6095
- const dir = fs12.mkdtempSync(path14.join(os2.tmpdir(), "zqagent-rg-"));
6096
- const file = path14.join(dir, process.platform === "win32" ? "rg.exe" : "rg");
6097
- fs12.writeFileSync(file, Buffer.from(asset));
6098
- fs12.chmodSync(file, 493);
6475
+ const dir = fs13.mkdtempSync(path15.join(os2.tmpdir(), "zqagent-rg-"));
6476
+ const file = path15.join(dir, process.platform === "win32" ? "rg.exe" : "rg");
6477
+ fs13.writeFileSync(file, Buffer.from(asset));
6478
+ fs13.chmodSync(file, 493);
6099
6479
  return file;
6100
6480
  } catch {
6101
6481
  return null;
@@ -6104,7 +6484,7 @@ function extractBundledRipgrep() {
6104
6484
  function resolveRipgrep() {
6105
6485
  if (cached2 !== void 0) return cached2;
6106
6486
  const explicit = (process.env.ZQAGENT_RG ?? "").trim();
6107
- if (explicit && fs12.existsSync(explicit)) {
6487
+ if (explicit && fs13.existsSync(explicit)) {
6108
6488
  cached2 = explicit;
6109
6489
  return cached2;
6110
6490
  }
@@ -6118,7 +6498,7 @@ function resolveRipgrep() {
6118
6498
  env: process.env,
6119
6499
  fileExists: (p) => {
6120
6500
  try {
6121
- return fs12.statSync(p).isFile();
6501
+ return fs13.statSync(p).isFile();
6122
6502
  } catch {
6123
6503
  return false;
6124
6504
  }
@@ -6667,8 +7047,8 @@ function registerWebTools(registry) {
6667
7047
 
6668
7048
  // src/tools/git.ts
6669
7049
  init_esm_shims();
6670
- import * as fs13 from "fs";
6671
- import * as path15 from "path";
7050
+ import * as fs14 from "fs";
7051
+ import * as path16 from "path";
6672
7052
  async function runGit(repoDir, args, timeout = 15e3) {
6673
7053
  const res = await execAsync("git", ["-c", "core.quotepath=false", ...args], {
6674
7054
  cwd: repoDir,
@@ -6685,9 +7065,9 @@ async function runGit(repoDir, args, timeout = 15e3) {
6685
7065
  async function resolveRepoRoot(workspace, pathArg) {
6686
7066
  let dir = workspace;
6687
7067
  if (pathArg) {
6688
- const abs = path15.resolve(workspace, pathArg);
7068
+ const abs = path16.resolve(workspace, pathArg);
6689
7069
  try {
6690
- dir = fs13.statSync(abs).isDirectory() ? abs : path15.dirname(abs);
7070
+ dir = fs14.statSync(abs).isDirectory() ? abs : path16.dirname(abs);
6691
7071
  } catch {
6692
7072
  dir = abs;
6693
7073
  }
@@ -6700,24 +7080,24 @@ function repoDirArg(workspace, raw) {
6700
7080
  if (typeof raw !== "string") return void 0;
6701
7081
  const v = raw.trim();
6702
7082
  if (!v) return void 0;
6703
- const base = path15.resolve(workspace);
6704
- const abs = path15.resolve(base, v);
6705
- const rel = path15.relative(base, abs);
6706
- if (rel.startsWith("..") || path15.isAbsolute(rel)) return void 0;
7083
+ const base = path16.resolve(workspace);
7084
+ const abs = path16.resolve(base, v);
7085
+ const rel = path16.relative(base, abs);
7086
+ if (rel.startsWith("..") || path16.isAbsolute(rel)) return void 0;
6707
7087
  return abs;
6708
7088
  }
6709
7089
  function toPathspec(repoRoot, workspace, pathArg) {
6710
7090
  if (!pathArg) return null;
6711
- const abs = path15.resolve(workspace, pathArg);
6712
- let rel = path15.relative(repoRoot, abs);
7091
+ const abs = path16.resolve(workspace, pathArg);
7092
+ let rel = path16.relative(repoRoot, abs);
6713
7093
  if (rel === "") return ".";
6714
- if (rel.startsWith("..") || path15.isAbsolute(rel)) {
7094
+ if (rel.startsWith("..") || path16.isAbsolute(rel)) {
6715
7095
  try {
6716
- const realRepo = fs13.realpathSync(repoRoot);
6717
- const realAbs = fs13.realpathSync(abs);
6718
- const rel2 = path15.relative(realRepo, realAbs);
7096
+ const realRepo = fs14.realpathSync(repoRoot);
7097
+ const realAbs = fs14.realpathSync(abs);
7098
+ const rel2 = path16.relative(realRepo, realAbs);
6719
7099
  if (rel2 === "") return ".";
6720
- if (!rel2.startsWith("..") && !path15.isAbsolute(rel2)) return rel2;
7100
+ if (!rel2.startsWith("..") && !path16.isAbsolute(rel2)) return rel2;
6721
7101
  } catch {
6722
7102
  }
6723
7103
  return null;
@@ -7143,18 +7523,18 @@ var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".hg", ".s
7143
7523
  var SCAN_DEFAULT_DEPTH = 3;
7144
7524
  var SCAN_MAX_DIRS = 2e3;
7145
7525
  function readRepoHead(repoDir) {
7146
- const dotGit = path15.join(repoDir, ".git");
7526
+ const dotGit = path16.join(repoDir, ".git");
7147
7527
  try {
7148
7528
  let headFile;
7149
- if (fs13.statSync(dotGit).isDirectory()) {
7150
- headFile = path15.join(dotGit, "HEAD");
7529
+ if (fs14.statSync(dotGit).isDirectory()) {
7530
+ headFile = path16.join(dotGit, "HEAD");
7151
7531
  } else {
7152
- const raw = fs13.readFileSync(dotGit, "utf-8");
7532
+ const raw = fs14.readFileSync(dotGit, "utf-8");
7153
7533
  const m = /^gitdir:\s*(.+)$/m.exec(raw);
7154
7534
  if (!m) return {};
7155
- headFile = path15.join(path15.resolve(repoDir, m[1].trim()), "HEAD");
7535
+ headFile = path16.join(path16.resolve(repoDir, m[1].trim()), "HEAD");
7156
7536
  }
7157
- const head = fs13.readFileSync(headFile, "utf-8").trim();
7537
+ const head = fs14.readFileSync(headFile, "utf-8").trim();
7158
7538
  const prefix = "ref: refs/heads/";
7159
7539
  if (head.startsWith(prefix)) return { branch: head.slice(prefix.length), detached: false };
7160
7540
  if (/^[0-9a-f]{7,40}$/i.test(head)) return { branch: head.slice(0, 7), detached: true };
@@ -7163,7 +7543,7 @@ function readRepoHead(repoDir) {
7163
7543
  return {};
7164
7544
  }
7165
7545
  function scanGitRepos(workspace, depth = SCAN_DEFAULT_DEPTH) {
7166
- const root = path15.resolve(workspace);
7546
+ const root = path16.resolve(workspace);
7167
7547
  const maxDepth = Number.isFinite(depth) ? Math.min(Math.max(Math.floor(depth), 0), 6) : SCAN_DEFAULT_DEPTH;
7168
7548
  const repos = [];
7169
7549
  const stack = [{ dir: root, depth: 0 }];
@@ -7176,24 +7556,24 @@ function scanGitRepos(workspace, depth = SCAN_DEFAULT_DEPTH) {
7176
7556
  truncated = true;
7177
7557
  break;
7178
7558
  }
7179
- const rel = path15.relative(root, dir).split(path15.sep).join("/");
7180
- if (fs13.existsSync(path15.join(dir, ".git"))) {
7559
+ const rel = path16.relative(root, dir).split(path16.sep).join("/");
7560
+ if (fs14.existsSync(path16.join(dir, ".git"))) {
7181
7561
  repos.push({
7182
7562
  path: rel,
7183
- name: rel ? path15.basename(dir) : path15.basename(root),
7563
+ name: rel ? path16.basename(dir) : path16.basename(root),
7184
7564
  ...readRepoHead(dir)
7185
7565
  });
7186
7566
  }
7187
7567
  if (d >= maxDepth) continue;
7188
7568
  let entries;
7189
7569
  try {
7190
- entries = fs13.readdirSync(dir, { withFileTypes: true });
7570
+ entries = fs14.readdirSync(dir, { withFileTypes: true });
7191
7571
  } catch {
7192
7572
  continue;
7193
7573
  }
7194
7574
  for (const e of entries) {
7195
7575
  if (!e.isDirectory() || SCAN_SKIP_DIRS.has(e.name)) continue;
7196
- stack.push({ dir: path15.join(dir, e.name), depth: d + 1 });
7576
+ stack.push({ dir: path16.join(dir, e.name), depth: d + 1 });
7197
7577
  }
7198
7578
  }
7199
7579
  repos.sort(
@@ -7203,9 +7583,9 @@ function scanGitRepos(workspace, depth = SCAN_DEFAULT_DEPTH) {
7203
7583
  }
7204
7584
  function makeGitRepos(workspace) {
7205
7585
  return async function git_repos(args) {
7206
- const root = path15.resolve(workspace);
7586
+ const root = path16.resolve(workspace);
7207
7587
  try {
7208
- if (!fs13.statSync(root).isDirectory()) return { root, repos: [], error: "\u5DE5\u4F5C\u533A\u4E0D\u662F\u76EE\u5F55" };
7588
+ if (!fs14.statSync(root).isDirectory()) return { root, repos: [], error: "\u5DE5\u4F5C\u533A\u4E0D\u662F\u76EE\u5F55" };
7209
7589
  } catch {
7210
7590
  return { root, repos: [], error: "\u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728" };
7211
7591
  }
@@ -7217,10 +7597,10 @@ function makeGitRepos(workspace) {
7217
7597
 
7218
7598
  // src/tools/diff.ts
7219
7599
  init_esm_shims();
7220
- import * as fs14 from "fs";
7221
- import * as path16 from "path";
7600
+ import * as fs15 from "fs";
7601
+ import * as path17 from "path";
7222
7602
  function makeApplyPatch(workspace) {
7223
- const root = path16.resolve(workspace);
7603
+ const root = path17.resolve(workspace);
7224
7604
  return function apply_patch(args) {
7225
7605
  const patch = String(args.patch ?? "");
7226
7606
  if (!patch.trim()) return { error: "\u8865\u4E01\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A" };
@@ -7259,12 +7639,12 @@ function makeApplyPatch(workspace) {
7259
7639
  contentLines.push(current.slice(1));
7260
7640
  i++;
7261
7641
  }
7262
- if (fs14.existsSync(target)) {
7642
+ if (fs15.existsSync(target)) {
7263
7643
  return { error: `\u6587\u4EF6\u5DF2\u5B58\u5728: ${filePath}` };
7264
7644
  }
7265
- fs14.mkdirSync(path16.dirname(target), { recursive: true });
7266
- fs14.writeFileSync(target, contentLines.join("\n") + "\n", "utf-8");
7267
- const rel = path16.relative(resolved.root, target);
7645
+ fs15.mkdirSync(path17.dirname(target), { recursive: true });
7646
+ fs15.writeFileSync(target, contentLines.join("\n") + "\n", "utf-8");
7647
+ const rel = path17.relative(resolved.root, target);
7268
7648
  changedFiles.push(rel);
7269
7649
  addedFiles.push(rel);
7270
7650
  fileCount++;
@@ -7277,11 +7657,11 @@ function makeApplyPatch(workspace) {
7277
7657
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
7278
7658
  }
7279
7659
  const target = entry.real;
7280
- if (!fs14.existsSync(target)) {
7660
+ if (!fs15.existsSync(target)) {
7281
7661
  return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
7282
7662
  }
7283
- fs14.unlinkSync(target);
7284
- deletedFiles.push(path16.relative(entry.root, target));
7663
+ fs15.unlinkSync(target);
7664
+ deletedFiles.push(path17.relative(entry.root, target));
7285
7665
  fileCount++;
7286
7666
  i++;
7287
7667
  continue;
@@ -7317,10 +7697,10 @@ function makeApplyPatch(workspace) {
7317
7697
  if (!chunkLines.length) {
7318
7698
  return { error: `\u8865\u4E01\u66F4\u65B0\u6CA1\u6709 hunks: ${filePath}` };
7319
7699
  }
7320
- if (!fs14.existsSync(target)) {
7700
+ if (!fs15.existsSync(target)) {
7321
7701
  return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
7322
7702
  }
7323
- const oldLines = fs14.readFileSync(target, "utf-8").split("\n");
7703
+ const oldLines = fs15.readFileSync(target, "utf-8").split("\n");
7324
7704
  const newLines = applyPatchHunks(oldLines, chunkLines, filePath);
7325
7705
  let finalResolved = resolved;
7326
7706
  if (moveTo) {
@@ -7331,20 +7711,20 @@ function makeApplyPatch(workspace) {
7331
7711
  finalResolved = moved;
7332
7712
  }
7333
7713
  const finalTarget = finalResolved.real;
7334
- if (moveTo && fs14.existsSync(finalTarget) && finalTarget !== target) {
7714
+ if (moveTo && fs15.existsSync(finalTarget) && finalTarget !== target) {
7335
7715
  return { error: `\u6587\u4EF6\u5DF2\u5B58\u5728: ${moveTo}` };
7336
7716
  }
7337
- fs14.mkdirSync(path16.dirname(finalTarget), { recursive: true });
7338
- fs14.writeFileSync(finalTarget, newLines.join("\n"), "utf-8");
7717
+ fs15.mkdirSync(path17.dirname(finalTarget), { recursive: true });
7718
+ fs15.writeFileSync(finalTarget, newLines.join("\n"), "utf-8");
7339
7719
  if (moveTo && finalTarget !== target) {
7340
7720
  const entry = resolveEntryWithinRoot(root, filePath);
7341
7721
  if (!entry) {
7342
7722
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
7343
7723
  }
7344
- fs14.unlinkSync(entry.real);
7345
- deletedFiles.push(path16.relative(resolved.root, target));
7724
+ fs15.unlinkSync(entry.real);
7725
+ deletedFiles.push(path17.relative(resolved.root, target));
7346
7726
  }
7347
- changedFiles.push(path16.relative(finalResolved.root, finalTarget));
7727
+ changedFiles.push(path17.relative(finalResolved.root, finalTarget));
7348
7728
  fileCount++;
7349
7729
  continue;
7350
7730
  }
@@ -7356,18 +7736,18 @@ function makeApplyPatch(workspace) {
7356
7736
  return { changed_files: changedFiles, added_files: addedFiles, deleted_files: deletedFiles, file_count: fileCount };
7357
7737
  };
7358
7738
  }
7359
- function applyPatchHunks(oldLines, chunks, path34) {
7739
+ function applyPatchHunks(oldLines, chunks, path36) {
7360
7740
  let newLines = [...oldLines];
7361
7741
  let cursor = 0;
7362
7742
  for (const chunk of chunks) {
7363
7743
  const oldBlock = chunk.filter((l) => l[0] === " " || l[0] === "-").map((l) => l.slice(1));
7364
7744
  const newBlock = chunk.filter((l) => l[0] === " " || l[0] === "+").map((l) => l.slice(1));
7365
7745
  if (!oldBlock.length) {
7366
- throw new Error(`\u8865\u4E01 hunk \u5FC5\u987B\u5305\u542B\u4E0A\u4E0B\u6587\u6216\u5220\u9664\u884C: ${path34}`);
7746
+ throw new Error(`\u8865\u4E01 hunk \u5FC5\u987B\u5305\u542B\u4E0A\u4E0B\u6587\u6216\u5220\u9664\u884C: ${path36}`);
7367
7747
  }
7368
7748
  const matchStart = findBlockMatch(newLines, oldBlock, cursor);
7369
7749
  if (matchStart === -1) {
7370
- throw new Error(`\u8865\u4E01\u4E0A\u4E0B\u6587\u4E0D\u5339\u914D: ${path34}`);
7750
+ throw new Error(`\u8865\u4E01\u4E0A\u4E0B\u6587\u4E0D\u5339\u914D: ${path36}`);
7371
7751
  }
7372
7752
  const matchEnd = matchStart + oldBlock.length;
7373
7753
  newLines.splice(matchStart, oldBlock.length, ...newBlock);
@@ -7384,7 +7764,7 @@ function findBlockMatch(lines, block, start) {
7384
7764
  return -1;
7385
7765
  }
7386
7766
  function makeApplyUnifiedDiff(workspace) {
7387
- const root = path16.resolve(workspace);
7767
+ const root = path17.resolve(workspace);
7388
7768
  return function apply_unified_diff(args) {
7389
7769
  const diff = String(args.diff ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
7390
7770
  if (!diff.trim()) return { error: "diff \u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A" };
@@ -7403,26 +7783,26 @@ function makeApplyUnifiedDiff(workspace) {
7403
7783
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${targetPath}` };
7404
7784
  }
7405
7785
  const target = resolved.real;
7406
- const oldLines = oldIsDevNull ? [] : fs14.existsSync(target) ? fs14.readFileSync(target, "utf-8").split("\n") : [];
7786
+ const oldLines = oldIsDevNull ? [] : fs15.existsSync(target) ? fs15.readFileSync(target, "utf-8").split("\n") : [];
7407
7787
  const newLines = applyDiffHunks(oldLines, patch.hunks, targetPath);
7408
7788
  hunkCount += patch.hunks.length;
7409
7789
  if (newIsDevNull) {
7410
- if (fs14.existsSync(target)) {
7411
- if (!fs14.statSync(target).isFile()) {
7790
+ if (fs15.existsSync(target)) {
7791
+ if (!fs15.statSync(target).isFile()) {
7412
7792
  return { error: `\u8DEF\u5F84\u4E0D\u662F\u6587\u4EF6: ${targetPath}` };
7413
7793
  }
7414
7794
  const entry = resolveEntryWithinRoot(root, targetPath);
7415
7795
  if (!entry) {
7416
7796
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${targetPath}` };
7417
7797
  }
7418
- fs14.unlinkSync(entry.real);
7798
+ fs15.unlinkSync(entry.real);
7419
7799
  }
7420
- deletedFiles.push(path16.relative(resolved.root, target));
7800
+ deletedFiles.push(path17.relative(resolved.root, target));
7421
7801
  continue;
7422
7802
  }
7423
- fs14.mkdirSync(path16.dirname(target), { recursive: true });
7424
- fs14.writeFileSync(target, newLines.join("\n"), "utf-8");
7425
- const rel = path16.relative(resolved.root, target);
7803
+ fs15.mkdirSync(path17.dirname(target), { recursive: true });
7804
+ fs15.writeFileSync(target, newLines.join("\n"), "utf-8");
7805
+ const rel = path17.relative(resolved.root, target);
7426
7806
  changedFiles.push(rel);
7427
7807
  if (oldIsDevNull) addedFiles.push(rel);
7428
7808
  }
@@ -7823,7 +8203,8 @@ function buildExplorerEngine(opts) {
7823
8203
  model: opts.model,
7824
8204
  instructions: EXPLORER_INSTRUCTIONS,
7825
8205
  maxIterations: parseMaxIterations(process.env.EXPLORE_MAX_ITERATIONS, 30),
7826
- hook: opts.hook
8206
+ hook: opts.hook,
8207
+ toolResultDir: opts.toolResultDir
7827
8208
  });
7828
8209
  }
7829
8210
  function makeExploreTool(opts) {
@@ -7835,7 +8216,8 @@ function makeExploreTool(opts) {
7835
8216
  provider: opts.provider,
7836
8217
  model: opts.model,
7837
8218
  readonlyRegistrars: opts.readonlyRegistrars,
7838
- hook: opts.hookFactory?.()
8219
+ hook: opts.hookFactory?.(),
8220
+ toolResultDir: opts.toolResultDir
7839
8221
  });
7840
8222
  let report = "";
7841
8223
  let status = "unknown";
@@ -7896,14 +8278,14 @@ function registerExploreTool(registry, opts) {
7896
8278
 
7897
8279
  // src/tools/media.ts
7898
8280
  init_esm_shims();
7899
- import * as fs16 from "fs";
7900
- import * as path18 from "path";
8281
+ import * as fs17 from "fs";
8282
+ import * as path19 from "path";
7901
8283
 
7902
8284
  // src/tools/ffmpegRuntime.ts
7903
8285
  init_esm_shims();
7904
- import * as fs15 from "fs";
8286
+ import * as fs16 from "fs";
7905
8287
  import * as os3 from "os";
7906
- import * as path17 from "path";
8288
+ import * as path18 from "path";
7907
8289
  import { createRequire as createRequire2 } from "module";
7908
8290
  import { createHash } from "crypto";
7909
8291
  import { pipeline } from "stream/promises";
@@ -7926,29 +8308,29 @@ if (PROXY_ENV2) {
7926
8308
  }
7927
8309
  var DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
7928
8310
  function agentBinDir(workspace) {
7929
- return path17.join(path17.resolve(workspace), ".agent", "bin");
8311
+ return path18.join(path18.resolve(workspace), ".agent", "bin");
7930
8312
  }
7931
8313
  function exeName(name, platform) {
7932
8314
  return platform === "win32" ? `${name}.exe` : name;
7933
8315
  }
7934
8316
  function defaultFileExists2(p) {
7935
8317
  try {
7936
- return fs15.statSync(p).isFile();
8318
+ return fs16.statSync(p).isFile();
7937
8319
  } catch {
7938
8320
  return false;
7939
8321
  }
7940
8322
  }
7941
8323
  function extractBundled(name, platform) {
7942
8324
  try {
7943
- const req2 = typeof __require === "function" ? __require : createRequire2(path17.join(process.cwd(), "__zqagent__.js"));
8325
+ const req2 = typeof __require === "function" ? __require : createRequire2(path18.join(process.cwd(), "__zqagent__.js"));
7944
8326
  const sea = req2("node:sea");
7945
8327
  if (!sea?.isSea?.() || typeof sea.getAsset !== "function") return null;
7946
8328
  const asset = sea.getAsset(name);
7947
8329
  if (!asset) return null;
7948
- const dir = fs15.mkdtempSync(path17.join(os3.tmpdir(), `zqagent-${name}-`));
7949
- const file = path17.join(dir, exeName(name, platform));
7950
- fs15.writeFileSync(file, Buffer.from(asset));
7951
- fs15.chmodSync(file, 493);
8330
+ const dir = fs16.mkdtempSync(path18.join(os3.tmpdir(), `zqagent-${name}-`));
8331
+ const file = path18.join(dir, exeName(name, platform));
8332
+ fs16.writeFileSync(file, Buffer.from(asset));
8333
+ fs16.chmodSync(file, 493);
7952
8334
  return file;
7953
8335
  } catch {
7954
8336
  return null;
@@ -7958,7 +8340,7 @@ function resolveOne(name, workspace, o) {
7958
8340
  const envKey = name === "ffmpeg" ? ENV_FFMPEG : ENV_FFPROBE;
7959
8341
  const explicit = (o.env[envKey] ?? "").trim();
7960
8342
  if (explicit && o.fileExists(explicit)) return { path: explicit, source: "env" };
7961
- const local = path17.join(agentBinDir(workspace), exeName(name, o.platform));
8343
+ const local = path18.join(agentBinDir(workspace), exeName(name, o.platform));
7962
8344
  if (o.fileExists(local)) return { path: local, source: "agent-bin" };
7963
8345
  const bundled = extractBundled(name, o.platform);
7964
8346
  if (bundled) return { path: bundled, source: "bundled" };
@@ -7972,7 +8354,7 @@ function resolveOne(name, workspace, o) {
7972
8354
  var cache = /* @__PURE__ */ new Map();
7973
8355
  function resolveMedia(workspace, opts = {}) {
7974
8356
  const platform = opts.platform ?? process.platform;
7975
- const key = `${platform}:${path17.resolve(workspace)}`;
8357
+ const key = `${platform}:${path18.resolve(workspace)}`;
7976
8358
  const hit = cache.get(key);
7977
8359
  if (hit) return hit;
7978
8360
  const ctx = {
@@ -8078,7 +8460,7 @@ function parseChecksumFile(text, filename) {
8078
8460
  if (!m) continue;
8079
8461
  const hash = m[1].toLowerCase();
8080
8462
  const name = m[2]?.trim() ?? "";
8081
- if (!name || name === filename || path17.basename(name) === filename) return hash;
8463
+ if (!name || name === filename || path18.basename(name) === filename) return hash;
8082
8464
  }
8083
8465
  const single = lines[0]?.trim();
8084
8466
  if (lines.length === 1 && single && /^[0-9a-f]{32,128}$/i.test(single)) {
@@ -8088,7 +8470,7 @@ function parseChecksumFile(text, filename) {
8088
8470
  }
8089
8471
  async function computeChecksum(file, algo) {
8090
8472
  const hash = createHash(algo);
8091
- await pipeline(fs15.createReadStream(file), hash);
8473
+ await pipeline(fs16.createReadStream(file), hash);
8092
8474
  return hash.digest("hex");
8093
8475
  }
8094
8476
  function fetchInit() {
@@ -8103,7 +8485,7 @@ async function downloadToFile(url, dest) {
8103
8485
  const resp = await nodeFetch3(url, fetchInit());
8104
8486
  if (!resp.ok) throw new Error(`HTTP ${resp.status} ${resp.statusText}`);
8105
8487
  if (!resp.body) throw new Error("\u54CD\u5E94\u6CA1\u6709\u5185\u5BB9");
8106
- await pipeline(resp.body, fs15.createWriteStream(dest));
8488
+ await pipeline(resp.body, fs16.createWriteStream(dest));
8107
8489
  }
8108
8490
  async function resolveComponentUrl(c) {
8109
8491
  if (c.url) return c.url;
@@ -8120,12 +8502,12 @@ function findBinaryInTree(dir, names) {
8120
8502
  const cur = stack.pop();
8121
8503
  let entries;
8122
8504
  try {
8123
- entries = fs15.readdirSync(cur, { withFileTypes: true });
8505
+ entries = fs16.readdirSync(cur, { withFileTypes: true });
8124
8506
  } catch {
8125
8507
  continue;
8126
8508
  }
8127
8509
  for (const e of entries) {
8128
- const full = path17.join(cur, e.name);
8510
+ const full = path18.join(cur, e.name);
8129
8511
  if (e.isDirectory()) {
8130
8512
  stack.push(full);
8131
8513
  } else if (names.includes(e.name)) {
@@ -8136,7 +8518,7 @@ function findBinaryInTree(dir, names) {
8136
8518
  return null;
8137
8519
  }
8138
8520
  async function extractArchive(archive, dest, kind, platform) {
8139
- fs15.mkdirSync(dest, { recursive: true });
8521
+ fs16.mkdirSync(dest, { recursive: true });
8140
8522
  let res;
8141
8523
  if (kind === "tar.xz") {
8142
8524
  res = await execAsync("tar", ["-xJf", archive, "-C", dest]);
@@ -8172,7 +8554,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
8172
8554
  error: `\u6682\u4E0D\u652F\u6301\u81EA\u52A8\u4E0B\u8F7D\u7684\u5E73\u53F0: ${platform}/${arch}`
8173
8555
  };
8174
8556
  }
8175
- const tmp = fs15.mkdtempSync(path17.join(os3.tmpdir(), "zqagent-ffmpeg-"));
8557
+ const tmp = fs16.mkdtempSync(path18.join(os3.tmpdir(), "zqagent-ffmpeg-"));
8176
8558
  const binDir = agentBinDir(workspace);
8177
8559
  const installed = {};
8178
8560
  try {
@@ -8186,7 +8568,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
8186
8568
  }
8187
8569
  let archive = fetched.get(url);
8188
8570
  if (!archive) {
8189
- const archivePath = path17.join(tmp, path17.basename(new URL(url).pathname) || `${c.name}.${c.archive}`);
8571
+ const archivePath = path18.join(tmp, path18.basename(new URL(url).pathname) || `${c.name}.${c.archive}`);
8190
8572
  log(`\u4E0B\u8F7D ${url}`);
8191
8573
  try {
8192
8574
  await downloadToFile(url, archivePath);
@@ -8196,7 +8578,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
8196
8578
  if (c.checksumUrl) {
8197
8579
  const algo = c.checksumAlgo ?? "sha256";
8198
8580
  try {
8199
- const expected = parseChecksumFile(await fetchText(c.checksumUrl), path17.basename(archivePath));
8581
+ const expected = parseChecksumFile(await fetchText(c.checksumUrl), path18.basename(archivePath));
8200
8582
  if (!expected) {
8201
8583
  notes.push(`\u672A\u80FD\u4ECE ${c.checksumUrl} \u89E3\u6790\u51FA\u6821\u9A8C\u548C\uFF0C\u5DF2\u8DF3\u8FC7\u6821\u9A8C`);
8202
8584
  } else {
@@ -8213,24 +8595,24 @@ async function downloadFfmpeg(workspace, opts = {}) {
8213
8595
  notes.push(`\u6821\u9A8C\u548C\u83B7\u53D6\u5931\u8D25 (${c.checksumUrl}): ${String(e instanceof Error ? e.message : e)}`);
8214
8596
  }
8215
8597
  } else {
8216
- notes.push(`${path17.basename(archivePath)} \u672A\u63D0\u4F9B\u6821\u9A8C\u548C\uFF08\u4EC5 HTTPS \u4F20\u8F93\u4FDD\u62A4\uFF09`);
8598
+ notes.push(`${path18.basename(archivePath)} \u672A\u63D0\u4F9B\u6821\u9A8C\u548C\uFF08\u4EC5 HTTPS \u4F20\u8F93\u4FDD\u62A4\uFF09`);
8217
8599
  }
8218
8600
  archive = { file: archivePath, kind: c.archive };
8219
8601
  fetched.set(url, archive);
8220
8602
  }
8221
- const extractDir = fs15.mkdtempSync(path17.join(tmp, "x-"));
8603
+ const extractDir = fs16.mkdtempSync(path18.join(tmp, "x-"));
8222
8604
  const err = await extractArchive(archive.file, extractDir, archive.kind, platform);
8223
8605
  if (err) return { ok: false, error: err };
8224
8606
  const found = findBinaryInTree(extractDir, [exeName(c.name, platform), c.name]);
8225
8607
  if (!found) {
8226
- return { ok: false, error: `\u5F52\u6863\u5185\u672A\u627E\u5230 ${c.name}: ${path17.basename(archive.file)}` };
8608
+ return { ok: false, error: `\u5F52\u6863\u5185\u672A\u627E\u5230 ${c.name}: ${path18.basename(archive.file)}` };
8227
8609
  }
8228
- fs15.mkdirSync(binDir, { recursive: true });
8229
- const target = path17.join(binDir, exeName(c.name, platform));
8610
+ fs16.mkdirSync(binDir, { recursive: true });
8611
+ const target = path18.join(binDir, exeName(c.name, platform));
8230
8612
  const staging = `${target}.tmp-${process.pid}`;
8231
- fs15.copyFileSync(found, staging);
8232
- fs15.chmodSync(staging, 493);
8233
- fs15.renameSync(staging, target);
8613
+ fs16.copyFileSync(found, staging);
8614
+ fs16.chmodSync(staging, 493);
8615
+ fs16.renameSync(staging, target);
8234
8616
  installed[c.name] = target;
8235
8617
  }
8236
8618
  resetFfmpegCache();
@@ -8239,7 +8621,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
8239
8621
  } catch (e) {
8240
8622
  return { ok: false, error: `\u5B89\u88C5\u5931\u8D25: ${String(e instanceof Error ? e.message : e)}` };
8241
8623
  } finally {
8242
- fs15.rmSync(tmp, { recursive: true, force: true });
8624
+ fs16.rmSync(tmp, { recursive: true, force: true });
8243
8625
  }
8244
8626
  }
8245
8627
  function canAutoDownload(platform, arch) {
@@ -8504,13 +8886,13 @@ function resolveTarget(workspace, value) {
8504
8886
  return { abs: resolved.real, root: resolved.root };
8505
8887
  }
8506
8888
  function relOut(workspace, abs) {
8507
- return path18.relative(workspace, abs) || abs;
8889
+ return path19.relative(workspace, abs) || abs;
8508
8890
  }
8509
8891
  function registerMediaTool(registry, workspace, opts = {}) {
8510
8892
  const platform = opts.platform ?? process.platform;
8511
8893
  const arch = process.arch;
8512
8894
  const media = async (args) => {
8513
- const root = path18.resolve(workspace);
8895
+ const root = path19.resolve(workspace);
8514
8896
  const op = str(args["op"]);
8515
8897
  if (!op) {
8516
8898
  return { error: `\u7F3A\u5C11\u53C2\u6570 op\u3002\u53EF\u9009\u503C\uFF1A${OPS.join(" / ")}` };
@@ -8555,7 +8937,7 @@ function registerMediaTool(registry, workspace, opts = {}) {
8555
8937
  if (!input) return { error: `\u64CD\u4F5C ${op} \u9700\u8981\u53C2\u6570 input\uFF08\u5DE5\u4F5C\u533A\u5185\u76F8\u5BF9\u8DEF\u5F84\uFF09` };
8556
8938
  const r = resolveTarget(root, input);
8557
8939
  if ("error" in r) return { error: r.error };
8558
- if (!fs16.existsSync(r.abs) || !fs16.statSync(r.abs).isFile()) {
8940
+ if (!fs17.existsSync(r.abs) || !fs17.statSync(r.abs).isFile()) {
8559
8941
  return { error: `\u8F93\u5165\u6587\u4EF6\u4E0D\u5B58\u5728: ${input}` };
8560
8942
  }
8561
8943
  inputAbs = r.abs;
@@ -8567,7 +8949,7 @@ function registerMediaTool(registry, workspace, opts = {}) {
8567
8949
  if (!output) return { error: `\u64CD\u4F5C ${op} \u9700\u8981\u53C2\u6570 output\uFF08\u5DE5\u4F5C\u533A\u5185\u76F8\u5BF9\u8DEF\u5F84\uFF09` };
8568
8950
  const r = resolveTarget(root, output);
8569
8951
  if ("error" in r) return { error: r.error };
8570
- fs16.mkdirSync(path18.dirname(r.abs), { recursive: true });
8952
+ fs17.mkdirSync(path19.dirname(r.abs), { recursive: true });
8571
8953
  outputAbs = r.abs;
8572
8954
  relRoot = r.root;
8573
8955
  }
@@ -8617,7 +8999,7 @@ function registerMediaTool(registry, workspace, opts = {}) {
8617
8999
  };
8618
9000
  if (outputAbs) {
8619
9001
  try {
8620
- result["size_bytes"] = fs16.statSync(outputAbs).size;
9002
+ result["size_bytes"] = fs17.statSync(outputAbs).size;
8621
9003
  } catch {
8622
9004
  }
8623
9005
  }
@@ -8808,8 +9190,8 @@ function resolveSessionProvider(store, tenant, providerId) {
8808
9190
 
8809
9191
  // src/server/usage.ts
8810
9192
  init_esm_shims();
8811
- import * as fs17 from "fs";
8812
- import * as path19 from "path";
9193
+ import * as fs18 from "fs";
9194
+ import * as path20 from "path";
8813
9195
  var LEDGER_RETENTION_MONTHS = 12;
8814
9196
  var USAGE_SCHEMA = 1;
8815
9197
  var DEFAULT_PRICING = {
@@ -8890,15 +9272,15 @@ function retainedMonths(now = /* @__PURE__ */ new Date()) {
8890
9272
  return out;
8891
9273
  }
8892
9274
  function atomicWrite(file, content) {
8893
- fs17.mkdirSync(path19.dirname(file), { recursive: true });
9275
+ fs18.mkdirSync(path20.dirname(file), { recursive: true });
8894
9276
  const tmp = `${file}.tmp`;
8895
- fs17.writeFileSync(tmp, content, "utf-8");
8896
- fs17.renameSync(tmp, file);
9277
+ fs18.writeFileSync(tmp, content, "utf-8");
9278
+ fs18.renameSync(tmp, file);
8897
9279
  }
8898
9280
  var UsageLedger = class {
8899
9281
  constructor(root) {
8900
9282
  this.root = root;
8901
- this.dir = path19.join(root, "usage");
9283
+ this.dir = path20.join(root, "usage");
8902
9284
  }
8903
9285
  root;
8904
9286
  dir;
@@ -8911,18 +9293,18 @@ var UsageLedger = class {
8911
9293
  return this.dir;
8912
9294
  }
8913
9295
  ledgerFile(month) {
8914
- return path19.join(this.ledgerDir, `ledger-${month}.jsonl`);
9296
+ return path20.join(this.ledgerDir, `ledger-${month}.jsonl`);
8915
9297
  }
8916
9298
  get rollupFile() {
8917
- return path19.join(this.ledgerDir, "rollup.json");
9299
+ return path20.join(this.ledgerDir, "rollup.json");
8918
9300
  }
8919
9301
  get pricingFile() {
8920
- return path19.join(this.ledgerDir, "pricing.json");
9302
+ return path20.join(this.ledgerDir, "pricing.json");
8921
9303
  }
8922
9304
  // ===================== 初始化 =====================
8923
9305
  init(now = /* @__PURE__ */ new Date()) {
8924
9306
  if (this.inited) return;
8925
- fs17.mkdirSync(this.ledgerDir, { recursive: true });
9307
+ fs18.mkdirSync(this.ledgerDir, { recursive: true });
8926
9308
  this.pricing = this.loadPricing();
8927
9309
  this.rollup = this.loadRollup(now);
8928
9310
  this.pruneLedgers(now);
@@ -8930,7 +9312,7 @@ var UsageLedger = class {
8930
9312
  }
8931
9313
  loadPricing() {
8932
9314
  try {
8933
- const raw = fs17.readFileSync(this.pricingFile, "utf-8");
9315
+ const raw = fs18.readFileSync(this.pricingFile, "utf-8");
8934
9316
  const parsed = JSON.parse(raw);
8935
9317
  const vendors = {};
8936
9318
  for (const v of /* @__PURE__ */ new Set([
@@ -8959,7 +9341,7 @@ var UsageLedger = class {
8959
9341
  }
8960
9342
  loadRollup(now) {
8961
9343
  try {
8962
- const raw = fs17.readFileSync(this.rollupFile, "utf-8");
9344
+ const raw = fs18.readFileSync(this.rollupFile, "utf-8");
8963
9345
  const parsed = JSON.parse(raw);
8964
9346
  return {
8965
9347
  schema: USAGE_SCHEMA,
@@ -8986,10 +9368,10 @@ var UsageLedger = class {
8986
9368
  const keep = retainedMonths(now);
8987
9369
  let changed = false;
8988
9370
  try {
8989
- for (const f of fs17.readdirSync(this.ledgerDir)) {
9371
+ for (const f of fs18.readdirSync(this.ledgerDir)) {
8990
9372
  const m = /^ledger-(\d{4}-\d{2})\.jsonl$/.exec(f);
8991
9373
  if (m && !keep.has(m[1])) {
8992
- fs17.unlinkSync(path19.join(this.ledgerDir, f));
9374
+ fs18.unlinkSync(path20.join(this.ledgerDir, f));
8993
9375
  }
8994
9376
  }
8995
9377
  } catch {
@@ -9066,7 +9448,7 @@ var UsageLedger = class {
9066
9448
  };
9067
9449
  const line = JSON.stringify(rec) + "\n";
9068
9450
  try {
9069
- fs17.appendFileSync(this.ledgerFile(monthOf(rec.ts)), line);
9451
+ fs18.appendFileSync(this.ledgerFile(monthOf(rec.ts)), line);
9070
9452
  } catch (e) {
9071
9453
  console.error(`[usage] \u5199\u8D26\u672C\u5931\u8D25: ${String(e)}`);
9072
9454
  }
@@ -9238,10 +9620,10 @@ var UsageLedger = class {
9238
9620
  const rows = [];
9239
9621
  for (const month of months) {
9240
9622
  const file = this.ledgerFile(month);
9241
- if (!fs17.existsSync(file)) continue;
9623
+ if (!fs18.existsSync(file)) continue;
9242
9624
  let content = "";
9243
9625
  try {
9244
- content = fs17.readFileSync(file, "utf-8");
9626
+ content = fs18.readFileSync(file, "utf-8");
9245
9627
  } catch {
9246
9628
  continue;
9247
9629
  }
@@ -9350,10 +9732,10 @@ var UsageLedger = class {
9350
9732
  const seen = /* @__PURE__ */ new Set();
9351
9733
  for (const month of this.monthsInRange("", "")) {
9352
9734
  const file = this.ledgerFile(month);
9353
- if (!fs17.existsSync(file)) continue;
9735
+ if (!fs18.existsSync(file)) continue;
9354
9736
  let content = "";
9355
9737
  try {
9356
- content = fs17.readFileSync(file, "utf-8");
9738
+ content = fs18.readFileSync(file, "utf-8");
9357
9739
  } catch {
9358
9740
  continue;
9359
9741
  }
@@ -10661,7 +11043,10 @@ data: ${JSON.stringify(entry.data)}
10661
11043
  // 上下文窗口:环境变量显式覆盖 > 供应商 /models 提供的真实长度 > 引擎内置表
10662
11044
  contextWindow: parseOptionalPositiveNumber(process.env.CONTEXT_WINDOW) ?? modelContextWindow(eff.modelMeta, activeModel),
10663
11045
  thresholdPct: parseRatio(process.env.COMPACTION_THRESHOLD_PCT),
10664
- capTokens: parseOptionalPositiveNumber(process.env.COMPACTION_CAP_TOKENS) ?? null
11046
+ softThresholdPct: parseRatio(process.env.COMPACTION_SOFT_THRESHOLD_PCT),
11047
+ capTokens: parseOptionalPositiveNumber(process.env.COMPACTION_CAP_TOKENS) ?? null,
11048
+ // 超预算工具结果按租户/工作区隔离落盘(多租户共用一个状态目录会混内容)
11049
+ toolResultDir: path21.join(workspace, ".agent", "tool-results")
10665
11050
  });
10666
11051
  engine.setApprover(this._makeApprover(rt));
10667
11052
  const mcpConfigs = loadServerMcpConfigs(tenant);
@@ -10842,11 +11227,11 @@ var ApiError = class extends Error {
10842
11227
  // src/server/routes.ts
10843
11228
  init_esm_shims();
10844
11229
  import { createServer } from "http";
10845
- import * as path24 from "path";
11230
+ import * as path26 from "path";
10846
11231
 
10847
11232
  // src/server/projects.ts
10848
11233
  init_esm_shims();
10849
- import * as path20 from "path";
11234
+ import * as path22 from "path";
10850
11235
  var DEFAULT_MODEL = () => process.env.OPENAI_MODEL || process.env.MODEL || "gpt-4o";
10851
11236
  function toProjectSummary(store, tenant) {
10852
11237
  return {
@@ -10979,9 +11364,9 @@ function createProject(store, input) {
10979
11364
  if (store.loadTenant(id)) {
10980
11365
  throw new ApiError(409, "project_exists", `\u9879\u76EE ${id} \u5DF2\u5B58\u5728`);
10981
11366
  }
10982
- const workspaceRoot = typeof raw.workspaceRoot === "string" && raw.workspaceRoot.trim() ? path20.resolve(raw.workspaceRoot.trim()) : void 0;
11367
+ const workspaceRoot = typeof raw.workspaceRoot === "string" && raw.workspaceRoot.trim() ? path22.resolve(raw.workspaceRoot.trim()) : void 0;
10983
11368
  const explicitName = typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : "";
10984
- const defaultName = workspaceRoot ? path20.basename(workspaceRoot) : id;
11369
+ const defaultName = workspaceRoot ? path22.basename(workspaceRoot) : id;
10985
11370
  const tenant = {
10986
11371
  id,
10987
11372
  name: explicitName || defaultName,
@@ -11011,7 +11396,7 @@ function buildNodeInfo(store, opts) {
11011
11396
  nodeId: opts.nodeId ?? ensureNodeId(store),
11012
11397
  platform: process.platform,
11013
11398
  mode: process.env.NODE_MODE ?? "standalone",
11014
- version: "0.2.5",
11399
+ version: "0.2.7",
11015
11400
  admin: opts.admin,
11016
11401
  projects: opts.projects
11017
11402
  };
@@ -11095,8 +11480,8 @@ function hubProxyEnabled(host) {
11095
11480
  const h = (host ?? "127.0.0.1").trim();
11096
11481
  return h === "127.0.0.1" || h === "localhost" || h === "::1";
11097
11482
  }
11098
- function isHubProxyPath(path34) {
11099
- return path34.startsWith(HUB_PROXY_PREFIX);
11483
+ function isHubProxyPath(path36) {
11484
+ return path36.startsWith(HUB_PROXY_PREFIX);
11100
11485
  }
11101
11486
  function decodeTarget(enc) {
11102
11487
  try {
@@ -11190,34 +11575,34 @@ async function handleHubProxy(req2, res) {
11190
11575
 
11191
11576
  // src/server/fsBrowser.ts
11192
11577
  init_esm_shims();
11193
- import * as fs18 from "fs";
11578
+ import * as fs19 from "fs";
11194
11579
  import * as os4 from "os";
11195
- import * as path21 from "path";
11580
+ import * as path23 from "path";
11196
11581
  var MAX_ENTRIES = 2e3;
11197
11582
  var DEFAULT_SEARCH_LIMIT = 200;
11198
11583
  var MAX_SEARCH_LIMIT = 1e3;
11199
11584
  function listDirs(input) {
11200
11585
  const base = (input ?? "").trim() || os4.homedir();
11201
- const target = path21.resolve(base);
11586
+ const target = path23.resolve(base);
11202
11587
  let st;
11203
11588
  try {
11204
- st = fs18.statSync(target);
11589
+ st = fs19.statSync(target);
11205
11590
  } catch {
11206
11591
  throw new Error(`\u8DEF\u5F84\u4E0D\u5B58\u5728: ${target}`);
11207
11592
  }
11208
11593
  if (!st.isDirectory()) throw new Error(`\u4E0D\u662F\u76EE\u5F55: ${target}`);
11209
11594
  let dirents;
11210
11595
  try {
11211
- dirents = fs18.readdirSync(target, { withFileTypes: true });
11596
+ dirents = fs19.readdirSync(target, { withFileTypes: true });
11212
11597
  } catch {
11213
11598
  throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF08\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09: ${target}`);
11214
11599
  }
11215
11600
  const entries = dirents.map((d) => {
11216
- const full = path21.join(target, d.name);
11601
+ const full = path23.join(target, d.name);
11217
11602
  let isDir = d.isDirectory();
11218
11603
  if (d.isSymbolicLink()) {
11219
11604
  try {
11220
- isDir = fs18.statSync(full).isDirectory();
11605
+ isDir = fs19.statSync(full).isDirectory();
11221
11606
  } catch {
11222
11607
  }
11223
11608
  }
@@ -11226,7 +11611,7 @@ function listDirs(input) {
11226
11611
  entries.sort(
11227
11612
  (a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1
11228
11613
  );
11229
- const parentDir = path21.dirname(target);
11614
+ const parentDir = path23.dirname(target);
11230
11615
  return {
11231
11616
  path: target,
11232
11617
  parent: parentDir !== target ? parentDir : null,
@@ -11257,7 +11642,7 @@ var NAME_CACHE_MAX_DIRS = 4;
11257
11642
  var NAME_CACHE_MAX_ENTRIES = 5e4;
11258
11643
  var nameCache = /* @__PURE__ */ new Map();
11259
11644
  function invalidateDirNames(target) {
11260
- nameCache.delete(path21.resolve(target));
11645
+ nameCache.delete(path23.resolve(target));
11261
11646
  }
11262
11647
  async function loadDirNames(target) {
11263
11648
  const now = Date.now();
@@ -11269,7 +11654,7 @@ async function loadDirNames(target) {
11269
11654
  }
11270
11655
  let dirents;
11271
11656
  try {
11272
- dirents = await fs18.promises.readdir(target, { withFileTypes: true });
11657
+ dirents = await fs19.promises.readdir(target, { withFileTypes: true });
11273
11658
  } catch {
11274
11659
  throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF08\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09: ${target}`);
11275
11660
  }
@@ -11292,11 +11677,11 @@ async function loadDirNames(target) {
11292
11677
  return entries;
11293
11678
  }
11294
11679
  async function toDirEntry(dir, e) {
11295
- const full = path21.join(dir, e.name);
11680
+ const full = path23.join(dir, e.name);
11296
11681
  let isDir = e.isDir;
11297
11682
  if (e.isLink) {
11298
11683
  try {
11299
- isDir = (await fs18.promises.stat(full)).isDirectory();
11684
+ isDir = (await fs19.promises.stat(full)).isDirectory();
11300
11685
  } catch {
11301
11686
  }
11302
11687
  }
@@ -11304,10 +11689,10 @@ async function toDirEntry(dir, e) {
11304
11689
  }
11305
11690
  async function searchDirs(input, query, limit) {
11306
11691
  const base = (input ?? "").trim() || os4.homedir();
11307
- const target = path21.resolve(base);
11692
+ const target = path23.resolve(base);
11308
11693
  let st;
11309
11694
  try {
11310
- st = await fs18.promises.stat(target);
11695
+ st = await fs19.promises.stat(target);
11311
11696
  } catch {
11312
11697
  throw new Error(`\u8DEF\u5F84\u4E0D\u5B58\u5728: ${target}`);
11313
11698
  }
@@ -11337,7 +11722,7 @@ async function searchDirs(input, query, limit) {
11337
11722
  );
11338
11723
  const hit = scored.slice(0, max);
11339
11724
  const entries = await Promise.all(hit.map(({ entry }) => toDirEntry(target, entry)));
11340
- const parentDir = path21.dirname(target);
11725
+ const parentDir = path23.dirname(target);
11341
11726
  return {
11342
11727
  path: target,
11343
11728
  parent: parentDir !== target ? parentDir : null,
@@ -11348,52 +11733,52 @@ async function searchDirs(input, query, limit) {
11348
11733
  };
11349
11734
  }
11350
11735
  function makeDirIn(parentInput, name) {
11351
- const parent = path21.resolve((parentInput ?? "").trim() || os4.homedir());
11736
+ const parent = path23.resolve((parentInput ?? "").trim() || os4.homedir());
11352
11737
  const clean = (name ?? "").trim();
11353
11738
  if (!clean) throw new Error("\u76EE\u5F55\u540D\u4E0D\u80FD\u4E3A\u7A7A");
11354
11739
  if (clean === "." || clean === "..") throw new Error("\u76EE\u5F55\u540D\u975E\u6CD5");
11355
- const target = path21.resolve(parent, clean);
11356
- const rel = path21.relative(parent, target);
11357
- if (!rel || rel.startsWith("..") || path21.isAbsolute(rel)) {
11740
+ const target = path23.resolve(parent, clean);
11741
+ const rel = path23.relative(parent, target);
11742
+ if (!rel || rel.startsWith("..") || path23.isAbsolute(rel)) {
11358
11743
  throw new Error("\u76EE\u5F55\u540D\u4E0D\u80FD\u8D8A\u51FA\u5F53\u524D\u76EE\u5F55");
11359
11744
  }
11360
11745
  let st = null;
11361
11746
  try {
11362
- st = fs18.statSync(target);
11747
+ st = fs19.statSync(target);
11363
11748
  } catch {
11364
11749
  st = null;
11365
11750
  }
11366
11751
  if (st && !st.isDirectory()) throw new Error(`\u5DF2\u5B58\u5728\u540C\u540D\u6587\u4EF6: ${target}`);
11367
11752
  try {
11368
- fs18.mkdirSync(target, { recursive: true });
11753
+ fs19.mkdirSync(target, { recursive: true });
11369
11754
  } catch (e) {
11370
11755
  throw new Error(`\u521B\u5EFA\u76EE\u5F55\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
11371
11756
  }
11372
11757
  invalidateDirNames(parent);
11373
- return { name: path21.basename(target), path: target, type: "dir" };
11758
+ return { name: path23.basename(target), path: target, type: "dir" };
11374
11759
  }
11375
11760
 
11376
11761
  // src/server/projectFiles.ts
11377
11762
  init_esm_shims();
11378
- import * as fs19 from "fs";
11379
- import * as path22 from "path";
11763
+ import * as fs20 from "fs";
11764
+ import * as path24 from "path";
11380
11765
  var MAX_FILE_BYTES = 2 * 1024 * 1024;
11381
11766
  var MAX_DOWNLOAD_BYTES = 16 * 1024 * 1024;
11382
11767
  var MAX_ENTRIES2 = 2e3;
11383
11768
  function toPosix(p) {
11384
- return p.split(path22.sep).join("/");
11769
+ return p.split(path24.sep).join("/");
11385
11770
  }
11386
11771
  function projectRoot(workspaceRoot) {
11387
11772
  const r = (workspaceRoot ?? "").trim();
11388
11773
  if (!r) {
11389
11774
  throw new Error("\u9879\u76EE\u672A\u8BBE\u7F6E\u5DE5\u4F5C\u533A\u76EE\u5F55\uFF08workspaceRoot\uFF09\uFF0C\u65E0\u6CD5\u6D4F\u89C8\u6587\u4EF6");
11390
11775
  }
11391
- const abs = path22.resolve(r);
11392
- if (!fs19.existsSync(abs)) {
11776
+ const abs = path24.resolve(r);
11777
+ if (!fs20.existsSync(abs)) {
11393
11778
  throw new Error(`\u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728: ${abs}`);
11394
11779
  }
11395
11780
  try {
11396
- return fs19.realpathSync(abs);
11781
+ return fs20.realpathSync(abs);
11397
11782
  } catch {
11398
11783
  return abs;
11399
11784
  }
@@ -11411,37 +11796,37 @@ function listProjectFiles(workspaceRoot, relPath) {
11411
11796
  const dir = resolveWithin(base, relPath ?? "");
11412
11797
  let st;
11413
11798
  try {
11414
- st = fs19.statSync(dir);
11799
+ st = fs20.statSync(dir);
11415
11800
  } catch {
11416
- throw new Error(`\u76EE\u5F55\u4E0D\u5B58\u5728: ${toPosix(path22.relative(base, dir)) || "/"}`);
11801
+ throw new Error(`\u76EE\u5F55\u4E0D\u5B58\u5728: ${toPosix(path24.relative(base, dir)) || "/"}`);
11417
11802
  }
11418
11803
  if (!st.isDirectory()) throw new Error("\u76EE\u6807\u4E0D\u662F\u76EE\u5F55");
11419
11804
  let dirents;
11420
11805
  try {
11421
- dirents = fs19.readdirSync(dir, { withFileTypes: true });
11806
+ dirents = fs20.readdirSync(dir, { withFileTypes: true });
11422
11807
  } catch {
11423
11808
  throw new Error("\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF08\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09");
11424
11809
  }
11425
11810
  const entries = dirents.map((d) => {
11426
- const full = path22.join(dir, d.name);
11811
+ const full = path24.join(dir, d.name);
11427
11812
  let isDir = d.isDirectory();
11428
11813
  if (d.isSymbolicLink()) {
11429
11814
  try {
11430
- isDir = fs19.statSync(full).isDirectory();
11815
+ isDir = fs20.statSync(full).isDirectory();
11431
11816
  } catch {
11432
11817
  }
11433
11818
  }
11434
11819
  let size = 0;
11435
11820
  let mtime = "";
11436
11821
  try {
11437
- const s = fs19.statSync(full);
11822
+ const s = fs20.statSync(full);
11438
11823
  size = s.size;
11439
11824
  mtime = s.mtime.toISOString();
11440
11825
  } catch {
11441
11826
  }
11442
11827
  return {
11443
11828
  name: d.name,
11444
- path: toPosix(path22.relative(base, full)),
11829
+ path: toPosix(path24.relative(base, full)),
11445
11830
  type: isDir ? "dir" : "file",
11446
11831
  size,
11447
11832
  mtime
@@ -11450,12 +11835,12 @@ function listProjectFiles(workspaceRoot, relPath) {
11450
11835
  entries.sort(
11451
11836
  (a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1
11452
11837
  );
11453
- const relDir = toPosix(path22.relative(base, dir));
11454
- const parentAbs = path22.dirname(dir);
11838
+ const relDir = toPosix(path24.relative(base, dir));
11839
+ const parentAbs = path24.dirname(dir);
11455
11840
  return {
11456
11841
  root: base,
11457
11842
  path: relDir,
11458
- parent: isWithinRoot(base, parentAbs) ? toPosix(path22.relative(base, parentAbs)) : null,
11843
+ parent: isWithinRoot(base, parentAbs) ? toPosix(path24.relative(base, parentAbs)) : null,
11459
11844
  entries: entries.slice(0, MAX_ENTRIES2)
11460
11845
  };
11461
11846
  }
@@ -11464,17 +11849,17 @@ function readProjectFile(workspaceRoot, relPath) {
11464
11849
  const file = resolveWithin(base, relPath ?? "");
11465
11850
  let st;
11466
11851
  try {
11467
- st = fs19.statSync(file);
11852
+ st = fs20.statSync(file);
11468
11853
  } catch {
11469
11854
  throw new Error("\u6587\u4EF6\u4E0D\u5B58\u5728");
11470
11855
  }
11471
11856
  if (!st.isFile()) throw new Error("\u76EE\u6807\u4E0D\u662F\u6587\u4EF6");
11472
- const buf = fs19.readFileSync(file);
11857
+ const buf = fs20.readFileSync(file);
11473
11858
  const binary = isBinary(buf);
11474
11859
  const truncated = buf.length > MAX_FILE_BYTES;
11475
11860
  const slice = truncated ? buf.subarray(0, MAX_FILE_BYTES) : buf;
11476
11861
  return {
11477
- path: toPosix(path22.relative(base, file)),
11862
+ path: toPosix(path24.relative(base, file)),
11478
11863
  size: st.size,
11479
11864
  binary,
11480
11865
  truncated,
@@ -11486,7 +11871,7 @@ function downloadProjectFile(workspaceRoot, relPath) {
11486
11871
  const file = resolveWithin(base, relPath ?? "");
11487
11872
  let st;
11488
11873
  try {
11489
- st = fs19.statSync(file);
11874
+ st = fs20.statSync(file);
11490
11875
  } catch {
11491
11876
  throw new Error("\u6587\u4EF6\u4E0D\u5B58\u5728");
11492
11877
  }
@@ -11494,10 +11879,10 @@ function downloadProjectFile(workspaceRoot, relPath) {
11494
11879
  if (st.size > MAX_DOWNLOAD_BYTES) {
11495
11880
  throw new Error(`\u6587\u4EF6\u8D85\u8FC7\u4E0B\u8F7D\u4E0A\u9650\uFF08${MAX_DOWNLOAD_BYTES / (1024 * 1024)}MB\uFF09`);
11496
11881
  }
11497
- const buf = fs19.readFileSync(file);
11882
+ const buf = fs20.readFileSync(file);
11498
11883
  return {
11499
- path: toPosix(path22.relative(base, file)),
11500
- name: path22.basename(file),
11884
+ path: toPosix(path24.relative(base, file)),
11885
+ name: path24.basename(file),
11501
11886
  size: buf.length,
11502
11887
  base64: buf.toString("base64")
11503
11888
  };
@@ -11519,12 +11904,12 @@ function writeProjectFile(workspaceRoot, relPath, content) {
11519
11904
  throw new Error("\u5185\u5BB9\u8D85\u8FC7 2MB \u4E0A\u9650\uFF0C\u65E0\u6CD5\u4FDD\u5B58");
11520
11905
  }
11521
11906
  try {
11522
- fs19.mkdirSync(path22.dirname(file), { recursive: true });
11523
- fs19.writeFileSync(file, buf);
11907
+ fs20.mkdirSync(path24.dirname(file), { recursive: true });
11908
+ fs20.writeFileSync(file, buf);
11524
11909
  } catch (e) {
11525
11910
  throw new Error(`\u5199\u5165\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
11526
11911
  }
11527
- return { path: toPosix(path22.relative(base, file)), size: buf.length };
11912
+ return { path: toPosix(path24.relative(base, file)), size: buf.length };
11528
11913
  }
11529
11914
  function createProjectDir(workspaceRoot, relPath) {
11530
11915
  const base = projectRoot(workspaceRoot);
@@ -11532,26 +11917,26 @@ function createProjectDir(workspaceRoot, relPath) {
11532
11917
  if (!rel) throw new Error("\u76EE\u5F55\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A");
11533
11918
  const dir = resolveWithin(base, rel);
11534
11919
  try {
11535
- fs19.mkdirSync(dir, { recursive: true });
11920
+ fs20.mkdirSync(dir, { recursive: true });
11536
11921
  } catch (e) {
11537
11922
  throw new Error(`\u521B\u5EFA\u76EE\u5F55\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
11538
11923
  }
11539
- return { path: toPosix(path22.relative(base, dir)) };
11924
+ return { path: toPosix(path24.relative(base, dir)) };
11540
11925
  }
11541
11926
  function createProjectFile(workspaceRoot, relPath, content = "") {
11542
11927
  const base = projectRoot(workspaceRoot);
11543
11928
  const rel = (relPath ?? "").trim();
11544
11929
  if (!rel) throw new Error("\u6587\u4EF6\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A");
11545
11930
  const file = resolveWithin(base, rel);
11546
- if (fs19.existsSync(file)) throw new Error("\u6587\u4EF6\u5DF2\u5B58\u5728");
11931
+ if (fs20.existsSync(file)) throw new Error("\u6587\u4EF6\u5DF2\u5B58\u5728");
11547
11932
  const buf = Buffer.from(String(content ?? ""), "utf-8");
11548
11933
  try {
11549
- fs19.mkdirSync(path22.dirname(file), { recursive: true });
11550
- fs19.writeFileSync(file, buf);
11934
+ fs20.mkdirSync(path24.dirname(file), { recursive: true });
11935
+ fs20.writeFileSync(file, buf);
11551
11936
  } catch (e) {
11552
11937
  throw new Error(`\u521B\u5EFA\u6587\u4EF6\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
11553
11938
  }
11554
- return { path: toPosix(path22.relative(base, file)), size: buf.length };
11939
+ return { path: toPosix(path24.relative(base, file)), size: buf.length };
11555
11940
  }
11556
11941
  function searchProjectFiles(workspaceRoot, query) {
11557
11942
  const base = projectRoot(workspaceRoot);
@@ -11574,7 +11959,7 @@ function searchProjectFiles(workspaceRoot, query) {
11574
11959
  if (out.length >= LIMIT) return;
11575
11960
  let dirents;
11576
11961
  try {
11577
- dirents = fs19.readdirSync(dir, { withFileTypes: true });
11962
+ dirents = fs20.readdirSync(dir, { withFileTypes: true });
11578
11963
  } catch {
11579
11964
  return;
11580
11965
  }
@@ -11585,7 +11970,7 @@ function searchProjectFiles(workspaceRoot, query) {
11585
11970
  let isDir = d.isDirectory();
11586
11971
  if (d.isSymbolicLink()) {
11587
11972
  try {
11588
- isDir = fs19.statSync(path22.join(dir, d.name)).isDirectory();
11973
+ isDir = fs20.statSync(path24.join(dir, d.name)).isDirectory();
11589
11974
  } catch {
11590
11975
  isDir = false;
11591
11976
  }
@@ -11593,7 +11978,7 @@ function searchProjectFiles(workspaceRoot, query) {
11593
11978
  if (d.name.toLowerCase().includes(q)) {
11594
11979
  out.push({ path: rel, type: isDir ? "dir" : "file" });
11595
11980
  }
11596
- if (isDir) walk(path22.join(dir, d.name), rel);
11981
+ if (isDir) walk(path24.join(dir, d.name), rel);
11597
11982
  }
11598
11983
  };
11599
11984
  walk(base, "");
@@ -11603,7 +11988,7 @@ function searchProjectFiles(workspaceRoot, query) {
11603
11988
  // src/server/projectOps.ts
11604
11989
  init_esm_shims();
11605
11990
  import * as os5 from "os";
11606
- import * as path23 from "path";
11991
+ import * as path25 from "path";
11607
11992
  var MAX_STDOUT2 = 2e4;
11608
11993
  var MAX_STDERR2 = 1e4;
11609
11994
  var MAX_BUFFER3 = 1024 * 1024 * 5;
@@ -11617,7 +12002,7 @@ function resolveInvocation2(spec, prepared) {
11617
12002
  }
11618
12003
  function workRoot(workspaceRoot) {
11619
12004
  const r = (workspaceRoot ?? "").trim();
11620
- return r ? path23.resolve(r) : os5.homedir();
12005
+ return r ? path25.resolve(r) : os5.homedir();
11621
12006
  }
11622
12007
  async function runProjectCommand(workspaceRoot, command, timeoutSeconds) {
11623
12008
  const cmd = String(command ?? "");
@@ -11737,6 +12122,92 @@ async function projectGitPush(workspaceRoot, repoPath) {
11737
12122
  });
11738
12123
  }
11739
12124
 
12125
+ // src/server/gitCommitMessage.ts
12126
+ init_esm_shims();
12127
+ import * as crypto5 from "crypto";
12128
+ var MAX_DIFF_CHARS = 12e3;
12129
+ var MAX_MESSAGE_CHARS = 200;
12130
+ 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
12131
+
12132
+ \u8981\u6C42\uFF1A
12133
+ 1. \u4F7F\u7528 Conventional Commits \u98CE\u683C\uFF1Atype(scope): \u63CF\u8FF0\uFF08type \u5982 feat / fix / refactor / docs / test / chore / perf / build / ci\uFF09\uFF1B
12134
+ 2. scope \u53D6\u53D8\u66F4\u6700\u96C6\u4E2D\u7684\u6A21\u5757\u6216\u76EE\u5F55\uFF0C\u65E0\u6CD5\u5224\u65AD\u65F6\u7701\u7565\uFF1B
12135
+ 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
12136
+ 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
12137
+ 5. \u4E0D\u8D85\u8FC7 72 \u4E2A\u5B57\u7B26\u3002`;
12138
+ function genReqId2() {
12139
+ return crypto5.randomBytes(6).toString("base64url");
12140
+ }
12141
+ function cleanCommitMessage(raw) {
12142
+ let text = String(raw ?? "").trim();
12143
+ if (!text) return "";
12144
+ text = text.replace(/^```[^\n]*\n?/, "").replace(/\n?```$/, "").trim();
12145
+ const first = text.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0) ?? "";
12146
+ return first.replace(/^(commit message|提交说明|提交信息|message)\s*[::]\s*/i, "").replace(/^["'“”`]+|["'“”`]+$/g, "").replace(/\s+/g, " ").trim().slice(0, MAX_MESSAGE_CHARS);
12147
+ }
12148
+ async function collectDiff(workspaceRoot, repo) {
12149
+ const staged = await projectGitDiff(workspaceRoot, void 0, true, repo);
12150
+ if (!staged.error && String(staged.diff ?? "").trim()) {
12151
+ return { text: String(staged.diff), source: "staged" };
12152
+ }
12153
+ const worktree = await projectGitDiff(workspaceRoot, void 0, false, repo);
12154
+ if (!worktree.error && String(worktree.diff ?? "").trim()) {
12155
+ return { text: String(worktree.diff), source: "worktree" };
12156
+ }
12157
+ const status = await projectGitStatus(workspaceRoot, void 0, repo);
12158
+ if (status.error) throw new ApiError(400, "git_error", String(status.error));
12159
+ const entries = status.entries ?? [];
12160
+ if (entries.length === 0) throw new ApiError(400, "no_changes", "\u6CA1\u6709\u53EF\u63D0\u4EA4\u7684\u53D8\u66F4");
12161
+ return {
12162
+ text: entries.map((e) => `${e.status ?? ""} ${e.file ?? ""}`.trim()).join("\n"),
12163
+ source: "worktree"
12164
+ };
12165
+ }
12166
+ async function generateGitCommitMessage(opts) {
12167
+ const { store, tenant } = opts;
12168
+ const { text, source } = await collectDiff(opts.workspaceRoot, opts.repo);
12169
+ const truncated = text.length > MAX_DIFF_CHARS;
12170
+ const clipped = text.slice(0, MAX_DIFF_CHARS);
12171
+ const eff = resolveSessionProvider(store, tenant);
12172
+ const apiKey = eff.apiKey || process.env.OPENAI_API_KEY || "";
12173
+ const model = (eff.model || tenant.model || process.env.OPENAI_MODEL || process.env.MODEL || "").trim();
12174
+ if (!apiKey) {
12175
+ throw new ApiError(
12176
+ 400,
12177
+ "llm_not_configured",
12178
+ "\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"
12179
+ );
12180
+ }
12181
+ if (!model) {
12182
+ throw new ApiError(
12183
+ 400,
12184
+ "llm_not_configured",
12185
+ "\u5F53\u524D\u8282\u70B9\u672A\u914D\u7F6E\u6A21\u578B\uFF0C\u8BF7\u5148\u5728\u8282\u70B9\u914D\u7F6E\u4E2D\u9009\u62E9\u6A21\u578B"
12186
+ );
12187
+ }
12188
+ const provider = new OpenAIProvider({ apiKey, baseURL: eff.baseURL });
12189
+ const raw = await provider.chat(
12190
+ model,
12191
+ [
12192
+ { role: "system", content: GEN_COMMIT_MESSAGE_INSTRUCTIONS },
12193
+ {
12194
+ role: "user",
12195
+ 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
12196
+
12197
+ ${clipped}`
12198
+ }
12199
+ ],
12200
+ {},
12201
+ { reqId: `llm_commit_${Date.now()}_${genReqId2()}`, logTime: (/* @__PURE__ */ new Date()).toISOString() },
12202
+ // 不设 maxTokens:推理模型的思考会占用输出配额,过小会导致 content 为空;
12203
+ // 提示词已约束只输出一行短说明。
12204
+ {}
12205
+ );
12206
+ const message = cleanCommitMessage(raw ?? "");
12207
+ if (!message) throw new ApiError(502, "llm_empty", "AI \u672A\u8FD4\u56DE\u6709\u6548\u7684\u63D0\u4EA4\u8BF4\u660E\uFF0C\u8BF7\u91CD\u8BD5");
12208
+ return message;
12209
+ }
12210
+
11740
12211
  // src/server/toolsCatalog.ts
11741
12212
  init_esm_shims();
11742
12213
  function buildToolCatalog(workspace) {
@@ -11842,11 +12313,11 @@ function projectStats(manager, tenantId) {
11842
12313
 
11843
12314
  // src/server/nodeHost.ts
11844
12315
  init_esm_shims();
11845
- import * as fs20 from "fs";
12316
+ import * as fs21 from "fs";
11846
12317
  import * as os6 from "os";
11847
12318
  function diskInfo(pathStr) {
11848
12319
  try {
11849
- const st = fs20.statfsSync(pathStr);
12320
+ const st = fs21.statfsSync(pathStr);
11850
12321
  return {
11851
12322
  path: pathStr,
11852
12323
  total: Number(st.blocks) * Number(st.bsize),
@@ -11918,7 +12389,7 @@ function buildNodeHost(store, manager, diskPath) {
11918
12389
  arch: process.arch,
11919
12390
  release: os6.release(),
11920
12391
  uptimeSec: Math.round(os6.uptime()),
11921
- version: "0.2.5",
12392
+ version: "0.2.7",
11922
12393
  mode: process.env.NODE_MODE ?? "standalone",
11923
12394
  cpu: {
11924
12395
  count: cpus2.length,
@@ -12493,7 +12964,7 @@ function createApp(store, manager, opts = {}) {
12493
12964
  applyProvidersPatch(tenant, raw);
12494
12965
  if (typeof raw.workspaceRoot === "string") {
12495
12966
  const ws = raw.workspaceRoot.trim();
12496
- if (ws) tenant.workspaceRoot = path24.resolve(ws);
12967
+ if (ws) tenant.workspaceRoot = path26.resolve(ws);
12497
12968
  else delete tenant.workspaceRoot;
12498
12969
  }
12499
12970
  if (typeof raw.name === "string" && raw.name.trim()) {
@@ -12902,6 +13373,25 @@ function createApp(store, manager, opts = {}) {
12902
13373
  }
12903
13374
  }
12904
13375
  },
13376
+ {
13377
+ method: "POST",
13378
+ pattern: ["git", "commit-message"],
13379
+ fn: async (_req, res, { tenant, body }) => {
13380
+ const raw = body ?? {};
13381
+ try {
13382
+ const message = await generateGitCommitMessage({
13383
+ store,
13384
+ tenant,
13385
+ workspaceRoot: tenant.workspaceRoot ?? null,
13386
+ repo: String(raw.repo ?? "") || void 0
13387
+ });
13388
+ ok(res, { message });
13389
+ } catch (e) {
13390
+ if (e instanceof ApiError) throw e;
13391
+ throw new ApiError(400, "bad_request", e instanceof Error ? e.message : String(e));
13392
+ }
13393
+ }
13394
+ },
12905
13395
  {
12906
13396
  method: "POST",
12907
13397
  pattern: ["git", "checkout"],
@@ -13425,10 +13915,10 @@ function createApp(store, manager, opts = {}) {
13425
13915
  function handle(req2, res) {
13426
13916
  const startedAt = Date.now();
13427
13917
  const url = new URL(req2.url ?? "/", "http://localhost");
13428
- const path34 = url.pathname;
13918
+ const path36 = url.pathname;
13429
13919
  const method = req2.method ?? "GET";
13430
13920
  console.log(
13431
- `[api] ${(/* @__PURE__ */ new Date()).toISOString()} \u2192 ${method} ${path34}${url.search}`
13921
+ `[api] ${(/* @__PURE__ */ new Date()).toISOString()} \u2192 ${method} ${path36}${url.search}`
13432
13922
  );
13433
13923
  let tenantId = null;
13434
13924
  let logged = false;
@@ -13436,7 +13926,7 @@ function createApp(store, manager, opts = {}) {
13436
13926
  if (logged) return;
13437
13927
  logged = true;
13438
13928
  console.log(
13439
- `[api] ${(/* @__PURE__ */ new Date()).toISOString()} \u2190 ${method} ${path34} ${extra} (${Date.now() - startedAt}ms) tenant=${tenantId ?? "-"}`
13929
+ `[api] ${(/* @__PURE__ */ new Date()).toISOString()} \u2190 ${method} ${path36} ${extra} (${Date.now() - startedAt}ms) tenant=${tenantId ?? "-"}`
13440
13930
  );
13441
13931
  };
13442
13932
  res.on("finish", () => logDone(`HTTP ${res.statusCode}`));
@@ -13447,16 +13937,16 @@ function createApp(store, manager, opts = {}) {
13447
13937
  res.end();
13448
13938
  return;
13449
13939
  }
13450
- if (req2.method === "GET" && path34 === "/healthz") {
13940
+ if (req2.method === "GET" && path36 === "/healthz") {
13451
13941
  json(res, 200, {
13452
13942
  status: "ok",
13453
13943
  // 版本让运维/探针能确认线上跑的是哪个构建(更新与回滚据此判断)
13454
- version: "0.2.5",
13944
+ version: "0.2.7",
13455
13945
  features: { mcp: hasServerMcpConfig() }
13456
13946
  });
13457
13947
  return;
13458
13948
  }
13459
- if (hubProxyEnabled(opts.host) && isHubProxyPath(path34)) {
13949
+ if (hubProxyEnabled(opts.host) && isHubProxyPath(path36)) {
13460
13950
  void handleHubProxy(req2, res).catch(() => {
13461
13951
  if (!res.headersSent) {
13462
13952
  res.writeHead(502, { "Content-Type": "application/json; charset=utf-8" });
@@ -13467,11 +13957,11 @@ function createApp(store, manager, opts = {}) {
13467
13957
  });
13468
13958
  return;
13469
13959
  }
13470
- if (!path34.startsWith(API_PREFIX)) {
13960
+ if (!path36.startsWith(API_PREFIX)) {
13471
13961
  json(res, 404, { error: { code: "not_found", message: "\u672A\u77E5\u8DEF\u5F84" } });
13472
13962
  return;
13473
13963
  }
13474
- const segs = path34.slice(API_PREFIX.length).split("/").filter(Boolean);
13964
+ const segs = path36.slice(API_PREFIX.length).split("/").filter(Boolean);
13475
13965
  const apiKey = extractApiKey(req2);
13476
13966
  if (!apiKey) {
13477
13967
  json(res, 401, {
@@ -13536,7 +14026,7 @@ function createApp(store, manager, opts = {}) {
13536
14026
  ).catch((err) => {
13537
14027
  const msg = err instanceof Error ? err.message : String(err);
13538
14028
  console.log(
13539
- `[api] ${(/* @__PURE__ */ new Date()).toISOString()} ! ${method} ${path34} ERROR: ${msg}`
14029
+ `[api] ${(/* @__PURE__ */ new Date()).toISOString()} ! ${method} ${path36} ERROR: ${msg}`
13540
14030
  );
13541
14031
  error(res, err);
13542
14032
  });
@@ -14051,6 +14541,17 @@ async function dispatchFleetCommand(deps, env3) {
14051
14541
  )
14052
14542
  );
14053
14543
  }
14544
+ case "git_commit_message": {
14545
+ const t = project();
14546
+ return ok2({
14547
+ message: await generateGitCommitMessage({
14548
+ store,
14549
+ tenant: t,
14550
+ workspaceRoot: t.workspaceRoot ?? null,
14551
+ repo: gitRepo(payload.repo)
14552
+ })
14553
+ });
14554
+ }
14054
14555
  case "git_checkout": {
14055
14556
  const t = project();
14056
14557
  return ok2(
@@ -14756,6 +15257,18 @@ var READONLY_CMD_TYPES = /* @__PURE__ */ new Set([
14756
15257
  "git_show"
14757
15258
  ]);
14758
15259
  var CMD_CACHE_TTL_MS = 1500;
15260
+ var STATE_CHANGING_EVENTS = /* @__PURE__ */ new Set([
15261
+ "turn_start",
15262
+ "turn_end",
15263
+ "permission_required",
15264
+ "ask_user_question",
15265
+ "plan_proposed",
15266
+ "directory_requested",
15267
+ "error",
15268
+ "interrupted",
15269
+ "queue_steering",
15270
+ "session_named"
15271
+ ]);
14759
15272
  var READONLY_CONCURRENCY = 4;
14760
15273
  var BATCH_MAX_SUBS = 64;
14761
15274
  var BATCH_SUB_TIMEOUT_MS = 7e3;
@@ -14991,6 +15504,20 @@ var FleetNodeBridge = class {
14991
15504
  this.writeEpoch += 1;
14992
15505
  this.cmdResultCache.clear();
14993
15506
  }
15507
+ /**
15508
+ * 会话快照缓存失效(事件/通知驱动的状态变化)。
15509
+ *
15510
+ * 与写操作不同,这些变化只影响会话相关查询;文件树 / Git 等重查询的缓存
15511
+ * 保持有效,避免多会话并行运行时把整个只读缓存反复清空。
15512
+ */
15513
+ _invalidateSessionCache() {
15514
+ this.writeEpoch += 1;
15515
+ for (const key of [...this.cmdResultCache.keys()]) {
15516
+ if (/^(get_state|get_events|list_sessions|get_session_states)\|/.test(key)) {
15517
+ this.cmdResultCache.delete(key);
15518
+ }
15519
+ }
15520
+ }
14994
15521
  close() {
14995
15522
  this.closing = true;
14996
15523
  if (this.usageTimer) {
@@ -15019,6 +15546,7 @@ var FleetNodeBridge = class {
15019
15546
  }
15020
15547
  /** 通知广播(SessionManager 注入的 notify 回调) */
15021
15548
  notify(tenantId, ev) {
15549
+ this._invalidateSessionCache();
15022
15550
  const payload = ev.kind === "approval_request" ? {
15023
15551
  ...ev,
15024
15552
  projectId: tenantId,
@@ -15031,6 +15559,7 @@ var FleetNodeBridge = class {
15031
15559
  }
15032
15560
  /** 事件桥接(SessionManager 注入的 onEvent 回调);发布按窗口批量合并 */
15033
15561
  onEvent(tenantId, sessionId, entry) {
15562
+ if (STATE_CHANGING_EVENTS.has(entry.type)) this._invalidateSessionCache();
15034
15563
  if (entry.type === "turn_end") this.stats.turns += 1;
15035
15564
  else if (entry.type === "tool_finished") this.stats.toolCalls += 1;
15036
15565
  else if (entry.type === "error") this.stats.errors += 1;
@@ -15115,7 +15644,7 @@ var FleetNodeBridge = class {
15115
15644
  label: this.opts.deps.store.loadNodeLabel() ?? void 0,
15116
15645
  platform: process.platform,
15117
15646
  mode: process.env.NODE_MODE ?? "standalone",
15118
- version: "0.2.5",
15647
+ version: "0.2.7",
15119
15648
  ip: primaryIp()
15120
15649
  };
15121
15650
  }
@@ -15149,7 +15678,10 @@ var FleetNodeBridge = class {
15149
15678
  sessionId: s.sessionId,
15150
15679
  name: s.name ?? null,
15151
15680
  status: s.status,
15152
- lastUserMessage: lastUserMessage(s)
15681
+ lastUserMessage: lastUserMessage(s),
15682
+ // 控制面的聚合邮件靠它判断「这台机器最近有没有在干活」;
15683
+ // 状态跃迁可能被 info 节流跳过,完成时间不会。
15684
+ lastCompletedAt: s.lastCompletedAt ?? null
15153
15685
  });
15154
15686
  }
15155
15687
  } catch {
@@ -15167,7 +15699,7 @@ var FleetNodeBridge = class {
15167
15699
  label: this.opts.deps.store.loadNodeLabel() ?? void 0,
15168
15700
  platform: process.platform,
15169
15701
  mode: process.env.NODE_MODE ?? "standalone",
15170
- version: "0.2.5",
15702
+ version: "0.2.7",
15171
15703
  ip: primaryIp(),
15172
15704
  projects,
15173
15705
  totals,
@@ -15936,8 +16468,8 @@ init_esm_shims();
15936
16468
 
15937
16469
  // src/server/qq/bindings.ts
15938
16470
  init_esm_shims();
15939
- import * as fs21 from "fs";
15940
- import * as path25 from "path";
16471
+ import * as fs22 from "fs";
16472
+ import * as path27 from "path";
15941
16473
  function conversationKey(target) {
15942
16474
  switch (target.kind) {
15943
16475
  case "c2c":
@@ -15976,12 +16508,12 @@ var QqBindingsStore = class _QqBindingsStore {
15976
16508
  cache = null;
15977
16509
  /** 基于 DATA_DIR 构造 */
15978
16510
  static in(dataDir) {
15979
- return new _QqBindingsStore(path25.join(dataDir, "qq", "bindings.json"));
16511
+ return new _QqBindingsStore(path27.join(dataDir, "qq", "bindings.json"));
15980
16512
  }
15981
16513
  load() {
15982
16514
  if (this.cache) return this.cache;
15983
16515
  try {
15984
- const raw = JSON.parse(fs21.readFileSync(this.file, "utf-8"));
16516
+ const raw = JSON.parse(fs22.readFileSync(this.file, "utf-8"));
15985
16517
  this.cache = {
15986
16518
  conversations: raw.conversations ?? {}
15987
16519
  };
@@ -16013,8 +16545,8 @@ var QqBindingsStore = class _QqBindingsStore {
16013
16545
  return null;
16014
16546
  }
16015
16547
  persist() {
16016
- fs21.mkdirSync(path25.dirname(this.file), { recursive: true });
16017
- fs21.writeFileSync(this.file, JSON.stringify(this.cache, null, 2), "utf-8");
16548
+ fs22.mkdirSync(path27.dirname(this.file), { recursive: true });
16549
+ fs22.writeFileSync(this.file, JSON.stringify(this.cache, null, 2), "utf-8");
16018
16550
  }
16019
16551
  };
16020
16552
 
@@ -16426,8 +16958,8 @@ ${lines.join("\n")}`, msgId);
16426
16958
 
16427
16959
  // src/server/qq/QqManager.ts
16428
16960
  init_esm_shims();
16429
- import * as fs22 from "fs";
16430
- import * as path26 from "path";
16961
+ import * as fs23 from "fs";
16962
+ import * as path28 from "path";
16431
16963
  var QqManager = class {
16432
16964
  constructor(opts) {
16433
16965
  this.opts = opts;
@@ -16436,7 +16968,7 @@ var QqManager = class {
16436
16968
  adapter = null;
16437
16969
  config = null;
16438
16970
  get configFile() {
16439
- return path26.join(this.opts.dataDir, "qq", "config.json");
16971
+ return path28.join(this.opts.dataDir, "qq", "config.json");
16440
16972
  }
16441
16973
  /** 启动:读配置(env 优先,其次文件),有则建连接 */
16442
16974
  start() {
@@ -16474,7 +17006,7 @@ var QqManager = class {
16474
17006
  /** 清空配置:删除文件 + 断开 */
16475
17007
  clearConfig() {
16476
17008
  try {
16477
- fs22.rmSync(this.configFile, { force: true });
17009
+ fs23.rmSync(this.configFile, { force: true });
16478
17010
  } catch {
16479
17011
  }
16480
17012
  this.stop();
@@ -16516,7 +17048,7 @@ var QqManager = class {
16516
17048
  }
16517
17049
  _readConfigFile() {
16518
17050
  try {
16519
- const raw = JSON.parse(fs22.readFileSync(this.configFile, "utf-8"));
17051
+ const raw = JSON.parse(fs23.readFileSync(this.configFile, "utf-8"));
16520
17052
  if (raw.appId && raw.appSecret) {
16521
17053
  return {
16522
17054
  appId: raw.appId,
@@ -16529,18 +17061,18 @@ var QqManager = class {
16529
17061
  return null;
16530
17062
  }
16531
17063
  _persistConfig(cfg) {
16532
- fs22.mkdirSync(path26.dirname(this.configFile), { recursive: true });
16533
- fs22.writeFileSync(this.configFile, JSON.stringify(cfg, null, 2), "utf-8");
17064
+ fs23.mkdirSync(path28.dirname(this.configFile), { recursive: true });
17065
+ fs23.writeFileSync(this.configFile, JSON.stringify(cfg, null, 2), "utf-8");
16534
17066
  }
16535
17067
  };
16536
17068
 
16537
17069
  // src/server/qq/pairingCodes.ts
16538
17070
  init_esm_shims();
16539
- import * as crypto5 from "crypto";
17071
+ import * as crypto6 from "crypto";
16540
17072
  var PAIRING_CODE_TTL_MS = 5 * 60 * 1e3;
16541
17073
  function randomCode(len = 8) {
16542
17074
  const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
16543
- const bytes = crypto5.randomBytes(len);
17075
+ const bytes = crypto6.randomBytes(len);
16544
17076
  let out = "";
16545
17077
  for (let i = 0; i < len; i++) {
16546
17078
  out += alphabet[bytes[i] % alphabet.length];
@@ -16582,13 +17114,13 @@ var baseUrlResolver = null;
16582
17114
  function resolveBase(baseUrl) {
16583
17115
  return baseUrlResolver ? baseUrlResolver(baseUrl) : baseUrl;
16584
17116
  }
16585
- async function req(baseUrl, token, path34, init = {}) {
17117
+ async function req(baseUrl, token, path36, init = {}) {
16586
17118
  const headers = {
16587
17119
  ...init.body ? { "Content-Type": "application/json" } : {},
16588
17120
  ...init.headers ?? {}
16589
17121
  };
16590
17122
  if (token) headers.Authorization = `Bearer ${token}`;
16591
- const res = await fetch(`${resolveBase(baseUrl).replace(/\/$/, "")}${path34}`, {
17123
+ const res = await fetch(`${resolveBase(baseUrl).replace(/\/$/, "")}${path36}`, {
16592
17124
  ...init,
16593
17125
  headers
16594
17126
  });
@@ -16630,17 +17162,17 @@ async function hubEnrollByAccount(baseUrl, input) {
16630
17162
 
16631
17163
  // src/update/controller.ts
16632
17164
  init_esm_shims();
16633
- import * as fs26 from "fs";
16634
- import * as path31 from "path";
17165
+ import * as fs28 from "fs";
17166
+ import * as path33 from "path";
16635
17167
 
16636
17168
  // src/update/downloader.ts
16637
17169
  init_esm_shims();
16638
17170
  import { createHash as createHash3 } from "crypto";
16639
- import * as fs23 from "fs";
17171
+ import * as fs24 from "fs";
16640
17172
  import { Readable as Readable2 } from "stream";
16641
17173
  import { pipeline as pipeline2 } from "stream/promises";
16642
17174
  import { spawnSync as spawnSync2 } from "child_process";
16643
- import * as path27 from "path";
17175
+ import * as path29 from "path";
16644
17176
 
16645
17177
  // src/update/manifest.ts
16646
17178
  init_esm_shims();
@@ -16815,8 +17347,8 @@ async function fetchManifest(url, publicKey, opts = {}) {
16815
17347
  async function downloadBinary(bin, destPath, log = () => {
16816
17348
  }) {
16817
17349
  const partPath = `${destPath}.part`;
16818
- fs23.mkdirSync(path27.dirname(destPath), { recursive: true });
16819
- fs23.rmSync(partPath, { force: true });
17350
+ fs24.mkdirSync(path29.dirname(destPath), { recursive: true });
17351
+ fs24.rmSync(partPath, { force: true });
16820
17352
  let res;
16821
17353
  try {
16822
17354
  res = await fetch(bin.url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS2) });
@@ -16841,25 +17373,25 @@ async function downloadBinary(bin, destPath, log = () => {
16841
17373
  yield chunk;
16842
17374
  }
16843
17375
  },
16844
- fs23.createWriteStream(partPath, { mode: 493 })
17376
+ fs24.createWriteStream(partPath, { mode: 493 })
16845
17377
  );
16846
17378
  } catch (err) {
16847
- fs23.rmSync(partPath, { force: true });
17379
+ fs24.rmSync(partPath, { force: true });
16848
17380
  return { error: `\u4E0B\u8F7D\u4E2D\u65AD\uFF1A${err.message}` };
16849
17381
  }
16850
17382
  const actual = hash.digest("hex");
16851
17383
  if (actual !== bin.sha256) {
16852
- fs23.rmSync(partPath, { force: true });
17384
+ fs24.rmSync(partPath, { force: true });
16853
17385
  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` };
16854
17386
  }
16855
17387
  if (expected && bytes !== expected) {
16856
- fs23.rmSync(partPath, { force: true });
17388
+ fs24.rmSync(partPath, { force: true });
16857
17389
  return { error: `\u4EA7\u7269\u5927\u5C0F\u4E0D\u5339\u914D\uFF08\u671F\u671B ${expected}\uFF0C\u5B9E\u9645 ${bytes}\uFF09` };
16858
17390
  }
16859
- fs23.rmSync(destPath, { force: true });
16860
- fs23.renameSync(partPath, destPath);
16861
- fs23.chmodSync(destPath, 493);
16862
- log(`\u5DF2\u4E0B\u8F7D ${path27.basename(destPath)}\uFF08${Math.round(bytes / 1024 / 1024)}MB\uFF0C\u6821\u9A8C\u901A\u8FC7\uFF09`);
17391
+ fs24.rmSync(destPath, { force: true });
17392
+ fs24.renameSync(partPath, destPath);
17393
+ fs24.chmodSync(destPath, 493);
17394
+ log(`\u5DF2\u4E0B\u8F7D ${path29.basename(destPath)}\uFF08${Math.round(bytes / 1024 / 1024)}MB\uFF0C\u6821\u9A8C\u901A\u8FC7\uFF09`);
16863
17395
  return { bytes };
16864
17396
  }
16865
17397
  function selfCheck(binPath, expectedVersion) {
@@ -16883,10 +17415,11 @@ function selfCheck(binPath, expectedVersion) {
16883
17415
 
16884
17416
  // src/update/platform.ts
16885
17417
  init_esm_shims();
16886
- import * as path28 from "path";
17418
+ import * as fs25 from "fs";
17419
+ import * as path30 from "path";
16887
17420
  import { createRequire as createRequire3 } from "module";
16888
17421
  function currentVersion() {
16889
- const injected = "0.2.5"?.trim();
17422
+ const injected = "0.2.7"?.trim();
16890
17423
  if (injected) return injected;
16891
17424
  try {
16892
17425
  const req2 = createRequire3(import.meta.url);
@@ -16898,12 +17431,36 @@ function currentVersion() {
16898
17431
  }
16899
17432
  function isSeaRuntime() {
16900
17433
  if (process.env.ZQAGENT_SEA === "1") return true;
16901
- const base = path28.basename(process.execPath).toLowerCase().replace(/\.exe$/, "");
17434
+ const base = path30.basename(process.execPath).toLowerCase().replace(/\.exe$/, "");
16902
17435
  return base !== "node" && base !== "nodejs";
16903
17436
  }
17437
+ var DEFAULT_SERVICE_NAME = "zqagent";
17438
+ function serviceManagerFromCgroup(cgroup, serviceName = DEFAULT_SERVICE_NAME) {
17439
+ const unitName = `${serviceName}.service`;
17440
+ for (const line of cgroup.split("\n")) {
17441
+ const idx = line.lastIndexOf(":");
17442
+ if (idx < 0) continue;
17443
+ const unitPath = line.slice(idx + 1).trim();
17444
+ if (unitPath.split("/").includes(unitName)) return "systemd";
17445
+ }
17446
+ return null;
17447
+ }
17448
+ function detectServiceManager() {
17449
+ if (process.platform !== "linux") return null;
17450
+ const serviceName = process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;
17451
+ try {
17452
+ return serviceManagerFromCgroup(
17453
+ fs25.readFileSync("/proc/self/cgroup", "utf8"),
17454
+ serviceName
17455
+ );
17456
+ } catch {
17457
+ return null;
17458
+ }
17459
+ }
16904
17460
  function serviceManager() {
16905
17461
  const v = process.env.ZQAGENT_SERVICE_MANAGER?.trim().toLowerCase();
16906
- return v === "systemd" || v === "launchd" ? v : null;
17462
+ if (v === "systemd" || v === "launchd") return v;
17463
+ return detectServiceManager();
16907
17464
  }
16908
17465
  function selfUpdateCapability(hasPublicKey) {
16909
17466
  if (!isSeaRuntime()) {
@@ -16926,22 +17483,22 @@ function selfUpdateCapability(hasPublicKey) {
16926
17483
 
16927
17484
  // src/update/installer.ts
16928
17485
  init_esm_shims();
16929
- import * as fs25 from "fs";
16930
- import * as path30 from "path";
17486
+ import * as fs27 from "fs";
17487
+ import * as path32 from "path";
16931
17488
 
16932
17489
  // src/update/state.ts
16933
17490
  init_esm_shims();
16934
- import * as fs24 from "fs";
16935
- import * as path29 from "path";
17491
+ import * as fs26 from "fs";
17492
+ import * as path31 from "path";
16936
17493
  function updatesDir(dataDir) {
16937
- return path29.join(dataDir, "updates");
17494
+ return path31.join(dataDir, "updates");
16938
17495
  }
16939
17496
  function statePath(dataDir) {
16940
- return path29.join(updatesDir(dataDir), "state.json");
17497
+ return path31.join(updatesDir(dataDir), "state.json");
16941
17498
  }
16942
17499
  function loadState(dataDir) {
16943
17500
  try {
16944
- const raw = fs24.readFileSync(statePath(dataDir), "utf-8");
17501
+ const raw = fs26.readFileSync(statePath(dataDir), "utf-8");
16945
17502
  const parsed = JSON.parse(raw);
16946
17503
  if (typeof parsed === "object" && parsed !== null) return parsed;
16947
17504
  } catch {
@@ -16950,11 +17507,11 @@ function loadState(dataDir) {
16950
17507
  }
16951
17508
  function saveState(dataDir, state) {
16952
17509
  const dir = updatesDir(dataDir);
16953
- fs24.mkdirSync(dir, { recursive: true });
17510
+ fs26.mkdirSync(dir, { recursive: true });
16954
17511
  const file = statePath(dataDir);
16955
17512
  const tmp = `${file}.tmp`;
16956
- fs24.writeFileSync(tmp, JSON.stringify(state, null, 2), "utf-8");
16957
- fs24.renameSync(tmp, file);
17513
+ fs26.writeFileSync(tmp, JSON.stringify(state, null, 2), "utf-8");
17514
+ fs26.renameSync(tmp, file);
16958
17515
  }
16959
17516
  function patchState(dataDir, patch) {
16960
17517
  const next = { ...loadState(dataDir), ...patch };
@@ -16964,36 +17521,36 @@ function patchState(dataDir, patch) {
16964
17521
 
16965
17522
  // src/update/installer.ts
16966
17523
  function backupDir(dataDir) {
16967
- return path30.join(updatesDir(dataDir), "backup");
17524
+ return path32.join(updatesDir(dataDir), "backup");
16968
17525
  }
16969
17526
  function backupCurrent(dataDir, execPath, version) {
16970
17527
  const dir = backupDir(dataDir);
16971
- fs25.mkdirSync(dir, { recursive: true });
16972
- const target = path30.join(dir, `zqagent-${version}${process.platform === "win32" ? ".exe" : ""}`);
16973
- fs25.copyFileSync(execPath, target);
16974
- fs25.chmodSync(target, 493);
17528
+ fs27.mkdirSync(dir, { recursive: true });
17529
+ const target = path32.join(dir, `zqagent-${version}${process.platform === "win32" ? ".exe" : ""}`);
17530
+ fs27.copyFileSync(execPath, target);
17531
+ fs27.chmodSync(target, 493);
16975
17532
  return target;
16976
17533
  }
16977
17534
  function replaceExecutable(execPath, stagedPath) {
16978
17535
  if (process.platform === "win32") {
16979
17536
  const oldPath = `${execPath}.old`;
16980
- fs25.rmSync(oldPath, { force: true });
16981
- fs25.renameSync(execPath, oldPath);
16982
- fs25.copyFileSync(stagedPath, execPath);
16983
- fs25.chmodSync(execPath, 493);
17537
+ fs27.rmSync(oldPath, { force: true });
17538
+ fs27.renameSync(execPath, oldPath);
17539
+ fs27.copyFileSync(stagedPath, execPath);
17540
+ fs27.chmodSync(execPath, 493);
16984
17541
  return;
16985
17542
  }
16986
17543
  const tmp = `${execPath}.new`;
16987
- fs25.rmSync(tmp, { force: true });
17544
+ fs27.rmSync(tmp, { force: true });
16988
17545
  try {
16989
- fs25.copyFileSync(stagedPath, tmp);
16990
- fs25.chmodSync(tmp, 493);
16991
- fs25.renameSync(tmp, execPath);
17546
+ fs27.copyFileSync(stagedPath, tmp);
17547
+ fs27.chmodSync(tmp, 493);
17548
+ fs27.renameSync(tmp, execPath);
16992
17549
  } catch (err) {
16993
- fs25.rmSync(tmp, { force: true });
17550
+ fs27.rmSync(tmp, { force: true });
16994
17551
  throw err;
16995
17552
  }
16996
- fs25.rmSync(stagedPath, { force: true });
17553
+ fs27.rmSync(stagedPath, { force: true });
16997
17554
  }
16998
17555
  function installStaged(opts) {
16999
17556
  const log = opts.log ?? (() => {
@@ -17001,7 +17558,7 @@ function installStaged(opts) {
17001
17558
  const from = currentVersion();
17002
17559
  log(`\u6B63\u5728\u5B89\u88C5 v${opts.version}\uFF08\u5F53\u524D v${from}\uFF09`);
17003
17560
  const backupPath = backupCurrent(opts.dataDir, opts.execPath, from);
17004
- log(`\u5DF2\u5907\u4EFD\u5F53\u524D\u7248\u672C\u5230 ${path30.basename(backupPath)}`);
17561
+ log(`\u5DF2\u5907\u4EFD\u5F53\u524D\u7248\u672C\u5230 ${path32.basename(backupPath)}`);
17005
17562
  replaceExecutable(opts.execPath, opts.stagedPath);
17006
17563
  return patchState(opts.dataDir, {
17007
17564
  stagedVersion: void 0,
@@ -17020,18 +17577,18 @@ function rollback(dataDir, execPath, log = () => {
17020
17577
  }) {
17021
17578
  const state = readRollbackTarget(dataDir);
17022
17579
  if (!state?.backupPath || !state.backupVersion) return null;
17023
- if (!fs25.existsSync(state.backupPath)) return null;
17580
+ if (!fs27.existsSync(state.backupPath)) return null;
17024
17581
  log(`\u6B63\u5728\u56DE\u6EDA\u5230 v${state.backupVersion}`);
17025
17582
  if (process.platform === "win32") {
17026
- fs25.rmSync(`${execPath}.old`, { force: true });
17027
- fs25.renameSync(execPath, `${execPath}.old`);
17028
- fs25.copyFileSync(state.backupPath, execPath);
17029
- fs25.chmodSync(execPath, 493);
17583
+ fs27.rmSync(`${execPath}.old`, { force: true });
17584
+ fs27.renameSync(execPath, `${execPath}.old`);
17585
+ fs27.copyFileSync(state.backupPath, execPath);
17586
+ fs27.chmodSync(execPath, 493);
17030
17587
  } else {
17031
17588
  const tmp = `${execPath}.rollback`;
17032
- fs25.copyFileSync(state.backupPath, tmp);
17033
- fs25.chmodSync(tmp, 493);
17034
- fs25.renameSync(tmp, execPath);
17589
+ fs27.copyFileSync(state.backupPath, tmp);
17590
+ fs27.chmodSync(tmp, 493);
17591
+ fs27.renameSync(tmp, execPath);
17035
17592
  }
17036
17593
  patchState(dataDir, {
17037
17594
  pendingVersion: state.backupVersion,
@@ -17045,7 +17602,7 @@ function rollback(dataDir, execPath, log = () => {
17045
17602
  }
17046
17603
  function readRollbackTarget(dataDir) {
17047
17604
  try {
17048
- const raw = fs25.readFileSync(path30.join(updatesDir(dataDir), "state.json"), "utf-8");
17605
+ const raw = fs27.readFileSync(path32.join(updatesDir(dataDir), "state.json"), "utf-8");
17049
17606
  const s = JSON.parse(raw);
17050
17607
  if (s.backupPath && s.backupVersion) return s;
17051
17608
  } catch {
@@ -17128,7 +17685,7 @@ var UpdateController = class {
17128
17685
  reconcile() {
17129
17686
  const state = loadState(this.opts.dataDir);
17130
17687
  const current = this.currentVersion;
17131
- if (state.stagedPath && !fs26.existsSync(state.stagedPath)) {
17688
+ if (state.stagedPath && !fs28.existsSync(state.stagedPath)) {
17132
17689
  patchState(this.opts.dataDir, { stagedVersion: void 0, stagedPath: void 0 });
17133
17690
  }
17134
17691
  const patch = { runningVersion: current };
@@ -17143,7 +17700,7 @@ var UpdateController = class {
17143
17700
  }
17144
17701
  patchState(this.opts.dataDir, patch);
17145
17702
  if (process.platform === "win32") {
17146
- fs26.rmSync(`${process.execPath}.old`, { force: true });
17703
+ fs28.rmSync(`${process.execPath}.old`, { force: true });
17147
17704
  }
17148
17705
  }
17149
17706
  schedule(delayMs) {
@@ -17219,8 +17776,8 @@ var UpdateController = class {
17219
17776
  const bin = manifest.binaries[platformKey()];
17220
17777
  if (!bin) return `\u6E05\u5355\u6CA1\u6709\u5F53\u524D\u5E73\u53F0\uFF08${platformKey()}\uFF09\u7684\u4EA7\u7269`;
17221
17778
  let stagedPath = state.stagedPath;
17222
- if (!(state.stagedVersion === manifest.version && stagedPath && fs26.existsSync(stagedPath))) {
17223
- stagedPath = path31.join(
17779
+ if (!(state.stagedVersion === manifest.version && stagedPath && fs28.existsSync(stagedPath))) {
17780
+ stagedPath = path33.join(
17224
17781
  updatesDir(this.opts.dataDir),
17225
17782
  "staged",
17226
17783
  `zqagent-${manifest.version}${process.platform === "win32" ? ".exe" : ""}`
@@ -17232,7 +17789,7 @@ var UpdateController = class {
17232
17789
  }
17233
17790
  const selfTest = selfCheck(stagedPath, manifest.version);
17234
17791
  if (!selfTest.ok) {
17235
- fs26.rmSync(stagedPath, { force: true });
17792
+ fs28.rmSync(stagedPath, { force: true });
17236
17793
  this.recordFailure(manifest.version, selfTest.error ?? "\u81EA\u68C0\u5931\u8D25");
17237
17794
  return `\u5B89\u88C5\u5305\u81EA\u68C0\u5931\u8D25\uFF1A${selfTest.error}`;
17238
17795
  }
@@ -17373,7 +17930,7 @@ function pemFile(key) {
17373
17930
  const p = env2(key);
17374
17931
  if (!p) return void 0;
17375
17932
  try {
17376
- return fs27.readFileSync(p);
17933
+ return fs29.readFileSync(p);
17377
17934
  } catch (e) {
17378
17935
  console.warn(`[tls] \u8BFB\u53D6 ${key}=${p} \u5931\u8D25: ${e instanceof Error ? e.message : e}`);
17379
17936
  return void 0;
@@ -17395,7 +17952,7 @@ function seedDemoTenant(store) {
17395
17952
  apiKey: env2("OPENAI_API_KEY"),
17396
17953
  baseURL: env2("OPENAI_BASE_URL")
17397
17954
  },
17398
- workspaceRoot: workspace ? path32.resolve(workspace) : void 0,
17955
+ workspaceRoot: workspace ? path34.resolve(workspace) : void 0,
17399
17956
  allowedCommands: []
17400
17957
  };
17401
17958
  store.saveTenant(demo);
@@ -17449,9 +18006,9 @@ function syncTenantModels(store) {
17449
18006
  tenant.model = model;
17450
18007
  changed = true;
17451
18008
  }
17452
- if (workspace && tenant.workspaceRoot !== path32.resolve(workspace)) {
17453
- console.log(`[sync] \u79DF\u6237 ${id} workspaceRoot \u5DF2\u540C\u6B65: ${tenant.workspaceRoot ?? "(\u6C99\u7BB1)"} \u2192 ${path32.resolve(workspace)}`);
17454
- tenant.workspaceRoot = path32.resolve(workspace);
18009
+ if (workspace && tenant.workspaceRoot !== path34.resolve(workspace)) {
18010
+ console.log(`[sync] \u79DF\u6237 ${id} workspaceRoot \u5DF2\u540C\u6B65: ${tenant.workspaceRoot ?? "(\u6C99\u7BB1)"} \u2192 ${path34.resolve(workspace)}`);
18011
+ tenant.workspaceRoot = path34.resolve(workspace);
17455
18012
  changed = true;
17456
18013
  }
17457
18014
  if (apiKey && tenant.provider.apiKey !== apiKey) {
@@ -17483,7 +18040,7 @@ function exitWhenOrphaned() {
17483
18040
  timer.unref();
17484
18041
  }
17485
18042
  function startServer(opts = {}) {
17486
- const dataDir = path32.resolve(opts.dataDir ?? env2("DATA_DIR") ?? DEFAULT_DATA_DIR);
18043
+ const dataDir = path34.resolve(opts.dataDir ?? env2("DATA_DIR") ?? DEFAULT_DATA_DIR);
17487
18044
  const port = opts.port ?? Number(env2("PORT") ?? 8787);
17488
18045
  const host = opts.host ?? env2("HOST") ?? "127.0.0.1";
17489
18046
  exitWhenOrphaned();
@@ -17513,7 +18070,7 @@ function startServer(opts = {}) {
17513
18070
  }
17514
18071
  const adminKey = ensureAdminKey();
17515
18072
  const secretKey = env2("SECRET_KEY");
17516
- const secretBox = secretKey ? SecretBox.fromHex(secretKey) : SecretBox.fromFile(path32.join(dataDir, "secret.key"));
18073
+ const secretBox = secretKey ? SecretBox.fromHex(secretKey) : SecretBox.fromFile(path34.join(dataDir, "secret.key"));
17517
18074
  if (secretKey && !secretBox) {
17518
18075
  console.warn("[crypto] SECRET_KEY \u975E\u6CD5\uFF08\u9700 64 \u4F4D hex\uFF09\uFF0C\u901A\u77E5\u51ED\u636E\u5C06\u9000\u5316\u4E3A\u660E\u6587");
17519
18076
  }
@@ -17742,7 +18299,7 @@ function printReady(info, qq) {
17742
18299
  `zqagent server \u76D1\u542C http://${info.host}:${info.port}`,
17743
18300
  `\u6570\u636E\u76EE\u5F55: ${info.dataDir}`,
17744
18301
  `\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`,
17745
- env2("ZQAGENT_LOG_ADMIN_KEY") === "0" ? `\u7BA1\u7406\u5458\u5BC6\u94A5: ${maskKey(info.adminKey)}\uFF08\u5DF2\u8131\u654F\uFF0C\u5B8C\u6574\u503C\u89C1 ${path32.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`
18302
+ env2("ZQAGENT_LOG_ADMIN_KEY") === "0" ? `\u7BA1\u7406\u5458\u5BC6\u94A5: ${maskKey(info.adminKey)}\uFF08\u5DF2\u8131\u654F\uFF0C\u5B8C\u6574\u503C\u89C1 ${path34.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`
17746
18303
  ];
17747
18304
  if (info.fleetNodeId) {
17748
18305
  lines.push(
@@ -17763,7 +18320,7 @@ function printReady(info, qq) {
17763
18320
 
17764
18321
  // src/cli/account-enroll.ts
17765
18322
  init_esm_shims();
17766
- import * as path33 from "path";
18323
+ import * as path35 from "path";
17767
18324
  import * as readline from "readline/promises";
17768
18325
  var SKIP_ANSWERS = /* @__PURE__ */ new Set(["-", "skip", "none", "no"]);
17769
18326
  function resolveDefaultHub(hubUrl2, env3 = process.env) {
@@ -17775,7 +18332,7 @@ function parseHubAnswer(answer, fallback) {
17775
18332
  return (a || fallback).replace(/\/$/, "");
17776
18333
  }
17777
18334
  function resolveDataDir(dataDir) {
17778
- return path33.resolve(dataDir ?? process.env.DATA_DIR ?? DEFAULT_DATA_DIR);
18335
+ return path35.resolve(dataDir ?? process.env.DATA_DIR ?? DEFAULT_DATA_DIR);
17779
18336
  }
17780
18337
  async function maybeAccountEnroll(opts = {}) {
17781
18338
  if (!process.stdin.isTTY || !process.stdout.isTTY) return;