@zqagent/core 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -569,9 +569,9 @@ var init_wire = __esm({
569
569
  // src/cli/index.ts
570
570
  init_cjs_shims();
571
571
  var import_config = require("dotenv/config");
572
- var fs32 = __toESM(require("fs"), 1);
572
+ var fs33 = __toESM(require("fs"), 1);
573
573
  var readline2 = __toESM(require("readline"), 1);
574
- var path40 = __toESM(require("path"), 1);
574
+ var path42 = __toESM(require("path"), 1);
575
575
  var import_child_process = require("child_process");
576
576
  var import_util = require("util");
577
577
 
@@ -937,8 +937,11 @@ function formatTokens(tokens) {
937
937
  return `${tokens}`;
938
938
  }
939
939
  var KEEP_RECENT_FRACTION = 0.25;
940
- var SPAN_TOOL_RESULT_CLIP = 400;
941
- var SPAN_BUDGET_CHARS = 4e5;
940
+ var SOFT_THRESHOLD_RATIO = 0.75;
941
+ var ELIDE_KEEP_FRACTION = 0.15;
942
+ var SUMMARY_MAX_TOKENS = 3e3;
943
+ var SPAN_TOOL_RESULT_CLIP = 200;
944
+ var SPAN_BUDGET_CHARS = 8e4;
942
945
  var USER_MESSAGE_CLIP = 600;
943
946
  var USER_MESSAGES_MAX = 40;
944
947
  var TRIM_FRACTION = 0.1;
@@ -961,9 +964,22 @@ function triggerTokens(contextWindow, thresholdPct = DEFAULT_THRESHOLD_PCT, capT
961
964
  }
962
965
  return base;
963
966
  }
967
+ function softTriggerTokens(contextWindow, thresholdPct = DEFAULT_THRESHOLD_PCT, capTokens, softThresholdPct) {
968
+ const hard = triggerTokens(contextWindow, thresholdPct, capTokens);
969
+ if (softThresholdPct != null && Number.isFinite(softThresholdPct) && softThresholdPct > 0) {
970
+ return Math.min(
971
+ triggerTokens(contextWindow, softThresholdPct, capTokens),
972
+ Math.floor(hard * 0.95)
973
+ );
974
+ }
975
+ return Math.floor(hard * SOFT_THRESHOLD_RATIO);
976
+ }
964
977
  function shouldCompact(signal, contextWindow, thresholdPct, capTokens) {
965
978
  return signal >= triggerTokens(contextWindow, thresholdPct, capTokens);
966
979
  }
980
+ function elisionKeepChars(hardTriggerTokens, fraction = ELIDE_KEEP_FRACTION) {
981
+ return Math.max(4e3, Math.floor(hardTriggerTokens * fraction * 4));
982
+ }
967
983
  function turnStarts(messages, start) {
968
984
  const users = [];
969
985
  const assistants = [];
@@ -1113,10 +1129,11 @@ var SUMMARY_SYSTEM_PROMPT = `\u4F60\u6B63\u5728\u538B\u7F29\u4E00\u4E2A AI \u534
1113
1129
 
1114
1130
  \u89C4\u5219\uFF1A
1115
1131
  - \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
1132
+ - \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
1116
1133
  - \u8981\u5177\u4F53\uFF1A\u8DEF\u5F84\u3001\u540D\u79F0\u3001\u547D\u4EE4\u3001id\u2014\u2014\u800C\u4E0D\u662F\u6A21\u7CCA\u7684\u5F15\u7528\u3002
1117
1134
  - \u4EC5\u8F93\u51FA\u6458\u8981\u7AE0\u8282\uFF0C\u4E0D\u9644\u52A0\u524D\u8A00\u3002`;
1118
1135
  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";
1119
- function renderSpan(span, budgetChars = SPAN_BUDGET_CHARS) {
1136
+ function renderSpan(span, budgetChars2 = SPAN_BUDGET_CHARS) {
1120
1137
  const lines = [];
1121
1138
  for (const m of span) {
1122
1139
  const role = m.role;
@@ -1140,8 +1157,8 @@ function renderSpan(span, budgetChars = SPAN_BUDGET_CHARS) {
1140
1157
  if (text) lines.push(`[${role}] ${text}`);
1141
1158
  }
1142
1159
  let rendered = lines.join("\n");
1143
- if (rendered.length > budgetChars) {
1144
- rendered = "(\u2026oldest turns elided\u2026)\n" + rendered.slice(-budgetChars);
1160
+ if (rendered.length > budgetChars2) {
1161
+ rendered = "(\u2026oldest turns elided\u2026)\n" + rendered.slice(-budgetChars2);
1145
1162
  }
1146
1163
  return rendered;
1147
1164
  }
@@ -1155,9 +1172,17 @@ function summarizerMessages(span, priorSummary = "") {
1155
1172
  { role: "user", content: body }
1156
1173
  ];
1157
1174
  }
1175
+ function summarizerReasoningEffort() {
1176
+ const v = process.env.COMPACTION_REASONING_EFFORT?.toLowerCase();
1177
+ if (v === "low" || v === "medium" || v === "high") return v;
1178
+ return "low";
1179
+ }
1158
1180
  async function summarizeSpan(provider, model, span, priorSummary = "", opts) {
1159
1181
  const msgs = summarizerMessages(span, priorSummary);
1160
- const chunks = await provider.stream(model, msgs, [], opts);
1182
+ const chunks = await provider.stream(model, msgs, [], {
1183
+ ...opts,
1184
+ maxTokens: SUMMARY_MAX_TOKENS
1185
+ });
1161
1186
  let text = "";
1162
1187
  for await (const chunk of chunks) {
1163
1188
  if (chunk.content) text += chunk.content;
@@ -1268,6 +1293,166 @@ function applyToOutbound(messages, state) {
1268
1293
  head.push({ role: "user", content: compactedBlock(state) });
1269
1294
  return [...head, ...messages.slice(boundary)];
1270
1295
  }
1296
+ var ELIDE_EXEMPT_TOOLS = /* @__PURE__ */ new Set([
1297
+ "explore",
1298
+ "ask_user",
1299
+ "propose_plan",
1300
+ "request_directory",
1301
+ "todo_write"
1302
+ ]);
1303
+ function elidedToolContent(m) {
1304
+ const name = m.tool_call_name || "tool";
1305
+ const size = m.content.length;
1306
+ const saved = /full output saved to ([^\]]+)/.exec(m.content)?.[1]?.trim();
1307
+ 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]`;
1308
+ }
1309
+ function isPlaceholder(content) {
1310
+ return content.startsWith("[old tool result elided") || content.startsWith("[superseded by a later read_file");
1311
+ }
1312
+ var ONE_SHOT_TOOLS = /* @__PURE__ */ new Set(["git_diff"]);
1313
+ var ONE_SHOT_KEEP_RECENT = 1;
1314
+ function applyOneShotElision(messages, keepRecent = ONE_SHOT_KEEP_RECENT, tools = ONE_SHOT_TOOLS) {
1315
+ const out = [...messages];
1316
+ let seen = 0;
1317
+ let elided = 0;
1318
+ let freed = 0;
1319
+ for (let i = messages.length - 1; i >= 0; i--) {
1320
+ const m = messages[i];
1321
+ if (m.role !== "tool" || !tools.has(m.tool_call_name ?? "")) continue;
1322
+ if (isPlaceholder(m.content)) continue;
1323
+ seen++;
1324
+ if (seen <= keepRecent) continue;
1325
+ const placeholder = elidedToolContent(m);
1326
+ freed += m.content.length - placeholder.length;
1327
+ out[i] = { ...m, content: placeholder };
1328
+ elided++;
1329
+ }
1330
+ return elided > 0 ? { messages: out, elided, freedChars: freed } : { messages, elided: 0, freedChars: 0 };
1331
+ }
1332
+ function applyToolResultElision(messages, keepChars, exempt = ELIDE_EXEMPT_TOOLS) {
1333
+ let total = 0;
1334
+ for (const m of messages) {
1335
+ if (m.role === "tool" && !exempt.has(m.tool_call_name ?? "")) {
1336
+ total += m.content.length;
1337
+ }
1338
+ }
1339
+ if (total <= keepChars) return { messages, elided: 0, freedChars: 0 };
1340
+ const out = [...messages];
1341
+ let budget = 0;
1342
+ let elided = 0;
1343
+ let freed = 0;
1344
+ for (let i = messages.length - 1; i >= 0; i--) {
1345
+ const m = messages[i];
1346
+ if (m.role !== "tool") continue;
1347
+ if (exempt.has(m.tool_call_name ?? "")) continue;
1348
+ if (elided === 0 && budget === 0 && m.content.length > keepChars) {
1349
+ const kept = m.content.slice(0, keepChars) + "\n\u2026(truncated)";
1350
+ freed += m.content.length - kept.length;
1351
+ out[i] = { ...m, content: kept };
1352
+ budget = keepChars;
1353
+ continue;
1354
+ }
1355
+ if (budget + m.content.length <= keepChars) {
1356
+ budget += m.content.length;
1357
+ continue;
1358
+ }
1359
+ const placeholder = elidedToolContent(m);
1360
+ freed += m.content.length - placeholder.length;
1361
+ out[i] = { ...m, content: placeholder };
1362
+ elided++;
1363
+ }
1364
+ return { messages: out, elided, freedChars: freed };
1365
+ }
1366
+ function normalizeToolPath(p) {
1367
+ let s = p.trim().replace(/\\/g, "/");
1368
+ while (s.startsWith("./")) s = s.slice(2);
1369
+ return s.replace(/\/{2,}/g, "/");
1370
+ }
1371
+ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
1372
+ "write_file",
1373
+ "replace_in_file",
1374
+ "apply_patch",
1375
+ "apply_unified_diff",
1376
+ "run_shell"
1377
+ ]);
1378
+ function parseReadFileSpan(content) {
1379
+ const body = content.replace(/^\[tool output truncated:[^\n]*\]\n\n/, "");
1380
+ let path43;
1381
+ let start;
1382
+ let end;
1383
+ try {
1384
+ const obj = JSON.parse(body);
1385
+ path43 = obj.path;
1386
+ start = obj.start_line;
1387
+ end = obj.end_line;
1388
+ } catch {
1389
+ const str2 = (key) => new RegExp(`"${key}"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)"`).exec(body)?.[1];
1390
+ const num2 = (key) => {
1391
+ const m = new RegExp(`"${key}"\\s*:\\s*(\\d+)`).exec(body);
1392
+ return m ? Number(m[1]) : void 0;
1393
+ };
1394
+ path43 = str2("path");
1395
+ start = num2("start_line");
1396
+ end = num2("end_line");
1397
+ }
1398
+ if (typeof path43 !== "string" || !path43) return null;
1399
+ if (typeof start !== "number" || typeof end !== "number") return null;
1400
+ if (!Number.isFinite(start) || !Number.isFinite(end)) return null;
1401
+ return { path: path43, start, end };
1402
+ }
1403
+ function addSpan(spans, start, end) {
1404
+ spans.push([start, end]);
1405
+ spans.sort((a, b) => a[0] - b[0]);
1406
+ const merged = [];
1407
+ for (const cur of spans) {
1408
+ const last = merged[merged.length - 1];
1409
+ if (last && cur[0] <= last[1] + 1) {
1410
+ last[1] = Math.max(last[1], cur[1]);
1411
+ } else {
1412
+ merged.push([...cur]);
1413
+ }
1414
+ }
1415
+ spans.length = 0;
1416
+ spans.push(...merged);
1417
+ }
1418
+ function coversSpans(spans, start, end) {
1419
+ return spans.some(([a, b]) => a <= start && b >= end);
1420
+ }
1421
+ function applyReadFileDedup(messages) {
1422
+ let candidates = 0;
1423
+ for (const m of messages) {
1424
+ if (m.role === "tool" && m.tool_call_name === "read_file") candidates++;
1425
+ }
1426
+ if (candidates < 2) return { messages, elided: 0 };
1427
+ const out = [...messages];
1428
+ const covered = /* @__PURE__ */ new Map();
1429
+ let elided = 0;
1430
+ for (let i = messages.length - 1; i >= 0; i--) {
1431
+ const m = messages[i];
1432
+ if (m.role === "assistant") {
1433
+ if (m.tool_calls?.some((tc) => MUTATING_TOOLS.has(tc.function.name))) {
1434
+ covered.clear();
1435
+ }
1436
+ continue;
1437
+ }
1438
+ if (m.role !== "tool" || m.tool_call_name !== "read_file") continue;
1439
+ const span = parseReadFileSpan(m.content);
1440
+ if (!span) continue;
1441
+ const key = normalizeToolPath(span.path);
1442
+ const seen = covered.get(key) ?? [];
1443
+ if (coversSpans(seen, span.start, span.end)) {
1444
+ out[i] = {
1445
+ ...m,
1446
+ content: `[superseded by a later read_file of ${span.path} (lines ${span.start}-${span.end})]`
1447
+ };
1448
+ elided++;
1449
+ continue;
1450
+ }
1451
+ addSpan(seen, span.start, span.end);
1452
+ covered.set(key, seen);
1453
+ }
1454
+ return { messages: elided > 0 ? out : messages, elided };
1455
+ }
1271
1456
  var OVERFLOW_MARKERS = [
1272
1457
  "context_length_exceeded",
1273
1458
  "maximum context length",
@@ -1283,9 +1468,116 @@ function isContextOverflow(exc) {
1283
1468
  return OVERFLOW_MARKERS.some((m) => text.includes(m));
1284
1469
  }
1285
1470
 
1471
+ // src/engine/toolResultBudget.ts
1472
+ init_cjs_shims();
1473
+ var fs = __toESM(require("fs"), 1);
1474
+ var path2 = __toESM(require("path"), 1);
1475
+
1476
+ // src/paths.ts
1477
+ init_cjs_shims();
1478
+ var os = __toESM(require("os"), 1);
1479
+ var path = __toESM(require("path"), 1);
1480
+ var APP_DIR_NAME = "zqagent";
1481
+ var DEFAULT_DATA_DIR = ".zqagent_data";
1482
+ var DEFAULT_HUB_URL = "https://hub.cd-zq.com";
1483
+ var IS_WINDOWS = process.platform === "win32";
1484
+ function homeDir() {
1485
+ return process.env.HOME || process.env.USERPROFILE || os.homedir() || os.tmpdir();
1486
+ }
1487
+ function configDir() {
1488
+ const base = process.env.XDG_CONFIG_HOME || (IS_WINDOWS ? process.env.APPDATA : void 0) || path.join(homeDir(), ".config");
1489
+ return path.join(base, APP_DIR_NAME);
1490
+ }
1491
+ function stateDir() {
1492
+ const base = process.env.XDG_STATE_HOME || (IS_WINDOWS ? process.env.LOCALAPPDATA : void 0) || path.join(homeDir(), ".local", "state");
1493
+ return path.join(base, APP_DIR_NAME);
1494
+ }
1495
+
1496
+ // src/engine/toolResultBudget.ts
1497
+ var DEFAULT_TOOL_RESULT_MAX_CHARS = 24e3;
1498
+ var HEAD_RATIO = 0.6;
1499
+ function budgetChars() {
1500
+ const n = Number(process.env.TOOL_RESULT_MAX_CHARS);
1501
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : DEFAULT_TOOL_RESULT_MAX_CHARS;
1502
+ }
1503
+ function toolResultDir() {
1504
+ const env3 = process.env.TOOL_RESULT_DIR?.trim();
1505
+ return env3 || path2.join(stateDir(), "tool-results");
1506
+ }
1507
+ function clipText(text, budget) {
1508
+ if (text.length <= budget) return text;
1509
+ const room = Math.max(
1510
+ 0,
1511
+ budget - `
1512
+
1513
+ \u2026 [${text.length} characters omitted] \u2026
1514
+
1515
+ `.length
1516
+ );
1517
+ const headLen = Math.floor(room * HEAD_RATIO);
1518
+ const tailLen = room - headLen;
1519
+ const omitted = text.length - headLen - tailLen;
1520
+ return text.slice(0, headLen) + `
1521
+
1522
+ \u2026 [${omitted} characters omitted] \u2026
1523
+
1524
+ ` + text.slice(-tailLen);
1525
+ }
1526
+ function clipDeep(value, perField) {
1527
+ if (typeof value === "string") {
1528
+ return value.length <= perField ? value : clipText(value, perField);
1529
+ }
1530
+ if (Array.isArray(value)) {
1531
+ return value.map((v) => clipDeep(v, perField));
1532
+ }
1533
+ if (value && typeof value === "object") {
1534
+ const out = {};
1535
+ for (const [k, v] of Object.entries(value)) out[k] = clipDeep(v, perField);
1536
+ return out;
1537
+ }
1538
+ return value;
1539
+ }
1540
+ function saveRaw(tool, raw, dir) {
1541
+ try {
1542
+ fs.mkdirSync(dir, { recursive: true });
1543
+ const safe2 = tool.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 40) || "tool";
1544
+ const name = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}-${safe2}.txt`;
1545
+ const file = path2.join(dir, name);
1546
+ fs.writeFileSync(file, raw, "utf-8");
1547
+ return file;
1548
+ } catch {
1549
+ return void 0;
1550
+ }
1551
+ }
1552
+ function applyToolResultBudget(tool, result, opts = {}) {
1553
+ const budget = opts.maxChars ?? budgetChars();
1554
+ const raw = typeof result === "string" ? result : JSON.stringify(result, null, 2) ?? String(result);
1555
+ if (raw.length <= budget) {
1556
+ return { content: raw, truncated: false };
1557
+ }
1558
+ let content = raw;
1559
+ if (typeof result === "object" && result !== null) {
1560
+ let perField = Math.floor(budget * 0.7);
1561
+ let clipped = JSON.stringify(clipDeep(result, perField), null, 2);
1562
+ for (let i = 0; i < 6 && clipped && clipped.length > budget && perField > 128; i++) {
1563
+ perField = Math.floor(perField / 2);
1564
+ clipped = JSON.stringify(clipDeep(result, perField), null, 2);
1565
+ }
1566
+ if (clipped && clipped.length <= budget) content = clipped;
1567
+ }
1568
+ if (content.length > budget) content = clipText(content, budget);
1569
+ const savedPath = saveRaw(tool, raw, opts.dir ?? toolResultDir());
1570
+ if (savedPath) {
1571
+ content = `[tool output truncated: ${raw.length} chars, full output saved to ${savedPath}]
1572
+
1573
+ ` + content;
1574
+ }
1575
+ return { content, truncated: true, savedPath };
1576
+ }
1577
+
1286
1578
  // src/engine/TurnEngine.ts
1287
1579
  var crypto = __toESM(require("crypto"), 1);
1288
- var fs = __toESM(require("fs"), 1);
1580
+ var fs2 = __toESM(require("fs"), 1);
1289
1581
  var _path = __toESM(require("path"), 1);
1290
1582
 
1291
1583
  // src/prompts/index.ts
@@ -1411,13 +1703,17 @@ var INTERACTIVE_TOOLS = /* @__PURE__ */ new Set([
1411
1703
  "propose_plan",
1412
1704
  "request_directory"
1413
1705
  ]);
1414
- function toolResultMessage(tc, result, taskId) {
1415
- const content = typeof result === "string" ? result : JSON.stringify(result, null, 2);
1706
+ var MAX_COMPACT_ROUNDS = 4;
1707
+ var OVERFLOW_TARGET_RATIO = 0.5;
1708
+ var OVERFLOW_TRIM_FRACTION = 0.5;
1709
+ var COMPACT_STALL_RETRY_DELTA = 20;
1710
+ function toolResultMessage(tc, result, taskId, toolResultDir2) {
1711
+ const budgeted = applyToolResultBudget(tc.name, result, { dir: toolResultDir2 });
1416
1712
  return {
1417
1713
  role: "tool",
1418
1714
  tool_call_id: tc.id,
1419
1715
  tool_call_name: tc.name,
1420
- content,
1716
+ content: budgeted.content,
1421
1717
  taskId
1422
1718
  };
1423
1719
  }
@@ -1528,12 +1824,22 @@ var TurnEngine = class _TurnEngine {
1528
1824
  // 上下文压缩
1529
1825
  compactionState = null;
1530
1826
  _lastContextTokens = null;
1827
+ /** 压缩无进展时的消息条数快照;-1 表示无冷却 */
1828
+ _compactStalledAt = -1;
1531
1829
  /** 显式配置的上下文窗口大小(token 数),未设置时为 undefined(走自动检测) */
1532
1830
  _contextWindow;
1533
1831
  /** 压缩触发阈值比例(默认 0.8) */
1534
1832
  _thresholdPct;
1833
+ /** 软阈值比例(undefined = 取硬阈值的 75%) */
1834
+ _softThresholdPct;
1835
+ /** 是否启用软阈值淘汰(COMPACTION_ELIDE=0 可关闭) */
1836
+ _elisionEnabled;
1837
+ /** 是否启用重复读取去重(COMPACTION_DEDUP=0 可关闭) */
1838
+ _dedupEnabled;
1535
1839
  /** 压缩触发阈值绝对上限(null = 不限制) */
1536
1840
  _capTokens;
1841
+ /** 超预算工具结果的落盘目录(undefined = 使用默认状态目录) */
1842
+ _toolResultDir;
1537
1843
  // 产出文件追踪(本轮 Turn 中成功写入的文件)
1538
1844
  _producedFiles = [];
1539
1845
  _turnStartTime = 0;
@@ -1585,7 +1891,15 @@ var TurnEngine = class _TurnEngine {
1585
1891
  this._hook = opts.hook;
1586
1892
  this._contextWindow = opts.contextWindow;
1587
1893
  this._thresholdPct = opts.thresholdPct ?? DEFAULT_THRESHOLD_PCT;
1894
+ this._softThresholdPct = opts.softThresholdPct;
1895
+ this._elisionEnabled = !/^(0|false|off|no)$/i.test(
1896
+ (process.env.COMPACTION_ELIDE ?? "").trim()
1897
+ );
1898
+ this._dedupEnabled = !/^(0|false|off|no)$/i.test(
1899
+ (process.env.COMPACTION_DEDUP ?? "").trim()
1900
+ );
1588
1901
  this._capTokens = opts.capTokens ?? null;
1902
+ this._toolResultDir = opts.toolResultDir;
1589
1903
  this._reasoningEffort = opts.reasoningEffort;
1590
1904
  if (this.messages.length === 0 || this.messages[0].role !== "system") {
1591
1905
  this.messages.unshift({ role: "system", content: this.instructions });
@@ -1641,6 +1955,7 @@ var TurnEngine = class _TurnEngine {
1641
1955
  this._cancel = false;
1642
1956
  this._producedFiles = [];
1643
1957
  this._turnStartTime = Date.now();
1958
+ this._compactStalledAt = -1;
1644
1959
  this._emitHook((h) => h.onTurnStart?.(userInput));
1645
1960
  yield {
1646
1961
  type: "turn_start" /* TURN_START */,
@@ -2357,7 +2672,9 @@ var TurnEngine = class _TurnEngine {
2357
2672
  for (let i = 0; i < concurrent.length; i++) {
2358
2673
  const tc = concurrent[i];
2359
2674
  const { result, status } = results[i];
2360
- this.messages.push(toolResultMessage(tc, result, this.taskId));
2675
+ this.messages.push(
2676
+ toolResultMessage(tc, result, this.taskId, this._toolResultDir)
2677
+ );
2361
2678
  this._trackProducedFile(
2362
2679
  tc,
2363
2680
  result,
@@ -2420,7 +2737,9 @@ var TurnEngine = class _TurnEngine {
2420
2737
  };
2421
2738
  status = "error";
2422
2739
  }
2423
- this.messages.push(toolResultMessage(tc, result, this.taskId));
2740
+ this.messages.push(
2741
+ toolResultMessage(tc, result, this.taskId, this._toolResultDir)
2742
+ );
2424
2743
  this._trackProducedFile(
2425
2744
  tc,
2426
2745
  result,
@@ -2455,13 +2774,41 @@ var TurnEngine = class _TurnEngine {
2455
2774
  * 过滤 notice 消息;tool 消息只保留必要字段;
2456
2775
  * 其余消息剥离 display-only sidecar 字段(source/_display/ts/
2457
2776
  * reasoning/usage),避免污染模型上下文。
2777
+ *
2778
+ * 达到软阈值时先淘汰旧工具结果(零 LLM 成本),再产出最终视图。
2458
2779
  */
2459
2780
  _outboundMessages() {
2460
- const _STRIP = /* @__PURE__ */ new Set(["source", "_display", "ts", "reasoning", "usage"]);
2461
- const sourceMessages = applyToOutbound(
2462
- this.messages,
2463
- this.compactionState
2781
+ const base = applyToOutbound(this.messages, this.compactionState);
2782
+ const deduped = this._dedupEnabled ? applyReadFileDedup(base).messages : base;
2783
+ let view = this._finalizeOutbound(deduped);
2784
+ if (!this._elisionEnabled) return view;
2785
+ if (estimateTokens(view) < this._softTriggerTokens()) {
2786
+ return view;
2787
+ }
2788
+ const oneShot = applyOneShotElision(deduped);
2789
+ if (oneShot.freedChars > 0) {
2790
+ view = this._finalizeOutbound(oneShot.messages);
2791
+ if (estimateTokens(view) < this._softTriggerTokens()) {
2792
+ return view;
2793
+ }
2794
+ }
2795
+ const keepChars = elisionKeepChars(
2796
+ triggerTokens(
2797
+ this._getContextWindow(),
2798
+ this._thresholdPct,
2799
+ this._capTokens
2800
+ )
2801
+ );
2802
+ const elided = applyToolResultElision(
2803
+ oneShot.messages,
2804
+ keepChars
2464
2805
  );
2806
+ if (elided.freedChars === 0) return view;
2807
+ return this._finalizeOutbound(elided.messages);
2808
+ }
2809
+ /** 过滤 notice + 剥离 display-only 字段,得到最终发送视图 */
2810
+ _finalizeOutbound(sourceMessages) {
2811
+ const _STRIP = /* @__PURE__ */ new Set(["source", "_display", "ts", "reasoning", "usage"]);
2465
2812
  return sourceMessages.filter((m) => m.role !== "notice").map((m) => {
2466
2813
  if (m.role === "tool") {
2467
2814
  return {
@@ -2479,6 +2826,15 @@ var TurnEngine = class _TurnEngine {
2479
2826
  return stripped;
2480
2827
  });
2481
2828
  }
2829
+ /** 软阈值(token 数):达到它只淘汰旧工具结果 */
2830
+ _softTriggerTokens() {
2831
+ return softTriggerTokens(
2832
+ this._getContextWindow(),
2833
+ this._thresholdPct,
2834
+ this._capTokens,
2835
+ this._softThresholdPct
2836
+ );
2837
+ }
2482
2838
  /** 通知消息:仅用于日志/展示,不发给模型 */
2483
2839
  _appendNotice(kind, text) {
2484
2840
  this.messages.push({
@@ -2520,18 +2876,36 @@ var TurnEngine = class _TurnEngine {
2520
2876
  return false;
2521
2877
  }
2522
2878
  // ===================== 自动压缩 =====================
2523
- /** 当前上下文 token 用量:优先采信 API 返回的精确数量,否则用 chars/4 估算 */
2879
+ /**
2880
+ * 当前上下文 token 用量。
2881
+ *
2882
+ * API 返回的 `usage.input` 是精确值,但反映的是「上一次请求」——工具结果
2883
+ * 与淘汰/压缩都会让它过时。因此同时估算当前 outbound 视图,取较大者:
2884
+ * 宁可早一点压缩,也不要漏判导致溢出。
2885
+ */
2524
2886
  _compactionTokens() {
2525
- if (this._lastContextTokens != null) {
2526
- return { tokens: this._lastContextTokens, estimated: false };
2887
+ const estimated = estimateTokens(this._outboundMessages());
2888
+ if (this._lastContextTokens == null) {
2889
+ return { tokens: estimated, estimated: true };
2527
2890
  }
2528
- return {
2529
- tokens: estimateTokens(this._outboundMessages()),
2530
- estimated: true
2531
- };
2891
+ if (estimated > this._lastContextTokens) {
2892
+ return { tokens: estimated, estimated: true };
2893
+ }
2894
+ return { tokens: this._lastContextTokens, estimated: false };
2895
+ }
2896
+ /** 压缩后重置精确计数:后续按估算值判断 */
2897
+ _resetContextTokens() {
2898
+ this._lastContextTokens = null;
2899
+ }
2900
+ /** 当前 outbound 视图的估算 token 数 */
2901
+ _estimateContextTokens() {
2902
+ return estimateTokens(this._outboundMessages());
2532
2903
  }
2533
2904
  /** 检查是否需要压缩(每轮 LLM 调用前) */
2534
2905
  _compactionDue() {
2906
+ if (this._compactStalledAt >= 0 && this.messages.length < this._compactStalledAt + COMPACT_STALL_RETRY_DELTA) {
2907
+ return false;
2908
+ }
2535
2909
  const { tokens } = this._compactionTokens();
2536
2910
  return shouldCompact(
2537
2911
  tokens,
@@ -2578,63 +2952,82 @@ var TurnEngine = class _TurnEngine {
2578
2952
  async _compactNow(reqId, force = false) {
2579
2953
  const contextWindow = this._getContextWindow();
2580
2954
  const messagesBefore = this._outboundMessages().length;
2581
- const keep = Math.floor(
2582
- KEEP_RECENT_FRACTION * triggerTokens(
2583
- contextWindow,
2584
- this._thresholdPct,
2585
- this._capTokens
2586
- )
2955
+ const trigger = triggerTokens(
2956
+ contextWindow,
2957
+ this._thresholdPct,
2958
+ this._capTokens
2587
2959
  );
2588
- const model = this.model;
2589
- let state = null;
2590
- let failed2 = false;
2591
- for (let attempt = 0; attempt < 2; attempt++) {
2592
- try {
2593
- const logTime = (/* @__PURE__ */ new Date()).toISOString();
2594
- state = await buildState(
2960
+ const target = Math.floor(
2961
+ trigger * (force ? OVERFLOW_TARGET_RATIO : 0.9)
2962
+ );
2963
+ const model = process.env.COMPACTION_MODEL?.trim() || this.model;
2964
+ const reasoningEffort = summarizerReasoningEffort();
2965
+ let keep = Math.floor(KEEP_RECENT_FRACTION * trigger);
2966
+ let method = null;
2967
+ let lastState = null;
2968
+ let progressed = false;
2969
+ for (let round = 0; round < MAX_COMPACT_ROUNDS; round++) {
2970
+ if (round > 0 && this._estimateContextTokens() <= target) break;
2971
+ let state = null;
2972
+ for (let attempt = 0; attempt < 2 && !state; attempt++) {
2973
+ try {
2974
+ const logTime = (/* @__PURE__ */ new Date()).toISOString();
2975
+ state = await buildState(
2976
+ this.messages,
2977
+ this.provider,
2978
+ model,
2979
+ keep,
2980
+ this.compactionState,
2981
+ { reqId, logTime, reasoningEffort }
2982
+ );
2983
+ } catch {
2984
+ }
2985
+ }
2986
+ if (state) {
2987
+ this.compactionState = state;
2988
+ lastState = state;
2989
+ method = "summarized";
2990
+ progressed = true;
2991
+ } else {
2992
+ const trimmed = trimState(
2595
2993
  this.messages,
2596
- this.provider,
2597
- model,
2598
- keep,
2599
2994
  this.compactionState,
2600
- { reqId, logTime, reasoningEffort: this._reasoningEffort }
2995
+ force ? OVERFLOW_TRIM_FRACTION : 0.25
2601
2996
  );
2602
- failed2 = false;
2603
- break;
2604
- } catch {
2605
- failed2 = true;
2606
- }
2607
- }
2608
- if (state) {
2609
- this.compactionState = state;
2610
- this._lastContextTokens = null;
2997
+ if (!trimmed) break;
2998
+ this.compactionState = trimmed;
2999
+ lastState = trimmed;
3000
+ method = "trimmed";
3001
+ progressed = true;
3002
+ }
3003
+ this._resetContextTokens();
3004
+ if (this._estimateContextTokens() <= trigger) break;
3005
+ keep = Math.max(Math.floor(trigger * 0.05), Math.floor(keep / 2));
3006
+ }
3007
+ if (!progressed || !lastState) return { notice: null, method: null };
3008
+ this._compactStalledAt = this._estimateContextTokens() >= trigger ? this.messages.length : -1;
3009
+ const messagesAfter = this._outboundMessages().length;
3010
+ const common = {
3011
+ boundaryIndex: lastState.boundaryIndex,
3012
+ messagesBefore,
3013
+ messagesAfter
3014
+ };
3015
+ if (method === "summarized") {
2611
3016
  return {
2612
3017
  notice: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29 \u2014 \u65E9\u671F\u8F6E\u6B21\u5DF2\u6458\u8981",
2613
3018
  method: "summarized",
2614
- modelUsed: state.modelUsed,
2615
- boundaryIndex: state.boundaryIndex,
2616
- summaryPreview: state.summaryText.slice(0, 200),
2617
- keptUserMessages: state.userMessages.length,
2618
- droppedUserMessages: state.userMessagesDropped,
2619
- messagesBefore,
2620
- messagesAfter: this._outboundMessages().length
3019
+ modelUsed: lastState.modelUsed,
3020
+ summaryPreview: lastState.summaryText.slice(0, 200),
3021
+ keptUserMessages: lastState.userMessages.length,
3022
+ droppedUserMessages: lastState.userMessagesDropped,
3023
+ ...common
2621
3024
  };
2622
3025
  }
2623
- if (failed2 || force) {
2624
- const trimmed = trimState(this.messages, this.compactionState);
2625
- if (trimmed) {
2626
- this.compactionState = trimmed;
2627
- this._lastContextTokens = null;
2628
- return {
2629
- notice: "\u4E0A\u4E0B\u6587\u5DF2\u88C1\u526A \u2014 \u6700\u65E7\u7684\u8F6E\u6B21\u5DF2\u4E22\u5F03\uFF08\u6458\u8981\u4E0D\u53EF\u7528\uFF09",
2630
- method: "trimmed",
2631
- boundaryIndex: trimmed.boundaryIndex,
2632
- messagesBefore,
2633
- messagesAfter: this._outboundMessages().length
2634
- };
2635
- }
2636
- }
2637
- return { notice: null, method: null };
3026
+ return {
3027
+ notice: "\u4E0A\u4E0B\u6587\u5DF2\u88C1\u526A \u2014 \u6700\u65E7\u7684\u8F6E\u6B21\u5DF2\u4E22\u5F03\uFF08\u6458\u8981\u4E0D\u53EF\u7528\uFF09",
3028
+ method: "trimmed",
3029
+ ...common
3030
+ };
2638
3031
  }
2639
3032
  /** 追踪文件写入工具成功产生的文件 */
2640
3033
  _trackProducedFile(tc, result, status) {
@@ -2644,9 +3037,9 @@ var TurnEngine = class _TurnEngine {
2644
3037
  return;
2645
3038
  }
2646
3039
  if (result && typeof result === "object") {
2647
- const path41 = result.path;
2648
- if (path41) {
2649
- this._producedFiles.push(path41);
3040
+ const path43 = result.path;
3041
+ if (path43) {
3042
+ this._producedFiles.push(path43);
2650
3043
  return;
2651
3044
  }
2652
3045
  const changed = result.changed_files;
@@ -2670,7 +3063,7 @@ var TurnEngine = class _TurnEngine {
2670
3063
  _fileExists(f, ws) {
2671
3064
  if (!ws && !_path.isAbsolute(f)) return true;
2672
3065
  try {
2673
- return fs.existsSync(_path.isAbsolute(f) ? f : _path.join(ws, f));
3066
+ return fs2.existsSync(_path.isAbsolute(f) ? f : _path.join(ws, f));
2674
3067
  } catch {
2675
3068
  return false;
2676
3069
  }
@@ -2693,7 +3086,7 @@ var TurnEngine = class _TurnEngine {
2693
3086
  if (depth > 4 || results.length >= 50) return;
2694
3087
  let entries;
2695
3088
  try {
2696
- entries = fs.readdirSync(dir, { withFileTypes: true });
3089
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
2697
3090
  } catch {
2698
3091
  return;
2699
3092
  }
@@ -2708,7 +3101,7 @@ var TurnEngine = class _TurnEngine {
2708
3101
  const ext = dotIdx !== -1 ? e.name.slice(dotIdx).toLowerCase() : "";
2709
3102
  if (!_TurnEngine.SCAN_SUFFIXES.has(ext)) continue;
2710
3103
  try {
2711
- const st = fs.statSync(full);
3104
+ const st = fs2.statSync(full);
2712
3105
  if (st.mtimeMs >= since) {
2713
3106
  results.push(
2714
3107
  _path.relative(_path.resolve(ws), _path.resolve(full))
@@ -2730,8 +3123,8 @@ var TurnEngine = class _TurnEngine {
2730
3123
 
2731
3124
  // src/providers/OpenAIProvider.ts
2732
3125
  init_cjs_shims();
2733
- var fs2 = __toESM(require("fs"), 1);
2734
- var path = __toESM(require("path"), 1);
3126
+ var fs3 = __toESM(require("fs"), 1);
3127
+ var path3 = __toESM(require("path"), 1);
2735
3128
  var import_openai = __toESM(require("openai"), 1);
2736
3129
 
2737
3130
  // src/terminal.ts
@@ -2917,7 +3310,7 @@ var OpenAIProvider = class {
2917
3310
  });
2918
3311
  if (opts.logDir) {
2919
3312
  this._logDir = opts.logDir;
2920
- fs2.mkdirSync(opts.logDir, { recursive: true });
3313
+ fs3.mkdirSync(opts.logDir, { recursive: true });
2921
3314
  }
2922
3315
  }
2923
3316
  // =====================================================================
@@ -3070,6 +3463,8 @@ var OpenAIProvider = class {
3070
3463
  tools: this._toResponsesTools(tools, model),
3071
3464
  // 推理强度:DeepSeek 不支持,跳过
3072
3465
  ...opts.reasoningEffort && !this._isDeepSeek(model) ? { reasoning: { effort: opts.reasoningEffort } } : {},
3466
+ // 限制输出长度(摘要器等轻量请求)
3467
+ ...opts.maxTokens ? { max_output_tokens: opts.maxTokens } : {},
3073
3468
  stream: true
3074
3469
  }, requestOptions);
3075
3470
  return this._responsesStream(reqId, startTime, rawStream, {
@@ -3168,6 +3563,8 @@ var OpenAIProvider = class {
3168
3563
  })),
3169
3564
  // 推理强度:DeepSeek 不支持,跳过
3170
3565
  ...opts.reasoningEffort && !this._isDeepSeek(model) ? { reasoning_effort: opts.reasoningEffort } : {},
3566
+ // 限制输出长度:o 系列 / gpt-5 推理模型仅支持 max_completion_tokens
3567
+ ...opts.maxTokens ? isReasoningModel(model) ? { max_completion_tokens: opts.maxTokens } : { max_tokens: opts.maxTokens } : {},
3171
3568
  stream: true,
3172
3569
  // 要求返回 Token 用量统计(在流结束前的一个 chunk 中携带)
3173
3570
  stream_options: { include_usage: true }
@@ -3281,8 +3678,8 @@ var OpenAIProvider = class {
3281
3678
  if (!this._logDir) return;
3282
3679
  try {
3283
3680
  const today = logTime?.split("T")[0];
3284
- const dir = path.join(this._logDir, today);
3285
- fs2.mkdirSync(dir, { recursive: true });
3681
+ const dir = path3.join(this._logDir, today);
3682
+ fs3.mkdirSync(dir, { recursive: true });
3286
3683
  const entry = {
3287
3684
  reqId,
3288
3685
  ts: logTime,
@@ -3294,8 +3691,8 @@ var OpenAIProvider = class {
3294
3691
  toolNames: tools?.map((t) => t.function.name) ?? []
3295
3692
  }
3296
3693
  };
3297
- fs2.writeFileSync(
3298
- path.join(dir, `${reqId}.json`),
3694
+ fs3.writeFileSync(
3695
+ path3.join(dir, `${reqId}.json`),
3299
3696
  JSON.stringify(entry, null, 2),
3300
3697
  "utf-8"
3301
3698
  );
@@ -3314,9 +3711,9 @@ var OpenAIProvider = class {
3314
3711
  if (!this._logDir) return;
3315
3712
  try {
3316
3713
  const today = logTime?.split("T")[0];
3317
- const filePath = path.join(this._logDir, today, `${reqId}.json`);
3318
- if (!fs2.existsSync(filePath)) return;
3319
- const entry = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
3714
+ const filePath = path3.join(this._logDir, today, `${reqId}.json`);
3715
+ if (!fs3.existsSync(filePath)) return;
3716
+ const entry = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
3320
3717
  entry.elapsedMs = Date.now() - startTime;
3321
3718
  if (data.response) {
3322
3719
  entry.response = data.response;
@@ -3324,7 +3721,7 @@ var OpenAIProvider = class {
3324
3721
  if (data.error) {
3325
3722
  entry.error = data.error;
3326
3723
  }
3327
- fs2.writeFileSync(filePath, JSON.stringify(entry, null, 2), "utf-8");
3724
+ fs3.writeFileSync(filePath, JSON.stringify(entry, null, 2), "utf-8");
3328
3725
  } catch (e) {
3329
3726
  console.error(e);
3330
3727
  }
@@ -3787,62 +4184,62 @@ init_cjs_shims();
3787
4184
 
3788
4185
  // src/tools/pathSafety.ts
3789
4186
  init_cjs_shims();
3790
- var fs3 = __toESM(require("fs"), 1);
3791
- var path2 = __toESM(require("path"), 1);
4187
+ var fs4 = __toESM(require("fs"), 1);
4188
+ var path4 = __toESM(require("path"), 1);
3792
4189
  function isInsideRelative(rel) {
3793
- return !path2.isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${path2.sep}`);
4190
+ return !path4.isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${path4.sep}`);
3794
4191
  }
3795
4192
  function isFilesystemRoot(p) {
3796
- return path2.parse(p).root === p;
4193
+ return path4.parse(p).root === p;
3797
4194
  }
3798
4195
  function resolveWithinRoot(workspace, target) {
3799
- const abs = path2.resolve(workspace, target);
4196
+ const abs = path4.resolve(workspace, target);
3800
4197
  let rootReal;
3801
4198
  try {
3802
- rootReal = fs3.realpathSync(workspace);
4199
+ rootReal = fs4.realpathSync(workspace);
3803
4200
  } catch {
3804
- const rootAbs = path2.resolve(workspace);
3805
- if (!isInsideRelative(path2.relative(rootAbs, abs))) return null;
4201
+ const rootAbs = path4.resolve(workspace);
4202
+ if (!isInsideRelative(path4.relative(rootAbs, abs))) return null;
3806
4203
  return { root: rootAbs, real: abs };
3807
4204
  }
3808
4205
  if (isFilesystemRoot(rootReal)) return null;
3809
4206
  let real;
3810
4207
  try {
3811
- real = fs3.realpathSync(abs);
4208
+ real = fs4.realpathSync(abs);
3812
4209
  } catch {
3813
4210
  let probe = abs;
3814
4211
  const tail = [];
3815
4212
  for (; ; ) {
3816
- const exists = fs3.existsSync(probe) || fs3.lstatSync(probe, { throwIfNoEntry: false }) !== void 0;
4213
+ const exists = fs4.existsSync(probe) || fs4.lstatSync(probe, { throwIfNoEntry: false }) !== void 0;
3817
4214
  if (exists) {
3818
4215
  try {
3819
- real = path2.join(fs3.realpathSync(probe), ...tail);
4216
+ real = path4.join(fs4.realpathSync(probe), ...tail);
3820
4217
  break;
3821
4218
  } catch {
3822
4219
  return null;
3823
4220
  }
3824
4221
  }
3825
- tail.unshift(path2.basename(probe));
3826
- const parent = path2.dirname(probe);
4222
+ tail.unshift(path4.basename(probe));
4223
+ const parent = path4.dirname(probe);
3827
4224
  if (parent === probe) return null;
3828
4225
  probe = parent;
3829
4226
  }
3830
4227
  }
3831
- if (!isInsideRelative(path2.relative(rootReal, real))) return null;
4228
+ if (!isInsideRelative(path4.relative(rootReal, real))) return null;
3832
4229
  return { root: rootReal, real };
3833
4230
  }
3834
4231
  function isWithinRoot(workspace, target) {
3835
4232
  return resolveWithinRoot(workspace, target) !== null;
3836
4233
  }
3837
4234
  function resolveEntryWithinRoot(workspace, target) {
3838
- const abs = path2.resolve(workspace, target);
3839
- const parent = resolveWithinRoot(workspace, path2.dirname(abs));
4235
+ const abs = path4.resolve(workspace, target);
4236
+ const parent = resolveWithinRoot(workspace, path4.dirname(abs));
3840
4237
  if (!parent) return null;
3841
- return { root: parent.root, real: path2.join(parent.real, path2.basename(abs)) };
4238
+ return { root: parent.root, real: path4.join(parent.real, path4.basename(abs)) };
3842
4239
  }
3843
4240
 
3844
4241
  // src/permissions/PermissionEngine.ts
3845
- var path3 = __toESM(require("path"), 1);
4242
+ var path5 = __toESM(require("path"), 1);
3846
4243
  var READ_ONLY_MODES = /* @__PURE__ */ new Set(["discuss" /* DISCUSS */, "plan" /* PLAN */]);
3847
4244
  var WRITE_TOOLS = /* @__PURE__ */ new Set([
3848
4245
  "write_file",
@@ -3892,7 +4289,7 @@ var PermissionEngine = class {
3892
4289
  /** 会话级「总是允许此命令」集合 */
3893
4290
  sessionAllowCommands;
3894
4291
  constructor(opts) {
3895
- this.workspaceRoot = path3.resolve(opts.workspaceRoot);
4292
+ this.workspaceRoot = path5.resolve(opts.workspaceRoot);
3896
4293
  this.mode = opts.mode;
3897
4294
  this.allowedCommands = Array.isArray(opts.allowedCommands) ? opts.allowedCommands : [];
3898
4295
  this.sessionAllowTools = /* @__PURE__ */ new Set();
@@ -4818,14 +5215,14 @@ function approvalToHtmlTable(input, maxLen = APPROVAL_VALUE_MAX) {
4818
5215
 
4819
5216
  // src/engine/FileLogHook.ts
4820
5217
  init_cjs_shims();
4821
- var fs4 = __toESM(require("fs"), 1);
4822
- var path4 = __toESM(require("path"), 1);
5218
+ var fs5 = __toESM(require("fs"), 1);
5219
+ var path6 = __toESM(require("path"), 1);
4823
5220
  var FileLogHook = class {
4824
5221
  constructor(sessionId, stateDir2) {
4825
5222
  this.sessionId = sessionId;
4826
- fs4.mkdirSync(stateDir2, { recursive: true });
4827
- this._logPath = path4.join(stateDir2, `${sessionId}.jsonl`);
4828
- this._statePath = path4.join(stateDir2, `${sessionId}_state.json`);
5223
+ fs5.mkdirSync(stateDir2, { recursive: true });
5224
+ this._logPath = path6.join(stateDir2, `${sessionId}.jsonl`);
5225
+ this._statePath = path6.join(stateDir2, `${sessionId}_state.json`);
4829
5226
  }
4830
5227
  sessionId;
4831
5228
  _logPath;
@@ -4919,13 +5316,13 @@ var FileLogHook = class {
4919
5316
  ...meta?.nameSource !== void 0 ? { nameSource: meta.nameSource } : {},
4920
5317
  ...meta?.taskCount !== void 0 ? { taskCount: meta.taskCount } : {}
4921
5318
  };
4922
- fs4.writeFileSync(this._statePath, JSON.stringify(state, null, 2), "utf-8");
5319
+ fs5.writeFileSync(this._statePath, JSON.stringify(state, null, 2), "utf-8");
4923
5320
  }
4924
5321
  /** 读取保存的引擎状态 */
4925
5322
  static loadState(sessionId, stateDir2) {
4926
- const p = path4.join(stateDir2, `${sessionId}_state.json`);
5323
+ const p = path6.join(stateDir2, `${sessionId}_state.json`);
4927
5324
  try {
4928
- const raw = fs4.readFileSync(p, "utf-8");
5325
+ const raw = fs5.readFileSync(p, "utf-8");
4929
5326
  return JSON.parse(raw);
4930
5327
  } catch (e) {
4931
5328
  console.error(e);
@@ -4934,9 +5331,9 @@ var FileLogHook = class {
4934
5331
  }
4935
5332
  /** 读取事件日志 */
4936
5333
  static loadEvents(sessionId, stateDir2) {
4937
- const p = path4.join(stateDir2, `${sessionId}.jsonl`);
5334
+ const p = path6.join(stateDir2, `${sessionId}.jsonl`);
4938
5335
  try {
4939
- const raw = fs4.readFileSync(p, "utf-8");
5336
+ const raw = fs5.readFileSync(p, "utf-8");
4940
5337
  return raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
4941
5338
  } catch (e) {
4942
5339
  console.error(e);
@@ -4947,7 +5344,7 @@ var FileLogHook = class {
4947
5344
  _append(entry) {
4948
5345
  entry["ts"] = (/* @__PURE__ */ new Date()).toISOString();
4949
5346
  try {
4950
- fs4.appendFileSync(this._logPath, JSON.stringify(entry) + "\n", "utf-8");
5347
+ fs5.appendFileSync(this._logPath, JSON.stringify(entry) + "\n", "utf-8");
4951
5348
  } catch (e) {
4952
5349
  console.error(e);
4953
5350
  }
@@ -5375,12 +5772,12 @@ var McpManager = class {
5375
5772
 
5376
5773
  // src/tools/files.ts
5377
5774
  init_cjs_shims();
5378
- var fs5 = __toESM(require("fs"), 1);
5379
- var path5 = __toESM(require("path"), 1);
5775
+ var fs6 = __toESM(require("fs"), 1);
5776
+ var path7 = __toESM(require("path"), 1);
5380
5777
  var DEFAULT_MAX_LINES = 2e3;
5381
5778
  var MAX_LINE_CHARS = 500;
5382
5779
  function makeReadFile(workspace) {
5383
- const root = path5.resolve(workspace);
5780
+ const root = path7.resolve(workspace);
5384
5781
  return function read_file(args) {
5385
5782
  const filePath = String(args.path ?? "");
5386
5783
  const startLine = Math.max(1, Number(args.start_line ?? 1));
@@ -5390,13 +5787,13 @@ function makeReadFile(workspace) {
5390
5787
  return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
5391
5788
  }
5392
5789
  const target = resolved.real;
5393
- if (!fs5.existsSync(target) || !fs5.statSync(target).isFile()) {
5790
+ if (!fs6.existsSync(target) || !fs6.statSync(target).isFile()) {
5394
5791
  return { error: `\u4E0D\u662F\u6587\u4EF6: ${filePath}` };
5395
5792
  }
5396
5793
  const selected = [];
5397
5794
  let total = 0;
5398
5795
  try {
5399
- const content = fs5.readFileSync(target, "utf-8");
5796
+ const content = fs6.readFileSync(target, "utf-8");
5400
5797
  const lines = content.split("\n");
5401
5798
  total = lines.length;
5402
5799
  for (let i = startLine - 1; i < Math.min(startLine - 1 + maxLines, lines.length); i++) {
@@ -5411,7 +5808,7 @@ function makeReadFile(workspace) {
5411
5808
  }
5412
5809
  const end = startLine + selected.length - 1;
5413
5810
  const result = {
5414
- path: path5.relative(resolved.root, target),
5811
+ path: path7.relative(resolved.root, target),
5415
5812
  start_line: startLine,
5416
5813
  end_line: end,
5417
5814
  total_lines: total,
@@ -5424,7 +5821,7 @@ function makeReadFile(workspace) {
5424
5821
  };
5425
5822
  }
5426
5823
  function makeWriteFile(workspace) {
5427
- const root = path5.resolve(workspace);
5824
+ const root = path7.resolve(workspace);
5428
5825
  return function write_file(args) {
5429
5826
  const filePath = String(args.path ?? "");
5430
5827
  const content = String(args.content ?? "");
@@ -5434,34 +5831,34 @@ function makeWriteFile(workspace) {
5434
5831
  return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
5435
5832
  }
5436
5833
  const target = resolved.real;
5437
- if (fs5.existsSync(target) && fs5.statSync(target).isDirectory()) {
5834
+ if (fs6.existsSync(target) && fs6.statSync(target).isDirectory()) {
5438
5835
  return { error: "\u8DEF\u5F84\u662F\u76EE\u5F55\uFF0C\u4E0D\u662F\u6587\u4EF6" };
5439
5836
  }
5440
- if (fs5.existsSync(target) && !overwrite) {
5837
+ if (fs6.existsSync(target) && !overwrite) {
5441
5838
  return { error: "\u6587\u4EF6\u5DF2\u5B58\u5728 (overwrite=false)" };
5442
5839
  }
5443
5840
  try {
5444
- fs5.mkdirSync(path5.dirname(target), { recursive: true });
5445
- fs5.writeFileSync(target, content, "utf-8");
5841
+ fs6.mkdirSync(path7.dirname(target), { recursive: true });
5842
+ fs6.writeFileSync(target, content, "utf-8");
5446
5843
  } catch (exc) {
5447
5844
  return { error: `\u5199\u5165\u5931\u8D25: ${String(exc)}` };
5448
5845
  }
5449
- return path5.relative(resolved.root, target);
5846
+ return path7.relative(resolved.root, target);
5450
5847
  };
5451
5848
  }
5452
5849
  function makeListFiles(workspace) {
5453
- const root = path5.resolve(workspace);
5850
+ const root = path7.resolve(workspace);
5454
5851
  return function list_files(args) {
5455
5852
  const dirPath = String(args.path ?? ".");
5456
5853
  const maxResults = Math.min(Number(args.max_results ?? 100), 100);
5457
5854
  const resolved = resolveWithinRoot(root, dirPath);
5458
5855
  if (!resolved) return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
5459
5856
  const target = resolved.real;
5460
- if (!fs5.existsSync(target) || !fs5.statSync(target).isDirectory()) {
5857
+ if (!fs6.existsSync(target) || !fs6.statSync(target).isDirectory()) {
5461
5858
  return { error: `\u4E0D\u662F\u76EE\u5F55: ${dirPath}` };
5462
5859
  }
5463
5860
  try {
5464
- const entries = fs5.readdirSync(target, { withFileTypes: true });
5861
+ const entries = fs6.readdirSync(target, { withFileTypes: true });
5465
5862
  const results = [];
5466
5863
  for (const e of entries) {
5467
5864
  if (results.length >= maxResults) break;
@@ -5470,14 +5867,14 @@ function makeListFiles(workspace) {
5470
5867
  results.push(e.isDirectory() ? `${e.name}/` : e.name);
5471
5868
  }
5472
5869
  results.sort();
5473
- return { path: path5.relative(resolved.root, target), entries: results.slice(0, maxResults) };
5870
+ return { path: path7.relative(resolved.root, target), entries: results.slice(0, maxResults) };
5474
5871
  } catch (exc) {
5475
5872
  return { error: `\u5217\u51FA\u6587\u4EF6\u5931\u8D25: ${String(exc)}` };
5476
5873
  }
5477
5874
  };
5478
5875
  }
5479
5876
  function makeReplaceInFile(workspace) {
5480
- const root = path5.resolve(workspace);
5877
+ const root = path7.resolve(workspace);
5481
5878
  return function replace_in_file(args) {
5482
5879
  const filePath = String(args.path ?? "");
5483
5880
  const oldStr = String(args.old ?? "");
@@ -5487,19 +5884,19 @@ function makeReplaceInFile(workspace) {
5487
5884
  const resolved = resolveWithinRoot(root, filePath);
5488
5885
  if (!resolved) return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
5489
5886
  const target = resolved.real;
5490
- if (!fs5.existsSync(target) || !fs5.statSync(target).isFile()) {
5887
+ if (!fs6.existsSync(target) || !fs6.statSync(target).isFile()) {
5491
5888
  return { error: `\u4E0D\u662F\u6587\u4EF6: ${filePath}` };
5492
5889
  }
5493
5890
  try {
5494
- const content = fs5.readFileSync(target, "utf-8");
5891
+ const content = fs6.readFileSync(target, "utf-8");
5495
5892
  const count = content.split(oldStr).length - 1;
5496
5893
  if (count !== expectedReplacements) {
5497
5894
  return { error: `\u671F\u671B ${expectedReplacements} \u5904\u66FF\u6362\uFF0C\u5B9E\u9645\u627E\u5230 ${count} \u5904: ${filePath}` };
5498
5895
  }
5499
5896
  const updated = content.replaceAll(oldStr, newStr);
5500
- fs5.writeFileSync(target, updated, "utf-8");
5897
+ fs6.writeFileSync(target, updated, "utf-8");
5501
5898
  return {
5502
- path: path5.relative(resolved.root, target),
5899
+ path: path7.relative(resolved.root, target),
5503
5900
  replacements: count,
5504
5901
  chars_before: content.length,
5505
5902
  chars_after: updated.length
@@ -5600,8 +5997,8 @@ function registerFileTools(registry, workspace) {
5600
5997
 
5601
5998
  // src/tools/shell.ts
5602
5999
  init_cjs_shims();
5603
- var fs7 = __toESM(require("fs"), 1);
5604
- var path7 = __toESM(require("path"), 1);
6000
+ var fs8 = __toESM(require("fs"), 1);
6001
+ var path9 = __toESM(require("path"), 1);
5605
6002
 
5606
6003
  // src/tools/execAsync.ts
5607
6004
  init_cjs_shims();
@@ -5766,15 +6163,15 @@ function execAsync(file, argv, opts = {}) {
5766
6163
 
5767
6164
  // src/tools/shellRuntime.ts
5768
6165
  init_cjs_shims();
5769
- var fs6 = __toESM(require("fs"), 1);
5770
- var path6 = __toESM(require("path"), 1);
6166
+ var fs7 = __toESM(require("fs"), 1);
6167
+ var path8 = __toESM(require("path"), 1);
5771
6168
  var WINDOWS_SUFFIXES = [".exe", ".cmd", ".bat", ".com", ""];
5772
6169
  function pathApi(platform) {
5773
- return platform === "win32" ? path6.win32 : path6.posix;
6170
+ return platform === "win32" ? path8.win32 : path8.posix;
5774
6171
  }
5775
6172
  function defaultFileExists(p) {
5776
6173
  try {
5777
- return fs6.statSync(p).isFile();
6174
+ return fs7.statSync(p).isFile();
5778
6175
  } catch {
5779
6176
  return false;
5780
6177
  }
@@ -5805,7 +6202,7 @@ function kindFromBasename(base) {
5805
6202
  return "sh";
5806
6203
  }
5807
6204
  function busyboxArgs(file, command) {
5808
- const base = (file ? path6.win32.basename(file) : "busybox").toLowerCase().replace(/\.(exe|cmd|bat|com)$/, "");
6205
+ const base = (file ? path8.win32.basename(file) : "busybox").toLowerCase().replace(/\.(exe|cmd|bat|com)$/, "");
5809
6206
  return base === "busybox" ? ["sh", "-c", command] : ["-c", command];
5810
6207
  }
5811
6208
  function argsFor(kind, command) {
@@ -6072,7 +6469,7 @@ var MAX_STDERR = 5e3;
6072
6469
  var MAX_BUFFER = 1024 * 1024 * 5;
6073
6470
  var shellSessions = /* @__PURE__ */ new Map();
6074
6471
  function sessionFor(workspace) {
6075
- const key = path7.resolve(workspace);
6472
+ const key = path9.resolve(workspace);
6076
6473
  let s = shellSessions.get(key);
6077
6474
  if (!s) {
6078
6475
  s = { cwd: key };
@@ -6129,8 +6526,8 @@ function makeRunShell(workspace, spec, tasks, scope) {
6129
6526
  let toRun = command;
6130
6527
  if (target) {
6131
6528
  try {
6132
- const resolved = path7.resolve(session.cwd, target);
6133
- if (fs7.existsSync(resolved) && fs7.statSync(resolved).isDirectory()) {
6529
+ const resolved = path9.resolve(session.cwd, target);
6530
+ if (fs8.existsSync(resolved) && fs8.statSync(resolved).isDirectory()) {
6134
6531
  session.cwd = resolved;
6135
6532
  }
6136
6533
  } catch {
@@ -6265,8 +6662,8 @@ init_cjs_shims();
6265
6662
 
6266
6663
  // src/tools/nodeGrep.ts
6267
6664
  init_cjs_shims();
6268
- var fs8 = __toESM(require("fs"), 1);
6269
- var path8 = __toESM(require("path"), 1);
6665
+ var fs9 = __toESM(require("fs"), 1);
6666
+ var path10 = __toESM(require("path"), 1);
6270
6667
  var SKIP_DIRS = /* @__PURE__ */ new Set([
6271
6668
  ".git",
6272
6669
  "node_modules",
@@ -6291,27 +6688,27 @@ function nodeGrep(opts) {
6291
6688
  } catch (err) {
6292
6689
  throw new Error(`\u65E0\u6548\u7684\u6B63\u5219\u8868\u8FBE\u5F0F: ${opts.pattern}\uFF08${String(err)}\uFF09`);
6293
6690
  }
6294
- const target = path8.resolve(opts.workspace, opts.target);
6295
- if (!fs8.existsSync(target)) {
6691
+ const target = path10.resolve(opts.workspace, opts.target);
6692
+ if (!fs9.existsSync(target)) {
6296
6693
  throw new Error(`\u641C\u7D22\u8DEF\u5F84\u4E0D\u5B58\u5728: ${opts.target}`);
6297
6694
  }
6298
6695
  const matches = [];
6299
6696
  const scanFile = (file) => {
6300
6697
  let stat2;
6301
6698
  try {
6302
- stat2 = fs8.statSync(file);
6699
+ stat2 = fs9.statSync(file);
6303
6700
  } catch {
6304
6701
  return false;
6305
6702
  }
6306
6703
  if (!stat2.isFile() || stat2.size > maxFileBytes) return false;
6307
6704
  let buf;
6308
6705
  try {
6309
- buf = fs8.readFileSync(file);
6706
+ buf = fs9.readFileSync(file);
6310
6707
  } catch {
6311
6708
  return false;
6312
6709
  }
6313
6710
  if (looksBinary(buf)) return false;
6314
- const rel = path8.relative(opts.workspace, file) || path8.basename(file);
6711
+ const rel = path10.relative(opts.workspace, file) || path10.basename(file);
6315
6712
  const lines = buf.toString("utf-8").split("\n");
6316
6713
  for (let i = 0; i < lines.length; i++) {
6317
6714
  const raw = lines[i].replace(/\r$/, "");
@@ -6322,7 +6719,7 @@ function nodeGrep(opts) {
6322
6719
  }
6323
6720
  return false;
6324
6721
  };
6325
- const stat = fs8.statSync(target);
6722
+ const stat = fs9.statSync(target);
6326
6723
  if (stat.isFile()) {
6327
6724
  scanFile(target);
6328
6725
  return matches;
@@ -6332,12 +6729,12 @@ function nodeGrep(opts) {
6332
6729
  const dir = stack.pop();
6333
6730
  let entries;
6334
6731
  try {
6335
- entries = fs8.readdirSync(dir, { withFileTypes: true });
6732
+ entries = fs9.readdirSync(dir, { withFileTypes: true });
6336
6733
  } catch {
6337
6734
  continue;
6338
6735
  }
6339
6736
  for (const entry of entries) {
6340
- const full = path8.join(dir, entry.name);
6737
+ const full = path10.join(dir, entry.name);
6341
6738
  if (entry.isDirectory()) {
6342
6739
  if (!SKIP_DIRS.has(entry.name)) stack.push(full);
6343
6740
  } else if (entry.isFile()) {
@@ -6350,22 +6747,22 @@ function nodeGrep(opts) {
6350
6747
 
6351
6748
  // src/tools/ripgrep.ts
6352
6749
  init_cjs_shims();
6353
- var fs9 = __toESM(require("fs"), 1);
6354
- var os = __toESM(require("os"), 1);
6355
- var path9 = __toESM(require("path"), 1);
6750
+ var fs10 = __toESM(require("fs"), 1);
6751
+ var os2 = __toESM(require("os"), 1);
6752
+ var path11 = __toESM(require("path"), 1);
6356
6753
  var import_node_module = require("module");
6357
6754
  var cached2;
6358
6755
  function extractBundledRipgrep() {
6359
6756
  try {
6360
- const req2 = typeof require === "function" ? require : (0, import_node_module.createRequire)(path9.join(process.cwd(), "__zqagent__.js"));
6757
+ const req2 = typeof require === "function" ? require : (0, import_node_module.createRequire)(path11.join(process.cwd(), "__zqagent__.js"));
6361
6758
  const sea = req2("node:sea");
6362
6759
  if (!sea?.isSea?.() || typeof sea.getAsset !== "function") return null;
6363
6760
  const asset = sea.getAsset("rg");
6364
6761
  if (!asset) return null;
6365
- const dir = fs9.mkdtempSync(path9.join(os.tmpdir(), "zqagent-rg-"));
6366
- const file = path9.join(dir, process.platform === "win32" ? "rg.exe" : "rg");
6367
- fs9.writeFileSync(file, Buffer.from(asset));
6368
- fs9.chmodSync(file, 493);
6762
+ const dir = fs10.mkdtempSync(path11.join(os2.tmpdir(), "zqagent-rg-"));
6763
+ const file = path11.join(dir, process.platform === "win32" ? "rg.exe" : "rg");
6764
+ fs10.writeFileSync(file, Buffer.from(asset));
6765
+ fs10.chmodSync(file, 493);
6369
6766
  return file;
6370
6767
  } catch {
6371
6768
  return null;
@@ -6374,7 +6771,7 @@ function extractBundledRipgrep() {
6374
6771
  function resolveRipgrep() {
6375
6772
  if (cached2 !== void 0) return cached2;
6376
6773
  const explicit = (process.env.ZQAGENT_RG ?? "").trim();
6377
- if (explicit && fs9.existsSync(explicit)) {
6774
+ if (explicit && fs10.existsSync(explicit)) {
6378
6775
  cached2 = explicit;
6379
6776
  return cached2;
6380
6777
  }
@@ -6388,7 +6785,7 @@ function resolveRipgrep() {
6388
6785
  env: process.env,
6389
6786
  fileExists: (p) => {
6390
6787
  try {
6391
- return fs9.statSync(p).isFile();
6788
+ return fs10.statSync(p).isFile();
6392
6789
  } catch {
6393
6790
  return false;
6394
6791
  }
@@ -6937,8 +7334,8 @@ function registerWebTools(registry) {
6937
7334
 
6938
7335
  // src/tools/git.ts
6939
7336
  init_cjs_shims();
6940
- var fs10 = __toESM(require("fs"), 1);
6941
- var path10 = __toESM(require("path"), 1);
7337
+ var fs11 = __toESM(require("fs"), 1);
7338
+ var path12 = __toESM(require("path"), 1);
6942
7339
  async function runGit(repoDir, args, timeout = 15e3) {
6943
7340
  const res = await execAsync("git", ["-c", "core.quotepath=false", ...args], {
6944
7341
  cwd: repoDir,
@@ -6955,9 +7352,9 @@ async function runGit(repoDir, args, timeout = 15e3) {
6955
7352
  async function resolveRepoRoot(workspace, pathArg) {
6956
7353
  let dir = workspace;
6957
7354
  if (pathArg) {
6958
- const abs = path10.resolve(workspace, pathArg);
7355
+ const abs = path12.resolve(workspace, pathArg);
6959
7356
  try {
6960
- dir = fs10.statSync(abs).isDirectory() ? abs : path10.dirname(abs);
7357
+ dir = fs11.statSync(abs).isDirectory() ? abs : path12.dirname(abs);
6961
7358
  } catch {
6962
7359
  dir = abs;
6963
7360
  }
@@ -6970,24 +7367,24 @@ function repoDirArg(workspace, raw) {
6970
7367
  if (typeof raw !== "string") return void 0;
6971
7368
  const v = raw.trim();
6972
7369
  if (!v) return void 0;
6973
- const base = path10.resolve(workspace);
6974
- const abs = path10.resolve(base, v);
6975
- const rel = path10.relative(base, abs);
6976
- if (rel.startsWith("..") || path10.isAbsolute(rel)) return void 0;
7370
+ const base = path12.resolve(workspace);
7371
+ const abs = path12.resolve(base, v);
7372
+ const rel = path12.relative(base, abs);
7373
+ if (rel.startsWith("..") || path12.isAbsolute(rel)) return void 0;
6977
7374
  return abs;
6978
7375
  }
6979
7376
  function toPathspec(repoRoot, workspace, pathArg) {
6980
7377
  if (!pathArg) return null;
6981
- const abs = path10.resolve(workspace, pathArg);
6982
- let rel = path10.relative(repoRoot, abs);
7378
+ const abs = path12.resolve(workspace, pathArg);
7379
+ let rel = path12.relative(repoRoot, abs);
6983
7380
  if (rel === "") return ".";
6984
- if (rel.startsWith("..") || path10.isAbsolute(rel)) {
7381
+ if (rel.startsWith("..") || path12.isAbsolute(rel)) {
6985
7382
  try {
6986
- const realRepo = fs10.realpathSync(repoRoot);
6987
- const realAbs = fs10.realpathSync(abs);
6988
- const rel2 = path10.relative(realRepo, realAbs);
7383
+ const realRepo = fs11.realpathSync(repoRoot);
7384
+ const realAbs = fs11.realpathSync(abs);
7385
+ const rel2 = path12.relative(realRepo, realAbs);
6989
7386
  if (rel2 === "") return ".";
6990
- if (!rel2.startsWith("..") && !path10.isAbsolute(rel2)) return rel2;
7387
+ if (!rel2.startsWith("..") && !path12.isAbsolute(rel2)) return rel2;
6991
7388
  } catch {
6992
7389
  }
6993
7390
  return null;
@@ -7413,18 +7810,18 @@ var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".hg", ".s
7413
7810
  var SCAN_DEFAULT_DEPTH = 3;
7414
7811
  var SCAN_MAX_DIRS = 2e3;
7415
7812
  function readRepoHead(repoDir) {
7416
- const dotGit = path10.join(repoDir, ".git");
7813
+ const dotGit = path12.join(repoDir, ".git");
7417
7814
  try {
7418
7815
  let headFile;
7419
- if (fs10.statSync(dotGit).isDirectory()) {
7420
- headFile = path10.join(dotGit, "HEAD");
7816
+ if (fs11.statSync(dotGit).isDirectory()) {
7817
+ headFile = path12.join(dotGit, "HEAD");
7421
7818
  } else {
7422
- const raw = fs10.readFileSync(dotGit, "utf-8");
7819
+ const raw = fs11.readFileSync(dotGit, "utf-8");
7423
7820
  const m = /^gitdir:\s*(.+)$/m.exec(raw);
7424
7821
  if (!m) return {};
7425
- headFile = path10.join(path10.resolve(repoDir, m[1].trim()), "HEAD");
7822
+ headFile = path12.join(path12.resolve(repoDir, m[1].trim()), "HEAD");
7426
7823
  }
7427
- const head = fs10.readFileSync(headFile, "utf-8").trim();
7824
+ const head = fs11.readFileSync(headFile, "utf-8").trim();
7428
7825
  const prefix = "ref: refs/heads/";
7429
7826
  if (head.startsWith(prefix)) return { branch: head.slice(prefix.length), detached: false };
7430
7827
  if (/^[0-9a-f]{7,40}$/i.test(head)) return { branch: head.slice(0, 7), detached: true };
@@ -7433,7 +7830,7 @@ function readRepoHead(repoDir) {
7433
7830
  return {};
7434
7831
  }
7435
7832
  function scanGitRepos(workspace, depth = SCAN_DEFAULT_DEPTH) {
7436
- const root = path10.resolve(workspace);
7833
+ const root = path12.resolve(workspace);
7437
7834
  const maxDepth = Number.isFinite(depth) ? Math.min(Math.max(Math.floor(depth), 0), 6) : SCAN_DEFAULT_DEPTH;
7438
7835
  const repos = [];
7439
7836
  const stack = [{ dir: root, depth: 0 }];
@@ -7446,24 +7843,24 @@ function scanGitRepos(workspace, depth = SCAN_DEFAULT_DEPTH) {
7446
7843
  truncated = true;
7447
7844
  break;
7448
7845
  }
7449
- const rel = path10.relative(root, dir).split(path10.sep).join("/");
7450
- if (fs10.existsSync(path10.join(dir, ".git"))) {
7846
+ const rel = path12.relative(root, dir).split(path12.sep).join("/");
7847
+ if (fs11.existsSync(path12.join(dir, ".git"))) {
7451
7848
  repos.push({
7452
7849
  path: rel,
7453
- name: rel ? path10.basename(dir) : path10.basename(root),
7850
+ name: rel ? path12.basename(dir) : path12.basename(root),
7454
7851
  ...readRepoHead(dir)
7455
7852
  });
7456
7853
  }
7457
7854
  if (d >= maxDepth) continue;
7458
7855
  let entries;
7459
7856
  try {
7460
- entries = fs10.readdirSync(dir, { withFileTypes: true });
7857
+ entries = fs11.readdirSync(dir, { withFileTypes: true });
7461
7858
  } catch {
7462
7859
  continue;
7463
7860
  }
7464
7861
  for (const e of entries) {
7465
7862
  if (!e.isDirectory() || SCAN_SKIP_DIRS.has(e.name)) continue;
7466
- stack.push({ dir: path10.join(dir, e.name), depth: d + 1 });
7863
+ stack.push({ dir: path12.join(dir, e.name), depth: d + 1 });
7467
7864
  }
7468
7865
  }
7469
7866
  repos.sort(
@@ -7473,9 +7870,9 @@ function scanGitRepos(workspace, depth = SCAN_DEFAULT_DEPTH) {
7473
7870
  }
7474
7871
  function makeGitRepos(workspace) {
7475
7872
  return async function git_repos(args) {
7476
- const root = path10.resolve(workspace);
7873
+ const root = path12.resolve(workspace);
7477
7874
  try {
7478
- if (!fs10.statSync(root).isDirectory()) return { root, repos: [], error: "\u5DE5\u4F5C\u533A\u4E0D\u662F\u76EE\u5F55" };
7875
+ if (!fs11.statSync(root).isDirectory()) return { root, repos: [], error: "\u5DE5\u4F5C\u533A\u4E0D\u662F\u76EE\u5F55" };
7479
7876
  } catch {
7480
7877
  return { root, repos: [], error: "\u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728" };
7481
7878
  }
@@ -7487,10 +7884,10 @@ function makeGitRepos(workspace) {
7487
7884
 
7488
7885
  // src/tools/diff.ts
7489
7886
  init_cjs_shims();
7490
- var fs11 = __toESM(require("fs"), 1);
7491
- var path11 = __toESM(require("path"), 1);
7887
+ var fs12 = __toESM(require("fs"), 1);
7888
+ var path13 = __toESM(require("path"), 1);
7492
7889
  function makeApplyPatch(workspace) {
7493
- const root = path11.resolve(workspace);
7890
+ const root = path13.resolve(workspace);
7494
7891
  return function apply_patch(args) {
7495
7892
  const patch = String(args.patch ?? "");
7496
7893
  if (!patch.trim()) return { error: "\u8865\u4E01\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A" };
@@ -7529,12 +7926,12 @@ function makeApplyPatch(workspace) {
7529
7926
  contentLines.push(current.slice(1));
7530
7927
  i++;
7531
7928
  }
7532
- if (fs11.existsSync(target)) {
7929
+ if (fs12.existsSync(target)) {
7533
7930
  return { error: `\u6587\u4EF6\u5DF2\u5B58\u5728: ${filePath}` };
7534
7931
  }
7535
- fs11.mkdirSync(path11.dirname(target), { recursive: true });
7536
- fs11.writeFileSync(target, contentLines.join("\n") + "\n", "utf-8");
7537
- const rel = path11.relative(resolved.root, target);
7932
+ fs12.mkdirSync(path13.dirname(target), { recursive: true });
7933
+ fs12.writeFileSync(target, contentLines.join("\n") + "\n", "utf-8");
7934
+ const rel = path13.relative(resolved.root, target);
7538
7935
  changedFiles.push(rel);
7539
7936
  addedFiles.push(rel);
7540
7937
  fileCount++;
@@ -7547,11 +7944,11 @@ function makeApplyPatch(workspace) {
7547
7944
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
7548
7945
  }
7549
7946
  const target = entry.real;
7550
- if (!fs11.existsSync(target)) {
7947
+ if (!fs12.existsSync(target)) {
7551
7948
  return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
7552
7949
  }
7553
- fs11.unlinkSync(target);
7554
- deletedFiles.push(path11.relative(entry.root, target));
7950
+ fs12.unlinkSync(target);
7951
+ deletedFiles.push(path13.relative(entry.root, target));
7555
7952
  fileCount++;
7556
7953
  i++;
7557
7954
  continue;
@@ -7587,10 +7984,10 @@ function makeApplyPatch(workspace) {
7587
7984
  if (!chunkLines.length) {
7588
7985
  return { error: `\u8865\u4E01\u66F4\u65B0\u6CA1\u6709 hunks: ${filePath}` };
7589
7986
  }
7590
- if (!fs11.existsSync(target)) {
7987
+ if (!fs12.existsSync(target)) {
7591
7988
  return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
7592
7989
  }
7593
- const oldLines = fs11.readFileSync(target, "utf-8").split("\n");
7990
+ const oldLines = fs12.readFileSync(target, "utf-8").split("\n");
7594
7991
  const newLines = applyPatchHunks(oldLines, chunkLines, filePath);
7595
7992
  let finalResolved = resolved;
7596
7993
  if (moveTo) {
@@ -7601,20 +7998,20 @@ function makeApplyPatch(workspace) {
7601
7998
  finalResolved = moved;
7602
7999
  }
7603
8000
  const finalTarget = finalResolved.real;
7604
- if (moveTo && fs11.existsSync(finalTarget) && finalTarget !== target) {
8001
+ if (moveTo && fs12.existsSync(finalTarget) && finalTarget !== target) {
7605
8002
  return { error: `\u6587\u4EF6\u5DF2\u5B58\u5728: ${moveTo}` };
7606
8003
  }
7607
- fs11.mkdirSync(path11.dirname(finalTarget), { recursive: true });
7608
- fs11.writeFileSync(finalTarget, newLines.join("\n"), "utf-8");
8004
+ fs12.mkdirSync(path13.dirname(finalTarget), { recursive: true });
8005
+ fs12.writeFileSync(finalTarget, newLines.join("\n"), "utf-8");
7609
8006
  if (moveTo && finalTarget !== target) {
7610
8007
  const entry = resolveEntryWithinRoot(root, filePath);
7611
8008
  if (!entry) {
7612
8009
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
7613
8010
  }
7614
- fs11.unlinkSync(entry.real);
7615
- deletedFiles.push(path11.relative(resolved.root, target));
8011
+ fs12.unlinkSync(entry.real);
8012
+ deletedFiles.push(path13.relative(resolved.root, target));
7616
8013
  }
7617
- changedFiles.push(path11.relative(finalResolved.root, finalTarget));
8014
+ changedFiles.push(path13.relative(finalResolved.root, finalTarget));
7618
8015
  fileCount++;
7619
8016
  continue;
7620
8017
  }
@@ -7626,18 +8023,18 @@ function makeApplyPatch(workspace) {
7626
8023
  return { changed_files: changedFiles, added_files: addedFiles, deleted_files: deletedFiles, file_count: fileCount };
7627
8024
  };
7628
8025
  }
7629
- function applyPatchHunks(oldLines, chunks, path41) {
8026
+ function applyPatchHunks(oldLines, chunks, path43) {
7630
8027
  let newLines = [...oldLines];
7631
8028
  let cursor = 0;
7632
8029
  for (const chunk of chunks) {
7633
8030
  const oldBlock = chunk.filter((l) => l[0] === " " || l[0] === "-").map((l) => l.slice(1));
7634
8031
  const newBlock = chunk.filter((l) => l[0] === " " || l[0] === "+").map((l) => l.slice(1));
7635
8032
  if (!oldBlock.length) {
7636
- throw new Error(`\u8865\u4E01 hunk \u5FC5\u987B\u5305\u542B\u4E0A\u4E0B\u6587\u6216\u5220\u9664\u884C: ${path41}`);
8033
+ throw new Error(`\u8865\u4E01 hunk \u5FC5\u987B\u5305\u542B\u4E0A\u4E0B\u6587\u6216\u5220\u9664\u884C: ${path43}`);
7637
8034
  }
7638
8035
  const matchStart = findBlockMatch(newLines, oldBlock, cursor);
7639
8036
  if (matchStart === -1) {
7640
- throw new Error(`\u8865\u4E01\u4E0A\u4E0B\u6587\u4E0D\u5339\u914D: ${path41}`);
8037
+ throw new Error(`\u8865\u4E01\u4E0A\u4E0B\u6587\u4E0D\u5339\u914D: ${path43}`);
7641
8038
  }
7642
8039
  const matchEnd = matchStart + oldBlock.length;
7643
8040
  newLines.splice(matchStart, oldBlock.length, ...newBlock);
@@ -7654,7 +8051,7 @@ function findBlockMatch(lines, block, start) {
7654
8051
  return -1;
7655
8052
  }
7656
8053
  function makeApplyUnifiedDiff(workspace) {
7657
- const root = path11.resolve(workspace);
8054
+ const root = path13.resolve(workspace);
7658
8055
  return function apply_unified_diff(args) {
7659
8056
  const diff = String(args.diff ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
7660
8057
  if (!diff.trim()) return { error: "diff \u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A" };
@@ -7673,26 +8070,26 @@ function makeApplyUnifiedDiff(workspace) {
7673
8070
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${targetPath}` };
7674
8071
  }
7675
8072
  const target = resolved.real;
7676
- const oldLines = oldIsDevNull ? [] : fs11.existsSync(target) ? fs11.readFileSync(target, "utf-8").split("\n") : [];
8073
+ const oldLines = oldIsDevNull ? [] : fs12.existsSync(target) ? fs12.readFileSync(target, "utf-8").split("\n") : [];
7677
8074
  const newLines = applyDiffHunks(oldLines, patch.hunks, targetPath);
7678
8075
  hunkCount += patch.hunks.length;
7679
8076
  if (newIsDevNull) {
7680
- if (fs11.existsSync(target)) {
7681
- if (!fs11.statSync(target).isFile()) {
8077
+ if (fs12.existsSync(target)) {
8078
+ if (!fs12.statSync(target).isFile()) {
7682
8079
  return { error: `\u8DEF\u5F84\u4E0D\u662F\u6587\u4EF6: ${targetPath}` };
7683
8080
  }
7684
8081
  const entry = resolveEntryWithinRoot(root, targetPath);
7685
8082
  if (!entry) {
7686
8083
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${targetPath}` };
7687
8084
  }
7688
- fs11.unlinkSync(entry.real);
8085
+ fs12.unlinkSync(entry.real);
7689
8086
  }
7690
- deletedFiles.push(path11.relative(resolved.root, target));
8087
+ deletedFiles.push(path13.relative(resolved.root, target));
7691
8088
  continue;
7692
8089
  }
7693
- fs11.mkdirSync(path11.dirname(target), { recursive: true });
7694
- fs11.writeFileSync(target, newLines.join("\n"), "utf-8");
7695
- const rel = path11.relative(resolved.root, target);
8090
+ fs12.mkdirSync(path13.dirname(target), { recursive: true });
8091
+ fs12.writeFileSync(target, newLines.join("\n"), "utf-8");
8092
+ const rel = path13.relative(resolved.root, target);
7696
8093
  changedFiles.push(rel);
7697
8094
  if (oldIsDevNull) addedFiles.push(rel);
7698
8095
  }
@@ -7956,7 +8353,8 @@ function buildExplorerEngine(opts) {
7956
8353
  model: opts.model,
7957
8354
  instructions: EXPLORER_INSTRUCTIONS,
7958
8355
  maxIterations: parseMaxIterations(process.env.EXPLORE_MAX_ITERATIONS, 30),
7959
- hook: opts.hook
8356
+ hook: opts.hook,
8357
+ toolResultDir: opts.toolResultDir
7960
8358
  });
7961
8359
  }
7962
8360
  function makeExploreTool(opts) {
@@ -7968,7 +8366,8 @@ function makeExploreTool(opts) {
7968
8366
  provider: opts.provider,
7969
8367
  model: opts.model,
7970
8368
  readonlyRegistrars: opts.readonlyRegistrars,
7971
- hook: opts.hookFactory?.()
8369
+ hook: opts.hookFactory?.(),
8370
+ toolResultDir: opts.toolResultDir
7972
8371
  });
7973
8372
  let report = "";
7974
8373
  let status = "unknown";
@@ -8029,14 +8428,14 @@ function registerExploreTool(registry, opts) {
8029
8428
 
8030
8429
  // src/tools/media.ts
8031
8430
  init_cjs_shims();
8032
- var fs13 = __toESM(require("fs"), 1);
8033
- var path13 = __toESM(require("path"), 1);
8431
+ var fs14 = __toESM(require("fs"), 1);
8432
+ var path15 = __toESM(require("path"), 1);
8034
8433
 
8035
8434
  // src/tools/ffmpegRuntime.ts
8036
8435
  init_cjs_shims();
8037
- var fs12 = __toESM(require("fs"), 1);
8038
- var os2 = __toESM(require("os"), 1);
8039
- var path12 = __toESM(require("path"), 1);
8436
+ var fs13 = __toESM(require("fs"), 1);
8437
+ var os3 = __toESM(require("os"), 1);
8438
+ var path14 = __toESM(require("path"), 1);
8040
8439
  var import_node_module2 = require("module");
8041
8440
  var import_node_crypto = require("crypto");
8042
8441
  var import_promises = require("stream/promises");
@@ -8059,29 +8458,29 @@ if (PROXY_ENV2) {
8059
8458
  }
8060
8459
  var DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
8061
8460
  function agentBinDir(workspace) {
8062
- return path12.join(path12.resolve(workspace), ".agent", "bin");
8461
+ return path14.join(path14.resolve(workspace), ".agent", "bin");
8063
8462
  }
8064
8463
  function exeName(name, platform) {
8065
8464
  return platform === "win32" ? `${name}.exe` : name;
8066
8465
  }
8067
8466
  function defaultFileExists2(p) {
8068
8467
  try {
8069
- return fs12.statSync(p).isFile();
8468
+ return fs13.statSync(p).isFile();
8070
8469
  } catch {
8071
8470
  return false;
8072
8471
  }
8073
8472
  }
8074
8473
  function extractBundled(name, platform) {
8075
8474
  try {
8076
- const req2 = typeof require === "function" ? require : (0, import_node_module2.createRequire)(path12.join(process.cwd(), "__zqagent__.js"));
8475
+ const req2 = typeof require === "function" ? require : (0, import_node_module2.createRequire)(path14.join(process.cwd(), "__zqagent__.js"));
8077
8476
  const sea = req2("node:sea");
8078
8477
  if (!sea?.isSea?.() || typeof sea.getAsset !== "function") return null;
8079
8478
  const asset = sea.getAsset(name);
8080
8479
  if (!asset) return null;
8081
- const dir = fs12.mkdtempSync(path12.join(os2.tmpdir(), `zqagent-${name}-`));
8082
- const file = path12.join(dir, exeName(name, platform));
8083
- fs12.writeFileSync(file, Buffer.from(asset));
8084
- fs12.chmodSync(file, 493);
8480
+ const dir = fs13.mkdtempSync(path14.join(os3.tmpdir(), `zqagent-${name}-`));
8481
+ const file = path14.join(dir, exeName(name, platform));
8482
+ fs13.writeFileSync(file, Buffer.from(asset));
8483
+ fs13.chmodSync(file, 493);
8085
8484
  return file;
8086
8485
  } catch {
8087
8486
  return null;
@@ -8091,7 +8490,7 @@ function resolveOne(name, workspace, o) {
8091
8490
  const envKey = name === "ffmpeg" ? ENV_FFMPEG : ENV_FFPROBE;
8092
8491
  const explicit = (o.env[envKey] ?? "").trim();
8093
8492
  if (explicit && o.fileExists(explicit)) return { path: explicit, source: "env" };
8094
- const local = path12.join(agentBinDir(workspace), exeName(name, o.platform));
8493
+ const local = path14.join(agentBinDir(workspace), exeName(name, o.platform));
8095
8494
  if (o.fileExists(local)) return { path: local, source: "agent-bin" };
8096
8495
  const bundled = extractBundled(name, o.platform);
8097
8496
  if (bundled) return { path: bundled, source: "bundled" };
@@ -8105,7 +8504,7 @@ function resolveOne(name, workspace, o) {
8105
8504
  var cache = /* @__PURE__ */ new Map();
8106
8505
  function resolveMedia(workspace, opts = {}) {
8107
8506
  const platform = opts.platform ?? process.platform;
8108
- const key = `${platform}:${path12.resolve(workspace)}`;
8507
+ const key = `${platform}:${path14.resolve(workspace)}`;
8109
8508
  const hit = cache.get(key);
8110
8509
  if (hit) return hit;
8111
8510
  const ctx = {
@@ -8211,7 +8610,7 @@ function parseChecksumFile(text, filename) {
8211
8610
  if (!m) continue;
8212
8611
  const hash = m[1].toLowerCase();
8213
8612
  const name = m[2]?.trim() ?? "";
8214
- if (!name || name === filename || path12.basename(name) === filename) return hash;
8613
+ if (!name || name === filename || path14.basename(name) === filename) return hash;
8215
8614
  }
8216
8615
  const single = lines[0]?.trim();
8217
8616
  if (lines.length === 1 && single && /^[0-9a-f]{32,128}$/i.test(single)) {
@@ -8221,7 +8620,7 @@ function parseChecksumFile(text, filename) {
8221
8620
  }
8222
8621
  async function computeChecksum(file, algo) {
8223
8622
  const hash = (0, import_node_crypto.createHash)(algo);
8224
- await (0, import_promises.pipeline)(fs12.createReadStream(file), hash);
8623
+ await (0, import_promises.pipeline)(fs13.createReadStream(file), hash);
8225
8624
  return hash.digest("hex");
8226
8625
  }
8227
8626
  function fetchInit() {
@@ -8236,7 +8635,7 @@ async function downloadToFile(url, dest) {
8236
8635
  const resp = await (0, import_node_fetch3.default)(url, fetchInit());
8237
8636
  if (!resp.ok) throw new Error(`HTTP ${resp.status} ${resp.statusText}`);
8238
8637
  if (!resp.body) throw new Error("\u54CD\u5E94\u6CA1\u6709\u5185\u5BB9");
8239
- await (0, import_promises.pipeline)(resp.body, fs12.createWriteStream(dest));
8638
+ await (0, import_promises.pipeline)(resp.body, fs13.createWriteStream(dest));
8240
8639
  }
8241
8640
  async function resolveComponentUrl(c) {
8242
8641
  if (c.url) return c.url;
@@ -8253,12 +8652,12 @@ function findBinaryInTree(dir, names) {
8253
8652
  const cur = stack.pop();
8254
8653
  let entries;
8255
8654
  try {
8256
- entries = fs12.readdirSync(cur, { withFileTypes: true });
8655
+ entries = fs13.readdirSync(cur, { withFileTypes: true });
8257
8656
  } catch {
8258
8657
  continue;
8259
8658
  }
8260
8659
  for (const e of entries) {
8261
- const full = path12.join(cur, e.name);
8660
+ const full = path14.join(cur, e.name);
8262
8661
  if (e.isDirectory()) {
8263
8662
  stack.push(full);
8264
8663
  } else if (names.includes(e.name)) {
@@ -8269,7 +8668,7 @@ function findBinaryInTree(dir, names) {
8269
8668
  return null;
8270
8669
  }
8271
8670
  async function extractArchive(archive, dest, kind, platform) {
8272
- fs12.mkdirSync(dest, { recursive: true });
8671
+ fs13.mkdirSync(dest, { recursive: true });
8273
8672
  let res;
8274
8673
  if (kind === "tar.xz") {
8275
8674
  res = await execAsync("tar", ["-xJf", archive, "-C", dest]);
@@ -8305,7 +8704,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
8305
8704
  error: `\u6682\u4E0D\u652F\u6301\u81EA\u52A8\u4E0B\u8F7D\u7684\u5E73\u53F0: ${platform}/${arch}`
8306
8705
  };
8307
8706
  }
8308
- const tmp = fs12.mkdtempSync(path12.join(os2.tmpdir(), "zqagent-ffmpeg-"));
8707
+ const tmp = fs13.mkdtempSync(path14.join(os3.tmpdir(), "zqagent-ffmpeg-"));
8309
8708
  const binDir = agentBinDir(workspace);
8310
8709
  const installed = {};
8311
8710
  try {
@@ -8319,7 +8718,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
8319
8718
  }
8320
8719
  let archive = fetched.get(url);
8321
8720
  if (!archive) {
8322
- const archivePath = path12.join(tmp, path12.basename(new URL(url).pathname) || `${c.name}.${c.archive}`);
8721
+ const archivePath = path14.join(tmp, path14.basename(new URL(url).pathname) || `${c.name}.${c.archive}`);
8323
8722
  log(`\u4E0B\u8F7D ${url}`);
8324
8723
  try {
8325
8724
  await downloadToFile(url, archivePath);
@@ -8329,7 +8728,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
8329
8728
  if (c.checksumUrl) {
8330
8729
  const algo = c.checksumAlgo ?? "sha256";
8331
8730
  try {
8332
- const expected = parseChecksumFile(await fetchText(c.checksumUrl), path12.basename(archivePath));
8731
+ const expected = parseChecksumFile(await fetchText(c.checksumUrl), path14.basename(archivePath));
8333
8732
  if (!expected) {
8334
8733
  notes.push(`\u672A\u80FD\u4ECE ${c.checksumUrl} \u89E3\u6790\u51FA\u6821\u9A8C\u548C\uFF0C\u5DF2\u8DF3\u8FC7\u6821\u9A8C`);
8335
8734
  } else {
@@ -8346,24 +8745,24 @@ async function downloadFfmpeg(workspace, opts = {}) {
8346
8745
  notes.push(`\u6821\u9A8C\u548C\u83B7\u53D6\u5931\u8D25 (${c.checksumUrl}): ${String(e instanceof Error ? e.message : e)}`);
8347
8746
  }
8348
8747
  } else {
8349
- notes.push(`${path12.basename(archivePath)} \u672A\u63D0\u4F9B\u6821\u9A8C\u548C\uFF08\u4EC5 HTTPS \u4F20\u8F93\u4FDD\u62A4\uFF09`);
8748
+ notes.push(`${path14.basename(archivePath)} \u672A\u63D0\u4F9B\u6821\u9A8C\u548C\uFF08\u4EC5 HTTPS \u4F20\u8F93\u4FDD\u62A4\uFF09`);
8350
8749
  }
8351
8750
  archive = { file: archivePath, kind: c.archive };
8352
8751
  fetched.set(url, archive);
8353
8752
  }
8354
- const extractDir = fs12.mkdtempSync(path12.join(tmp, "x-"));
8753
+ const extractDir = fs13.mkdtempSync(path14.join(tmp, "x-"));
8355
8754
  const err = await extractArchive(archive.file, extractDir, archive.kind, platform);
8356
8755
  if (err) return { ok: false, error: err };
8357
8756
  const found = findBinaryInTree(extractDir, [exeName(c.name, platform), c.name]);
8358
8757
  if (!found) {
8359
- return { ok: false, error: `\u5F52\u6863\u5185\u672A\u627E\u5230 ${c.name}: ${path12.basename(archive.file)}` };
8758
+ return { ok: false, error: `\u5F52\u6863\u5185\u672A\u627E\u5230 ${c.name}: ${path14.basename(archive.file)}` };
8360
8759
  }
8361
- fs12.mkdirSync(binDir, { recursive: true });
8362
- const target = path12.join(binDir, exeName(c.name, platform));
8760
+ fs13.mkdirSync(binDir, { recursive: true });
8761
+ const target = path14.join(binDir, exeName(c.name, platform));
8363
8762
  const staging = `${target}.tmp-${process.pid}`;
8364
- fs12.copyFileSync(found, staging);
8365
- fs12.chmodSync(staging, 493);
8366
- fs12.renameSync(staging, target);
8763
+ fs13.copyFileSync(found, staging);
8764
+ fs13.chmodSync(staging, 493);
8765
+ fs13.renameSync(staging, target);
8367
8766
  installed[c.name] = target;
8368
8767
  }
8369
8768
  resetFfmpegCache();
@@ -8372,7 +8771,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
8372
8771
  } catch (e) {
8373
8772
  return { ok: false, error: `\u5B89\u88C5\u5931\u8D25: ${String(e instanceof Error ? e.message : e)}` };
8374
8773
  } finally {
8375
- fs12.rmSync(tmp, { recursive: true, force: true });
8774
+ fs13.rmSync(tmp, { recursive: true, force: true });
8376
8775
  }
8377
8776
  }
8378
8777
  function canAutoDownload(platform, arch) {
@@ -8637,13 +9036,13 @@ function resolveTarget(workspace, value) {
8637
9036
  return { abs: resolved.real, root: resolved.root };
8638
9037
  }
8639
9038
  function relOut(workspace, abs) {
8640
- return path13.relative(workspace, abs) || abs;
9039
+ return path15.relative(workspace, abs) || abs;
8641
9040
  }
8642
9041
  function registerMediaTool(registry, workspace, opts = {}) {
8643
9042
  const platform = opts.platform ?? process.platform;
8644
9043
  const arch = process.arch;
8645
9044
  const media = async (args) => {
8646
- const root = path13.resolve(workspace);
9045
+ const root = path15.resolve(workspace);
8647
9046
  const op = str(args["op"]);
8648
9047
  if (!op) {
8649
9048
  return { error: `\u7F3A\u5C11\u53C2\u6570 op\u3002\u53EF\u9009\u503C\uFF1A${OPS.join(" / ")}` };
@@ -8688,7 +9087,7 @@ function registerMediaTool(registry, workspace, opts = {}) {
8688
9087
  if (!input) return { error: `\u64CD\u4F5C ${op} \u9700\u8981\u53C2\u6570 input\uFF08\u5DE5\u4F5C\u533A\u5185\u76F8\u5BF9\u8DEF\u5F84\uFF09` };
8689
9088
  const r = resolveTarget(root, input);
8690
9089
  if ("error" in r) return { error: r.error };
8691
- if (!fs13.existsSync(r.abs) || !fs13.statSync(r.abs).isFile()) {
9090
+ if (!fs14.existsSync(r.abs) || !fs14.statSync(r.abs).isFile()) {
8692
9091
  return { error: `\u8F93\u5165\u6587\u4EF6\u4E0D\u5B58\u5728: ${input}` };
8693
9092
  }
8694
9093
  inputAbs = r.abs;
@@ -8700,7 +9099,7 @@ function registerMediaTool(registry, workspace, opts = {}) {
8700
9099
  if (!output) return { error: `\u64CD\u4F5C ${op} \u9700\u8981\u53C2\u6570 output\uFF08\u5DE5\u4F5C\u533A\u5185\u76F8\u5BF9\u8DEF\u5F84\uFF09` };
8701
9100
  const r = resolveTarget(root, output);
8702
9101
  if ("error" in r) return { error: r.error };
8703
- fs13.mkdirSync(path13.dirname(r.abs), { recursive: true });
9102
+ fs14.mkdirSync(path15.dirname(r.abs), { recursive: true });
8704
9103
  outputAbs = r.abs;
8705
9104
  relRoot = r.root;
8706
9105
  }
@@ -8750,7 +9149,7 @@ function registerMediaTool(registry, workspace, opts = {}) {
8750
9149
  };
8751
9150
  if (outputAbs) {
8752
9151
  try {
8753
- result["size_bytes"] = fs13.statSync(outputAbs).size;
9152
+ result["size_bytes"] = fs14.statSync(outputAbs).size;
8754
9153
  } catch {
8755
9154
  }
8756
9155
  }
@@ -8993,7 +9392,9 @@ function createDefaultEngine(opts) {
8993
9392
  subagentHookFactory,
8994
9393
  contextWindow,
8995
9394
  thresholdPct,
9395
+ softThresholdPct,
8996
9396
  capTokens,
9397
+ toolResultDir: toolResultDir2,
8997
9398
  logDir
8998
9399
  } = opts;
8999
9400
  const envEffort = process.env.OPENAI_REASONING_EFFORT?.toLowerCase();
@@ -9016,7 +9417,8 @@ function createDefaultEngine(opts) {
9016
9417
  workspace,
9017
9418
  provider: effectiveProvider,
9018
9419
  model,
9019
- hookFactory: subagentHookFactory
9420
+ hookFactory: subagentHookFactory,
9421
+ toolResultDir: toolResultDir2
9020
9422
  });
9021
9423
  const permissions = new PermissionEngine({
9022
9424
  workspaceRoot: workspace,
@@ -9038,7 +9440,9 @@ function createDefaultEngine(opts) {
9038
9440
  hook,
9039
9441
  contextWindow,
9040
9442
  thresholdPct,
9041
- capTokens
9443
+ softThresholdPct,
9444
+ capTokens,
9445
+ toolResultDir: toolResultDir2
9042
9446
  });
9043
9447
  return engine;
9044
9448
  }
@@ -9048,8 +9452,8 @@ init_cjs_shims();
9048
9452
 
9049
9453
  // src/llmConfig.ts
9050
9454
  init_cjs_shims();
9051
- var fs15 = __toESM(require("fs"), 1);
9052
- var path16 = __toESM(require("path"), 1);
9455
+ var fs16 = __toESM(require("fs"), 1);
9456
+ var path17 = __toESM(require("path"), 1);
9053
9457
  var crypto3 = __toESM(require("crypto"), 1);
9054
9458
 
9055
9459
  // src/crypto/secrets.ts
@@ -9058,8 +9462,8 @@ init_cjs_shims();
9058
9462
  // ../shared/src/secrets.ts
9059
9463
  init_cjs_shims();
9060
9464
  var crypto2 = __toESM(require("crypto"), 1);
9061
- var fs14 = __toESM(require("fs"), 1);
9062
- var path14 = __toESM(require("path"), 1);
9465
+ var fs15 = __toESM(require("fs"), 1);
9466
+ var path16 = __toESM(require("path"), 1);
9063
9467
  function encryptWithKey(key, plain) {
9064
9468
  const iv = crypto2.randomBytes(12);
9065
9469
  const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
@@ -9090,16 +9494,16 @@ function decryptWithKey(key, payload) {
9090
9494
  }
9091
9495
  }
9092
9496
  function loadOrCreateMasterKey(masterKeyFile) {
9093
- if (fs14.existsSync(masterKeyFile)) {
9094
- const hex = fs14.readFileSync(masterKeyFile, "utf8").trim();
9497
+ if (fs15.existsSync(masterKeyFile)) {
9498
+ const hex = fs15.readFileSync(masterKeyFile, "utf8").trim();
9095
9499
  if (/^[0-9a-f]{64}$/i.test(hex)) {
9096
9500
  return Buffer.from(hex, "hex");
9097
9501
  }
9098
9502
  console.warn(`\u26A0\uFE0F \u4E3B\u5BC6\u94A5\u6587\u4EF6\u635F\u574F\uFF0C\u5DF2\u91CD\u65B0\u751F\u6210\uFF08\u65E7\u5BC6\u6587\u5C06\u65E0\u6CD5\u89E3\u5BC6\uFF09`);
9099
9503
  }
9100
9504
  const key = crypto2.randomBytes(32);
9101
- fs14.mkdirSync(path14.dirname(masterKeyFile), { recursive: true, mode: 448 });
9102
- fs14.writeFileSync(masterKeyFile, key.toString("hex"), {
9505
+ fs15.mkdirSync(path16.dirname(masterKeyFile), { recursive: true, mode: 448 });
9506
+ fs15.writeFileSync(masterKeyFile, key.toString("hex"), {
9103
9507
  mode: 384,
9104
9508
  encoding: "utf8"
9105
9509
  });
@@ -9317,26 +9721,6 @@ async function probeModel(baseURL, apiKey, model) {
9317
9721
  }
9318
9722
  }
9319
9723
 
9320
- // src/paths.ts
9321
- init_cjs_shims();
9322
- var os3 = __toESM(require("os"), 1);
9323
- var path15 = __toESM(require("path"), 1);
9324
- var APP_DIR_NAME = "zqagent";
9325
- var DEFAULT_DATA_DIR = ".zqagent_data";
9326
- var DEFAULT_HUB_URL = "https://hub.cd-zq.com";
9327
- var IS_WINDOWS = process.platform === "win32";
9328
- function homeDir() {
9329
- return process.env.HOME || process.env.USERPROFILE || os3.homedir() || os3.tmpdir();
9330
- }
9331
- function configDir() {
9332
- const base = process.env.XDG_CONFIG_HOME || (IS_WINDOWS ? process.env.APPDATA : void 0) || path15.join(homeDir(), ".config");
9333
- return path15.join(base, APP_DIR_NAME);
9334
- }
9335
- function stateDir() {
9336
- const base = process.env.XDG_STATE_HOME || (IS_WINDOWS ? process.env.LOCALAPPDATA : void 0) || path15.join(homeDir(), ".local", "state");
9337
- return path15.join(base, APP_DIR_NAME);
9338
- }
9339
-
9340
9724
  // ../shared/src/llm-presets.ts
9341
9725
  init_cjs_shims();
9342
9726
  var LLM_PRESETS = [
@@ -9401,11 +9785,11 @@ function llmPreset(id) {
9401
9785
  function llmConfigFile() {
9402
9786
  const override = process.env.ZQAGENT_LLM_CONFIG_FILE?.trim();
9403
9787
  if (override) return override;
9404
- return path16.join(configDir(), "connections.json");
9788
+ return path17.join(configDir(), "connections.json");
9405
9789
  }
9406
9790
  var keyCache = /* @__PURE__ */ new Map();
9407
9791
  function masterKeyFileFor(file) {
9408
- return path16.join(path16.dirname(file), "secret.key");
9792
+ return path17.join(path17.dirname(file), "secret.key");
9409
9793
  }
9410
9794
  function getMasterKey(file) {
9411
9795
  const keyFile = masterKeyFileFor(file);
@@ -9510,10 +9894,10 @@ function normalize(f) {
9510
9894
  return { ...f, providers, activeId };
9511
9895
  }
9512
9896
  function readRaw(file, keyFile = file) {
9513
- if (!fs15.existsSync(file)) return null;
9897
+ if (!fs16.existsSync(file)) return null;
9514
9898
  let raw;
9515
9899
  try {
9516
- raw = JSON.parse(fs15.readFileSync(file, "utf8"));
9900
+ raw = JSON.parse(fs16.readFileSync(file, "utf8"));
9517
9901
  } catch {
9518
9902
  return null;
9519
9903
  }
@@ -9561,19 +9945,19 @@ function readLlmConfig(opts = {}) {
9561
9945
  f = r.config;
9562
9946
  changed = changed || r.changed;
9563
9947
  }
9564
- if (changed || !fs15.existsSync(file)) writeLlmConfig(f, file);
9948
+ if (changed || !fs16.existsSync(file)) writeLlmConfig(f, file);
9565
9949
  return f;
9566
9950
  }
9567
9951
  function writeLlmConfig(f, file = llmConfigFile()) {
9568
9952
  const next = normalize(f);
9569
- fs15.mkdirSync(path16.dirname(file), { recursive: true, mode: 448 });
9570
- fs15.writeFileSync(file, JSON.stringify(next, null, 2) + "\n", {
9953
+ fs16.mkdirSync(path17.dirname(file), { recursive: true, mode: 448 });
9954
+ fs16.writeFileSync(file, JSON.stringify(next, null, 2) + "\n", {
9571
9955
  mode: 384,
9572
9956
  encoding: "utf8"
9573
9957
  });
9574
9958
  }
9575
9959
  function commonLegacyFiles() {
9576
- return [path16.join(stateDir(), "data", "node-llm.json")];
9960
+ return [path17.join(stateDir(), "data", "node-llm.json")];
9577
9961
  }
9578
9962
 
9579
9963
  // src/cli/connection.ts
@@ -10006,17 +10390,17 @@ function renderBanner(opts = {}) {
10006
10390
  // src/cli/terminalSetup.ts
10007
10391
  init_cjs_shims();
10008
10392
  var import_fs = require("fs");
10009
- var path17 = __toESM(require("path"), 1);
10393
+ var path18 = __toESM(require("path"), 1);
10010
10394
  var TERM_SETUP_ENV = "ZQAGENT_TERM_SETUP";
10011
10395
  var FALSE_FLAGS = ["0", "false", "no", "off"];
10012
10396
  var TRUE_FLAGS = ["1", "true", "yes", "on"];
10013
10397
  function hasWindowsTerminal(env3 = process.env, fileExists = import_fs.existsSync) {
10014
10398
  const raw = env3.PATH ?? env3.Path ?? "";
10015
- const dirs = raw.split(path17.delimiter).filter(Boolean);
10399
+ const dirs = raw.split(path18.delimiter).filter(Boolean);
10016
10400
  if (env3.LOCALAPPDATA) {
10017
- dirs.push(path17.join(env3.LOCALAPPDATA, "Microsoft", "WindowsApps"));
10401
+ dirs.push(path18.join(env3.LOCALAPPDATA, "Microsoft", "WindowsApps"));
10018
10402
  }
10019
- return dirs.some((dir) => fileExists(path17.join(dir, "wt.exe")));
10403
+ return dirs.some((dir) => fileExists(path18.join(dir, "wt.exe")));
10020
10404
  }
10021
10405
  function detectTerminalSetup(input = {}) {
10022
10406
  const env3 = input.env ?? process.env;
@@ -10097,13 +10481,13 @@ function renderTerminalTutorial() {
10097
10481
 
10098
10482
  // src/cli/link.ts
10099
10483
  init_cjs_shims();
10100
- var path20 = __toESM(require("path"), 1);
10484
+ var path21 = __toESM(require("path"), 1);
10101
10485
 
10102
10486
  // src/server/store.ts
10103
10487
  init_cjs_shims();
10104
- var fs16 = __toESM(require("fs"), 1);
10488
+ var fs17 = __toESM(require("fs"), 1);
10105
10489
  var fsp = __toESM(require("fs/promises"), 1);
10106
- var path18 = __toESM(require("path"), 1);
10490
+ var path19 = __toESM(require("path"), 1);
10107
10491
  var import_crypto = require("crypto");
10108
10492
 
10109
10493
  // src/server/dbgTrace.ts
@@ -10157,57 +10541,57 @@ var Store = class {
10157
10541
  sessionCache = /* @__PURE__ */ new Map();
10158
10542
  // ===================== 租户 =====================
10159
10543
  tenantDir(tenantId) {
10160
- return path18.join(this.root, "tenants", tenantId);
10544
+ return path19.join(this.root, "tenants", tenantId);
10161
10545
  }
10162
10546
  listTenants() {
10163
- const dir = path18.join(this.root, "tenants");
10547
+ const dir = path19.join(this.root, "tenants");
10164
10548
  try {
10165
- return fs16.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
10549
+ return fs17.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
10166
10550
  } catch {
10167
10551
  return [];
10168
10552
  }
10169
10553
  }
10170
10554
  loadTenant(tenantId) {
10171
- return this._readJson(path18.join(this.tenantDir(tenantId), "config.json"));
10555
+ return this._readJson(path19.join(this.tenantDir(tenantId), "config.json"));
10172
10556
  }
10173
10557
  saveTenant(config) {
10174
10558
  const dir = this.tenantDir(config.id);
10175
- fs16.mkdirSync(dir, { recursive: true });
10176
- fs16.writeFileSync(path18.join(dir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
10559
+ fs17.mkdirSync(dir, { recursive: true });
10560
+ fs17.writeFileSync(path19.join(dir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
10177
10561
  }
10178
10562
  deleteTenant(tenantId) {
10179
- fs16.rmSync(this.tenantDir(tenantId), { recursive: true, force: true });
10563
+ fs17.rmSync(this.tenantDir(tenantId), { recursive: true, force: true });
10180
10564
  }
10181
10565
  // ===================== 会话 =====================
10182
10566
  workspaceDir(tenantId, sessionId) {
10183
- return path18.join(this.tenantDir(tenantId), "workspaces", sessionId);
10567
+ return path19.join(this.tenantDir(tenantId), "workspaces", sessionId);
10184
10568
  }
10185
10569
  /** 创建(或复用)会话工作区沙箱目录 */
10186
10570
  ensureWorkspace(tenantId, sessionId) {
10187
10571
  const dir = this.workspaceDir(tenantId, sessionId);
10188
- fs16.mkdirSync(dir, { recursive: true });
10572
+ fs17.mkdirSync(dir, { recursive: true });
10189
10573
  return dir;
10190
10574
  }
10191
10575
  sessionDir(tenantId, sessionId) {
10192
- return path18.join(this.tenantDir(tenantId), "sessions", sessionId);
10576
+ return path19.join(this.tenantDir(tenantId), "sessions", sessionId);
10193
10577
  }
10194
10578
  listSessions(tenantId) {
10195
- const dir = path18.join(this.tenantDir(tenantId), "sessions");
10579
+ const dir = path19.join(this.tenantDir(tenantId), "sessions");
10196
10580
  try {
10197
- return fs16.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
10581
+ return fs17.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
10198
10582
  } catch {
10199
10583
  return [];
10200
10584
  }
10201
10585
  }
10202
10586
  loadSession(tenantId, sessionId) {
10203
- const p = path18.join(this.sessionDir(tenantId, sessionId), "session.json");
10587
+ const p = path19.join(this.sessionDir(tenantId, sessionId), "session.json");
10204
10588
  try {
10205
- const st = fs16.statSync(p);
10589
+ const st = fs17.statSync(p);
10206
10590
  const key = `${st.mtimeMs}:${st.size}`;
10207
10591
  const hit = this.sessionCache.get(p);
10208
10592
  if (hit && hit.key === key) return hit.rec;
10209
10593
  const __t0 = dbgNow();
10210
- const rec = JSON.parse(fs16.readFileSync(p, "utf-8"));
10594
+ const rec = JSON.parse(fs17.readFileSync(p, "utf-8"));
10211
10595
  dbgMs("store.loadSession(\u89E3\u6790)", __t0, `bytes=${st.size}`);
10212
10596
  this.sessionCache.set(p, { key, rec });
10213
10597
  if (this.sessionCache.size > 300) {
@@ -10223,8 +10607,8 @@ var Store = class {
10223
10607
  saveSession(record) {
10224
10608
  record.savedAt = (/* @__PURE__ */ new Date()).toISOString();
10225
10609
  const dir = this.sessionDir(record.tenantId, record.sessionId);
10226
- fs16.mkdirSync(dir, { recursive: true });
10227
- const file = path18.join(dir, "session.json");
10610
+ fs17.mkdirSync(dir, { recursive: true });
10611
+ const file = path19.join(dir, "session.json");
10228
10612
  const snapshot = JSON.stringify(record, null, 2);
10229
10613
  const tmp = `${file}.${(0, import_crypto.randomUUID)()}.tmp`;
10230
10614
  const prev = this.writeChains.get(file) ?? Promise.resolve();
@@ -10239,34 +10623,34 @@ var Store = class {
10239
10623
  saveSessionSync(record) {
10240
10624
  record.savedAt = (/* @__PURE__ */ new Date()).toISOString();
10241
10625
  const dir = this.sessionDir(record.tenantId, record.sessionId);
10242
- fs16.mkdirSync(dir, { recursive: true });
10243
- fs16.writeFileSync(
10244
- path18.join(dir, "session.json"),
10626
+ fs17.mkdirSync(dir, { recursive: true });
10627
+ fs17.writeFileSync(
10628
+ path19.join(dir, "session.json"),
10245
10629
  JSON.stringify(record, null, 2),
10246
10630
  "utf-8"
10247
10631
  );
10248
10632
  }
10249
10633
  deleteSession(tenantId, sessionId) {
10250
- this.sessionCache.delete(path18.join(this.sessionDir(tenantId, sessionId), "session.json"));
10251
- fs16.rmSync(this.sessionDir(tenantId, sessionId), { recursive: true, force: true });
10252
- fs16.rmSync(this.workspaceDir(tenantId, sessionId), { recursive: true, force: true });
10634
+ this.sessionCache.delete(path19.join(this.sessionDir(tenantId, sessionId), "session.json"));
10635
+ fs17.rmSync(this.sessionDir(tenantId, sessionId), { recursive: true, force: true });
10636
+ fs17.rmSync(this.workspaceDir(tenantId, sessionId), { recursive: true, force: true });
10253
10637
  }
10254
10638
  // ===================== 事件日志 =====================
10255
10639
  appendEvent(tenantId, sessionId, entry) {
10256
10640
  const dir = this.sessionDir(tenantId, sessionId);
10257
- fs16.mkdirSync(dir, { recursive: true });
10641
+ fs17.mkdirSync(dir, { recursive: true });
10258
10642
  return fsp.appendFile(
10259
- path18.join(dir, "events.jsonl"),
10643
+ path19.join(dir, "events.jsonl"),
10260
10644
  JSON.stringify(entry) + "\n",
10261
10645
  "utf-8"
10262
10646
  ).catch(() => {
10263
10647
  });
10264
10648
  }
10265
10649
  loadEvents(tenantId, sessionId) {
10266
- const p = path18.join(this.sessionDir(tenantId, sessionId), "events.jsonl");
10650
+ const p = path19.join(this.sessionDir(tenantId, sessionId), "events.jsonl");
10267
10651
  const __t0 = dbgNow();
10268
10652
  try {
10269
- const raw = fs16.readFileSync(p, "utf-8");
10653
+ const raw = fs17.readFileSync(p, "utf-8");
10270
10654
  const out = raw.trim().split("\n").filter(Boolean).map((l) => JSON.parse(l));
10271
10655
  dbgMs("store.loadEvents(\u6574\u6587\u4EF6)", __t0, `bytes=${raw.length} evts=${out.length}`);
10272
10656
  return out;
@@ -10284,15 +10668,15 @@ var Store = class {
10284
10668
  * 窗口内一条完整记录都没有时回退为整文件读取。
10285
10669
  */
10286
10670
  loadEventsTail(tenantId, sessionId, maxBytes = 1024 * 1024) {
10287
- const p = path18.join(this.sessionDir(tenantId, sessionId), "events.jsonl");
10671
+ const p = path19.join(this.sessionDir(tenantId, sessionId), "events.jsonl");
10288
10672
  const __t0 = dbgNow();
10289
10673
  let fd = null;
10290
10674
  try {
10291
- const size = fs16.statSync(p).size;
10675
+ const size = fs17.statSync(p).size;
10292
10676
  if (size <= maxBytes) return this.loadEvents(tenantId, sessionId);
10293
- fd = fs16.openSync(p, "r");
10677
+ fd = fs17.openSync(p, "r");
10294
10678
  const buf = Buffer.allocUnsafe(maxBytes);
10295
- const read2 = fs16.readSync(fd, buf, 0, maxBytes, size - maxBytes);
10679
+ const read2 = fs17.readSync(fd, buf, 0, maxBytes, size - maxBytes);
10296
10680
  let text = buf.subarray(0, read2).toString("utf-8");
10297
10681
  const nl = text.indexOf("\n");
10298
10682
  if (nl < 0) return this.loadEvents(tenantId, sessionId);
@@ -10317,7 +10701,7 @@ var Store = class {
10317
10701
  } finally {
10318
10702
  if (fd !== null) {
10319
10703
  try {
10320
- fs16.closeSync(fd);
10704
+ fs17.closeSync(fd);
10321
10705
  } catch {
10322
10706
  }
10323
10707
  }
@@ -10326,26 +10710,26 @@ var Store = class {
10326
10710
  // ===================== 节点身份 & 管理员密钥 =====================
10327
10711
  /** 节点稳定标识:{root}/node.json(首次启动生成后长期固定) */
10328
10712
  loadNodeId() {
10329
- const v = this._readJson(path18.join(this.root, "node.json"));
10713
+ const v = this._readJson(path19.join(this.root, "node.json"));
10330
10714
  return v?.id && typeof v.id === "string" ? v.id : null;
10331
10715
  }
10332
10716
  saveNodeId(id) {
10333
- fs16.mkdirSync(this.root, { recursive: true });
10334
- const file = path18.join(this.root, "node.json");
10717
+ fs17.mkdirSync(this.root, { recursive: true });
10718
+ const file = path19.join(this.root, "node.json");
10335
10719
  const cur = this._readJson(file) ?? {};
10336
- fs16.writeFileSync(file, JSON.stringify({ ...cur, id }, null, 2), "utf-8");
10720
+ fs17.writeFileSync(file, JSON.stringify({ ...cur, id }, null, 2), "utf-8");
10337
10721
  }
10338
10722
  /** 节点展示名:{root}/node.json 的 label(Hub 下发或本地设置) */
10339
10723
  loadNodeLabel() {
10340
- const v = this._readJson(path18.join(this.root, "node.json"));
10724
+ const v = this._readJson(path19.join(this.root, "node.json"));
10341
10725
  return v?.label && typeof v.label === "string" ? v.label : null;
10342
10726
  }
10343
10727
  /** 写入/更新节点展示名(保留 id 等既有字段) */
10344
10728
  saveNodeLabel(label) {
10345
- fs16.mkdirSync(this.root, { recursive: true });
10346
- const file = path18.join(this.root, "node.json");
10729
+ fs17.mkdirSync(this.root, { recursive: true });
10730
+ const file = path19.join(this.root, "node.json");
10347
10731
  const cur = this._readJson(file) ?? {};
10348
- fs16.writeFileSync(file, JSON.stringify({ ...cur, label }, null, 2), "utf-8");
10732
+ fs17.writeFileSync(file, JSON.stringify({ ...cur, label }, null, 2), "utf-8");
10349
10733
  }
10350
10734
  /**
10351
10735
  * 节点级 LLM 配置:统一存放于本机 `~/.config/zqagent/connections.json`
@@ -10356,7 +10740,7 @@ var Store = class {
10356
10740
  const file = this.llmFile();
10357
10741
  const cfg = readLlmConfig({
10358
10742
  file,
10359
- legacyFiles: [path18.join(this.root, "node-llm.json")]
10743
+ legacyFiles: [path19.join(this.root, "node-llm.json")]
10360
10744
  });
10361
10745
  return {
10362
10746
  providers: cfg.providers.map((p) => ({
@@ -10378,7 +10762,7 @@ var Store = class {
10378
10762
  const file = this.llmFile();
10379
10763
  const cur = readLlmConfig({
10380
10764
  file,
10381
- legacyFiles: [path18.join(this.root, "node-llm.json")]
10765
+ legacyFiles: [path19.join(this.root, "node-llm.json")]
10382
10766
  });
10383
10767
  const old = new Map(cur.providers.map((p) => [p.id, p]));
10384
10768
  const providers = cfg.providers.map((p) => {
@@ -10410,13 +10794,13 @@ var Store = class {
10410
10794
  }
10411
10795
  /** 管理员密钥:{root}/admin.json(可跨项目操作;桌面外壳注入) */
10412
10796
  loadAdminKey() {
10413
- const v = this._readJson(path18.join(this.root, "admin.json"));
10797
+ const v = this._readJson(path19.join(this.root, "admin.json"));
10414
10798
  return v?.apiKey && typeof v.apiKey === "string" ? v.apiKey : null;
10415
10799
  }
10416
10800
  saveAdminKey(apiKey) {
10417
- fs16.mkdirSync(this.root, { recursive: true });
10418
- fs16.writeFileSync(
10419
- path18.join(this.root, "admin.json"),
10801
+ fs17.mkdirSync(this.root, { recursive: true });
10802
+ fs17.writeFileSync(
10803
+ path19.join(this.root, "admin.json"),
10420
10804
  JSON.stringify({ apiKey }, null, 2),
10421
10805
  "utf-8"
10422
10806
  );
@@ -10426,18 +10810,18 @@ var Store = class {
10426
10810
  * 节点凭注册码接入后写入;启动时优先于环境变量读取。
10427
10811
  */
10428
10812
  loadFleetCredential() {
10429
- return this._readJson(path18.join(this.root, "fleet.json"));
10813
+ return this._readJson(path19.join(this.root, "fleet.json"));
10430
10814
  }
10431
10815
  saveFleetCredential(cred) {
10432
- fs16.mkdirSync(this.root, { recursive: true });
10433
- fs16.writeFileSync(
10434
- path18.join(this.root, "fleet.json"),
10816
+ fs17.mkdirSync(this.root, { recursive: true });
10817
+ fs17.writeFileSync(
10818
+ path19.join(this.root, "fleet.json"),
10435
10819
  JSON.stringify(cred, null, 2),
10436
10820
  { encoding: "utf-8", mode: 384 }
10437
10821
  );
10438
10822
  }
10439
10823
  clearFleetCredential() {
10440
- fs16.rmSync(path18.join(this.root, "fleet.json"), { force: true });
10824
+ fs17.rmSync(path19.join(this.root, "fleet.json"), { force: true });
10441
10825
  }
10442
10826
  /** 数据根目录(只读;节点管理配置等辅助文件定位用) */
10443
10827
  get rootDir() {
@@ -10446,7 +10830,7 @@ var Store = class {
10446
10830
  // ===================== 工具 =====================
10447
10831
  _readJson(p) {
10448
10832
  try {
10449
- return JSON.parse(fs16.readFileSync(p, "utf-8"));
10833
+ return JSON.parse(fs17.readFileSync(p, "utf-8"));
10450
10834
  } catch {
10451
10835
  return null;
10452
10836
  }
@@ -10459,13 +10843,13 @@ var baseUrlResolver = null;
10459
10843
  function resolveBase(baseUrl) {
10460
10844
  return baseUrlResolver ? baseUrlResolver(baseUrl) : baseUrl;
10461
10845
  }
10462
- async function req(baseUrl, token, path41, init = {}) {
10846
+ async function req(baseUrl, token, path43, init = {}) {
10463
10847
  const headers = {
10464
10848
  ...init.body ? { "Content-Type": "application/json" } : {},
10465
10849
  ...init.headers ?? {}
10466
10850
  };
10467
10851
  if (token) headers.Authorization = `Bearer ${token}`;
10468
- const res = await fetch(`${resolveBase(baseUrl).replace(/\/$/, "")}${path41}`, {
10852
+ const res = await fetch(`${resolveBase(baseUrl).replace(/\/$/, "")}${path43}`, {
10469
10853
  ...init,
10470
10854
  headers
10471
10855
  });
@@ -10508,8 +10892,8 @@ async function hubEnrollByAccount(baseUrl, input) {
10508
10892
  // src/cli/serve-instances.ts
10509
10893
  init_cjs_shims();
10510
10894
  var import_node_child_process4 = require("child_process");
10511
- var fs17 = __toESM(require("fs"), 1);
10512
- var path19 = __toESM(require("path"), 1);
10895
+ var fs18 = __toESM(require("fs"), 1);
10896
+ var path20 = __toESM(require("path"), 1);
10513
10897
  var DEFAULT_SERVE_HOST = "127.0.0.1";
10514
10898
  var DEFAULT_SERVE_PORT = 8787;
10515
10899
  function serveSubcommandIndex(argv) {
@@ -10547,9 +10931,9 @@ function parseServeArgs(argv) {
10547
10931
  }
10548
10932
  function buildHotCandidates(explicitDataDir, discovered, fallback) {
10549
10933
  if (explicitDataDir) {
10550
- return [{ dataDir: path19.resolve(explicitDataDir), ...fallback }];
10934
+ return [{ dataDir: path20.resolve(explicitDataDir), ...fallback }];
10551
10935
  }
10552
- const out = [{ dataDir: path19.resolve(DEFAULT_DATA_DIR), ...fallback }];
10936
+ const out = [{ dataDir: path20.resolve(DEFAULT_DATA_DIR), ...fallback }];
10553
10937
  for (const inst of discovered) {
10554
10938
  if (!out.some((o) => o.dataDir === inst.dataDir)) out.push(inst);
10555
10939
  }
@@ -10557,7 +10941,7 @@ function buildHotCandidates(explicitDataDir, discovered, fallback) {
10557
10941
  }
10558
10942
  function processCwd(pid) {
10559
10943
  try {
10560
- return fs17.readlinkSync(`/proc/${pid}/cwd`);
10944
+ return fs18.readlinkSync(`/proc/${pid}/cwd`);
10561
10945
  } catch {
10562
10946
  return null;
10563
10947
  }
@@ -10565,7 +10949,7 @@ function processCwd(pid) {
10565
10949
  function processEnv(pid) {
10566
10950
  const out = {};
10567
10951
  try {
10568
- const raw = fs17.readFileSync(`/proc/${pid}/environ`, "utf-8");
10952
+ const raw = fs18.readFileSync(`/proc/${pid}/environ`, "utf-8");
10569
10953
  for (const entry of raw.split("\0")) {
10570
10954
  const eq = entry.indexOf("=");
10571
10955
  if (eq > 0) out[entry.slice(0, eq)] = entry.slice(eq + 1);
@@ -10576,14 +10960,14 @@ function processEnv(pid) {
10576
10960
  }
10577
10961
  function resolveDataDir(rawDataDir, cwd, env3) {
10578
10962
  const pick = rawDataDir ?? env3.DATA_DIR ?? null;
10579
- if (pick) return path19.isAbsolute(pick) ? path19.normalize(pick) : cwd ? path19.resolve(cwd, pick) : null;
10580
- return cwd ? path19.resolve(cwd, DEFAULT_DATA_DIR) : null;
10963
+ if (pick) return path20.isAbsolute(pick) ? path20.normalize(pick) : cwd ? path20.resolve(cwd, pick) : null;
10964
+ return cwd ? path20.resolve(cwd, DEFAULT_DATA_DIR) : null;
10581
10965
  }
10582
10966
  function discoverFromProc() {
10583
10967
  const out = [];
10584
10968
  let pids;
10585
10969
  try {
10586
- pids = fs17.readdirSync("/proc");
10970
+ pids = fs18.readdirSync("/proc");
10587
10971
  } catch {
10588
10972
  return out;
10589
10973
  }
@@ -10593,7 +10977,7 @@ function discoverFromProc() {
10593
10977
  if (pid === process.pid) continue;
10594
10978
  let argv;
10595
10979
  try {
10596
- argv = fs17.readFileSync(`/proc/${pid}/cmdline`, "utf-8").split("\0").filter(Boolean);
10980
+ argv = fs18.readFileSync(`/proc/${pid}/cmdline`, "utf-8").split("\0").filter(Boolean);
10597
10981
  } catch {
10598
10982
  continue;
10599
10983
  }
@@ -10614,11 +10998,11 @@ function discoverFromPs() {
10614
10998
  const m = /--data-dir(?:=|\s+)(.+?)(?=\s+--|\s*$)/.exec(line);
10615
10999
  if (!m) continue;
10616
11000
  const raw = m[1].trim();
10617
- if (!raw || !path19.isAbsolute(raw)) continue;
11001
+ if (!raw || !path20.isAbsolute(raw)) continue;
10618
11002
  const host = /--host(?:=|\s+)(\S+)/.exec(line)?.[1] ?? DEFAULT_SERVE_HOST;
10619
11003
  const port = Number(/--port(?:=|\s+)(\d+)/.exec(line)?.[1] ?? DEFAULT_SERVE_PORT);
10620
11004
  out.push({
10621
- dataDir: path19.normalize(raw),
11005
+ dataDir: path20.normalize(raw),
10622
11006
  host: host === "0.0.0.0" || host === "::" ? DEFAULT_SERVE_HOST : host,
10623
11007
  port: Number.isFinite(port) && port > 0 ? port : DEFAULT_SERVE_PORT
10624
11008
  });
@@ -10721,7 +11105,7 @@ async function linkNode(opts) {
10721
11105
  const status = await enrollViaServer(inst.host, inst.port, adminKey, opts);
10722
11106
  return { mode: "hot", ...status, dataDir: inst.dataDir };
10723
11107
  }
10724
- const dataDir = path20.resolve(explicitDataDir ?? DEFAULT_DATA_DIR);
11108
+ const dataDir = path21.resolve(explicitDataDir ?? DEFAULT_DATA_DIR);
10725
11109
  const store = new Store(dataDir);
10726
11110
  const nodeId = ensureNodeId(store);
10727
11111
  const cred = await hubEnrollByCode(opts.hubUrl, {
@@ -10803,11 +11187,12 @@ init_cjs_shims();
10803
11187
 
10804
11188
  // src/server/runtime.ts
10805
11189
  init_cjs_shims();
10806
- var fs29 = __toESM(require("fs"), 1);
10807
- var path35 = __toESM(require("path"), 1);
11190
+ var fs30 = __toESM(require("fs"), 1);
11191
+ var path37 = __toESM(require("path"), 1);
10808
11192
 
10809
11193
  // src/server/session.ts
10810
11194
  init_cjs_shims();
11195
+ var path24 = __toESM(require("path"), 1);
10811
11196
  var import_async_hooks = require("async_hooks");
10812
11197
 
10813
11198
  // src/server/mcpConfig.ts
@@ -10988,8 +11373,8 @@ function resolveSessionProvider(store, tenant, providerId) {
10988
11373
 
10989
11374
  // src/server/usage.ts
10990
11375
  init_cjs_shims();
10991
- var fs19 = __toESM(require("fs"), 1);
10992
- var path22 = __toESM(require("path"), 1);
11376
+ var fs20 = __toESM(require("fs"), 1);
11377
+ var path23 = __toESM(require("path"), 1);
10993
11378
  var LEDGER_RETENTION_MONTHS = 12;
10994
11379
  var USAGE_SCHEMA = 1;
10995
11380
  var DEFAULT_PRICING = {
@@ -11070,15 +11455,15 @@ function retainedMonths(now = /* @__PURE__ */ new Date()) {
11070
11455
  return out;
11071
11456
  }
11072
11457
  function atomicWrite(file, content) {
11073
- fs19.mkdirSync(path22.dirname(file), { recursive: true });
11458
+ fs20.mkdirSync(path23.dirname(file), { recursive: true });
11074
11459
  const tmp = `${file}.tmp`;
11075
- fs19.writeFileSync(tmp, content, "utf-8");
11076
- fs19.renameSync(tmp, file);
11460
+ fs20.writeFileSync(tmp, content, "utf-8");
11461
+ fs20.renameSync(tmp, file);
11077
11462
  }
11078
11463
  var UsageLedger = class {
11079
11464
  constructor(root) {
11080
11465
  this.root = root;
11081
- this.dir = path22.join(root, "usage");
11466
+ this.dir = path23.join(root, "usage");
11082
11467
  }
11083
11468
  root;
11084
11469
  dir;
@@ -11091,18 +11476,18 @@ var UsageLedger = class {
11091
11476
  return this.dir;
11092
11477
  }
11093
11478
  ledgerFile(month) {
11094
- return path22.join(this.ledgerDir, `ledger-${month}.jsonl`);
11479
+ return path23.join(this.ledgerDir, `ledger-${month}.jsonl`);
11095
11480
  }
11096
11481
  get rollupFile() {
11097
- return path22.join(this.ledgerDir, "rollup.json");
11482
+ return path23.join(this.ledgerDir, "rollup.json");
11098
11483
  }
11099
11484
  get pricingFile() {
11100
- return path22.join(this.ledgerDir, "pricing.json");
11485
+ return path23.join(this.ledgerDir, "pricing.json");
11101
11486
  }
11102
11487
  // ===================== 初始化 =====================
11103
11488
  init(now = /* @__PURE__ */ new Date()) {
11104
11489
  if (this.inited) return;
11105
- fs19.mkdirSync(this.ledgerDir, { recursive: true });
11490
+ fs20.mkdirSync(this.ledgerDir, { recursive: true });
11106
11491
  this.pricing = this.loadPricing();
11107
11492
  this.rollup = this.loadRollup(now);
11108
11493
  this.pruneLedgers(now);
@@ -11110,7 +11495,7 @@ var UsageLedger = class {
11110
11495
  }
11111
11496
  loadPricing() {
11112
11497
  try {
11113
- const raw = fs19.readFileSync(this.pricingFile, "utf-8");
11498
+ const raw = fs20.readFileSync(this.pricingFile, "utf-8");
11114
11499
  const parsed = JSON.parse(raw);
11115
11500
  const vendors = {};
11116
11501
  for (const v of /* @__PURE__ */ new Set([
@@ -11139,7 +11524,7 @@ var UsageLedger = class {
11139
11524
  }
11140
11525
  loadRollup(now) {
11141
11526
  try {
11142
- const raw = fs19.readFileSync(this.rollupFile, "utf-8");
11527
+ const raw = fs20.readFileSync(this.rollupFile, "utf-8");
11143
11528
  const parsed = JSON.parse(raw);
11144
11529
  return {
11145
11530
  schema: USAGE_SCHEMA,
@@ -11166,10 +11551,10 @@ var UsageLedger = class {
11166
11551
  const keep = retainedMonths(now);
11167
11552
  let changed = false;
11168
11553
  try {
11169
- for (const f of fs19.readdirSync(this.ledgerDir)) {
11554
+ for (const f of fs20.readdirSync(this.ledgerDir)) {
11170
11555
  const m = /^ledger-(\d{4}-\d{2})\.jsonl$/.exec(f);
11171
11556
  if (m && !keep.has(m[1])) {
11172
- fs19.unlinkSync(path22.join(this.ledgerDir, f));
11557
+ fs20.unlinkSync(path23.join(this.ledgerDir, f));
11173
11558
  }
11174
11559
  }
11175
11560
  } catch {
@@ -11246,7 +11631,7 @@ var UsageLedger = class {
11246
11631
  };
11247
11632
  const line = JSON.stringify(rec) + "\n";
11248
11633
  try {
11249
- fs19.appendFileSync(this.ledgerFile(monthOf(rec.ts)), line);
11634
+ fs20.appendFileSync(this.ledgerFile(monthOf(rec.ts)), line);
11250
11635
  } catch (e) {
11251
11636
  console.error(`[usage] \u5199\u8D26\u672C\u5931\u8D25: ${String(e)}`);
11252
11637
  }
@@ -11418,10 +11803,10 @@ var UsageLedger = class {
11418
11803
  const rows = [];
11419
11804
  for (const month of months) {
11420
11805
  const file = this.ledgerFile(month);
11421
- if (!fs19.existsSync(file)) continue;
11806
+ if (!fs20.existsSync(file)) continue;
11422
11807
  let content = "";
11423
11808
  try {
11424
- content = fs19.readFileSync(file, "utf-8");
11809
+ content = fs20.readFileSync(file, "utf-8");
11425
11810
  } catch {
11426
11811
  continue;
11427
11812
  }
@@ -11530,10 +11915,10 @@ var UsageLedger = class {
11530
11915
  const seen = /* @__PURE__ */ new Set();
11531
11916
  for (const month of this.monthsInRange("", "")) {
11532
11917
  const file = this.ledgerFile(month);
11533
- if (!fs19.existsSync(file)) continue;
11918
+ if (!fs20.existsSync(file)) continue;
11534
11919
  let content = "";
11535
11920
  try {
11536
- content = fs19.readFileSync(file, "utf-8");
11921
+ content = fs20.readFileSync(file, "utf-8");
11537
11922
  } catch {
11538
11923
  continue;
11539
11924
  }
@@ -12841,7 +13226,10 @@ data: ${JSON.stringify(entry.data)}
12841
13226
  // 上下文窗口:环境变量显式覆盖 > 供应商 /models 提供的真实长度 > 引擎内置表
12842
13227
  contextWindow: parseOptionalPositiveNumber(process.env.CONTEXT_WINDOW) ?? modelContextWindow(eff.modelMeta, activeModel),
12843
13228
  thresholdPct: parseRatio(process.env.COMPACTION_THRESHOLD_PCT),
12844
- capTokens: parseOptionalPositiveNumber(process.env.COMPACTION_CAP_TOKENS) ?? null
13229
+ softThresholdPct: parseRatio(process.env.COMPACTION_SOFT_THRESHOLD_PCT),
13230
+ capTokens: parseOptionalPositiveNumber(process.env.COMPACTION_CAP_TOKENS) ?? null,
13231
+ // 超预算工具结果按租户/工作区隔离落盘(多租户共用一个状态目录会混内容)
13232
+ toolResultDir: path24.join(workspace, ".agent", "tool-results")
12845
13233
  });
12846
13234
  engine.setApprover(this._makeApprover(rt));
12847
13235
  const mcpConfigs = loadServerMcpConfigs(tenant);
@@ -13022,11 +13410,11 @@ var ApiError = class extends Error {
13022
13410
  // src/server/routes.ts
13023
13411
  init_cjs_shims();
13024
13412
  var import_http = require("http");
13025
- var path27 = __toESM(require("path"), 1);
13413
+ var path29 = __toESM(require("path"), 1);
13026
13414
 
13027
13415
  // src/server/projects.ts
13028
13416
  init_cjs_shims();
13029
- var path23 = __toESM(require("path"), 1);
13417
+ var path25 = __toESM(require("path"), 1);
13030
13418
  var DEFAULT_MODEL = () => process.env.OPENAI_MODEL || process.env.MODEL || "gpt-4o";
13031
13419
  function toProjectSummary(store, tenant) {
13032
13420
  return {
@@ -13159,9 +13547,9 @@ function createProject(store, input) {
13159
13547
  if (store.loadTenant(id)) {
13160
13548
  throw new ApiError(409, "project_exists", `\u9879\u76EE ${id} \u5DF2\u5B58\u5728`);
13161
13549
  }
13162
- const workspaceRoot = typeof raw.workspaceRoot === "string" && raw.workspaceRoot.trim() ? path23.resolve(raw.workspaceRoot.trim()) : void 0;
13550
+ const workspaceRoot = typeof raw.workspaceRoot === "string" && raw.workspaceRoot.trim() ? path25.resolve(raw.workspaceRoot.trim()) : void 0;
13163
13551
  const explicitName = typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : "";
13164
- const defaultName = workspaceRoot ? path23.basename(workspaceRoot) : id;
13552
+ const defaultName = workspaceRoot ? path25.basename(workspaceRoot) : id;
13165
13553
  const tenant = {
13166
13554
  id,
13167
13555
  name: explicitName || defaultName,
@@ -13191,7 +13579,7 @@ function buildNodeInfo(store, opts) {
13191
13579
  nodeId: opts.nodeId ?? ensureNodeId2(store),
13192
13580
  platform: process.platform,
13193
13581
  mode: process.env.NODE_MODE ?? "standalone",
13194
- version: "0.2.4",
13582
+ version: "0.2.6",
13195
13583
  admin: opts.admin,
13196
13584
  projects: opts.projects
13197
13585
  };
@@ -13275,8 +13663,8 @@ function hubProxyEnabled(host) {
13275
13663
  const h = (host ?? "127.0.0.1").trim();
13276
13664
  return h === "127.0.0.1" || h === "localhost" || h === "::1";
13277
13665
  }
13278
- function isHubProxyPath(path41) {
13279
- return path41.startsWith(HUB_PROXY_PREFIX);
13666
+ function isHubProxyPath(path43) {
13667
+ return path43.startsWith(HUB_PROXY_PREFIX);
13280
13668
  }
13281
13669
  function decodeTarget(enc) {
13282
13670
  try {
@@ -13370,34 +13758,34 @@ async function handleHubProxy(req2, res) {
13370
13758
 
13371
13759
  // src/server/fsBrowser.ts
13372
13760
  init_cjs_shims();
13373
- var fs20 = __toESM(require("fs"), 1);
13761
+ var fs21 = __toESM(require("fs"), 1);
13374
13762
  var os4 = __toESM(require("os"), 1);
13375
- var path24 = __toESM(require("path"), 1);
13763
+ var path26 = __toESM(require("path"), 1);
13376
13764
  var MAX_ENTRIES = 2e3;
13377
13765
  var DEFAULT_SEARCH_LIMIT = 200;
13378
13766
  var MAX_SEARCH_LIMIT = 1e3;
13379
13767
  function listDirs(input) {
13380
13768
  const base = (input ?? "").trim() || os4.homedir();
13381
- const target = path24.resolve(base);
13769
+ const target = path26.resolve(base);
13382
13770
  let st;
13383
13771
  try {
13384
- st = fs20.statSync(target);
13772
+ st = fs21.statSync(target);
13385
13773
  } catch {
13386
13774
  throw new Error(`\u8DEF\u5F84\u4E0D\u5B58\u5728: ${target}`);
13387
13775
  }
13388
13776
  if (!st.isDirectory()) throw new Error(`\u4E0D\u662F\u76EE\u5F55: ${target}`);
13389
13777
  let dirents;
13390
13778
  try {
13391
- dirents = fs20.readdirSync(target, { withFileTypes: true });
13779
+ dirents = fs21.readdirSync(target, { withFileTypes: true });
13392
13780
  } catch {
13393
13781
  throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF08\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09: ${target}`);
13394
13782
  }
13395
13783
  const entries = dirents.map((d) => {
13396
- const full = path24.join(target, d.name);
13784
+ const full = path26.join(target, d.name);
13397
13785
  let isDir = d.isDirectory();
13398
13786
  if (d.isSymbolicLink()) {
13399
13787
  try {
13400
- isDir = fs20.statSync(full).isDirectory();
13788
+ isDir = fs21.statSync(full).isDirectory();
13401
13789
  } catch {
13402
13790
  }
13403
13791
  }
@@ -13406,7 +13794,7 @@ function listDirs(input) {
13406
13794
  entries.sort(
13407
13795
  (a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1
13408
13796
  );
13409
- const parentDir = path24.dirname(target);
13797
+ const parentDir = path26.dirname(target);
13410
13798
  return {
13411
13799
  path: target,
13412
13800
  parent: parentDir !== target ? parentDir : null,
@@ -13437,7 +13825,7 @@ var NAME_CACHE_MAX_DIRS = 4;
13437
13825
  var NAME_CACHE_MAX_ENTRIES = 5e4;
13438
13826
  var nameCache = /* @__PURE__ */ new Map();
13439
13827
  function invalidateDirNames(target) {
13440
- nameCache.delete(path24.resolve(target));
13828
+ nameCache.delete(path26.resolve(target));
13441
13829
  }
13442
13830
  async function loadDirNames(target) {
13443
13831
  const now = Date.now();
@@ -13449,7 +13837,7 @@ async function loadDirNames(target) {
13449
13837
  }
13450
13838
  let dirents;
13451
13839
  try {
13452
- dirents = await fs20.promises.readdir(target, { withFileTypes: true });
13840
+ dirents = await fs21.promises.readdir(target, { withFileTypes: true });
13453
13841
  } catch {
13454
13842
  throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF08\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09: ${target}`);
13455
13843
  }
@@ -13472,11 +13860,11 @@ async function loadDirNames(target) {
13472
13860
  return entries;
13473
13861
  }
13474
13862
  async function toDirEntry(dir, e) {
13475
- const full = path24.join(dir, e.name);
13863
+ const full = path26.join(dir, e.name);
13476
13864
  let isDir = e.isDir;
13477
13865
  if (e.isLink) {
13478
13866
  try {
13479
- isDir = (await fs20.promises.stat(full)).isDirectory();
13867
+ isDir = (await fs21.promises.stat(full)).isDirectory();
13480
13868
  } catch {
13481
13869
  }
13482
13870
  }
@@ -13484,10 +13872,10 @@ async function toDirEntry(dir, e) {
13484
13872
  }
13485
13873
  async function searchDirs(input, query, limit) {
13486
13874
  const base = (input ?? "").trim() || os4.homedir();
13487
- const target = path24.resolve(base);
13875
+ const target = path26.resolve(base);
13488
13876
  let st;
13489
13877
  try {
13490
- st = await fs20.promises.stat(target);
13878
+ st = await fs21.promises.stat(target);
13491
13879
  } catch {
13492
13880
  throw new Error(`\u8DEF\u5F84\u4E0D\u5B58\u5728: ${target}`);
13493
13881
  }
@@ -13517,7 +13905,7 @@ async function searchDirs(input, query, limit) {
13517
13905
  );
13518
13906
  const hit = scored.slice(0, max);
13519
13907
  const entries = await Promise.all(hit.map(({ entry }) => toDirEntry(target, entry)));
13520
- const parentDir = path24.dirname(target);
13908
+ const parentDir = path26.dirname(target);
13521
13909
  return {
13522
13910
  path: target,
13523
13911
  parent: parentDir !== target ? parentDir : null,
@@ -13528,52 +13916,52 @@ async function searchDirs(input, query, limit) {
13528
13916
  };
13529
13917
  }
13530
13918
  function makeDirIn(parentInput, name) {
13531
- const parent = path24.resolve((parentInput ?? "").trim() || os4.homedir());
13919
+ const parent = path26.resolve((parentInput ?? "").trim() || os4.homedir());
13532
13920
  const clean = (name ?? "").trim();
13533
13921
  if (!clean) throw new Error("\u76EE\u5F55\u540D\u4E0D\u80FD\u4E3A\u7A7A");
13534
13922
  if (clean === "." || clean === "..") throw new Error("\u76EE\u5F55\u540D\u975E\u6CD5");
13535
- const target = path24.resolve(parent, clean);
13536
- const rel = path24.relative(parent, target);
13537
- if (!rel || rel.startsWith("..") || path24.isAbsolute(rel)) {
13923
+ const target = path26.resolve(parent, clean);
13924
+ const rel = path26.relative(parent, target);
13925
+ if (!rel || rel.startsWith("..") || path26.isAbsolute(rel)) {
13538
13926
  throw new Error("\u76EE\u5F55\u540D\u4E0D\u80FD\u8D8A\u51FA\u5F53\u524D\u76EE\u5F55");
13539
13927
  }
13540
13928
  let st = null;
13541
13929
  try {
13542
- st = fs20.statSync(target);
13930
+ st = fs21.statSync(target);
13543
13931
  } catch {
13544
13932
  st = null;
13545
13933
  }
13546
13934
  if (st && !st.isDirectory()) throw new Error(`\u5DF2\u5B58\u5728\u540C\u540D\u6587\u4EF6: ${target}`);
13547
13935
  try {
13548
- fs20.mkdirSync(target, { recursive: true });
13936
+ fs21.mkdirSync(target, { recursive: true });
13549
13937
  } catch (e) {
13550
13938
  throw new Error(`\u521B\u5EFA\u76EE\u5F55\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
13551
13939
  }
13552
13940
  invalidateDirNames(parent);
13553
- return { name: path24.basename(target), path: target, type: "dir" };
13941
+ return { name: path26.basename(target), path: target, type: "dir" };
13554
13942
  }
13555
13943
 
13556
13944
  // src/server/projectFiles.ts
13557
13945
  init_cjs_shims();
13558
- var fs21 = __toESM(require("fs"), 1);
13559
- var path25 = __toESM(require("path"), 1);
13946
+ var fs22 = __toESM(require("fs"), 1);
13947
+ var path27 = __toESM(require("path"), 1);
13560
13948
  var MAX_FILE_BYTES = 2 * 1024 * 1024;
13561
13949
  var MAX_DOWNLOAD_BYTES = 16 * 1024 * 1024;
13562
13950
  var MAX_ENTRIES2 = 2e3;
13563
13951
  function toPosix(p) {
13564
- return p.split(path25.sep).join("/");
13952
+ return p.split(path27.sep).join("/");
13565
13953
  }
13566
13954
  function projectRoot(workspaceRoot) {
13567
13955
  const r = (workspaceRoot ?? "").trim();
13568
13956
  if (!r) {
13569
13957
  throw new Error("\u9879\u76EE\u672A\u8BBE\u7F6E\u5DE5\u4F5C\u533A\u76EE\u5F55\uFF08workspaceRoot\uFF09\uFF0C\u65E0\u6CD5\u6D4F\u89C8\u6587\u4EF6");
13570
13958
  }
13571
- const abs = path25.resolve(r);
13572
- if (!fs21.existsSync(abs)) {
13959
+ const abs = path27.resolve(r);
13960
+ if (!fs22.existsSync(abs)) {
13573
13961
  throw new Error(`\u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728: ${abs}`);
13574
13962
  }
13575
13963
  try {
13576
- return fs21.realpathSync(abs);
13964
+ return fs22.realpathSync(abs);
13577
13965
  } catch {
13578
13966
  return abs;
13579
13967
  }
@@ -13591,37 +13979,37 @@ function listProjectFiles(workspaceRoot, relPath) {
13591
13979
  const dir = resolveWithin(base, relPath ?? "");
13592
13980
  let st;
13593
13981
  try {
13594
- st = fs21.statSync(dir);
13982
+ st = fs22.statSync(dir);
13595
13983
  } catch {
13596
- throw new Error(`\u76EE\u5F55\u4E0D\u5B58\u5728: ${toPosix(path25.relative(base, dir)) || "/"}`);
13984
+ throw new Error(`\u76EE\u5F55\u4E0D\u5B58\u5728: ${toPosix(path27.relative(base, dir)) || "/"}`);
13597
13985
  }
13598
13986
  if (!st.isDirectory()) throw new Error("\u76EE\u6807\u4E0D\u662F\u76EE\u5F55");
13599
13987
  let dirents;
13600
13988
  try {
13601
- dirents = fs21.readdirSync(dir, { withFileTypes: true });
13989
+ dirents = fs22.readdirSync(dir, { withFileTypes: true });
13602
13990
  } catch {
13603
13991
  throw new Error("\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF08\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09");
13604
13992
  }
13605
13993
  const entries = dirents.map((d) => {
13606
- const full = path25.join(dir, d.name);
13994
+ const full = path27.join(dir, d.name);
13607
13995
  let isDir = d.isDirectory();
13608
13996
  if (d.isSymbolicLink()) {
13609
13997
  try {
13610
- isDir = fs21.statSync(full).isDirectory();
13998
+ isDir = fs22.statSync(full).isDirectory();
13611
13999
  } catch {
13612
14000
  }
13613
14001
  }
13614
14002
  let size = 0;
13615
14003
  let mtime = "";
13616
14004
  try {
13617
- const s = fs21.statSync(full);
14005
+ const s = fs22.statSync(full);
13618
14006
  size = s.size;
13619
14007
  mtime = s.mtime.toISOString();
13620
14008
  } catch {
13621
14009
  }
13622
14010
  return {
13623
14011
  name: d.name,
13624
- path: toPosix(path25.relative(base, full)),
14012
+ path: toPosix(path27.relative(base, full)),
13625
14013
  type: isDir ? "dir" : "file",
13626
14014
  size,
13627
14015
  mtime
@@ -13630,12 +14018,12 @@ function listProjectFiles(workspaceRoot, relPath) {
13630
14018
  entries.sort(
13631
14019
  (a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1
13632
14020
  );
13633
- const relDir = toPosix(path25.relative(base, dir));
13634
- const parentAbs = path25.dirname(dir);
14021
+ const relDir = toPosix(path27.relative(base, dir));
14022
+ const parentAbs = path27.dirname(dir);
13635
14023
  return {
13636
14024
  root: base,
13637
14025
  path: relDir,
13638
- parent: isWithinRoot(base, parentAbs) ? toPosix(path25.relative(base, parentAbs)) : null,
14026
+ parent: isWithinRoot(base, parentAbs) ? toPosix(path27.relative(base, parentAbs)) : null,
13639
14027
  entries: entries.slice(0, MAX_ENTRIES2)
13640
14028
  };
13641
14029
  }
@@ -13644,17 +14032,17 @@ function readProjectFile(workspaceRoot, relPath) {
13644
14032
  const file = resolveWithin(base, relPath ?? "");
13645
14033
  let st;
13646
14034
  try {
13647
- st = fs21.statSync(file);
14035
+ st = fs22.statSync(file);
13648
14036
  } catch {
13649
14037
  throw new Error("\u6587\u4EF6\u4E0D\u5B58\u5728");
13650
14038
  }
13651
14039
  if (!st.isFile()) throw new Error("\u76EE\u6807\u4E0D\u662F\u6587\u4EF6");
13652
- const buf = fs21.readFileSync(file);
14040
+ const buf = fs22.readFileSync(file);
13653
14041
  const binary = isBinary(buf);
13654
14042
  const truncated = buf.length > MAX_FILE_BYTES;
13655
14043
  const slice = truncated ? buf.subarray(0, MAX_FILE_BYTES) : buf;
13656
14044
  return {
13657
- path: toPosix(path25.relative(base, file)),
14045
+ path: toPosix(path27.relative(base, file)),
13658
14046
  size: st.size,
13659
14047
  binary,
13660
14048
  truncated,
@@ -13666,7 +14054,7 @@ function downloadProjectFile(workspaceRoot, relPath) {
13666
14054
  const file = resolveWithin(base, relPath ?? "");
13667
14055
  let st;
13668
14056
  try {
13669
- st = fs21.statSync(file);
14057
+ st = fs22.statSync(file);
13670
14058
  } catch {
13671
14059
  throw new Error("\u6587\u4EF6\u4E0D\u5B58\u5728");
13672
14060
  }
@@ -13674,10 +14062,10 @@ function downloadProjectFile(workspaceRoot, relPath) {
13674
14062
  if (st.size > MAX_DOWNLOAD_BYTES) {
13675
14063
  throw new Error(`\u6587\u4EF6\u8D85\u8FC7\u4E0B\u8F7D\u4E0A\u9650\uFF08${MAX_DOWNLOAD_BYTES / (1024 * 1024)}MB\uFF09`);
13676
14064
  }
13677
- const buf = fs21.readFileSync(file);
14065
+ const buf = fs22.readFileSync(file);
13678
14066
  return {
13679
- path: toPosix(path25.relative(base, file)),
13680
- name: path25.basename(file),
14067
+ path: toPosix(path27.relative(base, file)),
14068
+ name: path27.basename(file),
13681
14069
  size: buf.length,
13682
14070
  base64: buf.toString("base64")
13683
14071
  };
@@ -13699,12 +14087,12 @@ function writeProjectFile(workspaceRoot, relPath, content) {
13699
14087
  throw new Error("\u5185\u5BB9\u8D85\u8FC7 2MB \u4E0A\u9650\uFF0C\u65E0\u6CD5\u4FDD\u5B58");
13700
14088
  }
13701
14089
  try {
13702
- fs21.mkdirSync(path25.dirname(file), { recursive: true });
13703
- fs21.writeFileSync(file, buf);
14090
+ fs22.mkdirSync(path27.dirname(file), { recursive: true });
14091
+ fs22.writeFileSync(file, buf);
13704
14092
  } catch (e) {
13705
14093
  throw new Error(`\u5199\u5165\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
13706
14094
  }
13707
- return { path: toPosix(path25.relative(base, file)), size: buf.length };
14095
+ return { path: toPosix(path27.relative(base, file)), size: buf.length };
13708
14096
  }
13709
14097
  function createProjectDir(workspaceRoot, relPath) {
13710
14098
  const base = projectRoot(workspaceRoot);
@@ -13712,26 +14100,26 @@ function createProjectDir(workspaceRoot, relPath) {
13712
14100
  if (!rel) throw new Error("\u76EE\u5F55\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A");
13713
14101
  const dir = resolveWithin(base, rel);
13714
14102
  try {
13715
- fs21.mkdirSync(dir, { recursive: true });
14103
+ fs22.mkdirSync(dir, { recursive: true });
13716
14104
  } catch (e) {
13717
14105
  throw new Error(`\u521B\u5EFA\u76EE\u5F55\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
13718
14106
  }
13719
- return { path: toPosix(path25.relative(base, dir)) };
14107
+ return { path: toPosix(path27.relative(base, dir)) };
13720
14108
  }
13721
14109
  function createProjectFile(workspaceRoot, relPath, content = "") {
13722
14110
  const base = projectRoot(workspaceRoot);
13723
14111
  const rel = (relPath ?? "").trim();
13724
14112
  if (!rel) throw new Error("\u6587\u4EF6\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A");
13725
14113
  const file = resolveWithin(base, rel);
13726
- if (fs21.existsSync(file)) throw new Error("\u6587\u4EF6\u5DF2\u5B58\u5728");
14114
+ if (fs22.existsSync(file)) throw new Error("\u6587\u4EF6\u5DF2\u5B58\u5728");
13727
14115
  const buf = Buffer.from(String(content ?? ""), "utf-8");
13728
14116
  try {
13729
- fs21.mkdirSync(path25.dirname(file), { recursive: true });
13730
- fs21.writeFileSync(file, buf);
14117
+ fs22.mkdirSync(path27.dirname(file), { recursive: true });
14118
+ fs22.writeFileSync(file, buf);
13731
14119
  } catch (e) {
13732
14120
  throw new Error(`\u521B\u5EFA\u6587\u4EF6\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
13733
14121
  }
13734
- return { path: toPosix(path25.relative(base, file)), size: buf.length };
14122
+ return { path: toPosix(path27.relative(base, file)), size: buf.length };
13735
14123
  }
13736
14124
  function searchProjectFiles(workspaceRoot, query) {
13737
14125
  const base = projectRoot(workspaceRoot);
@@ -13754,7 +14142,7 @@ function searchProjectFiles(workspaceRoot, query) {
13754
14142
  if (out.length >= LIMIT) return;
13755
14143
  let dirents;
13756
14144
  try {
13757
- dirents = fs21.readdirSync(dir, { withFileTypes: true });
14145
+ dirents = fs22.readdirSync(dir, { withFileTypes: true });
13758
14146
  } catch {
13759
14147
  return;
13760
14148
  }
@@ -13765,7 +14153,7 @@ function searchProjectFiles(workspaceRoot, query) {
13765
14153
  let isDir = d.isDirectory();
13766
14154
  if (d.isSymbolicLink()) {
13767
14155
  try {
13768
- isDir = fs21.statSync(path25.join(dir, d.name)).isDirectory();
14156
+ isDir = fs22.statSync(path27.join(dir, d.name)).isDirectory();
13769
14157
  } catch {
13770
14158
  isDir = false;
13771
14159
  }
@@ -13773,7 +14161,7 @@ function searchProjectFiles(workspaceRoot, query) {
13773
14161
  if (d.name.toLowerCase().includes(q)) {
13774
14162
  out.push({ path: rel, type: isDir ? "dir" : "file" });
13775
14163
  }
13776
- if (isDir) walk(path25.join(dir, d.name), rel);
14164
+ if (isDir) walk(path27.join(dir, d.name), rel);
13777
14165
  }
13778
14166
  };
13779
14167
  walk(base, "");
@@ -13783,7 +14171,7 @@ function searchProjectFiles(workspaceRoot, query) {
13783
14171
  // src/server/projectOps.ts
13784
14172
  init_cjs_shims();
13785
14173
  var os5 = __toESM(require("os"), 1);
13786
- var path26 = __toESM(require("path"), 1);
14174
+ var path28 = __toESM(require("path"), 1);
13787
14175
  var MAX_STDOUT2 = 2e4;
13788
14176
  var MAX_STDERR2 = 1e4;
13789
14177
  var MAX_BUFFER3 = 1024 * 1024 * 5;
@@ -13797,7 +14185,7 @@ function resolveInvocation2(spec, prepared) {
13797
14185
  }
13798
14186
  function workRoot(workspaceRoot) {
13799
14187
  const r = (workspaceRoot ?? "").trim();
13800
- return r ? path26.resolve(r) : os5.homedir();
14188
+ return r ? path28.resolve(r) : os5.homedir();
13801
14189
  }
13802
14190
  async function runProjectCommand(workspaceRoot, command, timeoutSeconds) {
13803
14191
  const cmd = String(command ?? "");
@@ -13917,6 +14305,92 @@ async function projectGitPush(workspaceRoot, repoPath) {
13917
14305
  });
13918
14306
  }
13919
14307
 
14308
+ // src/server/gitCommitMessage.ts
14309
+ init_cjs_shims();
14310
+ var crypto5 = __toESM(require("crypto"), 1);
14311
+ var MAX_DIFF_CHARS = 12e3;
14312
+ var MAX_MESSAGE_CHARS = 200;
14313
+ 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
14314
+
14315
+ \u8981\u6C42\uFF1A
14316
+ 1. \u4F7F\u7528 Conventional Commits \u98CE\u683C\uFF1Atype(scope): \u63CF\u8FF0\uFF08type \u5982 feat / fix / refactor / docs / test / chore / perf / build / ci\uFF09\uFF1B
14317
+ 2. scope \u53D6\u53D8\u66F4\u6700\u96C6\u4E2D\u7684\u6A21\u5757\u6216\u76EE\u5F55\uFF0C\u65E0\u6CD5\u5224\u65AD\u65F6\u7701\u7565\uFF1B
14318
+ 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
14319
+ 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
14320
+ 5. \u4E0D\u8D85\u8FC7 72 \u4E2A\u5B57\u7B26\u3002`;
14321
+ function genReqId2() {
14322
+ return crypto5.randomBytes(6).toString("base64url");
14323
+ }
14324
+ function cleanCommitMessage(raw) {
14325
+ let text = String(raw ?? "").trim();
14326
+ if (!text) return "";
14327
+ text = text.replace(/^```[^\n]*\n?/, "").replace(/\n?```$/, "").trim();
14328
+ const first = text.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0) ?? "";
14329
+ return first.replace(/^(commit message|提交说明|提交信息|message)\s*[::]\s*/i, "").replace(/^["'“”`]+|["'“”`]+$/g, "").replace(/\s+/g, " ").trim().slice(0, MAX_MESSAGE_CHARS);
14330
+ }
14331
+ async function collectDiff(workspaceRoot, repo) {
14332
+ const staged = await projectGitDiff(workspaceRoot, void 0, true, repo);
14333
+ if (!staged.error && String(staged.diff ?? "").trim()) {
14334
+ return { text: String(staged.diff), source: "staged" };
14335
+ }
14336
+ const worktree = await projectGitDiff(workspaceRoot, void 0, false, repo);
14337
+ if (!worktree.error && String(worktree.diff ?? "").trim()) {
14338
+ return { text: String(worktree.diff), source: "worktree" };
14339
+ }
14340
+ const status = await projectGitStatus(workspaceRoot, void 0, repo);
14341
+ if (status.error) throw new ApiError(400, "git_error", String(status.error));
14342
+ const entries = status.entries ?? [];
14343
+ if (entries.length === 0) throw new ApiError(400, "no_changes", "\u6CA1\u6709\u53EF\u63D0\u4EA4\u7684\u53D8\u66F4");
14344
+ return {
14345
+ text: entries.map((e) => `${e.status ?? ""} ${e.file ?? ""}`.trim()).join("\n"),
14346
+ source: "worktree"
14347
+ };
14348
+ }
14349
+ async function generateGitCommitMessage(opts) {
14350
+ const { store, tenant } = opts;
14351
+ const { text, source } = await collectDiff(opts.workspaceRoot, opts.repo);
14352
+ const truncated = text.length > MAX_DIFF_CHARS;
14353
+ const clipped = text.slice(0, MAX_DIFF_CHARS);
14354
+ const eff = resolveSessionProvider(store, tenant);
14355
+ const apiKey = eff.apiKey || process.env.OPENAI_API_KEY || "";
14356
+ const model = (eff.model || tenant.model || process.env.OPENAI_MODEL || process.env.MODEL || "").trim();
14357
+ if (!apiKey) {
14358
+ throw new ApiError(
14359
+ 400,
14360
+ "llm_not_configured",
14361
+ "\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"
14362
+ );
14363
+ }
14364
+ if (!model) {
14365
+ throw new ApiError(
14366
+ 400,
14367
+ "llm_not_configured",
14368
+ "\u5F53\u524D\u8282\u70B9\u672A\u914D\u7F6E\u6A21\u578B\uFF0C\u8BF7\u5148\u5728\u8282\u70B9\u914D\u7F6E\u4E2D\u9009\u62E9\u6A21\u578B"
14369
+ );
14370
+ }
14371
+ const provider = new OpenAIProvider({ apiKey, baseURL: eff.baseURL });
14372
+ const raw = await provider.chat(
14373
+ model,
14374
+ [
14375
+ { role: "system", content: GEN_COMMIT_MESSAGE_INSTRUCTIONS },
14376
+ {
14377
+ role: "user",
14378
+ 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
14379
+
14380
+ ${clipped}`
14381
+ }
14382
+ ],
14383
+ {},
14384
+ { reqId: `llm_commit_${Date.now()}_${genReqId2()}`, logTime: (/* @__PURE__ */ new Date()).toISOString() },
14385
+ // 不设 maxTokens:推理模型的思考会占用输出配额,过小会导致 content 为空;
14386
+ // 提示词已约束只输出一行短说明。
14387
+ {}
14388
+ );
14389
+ const message = cleanCommitMessage(raw ?? "");
14390
+ if (!message) throw new ApiError(502, "llm_empty", "AI \u672A\u8FD4\u56DE\u6709\u6548\u7684\u63D0\u4EA4\u8BF4\u660E\uFF0C\u8BF7\u91CD\u8BD5");
14391
+ return message;
14392
+ }
14393
+
13920
14394
  // src/server/toolsCatalog.ts
13921
14395
  init_cjs_shims();
13922
14396
  function buildToolCatalog(workspace) {
@@ -14022,11 +14496,11 @@ function projectStats(manager, tenantId) {
14022
14496
 
14023
14497
  // src/server/nodeHost.ts
14024
14498
  init_cjs_shims();
14025
- var fs22 = __toESM(require("fs"), 1);
14499
+ var fs23 = __toESM(require("fs"), 1);
14026
14500
  var os6 = __toESM(require("os"), 1);
14027
14501
  function diskInfo(pathStr) {
14028
14502
  try {
14029
- const st = fs22.statfsSync(pathStr);
14503
+ const st = fs23.statfsSync(pathStr);
14030
14504
  return {
14031
14505
  path: pathStr,
14032
14506
  total: Number(st.blocks) * Number(st.bsize),
@@ -14098,7 +14572,7 @@ function buildNodeHost(store, manager, diskPath) {
14098
14572
  arch: process.arch,
14099
14573
  release: os6.release(),
14100
14574
  uptimeSec: Math.round(os6.uptime()),
14101
- version: "0.2.4",
14575
+ version: "0.2.6",
14102
14576
  mode: process.env.NODE_MODE ?? "standalone",
14103
14577
  cpu: {
14104
14578
  count: cpus2.length,
@@ -14673,7 +15147,7 @@ function createApp(store, manager, opts = {}) {
14673
15147
  applyProvidersPatch(tenant, raw);
14674
15148
  if (typeof raw.workspaceRoot === "string") {
14675
15149
  const ws = raw.workspaceRoot.trim();
14676
- if (ws) tenant.workspaceRoot = path27.resolve(ws);
15150
+ if (ws) tenant.workspaceRoot = path29.resolve(ws);
14677
15151
  else delete tenant.workspaceRoot;
14678
15152
  }
14679
15153
  if (typeof raw.name === "string" && raw.name.trim()) {
@@ -15082,6 +15556,25 @@ function createApp(store, manager, opts = {}) {
15082
15556
  }
15083
15557
  }
15084
15558
  },
15559
+ {
15560
+ method: "POST",
15561
+ pattern: ["git", "commit-message"],
15562
+ fn: async (_req, res, { tenant, body }) => {
15563
+ const raw = body ?? {};
15564
+ try {
15565
+ const message = await generateGitCommitMessage({
15566
+ store,
15567
+ tenant,
15568
+ workspaceRoot: tenant.workspaceRoot ?? null,
15569
+ repo: String(raw.repo ?? "") || void 0
15570
+ });
15571
+ ok(res, { message });
15572
+ } catch (e) {
15573
+ if (e instanceof ApiError) throw e;
15574
+ throw new ApiError(400, "bad_request", e instanceof Error ? e.message : String(e));
15575
+ }
15576
+ }
15577
+ },
15085
15578
  {
15086
15579
  method: "POST",
15087
15580
  pattern: ["git", "checkout"],
@@ -15605,10 +16098,10 @@ function createApp(store, manager, opts = {}) {
15605
16098
  function handle(req2, res) {
15606
16099
  const startedAt2 = Date.now();
15607
16100
  const url = new URL(req2.url ?? "/", "http://localhost");
15608
- const path41 = url.pathname;
16101
+ const path43 = url.pathname;
15609
16102
  const method = req2.method ?? "GET";
15610
16103
  console.log(
15611
- `[api] ${(/* @__PURE__ */ new Date()).toISOString()} \u2192 ${method} ${path41}${url.search}`
16104
+ `[api] ${(/* @__PURE__ */ new Date()).toISOString()} \u2192 ${method} ${path43}${url.search}`
15612
16105
  );
15613
16106
  let tenantId = null;
15614
16107
  let logged = false;
@@ -15616,7 +16109,7 @@ function createApp(store, manager, opts = {}) {
15616
16109
  if (logged) return;
15617
16110
  logged = true;
15618
16111
  console.log(
15619
- `[api] ${(/* @__PURE__ */ new Date()).toISOString()} \u2190 ${method} ${path41} ${extra} (${Date.now() - startedAt2}ms) tenant=${tenantId ?? "-"}`
16112
+ `[api] ${(/* @__PURE__ */ new Date()).toISOString()} \u2190 ${method} ${path43} ${extra} (${Date.now() - startedAt2}ms) tenant=${tenantId ?? "-"}`
15620
16113
  );
15621
16114
  };
15622
16115
  res.on("finish", () => logDone(`HTTP ${res.statusCode}`));
@@ -15627,16 +16120,16 @@ function createApp(store, manager, opts = {}) {
15627
16120
  res.end();
15628
16121
  return;
15629
16122
  }
15630
- if (req2.method === "GET" && path41 === "/healthz") {
16123
+ if (req2.method === "GET" && path43 === "/healthz") {
15631
16124
  json(res, 200, {
15632
16125
  status: "ok",
15633
16126
  // 版本让运维/探针能确认线上跑的是哪个构建(更新与回滚据此判断)
15634
- version: "0.2.4",
16127
+ version: "0.2.6",
15635
16128
  features: { mcp: hasServerMcpConfig() }
15636
16129
  });
15637
16130
  return;
15638
16131
  }
15639
- if (hubProxyEnabled(opts.host) && isHubProxyPath(path41)) {
16132
+ if (hubProxyEnabled(opts.host) && isHubProxyPath(path43)) {
15640
16133
  void handleHubProxy(req2, res).catch(() => {
15641
16134
  if (!res.headersSent) {
15642
16135
  res.writeHead(502, { "Content-Type": "application/json; charset=utf-8" });
@@ -15647,11 +16140,11 @@ function createApp(store, manager, opts = {}) {
15647
16140
  });
15648
16141
  return;
15649
16142
  }
15650
- if (!path41.startsWith(API_PREFIX)) {
16143
+ if (!path43.startsWith(API_PREFIX)) {
15651
16144
  json(res, 404, { error: { code: "not_found", message: "\u672A\u77E5\u8DEF\u5F84" } });
15652
16145
  return;
15653
16146
  }
15654
- const segs = path41.slice(API_PREFIX.length).split("/").filter(Boolean);
16147
+ const segs = path43.slice(API_PREFIX.length).split("/").filter(Boolean);
15655
16148
  const apiKey = extractApiKey(req2);
15656
16149
  if (!apiKey) {
15657
16150
  json(res, 401, {
@@ -15716,7 +16209,7 @@ function createApp(store, manager, opts = {}) {
15716
16209
  ).catch((err) => {
15717
16210
  const msg = err instanceof Error ? err.message : String(err);
15718
16211
  console.log(
15719
- `[api] ${(/* @__PURE__ */ new Date()).toISOString()} ! ${method} ${path41} ERROR: ${msg}`
16212
+ `[api] ${(/* @__PURE__ */ new Date()).toISOString()} ! ${method} ${path43} ERROR: ${msg}`
15720
16213
  );
15721
16214
  error(res, err);
15722
16215
  });
@@ -16231,6 +16724,17 @@ async function dispatchFleetCommand(deps, env3) {
16231
16724
  )
16232
16725
  );
16233
16726
  }
16727
+ case "git_commit_message": {
16728
+ const t = project();
16729
+ return ok2({
16730
+ message: await generateGitCommitMessage({
16731
+ store,
16732
+ tenant: t,
16733
+ workspaceRoot: t.workspaceRoot ?? null,
16734
+ repo: gitRepo(payload.repo)
16735
+ })
16736
+ });
16737
+ }
16234
16738
  case "git_checkout": {
16235
16739
  const t = project();
16236
16740
  return ok2(
@@ -16871,6 +17375,18 @@ var READONLY_CMD_TYPES = /* @__PURE__ */ new Set([
16871
17375
  "git_show"
16872
17376
  ]);
16873
17377
  var CMD_CACHE_TTL_MS = 1500;
17378
+ var STATE_CHANGING_EVENTS = /* @__PURE__ */ new Set([
17379
+ "turn_start",
17380
+ "turn_end",
17381
+ "permission_required",
17382
+ "ask_user_question",
17383
+ "plan_proposed",
17384
+ "directory_requested",
17385
+ "error",
17386
+ "interrupted",
17387
+ "queue_steering",
17388
+ "session_named"
17389
+ ]);
16874
17390
  var READONLY_CONCURRENCY = 4;
16875
17391
  var BATCH_MAX_SUBS = 64;
16876
17392
  var BATCH_SUB_TIMEOUT_MS = 7e3;
@@ -17106,6 +17622,20 @@ var FleetNodeBridge = class {
17106
17622
  this.writeEpoch += 1;
17107
17623
  this.cmdResultCache.clear();
17108
17624
  }
17625
+ /**
17626
+ * 会话快照缓存失效(事件/通知驱动的状态变化)。
17627
+ *
17628
+ * 与写操作不同,这些变化只影响会话相关查询;文件树 / Git 等重查询的缓存
17629
+ * 保持有效,避免多会话并行运行时把整个只读缓存反复清空。
17630
+ */
17631
+ _invalidateSessionCache() {
17632
+ this.writeEpoch += 1;
17633
+ for (const key of [...this.cmdResultCache.keys()]) {
17634
+ if (/^(get_state|get_events|list_sessions|get_session_states)\|/.test(key)) {
17635
+ this.cmdResultCache.delete(key);
17636
+ }
17637
+ }
17638
+ }
17109
17639
  close() {
17110
17640
  this.closing = true;
17111
17641
  if (this.usageTimer) {
@@ -17134,6 +17664,7 @@ var FleetNodeBridge = class {
17134
17664
  }
17135
17665
  /** 通知广播(SessionManager 注入的 notify 回调) */
17136
17666
  notify(tenantId, ev) {
17667
+ this._invalidateSessionCache();
17137
17668
  const payload = ev.kind === "approval_request" ? {
17138
17669
  ...ev,
17139
17670
  projectId: tenantId,
@@ -17146,6 +17677,7 @@ var FleetNodeBridge = class {
17146
17677
  }
17147
17678
  /** 事件桥接(SessionManager 注入的 onEvent 回调);发布按窗口批量合并 */
17148
17679
  onEvent(tenantId, sessionId, entry) {
17680
+ if (STATE_CHANGING_EVENTS.has(entry.type)) this._invalidateSessionCache();
17149
17681
  if (entry.type === "turn_end") this.stats.turns += 1;
17150
17682
  else if (entry.type === "tool_finished") this.stats.toolCalls += 1;
17151
17683
  else if (entry.type === "error") this.stats.errors += 1;
@@ -17230,7 +17762,7 @@ var FleetNodeBridge = class {
17230
17762
  label: this.opts.deps.store.loadNodeLabel() ?? void 0,
17231
17763
  platform: process.platform,
17232
17764
  mode: process.env.NODE_MODE ?? "standalone",
17233
- version: "0.2.4",
17765
+ version: "0.2.6",
17234
17766
  ip: primaryIp()
17235
17767
  };
17236
17768
  }
@@ -17264,7 +17796,10 @@ var FleetNodeBridge = class {
17264
17796
  sessionId: s.sessionId,
17265
17797
  name: s.name ?? null,
17266
17798
  status: s.status,
17267
- lastUserMessage: lastUserMessage(s)
17799
+ lastUserMessage: lastUserMessage(s),
17800
+ // 控制面的聚合邮件靠它判断「这台机器最近有没有在干活」;
17801
+ // 状态跃迁可能被 info 节流跳过,完成时间不会。
17802
+ lastCompletedAt: s.lastCompletedAt ?? null
17268
17803
  });
17269
17804
  }
17270
17805
  } catch {
@@ -17282,7 +17817,7 @@ var FleetNodeBridge = class {
17282
17817
  label: this.opts.deps.store.loadNodeLabel() ?? void 0,
17283
17818
  platform: process.platform,
17284
17819
  mode: process.env.NODE_MODE ?? "standalone",
17285
- version: "0.2.4",
17820
+ version: "0.2.6",
17286
17821
  ip: primaryIp(),
17287
17822
  projects,
17288
17823
  totals,
@@ -18051,8 +18586,8 @@ init_cjs_shims();
18051
18586
 
18052
18587
  // src/server/qq/bindings.ts
18053
18588
  init_cjs_shims();
18054
- var fs23 = __toESM(require("fs"), 1);
18055
- var path28 = __toESM(require("path"), 1);
18589
+ var fs24 = __toESM(require("fs"), 1);
18590
+ var path30 = __toESM(require("path"), 1);
18056
18591
  function conversationKey(target) {
18057
18592
  switch (target.kind) {
18058
18593
  case "c2c":
@@ -18091,12 +18626,12 @@ var QqBindingsStore = class _QqBindingsStore {
18091
18626
  cache = null;
18092
18627
  /** 基于 DATA_DIR 构造 */
18093
18628
  static in(dataDir) {
18094
- return new _QqBindingsStore(path28.join(dataDir, "qq", "bindings.json"));
18629
+ return new _QqBindingsStore(path30.join(dataDir, "qq", "bindings.json"));
18095
18630
  }
18096
18631
  load() {
18097
18632
  if (this.cache) return this.cache;
18098
18633
  try {
18099
- const raw = JSON.parse(fs23.readFileSync(this.file, "utf-8"));
18634
+ const raw = JSON.parse(fs24.readFileSync(this.file, "utf-8"));
18100
18635
  this.cache = {
18101
18636
  conversations: raw.conversations ?? {}
18102
18637
  };
@@ -18128,8 +18663,8 @@ var QqBindingsStore = class _QqBindingsStore {
18128
18663
  return null;
18129
18664
  }
18130
18665
  persist() {
18131
- fs23.mkdirSync(path28.dirname(this.file), { recursive: true });
18132
- fs23.writeFileSync(this.file, JSON.stringify(this.cache, null, 2), "utf-8");
18666
+ fs24.mkdirSync(path30.dirname(this.file), { recursive: true });
18667
+ fs24.writeFileSync(this.file, JSON.stringify(this.cache, null, 2), "utf-8");
18133
18668
  }
18134
18669
  };
18135
18670
 
@@ -18541,8 +19076,8 @@ ${lines.join("\n")}`, msgId);
18541
19076
 
18542
19077
  // src/server/qq/QqManager.ts
18543
19078
  init_cjs_shims();
18544
- var fs24 = __toESM(require("fs"), 1);
18545
- var path29 = __toESM(require("path"), 1);
19079
+ var fs25 = __toESM(require("fs"), 1);
19080
+ var path31 = __toESM(require("path"), 1);
18546
19081
  var QqManager = class {
18547
19082
  constructor(opts) {
18548
19083
  this.opts = opts;
@@ -18551,7 +19086,7 @@ var QqManager = class {
18551
19086
  adapter = null;
18552
19087
  config = null;
18553
19088
  get configFile() {
18554
- return path29.join(this.opts.dataDir, "qq", "config.json");
19089
+ return path31.join(this.opts.dataDir, "qq", "config.json");
18555
19090
  }
18556
19091
  /** 启动:读配置(env 优先,其次文件),有则建连接 */
18557
19092
  start() {
@@ -18589,7 +19124,7 @@ var QqManager = class {
18589
19124
  /** 清空配置:删除文件 + 断开 */
18590
19125
  clearConfig() {
18591
19126
  try {
18592
- fs24.rmSync(this.configFile, { force: true });
19127
+ fs25.rmSync(this.configFile, { force: true });
18593
19128
  } catch {
18594
19129
  }
18595
19130
  this.stop();
@@ -18631,7 +19166,7 @@ var QqManager = class {
18631
19166
  }
18632
19167
  _readConfigFile() {
18633
19168
  try {
18634
- const raw = JSON.parse(fs24.readFileSync(this.configFile, "utf-8"));
19169
+ const raw = JSON.parse(fs25.readFileSync(this.configFile, "utf-8"));
18635
19170
  if (raw.appId && raw.appSecret) {
18636
19171
  return {
18637
19172
  appId: raw.appId,
@@ -18644,18 +19179,18 @@ var QqManager = class {
18644
19179
  return null;
18645
19180
  }
18646
19181
  _persistConfig(cfg) {
18647
- fs24.mkdirSync(path29.dirname(this.configFile), { recursive: true });
18648
- fs24.writeFileSync(this.configFile, JSON.stringify(cfg, null, 2), "utf-8");
19182
+ fs25.mkdirSync(path31.dirname(this.configFile), { recursive: true });
19183
+ fs25.writeFileSync(this.configFile, JSON.stringify(cfg, null, 2), "utf-8");
18649
19184
  }
18650
19185
  };
18651
19186
 
18652
19187
  // src/server/qq/pairingCodes.ts
18653
19188
  init_cjs_shims();
18654
- var crypto5 = __toESM(require("crypto"), 1);
19189
+ var crypto6 = __toESM(require("crypto"), 1);
18655
19190
  var PAIRING_CODE_TTL_MS = 5 * 60 * 1e3;
18656
19191
  function randomCode(len = 8) {
18657
19192
  const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
18658
- const bytes = crypto5.randomBytes(len);
19193
+ const bytes = crypto6.randomBytes(len);
18659
19194
  let out = "";
18660
19195
  for (let i = 0; i < len; i++) {
18661
19196
  out += alphabet[bytes[i] % alphabet.length];
@@ -18693,17 +19228,17 @@ var PairingCodeStore = class {
18693
19228
 
18694
19229
  // src/update/controller.ts
18695
19230
  init_cjs_shims();
18696
- var fs28 = __toESM(require("fs"), 1);
18697
- var path34 = __toESM(require("path"), 1);
19231
+ var fs29 = __toESM(require("fs"), 1);
19232
+ var path36 = __toESM(require("path"), 1);
18698
19233
 
18699
19234
  // src/update/downloader.ts
18700
19235
  init_cjs_shims();
18701
19236
  var import_node_crypto5 = require("crypto");
18702
- var fs25 = __toESM(require("fs"), 1);
19237
+ var fs26 = __toESM(require("fs"), 1);
18703
19238
  var import_node_stream2 = require("stream");
18704
19239
  var import_promises2 = require("stream/promises");
18705
19240
  var import_node_child_process5 = require("child_process");
18706
- var path30 = __toESM(require("path"), 1);
19241
+ var path32 = __toESM(require("path"), 1);
18707
19242
 
18708
19243
  // src/update/manifest.ts
18709
19244
  init_cjs_shims();
@@ -18878,8 +19413,8 @@ async function fetchManifest(url, publicKey, opts = {}) {
18878
19413
  async function downloadBinary(bin, destPath, log = () => {
18879
19414
  }) {
18880
19415
  const partPath = `${destPath}.part`;
18881
- fs25.mkdirSync(path30.dirname(destPath), { recursive: true });
18882
- fs25.rmSync(partPath, { force: true });
19416
+ fs26.mkdirSync(path32.dirname(destPath), { recursive: true });
19417
+ fs26.rmSync(partPath, { force: true });
18883
19418
  let res;
18884
19419
  try {
18885
19420
  res = await fetch(bin.url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS2) });
@@ -18904,25 +19439,25 @@ async function downloadBinary(bin, destPath, log = () => {
18904
19439
  yield chunk;
18905
19440
  }
18906
19441
  },
18907
- fs25.createWriteStream(partPath, { mode: 493 })
19442
+ fs26.createWriteStream(partPath, { mode: 493 })
18908
19443
  );
18909
19444
  } catch (err) {
18910
- fs25.rmSync(partPath, { force: true });
19445
+ fs26.rmSync(partPath, { force: true });
18911
19446
  return { error: `\u4E0B\u8F7D\u4E2D\u65AD\uFF1A${err.message}` };
18912
19447
  }
18913
19448
  const actual = hash.digest("hex");
18914
19449
  if (actual !== bin.sha256) {
18915
- fs25.rmSync(partPath, { force: true });
19450
+ fs26.rmSync(partPath, { force: true });
18916
19451
  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` };
18917
19452
  }
18918
19453
  if (expected && bytes !== expected) {
18919
- fs25.rmSync(partPath, { force: true });
19454
+ fs26.rmSync(partPath, { force: true });
18920
19455
  return { error: `\u4EA7\u7269\u5927\u5C0F\u4E0D\u5339\u914D\uFF08\u671F\u671B ${expected}\uFF0C\u5B9E\u9645 ${bytes}\uFF09` };
18921
19456
  }
18922
- fs25.rmSync(destPath, { force: true });
18923
- fs25.renameSync(partPath, destPath);
18924
- fs25.chmodSync(destPath, 493);
18925
- log(`\u5DF2\u4E0B\u8F7D ${path30.basename(destPath)}\uFF08${Math.round(bytes / 1024 / 1024)}MB\uFF0C\u6821\u9A8C\u901A\u8FC7\uFF09`);
19457
+ fs26.rmSync(destPath, { force: true });
19458
+ fs26.renameSync(partPath, destPath);
19459
+ fs26.chmodSync(destPath, 493);
19460
+ log(`\u5DF2\u4E0B\u8F7D ${path32.basename(destPath)}\uFF08${Math.round(bytes / 1024 / 1024)}MB\uFF0C\u6821\u9A8C\u901A\u8FC7\uFF09`);
18926
19461
  return { bytes };
18927
19462
  }
18928
19463
  function selfCheck(binPath, expectedVersion) {
@@ -18946,10 +19481,10 @@ function selfCheck(binPath, expectedVersion) {
18946
19481
 
18947
19482
  // src/update/platform.ts
18948
19483
  init_cjs_shims();
18949
- var path31 = __toESM(require("path"), 1);
19484
+ var path33 = __toESM(require("path"), 1);
18950
19485
  var import_node_module3 = require("module");
18951
19486
  function currentVersion() {
18952
- const injected = "0.2.4"?.trim();
19487
+ const injected = "0.2.6"?.trim();
18953
19488
  if (injected) return injected;
18954
19489
  try {
18955
19490
  const req2 = (0, import_node_module3.createRequire)(importMetaUrl);
@@ -18961,7 +19496,7 @@ function currentVersion() {
18961
19496
  }
18962
19497
  function isSeaRuntime() {
18963
19498
  if (process.env.ZQAGENT_SEA === "1") return true;
18964
- const base = path31.basename(process.execPath).toLowerCase().replace(/\.exe$/, "");
19499
+ const base = path33.basename(process.execPath).toLowerCase().replace(/\.exe$/, "");
18965
19500
  return base !== "node" && base !== "nodejs";
18966
19501
  }
18967
19502
  function serviceManager() {
@@ -18989,22 +19524,22 @@ function selfUpdateCapability(hasPublicKey) {
18989
19524
 
18990
19525
  // src/update/installer.ts
18991
19526
  init_cjs_shims();
18992
- var fs27 = __toESM(require("fs"), 1);
18993
- var path33 = __toESM(require("path"), 1);
19527
+ var fs28 = __toESM(require("fs"), 1);
19528
+ var path35 = __toESM(require("path"), 1);
18994
19529
 
18995
19530
  // src/update/state.ts
18996
19531
  init_cjs_shims();
18997
- var fs26 = __toESM(require("fs"), 1);
18998
- var path32 = __toESM(require("path"), 1);
19532
+ var fs27 = __toESM(require("fs"), 1);
19533
+ var path34 = __toESM(require("path"), 1);
18999
19534
  function updatesDir(dataDir) {
19000
- return path32.join(dataDir, "updates");
19535
+ return path34.join(dataDir, "updates");
19001
19536
  }
19002
19537
  function statePath(dataDir) {
19003
- return path32.join(updatesDir(dataDir), "state.json");
19538
+ return path34.join(updatesDir(dataDir), "state.json");
19004
19539
  }
19005
19540
  function loadState(dataDir) {
19006
19541
  try {
19007
- const raw = fs26.readFileSync(statePath(dataDir), "utf-8");
19542
+ const raw = fs27.readFileSync(statePath(dataDir), "utf-8");
19008
19543
  const parsed = JSON.parse(raw);
19009
19544
  if (typeof parsed === "object" && parsed !== null) return parsed;
19010
19545
  } catch {
@@ -19013,11 +19548,11 @@ function loadState(dataDir) {
19013
19548
  }
19014
19549
  function saveState(dataDir, state) {
19015
19550
  const dir = updatesDir(dataDir);
19016
- fs26.mkdirSync(dir, { recursive: true });
19551
+ fs27.mkdirSync(dir, { recursive: true });
19017
19552
  const file = statePath(dataDir);
19018
19553
  const tmp = `${file}.tmp`;
19019
- fs26.writeFileSync(tmp, JSON.stringify(state, null, 2), "utf-8");
19020
- fs26.renameSync(tmp, file);
19554
+ fs27.writeFileSync(tmp, JSON.stringify(state, null, 2), "utf-8");
19555
+ fs27.renameSync(tmp, file);
19021
19556
  }
19022
19557
  function patchState(dataDir, patch) {
19023
19558
  const next = { ...loadState(dataDir), ...patch };
@@ -19027,36 +19562,36 @@ function patchState(dataDir, patch) {
19027
19562
 
19028
19563
  // src/update/installer.ts
19029
19564
  function backupDir(dataDir) {
19030
- return path33.join(updatesDir(dataDir), "backup");
19565
+ return path35.join(updatesDir(dataDir), "backup");
19031
19566
  }
19032
19567
  function backupCurrent(dataDir, execPath, version) {
19033
19568
  const dir = backupDir(dataDir);
19034
- fs27.mkdirSync(dir, { recursive: true });
19035
- const target = path33.join(dir, `zqagent-${version}${process.platform === "win32" ? ".exe" : ""}`);
19036
- fs27.copyFileSync(execPath, target);
19037
- fs27.chmodSync(target, 493);
19569
+ fs28.mkdirSync(dir, { recursive: true });
19570
+ const target = path35.join(dir, `zqagent-${version}${process.platform === "win32" ? ".exe" : ""}`);
19571
+ fs28.copyFileSync(execPath, target);
19572
+ fs28.chmodSync(target, 493);
19038
19573
  return target;
19039
19574
  }
19040
19575
  function replaceExecutable(execPath, stagedPath) {
19041
19576
  if (process.platform === "win32") {
19042
19577
  const oldPath = `${execPath}.old`;
19043
- fs27.rmSync(oldPath, { force: true });
19044
- fs27.renameSync(execPath, oldPath);
19045
- fs27.copyFileSync(stagedPath, execPath);
19046
- fs27.chmodSync(execPath, 493);
19578
+ fs28.rmSync(oldPath, { force: true });
19579
+ fs28.renameSync(execPath, oldPath);
19580
+ fs28.copyFileSync(stagedPath, execPath);
19581
+ fs28.chmodSync(execPath, 493);
19047
19582
  return;
19048
19583
  }
19049
19584
  const tmp = `${execPath}.new`;
19050
- fs27.rmSync(tmp, { force: true });
19585
+ fs28.rmSync(tmp, { force: true });
19051
19586
  try {
19052
- fs27.copyFileSync(stagedPath, tmp);
19053
- fs27.chmodSync(tmp, 493);
19054
- fs27.renameSync(tmp, execPath);
19587
+ fs28.copyFileSync(stagedPath, tmp);
19588
+ fs28.chmodSync(tmp, 493);
19589
+ fs28.renameSync(tmp, execPath);
19055
19590
  } catch (err) {
19056
- fs27.rmSync(tmp, { force: true });
19591
+ fs28.rmSync(tmp, { force: true });
19057
19592
  throw err;
19058
19593
  }
19059
- fs27.rmSync(stagedPath, { force: true });
19594
+ fs28.rmSync(stagedPath, { force: true });
19060
19595
  }
19061
19596
  function installStaged(opts) {
19062
19597
  const log = opts.log ?? (() => {
@@ -19064,7 +19599,7 @@ function installStaged(opts) {
19064
19599
  const from = currentVersion();
19065
19600
  log(`\u6B63\u5728\u5B89\u88C5 v${opts.version}\uFF08\u5F53\u524D v${from}\uFF09`);
19066
19601
  const backupPath = backupCurrent(opts.dataDir, opts.execPath, from);
19067
- log(`\u5DF2\u5907\u4EFD\u5F53\u524D\u7248\u672C\u5230 ${path33.basename(backupPath)}`);
19602
+ log(`\u5DF2\u5907\u4EFD\u5F53\u524D\u7248\u672C\u5230 ${path35.basename(backupPath)}`);
19068
19603
  replaceExecutable(opts.execPath, opts.stagedPath);
19069
19604
  return patchState(opts.dataDir, {
19070
19605
  stagedVersion: void 0,
@@ -19083,18 +19618,18 @@ function rollback(dataDir, execPath, log = () => {
19083
19618
  }) {
19084
19619
  const state = readRollbackTarget(dataDir);
19085
19620
  if (!state?.backupPath || !state.backupVersion) return null;
19086
- if (!fs27.existsSync(state.backupPath)) return null;
19621
+ if (!fs28.existsSync(state.backupPath)) return null;
19087
19622
  log(`\u6B63\u5728\u56DE\u6EDA\u5230 v${state.backupVersion}`);
19088
19623
  if (process.platform === "win32") {
19089
- fs27.rmSync(`${execPath}.old`, { force: true });
19090
- fs27.renameSync(execPath, `${execPath}.old`);
19091
- fs27.copyFileSync(state.backupPath, execPath);
19092
- fs27.chmodSync(execPath, 493);
19624
+ fs28.rmSync(`${execPath}.old`, { force: true });
19625
+ fs28.renameSync(execPath, `${execPath}.old`);
19626
+ fs28.copyFileSync(state.backupPath, execPath);
19627
+ fs28.chmodSync(execPath, 493);
19093
19628
  } else {
19094
19629
  const tmp = `${execPath}.rollback`;
19095
- fs27.copyFileSync(state.backupPath, tmp);
19096
- fs27.chmodSync(tmp, 493);
19097
- fs27.renameSync(tmp, execPath);
19630
+ fs28.copyFileSync(state.backupPath, tmp);
19631
+ fs28.chmodSync(tmp, 493);
19632
+ fs28.renameSync(tmp, execPath);
19098
19633
  }
19099
19634
  patchState(dataDir, {
19100
19635
  pendingVersion: state.backupVersion,
@@ -19108,7 +19643,7 @@ function rollback(dataDir, execPath, log = () => {
19108
19643
  }
19109
19644
  function readRollbackTarget(dataDir) {
19110
19645
  try {
19111
- const raw = fs27.readFileSync(path33.join(updatesDir(dataDir), "state.json"), "utf-8");
19646
+ const raw = fs28.readFileSync(path35.join(updatesDir(dataDir), "state.json"), "utf-8");
19112
19647
  const s = JSON.parse(raw);
19113
19648
  if (s.backupPath && s.backupVersion) return s;
19114
19649
  } catch {
@@ -19191,7 +19726,7 @@ var UpdateController = class {
19191
19726
  reconcile() {
19192
19727
  const state = loadState(this.opts.dataDir);
19193
19728
  const current = this.currentVersion;
19194
- if (state.stagedPath && !fs28.existsSync(state.stagedPath)) {
19729
+ if (state.stagedPath && !fs29.existsSync(state.stagedPath)) {
19195
19730
  patchState(this.opts.dataDir, { stagedVersion: void 0, stagedPath: void 0 });
19196
19731
  }
19197
19732
  const patch = { runningVersion: current };
@@ -19206,7 +19741,7 @@ var UpdateController = class {
19206
19741
  }
19207
19742
  patchState(this.opts.dataDir, patch);
19208
19743
  if (process.platform === "win32") {
19209
- fs28.rmSync(`${process.execPath}.old`, { force: true });
19744
+ fs29.rmSync(`${process.execPath}.old`, { force: true });
19210
19745
  }
19211
19746
  }
19212
19747
  schedule(delayMs) {
@@ -19282,8 +19817,8 @@ var UpdateController = class {
19282
19817
  const bin = manifest.binaries[platformKey()];
19283
19818
  if (!bin) return `\u6E05\u5355\u6CA1\u6709\u5F53\u524D\u5E73\u53F0\uFF08${platformKey()}\uFF09\u7684\u4EA7\u7269`;
19284
19819
  let stagedPath = state.stagedPath;
19285
- if (!(state.stagedVersion === manifest.version && stagedPath && fs28.existsSync(stagedPath))) {
19286
- stagedPath = path34.join(
19820
+ if (!(state.stagedVersion === manifest.version && stagedPath && fs29.existsSync(stagedPath))) {
19821
+ stagedPath = path36.join(
19287
19822
  updatesDir(this.opts.dataDir),
19288
19823
  "staged",
19289
19824
  `zqagent-${manifest.version}${process.platform === "win32" ? ".exe" : ""}`
@@ -19295,7 +19830,7 @@ var UpdateController = class {
19295
19830
  }
19296
19831
  const selfTest = selfCheck(stagedPath, manifest.version);
19297
19832
  if (!selfTest.ok) {
19298
- fs28.rmSync(stagedPath, { force: true });
19833
+ fs29.rmSync(stagedPath, { force: true });
19299
19834
  this.recordFailure(manifest.version, selfTest.error ?? "\u81EA\u68C0\u5931\u8D25");
19300
19835
  return `\u5B89\u88C5\u5305\u81EA\u68C0\u5931\u8D25\uFF1A${selfTest.error}`;
19301
19836
  }
@@ -19436,7 +19971,7 @@ function pemFile(key) {
19436
19971
  const p = env2(key);
19437
19972
  if (!p) return void 0;
19438
19973
  try {
19439
- return fs29.readFileSync(p);
19974
+ return fs30.readFileSync(p);
19440
19975
  } catch (e) {
19441
19976
  console.warn(`[tls] \u8BFB\u53D6 ${key}=${p} \u5931\u8D25: ${e instanceof Error ? e.message : e}`);
19442
19977
  return void 0;
@@ -19458,7 +19993,7 @@ function seedDemoTenant(store) {
19458
19993
  apiKey: env2("OPENAI_API_KEY"),
19459
19994
  baseURL: env2("OPENAI_BASE_URL")
19460
19995
  },
19461
- workspaceRoot: workspace ? path35.resolve(workspace) : void 0,
19996
+ workspaceRoot: workspace ? path37.resolve(workspace) : void 0,
19462
19997
  allowedCommands: []
19463
19998
  };
19464
19999
  store.saveTenant(demo);
@@ -19512,9 +20047,9 @@ function syncTenantModels(store) {
19512
20047
  tenant.model = model;
19513
20048
  changed = true;
19514
20049
  }
19515
- if (workspace && tenant.workspaceRoot !== path35.resolve(workspace)) {
19516
- console.log(`[sync] \u79DF\u6237 ${id} workspaceRoot \u5DF2\u540C\u6B65: ${tenant.workspaceRoot ?? "(\u6C99\u7BB1)"} \u2192 ${path35.resolve(workspace)}`);
19517
- tenant.workspaceRoot = path35.resolve(workspace);
20050
+ if (workspace && tenant.workspaceRoot !== path37.resolve(workspace)) {
20051
+ console.log(`[sync] \u79DF\u6237 ${id} workspaceRoot \u5DF2\u540C\u6B65: ${tenant.workspaceRoot ?? "(\u6C99\u7BB1)"} \u2192 ${path37.resolve(workspace)}`);
20052
+ tenant.workspaceRoot = path37.resolve(workspace);
19518
20053
  changed = true;
19519
20054
  }
19520
20055
  if (apiKey && tenant.provider.apiKey !== apiKey) {
@@ -19546,7 +20081,7 @@ function exitWhenOrphaned() {
19546
20081
  timer.unref();
19547
20082
  }
19548
20083
  function startServer(opts = {}) {
19549
- const dataDir = path35.resolve(opts.dataDir ?? env2("DATA_DIR") ?? DEFAULT_DATA_DIR);
20084
+ const dataDir = path37.resolve(opts.dataDir ?? env2("DATA_DIR") ?? DEFAULT_DATA_DIR);
19550
20085
  const port = opts.port ?? Number(env2("PORT") ?? 8787);
19551
20086
  const host = opts.host ?? env2("HOST") ?? "127.0.0.1";
19552
20087
  exitWhenOrphaned();
@@ -19576,7 +20111,7 @@ function startServer(opts = {}) {
19576
20111
  }
19577
20112
  const adminKey = ensureAdminKey();
19578
20113
  const secretKey = env2("SECRET_KEY");
19579
- const secretBox = secretKey ? SecretBox.fromHex(secretKey) : SecretBox.fromFile(path35.join(dataDir, "secret.key"));
20114
+ const secretBox = secretKey ? SecretBox.fromHex(secretKey) : SecretBox.fromFile(path37.join(dataDir, "secret.key"));
19580
20115
  if (secretKey && !secretBox) {
19581
20116
  console.warn("[crypto] SECRET_KEY \u975E\u6CD5\uFF08\u9700 64 \u4F4D hex\uFF09\uFF0C\u901A\u77E5\u51ED\u636E\u5C06\u9000\u5316\u4E3A\u660E\u6587");
19582
20117
  }
@@ -19805,7 +20340,7 @@ function printReady(info, qq) {
19805
20340
  `zqagent server \u76D1\u542C http://${info.host}:${info.port}`,
19806
20341
  `\u6570\u636E\u76EE\u5F55: ${info.dataDir}`,
19807
20342
  `\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`,
19808
- env2("ZQAGENT_LOG_ADMIN_KEY") === "0" ? `\u7BA1\u7406\u5458\u5BC6\u94A5: ${maskKey(info.adminKey)}\uFF08\u5DF2\u8131\u654F\uFF0C\u5B8C\u6574\u503C\u89C1 ${path35.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`
20343
+ env2("ZQAGENT_LOG_ADMIN_KEY") === "0" ? `\u7BA1\u7406\u5458\u5BC6\u94A5: ${maskKey(info.adminKey)}\uFF08\u5DF2\u8131\u654F\uFF0C\u5B8C\u6574\u503C\u89C1 ${path37.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`
19809
20344
  ];
19810
20345
  if (info.fleetNodeId) {
19811
20346
  lines.push(
@@ -19826,7 +20361,7 @@ function printReady(info, qq) {
19826
20361
 
19827
20362
  // src/cli/account-enroll.ts
19828
20363
  init_cjs_shims();
19829
- var path36 = __toESM(require("path"), 1);
20364
+ var path38 = __toESM(require("path"), 1);
19830
20365
  var readline = __toESM(require("readline/promises"), 1);
19831
20366
  var SKIP_ANSWERS = /* @__PURE__ */ new Set(["-", "skip", "none", "no"]);
19832
20367
  function resolveDefaultHub(hubUrl, env3 = process.env) {
@@ -19838,7 +20373,7 @@ function parseHubAnswer(answer, fallback) {
19838
20373
  return (a || fallback).replace(/\/$/, "");
19839
20374
  }
19840
20375
  function resolveDataDir2(dataDir) {
19841
- return path36.resolve(dataDir ?? process.env.DATA_DIR ?? DEFAULT_DATA_DIR);
20376
+ return path38.resolve(dataDir ?? process.env.DATA_DIR ?? DEFAULT_DATA_DIR);
19842
20377
  }
19843
20378
  async function maybeAccountEnroll(opts = {}) {
19844
20379
  if (!process.stdin.isTTY || !process.stdout.isTTY) return;
@@ -20044,7 +20579,7 @@ async function runServeCommand(argv) {
20044
20579
 
20045
20580
  // src/cli/update.ts
20046
20581
  init_cjs_shims();
20047
- var path37 = __toESM(require("path"), 1);
20582
+ var path39 = __toESM(require("path"), 1);
20048
20583
  function parse3(argv) {
20049
20584
  const out = { status: false, check: false, now: false, rollback: false };
20050
20585
  for (let i = 0; i < argv.length; i++) {
@@ -20073,7 +20608,7 @@ function restartHint() {
20073
20608
  }
20074
20609
  async function runUpdateCommand(argv) {
20075
20610
  const args = parse3(argv);
20076
- const dataDir = path37.resolve(args.dataDir ?? process.env.DATA_DIR ?? DEFAULT_DATA_DIR);
20611
+ const dataDir = path39.resolve(args.dataDir ?? process.env.DATA_DIR ?? DEFAULT_DATA_DIR);
20077
20612
  const controller = new UpdateController({ dataDir, isIdle: () => true });
20078
20613
  if (args.rollback) {
20079
20614
  const restored = controller.rollbackNow();
@@ -20121,17 +20656,17 @@ async function runUpdateCommand(argv) {
20121
20656
 
20122
20657
  // src/cli/sessionMeta.ts
20123
20658
  init_cjs_shims();
20124
- var fs30 = __toESM(require("fs"), 1);
20125
- var path38 = __toESM(require("path"), 1);
20659
+ var fs31 = __toESM(require("fs"), 1);
20660
+ var path40 = __toESM(require("path"), 1);
20126
20661
  function updateSessionMetaFile(stateDir2, sessionId, patch) {
20127
- const file = path38.join(stateDir2, `${sessionId}_state.json`);
20662
+ const file = path40.join(stateDir2, `${sessionId}_state.json`);
20128
20663
  try {
20129
- const raw = fs30.readFileSync(file, "utf8");
20664
+ const raw = fs31.readFileSync(file, "utf8");
20130
20665
  const state = JSON.parse(raw);
20131
20666
  if (patch.name !== void 0) state.name = patch.name;
20132
20667
  if (patch.nameSource !== void 0) state.nameSource = patch.nameSource;
20133
20668
  if (patch.taskCount !== void 0) state.taskCount = patch.taskCount;
20134
- fs30.writeFileSync(file, JSON.stringify(state, null, 2), "utf8");
20669
+ fs31.writeFileSync(file, JSON.stringify(state, null, 2), "utf8");
20135
20670
  return true;
20136
20671
  } catch {
20137
20672
  return false;
@@ -20140,9 +20675,9 @@ function updateSessionMetaFile(stateDir2, sessionId, patch) {
20140
20675
 
20141
20676
  // src/cli/debug.ts
20142
20677
  init_cjs_shims();
20143
- var fs31 = __toESM(require("fs"), 1);
20678
+ var fs32 = __toESM(require("fs"), 1);
20144
20679
  var os8 = __toESM(require("os"), 1);
20145
- var path39 = __toESM(require("path"), 1);
20680
+ var path41 = __toESM(require("path"), 1);
20146
20681
  var LOG_NAME = "zqagent-debug.log";
20147
20682
  var TRUTHY = /^(1|true|yes|on)$/i;
20148
20683
  function bootRef() {
@@ -20153,9 +20688,9 @@ var logFile = null;
20153
20688
  var startedAt = Date.now();
20154
20689
  function resolveLogFile() {
20155
20690
  const explicit = process.env.ZQAGENT_DEBUG_LOG;
20156
- if (explicit) return path39.resolve(explicit);
20691
+ if (explicit) return path41.resolve(explicit);
20157
20692
  const dir = process.env.ZQAGENT_DEBUG_DIR;
20158
- return path39.join(dir ? path39.resolve(dir) : os8.tmpdir(), LOG_NAME);
20693
+ return path41.join(dir ? path41.resolve(dir) : os8.tmpdir(), LOG_NAME);
20159
20694
  }
20160
20695
  function enableDebug(argv = process.argv) {
20161
20696
  if (enabled) return true;
@@ -20171,8 +20706,8 @@ function enableDebug(argv = process.argv) {
20171
20706
  enabled = true;
20172
20707
  logFile = resolveLogFile();
20173
20708
  try {
20174
- fs31.mkdirSync(path39.dirname(logFile), { recursive: true });
20175
- fs31.writeFileSync(logFile, "");
20709
+ fs32.mkdirSync(path41.dirname(logFile), { recursive: true });
20710
+ fs32.writeFileSync(logFile, "");
20176
20711
  } catch {
20177
20712
  logFile = null;
20178
20713
  }
@@ -20189,7 +20724,7 @@ function dlog(stage, message = "") {
20189
20724
  const line = `[+${String(Date.now() - startedAt).padStart(6)}ms] [${stage}] ${message}`;
20190
20725
  if (logFile) {
20191
20726
  try {
20192
- fs31.appendFileSync(logFile, line + "\n");
20727
+ fs32.appendFileSync(logFile, line + "\n");
20193
20728
  } catch {
20194
20729
  }
20195
20730
  }
@@ -20349,18 +20884,18 @@ if (enableDebug(process.argv)) {
20349
20884
  logEnvironment();
20350
20885
  }
20351
20886
  var MODEL = process.env.OPENAI_MODEL ?? "gpt-4o";
20352
- var WORKSPACE = path40.resolve(process.env.WORKSPACE ?? ".");
20887
+ var WORKSPACE = path42.resolve(process.env.WORKSPACE ?? ".");
20353
20888
  var MAX_ITERATIONS = parseMaxIterations(process.env.MAX_ITERATIONS, 300);
20354
20889
  var STATE_DIR = stateDir();
20355
20890
  function listSessions() {
20356
20891
  try {
20357
- const files = fs32.readdirSync(STATE_DIR);
20892
+ const files = fs33.readdirSync(STATE_DIR);
20358
20893
  const sessions = [];
20359
20894
  for (const f of files) {
20360
20895
  if (!f.endsWith("_state.json")) continue;
20361
20896
  try {
20362
20897
  const parsed = JSON.parse(
20363
- fs32.readFileSync(path40.join(STATE_DIR, f), "utf8")
20898
+ fs33.readFileSync(path42.join(STATE_DIR, f), "utf8")
20364
20899
  );
20365
20900
  if (parsed && typeof parsed.sessionId === "string") {
20366
20901
  sessions.push(parsed);
@@ -20394,11 +20929,11 @@ function printLastAssistantMessage(messages) {
20394
20929
  out += renderer.flush();
20395
20930
  cliWrite(out + "\n");
20396
20931
  }
20397
- var AGENTS_FILE = path40.join(WORKSPACE, ".agent", "agents.json");
20932
+ var AGENTS_FILE = path42.join(WORKSPACE, ".agent", "agents.json");
20398
20933
  function loadAgentsFromFile() {
20399
20934
  try {
20400
- if (!fs32.existsSync(AGENTS_FILE)) return [];
20401
- const raw = fs32.readFileSync(AGENTS_FILE, "utf8");
20935
+ if (!fs33.existsSync(AGENTS_FILE)) return [];
20936
+ const raw = fs33.readFileSync(AGENTS_FILE, "utf8");
20402
20937
  const parsed = JSON.parse(raw);
20403
20938
  if (!Array.isArray(parsed)) return [];
20404
20939
  return parsed.filter(
@@ -20410,8 +20945,8 @@ function loadAgentsFromFile() {
20410
20945
  }
20411
20946
  function saveAgentsToFile(defs) {
20412
20947
  try {
20413
- fs32.mkdirSync(path40.dirname(AGENTS_FILE), { recursive: true });
20414
- fs32.writeFileSync(AGENTS_FILE, JSON.stringify(defs, null, 2) + "\n", "utf8");
20948
+ fs33.mkdirSync(path42.dirname(AGENTS_FILE), { recursive: true });
20949
+ fs33.writeFileSync(AGENTS_FILE, JSON.stringify(defs, null, 2) + "\n", "utf8");
20415
20950
  } catch (err) {
20416
20951
  cliErr(`\u26A0\uFE0F \u4FDD\u5B58 agents \u914D\u7F6E\u5931\u8D25: ${err}`);
20417
20952
  }
@@ -20420,12 +20955,12 @@ var agentRegistry = new AgentRegistry([
20420
20955
  ...BUILTIN_AGENTS,
20421
20956
  ...loadAgentsFromFile()
20422
20957
  ]);
20423
- var MCP_FILE = path40.join(WORKSPACE, ".agent", "mcp.json");
20424
- var COMMAND_TOOLS_FILE = path40.join(WORKSPACE, ".agent", "tools.json");
20958
+ var MCP_FILE = path42.join(WORKSPACE, ".agent", "mcp.json");
20959
+ var COMMAND_TOOLS_FILE = path42.join(WORKSPACE, ".agent", "tools.json");
20425
20960
  function loadJsonArray(file, validate) {
20426
20961
  try {
20427
- if (!fs32.existsSync(file)) return [];
20428
- const parsed = JSON.parse(fs32.readFileSync(file, "utf8"));
20962
+ if (!fs33.existsSync(file)) return [];
20963
+ const parsed = JSON.parse(fs33.readFileSync(file, "utf8"));
20429
20964
  if (!Array.isArray(parsed)) return [];
20430
20965
  if (!validate) return parsed;
20431
20966
  return parsed.filter((v) => validate(v));
@@ -20435,8 +20970,8 @@ function loadJsonArray(file, validate) {
20435
20970
  }
20436
20971
  function saveJsonFile(file, data) {
20437
20972
  try {
20438
- fs32.mkdirSync(path40.dirname(file), { recursive: true });
20439
- fs32.writeFileSync(file, JSON.stringify(data, null, 2) + "\n", "utf8");
20973
+ fs33.mkdirSync(path42.dirname(file), { recursive: true });
20974
+ fs33.writeFileSync(file, JSON.stringify(data, null, 2) + "\n", "utf8");
20440
20975
  } catch (err) {
20441
20976
  cliErr(`\u26A0\uFE0F \u4FDD\u5B58\u914D\u7F6E\u5931\u8D25: ${err}`);
20442
20977
  }
@@ -20537,7 +21072,7 @@ function pruneEmptySessions(keepSessionId) {
20537
21072
  if (!isEmptySession(s) || s.sessionId === keepSessionId) continue;
20538
21073
  for (const suffix of ["_state.json", ".jsonl"]) {
20539
21074
  try {
20540
- fs32.unlinkSync(path40.join(STATE_DIR, `${s.sessionId}${suffix}`));
21075
+ fs33.unlinkSync(path42.join(STATE_DIR, `${s.sessionId}${suffix}`));
20541
21076
  } catch {
20542
21077
  }
20543
21078
  }
@@ -20727,6 +21262,7 @@ function buildEngine(sessionId, savedState) {
20727
21262
  // 引擎内置表(lookupContextWindow 兜底,见 TurnEngine._contextWindow)
20728
21263
  contextWindow: parseOptionalPositiveNumber(process.env.CONTEXT_WINDOW) ?? modelContextWindow(conn?.modelMeta, model),
20729
21264
  thresholdPct: parseRatio(process.env.COMPACTION_THRESHOLD_PCT),
21265
+ softThresholdPct: parseRatio(process.env.COMPACTION_SOFT_THRESHOLD_PCT),
20730
21266
  capTokens: parseOptionalPositiveNumber(process.env.COMPACTION_CAP_TOKENS) ?? null,
20731
21267
  // 系统提示词按当前 agent 组装(agent prompt + 叙述规则 + 环境信息)
20732
21268
  instructions: agentRegistry.buildSystemPrompt(
@@ -21069,7 +21605,7 @@ async function offerTerminalSetup(engine, canUseAgent) {
21069
21605
  }
21070
21606
  var KNOWN_SUBCOMMANDS = /* @__PURE__ */ new Set(["link", "serve", "update"]);
21071
21607
  function cliVersion() {
21072
- const env3 = "0.2.4"?.trim();
21608
+ const env3 = "0.2.6"?.trim();
21073
21609
  if (env3) return env3;
21074
21610
  try {
21075
21611
  const req2 = (0, import_module.createRequire)(importMetaUrl);
@@ -21273,8 +21809,8 @@ async function main() {
21273
21809
  }
21274
21810
  });
21275
21811
  const savedState = resumeId ? FileLogHook.loadState(resumeId, STATE_DIR) : null;
21276
- const metaStateFile = path40.join(STATE_DIR, `${SESSION_ID}_state.json`);
21277
- const stateForMeta = savedState ?? (fs32.existsSync(metaStateFile) ? FileLogHook.loadState(SESSION_ID, STATE_DIR) : null);
21812
+ const metaStateFile = path42.join(STATE_DIR, `${SESSION_ID}_state.json`);
21813
+ const stateForMeta = savedState ?? (fs33.existsSync(metaStateFile) ? FileLogHook.loadState(SESSION_ID, STATE_DIR) : null);
21278
21814
  sessionMeta = {
21279
21815
  sessionId: SESSION_ID,
21280
21816
  name: stateForMeta?.name,