@zqagent/core 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -552,9 +552,9 @@ var init_wire = __esm({
552
552
  // src/cli/index.ts
553
553
  init_esm_shims();
554
554
  import "dotenv/config";
555
- import * as fs32 from "fs";
555
+ import * as fs34 from "fs";
556
556
  import * as readline2 from "readline";
557
- import * as path41 from "path";
557
+ import * as path43 from "path";
558
558
  import { exec } from "child_process";
559
559
  import { promisify } from "util";
560
560
 
@@ -920,8 +920,11 @@ function formatTokens(tokens) {
920
920
  return `${tokens}`;
921
921
  }
922
922
  var KEEP_RECENT_FRACTION = 0.25;
923
- var SPAN_TOOL_RESULT_CLIP = 400;
924
- var SPAN_BUDGET_CHARS = 4e5;
923
+ var SOFT_THRESHOLD_RATIO = 0.75;
924
+ var ELIDE_KEEP_FRACTION = 0.15;
925
+ var SUMMARY_MAX_TOKENS = 3e3;
926
+ var SPAN_TOOL_RESULT_CLIP = 200;
927
+ var SPAN_BUDGET_CHARS = 8e4;
925
928
  var USER_MESSAGE_CLIP = 600;
926
929
  var USER_MESSAGES_MAX = 40;
927
930
  var TRIM_FRACTION = 0.1;
@@ -944,9 +947,22 @@ function triggerTokens(contextWindow, thresholdPct = DEFAULT_THRESHOLD_PCT, capT
944
947
  }
945
948
  return base;
946
949
  }
950
+ function softTriggerTokens(contextWindow, thresholdPct = DEFAULT_THRESHOLD_PCT, capTokens, softThresholdPct) {
951
+ const hard = triggerTokens(contextWindow, thresholdPct, capTokens);
952
+ if (softThresholdPct != null && Number.isFinite(softThresholdPct) && softThresholdPct > 0) {
953
+ return Math.min(
954
+ triggerTokens(contextWindow, softThresholdPct, capTokens),
955
+ Math.floor(hard * 0.95)
956
+ );
957
+ }
958
+ return Math.floor(hard * SOFT_THRESHOLD_RATIO);
959
+ }
947
960
  function shouldCompact(signal, contextWindow, thresholdPct, capTokens) {
948
961
  return signal >= triggerTokens(contextWindow, thresholdPct, capTokens);
949
962
  }
963
+ function elisionKeepChars(hardTriggerTokens, fraction = ELIDE_KEEP_FRACTION) {
964
+ return Math.max(4e3, Math.floor(hardTriggerTokens * fraction * 4));
965
+ }
950
966
  function turnStarts(messages, start) {
951
967
  const users = [];
952
968
  const assistants = [];
@@ -1096,10 +1112,11 @@ var SUMMARY_SYSTEM_PROMPT = `\u4F60\u6B63\u5728\u538B\u7F29\u4E00\u4E2A AI \u534
1096
1112
 
1097
1113
  \u89C4\u5219\uFF1A
1098
1114
  - \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
1115
+ - \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
1099
1116
  - \u8981\u5177\u4F53\uFF1A\u8DEF\u5F84\u3001\u540D\u79F0\u3001\u547D\u4EE4\u3001id\u2014\u2014\u800C\u4E0D\u662F\u6A21\u7CCA\u7684\u5F15\u7528\u3002
1100
1117
  - \u4EC5\u8F93\u51FA\u6458\u8981\u7AE0\u8282\uFF0C\u4E0D\u9644\u52A0\u524D\u8A00\u3002`;
1101
1118
  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";
1102
- function renderSpan(span, budgetChars = SPAN_BUDGET_CHARS) {
1119
+ function renderSpan(span, budgetChars2 = SPAN_BUDGET_CHARS) {
1103
1120
  const lines = [];
1104
1121
  for (const m of span) {
1105
1122
  const role = m.role;
@@ -1123,8 +1140,8 @@ function renderSpan(span, budgetChars = SPAN_BUDGET_CHARS) {
1123
1140
  if (text) lines.push(`[${role}] ${text}`);
1124
1141
  }
1125
1142
  let rendered = lines.join("\n");
1126
- if (rendered.length > budgetChars) {
1127
- rendered = "(\u2026oldest turns elided\u2026)\n" + rendered.slice(-budgetChars);
1143
+ if (rendered.length > budgetChars2) {
1144
+ rendered = "(\u2026oldest turns elided\u2026)\n" + rendered.slice(-budgetChars2);
1128
1145
  }
1129
1146
  return rendered;
1130
1147
  }
@@ -1138,9 +1155,17 @@ function summarizerMessages(span, priorSummary = "") {
1138
1155
  { role: "user", content: body }
1139
1156
  ];
1140
1157
  }
1158
+ function summarizerReasoningEffort() {
1159
+ const v = process.env.COMPACTION_REASONING_EFFORT?.toLowerCase();
1160
+ if (v === "low" || v === "medium" || v === "high") return v;
1161
+ return "low";
1162
+ }
1141
1163
  async function summarizeSpan(provider, model, span, priorSummary = "", opts) {
1142
1164
  const msgs = summarizerMessages(span, priorSummary);
1143
- const chunks = await provider.stream(model, msgs, [], opts);
1165
+ const chunks = await provider.stream(model, msgs, [], {
1166
+ ...opts,
1167
+ maxTokens: SUMMARY_MAX_TOKENS
1168
+ });
1144
1169
  let text = "";
1145
1170
  for await (const chunk of chunks) {
1146
1171
  if (chunk.content) text += chunk.content;
@@ -1251,6 +1276,166 @@ function applyToOutbound(messages, state) {
1251
1276
  head.push({ role: "user", content: compactedBlock(state) });
1252
1277
  return [...head, ...messages.slice(boundary)];
1253
1278
  }
1279
+ var ELIDE_EXEMPT_TOOLS = /* @__PURE__ */ new Set([
1280
+ "explore",
1281
+ "ask_user",
1282
+ "propose_plan",
1283
+ "request_directory",
1284
+ "todo_write"
1285
+ ]);
1286
+ function elidedToolContent(m) {
1287
+ const name = m.tool_call_name || "tool";
1288
+ const size = m.content.length;
1289
+ const saved = /full output saved to ([^\]]+)/.exec(m.content)?.[1]?.trim();
1290
+ 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]`;
1291
+ }
1292
+ function isPlaceholder(content) {
1293
+ return content.startsWith("[old tool result elided") || content.startsWith("[superseded by a later read_file");
1294
+ }
1295
+ var ONE_SHOT_TOOLS = /* @__PURE__ */ new Set(["git_diff"]);
1296
+ var ONE_SHOT_KEEP_RECENT = 1;
1297
+ function applyOneShotElision(messages, keepRecent = ONE_SHOT_KEEP_RECENT, tools = ONE_SHOT_TOOLS) {
1298
+ const out = [...messages];
1299
+ let seen = 0;
1300
+ let elided = 0;
1301
+ let freed = 0;
1302
+ for (let i = messages.length - 1; i >= 0; i--) {
1303
+ const m = messages[i];
1304
+ if (m.role !== "tool" || !tools.has(m.tool_call_name ?? "")) continue;
1305
+ if (isPlaceholder(m.content)) continue;
1306
+ seen++;
1307
+ if (seen <= keepRecent) continue;
1308
+ const placeholder = elidedToolContent(m);
1309
+ freed += m.content.length - placeholder.length;
1310
+ out[i] = { ...m, content: placeholder };
1311
+ elided++;
1312
+ }
1313
+ return elided > 0 ? { messages: out, elided, freedChars: freed } : { messages, elided: 0, freedChars: 0 };
1314
+ }
1315
+ function applyToolResultElision(messages, keepChars, exempt = ELIDE_EXEMPT_TOOLS) {
1316
+ let total = 0;
1317
+ for (const m of messages) {
1318
+ if (m.role === "tool" && !exempt.has(m.tool_call_name ?? "")) {
1319
+ total += m.content.length;
1320
+ }
1321
+ }
1322
+ if (total <= keepChars) return { messages, elided: 0, freedChars: 0 };
1323
+ const out = [...messages];
1324
+ let budget = 0;
1325
+ let elided = 0;
1326
+ let freed = 0;
1327
+ for (let i = messages.length - 1; i >= 0; i--) {
1328
+ const m = messages[i];
1329
+ if (m.role !== "tool") continue;
1330
+ if (exempt.has(m.tool_call_name ?? "")) continue;
1331
+ if (elided === 0 && budget === 0 && m.content.length > keepChars) {
1332
+ const kept = m.content.slice(0, keepChars) + "\n\u2026(truncated)";
1333
+ freed += m.content.length - kept.length;
1334
+ out[i] = { ...m, content: kept };
1335
+ budget = keepChars;
1336
+ continue;
1337
+ }
1338
+ if (budget + m.content.length <= keepChars) {
1339
+ budget += m.content.length;
1340
+ continue;
1341
+ }
1342
+ const placeholder = elidedToolContent(m);
1343
+ freed += m.content.length - placeholder.length;
1344
+ out[i] = { ...m, content: placeholder };
1345
+ elided++;
1346
+ }
1347
+ return { messages: out, elided, freedChars: freed };
1348
+ }
1349
+ function normalizeToolPath(p) {
1350
+ let s = p.trim().replace(/\\/g, "/");
1351
+ while (s.startsWith("./")) s = s.slice(2);
1352
+ return s.replace(/\/{2,}/g, "/");
1353
+ }
1354
+ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
1355
+ "write_file",
1356
+ "replace_in_file",
1357
+ "apply_patch",
1358
+ "apply_unified_diff",
1359
+ "run_shell"
1360
+ ]);
1361
+ function parseReadFileSpan(content) {
1362
+ const body = content.replace(/^\[tool output truncated:[^\n]*\]\n\n/, "");
1363
+ let path44;
1364
+ let start;
1365
+ let end;
1366
+ try {
1367
+ const obj = JSON.parse(body);
1368
+ path44 = obj.path;
1369
+ start = obj.start_line;
1370
+ end = obj.end_line;
1371
+ } catch {
1372
+ const str2 = (key) => new RegExp(`"${key}"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)"`).exec(body)?.[1];
1373
+ const num2 = (key) => {
1374
+ const m = new RegExp(`"${key}"\\s*:\\s*(\\d+)`).exec(body);
1375
+ return m ? Number(m[1]) : void 0;
1376
+ };
1377
+ path44 = str2("path");
1378
+ start = num2("start_line");
1379
+ end = num2("end_line");
1380
+ }
1381
+ if (typeof path44 !== "string" || !path44) return null;
1382
+ if (typeof start !== "number" || typeof end !== "number") return null;
1383
+ if (!Number.isFinite(start) || !Number.isFinite(end)) return null;
1384
+ return { path: path44, start, end };
1385
+ }
1386
+ function addSpan(spans, start, end) {
1387
+ spans.push([start, end]);
1388
+ spans.sort((a, b) => a[0] - b[0]);
1389
+ const merged = [];
1390
+ for (const cur of spans) {
1391
+ const last = merged[merged.length - 1];
1392
+ if (last && cur[0] <= last[1] + 1) {
1393
+ last[1] = Math.max(last[1], cur[1]);
1394
+ } else {
1395
+ merged.push([...cur]);
1396
+ }
1397
+ }
1398
+ spans.length = 0;
1399
+ spans.push(...merged);
1400
+ }
1401
+ function coversSpans(spans, start, end) {
1402
+ return spans.some(([a, b]) => a <= start && b >= end);
1403
+ }
1404
+ function applyReadFileDedup(messages) {
1405
+ let candidates = 0;
1406
+ for (const m of messages) {
1407
+ if (m.role === "tool" && m.tool_call_name === "read_file") candidates++;
1408
+ }
1409
+ if (candidates < 2) return { messages, elided: 0 };
1410
+ const out = [...messages];
1411
+ const covered = /* @__PURE__ */ new Map();
1412
+ let elided = 0;
1413
+ for (let i = messages.length - 1; i >= 0; i--) {
1414
+ const m = messages[i];
1415
+ if (m.role === "assistant") {
1416
+ if (m.tool_calls?.some((tc) => MUTATING_TOOLS.has(tc.function.name))) {
1417
+ covered.clear();
1418
+ }
1419
+ continue;
1420
+ }
1421
+ if (m.role !== "tool" || m.tool_call_name !== "read_file") continue;
1422
+ const span = parseReadFileSpan(m.content);
1423
+ if (!span) continue;
1424
+ const key = normalizeToolPath(span.path);
1425
+ const seen = covered.get(key) ?? [];
1426
+ if (coversSpans(seen, span.start, span.end)) {
1427
+ out[i] = {
1428
+ ...m,
1429
+ content: `[superseded by a later read_file of ${span.path} (lines ${span.start}-${span.end})]`
1430
+ };
1431
+ elided++;
1432
+ continue;
1433
+ }
1434
+ addSpan(seen, span.start, span.end);
1435
+ covered.set(key, seen);
1436
+ }
1437
+ return { messages: elided > 0 ? out : messages, elided };
1438
+ }
1254
1439
  var OVERFLOW_MARKERS = [
1255
1440
  "context_length_exceeded",
1256
1441
  "maximum context length",
@@ -1266,9 +1451,116 @@ function isContextOverflow(exc) {
1266
1451
  return OVERFLOW_MARKERS.some((m) => text.includes(m));
1267
1452
  }
1268
1453
 
1454
+ // src/engine/toolResultBudget.ts
1455
+ init_esm_shims();
1456
+ import * as fs from "fs";
1457
+ import * as path3 from "path";
1458
+
1459
+ // src/paths.ts
1460
+ init_esm_shims();
1461
+ import * as os from "os";
1462
+ import * as path2 from "path";
1463
+ var APP_DIR_NAME = "zqagent";
1464
+ var DEFAULT_DATA_DIR = ".zqagent_data";
1465
+ var DEFAULT_HUB_URL = "https://hub.cd-zq.com";
1466
+ var IS_WINDOWS = process.platform === "win32";
1467
+ function homeDir() {
1468
+ return process.env.HOME || process.env.USERPROFILE || os.homedir() || os.tmpdir();
1469
+ }
1470
+ function configDir() {
1471
+ const base = process.env.XDG_CONFIG_HOME || (IS_WINDOWS ? process.env.APPDATA : void 0) || path2.join(homeDir(), ".config");
1472
+ return path2.join(base, APP_DIR_NAME);
1473
+ }
1474
+ function stateDir() {
1475
+ const base = process.env.XDG_STATE_HOME || (IS_WINDOWS ? process.env.LOCALAPPDATA : void 0) || path2.join(homeDir(), ".local", "state");
1476
+ return path2.join(base, APP_DIR_NAME);
1477
+ }
1478
+
1479
+ // src/engine/toolResultBudget.ts
1480
+ var DEFAULT_TOOL_RESULT_MAX_CHARS = 24e3;
1481
+ var HEAD_RATIO = 0.6;
1482
+ function budgetChars() {
1483
+ const n = Number(process.env.TOOL_RESULT_MAX_CHARS);
1484
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : DEFAULT_TOOL_RESULT_MAX_CHARS;
1485
+ }
1486
+ function toolResultDir() {
1487
+ const env3 = process.env.TOOL_RESULT_DIR?.trim();
1488
+ return env3 || path3.join(stateDir(), "tool-results");
1489
+ }
1490
+ function clipText(text, budget) {
1491
+ if (text.length <= budget) return text;
1492
+ const room = Math.max(
1493
+ 0,
1494
+ budget - `
1495
+
1496
+ \u2026 [${text.length} characters omitted] \u2026
1497
+
1498
+ `.length
1499
+ );
1500
+ const headLen = Math.floor(room * HEAD_RATIO);
1501
+ const tailLen = room - headLen;
1502
+ const omitted = text.length - headLen - tailLen;
1503
+ return text.slice(0, headLen) + `
1504
+
1505
+ \u2026 [${omitted} characters omitted] \u2026
1506
+
1507
+ ` + text.slice(-tailLen);
1508
+ }
1509
+ function clipDeep(value, perField) {
1510
+ if (typeof value === "string") {
1511
+ return value.length <= perField ? value : clipText(value, perField);
1512
+ }
1513
+ if (Array.isArray(value)) {
1514
+ return value.map((v) => clipDeep(v, perField));
1515
+ }
1516
+ if (value && typeof value === "object") {
1517
+ const out = {};
1518
+ for (const [k, v] of Object.entries(value)) out[k] = clipDeep(v, perField);
1519
+ return out;
1520
+ }
1521
+ return value;
1522
+ }
1523
+ function saveRaw(tool, raw, dir) {
1524
+ try {
1525
+ fs.mkdirSync(dir, { recursive: true });
1526
+ const safe2 = tool.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 40) || "tool";
1527
+ const name = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}-${safe2}.txt`;
1528
+ const file = path3.join(dir, name);
1529
+ fs.writeFileSync(file, raw, "utf-8");
1530
+ return file;
1531
+ } catch {
1532
+ return void 0;
1533
+ }
1534
+ }
1535
+ function applyToolResultBudget(tool, result, opts = {}) {
1536
+ const budget = opts.maxChars ?? budgetChars();
1537
+ const raw = typeof result === "string" ? result : JSON.stringify(result, null, 2) ?? String(result);
1538
+ if (raw.length <= budget) {
1539
+ return { content: raw, truncated: false };
1540
+ }
1541
+ let content = raw;
1542
+ if (typeof result === "object" && result !== null) {
1543
+ let perField = Math.floor(budget * 0.7);
1544
+ let clipped = JSON.stringify(clipDeep(result, perField), null, 2);
1545
+ for (let i = 0; i < 6 && clipped && clipped.length > budget && perField > 128; i++) {
1546
+ perField = Math.floor(perField / 2);
1547
+ clipped = JSON.stringify(clipDeep(result, perField), null, 2);
1548
+ }
1549
+ if (clipped && clipped.length <= budget) content = clipped;
1550
+ }
1551
+ if (content.length > budget) content = clipText(content, budget);
1552
+ const savedPath = saveRaw(tool, raw, opts.dir ?? toolResultDir());
1553
+ if (savedPath) {
1554
+ content = `[tool output truncated: ${raw.length} chars, full output saved to ${savedPath}]
1555
+
1556
+ ` + content;
1557
+ }
1558
+ return { content, truncated: true, savedPath };
1559
+ }
1560
+
1269
1561
  // src/engine/TurnEngine.ts
1270
1562
  import * as crypto from "crypto";
1271
- import * as fs from "fs";
1563
+ import * as fs2 from "fs";
1272
1564
  import * as _path from "path";
1273
1565
 
1274
1566
  // src/prompts/index.ts
@@ -1394,13 +1686,17 @@ var INTERACTIVE_TOOLS = /* @__PURE__ */ new Set([
1394
1686
  "propose_plan",
1395
1687
  "request_directory"
1396
1688
  ]);
1397
- function toolResultMessage(tc, result, taskId) {
1398
- const content = typeof result === "string" ? result : JSON.stringify(result, null, 2);
1689
+ var MAX_COMPACT_ROUNDS = 4;
1690
+ var OVERFLOW_TARGET_RATIO = 0.5;
1691
+ var OVERFLOW_TRIM_FRACTION = 0.5;
1692
+ var COMPACT_STALL_RETRY_DELTA = 20;
1693
+ function toolResultMessage(tc, result, taskId, toolResultDir2) {
1694
+ const budgeted = applyToolResultBudget(tc.name, result, { dir: toolResultDir2 });
1399
1695
  return {
1400
1696
  role: "tool",
1401
1697
  tool_call_id: tc.id,
1402
1698
  tool_call_name: tc.name,
1403
- content,
1699
+ content: budgeted.content,
1404
1700
  taskId
1405
1701
  };
1406
1702
  }
@@ -1511,12 +1807,22 @@ var TurnEngine = class _TurnEngine {
1511
1807
  // 上下文压缩
1512
1808
  compactionState = null;
1513
1809
  _lastContextTokens = null;
1810
+ /** 压缩无进展时的消息条数快照;-1 表示无冷却 */
1811
+ _compactStalledAt = -1;
1514
1812
  /** 显式配置的上下文窗口大小(token 数),未设置时为 undefined(走自动检测) */
1515
1813
  _contextWindow;
1516
1814
  /** 压缩触发阈值比例(默认 0.8) */
1517
1815
  _thresholdPct;
1816
+ /** 软阈值比例(undefined = 取硬阈值的 75%) */
1817
+ _softThresholdPct;
1818
+ /** 是否启用软阈值淘汰(COMPACTION_ELIDE=0 可关闭) */
1819
+ _elisionEnabled;
1820
+ /** 是否启用重复读取去重(COMPACTION_DEDUP=0 可关闭) */
1821
+ _dedupEnabled;
1518
1822
  /** 压缩触发阈值绝对上限(null = 不限制) */
1519
1823
  _capTokens;
1824
+ /** 超预算工具结果的落盘目录(undefined = 使用默认状态目录) */
1825
+ _toolResultDir;
1520
1826
  // 产出文件追踪(本轮 Turn 中成功写入的文件)
1521
1827
  _producedFiles = [];
1522
1828
  _turnStartTime = 0;
@@ -1568,7 +1874,15 @@ var TurnEngine = class _TurnEngine {
1568
1874
  this._hook = opts.hook;
1569
1875
  this._contextWindow = opts.contextWindow;
1570
1876
  this._thresholdPct = opts.thresholdPct ?? DEFAULT_THRESHOLD_PCT;
1877
+ this._softThresholdPct = opts.softThresholdPct;
1878
+ this._elisionEnabled = !/^(0|false|off|no)$/i.test(
1879
+ (process.env.COMPACTION_ELIDE ?? "").trim()
1880
+ );
1881
+ this._dedupEnabled = !/^(0|false|off|no)$/i.test(
1882
+ (process.env.COMPACTION_DEDUP ?? "").trim()
1883
+ );
1571
1884
  this._capTokens = opts.capTokens ?? null;
1885
+ this._toolResultDir = opts.toolResultDir;
1572
1886
  this._reasoningEffort = opts.reasoningEffort;
1573
1887
  if (this.messages.length === 0 || this.messages[0].role !== "system") {
1574
1888
  this.messages.unshift({ role: "system", content: this.instructions });
@@ -1624,6 +1938,7 @@ var TurnEngine = class _TurnEngine {
1624
1938
  this._cancel = false;
1625
1939
  this._producedFiles = [];
1626
1940
  this._turnStartTime = Date.now();
1941
+ this._compactStalledAt = -1;
1627
1942
  this._emitHook((h) => h.onTurnStart?.(userInput));
1628
1943
  yield {
1629
1944
  type: "turn_start" /* TURN_START */,
@@ -2340,7 +2655,9 @@ var TurnEngine = class _TurnEngine {
2340
2655
  for (let i = 0; i < concurrent.length; i++) {
2341
2656
  const tc = concurrent[i];
2342
2657
  const { result, status } = results[i];
2343
- this.messages.push(toolResultMessage(tc, result, this.taskId));
2658
+ this.messages.push(
2659
+ toolResultMessage(tc, result, this.taskId, this._toolResultDir)
2660
+ );
2344
2661
  this._trackProducedFile(
2345
2662
  tc,
2346
2663
  result,
@@ -2403,7 +2720,9 @@ var TurnEngine = class _TurnEngine {
2403
2720
  };
2404
2721
  status = "error";
2405
2722
  }
2406
- this.messages.push(toolResultMessage(tc, result, this.taskId));
2723
+ this.messages.push(
2724
+ toolResultMessage(tc, result, this.taskId, this._toolResultDir)
2725
+ );
2407
2726
  this._trackProducedFile(
2408
2727
  tc,
2409
2728
  result,
@@ -2438,13 +2757,41 @@ var TurnEngine = class _TurnEngine {
2438
2757
  * 过滤 notice 消息;tool 消息只保留必要字段;
2439
2758
  * 其余消息剥离 display-only sidecar 字段(source/_display/ts/
2440
2759
  * reasoning/usage),避免污染模型上下文。
2760
+ *
2761
+ * 达到软阈值时先淘汰旧工具结果(零 LLM 成本),再产出最终视图。
2441
2762
  */
2442
2763
  _outboundMessages() {
2443
- const _STRIP = /* @__PURE__ */ new Set(["source", "_display", "ts", "reasoning", "usage"]);
2444
- const sourceMessages = applyToOutbound(
2445
- this.messages,
2446
- this.compactionState
2764
+ const base = applyToOutbound(this.messages, this.compactionState);
2765
+ const deduped = this._dedupEnabled ? applyReadFileDedup(base).messages : base;
2766
+ let view = this._finalizeOutbound(deduped);
2767
+ if (!this._elisionEnabled) return view;
2768
+ if (estimateTokens(view) < this._softTriggerTokens()) {
2769
+ return view;
2770
+ }
2771
+ const oneShot = applyOneShotElision(deduped);
2772
+ if (oneShot.freedChars > 0) {
2773
+ view = this._finalizeOutbound(oneShot.messages);
2774
+ if (estimateTokens(view) < this._softTriggerTokens()) {
2775
+ return view;
2776
+ }
2777
+ }
2778
+ const keepChars = elisionKeepChars(
2779
+ triggerTokens(
2780
+ this._getContextWindow(),
2781
+ this._thresholdPct,
2782
+ this._capTokens
2783
+ )
2784
+ );
2785
+ const elided = applyToolResultElision(
2786
+ oneShot.messages,
2787
+ keepChars
2447
2788
  );
2789
+ if (elided.freedChars === 0) return view;
2790
+ return this._finalizeOutbound(elided.messages);
2791
+ }
2792
+ /** 过滤 notice + 剥离 display-only 字段,得到最终发送视图 */
2793
+ _finalizeOutbound(sourceMessages) {
2794
+ const _STRIP = /* @__PURE__ */ new Set(["source", "_display", "ts", "reasoning", "usage"]);
2448
2795
  return sourceMessages.filter((m) => m.role !== "notice").map((m) => {
2449
2796
  if (m.role === "tool") {
2450
2797
  return {
@@ -2462,6 +2809,15 @@ var TurnEngine = class _TurnEngine {
2462
2809
  return stripped;
2463
2810
  });
2464
2811
  }
2812
+ /** 软阈值(token 数):达到它只淘汰旧工具结果 */
2813
+ _softTriggerTokens() {
2814
+ return softTriggerTokens(
2815
+ this._getContextWindow(),
2816
+ this._thresholdPct,
2817
+ this._capTokens,
2818
+ this._softThresholdPct
2819
+ );
2820
+ }
2465
2821
  /** 通知消息:仅用于日志/展示,不发给模型 */
2466
2822
  _appendNotice(kind, text) {
2467
2823
  this.messages.push({
@@ -2503,18 +2859,36 @@ var TurnEngine = class _TurnEngine {
2503
2859
  return false;
2504
2860
  }
2505
2861
  // ===================== 自动压缩 =====================
2506
- /** 当前上下文 token 用量:优先采信 API 返回的精确数量,否则用 chars/4 估算 */
2862
+ /**
2863
+ * 当前上下文 token 用量。
2864
+ *
2865
+ * API 返回的 `usage.input` 是精确值,但反映的是「上一次请求」——工具结果
2866
+ * 与淘汰/压缩都会让它过时。因此同时估算当前 outbound 视图,取较大者:
2867
+ * 宁可早一点压缩,也不要漏判导致溢出。
2868
+ */
2507
2869
  _compactionTokens() {
2508
- if (this._lastContextTokens != null) {
2509
- return { tokens: this._lastContextTokens, estimated: false };
2870
+ const estimated = estimateTokens(this._outboundMessages());
2871
+ if (this._lastContextTokens == null) {
2872
+ return { tokens: estimated, estimated: true };
2510
2873
  }
2511
- return {
2512
- tokens: estimateTokens(this._outboundMessages()),
2513
- estimated: true
2514
- };
2874
+ if (estimated > this._lastContextTokens) {
2875
+ return { tokens: estimated, estimated: true };
2876
+ }
2877
+ return { tokens: this._lastContextTokens, estimated: false };
2878
+ }
2879
+ /** 压缩后重置精确计数:后续按估算值判断 */
2880
+ _resetContextTokens() {
2881
+ this._lastContextTokens = null;
2882
+ }
2883
+ /** 当前 outbound 视图的估算 token 数 */
2884
+ _estimateContextTokens() {
2885
+ return estimateTokens(this._outboundMessages());
2515
2886
  }
2516
2887
  /** 检查是否需要压缩(每轮 LLM 调用前) */
2517
2888
  _compactionDue() {
2889
+ if (this._compactStalledAt >= 0 && this.messages.length < this._compactStalledAt + COMPACT_STALL_RETRY_DELTA) {
2890
+ return false;
2891
+ }
2518
2892
  const { tokens } = this._compactionTokens();
2519
2893
  return shouldCompact(
2520
2894
  tokens,
@@ -2561,63 +2935,82 @@ var TurnEngine = class _TurnEngine {
2561
2935
  async _compactNow(reqId, force = false) {
2562
2936
  const contextWindow = this._getContextWindow();
2563
2937
  const messagesBefore = this._outboundMessages().length;
2564
- const keep = Math.floor(
2565
- KEEP_RECENT_FRACTION * triggerTokens(
2566
- contextWindow,
2567
- this._thresholdPct,
2568
- this._capTokens
2569
- )
2938
+ const trigger = triggerTokens(
2939
+ contextWindow,
2940
+ this._thresholdPct,
2941
+ this._capTokens
2570
2942
  );
2571
- const model = this.model;
2572
- let state = null;
2573
- let failed2 = false;
2574
- for (let attempt = 0; attempt < 2; attempt++) {
2575
- try {
2576
- const logTime = (/* @__PURE__ */ new Date()).toISOString();
2577
- state = await buildState(
2943
+ const target = Math.floor(
2944
+ trigger * (force ? OVERFLOW_TARGET_RATIO : 0.9)
2945
+ );
2946
+ const model = process.env.COMPACTION_MODEL?.trim() || this.model;
2947
+ const reasoningEffort = summarizerReasoningEffort();
2948
+ let keep = Math.floor(KEEP_RECENT_FRACTION * trigger);
2949
+ let method = null;
2950
+ let lastState = null;
2951
+ let progressed = false;
2952
+ for (let round = 0; round < MAX_COMPACT_ROUNDS; round++) {
2953
+ if (round > 0 && this._estimateContextTokens() <= target) break;
2954
+ let state = null;
2955
+ for (let attempt = 0; attempt < 2 && !state; attempt++) {
2956
+ try {
2957
+ const logTime = (/* @__PURE__ */ new Date()).toISOString();
2958
+ state = await buildState(
2959
+ this.messages,
2960
+ this.provider,
2961
+ model,
2962
+ keep,
2963
+ this.compactionState,
2964
+ { reqId, logTime, reasoningEffort }
2965
+ );
2966
+ } catch {
2967
+ }
2968
+ }
2969
+ if (state) {
2970
+ this.compactionState = state;
2971
+ lastState = state;
2972
+ method = "summarized";
2973
+ progressed = true;
2974
+ } else {
2975
+ const trimmed = trimState(
2578
2976
  this.messages,
2579
- this.provider,
2580
- model,
2581
- keep,
2582
2977
  this.compactionState,
2583
- { reqId, logTime, reasoningEffort: this._reasoningEffort }
2978
+ force ? OVERFLOW_TRIM_FRACTION : 0.25
2584
2979
  );
2585
- failed2 = false;
2586
- break;
2587
- } catch {
2588
- failed2 = true;
2589
- }
2590
- }
2591
- if (state) {
2592
- this.compactionState = state;
2593
- this._lastContextTokens = null;
2980
+ if (!trimmed) break;
2981
+ this.compactionState = trimmed;
2982
+ lastState = trimmed;
2983
+ method = "trimmed";
2984
+ progressed = true;
2985
+ }
2986
+ this._resetContextTokens();
2987
+ if (this._estimateContextTokens() <= trigger) break;
2988
+ keep = Math.max(Math.floor(trigger * 0.05), Math.floor(keep / 2));
2989
+ }
2990
+ if (!progressed || !lastState) return { notice: null, method: null };
2991
+ this._compactStalledAt = this._estimateContextTokens() >= trigger ? this.messages.length : -1;
2992
+ const messagesAfter = this._outboundMessages().length;
2993
+ const common = {
2994
+ boundaryIndex: lastState.boundaryIndex,
2995
+ messagesBefore,
2996
+ messagesAfter
2997
+ };
2998
+ if (method === "summarized") {
2594
2999
  return {
2595
3000
  notice: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29 \u2014 \u65E9\u671F\u8F6E\u6B21\u5DF2\u6458\u8981",
2596
3001
  method: "summarized",
2597
- modelUsed: state.modelUsed,
2598
- boundaryIndex: state.boundaryIndex,
2599
- summaryPreview: state.summaryText.slice(0, 200),
2600
- keptUserMessages: state.userMessages.length,
2601
- droppedUserMessages: state.userMessagesDropped,
2602
- messagesBefore,
2603
- messagesAfter: this._outboundMessages().length
3002
+ modelUsed: lastState.modelUsed,
3003
+ summaryPreview: lastState.summaryText.slice(0, 200),
3004
+ keptUserMessages: lastState.userMessages.length,
3005
+ droppedUserMessages: lastState.userMessagesDropped,
3006
+ ...common
2604
3007
  };
2605
3008
  }
2606
- if (failed2 || force) {
2607
- const trimmed = trimState(this.messages, this.compactionState);
2608
- if (trimmed) {
2609
- this.compactionState = trimmed;
2610
- this._lastContextTokens = null;
2611
- return {
2612
- notice: "\u4E0A\u4E0B\u6587\u5DF2\u88C1\u526A \u2014 \u6700\u65E7\u7684\u8F6E\u6B21\u5DF2\u4E22\u5F03\uFF08\u6458\u8981\u4E0D\u53EF\u7528\uFF09",
2613
- method: "trimmed",
2614
- boundaryIndex: trimmed.boundaryIndex,
2615
- messagesBefore,
2616
- messagesAfter: this._outboundMessages().length
2617
- };
2618
- }
2619
- }
2620
- return { notice: null, method: null };
3009
+ return {
3010
+ notice: "\u4E0A\u4E0B\u6587\u5DF2\u88C1\u526A \u2014 \u6700\u65E7\u7684\u8F6E\u6B21\u5DF2\u4E22\u5F03\uFF08\u6458\u8981\u4E0D\u53EF\u7528\uFF09",
3011
+ method: "trimmed",
3012
+ ...common
3013
+ };
2621
3014
  }
2622
3015
  /** 追踪文件写入工具成功产生的文件 */
2623
3016
  _trackProducedFile(tc, result, status) {
@@ -2627,9 +3020,9 @@ var TurnEngine = class _TurnEngine {
2627
3020
  return;
2628
3021
  }
2629
3022
  if (result && typeof result === "object") {
2630
- const path42 = result.path;
2631
- if (path42) {
2632
- this._producedFiles.push(path42);
3023
+ const path44 = result.path;
3024
+ if (path44) {
3025
+ this._producedFiles.push(path44);
2633
3026
  return;
2634
3027
  }
2635
3028
  const changed = result.changed_files;
@@ -2653,7 +3046,7 @@ var TurnEngine = class _TurnEngine {
2653
3046
  _fileExists(f, ws) {
2654
3047
  if (!ws && !_path.isAbsolute(f)) return true;
2655
3048
  try {
2656
- return fs.existsSync(_path.isAbsolute(f) ? f : _path.join(ws, f));
3049
+ return fs2.existsSync(_path.isAbsolute(f) ? f : _path.join(ws, f));
2657
3050
  } catch {
2658
3051
  return false;
2659
3052
  }
@@ -2676,7 +3069,7 @@ var TurnEngine = class _TurnEngine {
2676
3069
  if (depth > 4 || results.length >= 50) return;
2677
3070
  let entries;
2678
3071
  try {
2679
- entries = fs.readdirSync(dir, { withFileTypes: true });
3072
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
2680
3073
  } catch {
2681
3074
  return;
2682
3075
  }
@@ -2691,7 +3084,7 @@ var TurnEngine = class _TurnEngine {
2691
3084
  const ext = dotIdx !== -1 ? e.name.slice(dotIdx).toLowerCase() : "";
2692
3085
  if (!_TurnEngine.SCAN_SUFFIXES.has(ext)) continue;
2693
3086
  try {
2694
- const st = fs.statSync(full);
3087
+ const st = fs2.statSync(full);
2695
3088
  if (st.mtimeMs >= since) {
2696
3089
  results.push(
2697
3090
  _path.relative(_path.resolve(ws), _path.resolve(full))
@@ -2713,8 +3106,8 @@ var TurnEngine = class _TurnEngine {
2713
3106
 
2714
3107
  // src/providers/OpenAIProvider.ts
2715
3108
  init_esm_shims();
2716
- import * as fs2 from "fs";
2717
- import * as path2 from "path";
3109
+ import * as fs3 from "fs";
3110
+ import * as path4 from "path";
2718
3111
  import OpenAI from "openai";
2719
3112
 
2720
3113
  // src/terminal.ts
@@ -2900,7 +3293,7 @@ var OpenAIProvider = class {
2900
3293
  });
2901
3294
  if (opts.logDir) {
2902
3295
  this._logDir = opts.logDir;
2903
- fs2.mkdirSync(opts.logDir, { recursive: true });
3296
+ fs3.mkdirSync(opts.logDir, { recursive: true });
2904
3297
  }
2905
3298
  }
2906
3299
  // =====================================================================
@@ -3053,6 +3446,8 @@ var OpenAIProvider = class {
3053
3446
  tools: this._toResponsesTools(tools, model),
3054
3447
  // 推理强度:DeepSeek 不支持,跳过
3055
3448
  ...opts.reasoningEffort && !this._isDeepSeek(model) ? { reasoning: { effort: opts.reasoningEffort } } : {},
3449
+ // 限制输出长度(摘要器等轻量请求)
3450
+ ...opts.maxTokens ? { max_output_tokens: opts.maxTokens } : {},
3056
3451
  stream: true
3057
3452
  }, requestOptions);
3058
3453
  return this._responsesStream(reqId, startTime, rawStream, {
@@ -3151,6 +3546,8 @@ var OpenAIProvider = class {
3151
3546
  })),
3152
3547
  // 推理强度:DeepSeek 不支持,跳过
3153
3548
  ...opts.reasoningEffort && !this._isDeepSeek(model) ? { reasoning_effort: opts.reasoningEffort } : {},
3549
+ // 限制输出长度:o 系列 / gpt-5 推理模型仅支持 max_completion_tokens
3550
+ ...opts.maxTokens ? isReasoningModel(model) ? { max_completion_tokens: opts.maxTokens } : { max_tokens: opts.maxTokens } : {},
3154
3551
  stream: true,
3155
3552
  // 要求返回 Token 用量统计(在流结束前的一个 chunk 中携带)
3156
3553
  stream_options: { include_usage: true }
@@ -3264,8 +3661,8 @@ var OpenAIProvider = class {
3264
3661
  if (!this._logDir) return;
3265
3662
  try {
3266
3663
  const today = logTime?.split("T")[0];
3267
- const dir = path2.join(this._logDir, today);
3268
- fs2.mkdirSync(dir, { recursive: true });
3664
+ const dir = path4.join(this._logDir, today);
3665
+ fs3.mkdirSync(dir, { recursive: true });
3269
3666
  const entry = {
3270
3667
  reqId,
3271
3668
  ts: logTime,
@@ -3277,8 +3674,8 @@ var OpenAIProvider = class {
3277
3674
  toolNames: tools?.map((t) => t.function.name) ?? []
3278
3675
  }
3279
3676
  };
3280
- fs2.writeFileSync(
3281
- path2.join(dir, `${reqId}.json`),
3677
+ fs3.writeFileSync(
3678
+ path4.join(dir, `${reqId}.json`),
3282
3679
  JSON.stringify(entry, null, 2),
3283
3680
  "utf-8"
3284
3681
  );
@@ -3297,9 +3694,9 @@ var OpenAIProvider = class {
3297
3694
  if (!this._logDir) return;
3298
3695
  try {
3299
3696
  const today = logTime?.split("T")[0];
3300
- const filePath = path2.join(this._logDir, today, `${reqId}.json`);
3301
- if (!fs2.existsSync(filePath)) return;
3302
- const entry = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
3697
+ const filePath = path4.join(this._logDir, today, `${reqId}.json`);
3698
+ if (!fs3.existsSync(filePath)) return;
3699
+ const entry = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
3303
3700
  entry.elapsedMs = Date.now() - startTime;
3304
3701
  if (data.response) {
3305
3702
  entry.response = data.response;
@@ -3307,7 +3704,7 @@ var OpenAIProvider = class {
3307
3704
  if (data.error) {
3308
3705
  entry.error = data.error;
3309
3706
  }
3310
- fs2.writeFileSync(filePath, JSON.stringify(entry, null, 2), "utf-8");
3707
+ fs3.writeFileSync(filePath, JSON.stringify(entry, null, 2), "utf-8");
3311
3708
  } catch (e) {
3312
3709
  console.error(e);
3313
3710
  }
@@ -3770,62 +4167,62 @@ init_esm_shims();
3770
4167
 
3771
4168
  // src/tools/pathSafety.ts
3772
4169
  init_esm_shims();
3773
- import * as fs3 from "fs";
3774
- import * as path3 from "path";
4170
+ import * as fs4 from "fs";
4171
+ import * as path5 from "path";
3775
4172
  function isInsideRelative(rel) {
3776
- return !path3.isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${path3.sep}`);
4173
+ return !path5.isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${path5.sep}`);
3777
4174
  }
3778
4175
  function isFilesystemRoot(p) {
3779
- return path3.parse(p).root === p;
4176
+ return path5.parse(p).root === p;
3780
4177
  }
3781
4178
  function resolveWithinRoot(workspace, target) {
3782
- const abs = path3.resolve(workspace, target);
4179
+ const abs = path5.resolve(workspace, target);
3783
4180
  let rootReal;
3784
4181
  try {
3785
- rootReal = fs3.realpathSync(workspace);
4182
+ rootReal = fs4.realpathSync(workspace);
3786
4183
  } catch {
3787
- const rootAbs = path3.resolve(workspace);
3788
- if (!isInsideRelative(path3.relative(rootAbs, abs))) return null;
4184
+ const rootAbs = path5.resolve(workspace);
4185
+ if (!isInsideRelative(path5.relative(rootAbs, abs))) return null;
3789
4186
  return { root: rootAbs, real: abs };
3790
4187
  }
3791
4188
  if (isFilesystemRoot(rootReal)) return null;
3792
4189
  let real;
3793
4190
  try {
3794
- real = fs3.realpathSync(abs);
4191
+ real = fs4.realpathSync(abs);
3795
4192
  } catch {
3796
4193
  let probe = abs;
3797
4194
  const tail = [];
3798
4195
  for (; ; ) {
3799
- const exists = fs3.existsSync(probe) || fs3.lstatSync(probe, { throwIfNoEntry: false }) !== void 0;
4196
+ const exists = fs4.existsSync(probe) || fs4.lstatSync(probe, { throwIfNoEntry: false }) !== void 0;
3800
4197
  if (exists) {
3801
4198
  try {
3802
- real = path3.join(fs3.realpathSync(probe), ...tail);
4199
+ real = path5.join(fs4.realpathSync(probe), ...tail);
3803
4200
  break;
3804
4201
  } catch {
3805
4202
  return null;
3806
4203
  }
3807
4204
  }
3808
- tail.unshift(path3.basename(probe));
3809
- const parent = path3.dirname(probe);
4205
+ tail.unshift(path5.basename(probe));
4206
+ const parent = path5.dirname(probe);
3810
4207
  if (parent === probe) return null;
3811
4208
  probe = parent;
3812
4209
  }
3813
4210
  }
3814
- if (!isInsideRelative(path3.relative(rootReal, real))) return null;
4211
+ if (!isInsideRelative(path5.relative(rootReal, real))) return null;
3815
4212
  return { root: rootReal, real };
3816
4213
  }
3817
4214
  function isWithinRoot(workspace, target) {
3818
4215
  return resolveWithinRoot(workspace, target) !== null;
3819
4216
  }
3820
4217
  function resolveEntryWithinRoot(workspace, target) {
3821
- const abs = path3.resolve(workspace, target);
3822
- const parent = resolveWithinRoot(workspace, path3.dirname(abs));
4218
+ const abs = path5.resolve(workspace, target);
4219
+ const parent = resolveWithinRoot(workspace, path5.dirname(abs));
3823
4220
  if (!parent) return null;
3824
- return { root: parent.root, real: path3.join(parent.real, path3.basename(abs)) };
4221
+ return { root: parent.root, real: path5.join(parent.real, path5.basename(abs)) };
3825
4222
  }
3826
4223
 
3827
4224
  // src/permissions/PermissionEngine.ts
3828
- import * as path4 from "path";
4225
+ import * as path6 from "path";
3829
4226
  var READ_ONLY_MODES = /* @__PURE__ */ new Set(["discuss" /* DISCUSS */, "plan" /* PLAN */]);
3830
4227
  var WRITE_TOOLS = /* @__PURE__ */ new Set([
3831
4228
  "write_file",
@@ -3875,7 +4272,7 @@ var PermissionEngine = class {
3875
4272
  /** 会话级「总是允许此命令」集合 */
3876
4273
  sessionAllowCommands;
3877
4274
  constructor(opts) {
3878
- this.workspaceRoot = path4.resolve(opts.workspaceRoot);
4275
+ this.workspaceRoot = path6.resolve(opts.workspaceRoot);
3879
4276
  this.mode = opts.mode;
3880
4277
  this.allowedCommands = Array.isArray(opts.allowedCommands) ? opts.allowedCommands : [];
3881
4278
  this.sessionAllowTools = /* @__PURE__ */ new Set();
@@ -4801,14 +5198,14 @@ function approvalToHtmlTable(input, maxLen = APPROVAL_VALUE_MAX) {
4801
5198
 
4802
5199
  // src/engine/FileLogHook.ts
4803
5200
  init_esm_shims();
4804
- import * as fs4 from "fs";
4805
- import * as path5 from "path";
5201
+ import * as fs5 from "fs";
5202
+ import * as path7 from "path";
4806
5203
  var FileLogHook = class {
4807
5204
  constructor(sessionId, stateDir2) {
4808
5205
  this.sessionId = sessionId;
4809
- fs4.mkdirSync(stateDir2, { recursive: true });
4810
- this._logPath = path5.join(stateDir2, `${sessionId}.jsonl`);
4811
- this._statePath = path5.join(stateDir2, `${sessionId}_state.json`);
5206
+ fs5.mkdirSync(stateDir2, { recursive: true });
5207
+ this._logPath = path7.join(stateDir2, `${sessionId}.jsonl`);
5208
+ this._statePath = path7.join(stateDir2, `${sessionId}_state.json`);
4812
5209
  }
4813
5210
  sessionId;
4814
5211
  _logPath;
@@ -4902,13 +5299,13 @@ var FileLogHook = class {
4902
5299
  ...meta?.nameSource !== void 0 ? { nameSource: meta.nameSource } : {},
4903
5300
  ...meta?.taskCount !== void 0 ? { taskCount: meta.taskCount } : {}
4904
5301
  };
4905
- fs4.writeFileSync(this._statePath, JSON.stringify(state, null, 2), "utf-8");
5302
+ fs5.writeFileSync(this._statePath, JSON.stringify(state, null, 2), "utf-8");
4906
5303
  }
4907
5304
  /** 读取保存的引擎状态 */
4908
5305
  static loadState(sessionId, stateDir2) {
4909
- const p = path5.join(stateDir2, `${sessionId}_state.json`);
5306
+ const p = path7.join(stateDir2, `${sessionId}_state.json`);
4910
5307
  try {
4911
- const raw = fs4.readFileSync(p, "utf-8");
5308
+ const raw = fs5.readFileSync(p, "utf-8");
4912
5309
  return JSON.parse(raw);
4913
5310
  } catch (e) {
4914
5311
  console.error(e);
@@ -4917,9 +5314,9 @@ var FileLogHook = class {
4917
5314
  }
4918
5315
  /** 读取事件日志 */
4919
5316
  static loadEvents(sessionId, stateDir2) {
4920
- const p = path5.join(stateDir2, `${sessionId}.jsonl`);
5317
+ const p = path7.join(stateDir2, `${sessionId}.jsonl`);
4921
5318
  try {
4922
- const raw = fs4.readFileSync(p, "utf-8");
5319
+ const raw = fs5.readFileSync(p, "utf-8");
4923
5320
  return raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
4924
5321
  } catch (e) {
4925
5322
  console.error(e);
@@ -4930,7 +5327,7 @@ var FileLogHook = class {
4930
5327
  _append(entry) {
4931
5328
  entry["ts"] = (/* @__PURE__ */ new Date()).toISOString();
4932
5329
  try {
4933
- fs4.appendFileSync(this._logPath, JSON.stringify(entry) + "\n", "utf-8");
5330
+ fs5.appendFileSync(this._logPath, JSON.stringify(entry) + "\n", "utf-8");
4934
5331
  } catch (e) {
4935
5332
  console.error(e);
4936
5333
  }
@@ -5358,12 +5755,12 @@ var McpManager = class {
5358
5755
 
5359
5756
  // src/tools/files.ts
5360
5757
  init_esm_shims();
5361
- import * as fs5 from "fs";
5362
- import * as path6 from "path";
5758
+ import * as fs6 from "fs";
5759
+ import * as path8 from "path";
5363
5760
  var DEFAULT_MAX_LINES = 2e3;
5364
5761
  var MAX_LINE_CHARS = 500;
5365
5762
  function makeReadFile(workspace) {
5366
- const root = path6.resolve(workspace);
5763
+ const root = path8.resolve(workspace);
5367
5764
  return function read_file(args) {
5368
5765
  const filePath = String(args.path ?? "");
5369
5766
  const startLine = Math.max(1, Number(args.start_line ?? 1));
@@ -5373,13 +5770,13 @@ function makeReadFile(workspace) {
5373
5770
  return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
5374
5771
  }
5375
5772
  const target = resolved.real;
5376
- if (!fs5.existsSync(target) || !fs5.statSync(target).isFile()) {
5773
+ if (!fs6.existsSync(target) || !fs6.statSync(target).isFile()) {
5377
5774
  return { error: `\u4E0D\u662F\u6587\u4EF6: ${filePath}` };
5378
5775
  }
5379
5776
  const selected = [];
5380
5777
  let total = 0;
5381
5778
  try {
5382
- const content = fs5.readFileSync(target, "utf-8");
5779
+ const content = fs6.readFileSync(target, "utf-8");
5383
5780
  const lines = content.split("\n");
5384
5781
  total = lines.length;
5385
5782
  for (let i = startLine - 1; i < Math.min(startLine - 1 + maxLines, lines.length); i++) {
@@ -5394,7 +5791,7 @@ function makeReadFile(workspace) {
5394
5791
  }
5395
5792
  const end = startLine + selected.length - 1;
5396
5793
  const result = {
5397
- path: path6.relative(resolved.root, target),
5794
+ path: path8.relative(resolved.root, target),
5398
5795
  start_line: startLine,
5399
5796
  end_line: end,
5400
5797
  total_lines: total,
@@ -5407,7 +5804,7 @@ function makeReadFile(workspace) {
5407
5804
  };
5408
5805
  }
5409
5806
  function makeWriteFile(workspace) {
5410
- const root = path6.resolve(workspace);
5807
+ const root = path8.resolve(workspace);
5411
5808
  return function write_file(args) {
5412
5809
  const filePath = String(args.path ?? "");
5413
5810
  const content = String(args.content ?? "");
@@ -5417,34 +5814,34 @@ function makeWriteFile(workspace) {
5417
5814
  return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
5418
5815
  }
5419
5816
  const target = resolved.real;
5420
- if (fs5.existsSync(target) && fs5.statSync(target).isDirectory()) {
5817
+ if (fs6.existsSync(target) && fs6.statSync(target).isDirectory()) {
5421
5818
  return { error: "\u8DEF\u5F84\u662F\u76EE\u5F55\uFF0C\u4E0D\u662F\u6587\u4EF6" };
5422
5819
  }
5423
- if (fs5.existsSync(target) && !overwrite) {
5820
+ if (fs6.existsSync(target) && !overwrite) {
5424
5821
  return { error: "\u6587\u4EF6\u5DF2\u5B58\u5728 (overwrite=false)" };
5425
5822
  }
5426
5823
  try {
5427
- fs5.mkdirSync(path6.dirname(target), { recursive: true });
5428
- fs5.writeFileSync(target, content, "utf-8");
5824
+ fs6.mkdirSync(path8.dirname(target), { recursive: true });
5825
+ fs6.writeFileSync(target, content, "utf-8");
5429
5826
  } catch (exc) {
5430
5827
  return { error: `\u5199\u5165\u5931\u8D25: ${String(exc)}` };
5431
5828
  }
5432
- return path6.relative(resolved.root, target);
5829
+ return path8.relative(resolved.root, target);
5433
5830
  };
5434
5831
  }
5435
5832
  function makeListFiles(workspace) {
5436
- const root = path6.resolve(workspace);
5833
+ const root = path8.resolve(workspace);
5437
5834
  return function list_files(args) {
5438
5835
  const dirPath = String(args.path ?? ".");
5439
5836
  const maxResults = Math.min(Number(args.max_results ?? 100), 100);
5440
5837
  const resolved = resolveWithinRoot(root, dirPath);
5441
5838
  if (!resolved) return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
5442
5839
  const target = resolved.real;
5443
- if (!fs5.existsSync(target) || !fs5.statSync(target).isDirectory()) {
5840
+ if (!fs6.existsSync(target) || !fs6.statSync(target).isDirectory()) {
5444
5841
  return { error: `\u4E0D\u662F\u76EE\u5F55: ${dirPath}` };
5445
5842
  }
5446
5843
  try {
5447
- const entries = fs5.readdirSync(target, { withFileTypes: true });
5844
+ const entries = fs6.readdirSync(target, { withFileTypes: true });
5448
5845
  const results = [];
5449
5846
  for (const e of entries) {
5450
5847
  if (results.length >= maxResults) break;
@@ -5453,14 +5850,14 @@ function makeListFiles(workspace) {
5453
5850
  results.push(e.isDirectory() ? `${e.name}/` : e.name);
5454
5851
  }
5455
5852
  results.sort();
5456
- return { path: path6.relative(resolved.root, target), entries: results.slice(0, maxResults) };
5853
+ return { path: path8.relative(resolved.root, target), entries: results.slice(0, maxResults) };
5457
5854
  } catch (exc) {
5458
5855
  return { error: `\u5217\u51FA\u6587\u4EF6\u5931\u8D25: ${String(exc)}` };
5459
5856
  }
5460
5857
  };
5461
5858
  }
5462
5859
  function makeReplaceInFile(workspace) {
5463
- const root = path6.resolve(workspace);
5860
+ const root = path8.resolve(workspace);
5464
5861
  return function replace_in_file(args) {
5465
5862
  const filePath = String(args.path ?? "");
5466
5863
  const oldStr = String(args.old ?? "");
@@ -5470,19 +5867,19 @@ function makeReplaceInFile(workspace) {
5470
5867
  const resolved = resolveWithinRoot(root, filePath);
5471
5868
  if (!resolved) return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
5472
5869
  const target = resolved.real;
5473
- if (!fs5.existsSync(target) || !fs5.statSync(target).isFile()) {
5870
+ if (!fs6.existsSync(target) || !fs6.statSync(target).isFile()) {
5474
5871
  return { error: `\u4E0D\u662F\u6587\u4EF6: ${filePath}` };
5475
5872
  }
5476
5873
  try {
5477
- const content = fs5.readFileSync(target, "utf-8");
5874
+ const content = fs6.readFileSync(target, "utf-8");
5478
5875
  const count = content.split(oldStr).length - 1;
5479
5876
  if (count !== expectedReplacements) {
5480
5877
  return { error: `\u671F\u671B ${expectedReplacements} \u5904\u66FF\u6362\uFF0C\u5B9E\u9645\u627E\u5230 ${count} \u5904: ${filePath}` };
5481
5878
  }
5482
5879
  const updated = content.replaceAll(oldStr, newStr);
5483
- fs5.writeFileSync(target, updated, "utf-8");
5880
+ fs6.writeFileSync(target, updated, "utf-8");
5484
5881
  return {
5485
- path: path6.relative(resolved.root, target),
5882
+ path: path8.relative(resolved.root, target),
5486
5883
  replacements: count,
5487
5884
  chars_before: content.length,
5488
5885
  chars_after: updated.length
@@ -5583,8 +5980,8 @@ function registerFileTools(registry, workspace) {
5583
5980
 
5584
5981
  // src/tools/shell.ts
5585
5982
  init_esm_shims();
5586
- import * as fs7 from "fs";
5587
- import * as path8 from "path";
5983
+ import * as fs8 from "fs";
5984
+ import * as path10 from "path";
5588
5985
 
5589
5986
  // src/tools/execAsync.ts
5590
5987
  init_esm_shims();
@@ -5749,15 +6146,15 @@ function execAsync(file, argv, opts = {}) {
5749
6146
 
5750
6147
  // src/tools/shellRuntime.ts
5751
6148
  init_esm_shims();
5752
- import * as fs6 from "fs";
5753
- import * as path7 from "path";
6149
+ import * as fs7 from "fs";
6150
+ import * as path9 from "path";
5754
6151
  var WINDOWS_SUFFIXES = [".exe", ".cmd", ".bat", ".com", ""];
5755
6152
  function pathApi(platform) {
5756
- return platform === "win32" ? path7.win32 : path7.posix;
6153
+ return platform === "win32" ? path9.win32 : path9.posix;
5757
6154
  }
5758
6155
  function defaultFileExists(p) {
5759
6156
  try {
5760
- return fs6.statSync(p).isFile();
6157
+ return fs7.statSync(p).isFile();
5761
6158
  } catch {
5762
6159
  return false;
5763
6160
  }
@@ -5788,7 +6185,7 @@ function kindFromBasename(base) {
5788
6185
  return "sh";
5789
6186
  }
5790
6187
  function busyboxArgs(file, command) {
5791
- const base = (file ? path7.win32.basename(file) : "busybox").toLowerCase().replace(/\.(exe|cmd|bat|com)$/, "");
6188
+ const base = (file ? path9.win32.basename(file) : "busybox").toLowerCase().replace(/\.(exe|cmd|bat|com)$/, "");
5792
6189
  return base === "busybox" ? ["sh", "-c", command] : ["-c", command];
5793
6190
  }
5794
6191
  function argsFor(kind, command) {
@@ -6055,7 +6452,7 @@ var MAX_STDERR = 5e3;
6055
6452
  var MAX_BUFFER = 1024 * 1024 * 5;
6056
6453
  var shellSessions = /* @__PURE__ */ new Map();
6057
6454
  function sessionFor(workspace) {
6058
- const key = path8.resolve(workspace);
6455
+ const key = path10.resolve(workspace);
6059
6456
  let s = shellSessions.get(key);
6060
6457
  if (!s) {
6061
6458
  s = { cwd: key };
@@ -6112,8 +6509,8 @@ function makeRunShell(workspace, spec, tasks, scope) {
6112
6509
  let toRun = command;
6113
6510
  if (target) {
6114
6511
  try {
6115
- const resolved = path8.resolve(session.cwd, target);
6116
- if (fs7.existsSync(resolved) && fs7.statSync(resolved).isDirectory()) {
6512
+ const resolved = path10.resolve(session.cwd, target);
6513
+ if (fs8.existsSync(resolved) && fs8.statSync(resolved).isDirectory()) {
6117
6514
  session.cwd = resolved;
6118
6515
  }
6119
6516
  } catch {
@@ -6248,8 +6645,8 @@ init_esm_shims();
6248
6645
 
6249
6646
  // src/tools/nodeGrep.ts
6250
6647
  init_esm_shims();
6251
- import * as fs8 from "fs";
6252
- import * as path9 from "path";
6648
+ import * as fs9 from "fs";
6649
+ import * as path11 from "path";
6253
6650
  var SKIP_DIRS = /* @__PURE__ */ new Set([
6254
6651
  ".git",
6255
6652
  "node_modules",
@@ -6274,27 +6671,27 @@ function nodeGrep(opts) {
6274
6671
  } catch (err) {
6275
6672
  throw new Error(`\u65E0\u6548\u7684\u6B63\u5219\u8868\u8FBE\u5F0F: ${opts.pattern}\uFF08${String(err)}\uFF09`);
6276
6673
  }
6277
- const target = path9.resolve(opts.workspace, opts.target);
6278
- if (!fs8.existsSync(target)) {
6674
+ const target = path11.resolve(opts.workspace, opts.target);
6675
+ if (!fs9.existsSync(target)) {
6279
6676
  throw new Error(`\u641C\u7D22\u8DEF\u5F84\u4E0D\u5B58\u5728: ${opts.target}`);
6280
6677
  }
6281
6678
  const matches = [];
6282
6679
  const scanFile = (file) => {
6283
6680
  let stat2;
6284
6681
  try {
6285
- stat2 = fs8.statSync(file);
6682
+ stat2 = fs9.statSync(file);
6286
6683
  } catch {
6287
6684
  return false;
6288
6685
  }
6289
6686
  if (!stat2.isFile() || stat2.size > maxFileBytes) return false;
6290
6687
  let buf;
6291
6688
  try {
6292
- buf = fs8.readFileSync(file);
6689
+ buf = fs9.readFileSync(file);
6293
6690
  } catch {
6294
6691
  return false;
6295
6692
  }
6296
6693
  if (looksBinary(buf)) return false;
6297
- const rel = path9.relative(opts.workspace, file) || path9.basename(file);
6694
+ const rel = path11.relative(opts.workspace, file) || path11.basename(file);
6298
6695
  const lines = buf.toString("utf-8").split("\n");
6299
6696
  for (let i = 0; i < lines.length; i++) {
6300
6697
  const raw = lines[i].replace(/\r$/, "");
@@ -6305,7 +6702,7 @@ function nodeGrep(opts) {
6305
6702
  }
6306
6703
  return false;
6307
6704
  };
6308
- const stat = fs8.statSync(target);
6705
+ const stat = fs9.statSync(target);
6309
6706
  if (stat.isFile()) {
6310
6707
  scanFile(target);
6311
6708
  return matches;
@@ -6315,12 +6712,12 @@ function nodeGrep(opts) {
6315
6712
  const dir = stack.pop();
6316
6713
  let entries;
6317
6714
  try {
6318
- entries = fs8.readdirSync(dir, { withFileTypes: true });
6715
+ entries = fs9.readdirSync(dir, { withFileTypes: true });
6319
6716
  } catch {
6320
6717
  continue;
6321
6718
  }
6322
6719
  for (const entry of entries) {
6323
- const full = path9.join(dir, entry.name);
6720
+ const full = path11.join(dir, entry.name);
6324
6721
  if (entry.isDirectory()) {
6325
6722
  if (!SKIP_DIRS.has(entry.name)) stack.push(full);
6326
6723
  } else if (entry.isFile()) {
@@ -6333,22 +6730,22 @@ function nodeGrep(opts) {
6333
6730
 
6334
6731
  // src/tools/ripgrep.ts
6335
6732
  init_esm_shims();
6336
- import * as fs9 from "fs";
6337
- import * as os from "os";
6338
- import * as path10 from "path";
6733
+ import * as fs10 from "fs";
6734
+ import * as os2 from "os";
6735
+ import * as path12 from "path";
6339
6736
  import { createRequire } from "module";
6340
6737
  var cached2;
6341
6738
  function extractBundledRipgrep() {
6342
6739
  try {
6343
- const req2 = typeof __require === "function" ? __require : createRequire(path10.join(process.cwd(), "__zqagent__.js"));
6740
+ const req2 = typeof __require === "function" ? __require : createRequire(path12.join(process.cwd(), "__zqagent__.js"));
6344
6741
  const sea = req2("node:sea");
6345
6742
  if (!sea?.isSea?.() || typeof sea.getAsset !== "function") return null;
6346
6743
  const asset = sea.getAsset("rg");
6347
6744
  if (!asset) return null;
6348
- const dir = fs9.mkdtempSync(path10.join(os.tmpdir(), "zqagent-rg-"));
6349
- const file = path10.join(dir, process.platform === "win32" ? "rg.exe" : "rg");
6350
- fs9.writeFileSync(file, Buffer.from(asset));
6351
- fs9.chmodSync(file, 493);
6745
+ const dir = fs10.mkdtempSync(path12.join(os2.tmpdir(), "zqagent-rg-"));
6746
+ const file = path12.join(dir, process.platform === "win32" ? "rg.exe" : "rg");
6747
+ fs10.writeFileSync(file, Buffer.from(asset));
6748
+ fs10.chmodSync(file, 493);
6352
6749
  return file;
6353
6750
  } catch {
6354
6751
  return null;
@@ -6357,7 +6754,7 @@ function extractBundledRipgrep() {
6357
6754
  function resolveRipgrep() {
6358
6755
  if (cached2 !== void 0) return cached2;
6359
6756
  const explicit = (process.env.ZQAGENT_RG ?? "").trim();
6360
- if (explicit && fs9.existsSync(explicit)) {
6757
+ if (explicit && fs10.existsSync(explicit)) {
6361
6758
  cached2 = explicit;
6362
6759
  return cached2;
6363
6760
  }
@@ -6371,7 +6768,7 @@ function resolveRipgrep() {
6371
6768
  env: process.env,
6372
6769
  fileExists: (p) => {
6373
6770
  try {
6374
- return fs9.statSync(p).isFile();
6771
+ return fs10.statSync(p).isFile();
6375
6772
  } catch {
6376
6773
  return false;
6377
6774
  }
@@ -6920,8 +7317,8 @@ function registerWebTools(registry) {
6920
7317
 
6921
7318
  // src/tools/git.ts
6922
7319
  init_esm_shims();
6923
- import * as fs10 from "fs";
6924
- import * as path11 from "path";
7320
+ import * as fs11 from "fs";
7321
+ import * as path13 from "path";
6925
7322
  async function runGit(repoDir, args, timeout = 15e3) {
6926
7323
  const res = await execAsync("git", ["-c", "core.quotepath=false", ...args], {
6927
7324
  cwd: repoDir,
@@ -6938,9 +7335,9 @@ async function runGit(repoDir, args, timeout = 15e3) {
6938
7335
  async function resolveRepoRoot(workspace, pathArg) {
6939
7336
  let dir = workspace;
6940
7337
  if (pathArg) {
6941
- const abs = path11.resolve(workspace, pathArg);
7338
+ const abs = path13.resolve(workspace, pathArg);
6942
7339
  try {
6943
- dir = fs10.statSync(abs).isDirectory() ? abs : path11.dirname(abs);
7340
+ dir = fs11.statSync(abs).isDirectory() ? abs : path13.dirname(abs);
6944
7341
  } catch {
6945
7342
  dir = abs;
6946
7343
  }
@@ -6953,24 +7350,24 @@ function repoDirArg(workspace, raw) {
6953
7350
  if (typeof raw !== "string") return void 0;
6954
7351
  const v = raw.trim();
6955
7352
  if (!v) return void 0;
6956
- const base = path11.resolve(workspace);
6957
- const abs = path11.resolve(base, v);
6958
- const rel = path11.relative(base, abs);
6959
- if (rel.startsWith("..") || path11.isAbsolute(rel)) return void 0;
7353
+ const base = path13.resolve(workspace);
7354
+ const abs = path13.resolve(base, v);
7355
+ const rel = path13.relative(base, abs);
7356
+ if (rel.startsWith("..") || path13.isAbsolute(rel)) return void 0;
6960
7357
  return abs;
6961
7358
  }
6962
7359
  function toPathspec(repoRoot, workspace, pathArg) {
6963
7360
  if (!pathArg) return null;
6964
- const abs = path11.resolve(workspace, pathArg);
6965
- let rel = path11.relative(repoRoot, abs);
7361
+ const abs = path13.resolve(workspace, pathArg);
7362
+ let rel = path13.relative(repoRoot, abs);
6966
7363
  if (rel === "") return ".";
6967
- if (rel.startsWith("..") || path11.isAbsolute(rel)) {
7364
+ if (rel.startsWith("..") || path13.isAbsolute(rel)) {
6968
7365
  try {
6969
- const realRepo = fs10.realpathSync(repoRoot);
6970
- const realAbs = fs10.realpathSync(abs);
6971
- const rel2 = path11.relative(realRepo, realAbs);
7366
+ const realRepo = fs11.realpathSync(repoRoot);
7367
+ const realAbs = fs11.realpathSync(abs);
7368
+ const rel2 = path13.relative(realRepo, realAbs);
6972
7369
  if (rel2 === "") return ".";
6973
- if (!rel2.startsWith("..") && !path11.isAbsolute(rel2)) return rel2;
7370
+ if (!rel2.startsWith("..") && !path13.isAbsolute(rel2)) return rel2;
6974
7371
  } catch {
6975
7372
  }
6976
7373
  return null;
@@ -7396,18 +7793,18 @@ var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".hg", ".s
7396
7793
  var SCAN_DEFAULT_DEPTH = 3;
7397
7794
  var SCAN_MAX_DIRS = 2e3;
7398
7795
  function readRepoHead(repoDir) {
7399
- const dotGit = path11.join(repoDir, ".git");
7796
+ const dotGit = path13.join(repoDir, ".git");
7400
7797
  try {
7401
7798
  let headFile;
7402
- if (fs10.statSync(dotGit).isDirectory()) {
7403
- headFile = path11.join(dotGit, "HEAD");
7799
+ if (fs11.statSync(dotGit).isDirectory()) {
7800
+ headFile = path13.join(dotGit, "HEAD");
7404
7801
  } else {
7405
- const raw = fs10.readFileSync(dotGit, "utf-8");
7802
+ const raw = fs11.readFileSync(dotGit, "utf-8");
7406
7803
  const m = /^gitdir:\s*(.+)$/m.exec(raw);
7407
7804
  if (!m) return {};
7408
- headFile = path11.join(path11.resolve(repoDir, m[1].trim()), "HEAD");
7805
+ headFile = path13.join(path13.resolve(repoDir, m[1].trim()), "HEAD");
7409
7806
  }
7410
- const head = fs10.readFileSync(headFile, "utf-8").trim();
7807
+ const head = fs11.readFileSync(headFile, "utf-8").trim();
7411
7808
  const prefix = "ref: refs/heads/";
7412
7809
  if (head.startsWith(prefix)) return { branch: head.slice(prefix.length), detached: false };
7413
7810
  if (/^[0-9a-f]{7,40}$/i.test(head)) return { branch: head.slice(0, 7), detached: true };
@@ -7416,7 +7813,7 @@ function readRepoHead(repoDir) {
7416
7813
  return {};
7417
7814
  }
7418
7815
  function scanGitRepos(workspace, depth = SCAN_DEFAULT_DEPTH) {
7419
- const root = path11.resolve(workspace);
7816
+ const root = path13.resolve(workspace);
7420
7817
  const maxDepth = Number.isFinite(depth) ? Math.min(Math.max(Math.floor(depth), 0), 6) : SCAN_DEFAULT_DEPTH;
7421
7818
  const repos = [];
7422
7819
  const stack = [{ dir: root, depth: 0 }];
@@ -7429,24 +7826,24 @@ function scanGitRepos(workspace, depth = SCAN_DEFAULT_DEPTH) {
7429
7826
  truncated = true;
7430
7827
  break;
7431
7828
  }
7432
- const rel = path11.relative(root, dir).split(path11.sep).join("/");
7433
- if (fs10.existsSync(path11.join(dir, ".git"))) {
7829
+ const rel = path13.relative(root, dir).split(path13.sep).join("/");
7830
+ if (fs11.existsSync(path13.join(dir, ".git"))) {
7434
7831
  repos.push({
7435
7832
  path: rel,
7436
- name: rel ? path11.basename(dir) : path11.basename(root),
7833
+ name: rel ? path13.basename(dir) : path13.basename(root),
7437
7834
  ...readRepoHead(dir)
7438
7835
  });
7439
7836
  }
7440
7837
  if (d >= maxDepth) continue;
7441
7838
  let entries;
7442
7839
  try {
7443
- entries = fs10.readdirSync(dir, { withFileTypes: true });
7840
+ entries = fs11.readdirSync(dir, { withFileTypes: true });
7444
7841
  } catch {
7445
7842
  continue;
7446
7843
  }
7447
7844
  for (const e of entries) {
7448
7845
  if (!e.isDirectory() || SCAN_SKIP_DIRS.has(e.name)) continue;
7449
- stack.push({ dir: path11.join(dir, e.name), depth: d + 1 });
7846
+ stack.push({ dir: path13.join(dir, e.name), depth: d + 1 });
7450
7847
  }
7451
7848
  }
7452
7849
  repos.sort(
@@ -7456,9 +7853,9 @@ function scanGitRepos(workspace, depth = SCAN_DEFAULT_DEPTH) {
7456
7853
  }
7457
7854
  function makeGitRepos(workspace) {
7458
7855
  return async function git_repos(args) {
7459
- const root = path11.resolve(workspace);
7856
+ const root = path13.resolve(workspace);
7460
7857
  try {
7461
- if (!fs10.statSync(root).isDirectory()) return { root, repos: [], error: "\u5DE5\u4F5C\u533A\u4E0D\u662F\u76EE\u5F55" };
7858
+ if (!fs11.statSync(root).isDirectory()) return { root, repos: [], error: "\u5DE5\u4F5C\u533A\u4E0D\u662F\u76EE\u5F55" };
7462
7859
  } catch {
7463
7860
  return { root, repos: [], error: "\u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728" };
7464
7861
  }
@@ -7470,10 +7867,10 @@ function makeGitRepos(workspace) {
7470
7867
 
7471
7868
  // src/tools/diff.ts
7472
7869
  init_esm_shims();
7473
- import * as fs11 from "fs";
7474
- import * as path12 from "path";
7870
+ import * as fs12 from "fs";
7871
+ import * as path14 from "path";
7475
7872
  function makeApplyPatch(workspace) {
7476
- const root = path12.resolve(workspace);
7873
+ const root = path14.resolve(workspace);
7477
7874
  return function apply_patch(args) {
7478
7875
  const patch = String(args.patch ?? "");
7479
7876
  if (!patch.trim()) return { error: "\u8865\u4E01\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A" };
@@ -7512,12 +7909,12 @@ function makeApplyPatch(workspace) {
7512
7909
  contentLines.push(current.slice(1));
7513
7910
  i++;
7514
7911
  }
7515
- if (fs11.existsSync(target)) {
7912
+ if (fs12.existsSync(target)) {
7516
7913
  return { error: `\u6587\u4EF6\u5DF2\u5B58\u5728: ${filePath}` };
7517
7914
  }
7518
- fs11.mkdirSync(path12.dirname(target), { recursive: true });
7519
- fs11.writeFileSync(target, contentLines.join("\n") + "\n", "utf-8");
7520
- const rel = path12.relative(resolved.root, target);
7915
+ fs12.mkdirSync(path14.dirname(target), { recursive: true });
7916
+ fs12.writeFileSync(target, contentLines.join("\n") + "\n", "utf-8");
7917
+ const rel = path14.relative(resolved.root, target);
7521
7918
  changedFiles.push(rel);
7522
7919
  addedFiles.push(rel);
7523
7920
  fileCount++;
@@ -7530,11 +7927,11 @@ function makeApplyPatch(workspace) {
7530
7927
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
7531
7928
  }
7532
7929
  const target = entry.real;
7533
- if (!fs11.existsSync(target)) {
7930
+ if (!fs12.existsSync(target)) {
7534
7931
  return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
7535
7932
  }
7536
- fs11.unlinkSync(target);
7537
- deletedFiles.push(path12.relative(entry.root, target));
7933
+ fs12.unlinkSync(target);
7934
+ deletedFiles.push(path14.relative(entry.root, target));
7538
7935
  fileCount++;
7539
7936
  i++;
7540
7937
  continue;
@@ -7570,10 +7967,10 @@ function makeApplyPatch(workspace) {
7570
7967
  if (!chunkLines.length) {
7571
7968
  return { error: `\u8865\u4E01\u66F4\u65B0\u6CA1\u6709 hunks: ${filePath}` };
7572
7969
  }
7573
- if (!fs11.existsSync(target)) {
7970
+ if (!fs12.existsSync(target)) {
7574
7971
  return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
7575
7972
  }
7576
- const oldLines = fs11.readFileSync(target, "utf-8").split("\n");
7973
+ const oldLines = fs12.readFileSync(target, "utf-8").split("\n");
7577
7974
  const newLines = applyPatchHunks(oldLines, chunkLines, filePath);
7578
7975
  let finalResolved = resolved;
7579
7976
  if (moveTo) {
@@ -7584,20 +7981,20 @@ function makeApplyPatch(workspace) {
7584
7981
  finalResolved = moved;
7585
7982
  }
7586
7983
  const finalTarget = finalResolved.real;
7587
- if (moveTo && fs11.existsSync(finalTarget) && finalTarget !== target) {
7984
+ if (moveTo && fs12.existsSync(finalTarget) && finalTarget !== target) {
7588
7985
  return { error: `\u6587\u4EF6\u5DF2\u5B58\u5728: ${moveTo}` };
7589
7986
  }
7590
- fs11.mkdirSync(path12.dirname(finalTarget), { recursive: true });
7591
- fs11.writeFileSync(finalTarget, newLines.join("\n"), "utf-8");
7987
+ fs12.mkdirSync(path14.dirname(finalTarget), { recursive: true });
7988
+ fs12.writeFileSync(finalTarget, newLines.join("\n"), "utf-8");
7592
7989
  if (moveTo && finalTarget !== target) {
7593
7990
  const entry = resolveEntryWithinRoot(root, filePath);
7594
7991
  if (!entry) {
7595
7992
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
7596
7993
  }
7597
- fs11.unlinkSync(entry.real);
7598
- deletedFiles.push(path12.relative(resolved.root, target));
7994
+ fs12.unlinkSync(entry.real);
7995
+ deletedFiles.push(path14.relative(resolved.root, target));
7599
7996
  }
7600
- changedFiles.push(path12.relative(finalResolved.root, finalTarget));
7997
+ changedFiles.push(path14.relative(finalResolved.root, finalTarget));
7601
7998
  fileCount++;
7602
7999
  continue;
7603
8000
  }
@@ -7609,18 +8006,18 @@ function makeApplyPatch(workspace) {
7609
8006
  return { changed_files: changedFiles, added_files: addedFiles, deleted_files: deletedFiles, file_count: fileCount };
7610
8007
  };
7611
8008
  }
7612
- function applyPatchHunks(oldLines, chunks, path42) {
8009
+ function applyPatchHunks(oldLines, chunks, path44) {
7613
8010
  let newLines = [...oldLines];
7614
8011
  let cursor = 0;
7615
8012
  for (const chunk of chunks) {
7616
8013
  const oldBlock = chunk.filter((l) => l[0] === " " || l[0] === "-").map((l) => l.slice(1));
7617
8014
  const newBlock = chunk.filter((l) => l[0] === " " || l[0] === "+").map((l) => l.slice(1));
7618
8015
  if (!oldBlock.length) {
7619
- throw new Error(`\u8865\u4E01 hunk \u5FC5\u987B\u5305\u542B\u4E0A\u4E0B\u6587\u6216\u5220\u9664\u884C: ${path42}`);
8016
+ throw new Error(`\u8865\u4E01 hunk \u5FC5\u987B\u5305\u542B\u4E0A\u4E0B\u6587\u6216\u5220\u9664\u884C: ${path44}`);
7620
8017
  }
7621
8018
  const matchStart = findBlockMatch(newLines, oldBlock, cursor);
7622
8019
  if (matchStart === -1) {
7623
- throw new Error(`\u8865\u4E01\u4E0A\u4E0B\u6587\u4E0D\u5339\u914D: ${path42}`);
8020
+ throw new Error(`\u8865\u4E01\u4E0A\u4E0B\u6587\u4E0D\u5339\u914D: ${path44}`);
7624
8021
  }
7625
8022
  const matchEnd = matchStart + oldBlock.length;
7626
8023
  newLines.splice(matchStart, oldBlock.length, ...newBlock);
@@ -7637,7 +8034,7 @@ function findBlockMatch(lines, block, start) {
7637
8034
  return -1;
7638
8035
  }
7639
8036
  function makeApplyUnifiedDiff(workspace) {
7640
- const root = path12.resolve(workspace);
8037
+ const root = path14.resolve(workspace);
7641
8038
  return function apply_unified_diff(args) {
7642
8039
  const diff = String(args.diff ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
7643
8040
  if (!diff.trim()) return { error: "diff \u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A" };
@@ -7656,26 +8053,26 @@ function makeApplyUnifiedDiff(workspace) {
7656
8053
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${targetPath}` };
7657
8054
  }
7658
8055
  const target = resolved.real;
7659
- const oldLines = oldIsDevNull ? [] : fs11.existsSync(target) ? fs11.readFileSync(target, "utf-8").split("\n") : [];
8056
+ const oldLines = oldIsDevNull ? [] : fs12.existsSync(target) ? fs12.readFileSync(target, "utf-8").split("\n") : [];
7660
8057
  const newLines = applyDiffHunks(oldLines, patch.hunks, targetPath);
7661
8058
  hunkCount += patch.hunks.length;
7662
8059
  if (newIsDevNull) {
7663
- if (fs11.existsSync(target)) {
7664
- if (!fs11.statSync(target).isFile()) {
8060
+ if (fs12.existsSync(target)) {
8061
+ if (!fs12.statSync(target).isFile()) {
7665
8062
  return { error: `\u8DEF\u5F84\u4E0D\u662F\u6587\u4EF6: ${targetPath}` };
7666
8063
  }
7667
8064
  const entry = resolveEntryWithinRoot(root, targetPath);
7668
8065
  if (!entry) {
7669
8066
  return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${targetPath}` };
7670
8067
  }
7671
- fs11.unlinkSync(entry.real);
8068
+ fs12.unlinkSync(entry.real);
7672
8069
  }
7673
- deletedFiles.push(path12.relative(resolved.root, target));
8070
+ deletedFiles.push(path14.relative(resolved.root, target));
7674
8071
  continue;
7675
8072
  }
7676
- fs11.mkdirSync(path12.dirname(target), { recursive: true });
7677
- fs11.writeFileSync(target, newLines.join("\n"), "utf-8");
7678
- const rel = path12.relative(resolved.root, target);
8073
+ fs12.mkdirSync(path14.dirname(target), { recursive: true });
8074
+ fs12.writeFileSync(target, newLines.join("\n"), "utf-8");
8075
+ const rel = path14.relative(resolved.root, target);
7679
8076
  changedFiles.push(rel);
7680
8077
  if (oldIsDevNull) addedFiles.push(rel);
7681
8078
  }
@@ -7939,7 +8336,8 @@ function buildExplorerEngine(opts) {
7939
8336
  model: opts.model,
7940
8337
  instructions: EXPLORER_INSTRUCTIONS,
7941
8338
  maxIterations: parseMaxIterations(process.env.EXPLORE_MAX_ITERATIONS, 30),
7942
- hook: opts.hook
8339
+ hook: opts.hook,
8340
+ toolResultDir: opts.toolResultDir
7943
8341
  });
7944
8342
  }
7945
8343
  function makeExploreTool(opts) {
@@ -7951,7 +8349,8 @@ function makeExploreTool(opts) {
7951
8349
  provider: opts.provider,
7952
8350
  model: opts.model,
7953
8351
  readonlyRegistrars: opts.readonlyRegistrars,
7954
- hook: opts.hookFactory?.()
8352
+ hook: opts.hookFactory?.(),
8353
+ toolResultDir: opts.toolResultDir
7955
8354
  });
7956
8355
  let report = "";
7957
8356
  let status = "unknown";
@@ -8012,14 +8411,14 @@ function registerExploreTool(registry, opts) {
8012
8411
 
8013
8412
  // src/tools/media.ts
8014
8413
  init_esm_shims();
8015
- import * as fs13 from "fs";
8016
- import * as path14 from "path";
8414
+ import * as fs14 from "fs";
8415
+ import * as path16 from "path";
8017
8416
 
8018
8417
  // src/tools/ffmpegRuntime.ts
8019
8418
  init_esm_shims();
8020
- import * as fs12 from "fs";
8021
- import * as os2 from "os";
8022
- import * as path13 from "path";
8419
+ import * as fs13 from "fs";
8420
+ import * as os3 from "os";
8421
+ import * as path15 from "path";
8023
8422
  import { createRequire as createRequire2 } from "module";
8024
8423
  import { createHash } from "crypto";
8025
8424
  import { pipeline } from "stream/promises";
@@ -8042,29 +8441,29 @@ if (PROXY_ENV2) {
8042
8441
  }
8043
8442
  var DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
8044
8443
  function agentBinDir(workspace) {
8045
- return path13.join(path13.resolve(workspace), ".agent", "bin");
8444
+ return path15.join(path15.resolve(workspace), ".agent", "bin");
8046
8445
  }
8047
8446
  function exeName(name, platform) {
8048
8447
  return platform === "win32" ? `${name}.exe` : name;
8049
8448
  }
8050
8449
  function defaultFileExists2(p) {
8051
8450
  try {
8052
- return fs12.statSync(p).isFile();
8451
+ return fs13.statSync(p).isFile();
8053
8452
  } catch {
8054
8453
  return false;
8055
8454
  }
8056
8455
  }
8057
8456
  function extractBundled(name, platform) {
8058
8457
  try {
8059
- const req2 = typeof __require === "function" ? __require : createRequire2(path13.join(process.cwd(), "__zqagent__.js"));
8458
+ const req2 = typeof __require === "function" ? __require : createRequire2(path15.join(process.cwd(), "__zqagent__.js"));
8060
8459
  const sea = req2("node:sea");
8061
8460
  if (!sea?.isSea?.() || typeof sea.getAsset !== "function") return null;
8062
8461
  const asset = sea.getAsset(name);
8063
8462
  if (!asset) return null;
8064
- const dir = fs12.mkdtempSync(path13.join(os2.tmpdir(), `zqagent-${name}-`));
8065
- const file = path13.join(dir, exeName(name, platform));
8066
- fs12.writeFileSync(file, Buffer.from(asset));
8067
- fs12.chmodSync(file, 493);
8463
+ const dir = fs13.mkdtempSync(path15.join(os3.tmpdir(), `zqagent-${name}-`));
8464
+ const file = path15.join(dir, exeName(name, platform));
8465
+ fs13.writeFileSync(file, Buffer.from(asset));
8466
+ fs13.chmodSync(file, 493);
8068
8467
  return file;
8069
8468
  } catch {
8070
8469
  return null;
@@ -8074,7 +8473,7 @@ function resolveOne(name, workspace, o) {
8074
8473
  const envKey = name === "ffmpeg" ? ENV_FFMPEG : ENV_FFPROBE;
8075
8474
  const explicit = (o.env[envKey] ?? "").trim();
8076
8475
  if (explicit && o.fileExists(explicit)) return { path: explicit, source: "env" };
8077
- const local = path13.join(agentBinDir(workspace), exeName(name, o.platform));
8476
+ const local = path15.join(agentBinDir(workspace), exeName(name, o.platform));
8078
8477
  if (o.fileExists(local)) return { path: local, source: "agent-bin" };
8079
8478
  const bundled = extractBundled(name, o.platform);
8080
8479
  if (bundled) return { path: bundled, source: "bundled" };
@@ -8088,7 +8487,7 @@ function resolveOne(name, workspace, o) {
8088
8487
  var cache = /* @__PURE__ */ new Map();
8089
8488
  function resolveMedia(workspace, opts = {}) {
8090
8489
  const platform = opts.platform ?? process.platform;
8091
- const key = `${platform}:${path13.resolve(workspace)}`;
8490
+ const key = `${platform}:${path15.resolve(workspace)}`;
8092
8491
  const hit = cache.get(key);
8093
8492
  if (hit) return hit;
8094
8493
  const ctx = {
@@ -8194,7 +8593,7 @@ function parseChecksumFile(text, filename) {
8194
8593
  if (!m) continue;
8195
8594
  const hash = m[1].toLowerCase();
8196
8595
  const name = m[2]?.trim() ?? "";
8197
- if (!name || name === filename || path13.basename(name) === filename) return hash;
8596
+ if (!name || name === filename || path15.basename(name) === filename) return hash;
8198
8597
  }
8199
8598
  const single = lines[0]?.trim();
8200
8599
  if (lines.length === 1 && single && /^[0-9a-f]{32,128}$/i.test(single)) {
@@ -8204,7 +8603,7 @@ function parseChecksumFile(text, filename) {
8204
8603
  }
8205
8604
  async function computeChecksum(file, algo) {
8206
8605
  const hash = createHash(algo);
8207
- await pipeline(fs12.createReadStream(file), hash);
8606
+ await pipeline(fs13.createReadStream(file), hash);
8208
8607
  return hash.digest("hex");
8209
8608
  }
8210
8609
  function fetchInit() {
@@ -8219,7 +8618,7 @@ async function downloadToFile(url, dest) {
8219
8618
  const resp = await nodeFetch3(url, fetchInit());
8220
8619
  if (!resp.ok) throw new Error(`HTTP ${resp.status} ${resp.statusText}`);
8221
8620
  if (!resp.body) throw new Error("\u54CD\u5E94\u6CA1\u6709\u5185\u5BB9");
8222
- await pipeline(resp.body, fs12.createWriteStream(dest));
8621
+ await pipeline(resp.body, fs13.createWriteStream(dest));
8223
8622
  }
8224
8623
  async function resolveComponentUrl(c) {
8225
8624
  if (c.url) return c.url;
@@ -8236,12 +8635,12 @@ function findBinaryInTree(dir, names) {
8236
8635
  const cur = stack.pop();
8237
8636
  let entries;
8238
8637
  try {
8239
- entries = fs12.readdirSync(cur, { withFileTypes: true });
8638
+ entries = fs13.readdirSync(cur, { withFileTypes: true });
8240
8639
  } catch {
8241
8640
  continue;
8242
8641
  }
8243
8642
  for (const e of entries) {
8244
- const full = path13.join(cur, e.name);
8643
+ const full = path15.join(cur, e.name);
8245
8644
  if (e.isDirectory()) {
8246
8645
  stack.push(full);
8247
8646
  } else if (names.includes(e.name)) {
@@ -8252,7 +8651,7 @@ function findBinaryInTree(dir, names) {
8252
8651
  return null;
8253
8652
  }
8254
8653
  async function extractArchive(archive, dest, kind, platform) {
8255
- fs12.mkdirSync(dest, { recursive: true });
8654
+ fs13.mkdirSync(dest, { recursive: true });
8256
8655
  let res;
8257
8656
  if (kind === "tar.xz") {
8258
8657
  res = await execAsync("tar", ["-xJf", archive, "-C", dest]);
@@ -8288,7 +8687,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
8288
8687
  error: `\u6682\u4E0D\u652F\u6301\u81EA\u52A8\u4E0B\u8F7D\u7684\u5E73\u53F0: ${platform}/${arch}`
8289
8688
  };
8290
8689
  }
8291
- const tmp = fs12.mkdtempSync(path13.join(os2.tmpdir(), "zqagent-ffmpeg-"));
8690
+ const tmp = fs13.mkdtempSync(path15.join(os3.tmpdir(), "zqagent-ffmpeg-"));
8292
8691
  const binDir = agentBinDir(workspace);
8293
8692
  const installed = {};
8294
8693
  try {
@@ -8302,7 +8701,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
8302
8701
  }
8303
8702
  let archive = fetched.get(url);
8304
8703
  if (!archive) {
8305
- const archivePath = path13.join(tmp, path13.basename(new URL(url).pathname) || `${c.name}.${c.archive}`);
8704
+ const archivePath = path15.join(tmp, path15.basename(new URL(url).pathname) || `${c.name}.${c.archive}`);
8306
8705
  log(`\u4E0B\u8F7D ${url}`);
8307
8706
  try {
8308
8707
  await downloadToFile(url, archivePath);
@@ -8312,7 +8711,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
8312
8711
  if (c.checksumUrl) {
8313
8712
  const algo = c.checksumAlgo ?? "sha256";
8314
8713
  try {
8315
- const expected = parseChecksumFile(await fetchText(c.checksumUrl), path13.basename(archivePath));
8714
+ const expected = parseChecksumFile(await fetchText(c.checksumUrl), path15.basename(archivePath));
8316
8715
  if (!expected) {
8317
8716
  notes.push(`\u672A\u80FD\u4ECE ${c.checksumUrl} \u89E3\u6790\u51FA\u6821\u9A8C\u548C\uFF0C\u5DF2\u8DF3\u8FC7\u6821\u9A8C`);
8318
8717
  } else {
@@ -8329,24 +8728,24 @@ async function downloadFfmpeg(workspace, opts = {}) {
8329
8728
  notes.push(`\u6821\u9A8C\u548C\u83B7\u53D6\u5931\u8D25 (${c.checksumUrl}): ${String(e instanceof Error ? e.message : e)}`);
8330
8729
  }
8331
8730
  } else {
8332
- notes.push(`${path13.basename(archivePath)} \u672A\u63D0\u4F9B\u6821\u9A8C\u548C\uFF08\u4EC5 HTTPS \u4F20\u8F93\u4FDD\u62A4\uFF09`);
8731
+ notes.push(`${path15.basename(archivePath)} \u672A\u63D0\u4F9B\u6821\u9A8C\u548C\uFF08\u4EC5 HTTPS \u4F20\u8F93\u4FDD\u62A4\uFF09`);
8333
8732
  }
8334
8733
  archive = { file: archivePath, kind: c.archive };
8335
8734
  fetched.set(url, archive);
8336
8735
  }
8337
- const extractDir = fs12.mkdtempSync(path13.join(tmp, "x-"));
8736
+ const extractDir = fs13.mkdtempSync(path15.join(tmp, "x-"));
8338
8737
  const err = await extractArchive(archive.file, extractDir, archive.kind, platform);
8339
8738
  if (err) return { ok: false, error: err };
8340
8739
  const found = findBinaryInTree(extractDir, [exeName(c.name, platform), c.name]);
8341
8740
  if (!found) {
8342
- return { ok: false, error: `\u5F52\u6863\u5185\u672A\u627E\u5230 ${c.name}: ${path13.basename(archive.file)}` };
8741
+ return { ok: false, error: `\u5F52\u6863\u5185\u672A\u627E\u5230 ${c.name}: ${path15.basename(archive.file)}` };
8343
8742
  }
8344
- fs12.mkdirSync(binDir, { recursive: true });
8345
- const target = path13.join(binDir, exeName(c.name, platform));
8743
+ fs13.mkdirSync(binDir, { recursive: true });
8744
+ const target = path15.join(binDir, exeName(c.name, platform));
8346
8745
  const staging = `${target}.tmp-${process.pid}`;
8347
- fs12.copyFileSync(found, staging);
8348
- fs12.chmodSync(staging, 493);
8349
- fs12.renameSync(staging, target);
8746
+ fs13.copyFileSync(found, staging);
8747
+ fs13.chmodSync(staging, 493);
8748
+ fs13.renameSync(staging, target);
8350
8749
  installed[c.name] = target;
8351
8750
  }
8352
8751
  resetFfmpegCache();
@@ -8355,7 +8754,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
8355
8754
  } catch (e) {
8356
8755
  return { ok: false, error: `\u5B89\u88C5\u5931\u8D25: ${String(e instanceof Error ? e.message : e)}` };
8357
8756
  } finally {
8358
- fs12.rmSync(tmp, { recursive: true, force: true });
8757
+ fs13.rmSync(tmp, { recursive: true, force: true });
8359
8758
  }
8360
8759
  }
8361
8760
  function canAutoDownload(platform, arch) {
@@ -8620,13 +9019,13 @@ function resolveTarget(workspace, value) {
8620
9019
  return { abs: resolved.real, root: resolved.root };
8621
9020
  }
8622
9021
  function relOut(workspace, abs) {
8623
- return path14.relative(workspace, abs) || abs;
9022
+ return path16.relative(workspace, abs) || abs;
8624
9023
  }
8625
9024
  function registerMediaTool(registry, workspace, opts = {}) {
8626
9025
  const platform = opts.platform ?? process.platform;
8627
9026
  const arch = process.arch;
8628
9027
  const media = async (args) => {
8629
- const root = path14.resolve(workspace);
9028
+ const root = path16.resolve(workspace);
8630
9029
  const op = str(args["op"]);
8631
9030
  if (!op) {
8632
9031
  return { error: `\u7F3A\u5C11\u53C2\u6570 op\u3002\u53EF\u9009\u503C\uFF1A${OPS.join(" / ")}` };
@@ -8671,7 +9070,7 @@ function registerMediaTool(registry, workspace, opts = {}) {
8671
9070
  if (!input) return { error: `\u64CD\u4F5C ${op} \u9700\u8981\u53C2\u6570 input\uFF08\u5DE5\u4F5C\u533A\u5185\u76F8\u5BF9\u8DEF\u5F84\uFF09` };
8672
9071
  const r = resolveTarget(root, input);
8673
9072
  if ("error" in r) return { error: r.error };
8674
- if (!fs13.existsSync(r.abs) || !fs13.statSync(r.abs).isFile()) {
9073
+ if (!fs14.existsSync(r.abs) || !fs14.statSync(r.abs).isFile()) {
8675
9074
  return { error: `\u8F93\u5165\u6587\u4EF6\u4E0D\u5B58\u5728: ${input}` };
8676
9075
  }
8677
9076
  inputAbs = r.abs;
@@ -8683,7 +9082,7 @@ function registerMediaTool(registry, workspace, opts = {}) {
8683
9082
  if (!output) return { error: `\u64CD\u4F5C ${op} \u9700\u8981\u53C2\u6570 output\uFF08\u5DE5\u4F5C\u533A\u5185\u76F8\u5BF9\u8DEF\u5F84\uFF09` };
8684
9083
  const r = resolveTarget(root, output);
8685
9084
  if ("error" in r) return { error: r.error };
8686
- fs13.mkdirSync(path14.dirname(r.abs), { recursive: true });
9085
+ fs14.mkdirSync(path16.dirname(r.abs), { recursive: true });
8687
9086
  outputAbs = r.abs;
8688
9087
  relRoot = r.root;
8689
9088
  }
@@ -8733,7 +9132,7 @@ function registerMediaTool(registry, workspace, opts = {}) {
8733
9132
  };
8734
9133
  if (outputAbs) {
8735
9134
  try {
8736
- result["size_bytes"] = fs13.statSync(outputAbs).size;
9135
+ result["size_bytes"] = fs14.statSync(outputAbs).size;
8737
9136
  } catch {
8738
9137
  }
8739
9138
  }
@@ -8976,7 +9375,9 @@ function createDefaultEngine(opts) {
8976
9375
  subagentHookFactory,
8977
9376
  contextWindow,
8978
9377
  thresholdPct,
9378
+ softThresholdPct,
8979
9379
  capTokens,
9380
+ toolResultDir: toolResultDir2,
8980
9381
  logDir
8981
9382
  } = opts;
8982
9383
  const envEffort = process.env.OPENAI_REASONING_EFFORT?.toLowerCase();
@@ -8999,7 +9400,8 @@ function createDefaultEngine(opts) {
8999
9400
  workspace,
9000
9401
  provider: effectiveProvider,
9001
9402
  model,
9002
- hookFactory: subagentHookFactory
9403
+ hookFactory: subagentHookFactory,
9404
+ toolResultDir: toolResultDir2
9003
9405
  });
9004
9406
  const permissions = new PermissionEngine({
9005
9407
  workspaceRoot: workspace,
@@ -9021,7 +9423,9 @@ function createDefaultEngine(opts) {
9021
9423
  hook,
9022
9424
  contextWindow,
9023
9425
  thresholdPct,
9024
- capTokens
9426
+ softThresholdPct,
9427
+ capTokens,
9428
+ toolResultDir: toolResultDir2
9025
9429
  });
9026
9430
  return engine;
9027
9431
  }
@@ -9031,8 +9435,8 @@ init_esm_shims();
9031
9435
 
9032
9436
  // src/llmConfig.ts
9033
9437
  init_esm_shims();
9034
- import * as fs15 from "fs";
9035
- import * as path17 from "path";
9438
+ import * as fs16 from "fs";
9439
+ import * as path18 from "path";
9036
9440
  import * as crypto3 from "crypto";
9037
9441
 
9038
9442
  // src/crypto/secrets.ts
@@ -9041,8 +9445,8 @@ init_esm_shims();
9041
9445
  // ../shared/src/secrets.ts
9042
9446
  init_esm_shims();
9043
9447
  import * as crypto2 from "crypto";
9044
- import * as fs14 from "fs";
9045
- import * as path15 from "path";
9448
+ import * as fs15 from "fs";
9449
+ import * as path17 from "path";
9046
9450
  function encryptWithKey(key, plain) {
9047
9451
  const iv = crypto2.randomBytes(12);
9048
9452
  const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
@@ -9073,16 +9477,16 @@ function decryptWithKey(key, payload) {
9073
9477
  }
9074
9478
  }
9075
9479
  function loadOrCreateMasterKey(masterKeyFile) {
9076
- if (fs14.existsSync(masterKeyFile)) {
9077
- const hex = fs14.readFileSync(masterKeyFile, "utf8").trim();
9480
+ if (fs15.existsSync(masterKeyFile)) {
9481
+ const hex = fs15.readFileSync(masterKeyFile, "utf8").trim();
9078
9482
  if (/^[0-9a-f]{64}$/i.test(hex)) {
9079
9483
  return Buffer.from(hex, "hex");
9080
9484
  }
9081
9485
  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`);
9082
9486
  }
9083
9487
  const key = crypto2.randomBytes(32);
9084
- fs14.mkdirSync(path15.dirname(masterKeyFile), { recursive: true, mode: 448 });
9085
- fs14.writeFileSync(masterKeyFile, key.toString("hex"), {
9488
+ fs15.mkdirSync(path17.dirname(masterKeyFile), { recursive: true, mode: 448 });
9489
+ fs15.writeFileSync(masterKeyFile, key.toString("hex"), {
9086
9490
  mode: 384,
9087
9491
  encoding: "utf8"
9088
9492
  });
@@ -9300,26 +9704,6 @@ async function probeModel(baseURL, apiKey, model) {
9300
9704
  }
9301
9705
  }
9302
9706
 
9303
- // src/paths.ts
9304
- init_esm_shims();
9305
- import * as os3 from "os";
9306
- import * as path16 from "path";
9307
- var APP_DIR_NAME = "zqagent";
9308
- var DEFAULT_DATA_DIR = ".zqagent_data";
9309
- var DEFAULT_HUB_URL = "https://hub.cd-zq.com";
9310
- var IS_WINDOWS = process.platform === "win32";
9311
- function homeDir() {
9312
- return process.env.HOME || process.env.USERPROFILE || os3.homedir() || os3.tmpdir();
9313
- }
9314
- function configDir() {
9315
- const base = process.env.XDG_CONFIG_HOME || (IS_WINDOWS ? process.env.APPDATA : void 0) || path16.join(homeDir(), ".config");
9316
- return path16.join(base, APP_DIR_NAME);
9317
- }
9318
- function stateDir() {
9319
- const base = process.env.XDG_STATE_HOME || (IS_WINDOWS ? process.env.LOCALAPPDATA : void 0) || path16.join(homeDir(), ".local", "state");
9320
- return path16.join(base, APP_DIR_NAME);
9321
- }
9322
-
9323
9707
  // ../shared/src/llm-presets.ts
9324
9708
  init_esm_shims();
9325
9709
  var LLM_PRESETS = [
@@ -9384,11 +9768,11 @@ function llmPreset(id) {
9384
9768
  function llmConfigFile() {
9385
9769
  const override = process.env.ZQAGENT_LLM_CONFIG_FILE?.trim();
9386
9770
  if (override) return override;
9387
- return path17.join(configDir(), "connections.json");
9771
+ return path18.join(configDir(), "connections.json");
9388
9772
  }
9389
9773
  var keyCache = /* @__PURE__ */ new Map();
9390
9774
  function masterKeyFileFor(file) {
9391
- return path17.join(path17.dirname(file), "secret.key");
9775
+ return path18.join(path18.dirname(file), "secret.key");
9392
9776
  }
9393
9777
  function getMasterKey(file) {
9394
9778
  const keyFile = masterKeyFileFor(file);
@@ -9493,10 +9877,10 @@ function normalize(f) {
9493
9877
  return { ...f, providers, activeId };
9494
9878
  }
9495
9879
  function readRaw(file, keyFile = file) {
9496
- if (!fs15.existsSync(file)) return null;
9880
+ if (!fs16.existsSync(file)) return null;
9497
9881
  let raw;
9498
9882
  try {
9499
- raw = JSON.parse(fs15.readFileSync(file, "utf8"));
9883
+ raw = JSON.parse(fs16.readFileSync(file, "utf8"));
9500
9884
  } catch {
9501
9885
  return null;
9502
9886
  }
@@ -9544,19 +9928,19 @@ function readLlmConfig(opts = {}) {
9544
9928
  f = r.config;
9545
9929
  changed = changed || r.changed;
9546
9930
  }
9547
- if (changed || !fs15.existsSync(file)) writeLlmConfig(f, file);
9931
+ if (changed || !fs16.existsSync(file)) writeLlmConfig(f, file);
9548
9932
  return f;
9549
9933
  }
9550
9934
  function writeLlmConfig(f, file = llmConfigFile()) {
9551
9935
  const next = normalize(f);
9552
- fs15.mkdirSync(path17.dirname(file), { recursive: true, mode: 448 });
9553
- fs15.writeFileSync(file, JSON.stringify(next, null, 2) + "\n", {
9936
+ fs16.mkdirSync(path18.dirname(file), { recursive: true, mode: 448 });
9937
+ fs16.writeFileSync(file, JSON.stringify(next, null, 2) + "\n", {
9554
9938
  mode: 384,
9555
9939
  encoding: "utf8"
9556
9940
  });
9557
9941
  }
9558
9942
  function commonLegacyFiles() {
9559
- return [path17.join(stateDir(), "data", "node-llm.json")];
9943
+ return [path18.join(stateDir(), "data", "node-llm.json")];
9560
9944
  }
9561
9945
 
9562
9946
  // src/cli/connection.ts
@@ -9989,17 +10373,17 @@ function renderBanner(opts = {}) {
9989
10373
  // src/cli/terminalSetup.ts
9990
10374
  init_esm_shims();
9991
10375
  import { existsSync as existsSync13 } from "fs";
9992
- import * as path18 from "path";
10376
+ import * as path19 from "path";
9993
10377
  var TERM_SETUP_ENV = "ZQAGENT_TERM_SETUP";
9994
10378
  var FALSE_FLAGS = ["0", "false", "no", "off"];
9995
10379
  var TRUE_FLAGS = ["1", "true", "yes", "on"];
9996
10380
  function hasWindowsTerminal(env3 = process.env, fileExists = existsSync13) {
9997
10381
  const raw = env3.PATH ?? env3.Path ?? "";
9998
- const dirs = raw.split(path18.delimiter).filter(Boolean);
10382
+ const dirs = raw.split(path19.delimiter).filter(Boolean);
9999
10383
  if (env3.LOCALAPPDATA) {
10000
- dirs.push(path18.join(env3.LOCALAPPDATA, "Microsoft", "WindowsApps"));
10384
+ dirs.push(path19.join(env3.LOCALAPPDATA, "Microsoft", "WindowsApps"));
10001
10385
  }
10002
- return dirs.some((dir) => fileExists(path18.join(dir, "wt.exe")));
10386
+ return dirs.some((dir) => fileExists(path19.join(dir, "wt.exe")));
10003
10387
  }
10004
10388
  function detectTerminalSetup(input = {}) {
10005
10389
  const env3 = input.env ?? process.env;
@@ -10080,13 +10464,13 @@ function renderTerminalTutorial() {
10080
10464
 
10081
10465
  // src/cli/link.ts
10082
10466
  init_esm_shims();
10083
- import * as path21 from "path";
10467
+ import * as path22 from "path";
10084
10468
 
10085
10469
  // src/server/store.ts
10086
10470
  init_esm_shims();
10087
- import * as fs16 from "fs";
10471
+ import * as fs17 from "fs";
10088
10472
  import * as fsp from "fs/promises";
10089
- import * as path19 from "path";
10473
+ import * as path20 from "path";
10090
10474
  import { randomUUID } from "crypto";
10091
10475
 
10092
10476
  // src/server/dbgTrace.ts
@@ -10140,57 +10524,57 @@ var Store = class {
10140
10524
  sessionCache = /* @__PURE__ */ new Map();
10141
10525
  // ===================== 租户 =====================
10142
10526
  tenantDir(tenantId) {
10143
- return path19.join(this.root, "tenants", tenantId);
10527
+ return path20.join(this.root, "tenants", tenantId);
10144
10528
  }
10145
10529
  listTenants() {
10146
- const dir = path19.join(this.root, "tenants");
10530
+ const dir = path20.join(this.root, "tenants");
10147
10531
  try {
10148
- return fs16.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
10532
+ return fs17.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
10149
10533
  } catch {
10150
10534
  return [];
10151
10535
  }
10152
10536
  }
10153
10537
  loadTenant(tenantId) {
10154
- return this._readJson(path19.join(this.tenantDir(tenantId), "config.json"));
10538
+ return this._readJson(path20.join(this.tenantDir(tenantId), "config.json"));
10155
10539
  }
10156
10540
  saveTenant(config) {
10157
10541
  const dir = this.tenantDir(config.id);
10158
- fs16.mkdirSync(dir, { recursive: true });
10159
- fs16.writeFileSync(path19.join(dir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
10542
+ fs17.mkdirSync(dir, { recursive: true });
10543
+ fs17.writeFileSync(path20.join(dir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
10160
10544
  }
10161
10545
  deleteTenant(tenantId) {
10162
- fs16.rmSync(this.tenantDir(tenantId), { recursive: true, force: true });
10546
+ fs17.rmSync(this.tenantDir(tenantId), { recursive: true, force: true });
10163
10547
  }
10164
10548
  // ===================== 会话 =====================
10165
10549
  workspaceDir(tenantId, sessionId) {
10166
- return path19.join(this.tenantDir(tenantId), "workspaces", sessionId);
10550
+ return path20.join(this.tenantDir(tenantId), "workspaces", sessionId);
10167
10551
  }
10168
10552
  /** 创建(或复用)会话工作区沙箱目录 */
10169
10553
  ensureWorkspace(tenantId, sessionId) {
10170
10554
  const dir = this.workspaceDir(tenantId, sessionId);
10171
- fs16.mkdirSync(dir, { recursive: true });
10555
+ fs17.mkdirSync(dir, { recursive: true });
10172
10556
  return dir;
10173
10557
  }
10174
10558
  sessionDir(tenantId, sessionId) {
10175
- return path19.join(this.tenantDir(tenantId), "sessions", sessionId);
10559
+ return path20.join(this.tenantDir(tenantId), "sessions", sessionId);
10176
10560
  }
10177
10561
  listSessions(tenantId) {
10178
- const dir = path19.join(this.tenantDir(tenantId), "sessions");
10562
+ const dir = path20.join(this.tenantDir(tenantId), "sessions");
10179
10563
  try {
10180
- return fs16.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
10564
+ return fs17.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
10181
10565
  } catch {
10182
10566
  return [];
10183
10567
  }
10184
10568
  }
10185
10569
  loadSession(tenantId, sessionId) {
10186
- const p = path19.join(this.sessionDir(tenantId, sessionId), "session.json");
10570
+ const p = path20.join(this.sessionDir(tenantId, sessionId), "session.json");
10187
10571
  try {
10188
- const st = fs16.statSync(p);
10572
+ const st = fs17.statSync(p);
10189
10573
  const key = `${st.mtimeMs}:${st.size}`;
10190
10574
  const hit = this.sessionCache.get(p);
10191
10575
  if (hit && hit.key === key) return hit.rec;
10192
10576
  const __t0 = dbgNow();
10193
- const rec = JSON.parse(fs16.readFileSync(p, "utf-8"));
10577
+ const rec = JSON.parse(fs17.readFileSync(p, "utf-8"));
10194
10578
  dbgMs("store.loadSession(\u89E3\u6790)", __t0, `bytes=${st.size}`);
10195
10579
  this.sessionCache.set(p, { key, rec });
10196
10580
  if (this.sessionCache.size > 300) {
@@ -10206,8 +10590,8 @@ var Store = class {
10206
10590
  saveSession(record) {
10207
10591
  record.savedAt = (/* @__PURE__ */ new Date()).toISOString();
10208
10592
  const dir = this.sessionDir(record.tenantId, record.sessionId);
10209
- fs16.mkdirSync(dir, { recursive: true });
10210
- const file = path19.join(dir, "session.json");
10593
+ fs17.mkdirSync(dir, { recursive: true });
10594
+ const file = path20.join(dir, "session.json");
10211
10595
  const snapshot = JSON.stringify(record, null, 2);
10212
10596
  const tmp = `${file}.${randomUUID()}.tmp`;
10213
10597
  const prev = this.writeChains.get(file) ?? Promise.resolve();
@@ -10222,34 +10606,34 @@ var Store = class {
10222
10606
  saveSessionSync(record) {
10223
10607
  record.savedAt = (/* @__PURE__ */ new Date()).toISOString();
10224
10608
  const dir = this.sessionDir(record.tenantId, record.sessionId);
10225
- fs16.mkdirSync(dir, { recursive: true });
10226
- fs16.writeFileSync(
10227
- path19.join(dir, "session.json"),
10609
+ fs17.mkdirSync(dir, { recursive: true });
10610
+ fs17.writeFileSync(
10611
+ path20.join(dir, "session.json"),
10228
10612
  JSON.stringify(record, null, 2),
10229
10613
  "utf-8"
10230
10614
  );
10231
10615
  }
10232
10616
  deleteSession(tenantId, sessionId) {
10233
- this.sessionCache.delete(path19.join(this.sessionDir(tenantId, sessionId), "session.json"));
10234
- fs16.rmSync(this.sessionDir(tenantId, sessionId), { recursive: true, force: true });
10235
- fs16.rmSync(this.workspaceDir(tenantId, sessionId), { recursive: true, force: true });
10617
+ this.sessionCache.delete(path20.join(this.sessionDir(tenantId, sessionId), "session.json"));
10618
+ fs17.rmSync(this.sessionDir(tenantId, sessionId), { recursive: true, force: true });
10619
+ fs17.rmSync(this.workspaceDir(tenantId, sessionId), { recursive: true, force: true });
10236
10620
  }
10237
10621
  // ===================== 事件日志 =====================
10238
10622
  appendEvent(tenantId, sessionId, entry) {
10239
10623
  const dir = this.sessionDir(tenantId, sessionId);
10240
- fs16.mkdirSync(dir, { recursive: true });
10624
+ fs17.mkdirSync(dir, { recursive: true });
10241
10625
  return fsp.appendFile(
10242
- path19.join(dir, "events.jsonl"),
10626
+ path20.join(dir, "events.jsonl"),
10243
10627
  JSON.stringify(entry) + "\n",
10244
10628
  "utf-8"
10245
10629
  ).catch(() => {
10246
10630
  });
10247
10631
  }
10248
10632
  loadEvents(tenantId, sessionId) {
10249
- const p = path19.join(this.sessionDir(tenantId, sessionId), "events.jsonl");
10633
+ const p = path20.join(this.sessionDir(tenantId, sessionId), "events.jsonl");
10250
10634
  const __t0 = dbgNow();
10251
10635
  try {
10252
- const raw = fs16.readFileSync(p, "utf-8");
10636
+ const raw = fs17.readFileSync(p, "utf-8");
10253
10637
  const out = raw.trim().split("\n").filter(Boolean).map((l) => JSON.parse(l));
10254
10638
  dbgMs("store.loadEvents(\u6574\u6587\u4EF6)", __t0, `bytes=${raw.length} evts=${out.length}`);
10255
10639
  return out;
@@ -10267,15 +10651,15 @@ var Store = class {
10267
10651
  * 窗口内一条完整记录都没有时回退为整文件读取。
10268
10652
  */
10269
10653
  loadEventsTail(tenantId, sessionId, maxBytes = 1024 * 1024) {
10270
- const p = path19.join(this.sessionDir(tenantId, sessionId), "events.jsonl");
10654
+ const p = path20.join(this.sessionDir(tenantId, sessionId), "events.jsonl");
10271
10655
  const __t0 = dbgNow();
10272
10656
  let fd = null;
10273
10657
  try {
10274
- const size = fs16.statSync(p).size;
10658
+ const size = fs17.statSync(p).size;
10275
10659
  if (size <= maxBytes) return this.loadEvents(tenantId, sessionId);
10276
- fd = fs16.openSync(p, "r");
10660
+ fd = fs17.openSync(p, "r");
10277
10661
  const buf = Buffer.allocUnsafe(maxBytes);
10278
- const read2 = fs16.readSync(fd, buf, 0, maxBytes, size - maxBytes);
10662
+ const read2 = fs17.readSync(fd, buf, 0, maxBytes, size - maxBytes);
10279
10663
  let text = buf.subarray(0, read2).toString("utf-8");
10280
10664
  const nl = text.indexOf("\n");
10281
10665
  if (nl < 0) return this.loadEvents(tenantId, sessionId);
@@ -10300,7 +10684,7 @@ var Store = class {
10300
10684
  } finally {
10301
10685
  if (fd !== null) {
10302
10686
  try {
10303
- fs16.closeSync(fd);
10687
+ fs17.closeSync(fd);
10304
10688
  } catch {
10305
10689
  }
10306
10690
  }
@@ -10309,26 +10693,26 @@ var Store = class {
10309
10693
  // ===================== 节点身份 & 管理员密钥 =====================
10310
10694
  /** 节点稳定标识:{root}/node.json(首次启动生成后长期固定) */
10311
10695
  loadNodeId() {
10312
- const v = this._readJson(path19.join(this.root, "node.json"));
10696
+ const v = this._readJson(path20.join(this.root, "node.json"));
10313
10697
  return v?.id && typeof v.id === "string" ? v.id : null;
10314
10698
  }
10315
10699
  saveNodeId(id) {
10316
- fs16.mkdirSync(this.root, { recursive: true });
10317
- const file = path19.join(this.root, "node.json");
10700
+ fs17.mkdirSync(this.root, { recursive: true });
10701
+ const file = path20.join(this.root, "node.json");
10318
10702
  const cur = this._readJson(file) ?? {};
10319
- fs16.writeFileSync(file, JSON.stringify({ ...cur, id }, null, 2), "utf-8");
10703
+ fs17.writeFileSync(file, JSON.stringify({ ...cur, id }, null, 2), "utf-8");
10320
10704
  }
10321
10705
  /** 节点展示名:{root}/node.json 的 label(Hub 下发或本地设置) */
10322
10706
  loadNodeLabel() {
10323
- const v = this._readJson(path19.join(this.root, "node.json"));
10707
+ const v = this._readJson(path20.join(this.root, "node.json"));
10324
10708
  return v?.label && typeof v.label === "string" ? v.label : null;
10325
10709
  }
10326
10710
  /** 写入/更新节点展示名(保留 id 等既有字段) */
10327
10711
  saveNodeLabel(label) {
10328
- fs16.mkdirSync(this.root, { recursive: true });
10329
- const file = path19.join(this.root, "node.json");
10712
+ fs17.mkdirSync(this.root, { recursive: true });
10713
+ const file = path20.join(this.root, "node.json");
10330
10714
  const cur = this._readJson(file) ?? {};
10331
- fs16.writeFileSync(file, JSON.stringify({ ...cur, label }, null, 2), "utf-8");
10715
+ fs17.writeFileSync(file, JSON.stringify({ ...cur, label }, null, 2), "utf-8");
10332
10716
  }
10333
10717
  /**
10334
10718
  * 节点级 LLM 配置:统一存放于本机 `~/.config/zqagent/connections.json`
@@ -10339,7 +10723,7 @@ var Store = class {
10339
10723
  const file = this.llmFile();
10340
10724
  const cfg = readLlmConfig({
10341
10725
  file,
10342
- legacyFiles: [path19.join(this.root, "node-llm.json")]
10726
+ legacyFiles: [path20.join(this.root, "node-llm.json")]
10343
10727
  });
10344
10728
  return {
10345
10729
  providers: cfg.providers.map((p) => ({
@@ -10361,7 +10745,7 @@ var Store = class {
10361
10745
  const file = this.llmFile();
10362
10746
  const cur = readLlmConfig({
10363
10747
  file,
10364
- legacyFiles: [path19.join(this.root, "node-llm.json")]
10748
+ legacyFiles: [path20.join(this.root, "node-llm.json")]
10365
10749
  });
10366
10750
  const old = new Map(cur.providers.map((p) => [p.id, p]));
10367
10751
  const providers = cfg.providers.map((p) => {
@@ -10393,13 +10777,13 @@ var Store = class {
10393
10777
  }
10394
10778
  /** 管理员密钥:{root}/admin.json(可跨项目操作;桌面外壳注入) */
10395
10779
  loadAdminKey() {
10396
- const v = this._readJson(path19.join(this.root, "admin.json"));
10780
+ const v = this._readJson(path20.join(this.root, "admin.json"));
10397
10781
  return v?.apiKey && typeof v.apiKey === "string" ? v.apiKey : null;
10398
10782
  }
10399
10783
  saveAdminKey(apiKey) {
10400
- fs16.mkdirSync(this.root, { recursive: true });
10401
- fs16.writeFileSync(
10402
- path19.join(this.root, "admin.json"),
10784
+ fs17.mkdirSync(this.root, { recursive: true });
10785
+ fs17.writeFileSync(
10786
+ path20.join(this.root, "admin.json"),
10403
10787
  JSON.stringify({ apiKey }, null, 2),
10404
10788
  "utf-8"
10405
10789
  );
@@ -10409,18 +10793,18 @@ var Store = class {
10409
10793
  * 节点凭注册码接入后写入;启动时优先于环境变量读取。
10410
10794
  */
10411
10795
  loadFleetCredential() {
10412
- return this._readJson(path19.join(this.root, "fleet.json"));
10796
+ return this._readJson(path20.join(this.root, "fleet.json"));
10413
10797
  }
10414
10798
  saveFleetCredential(cred) {
10415
- fs16.mkdirSync(this.root, { recursive: true });
10416
- fs16.writeFileSync(
10417
- path19.join(this.root, "fleet.json"),
10799
+ fs17.mkdirSync(this.root, { recursive: true });
10800
+ fs17.writeFileSync(
10801
+ path20.join(this.root, "fleet.json"),
10418
10802
  JSON.stringify(cred, null, 2),
10419
10803
  { encoding: "utf-8", mode: 384 }
10420
10804
  );
10421
10805
  }
10422
10806
  clearFleetCredential() {
10423
- fs16.rmSync(path19.join(this.root, "fleet.json"), { force: true });
10807
+ fs17.rmSync(path20.join(this.root, "fleet.json"), { force: true });
10424
10808
  }
10425
10809
  /** 数据根目录(只读;节点管理配置等辅助文件定位用) */
10426
10810
  get rootDir() {
@@ -10429,7 +10813,7 @@ var Store = class {
10429
10813
  // ===================== 工具 =====================
10430
10814
  _readJson(p) {
10431
10815
  try {
10432
- return JSON.parse(fs16.readFileSync(p, "utf-8"));
10816
+ return JSON.parse(fs17.readFileSync(p, "utf-8"));
10433
10817
  } catch {
10434
10818
  return null;
10435
10819
  }
@@ -10442,13 +10826,13 @@ var baseUrlResolver = null;
10442
10826
  function resolveBase(baseUrl) {
10443
10827
  return baseUrlResolver ? baseUrlResolver(baseUrl) : baseUrl;
10444
10828
  }
10445
- async function req(baseUrl, token, path42, init = {}) {
10829
+ async function req(baseUrl, token, path44, init = {}) {
10446
10830
  const headers = {
10447
10831
  ...init.body ? { "Content-Type": "application/json" } : {},
10448
10832
  ...init.headers ?? {}
10449
10833
  };
10450
10834
  if (token) headers.Authorization = `Bearer ${token}`;
10451
- const res = await fetch(`${resolveBase(baseUrl).replace(/\/$/, "")}${path42}`, {
10835
+ const res = await fetch(`${resolveBase(baseUrl).replace(/\/$/, "")}${path44}`, {
10452
10836
  ...init,
10453
10837
  headers
10454
10838
  });
@@ -10491,8 +10875,8 @@ async function hubEnrollByAccount(baseUrl, input) {
10491
10875
  // src/cli/serve-instances.ts
10492
10876
  init_esm_shims();
10493
10877
  import { spawnSync as spawnSync2 } from "child_process";
10494
- import * as fs17 from "fs";
10495
- import * as path20 from "path";
10878
+ import * as fs18 from "fs";
10879
+ import * as path21 from "path";
10496
10880
  var DEFAULT_SERVE_HOST = "127.0.0.1";
10497
10881
  var DEFAULT_SERVE_PORT = 8787;
10498
10882
  function serveSubcommandIndex(argv) {
@@ -10530,9 +10914,9 @@ function parseServeArgs(argv) {
10530
10914
  }
10531
10915
  function buildHotCandidates(explicitDataDir, discovered, fallback) {
10532
10916
  if (explicitDataDir) {
10533
- return [{ dataDir: path20.resolve(explicitDataDir), ...fallback }];
10917
+ return [{ dataDir: path21.resolve(explicitDataDir), ...fallback }];
10534
10918
  }
10535
- const out = [{ dataDir: path20.resolve(DEFAULT_DATA_DIR), ...fallback }];
10919
+ const out = [{ dataDir: path21.resolve(DEFAULT_DATA_DIR), ...fallback }];
10536
10920
  for (const inst of discovered) {
10537
10921
  if (!out.some((o) => o.dataDir === inst.dataDir)) out.push(inst);
10538
10922
  }
@@ -10540,7 +10924,7 @@ function buildHotCandidates(explicitDataDir, discovered, fallback) {
10540
10924
  }
10541
10925
  function processCwd(pid) {
10542
10926
  try {
10543
- return fs17.readlinkSync(`/proc/${pid}/cwd`);
10927
+ return fs18.readlinkSync(`/proc/${pid}/cwd`);
10544
10928
  } catch {
10545
10929
  return null;
10546
10930
  }
@@ -10548,7 +10932,7 @@ function processCwd(pid) {
10548
10932
  function processEnv(pid) {
10549
10933
  const out = {};
10550
10934
  try {
10551
- const raw = fs17.readFileSync(`/proc/${pid}/environ`, "utf-8");
10935
+ const raw = fs18.readFileSync(`/proc/${pid}/environ`, "utf-8");
10552
10936
  for (const entry of raw.split("\0")) {
10553
10937
  const eq = entry.indexOf("=");
10554
10938
  if (eq > 0) out[entry.slice(0, eq)] = entry.slice(eq + 1);
@@ -10559,14 +10943,14 @@ function processEnv(pid) {
10559
10943
  }
10560
10944
  function resolveDataDir(rawDataDir, cwd, env3) {
10561
10945
  const pick = rawDataDir ?? env3.DATA_DIR ?? null;
10562
- if (pick) return path20.isAbsolute(pick) ? path20.normalize(pick) : cwd ? path20.resolve(cwd, pick) : null;
10563
- return cwd ? path20.resolve(cwd, DEFAULT_DATA_DIR) : null;
10946
+ if (pick) return path21.isAbsolute(pick) ? path21.normalize(pick) : cwd ? path21.resolve(cwd, pick) : null;
10947
+ return cwd ? path21.resolve(cwd, DEFAULT_DATA_DIR) : null;
10564
10948
  }
10565
10949
  function discoverFromProc() {
10566
10950
  const out = [];
10567
10951
  let pids;
10568
10952
  try {
10569
- pids = fs17.readdirSync("/proc");
10953
+ pids = fs18.readdirSync("/proc");
10570
10954
  } catch {
10571
10955
  return out;
10572
10956
  }
@@ -10576,7 +10960,7 @@ function discoverFromProc() {
10576
10960
  if (pid === process.pid) continue;
10577
10961
  let argv;
10578
10962
  try {
10579
- argv = fs17.readFileSync(`/proc/${pid}/cmdline`, "utf-8").split("\0").filter(Boolean);
10963
+ argv = fs18.readFileSync(`/proc/${pid}/cmdline`, "utf-8").split("\0").filter(Boolean);
10580
10964
  } catch {
10581
10965
  continue;
10582
10966
  }
@@ -10597,11 +10981,11 @@ function discoverFromPs() {
10597
10981
  const m = /--data-dir(?:=|\s+)(.+?)(?=\s+--|\s*$)/.exec(line);
10598
10982
  if (!m) continue;
10599
10983
  const raw = m[1].trim();
10600
- if (!raw || !path20.isAbsolute(raw)) continue;
10984
+ if (!raw || !path21.isAbsolute(raw)) continue;
10601
10985
  const host = /--host(?:=|\s+)(\S+)/.exec(line)?.[1] ?? DEFAULT_SERVE_HOST;
10602
10986
  const port = Number(/--port(?:=|\s+)(\d+)/.exec(line)?.[1] ?? DEFAULT_SERVE_PORT);
10603
10987
  out.push({
10604
- dataDir: path20.normalize(raw),
10988
+ dataDir: path21.normalize(raw),
10605
10989
  host: host === "0.0.0.0" || host === "::" ? DEFAULT_SERVE_HOST : host,
10606
10990
  port: Number.isFinite(port) && port > 0 ? port : DEFAULT_SERVE_PORT
10607
10991
  });
@@ -10704,7 +11088,7 @@ async function linkNode(opts) {
10704
11088
  const status = await enrollViaServer(inst.host, inst.port, adminKey, opts);
10705
11089
  return { mode: "hot", ...status, dataDir: inst.dataDir };
10706
11090
  }
10707
- const dataDir = path21.resolve(explicitDataDir ?? DEFAULT_DATA_DIR);
11091
+ const dataDir = path22.resolve(explicitDataDir ?? DEFAULT_DATA_DIR);
10708
11092
  const store = new Store(dataDir);
10709
11093
  const nodeId = ensureNodeId(store);
10710
11094
  const cred = await hubEnrollByCode(opts.hubUrl, {
@@ -10786,25 +11170,26 @@ init_esm_shims();
10786
11170
 
10787
11171
  // src/server/runtime.ts
10788
11172
  init_esm_shims();
10789
- import * as fs29 from "fs";
10790
- import * as path36 from "path";
11173
+ import * as fs31 from "fs";
11174
+ import * as path38 from "path";
10791
11175
 
10792
11176
  // src/server/session.ts
10793
11177
  init_esm_shims();
11178
+ import * as path25 from "path";
10794
11179
  import { AsyncLocalStorage } from "async_hooks";
10795
11180
 
10796
11181
  // src/server/mcpConfig.ts
10797
11182
  init_esm_shims();
10798
- import fs18 from "fs";
10799
- import path22 from "path";
11183
+ import fs19 from "fs";
11184
+ import path23 from "path";
10800
11185
  function isValidMcpConfig(v) {
10801
11186
  return !!v && typeof v === "object" && typeof v.name === "string" && (v.type === "stdio" || v.type === "http");
10802
11187
  }
10803
11188
  function readConfigFile(file) {
10804
11189
  if (!file) return [];
10805
11190
  try {
10806
- if (!fs18.existsSync(file)) return [];
10807
- const raw = JSON.parse(fs18.readFileSync(file, "utf8"));
11191
+ if (!fs19.existsSync(file)) return [];
11192
+ const raw = JSON.parse(fs19.readFileSync(file, "utf8"));
10808
11193
  const list = Array.isArray(raw) ? raw : raw && typeof raw === "object" && Array.isArray(raw.servers) ? raw.servers : [];
10809
11194
  return list.filter(isValidMcpConfig);
10810
11195
  } catch (err) {
@@ -10816,7 +11201,7 @@ function hasServerMcpConfig() {
10816
11201
  if (readConfigFile(process.env.MCP_CONFIG_PATH).length > 0) return true;
10817
11202
  const workspace = process.env.WORKSPACE;
10818
11203
  if (!workspace) return false;
10819
- return readConfigFile(path22.join(workspace, ".agent", "mcp.json")).length > 0;
11204
+ return readConfigFile(path23.join(workspace, ".agent", "mcp.json")).length > 0;
10820
11205
  }
10821
11206
  function loadServerMcpConfigs(tenant) {
10822
11207
  const fromEnv = readConfigFile(process.env.MCP_CONFIG_PATH);
@@ -10826,7 +11211,7 @@ function loadServerMcpConfigs(tenant) {
10826
11211
  }
10827
11212
  const workspace = tenant.workspaceRoot ?? process.env.WORKSPACE;
10828
11213
  if (workspace)
10829
- return readConfigFile(path22.join(workspace, ".agent", "mcp.json"));
11214
+ return readConfigFile(path23.join(workspace, ".agent", "mcp.json"));
10830
11215
  return [];
10831
11216
  }
10832
11217
 
@@ -10971,8 +11356,8 @@ function resolveSessionProvider(store, tenant, providerId) {
10971
11356
 
10972
11357
  // src/server/usage.ts
10973
11358
  init_esm_shims();
10974
- import * as fs19 from "fs";
10975
- import * as path23 from "path";
11359
+ import * as fs20 from "fs";
11360
+ import * as path24 from "path";
10976
11361
  var LEDGER_RETENTION_MONTHS = 12;
10977
11362
  var USAGE_SCHEMA = 1;
10978
11363
  var DEFAULT_PRICING = {
@@ -11053,15 +11438,15 @@ function retainedMonths(now = /* @__PURE__ */ new Date()) {
11053
11438
  return out;
11054
11439
  }
11055
11440
  function atomicWrite(file, content) {
11056
- fs19.mkdirSync(path23.dirname(file), { recursive: true });
11441
+ fs20.mkdirSync(path24.dirname(file), { recursive: true });
11057
11442
  const tmp = `${file}.tmp`;
11058
- fs19.writeFileSync(tmp, content, "utf-8");
11059
- fs19.renameSync(tmp, file);
11443
+ fs20.writeFileSync(tmp, content, "utf-8");
11444
+ fs20.renameSync(tmp, file);
11060
11445
  }
11061
11446
  var UsageLedger = class {
11062
11447
  constructor(root) {
11063
11448
  this.root = root;
11064
- this.dir = path23.join(root, "usage");
11449
+ this.dir = path24.join(root, "usage");
11065
11450
  }
11066
11451
  root;
11067
11452
  dir;
@@ -11074,18 +11459,18 @@ var UsageLedger = class {
11074
11459
  return this.dir;
11075
11460
  }
11076
11461
  ledgerFile(month) {
11077
- return path23.join(this.ledgerDir, `ledger-${month}.jsonl`);
11462
+ return path24.join(this.ledgerDir, `ledger-${month}.jsonl`);
11078
11463
  }
11079
11464
  get rollupFile() {
11080
- return path23.join(this.ledgerDir, "rollup.json");
11465
+ return path24.join(this.ledgerDir, "rollup.json");
11081
11466
  }
11082
11467
  get pricingFile() {
11083
- return path23.join(this.ledgerDir, "pricing.json");
11468
+ return path24.join(this.ledgerDir, "pricing.json");
11084
11469
  }
11085
11470
  // ===================== 初始化 =====================
11086
11471
  init(now = /* @__PURE__ */ new Date()) {
11087
11472
  if (this.inited) return;
11088
- fs19.mkdirSync(this.ledgerDir, { recursive: true });
11473
+ fs20.mkdirSync(this.ledgerDir, { recursive: true });
11089
11474
  this.pricing = this.loadPricing();
11090
11475
  this.rollup = this.loadRollup(now);
11091
11476
  this.pruneLedgers(now);
@@ -11093,7 +11478,7 @@ var UsageLedger = class {
11093
11478
  }
11094
11479
  loadPricing() {
11095
11480
  try {
11096
- const raw = fs19.readFileSync(this.pricingFile, "utf-8");
11481
+ const raw = fs20.readFileSync(this.pricingFile, "utf-8");
11097
11482
  const parsed = JSON.parse(raw);
11098
11483
  const vendors = {};
11099
11484
  for (const v of /* @__PURE__ */ new Set([
@@ -11122,7 +11507,7 @@ var UsageLedger = class {
11122
11507
  }
11123
11508
  loadRollup(now) {
11124
11509
  try {
11125
- const raw = fs19.readFileSync(this.rollupFile, "utf-8");
11510
+ const raw = fs20.readFileSync(this.rollupFile, "utf-8");
11126
11511
  const parsed = JSON.parse(raw);
11127
11512
  return {
11128
11513
  schema: USAGE_SCHEMA,
@@ -11149,10 +11534,10 @@ var UsageLedger = class {
11149
11534
  const keep = retainedMonths(now);
11150
11535
  let changed = false;
11151
11536
  try {
11152
- for (const f of fs19.readdirSync(this.ledgerDir)) {
11537
+ for (const f of fs20.readdirSync(this.ledgerDir)) {
11153
11538
  const m = /^ledger-(\d{4}-\d{2})\.jsonl$/.exec(f);
11154
11539
  if (m && !keep.has(m[1])) {
11155
- fs19.unlinkSync(path23.join(this.ledgerDir, f));
11540
+ fs20.unlinkSync(path24.join(this.ledgerDir, f));
11156
11541
  }
11157
11542
  }
11158
11543
  } catch {
@@ -11229,7 +11614,7 @@ var UsageLedger = class {
11229
11614
  };
11230
11615
  const line = JSON.stringify(rec) + "\n";
11231
11616
  try {
11232
- fs19.appendFileSync(this.ledgerFile(monthOf(rec.ts)), line);
11617
+ fs20.appendFileSync(this.ledgerFile(monthOf(rec.ts)), line);
11233
11618
  } catch (e) {
11234
11619
  console.error(`[usage] \u5199\u8D26\u672C\u5931\u8D25: ${String(e)}`);
11235
11620
  }
@@ -11401,10 +11786,10 @@ var UsageLedger = class {
11401
11786
  const rows = [];
11402
11787
  for (const month of months) {
11403
11788
  const file = this.ledgerFile(month);
11404
- if (!fs19.existsSync(file)) continue;
11789
+ if (!fs20.existsSync(file)) continue;
11405
11790
  let content = "";
11406
11791
  try {
11407
- content = fs19.readFileSync(file, "utf-8");
11792
+ content = fs20.readFileSync(file, "utf-8");
11408
11793
  } catch {
11409
11794
  continue;
11410
11795
  }
@@ -11513,10 +11898,10 @@ var UsageLedger = class {
11513
11898
  const seen = /* @__PURE__ */ new Set();
11514
11899
  for (const month of this.monthsInRange("", "")) {
11515
11900
  const file = this.ledgerFile(month);
11516
- if (!fs19.existsSync(file)) continue;
11901
+ if (!fs20.existsSync(file)) continue;
11517
11902
  let content = "";
11518
11903
  try {
11519
- content = fs19.readFileSync(file, "utf-8");
11904
+ content = fs20.readFileSync(file, "utf-8");
11520
11905
  } catch {
11521
11906
  continue;
11522
11907
  }
@@ -12824,7 +13209,10 @@ data: ${JSON.stringify(entry.data)}
12824
13209
  // 上下文窗口:环境变量显式覆盖 > 供应商 /models 提供的真实长度 > 引擎内置表
12825
13210
  contextWindow: parseOptionalPositiveNumber(process.env.CONTEXT_WINDOW) ?? modelContextWindow(eff.modelMeta, activeModel),
12826
13211
  thresholdPct: parseRatio(process.env.COMPACTION_THRESHOLD_PCT),
12827
- capTokens: parseOptionalPositiveNumber(process.env.COMPACTION_CAP_TOKENS) ?? null
13212
+ softThresholdPct: parseRatio(process.env.COMPACTION_SOFT_THRESHOLD_PCT),
13213
+ capTokens: parseOptionalPositiveNumber(process.env.COMPACTION_CAP_TOKENS) ?? null,
13214
+ // 超预算工具结果按租户/工作区隔离落盘(多租户共用一个状态目录会混内容)
13215
+ toolResultDir: path25.join(workspace, ".agent", "tool-results")
12828
13216
  });
12829
13217
  engine.setApprover(this._makeApprover(rt));
12830
13218
  const mcpConfigs = loadServerMcpConfigs(tenant);
@@ -13005,11 +13393,11 @@ var ApiError = class extends Error {
13005
13393
  // src/server/routes.ts
13006
13394
  init_esm_shims();
13007
13395
  import { createServer } from "http";
13008
- import * as path28 from "path";
13396
+ import * as path30 from "path";
13009
13397
 
13010
13398
  // src/server/projects.ts
13011
13399
  init_esm_shims();
13012
- import * as path24 from "path";
13400
+ import * as path26 from "path";
13013
13401
  var DEFAULT_MODEL = () => process.env.OPENAI_MODEL || process.env.MODEL || "gpt-4o";
13014
13402
  function toProjectSummary(store, tenant) {
13015
13403
  return {
@@ -13142,9 +13530,9 @@ function createProject(store, input) {
13142
13530
  if (store.loadTenant(id)) {
13143
13531
  throw new ApiError(409, "project_exists", `\u9879\u76EE ${id} \u5DF2\u5B58\u5728`);
13144
13532
  }
13145
- const workspaceRoot = typeof raw.workspaceRoot === "string" && raw.workspaceRoot.trim() ? path24.resolve(raw.workspaceRoot.trim()) : void 0;
13533
+ const workspaceRoot = typeof raw.workspaceRoot === "string" && raw.workspaceRoot.trim() ? path26.resolve(raw.workspaceRoot.trim()) : void 0;
13146
13534
  const explicitName = typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : "";
13147
- const defaultName = workspaceRoot ? path24.basename(workspaceRoot) : id;
13535
+ const defaultName = workspaceRoot ? path26.basename(workspaceRoot) : id;
13148
13536
  const tenant = {
13149
13537
  id,
13150
13538
  name: explicitName || defaultName,
@@ -13174,7 +13562,7 @@ function buildNodeInfo(store, opts) {
13174
13562
  nodeId: opts.nodeId ?? ensureNodeId2(store),
13175
13563
  platform: process.platform,
13176
13564
  mode: process.env.NODE_MODE ?? "standalone",
13177
- version: "0.2.5",
13565
+ version: "0.2.7",
13178
13566
  admin: opts.admin,
13179
13567
  projects: opts.projects
13180
13568
  };
@@ -13258,8 +13646,8 @@ function hubProxyEnabled(host) {
13258
13646
  const h = (host ?? "127.0.0.1").trim();
13259
13647
  return h === "127.0.0.1" || h === "localhost" || h === "::1";
13260
13648
  }
13261
- function isHubProxyPath(path42) {
13262
- return path42.startsWith(HUB_PROXY_PREFIX);
13649
+ function isHubProxyPath(path44) {
13650
+ return path44.startsWith(HUB_PROXY_PREFIX);
13263
13651
  }
13264
13652
  function decodeTarget(enc) {
13265
13653
  try {
@@ -13353,34 +13741,34 @@ async function handleHubProxy(req2, res) {
13353
13741
 
13354
13742
  // src/server/fsBrowser.ts
13355
13743
  init_esm_shims();
13356
- import * as fs20 from "fs";
13744
+ import * as fs21 from "fs";
13357
13745
  import * as os4 from "os";
13358
- import * as path25 from "path";
13746
+ import * as path27 from "path";
13359
13747
  var MAX_ENTRIES = 2e3;
13360
13748
  var DEFAULT_SEARCH_LIMIT = 200;
13361
13749
  var MAX_SEARCH_LIMIT = 1e3;
13362
13750
  function listDirs(input) {
13363
13751
  const base = (input ?? "").trim() || os4.homedir();
13364
- const target = path25.resolve(base);
13752
+ const target = path27.resolve(base);
13365
13753
  let st;
13366
13754
  try {
13367
- st = fs20.statSync(target);
13755
+ st = fs21.statSync(target);
13368
13756
  } catch {
13369
13757
  throw new Error(`\u8DEF\u5F84\u4E0D\u5B58\u5728: ${target}`);
13370
13758
  }
13371
13759
  if (!st.isDirectory()) throw new Error(`\u4E0D\u662F\u76EE\u5F55: ${target}`);
13372
13760
  let dirents;
13373
13761
  try {
13374
- dirents = fs20.readdirSync(target, { withFileTypes: true });
13762
+ dirents = fs21.readdirSync(target, { withFileTypes: true });
13375
13763
  } catch {
13376
13764
  throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF08\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09: ${target}`);
13377
13765
  }
13378
13766
  const entries = dirents.map((d) => {
13379
- const full = path25.join(target, d.name);
13767
+ const full = path27.join(target, d.name);
13380
13768
  let isDir = d.isDirectory();
13381
13769
  if (d.isSymbolicLink()) {
13382
13770
  try {
13383
- isDir = fs20.statSync(full).isDirectory();
13771
+ isDir = fs21.statSync(full).isDirectory();
13384
13772
  } catch {
13385
13773
  }
13386
13774
  }
@@ -13389,7 +13777,7 @@ function listDirs(input) {
13389
13777
  entries.sort(
13390
13778
  (a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1
13391
13779
  );
13392
- const parentDir = path25.dirname(target);
13780
+ const parentDir = path27.dirname(target);
13393
13781
  return {
13394
13782
  path: target,
13395
13783
  parent: parentDir !== target ? parentDir : null,
@@ -13420,7 +13808,7 @@ var NAME_CACHE_MAX_DIRS = 4;
13420
13808
  var NAME_CACHE_MAX_ENTRIES = 5e4;
13421
13809
  var nameCache = /* @__PURE__ */ new Map();
13422
13810
  function invalidateDirNames(target) {
13423
- nameCache.delete(path25.resolve(target));
13811
+ nameCache.delete(path27.resolve(target));
13424
13812
  }
13425
13813
  async function loadDirNames(target) {
13426
13814
  const now = Date.now();
@@ -13432,7 +13820,7 @@ async function loadDirNames(target) {
13432
13820
  }
13433
13821
  let dirents;
13434
13822
  try {
13435
- dirents = await fs20.promises.readdir(target, { withFileTypes: true });
13823
+ dirents = await fs21.promises.readdir(target, { withFileTypes: true });
13436
13824
  } catch {
13437
13825
  throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF08\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09: ${target}`);
13438
13826
  }
@@ -13455,11 +13843,11 @@ async function loadDirNames(target) {
13455
13843
  return entries;
13456
13844
  }
13457
13845
  async function toDirEntry(dir, e) {
13458
- const full = path25.join(dir, e.name);
13846
+ const full = path27.join(dir, e.name);
13459
13847
  let isDir = e.isDir;
13460
13848
  if (e.isLink) {
13461
13849
  try {
13462
- isDir = (await fs20.promises.stat(full)).isDirectory();
13850
+ isDir = (await fs21.promises.stat(full)).isDirectory();
13463
13851
  } catch {
13464
13852
  }
13465
13853
  }
@@ -13467,10 +13855,10 @@ async function toDirEntry(dir, e) {
13467
13855
  }
13468
13856
  async function searchDirs(input, query, limit) {
13469
13857
  const base = (input ?? "").trim() || os4.homedir();
13470
- const target = path25.resolve(base);
13858
+ const target = path27.resolve(base);
13471
13859
  let st;
13472
13860
  try {
13473
- st = await fs20.promises.stat(target);
13861
+ st = await fs21.promises.stat(target);
13474
13862
  } catch {
13475
13863
  throw new Error(`\u8DEF\u5F84\u4E0D\u5B58\u5728: ${target}`);
13476
13864
  }
@@ -13500,7 +13888,7 @@ async function searchDirs(input, query, limit) {
13500
13888
  );
13501
13889
  const hit = scored.slice(0, max);
13502
13890
  const entries = await Promise.all(hit.map(({ entry }) => toDirEntry(target, entry)));
13503
- const parentDir = path25.dirname(target);
13891
+ const parentDir = path27.dirname(target);
13504
13892
  return {
13505
13893
  path: target,
13506
13894
  parent: parentDir !== target ? parentDir : null,
@@ -13511,52 +13899,52 @@ async function searchDirs(input, query, limit) {
13511
13899
  };
13512
13900
  }
13513
13901
  function makeDirIn(parentInput, name) {
13514
- const parent = path25.resolve((parentInput ?? "").trim() || os4.homedir());
13902
+ const parent = path27.resolve((parentInput ?? "").trim() || os4.homedir());
13515
13903
  const clean = (name ?? "").trim();
13516
13904
  if (!clean) throw new Error("\u76EE\u5F55\u540D\u4E0D\u80FD\u4E3A\u7A7A");
13517
13905
  if (clean === "." || clean === "..") throw new Error("\u76EE\u5F55\u540D\u975E\u6CD5");
13518
- const target = path25.resolve(parent, clean);
13519
- const rel = path25.relative(parent, target);
13520
- if (!rel || rel.startsWith("..") || path25.isAbsolute(rel)) {
13906
+ const target = path27.resolve(parent, clean);
13907
+ const rel = path27.relative(parent, target);
13908
+ if (!rel || rel.startsWith("..") || path27.isAbsolute(rel)) {
13521
13909
  throw new Error("\u76EE\u5F55\u540D\u4E0D\u80FD\u8D8A\u51FA\u5F53\u524D\u76EE\u5F55");
13522
13910
  }
13523
13911
  let st = null;
13524
13912
  try {
13525
- st = fs20.statSync(target);
13913
+ st = fs21.statSync(target);
13526
13914
  } catch {
13527
13915
  st = null;
13528
13916
  }
13529
13917
  if (st && !st.isDirectory()) throw new Error(`\u5DF2\u5B58\u5728\u540C\u540D\u6587\u4EF6: ${target}`);
13530
13918
  try {
13531
- fs20.mkdirSync(target, { recursive: true });
13919
+ fs21.mkdirSync(target, { recursive: true });
13532
13920
  } catch (e) {
13533
13921
  throw new Error(`\u521B\u5EFA\u76EE\u5F55\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
13534
13922
  }
13535
13923
  invalidateDirNames(parent);
13536
- return { name: path25.basename(target), path: target, type: "dir" };
13924
+ return { name: path27.basename(target), path: target, type: "dir" };
13537
13925
  }
13538
13926
 
13539
13927
  // src/server/projectFiles.ts
13540
13928
  init_esm_shims();
13541
- import * as fs21 from "fs";
13542
- import * as path26 from "path";
13929
+ import * as fs22 from "fs";
13930
+ import * as path28 from "path";
13543
13931
  var MAX_FILE_BYTES = 2 * 1024 * 1024;
13544
13932
  var MAX_DOWNLOAD_BYTES = 16 * 1024 * 1024;
13545
13933
  var MAX_ENTRIES2 = 2e3;
13546
13934
  function toPosix(p) {
13547
- return p.split(path26.sep).join("/");
13935
+ return p.split(path28.sep).join("/");
13548
13936
  }
13549
13937
  function projectRoot(workspaceRoot) {
13550
13938
  const r = (workspaceRoot ?? "").trim();
13551
13939
  if (!r) {
13552
13940
  throw new Error("\u9879\u76EE\u672A\u8BBE\u7F6E\u5DE5\u4F5C\u533A\u76EE\u5F55\uFF08workspaceRoot\uFF09\uFF0C\u65E0\u6CD5\u6D4F\u89C8\u6587\u4EF6");
13553
13941
  }
13554
- const abs = path26.resolve(r);
13555
- if (!fs21.existsSync(abs)) {
13942
+ const abs = path28.resolve(r);
13943
+ if (!fs22.existsSync(abs)) {
13556
13944
  throw new Error(`\u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728: ${abs}`);
13557
13945
  }
13558
13946
  try {
13559
- return fs21.realpathSync(abs);
13947
+ return fs22.realpathSync(abs);
13560
13948
  } catch {
13561
13949
  return abs;
13562
13950
  }
@@ -13574,37 +13962,37 @@ function listProjectFiles(workspaceRoot, relPath) {
13574
13962
  const dir = resolveWithin(base, relPath ?? "");
13575
13963
  let st;
13576
13964
  try {
13577
- st = fs21.statSync(dir);
13965
+ st = fs22.statSync(dir);
13578
13966
  } catch {
13579
- throw new Error(`\u76EE\u5F55\u4E0D\u5B58\u5728: ${toPosix(path26.relative(base, dir)) || "/"}`);
13967
+ throw new Error(`\u76EE\u5F55\u4E0D\u5B58\u5728: ${toPosix(path28.relative(base, dir)) || "/"}`);
13580
13968
  }
13581
13969
  if (!st.isDirectory()) throw new Error("\u76EE\u6807\u4E0D\u662F\u76EE\u5F55");
13582
13970
  let dirents;
13583
13971
  try {
13584
- dirents = fs21.readdirSync(dir, { withFileTypes: true });
13972
+ dirents = fs22.readdirSync(dir, { withFileTypes: true });
13585
13973
  } catch {
13586
13974
  throw new Error("\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF08\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09");
13587
13975
  }
13588
13976
  const entries = dirents.map((d) => {
13589
- const full = path26.join(dir, d.name);
13977
+ const full = path28.join(dir, d.name);
13590
13978
  let isDir = d.isDirectory();
13591
13979
  if (d.isSymbolicLink()) {
13592
13980
  try {
13593
- isDir = fs21.statSync(full).isDirectory();
13981
+ isDir = fs22.statSync(full).isDirectory();
13594
13982
  } catch {
13595
13983
  }
13596
13984
  }
13597
13985
  let size = 0;
13598
13986
  let mtime = "";
13599
13987
  try {
13600
- const s = fs21.statSync(full);
13988
+ const s = fs22.statSync(full);
13601
13989
  size = s.size;
13602
13990
  mtime = s.mtime.toISOString();
13603
13991
  } catch {
13604
13992
  }
13605
13993
  return {
13606
13994
  name: d.name,
13607
- path: toPosix(path26.relative(base, full)),
13995
+ path: toPosix(path28.relative(base, full)),
13608
13996
  type: isDir ? "dir" : "file",
13609
13997
  size,
13610
13998
  mtime
@@ -13613,12 +14001,12 @@ function listProjectFiles(workspaceRoot, relPath) {
13613
14001
  entries.sort(
13614
14002
  (a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1
13615
14003
  );
13616
- const relDir = toPosix(path26.relative(base, dir));
13617
- const parentAbs = path26.dirname(dir);
14004
+ const relDir = toPosix(path28.relative(base, dir));
14005
+ const parentAbs = path28.dirname(dir);
13618
14006
  return {
13619
14007
  root: base,
13620
14008
  path: relDir,
13621
- parent: isWithinRoot(base, parentAbs) ? toPosix(path26.relative(base, parentAbs)) : null,
14009
+ parent: isWithinRoot(base, parentAbs) ? toPosix(path28.relative(base, parentAbs)) : null,
13622
14010
  entries: entries.slice(0, MAX_ENTRIES2)
13623
14011
  };
13624
14012
  }
@@ -13627,17 +14015,17 @@ function readProjectFile(workspaceRoot, relPath) {
13627
14015
  const file = resolveWithin(base, relPath ?? "");
13628
14016
  let st;
13629
14017
  try {
13630
- st = fs21.statSync(file);
14018
+ st = fs22.statSync(file);
13631
14019
  } catch {
13632
14020
  throw new Error("\u6587\u4EF6\u4E0D\u5B58\u5728");
13633
14021
  }
13634
14022
  if (!st.isFile()) throw new Error("\u76EE\u6807\u4E0D\u662F\u6587\u4EF6");
13635
- const buf = fs21.readFileSync(file);
14023
+ const buf = fs22.readFileSync(file);
13636
14024
  const binary = isBinary(buf);
13637
14025
  const truncated = buf.length > MAX_FILE_BYTES;
13638
14026
  const slice = truncated ? buf.subarray(0, MAX_FILE_BYTES) : buf;
13639
14027
  return {
13640
- path: toPosix(path26.relative(base, file)),
14028
+ path: toPosix(path28.relative(base, file)),
13641
14029
  size: st.size,
13642
14030
  binary,
13643
14031
  truncated,
@@ -13649,7 +14037,7 @@ function downloadProjectFile(workspaceRoot, relPath) {
13649
14037
  const file = resolveWithin(base, relPath ?? "");
13650
14038
  let st;
13651
14039
  try {
13652
- st = fs21.statSync(file);
14040
+ st = fs22.statSync(file);
13653
14041
  } catch {
13654
14042
  throw new Error("\u6587\u4EF6\u4E0D\u5B58\u5728");
13655
14043
  }
@@ -13657,10 +14045,10 @@ function downloadProjectFile(workspaceRoot, relPath) {
13657
14045
  if (st.size > MAX_DOWNLOAD_BYTES) {
13658
14046
  throw new Error(`\u6587\u4EF6\u8D85\u8FC7\u4E0B\u8F7D\u4E0A\u9650\uFF08${MAX_DOWNLOAD_BYTES / (1024 * 1024)}MB\uFF09`);
13659
14047
  }
13660
- const buf = fs21.readFileSync(file);
14048
+ const buf = fs22.readFileSync(file);
13661
14049
  return {
13662
- path: toPosix(path26.relative(base, file)),
13663
- name: path26.basename(file),
14050
+ path: toPosix(path28.relative(base, file)),
14051
+ name: path28.basename(file),
13664
14052
  size: buf.length,
13665
14053
  base64: buf.toString("base64")
13666
14054
  };
@@ -13682,12 +14070,12 @@ function writeProjectFile(workspaceRoot, relPath, content) {
13682
14070
  throw new Error("\u5185\u5BB9\u8D85\u8FC7 2MB \u4E0A\u9650\uFF0C\u65E0\u6CD5\u4FDD\u5B58");
13683
14071
  }
13684
14072
  try {
13685
- fs21.mkdirSync(path26.dirname(file), { recursive: true });
13686
- fs21.writeFileSync(file, buf);
14073
+ fs22.mkdirSync(path28.dirname(file), { recursive: true });
14074
+ fs22.writeFileSync(file, buf);
13687
14075
  } catch (e) {
13688
14076
  throw new Error(`\u5199\u5165\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
13689
14077
  }
13690
- return { path: toPosix(path26.relative(base, file)), size: buf.length };
14078
+ return { path: toPosix(path28.relative(base, file)), size: buf.length };
13691
14079
  }
13692
14080
  function createProjectDir(workspaceRoot, relPath) {
13693
14081
  const base = projectRoot(workspaceRoot);
@@ -13695,26 +14083,26 @@ function createProjectDir(workspaceRoot, relPath) {
13695
14083
  if (!rel) throw new Error("\u76EE\u5F55\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A");
13696
14084
  const dir = resolveWithin(base, rel);
13697
14085
  try {
13698
- fs21.mkdirSync(dir, { recursive: true });
14086
+ fs22.mkdirSync(dir, { recursive: true });
13699
14087
  } catch (e) {
13700
14088
  throw new Error(`\u521B\u5EFA\u76EE\u5F55\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
13701
14089
  }
13702
- return { path: toPosix(path26.relative(base, dir)) };
14090
+ return { path: toPosix(path28.relative(base, dir)) };
13703
14091
  }
13704
14092
  function createProjectFile(workspaceRoot, relPath, content = "") {
13705
14093
  const base = projectRoot(workspaceRoot);
13706
14094
  const rel = (relPath ?? "").trim();
13707
14095
  if (!rel) throw new Error("\u6587\u4EF6\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A");
13708
14096
  const file = resolveWithin(base, rel);
13709
- if (fs21.existsSync(file)) throw new Error("\u6587\u4EF6\u5DF2\u5B58\u5728");
14097
+ if (fs22.existsSync(file)) throw new Error("\u6587\u4EF6\u5DF2\u5B58\u5728");
13710
14098
  const buf = Buffer.from(String(content ?? ""), "utf-8");
13711
14099
  try {
13712
- fs21.mkdirSync(path26.dirname(file), { recursive: true });
13713
- fs21.writeFileSync(file, buf);
14100
+ fs22.mkdirSync(path28.dirname(file), { recursive: true });
14101
+ fs22.writeFileSync(file, buf);
13714
14102
  } catch (e) {
13715
14103
  throw new Error(`\u521B\u5EFA\u6587\u4EF6\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
13716
14104
  }
13717
- return { path: toPosix(path26.relative(base, file)), size: buf.length };
14105
+ return { path: toPosix(path28.relative(base, file)), size: buf.length };
13718
14106
  }
13719
14107
  function searchProjectFiles(workspaceRoot, query) {
13720
14108
  const base = projectRoot(workspaceRoot);
@@ -13737,7 +14125,7 @@ function searchProjectFiles(workspaceRoot, query) {
13737
14125
  if (out.length >= LIMIT) return;
13738
14126
  let dirents;
13739
14127
  try {
13740
- dirents = fs21.readdirSync(dir, { withFileTypes: true });
14128
+ dirents = fs22.readdirSync(dir, { withFileTypes: true });
13741
14129
  } catch {
13742
14130
  return;
13743
14131
  }
@@ -13748,7 +14136,7 @@ function searchProjectFiles(workspaceRoot, query) {
13748
14136
  let isDir = d.isDirectory();
13749
14137
  if (d.isSymbolicLink()) {
13750
14138
  try {
13751
- isDir = fs21.statSync(path26.join(dir, d.name)).isDirectory();
14139
+ isDir = fs22.statSync(path28.join(dir, d.name)).isDirectory();
13752
14140
  } catch {
13753
14141
  isDir = false;
13754
14142
  }
@@ -13756,7 +14144,7 @@ function searchProjectFiles(workspaceRoot, query) {
13756
14144
  if (d.name.toLowerCase().includes(q)) {
13757
14145
  out.push({ path: rel, type: isDir ? "dir" : "file" });
13758
14146
  }
13759
- if (isDir) walk(path26.join(dir, d.name), rel);
14147
+ if (isDir) walk(path28.join(dir, d.name), rel);
13760
14148
  }
13761
14149
  };
13762
14150
  walk(base, "");
@@ -13766,7 +14154,7 @@ function searchProjectFiles(workspaceRoot, query) {
13766
14154
  // src/server/projectOps.ts
13767
14155
  init_esm_shims();
13768
14156
  import * as os5 from "os";
13769
- import * as path27 from "path";
14157
+ import * as path29 from "path";
13770
14158
  var MAX_STDOUT2 = 2e4;
13771
14159
  var MAX_STDERR2 = 1e4;
13772
14160
  var MAX_BUFFER3 = 1024 * 1024 * 5;
@@ -13780,7 +14168,7 @@ function resolveInvocation2(spec, prepared) {
13780
14168
  }
13781
14169
  function workRoot(workspaceRoot) {
13782
14170
  const r = (workspaceRoot ?? "").trim();
13783
- return r ? path27.resolve(r) : os5.homedir();
14171
+ return r ? path29.resolve(r) : os5.homedir();
13784
14172
  }
13785
14173
  async function runProjectCommand(workspaceRoot, command, timeoutSeconds) {
13786
14174
  const cmd = String(command ?? "");
@@ -13900,6 +14288,92 @@ async function projectGitPush(workspaceRoot, repoPath) {
13900
14288
  });
13901
14289
  }
13902
14290
 
14291
+ // src/server/gitCommitMessage.ts
14292
+ init_esm_shims();
14293
+ import * as crypto5 from "crypto";
14294
+ var MAX_DIFF_CHARS = 12e3;
14295
+ var MAX_MESSAGE_CHARS = 200;
14296
+ 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
14297
+
14298
+ \u8981\u6C42\uFF1A
14299
+ 1. \u4F7F\u7528 Conventional Commits \u98CE\u683C\uFF1Atype(scope): \u63CF\u8FF0\uFF08type \u5982 feat / fix / refactor / docs / test / chore / perf / build / ci\uFF09\uFF1B
14300
+ 2. scope \u53D6\u53D8\u66F4\u6700\u96C6\u4E2D\u7684\u6A21\u5757\u6216\u76EE\u5F55\uFF0C\u65E0\u6CD5\u5224\u65AD\u65F6\u7701\u7565\uFF1B
14301
+ 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
14302
+ 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
14303
+ 5. \u4E0D\u8D85\u8FC7 72 \u4E2A\u5B57\u7B26\u3002`;
14304
+ function genReqId2() {
14305
+ return crypto5.randomBytes(6).toString("base64url");
14306
+ }
14307
+ function cleanCommitMessage(raw) {
14308
+ let text = String(raw ?? "").trim();
14309
+ if (!text) return "";
14310
+ text = text.replace(/^```[^\n]*\n?/, "").replace(/\n?```$/, "").trim();
14311
+ const first = text.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0) ?? "";
14312
+ return first.replace(/^(commit message|提交说明|提交信息|message)\s*[::]\s*/i, "").replace(/^["'“”`]+|["'“”`]+$/g, "").replace(/\s+/g, " ").trim().slice(0, MAX_MESSAGE_CHARS);
14313
+ }
14314
+ async function collectDiff(workspaceRoot, repo) {
14315
+ const staged = await projectGitDiff(workspaceRoot, void 0, true, repo);
14316
+ if (!staged.error && String(staged.diff ?? "").trim()) {
14317
+ return { text: String(staged.diff), source: "staged" };
14318
+ }
14319
+ const worktree = await projectGitDiff(workspaceRoot, void 0, false, repo);
14320
+ if (!worktree.error && String(worktree.diff ?? "").trim()) {
14321
+ return { text: String(worktree.diff), source: "worktree" };
14322
+ }
14323
+ const status = await projectGitStatus(workspaceRoot, void 0, repo);
14324
+ if (status.error) throw new ApiError(400, "git_error", String(status.error));
14325
+ const entries = status.entries ?? [];
14326
+ if (entries.length === 0) throw new ApiError(400, "no_changes", "\u6CA1\u6709\u53EF\u63D0\u4EA4\u7684\u53D8\u66F4");
14327
+ return {
14328
+ text: entries.map((e) => `${e.status ?? ""} ${e.file ?? ""}`.trim()).join("\n"),
14329
+ source: "worktree"
14330
+ };
14331
+ }
14332
+ async function generateGitCommitMessage(opts) {
14333
+ const { store, tenant } = opts;
14334
+ const { text, source } = await collectDiff(opts.workspaceRoot, opts.repo);
14335
+ const truncated = text.length > MAX_DIFF_CHARS;
14336
+ const clipped = text.slice(0, MAX_DIFF_CHARS);
14337
+ const eff = resolveSessionProvider(store, tenant);
14338
+ const apiKey = eff.apiKey || process.env.OPENAI_API_KEY || "";
14339
+ const model = (eff.model || tenant.model || process.env.OPENAI_MODEL || process.env.MODEL || "").trim();
14340
+ if (!apiKey) {
14341
+ throw new ApiError(
14342
+ 400,
14343
+ "llm_not_configured",
14344
+ "\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"
14345
+ );
14346
+ }
14347
+ if (!model) {
14348
+ throw new ApiError(
14349
+ 400,
14350
+ "llm_not_configured",
14351
+ "\u5F53\u524D\u8282\u70B9\u672A\u914D\u7F6E\u6A21\u578B\uFF0C\u8BF7\u5148\u5728\u8282\u70B9\u914D\u7F6E\u4E2D\u9009\u62E9\u6A21\u578B"
14352
+ );
14353
+ }
14354
+ const provider = new OpenAIProvider({ apiKey, baseURL: eff.baseURL });
14355
+ const raw = await provider.chat(
14356
+ model,
14357
+ [
14358
+ { role: "system", content: GEN_COMMIT_MESSAGE_INSTRUCTIONS },
14359
+ {
14360
+ role: "user",
14361
+ 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
14362
+
14363
+ ${clipped}`
14364
+ }
14365
+ ],
14366
+ {},
14367
+ { reqId: `llm_commit_${Date.now()}_${genReqId2()}`, logTime: (/* @__PURE__ */ new Date()).toISOString() },
14368
+ // 不设 maxTokens:推理模型的思考会占用输出配额,过小会导致 content 为空;
14369
+ // 提示词已约束只输出一行短说明。
14370
+ {}
14371
+ );
14372
+ const message = cleanCommitMessage(raw ?? "");
14373
+ if (!message) throw new ApiError(502, "llm_empty", "AI \u672A\u8FD4\u56DE\u6709\u6548\u7684\u63D0\u4EA4\u8BF4\u660E\uFF0C\u8BF7\u91CD\u8BD5");
14374
+ return message;
14375
+ }
14376
+
13903
14377
  // src/server/toolsCatalog.ts
13904
14378
  init_esm_shims();
13905
14379
  function buildToolCatalog(workspace) {
@@ -14005,11 +14479,11 @@ function projectStats(manager, tenantId) {
14005
14479
 
14006
14480
  // src/server/nodeHost.ts
14007
14481
  init_esm_shims();
14008
- import * as fs22 from "fs";
14482
+ import * as fs23 from "fs";
14009
14483
  import * as os6 from "os";
14010
14484
  function diskInfo(pathStr) {
14011
14485
  try {
14012
- const st = fs22.statfsSync(pathStr);
14486
+ const st = fs23.statfsSync(pathStr);
14013
14487
  return {
14014
14488
  path: pathStr,
14015
14489
  total: Number(st.blocks) * Number(st.bsize),
@@ -14081,7 +14555,7 @@ function buildNodeHost(store, manager, diskPath) {
14081
14555
  arch: process.arch,
14082
14556
  release: os6.release(),
14083
14557
  uptimeSec: Math.round(os6.uptime()),
14084
- version: "0.2.5",
14558
+ version: "0.2.7",
14085
14559
  mode: process.env.NODE_MODE ?? "standalone",
14086
14560
  cpu: {
14087
14561
  count: cpus2.length,
@@ -14656,7 +15130,7 @@ function createApp(store, manager, opts = {}) {
14656
15130
  applyProvidersPatch(tenant, raw);
14657
15131
  if (typeof raw.workspaceRoot === "string") {
14658
15132
  const ws = raw.workspaceRoot.trim();
14659
- if (ws) tenant.workspaceRoot = path28.resolve(ws);
15133
+ if (ws) tenant.workspaceRoot = path30.resolve(ws);
14660
15134
  else delete tenant.workspaceRoot;
14661
15135
  }
14662
15136
  if (typeof raw.name === "string" && raw.name.trim()) {
@@ -15065,6 +15539,25 @@ function createApp(store, manager, opts = {}) {
15065
15539
  }
15066
15540
  }
15067
15541
  },
15542
+ {
15543
+ method: "POST",
15544
+ pattern: ["git", "commit-message"],
15545
+ fn: async (_req, res, { tenant, body }) => {
15546
+ const raw = body ?? {};
15547
+ try {
15548
+ const message = await generateGitCommitMessage({
15549
+ store,
15550
+ tenant,
15551
+ workspaceRoot: tenant.workspaceRoot ?? null,
15552
+ repo: String(raw.repo ?? "") || void 0
15553
+ });
15554
+ ok(res, { message });
15555
+ } catch (e) {
15556
+ if (e instanceof ApiError) throw e;
15557
+ throw new ApiError(400, "bad_request", e instanceof Error ? e.message : String(e));
15558
+ }
15559
+ }
15560
+ },
15068
15561
  {
15069
15562
  method: "POST",
15070
15563
  pattern: ["git", "checkout"],
@@ -15588,10 +16081,10 @@ function createApp(store, manager, opts = {}) {
15588
16081
  function handle(req2, res) {
15589
16082
  const startedAt2 = Date.now();
15590
16083
  const url = new URL(req2.url ?? "/", "http://localhost");
15591
- const path42 = url.pathname;
16084
+ const path44 = url.pathname;
15592
16085
  const method = req2.method ?? "GET";
15593
16086
  console.log(
15594
- `[api] ${(/* @__PURE__ */ new Date()).toISOString()} \u2192 ${method} ${path42}${url.search}`
16087
+ `[api] ${(/* @__PURE__ */ new Date()).toISOString()} \u2192 ${method} ${path44}${url.search}`
15595
16088
  );
15596
16089
  let tenantId = null;
15597
16090
  let logged = false;
@@ -15599,7 +16092,7 @@ function createApp(store, manager, opts = {}) {
15599
16092
  if (logged) return;
15600
16093
  logged = true;
15601
16094
  console.log(
15602
- `[api] ${(/* @__PURE__ */ new Date()).toISOString()} \u2190 ${method} ${path42} ${extra} (${Date.now() - startedAt2}ms) tenant=${tenantId ?? "-"}`
16095
+ `[api] ${(/* @__PURE__ */ new Date()).toISOString()} \u2190 ${method} ${path44} ${extra} (${Date.now() - startedAt2}ms) tenant=${tenantId ?? "-"}`
15603
16096
  );
15604
16097
  };
15605
16098
  res.on("finish", () => logDone(`HTTP ${res.statusCode}`));
@@ -15610,16 +16103,16 @@ function createApp(store, manager, opts = {}) {
15610
16103
  res.end();
15611
16104
  return;
15612
16105
  }
15613
- if (req2.method === "GET" && path42 === "/healthz") {
16106
+ if (req2.method === "GET" && path44 === "/healthz") {
15614
16107
  json(res, 200, {
15615
16108
  status: "ok",
15616
16109
  // 版本让运维/探针能确认线上跑的是哪个构建(更新与回滚据此判断)
15617
- version: "0.2.5",
16110
+ version: "0.2.7",
15618
16111
  features: { mcp: hasServerMcpConfig() }
15619
16112
  });
15620
16113
  return;
15621
16114
  }
15622
- if (hubProxyEnabled(opts.host) && isHubProxyPath(path42)) {
16115
+ if (hubProxyEnabled(opts.host) && isHubProxyPath(path44)) {
15623
16116
  void handleHubProxy(req2, res).catch(() => {
15624
16117
  if (!res.headersSent) {
15625
16118
  res.writeHead(502, { "Content-Type": "application/json; charset=utf-8" });
@@ -15630,11 +16123,11 @@ function createApp(store, manager, opts = {}) {
15630
16123
  });
15631
16124
  return;
15632
16125
  }
15633
- if (!path42.startsWith(API_PREFIX)) {
16126
+ if (!path44.startsWith(API_PREFIX)) {
15634
16127
  json(res, 404, { error: { code: "not_found", message: "\u672A\u77E5\u8DEF\u5F84" } });
15635
16128
  return;
15636
16129
  }
15637
- const segs = path42.slice(API_PREFIX.length).split("/").filter(Boolean);
16130
+ const segs = path44.slice(API_PREFIX.length).split("/").filter(Boolean);
15638
16131
  const apiKey = extractApiKey(req2);
15639
16132
  if (!apiKey) {
15640
16133
  json(res, 401, {
@@ -15699,7 +16192,7 @@ function createApp(store, manager, opts = {}) {
15699
16192
  ).catch((err) => {
15700
16193
  const msg = err instanceof Error ? err.message : String(err);
15701
16194
  console.log(
15702
- `[api] ${(/* @__PURE__ */ new Date()).toISOString()} ! ${method} ${path42} ERROR: ${msg}`
16195
+ `[api] ${(/* @__PURE__ */ new Date()).toISOString()} ! ${method} ${path44} ERROR: ${msg}`
15703
16196
  );
15704
16197
  error(res, err);
15705
16198
  });
@@ -16214,6 +16707,17 @@ async function dispatchFleetCommand(deps, env3) {
16214
16707
  )
16215
16708
  );
16216
16709
  }
16710
+ case "git_commit_message": {
16711
+ const t = project();
16712
+ return ok2({
16713
+ message: await generateGitCommitMessage({
16714
+ store,
16715
+ tenant: t,
16716
+ workspaceRoot: t.workspaceRoot ?? null,
16717
+ repo: gitRepo(payload.repo)
16718
+ })
16719
+ });
16720
+ }
16217
16721
  case "git_checkout": {
16218
16722
  const t = project();
16219
16723
  return ok2(
@@ -16854,6 +17358,18 @@ var READONLY_CMD_TYPES = /* @__PURE__ */ new Set([
16854
17358
  "git_show"
16855
17359
  ]);
16856
17360
  var CMD_CACHE_TTL_MS = 1500;
17361
+ var STATE_CHANGING_EVENTS = /* @__PURE__ */ new Set([
17362
+ "turn_start",
17363
+ "turn_end",
17364
+ "permission_required",
17365
+ "ask_user_question",
17366
+ "plan_proposed",
17367
+ "directory_requested",
17368
+ "error",
17369
+ "interrupted",
17370
+ "queue_steering",
17371
+ "session_named"
17372
+ ]);
16857
17373
  var READONLY_CONCURRENCY = 4;
16858
17374
  var BATCH_MAX_SUBS = 64;
16859
17375
  var BATCH_SUB_TIMEOUT_MS = 7e3;
@@ -17089,6 +17605,20 @@ var FleetNodeBridge = class {
17089
17605
  this.writeEpoch += 1;
17090
17606
  this.cmdResultCache.clear();
17091
17607
  }
17608
+ /**
17609
+ * 会话快照缓存失效(事件/通知驱动的状态变化)。
17610
+ *
17611
+ * 与写操作不同,这些变化只影响会话相关查询;文件树 / Git 等重查询的缓存
17612
+ * 保持有效,避免多会话并行运行时把整个只读缓存反复清空。
17613
+ */
17614
+ _invalidateSessionCache() {
17615
+ this.writeEpoch += 1;
17616
+ for (const key of [...this.cmdResultCache.keys()]) {
17617
+ if (/^(get_state|get_events|list_sessions|get_session_states)\|/.test(key)) {
17618
+ this.cmdResultCache.delete(key);
17619
+ }
17620
+ }
17621
+ }
17092
17622
  close() {
17093
17623
  this.closing = true;
17094
17624
  if (this.usageTimer) {
@@ -17117,6 +17647,7 @@ var FleetNodeBridge = class {
17117
17647
  }
17118
17648
  /** 通知广播(SessionManager 注入的 notify 回调) */
17119
17649
  notify(tenantId, ev) {
17650
+ this._invalidateSessionCache();
17120
17651
  const payload = ev.kind === "approval_request" ? {
17121
17652
  ...ev,
17122
17653
  projectId: tenantId,
@@ -17129,6 +17660,7 @@ var FleetNodeBridge = class {
17129
17660
  }
17130
17661
  /** 事件桥接(SessionManager 注入的 onEvent 回调);发布按窗口批量合并 */
17131
17662
  onEvent(tenantId, sessionId, entry) {
17663
+ if (STATE_CHANGING_EVENTS.has(entry.type)) this._invalidateSessionCache();
17132
17664
  if (entry.type === "turn_end") this.stats.turns += 1;
17133
17665
  else if (entry.type === "tool_finished") this.stats.toolCalls += 1;
17134
17666
  else if (entry.type === "error") this.stats.errors += 1;
@@ -17213,7 +17745,7 @@ var FleetNodeBridge = class {
17213
17745
  label: this.opts.deps.store.loadNodeLabel() ?? void 0,
17214
17746
  platform: process.platform,
17215
17747
  mode: process.env.NODE_MODE ?? "standalone",
17216
- version: "0.2.5",
17748
+ version: "0.2.7",
17217
17749
  ip: primaryIp()
17218
17750
  };
17219
17751
  }
@@ -17247,7 +17779,10 @@ var FleetNodeBridge = class {
17247
17779
  sessionId: s.sessionId,
17248
17780
  name: s.name ?? null,
17249
17781
  status: s.status,
17250
- lastUserMessage: lastUserMessage(s)
17782
+ lastUserMessage: lastUserMessage(s),
17783
+ // 控制面的聚合邮件靠它判断「这台机器最近有没有在干活」;
17784
+ // 状态跃迁可能被 info 节流跳过,完成时间不会。
17785
+ lastCompletedAt: s.lastCompletedAt ?? null
17251
17786
  });
17252
17787
  }
17253
17788
  } catch {
@@ -17265,7 +17800,7 @@ var FleetNodeBridge = class {
17265
17800
  label: this.opts.deps.store.loadNodeLabel() ?? void 0,
17266
17801
  platform: process.platform,
17267
17802
  mode: process.env.NODE_MODE ?? "standalone",
17268
- version: "0.2.5",
17803
+ version: "0.2.7",
17269
17804
  ip: primaryIp(),
17270
17805
  projects,
17271
17806
  totals,
@@ -18034,8 +18569,8 @@ init_esm_shims();
18034
18569
 
18035
18570
  // src/server/qq/bindings.ts
18036
18571
  init_esm_shims();
18037
- import * as fs23 from "fs";
18038
- import * as path29 from "path";
18572
+ import * as fs24 from "fs";
18573
+ import * as path31 from "path";
18039
18574
  function conversationKey(target) {
18040
18575
  switch (target.kind) {
18041
18576
  case "c2c":
@@ -18074,12 +18609,12 @@ var QqBindingsStore = class _QqBindingsStore {
18074
18609
  cache = null;
18075
18610
  /** 基于 DATA_DIR 构造 */
18076
18611
  static in(dataDir) {
18077
- return new _QqBindingsStore(path29.join(dataDir, "qq", "bindings.json"));
18612
+ return new _QqBindingsStore(path31.join(dataDir, "qq", "bindings.json"));
18078
18613
  }
18079
18614
  load() {
18080
18615
  if (this.cache) return this.cache;
18081
18616
  try {
18082
- const raw = JSON.parse(fs23.readFileSync(this.file, "utf-8"));
18617
+ const raw = JSON.parse(fs24.readFileSync(this.file, "utf-8"));
18083
18618
  this.cache = {
18084
18619
  conversations: raw.conversations ?? {}
18085
18620
  };
@@ -18111,8 +18646,8 @@ var QqBindingsStore = class _QqBindingsStore {
18111
18646
  return null;
18112
18647
  }
18113
18648
  persist() {
18114
- fs23.mkdirSync(path29.dirname(this.file), { recursive: true });
18115
- fs23.writeFileSync(this.file, JSON.stringify(this.cache, null, 2), "utf-8");
18649
+ fs24.mkdirSync(path31.dirname(this.file), { recursive: true });
18650
+ fs24.writeFileSync(this.file, JSON.stringify(this.cache, null, 2), "utf-8");
18116
18651
  }
18117
18652
  };
18118
18653
 
@@ -18524,8 +19059,8 @@ ${lines.join("\n")}`, msgId);
18524
19059
 
18525
19060
  // src/server/qq/QqManager.ts
18526
19061
  init_esm_shims();
18527
- import * as fs24 from "fs";
18528
- import * as path30 from "path";
19062
+ import * as fs25 from "fs";
19063
+ import * as path32 from "path";
18529
19064
  var QqManager = class {
18530
19065
  constructor(opts) {
18531
19066
  this.opts = opts;
@@ -18534,7 +19069,7 @@ var QqManager = class {
18534
19069
  adapter = null;
18535
19070
  config = null;
18536
19071
  get configFile() {
18537
- return path30.join(this.opts.dataDir, "qq", "config.json");
19072
+ return path32.join(this.opts.dataDir, "qq", "config.json");
18538
19073
  }
18539
19074
  /** 启动:读配置(env 优先,其次文件),有则建连接 */
18540
19075
  start() {
@@ -18572,7 +19107,7 @@ var QqManager = class {
18572
19107
  /** 清空配置:删除文件 + 断开 */
18573
19108
  clearConfig() {
18574
19109
  try {
18575
- fs24.rmSync(this.configFile, { force: true });
19110
+ fs25.rmSync(this.configFile, { force: true });
18576
19111
  } catch {
18577
19112
  }
18578
19113
  this.stop();
@@ -18614,7 +19149,7 @@ var QqManager = class {
18614
19149
  }
18615
19150
  _readConfigFile() {
18616
19151
  try {
18617
- const raw = JSON.parse(fs24.readFileSync(this.configFile, "utf-8"));
19152
+ const raw = JSON.parse(fs25.readFileSync(this.configFile, "utf-8"));
18618
19153
  if (raw.appId && raw.appSecret) {
18619
19154
  return {
18620
19155
  appId: raw.appId,
@@ -18627,18 +19162,18 @@ var QqManager = class {
18627
19162
  return null;
18628
19163
  }
18629
19164
  _persistConfig(cfg) {
18630
- fs24.mkdirSync(path30.dirname(this.configFile), { recursive: true });
18631
- fs24.writeFileSync(this.configFile, JSON.stringify(cfg, null, 2), "utf-8");
19165
+ fs25.mkdirSync(path32.dirname(this.configFile), { recursive: true });
19166
+ fs25.writeFileSync(this.configFile, JSON.stringify(cfg, null, 2), "utf-8");
18632
19167
  }
18633
19168
  };
18634
19169
 
18635
19170
  // src/server/qq/pairingCodes.ts
18636
19171
  init_esm_shims();
18637
- import * as crypto5 from "crypto";
19172
+ import * as crypto6 from "crypto";
18638
19173
  var PAIRING_CODE_TTL_MS = 5 * 60 * 1e3;
18639
19174
  function randomCode(len = 8) {
18640
19175
  const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
18641
- const bytes = crypto5.randomBytes(len);
19176
+ const bytes = crypto6.randomBytes(len);
18642
19177
  let out = "";
18643
19178
  for (let i = 0; i < len; i++) {
18644
19179
  out += alphabet[bytes[i] % alphabet.length];
@@ -18676,17 +19211,17 @@ var PairingCodeStore = class {
18676
19211
 
18677
19212
  // src/update/controller.ts
18678
19213
  init_esm_shims();
18679
- import * as fs28 from "fs";
18680
- import * as path35 from "path";
19214
+ import * as fs30 from "fs";
19215
+ import * as path37 from "path";
18681
19216
 
18682
19217
  // src/update/downloader.ts
18683
19218
  init_esm_shims();
18684
19219
  import { createHash as createHash3 } from "crypto";
18685
- import * as fs25 from "fs";
19220
+ import * as fs26 from "fs";
18686
19221
  import { Readable as Readable2 } from "stream";
18687
19222
  import { pipeline as pipeline2 } from "stream/promises";
18688
19223
  import { spawnSync as spawnSync3 } from "child_process";
18689
- import * as path31 from "path";
19224
+ import * as path33 from "path";
18690
19225
 
18691
19226
  // src/update/manifest.ts
18692
19227
  init_esm_shims();
@@ -18861,8 +19396,8 @@ async function fetchManifest(url, publicKey, opts = {}) {
18861
19396
  async function downloadBinary(bin, destPath, log = () => {
18862
19397
  }) {
18863
19398
  const partPath = `${destPath}.part`;
18864
- fs25.mkdirSync(path31.dirname(destPath), { recursive: true });
18865
- fs25.rmSync(partPath, { force: true });
19399
+ fs26.mkdirSync(path33.dirname(destPath), { recursive: true });
19400
+ fs26.rmSync(partPath, { force: true });
18866
19401
  let res;
18867
19402
  try {
18868
19403
  res = await fetch(bin.url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS2) });
@@ -18887,25 +19422,25 @@ async function downloadBinary(bin, destPath, log = () => {
18887
19422
  yield chunk;
18888
19423
  }
18889
19424
  },
18890
- fs25.createWriteStream(partPath, { mode: 493 })
19425
+ fs26.createWriteStream(partPath, { mode: 493 })
18891
19426
  );
18892
19427
  } catch (err) {
18893
- fs25.rmSync(partPath, { force: true });
19428
+ fs26.rmSync(partPath, { force: true });
18894
19429
  return { error: `\u4E0B\u8F7D\u4E2D\u65AD\uFF1A${err.message}` };
18895
19430
  }
18896
19431
  const actual = hash.digest("hex");
18897
19432
  if (actual !== bin.sha256) {
18898
- fs25.rmSync(partPath, { force: true });
19433
+ fs26.rmSync(partPath, { force: true });
18899
19434
  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` };
18900
19435
  }
18901
19436
  if (expected && bytes !== expected) {
18902
- fs25.rmSync(partPath, { force: true });
19437
+ fs26.rmSync(partPath, { force: true });
18903
19438
  return { error: `\u4EA7\u7269\u5927\u5C0F\u4E0D\u5339\u914D\uFF08\u671F\u671B ${expected}\uFF0C\u5B9E\u9645 ${bytes}\uFF09` };
18904
19439
  }
18905
- fs25.rmSync(destPath, { force: true });
18906
- fs25.renameSync(partPath, destPath);
18907
- fs25.chmodSync(destPath, 493);
18908
- log(`\u5DF2\u4E0B\u8F7D ${path31.basename(destPath)}\uFF08${Math.round(bytes / 1024 / 1024)}MB\uFF0C\u6821\u9A8C\u901A\u8FC7\uFF09`);
19440
+ fs26.rmSync(destPath, { force: true });
19441
+ fs26.renameSync(partPath, destPath);
19442
+ fs26.chmodSync(destPath, 493);
19443
+ log(`\u5DF2\u4E0B\u8F7D ${path33.basename(destPath)}\uFF08${Math.round(bytes / 1024 / 1024)}MB\uFF0C\u6821\u9A8C\u901A\u8FC7\uFF09`);
18909
19444
  return { bytes };
18910
19445
  }
18911
19446
  function selfCheck(binPath, expectedVersion) {
@@ -18929,10 +19464,11 @@ function selfCheck(binPath, expectedVersion) {
18929
19464
 
18930
19465
  // src/update/platform.ts
18931
19466
  init_esm_shims();
18932
- import * as path32 from "path";
19467
+ import * as fs27 from "fs";
19468
+ import * as path34 from "path";
18933
19469
  import { createRequire as createRequire3 } from "module";
18934
19470
  function currentVersion() {
18935
- const injected = "0.2.5"?.trim();
19471
+ const injected = "0.2.7"?.trim();
18936
19472
  if (injected) return injected;
18937
19473
  try {
18938
19474
  const req2 = createRequire3(import.meta.url);
@@ -18944,12 +19480,36 @@ function currentVersion() {
18944
19480
  }
18945
19481
  function isSeaRuntime() {
18946
19482
  if (process.env.ZQAGENT_SEA === "1") return true;
18947
- const base = path32.basename(process.execPath).toLowerCase().replace(/\.exe$/, "");
19483
+ const base = path34.basename(process.execPath).toLowerCase().replace(/\.exe$/, "");
18948
19484
  return base !== "node" && base !== "nodejs";
18949
19485
  }
19486
+ var DEFAULT_SERVICE_NAME = "zqagent";
19487
+ function serviceManagerFromCgroup(cgroup, serviceName = DEFAULT_SERVICE_NAME) {
19488
+ const unitName = `${serviceName}.service`;
19489
+ for (const line of cgroup.split("\n")) {
19490
+ const idx = line.lastIndexOf(":");
19491
+ if (idx < 0) continue;
19492
+ const unitPath = line.slice(idx + 1).trim();
19493
+ if (unitPath.split("/").includes(unitName)) return "systemd";
19494
+ }
19495
+ return null;
19496
+ }
19497
+ function detectServiceManager() {
19498
+ if (process.platform !== "linux") return null;
19499
+ const serviceName = process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;
19500
+ try {
19501
+ return serviceManagerFromCgroup(
19502
+ fs27.readFileSync("/proc/self/cgroup", "utf8"),
19503
+ serviceName
19504
+ );
19505
+ } catch {
19506
+ return null;
19507
+ }
19508
+ }
18950
19509
  function serviceManager() {
18951
19510
  const v = process.env.ZQAGENT_SERVICE_MANAGER?.trim().toLowerCase();
18952
- return v === "systemd" || v === "launchd" ? v : null;
19511
+ if (v === "systemd" || v === "launchd") return v;
19512
+ return detectServiceManager();
18953
19513
  }
18954
19514
  function selfUpdateCapability(hasPublicKey) {
18955
19515
  if (!isSeaRuntime()) {
@@ -18972,22 +19532,22 @@ function selfUpdateCapability(hasPublicKey) {
18972
19532
 
18973
19533
  // src/update/installer.ts
18974
19534
  init_esm_shims();
18975
- import * as fs27 from "fs";
18976
- import * as path34 from "path";
19535
+ import * as fs29 from "fs";
19536
+ import * as path36 from "path";
18977
19537
 
18978
19538
  // src/update/state.ts
18979
19539
  init_esm_shims();
18980
- import * as fs26 from "fs";
18981
- import * as path33 from "path";
19540
+ import * as fs28 from "fs";
19541
+ import * as path35 from "path";
18982
19542
  function updatesDir(dataDir) {
18983
- return path33.join(dataDir, "updates");
19543
+ return path35.join(dataDir, "updates");
18984
19544
  }
18985
19545
  function statePath(dataDir) {
18986
- return path33.join(updatesDir(dataDir), "state.json");
19546
+ return path35.join(updatesDir(dataDir), "state.json");
18987
19547
  }
18988
19548
  function loadState(dataDir) {
18989
19549
  try {
18990
- const raw = fs26.readFileSync(statePath(dataDir), "utf-8");
19550
+ const raw = fs28.readFileSync(statePath(dataDir), "utf-8");
18991
19551
  const parsed = JSON.parse(raw);
18992
19552
  if (typeof parsed === "object" && parsed !== null) return parsed;
18993
19553
  } catch {
@@ -18996,11 +19556,11 @@ function loadState(dataDir) {
18996
19556
  }
18997
19557
  function saveState(dataDir, state) {
18998
19558
  const dir = updatesDir(dataDir);
18999
- fs26.mkdirSync(dir, { recursive: true });
19559
+ fs28.mkdirSync(dir, { recursive: true });
19000
19560
  const file = statePath(dataDir);
19001
19561
  const tmp = `${file}.tmp`;
19002
- fs26.writeFileSync(tmp, JSON.stringify(state, null, 2), "utf-8");
19003
- fs26.renameSync(tmp, file);
19562
+ fs28.writeFileSync(tmp, JSON.stringify(state, null, 2), "utf-8");
19563
+ fs28.renameSync(tmp, file);
19004
19564
  }
19005
19565
  function patchState(dataDir, patch) {
19006
19566
  const next = { ...loadState(dataDir), ...patch };
@@ -19010,36 +19570,36 @@ function patchState(dataDir, patch) {
19010
19570
 
19011
19571
  // src/update/installer.ts
19012
19572
  function backupDir(dataDir) {
19013
- return path34.join(updatesDir(dataDir), "backup");
19573
+ return path36.join(updatesDir(dataDir), "backup");
19014
19574
  }
19015
19575
  function backupCurrent(dataDir, execPath, version) {
19016
19576
  const dir = backupDir(dataDir);
19017
- fs27.mkdirSync(dir, { recursive: true });
19018
- const target = path34.join(dir, `zqagent-${version}${process.platform === "win32" ? ".exe" : ""}`);
19019
- fs27.copyFileSync(execPath, target);
19020
- fs27.chmodSync(target, 493);
19577
+ fs29.mkdirSync(dir, { recursive: true });
19578
+ const target = path36.join(dir, `zqagent-${version}${process.platform === "win32" ? ".exe" : ""}`);
19579
+ fs29.copyFileSync(execPath, target);
19580
+ fs29.chmodSync(target, 493);
19021
19581
  return target;
19022
19582
  }
19023
19583
  function replaceExecutable(execPath, stagedPath) {
19024
19584
  if (process.platform === "win32") {
19025
19585
  const oldPath = `${execPath}.old`;
19026
- fs27.rmSync(oldPath, { force: true });
19027
- fs27.renameSync(execPath, oldPath);
19028
- fs27.copyFileSync(stagedPath, execPath);
19029
- fs27.chmodSync(execPath, 493);
19586
+ fs29.rmSync(oldPath, { force: true });
19587
+ fs29.renameSync(execPath, oldPath);
19588
+ fs29.copyFileSync(stagedPath, execPath);
19589
+ fs29.chmodSync(execPath, 493);
19030
19590
  return;
19031
19591
  }
19032
19592
  const tmp = `${execPath}.new`;
19033
- fs27.rmSync(tmp, { force: true });
19593
+ fs29.rmSync(tmp, { force: true });
19034
19594
  try {
19035
- fs27.copyFileSync(stagedPath, tmp);
19036
- fs27.chmodSync(tmp, 493);
19037
- fs27.renameSync(tmp, execPath);
19595
+ fs29.copyFileSync(stagedPath, tmp);
19596
+ fs29.chmodSync(tmp, 493);
19597
+ fs29.renameSync(tmp, execPath);
19038
19598
  } catch (err) {
19039
- fs27.rmSync(tmp, { force: true });
19599
+ fs29.rmSync(tmp, { force: true });
19040
19600
  throw err;
19041
19601
  }
19042
- fs27.rmSync(stagedPath, { force: true });
19602
+ fs29.rmSync(stagedPath, { force: true });
19043
19603
  }
19044
19604
  function installStaged(opts) {
19045
19605
  const log = opts.log ?? (() => {
@@ -19047,7 +19607,7 @@ function installStaged(opts) {
19047
19607
  const from = currentVersion();
19048
19608
  log(`\u6B63\u5728\u5B89\u88C5 v${opts.version}\uFF08\u5F53\u524D v${from}\uFF09`);
19049
19609
  const backupPath = backupCurrent(opts.dataDir, opts.execPath, from);
19050
- log(`\u5DF2\u5907\u4EFD\u5F53\u524D\u7248\u672C\u5230 ${path34.basename(backupPath)}`);
19610
+ log(`\u5DF2\u5907\u4EFD\u5F53\u524D\u7248\u672C\u5230 ${path36.basename(backupPath)}`);
19051
19611
  replaceExecutable(opts.execPath, opts.stagedPath);
19052
19612
  return patchState(opts.dataDir, {
19053
19613
  stagedVersion: void 0,
@@ -19066,18 +19626,18 @@ function rollback(dataDir, execPath, log = () => {
19066
19626
  }) {
19067
19627
  const state = readRollbackTarget(dataDir);
19068
19628
  if (!state?.backupPath || !state.backupVersion) return null;
19069
- if (!fs27.existsSync(state.backupPath)) return null;
19629
+ if (!fs29.existsSync(state.backupPath)) return null;
19070
19630
  log(`\u6B63\u5728\u56DE\u6EDA\u5230 v${state.backupVersion}`);
19071
19631
  if (process.platform === "win32") {
19072
- fs27.rmSync(`${execPath}.old`, { force: true });
19073
- fs27.renameSync(execPath, `${execPath}.old`);
19074
- fs27.copyFileSync(state.backupPath, execPath);
19075
- fs27.chmodSync(execPath, 493);
19632
+ fs29.rmSync(`${execPath}.old`, { force: true });
19633
+ fs29.renameSync(execPath, `${execPath}.old`);
19634
+ fs29.copyFileSync(state.backupPath, execPath);
19635
+ fs29.chmodSync(execPath, 493);
19076
19636
  } else {
19077
19637
  const tmp = `${execPath}.rollback`;
19078
- fs27.copyFileSync(state.backupPath, tmp);
19079
- fs27.chmodSync(tmp, 493);
19080
- fs27.renameSync(tmp, execPath);
19638
+ fs29.copyFileSync(state.backupPath, tmp);
19639
+ fs29.chmodSync(tmp, 493);
19640
+ fs29.renameSync(tmp, execPath);
19081
19641
  }
19082
19642
  patchState(dataDir, {
19083
19643
  pendingVersion: state.backupVersion,
@@ -19091,7 +19651,7 @@ function rollback(dataDir, execPath, log = () => {
19091
19651
  }
19092
19652
  function readRollbackTarget(dataDir) {
19093
19653
  try {
19094
- const raw = fs27.readFileSync(path34.join(updatesDir(dataDir), "state.json"), "utf-8");
19654
+ const raw = fs29.readFileSync(path36.join(updatesDir(dataDir), "state.json"), "utf-8");
19095
19655
  const s = JSON.parse(raw);
19096
19656
  if (s.backupPath && s.backupVersion) return s;
19097
19657
  } catch {
@@ -19174,7 +19734,7 @@ var UpdateController = class {
19174
19734
  reconcile() {
19175
19735
  const state = loadState(this.opts.dataDir);
19176
19736
  const current = this.currentVersion;
19177
- if (state.stagedPath && !fs28.existsSync(state.stagedPath)) {
19737
+ if (state.stagedPath && !fs30.existsSync(state.stagedPath)) {
19178
19738
  patchState(this.opts.dataDir, { stagedVersion: void 0, stagedPath: void 0 });
19179
19739
  }
19180
19740
  const patch = { runningVersion: current };
@@ -19189,7 +19749,7 @@ var UpdateController = class {
19189
19749
  }
19190
19750
  patchState(this.opts.dataDir, patch);
19191
19751
  if (process.platform === "win32") {
19192
- fs28.rmSync(`${process.execPath}.old`, { force: true });
19752
+ fs30.rmSync(`${process.execPath}.old`, { force: true });
19193
19753
  }
19194
19754
  }
19195
19755
  schedule(delayMs) {
@@ -19265,8 +19825,8 @@ var UpdateController = class {
19265
19825
  const bin = manifest.binaries[platformKey()];
19266
19826
  if (!bin) return `\u6E05\u5355\u6CA1\u6709\u5F53\u524D\u5E73\u53F0\uFF08${platformKey()}\uFF09\u7684\u4EA7\u7269`;
19267
19827
  let stagedPath = state.stagedPath;
19268
- if (!(state.stagedVersion === manifest.version && stagedPath && fs28.existsSync(stagedPath))) {
19269
- stagedPath = path35.join(
19828
+ if (!(state.stagedVersion === manifest.version && stagedPath && fs30.existsSync(stagedPath))) {
19829
+ stagedPath = path37.join(
19270
19830
  updatesDir(this.opts.dataDir),
19271
19831
  "staged",
19272
19832
  `zqagent-${manifest.version}${process.platform === "win32" ? ".exe" : ""}`
@@ -19278,7 +19838,7 @@ var UpdateController = class {
19278
19838
  }
19279
19839
  const selfTest = selfCheck(stagedPath, manifest.version);
19280
19840
  if (!selfTest.ok) {
19281
- fs28.rmSync(stagedPath, { force: true });
19841
+ fs30.rmSync(stagedPath, { force: true });
19282
19842
  this.recordFailure(manifest.version, selfTest.error ?? "\u81EA\u68C0\u5931\u8D25");
19283
19843
  return `\u5B89\u88C5\u5305\u81EA\u68C0\u5931\u8D25\uFF1A${selfTest.error}`;
19284
19844
  }
@@ -19419,7 +19979,7 @@ function pemFile(key) {
19419
19979
  const p = env2(key);
19420
19980
  if (!p) return void 0;
19421
19981
  try {
19422
- return fs29.readFileSync(p);
19982
+ return fs31.readFileSync(p);
19423
19983
  } catch (e) {
19424
19984
  console.warn(`[tls] \u8BFB\u53D6 ${key}=${p} \u5931\u8D25: ${e instanceof Error ? e.message : e}`);
19425
19985
  return void 0;
@@ -19441,7 +20001,7 @@ function seedDemoTenant(store) {
19441
20001
  apiKey: env2("OPENAI_API_KEY"),
19442
20002
  baseURL: env2("OPENAI_BASE_URL")
19443
20003
  },
19444
- workspaceRoot: workspace ? path36.resolve(workspace) : void 0,
20004
+ workspaceRoot: workspace ? path38.resolve(workspace) : void 0,
19445
20005
  allowedCommands: []
19446
20006
  };
19447
20007
  store.saveTenant(demo);
@@ -19495,9 +20055,9 @@ function syncTenantModels(store) {
19495
20055
  tenant.model = model;
19496
20056
  changed = true;
19497
20057
  }
19498
- if (workspace && tenant.workspaceRoot !== path36.resolve(workspace)) {
19499
- console.log(`[sync] \u79DF\u6237 ${id} workspaceRoot \u5DF2\u540C\u6B65: ${tenant.workspaceRoot ?? "(\u6C99\u7BB1)"} \u2192 ${path36.resolve(workspace)}`);
19500
- tenant.workspaceRoot = path36.resolve(workspace);
20058
+ if (workspace && tenant.workspaceRoot !== path38.resolve(workspace)) {
20059
+ console.log(`[sync] \u79DF\u6237 ${id} workspaceRoot \u5DF2\u540C\u6B65: ${tenant.workspaceRoot ?? "(\u6C99\u7BB1)"} \u2192 ${path38.resolve(workspace)}`);
20060
+ tenant.workspaceRoot = path38.resolve(workspace);
19501
20061
  changed = true;
19502
20062
  }
19503
20063
  if (apiKey && tenant.provider.apiKey !== apiKey) {
@@ -19529,7 +20089,7 @@ function exitWhenOrphaned() {
19529
20089
  timer.unref();
19530
20090
  }
19531
20091
  function startServer(opts = {}) {
19532
- const dataDir = path36.resolve(opts.dataDir ?? env2("DATA_DIR") ?? DEFAULT_DATA_DIR);
20092
+ const dataDir = path38.resolve(opts.dataDir ?? env2("DATA_DIR") ?? DEFAULT_DATA_DIR);
19533
20093
  const port = opts.port ?? Number(env2("PORT") ?? 8787);
19534
20094
  const host = opts.host ?? env2("HOST") ?? "127.0.0.1";
19535
20095
  exitWhenOrphaned();
@@ -19559,7 +20119,7 @@ function startServer(opts = {}) {
19559
20119
  }
19560
20120
  const adminKey = ensureAdminKey();
19561
20121
  const secretKey = env2("SECRET_KEY");
19562
- const secretBox = secretKey ? SecretBox.fromHex(secretKey) : SecretBox.fromFile(path36.join(dataDir, "secret.key"));
20122
+ const secretBox = secretKey ? SecretBox.fromHex(secretKey) : SecretBox.fromFile(path38.join(dataDir, "secret.key"));
19563
20123
  if (secretKey && !secretBox) {
19564
20124
  console.warn("[crypto] SECRET_KEY \u975E\u6CD5\uFF08\u9700 64 \u4F4D hex\uFF09\uFF0C\u901A\u77E5\u51ED\u636E\u5C06\u9000\u5316\u4E3A\u660E\u6587");
19565
20125
  }
@@ -19788,7 +20348,7 @@ function printReady(info, qq) {
19788
20348
  `zqagent server \u76D1\u542C http://${info.host}:${info.port}`,
19789
20349
  `\u6570\u636E\u76EE\u5F55: ${info.dataDir}`,
19790
20350
  `\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`,
19791
- env2("ZQAGENT_LOG_ADMIN_KEY") === "0" ? `\u7BA1\u7406\u5458\u5BC6\u94A5: ${maskKey(info.adminKey)}\uFF08\u5DF2\u8131\u654F\uFF0C\u5B8C\u6574\u503C\u89C1 ${path36.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`
20351
+ env2("ZQAGENT_LOG_ADMIN_KEY") === "0" ? `\u7BA1\u7406\u5458\u5BC6\u94A5: ${maskKey(info.adminKey)}\uFF08\u5DF2\u8131\u654F\uFF0C\u5B8C\u6574\u503C\u89C1 ${path38.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`
19792
20352
  ];
19793
20353
  if (info.fleetNodeId) {
19794
20354
  lines.push(
@@ -19809,7 +20369,7 @@ function printReady(info, qq) {
19809
20369
 
19810
20370
  // src/cli/account-enroll.ts
19811
20371
  init_esm_shims();
19812
- import * as path37 from "path";
20372
+ import * as path39 from "path";
19813
20373
  import * as readline from "readline/promises";
19814
20374
  var SKIP_ANSWERS = /* @__PURE__ */ new Set(["-", "skip", "none", "no"]);
19815
20375
  function resolveDefaultHub(hubUrl, env3 = process.env) {
@@ -19821,7 +20381,7 @@ function parseHubAnswer(answer, fallback) {
19821
20381
  return (a || fallback).replace(/\/$/, "");
19822
20382
  }
19823
20383
  function resolveDataDir2(dataDir) {
19824
- return path37.resolve(dataDir ?? process.env.DATA_DIR ?? DEFAULT_DATA_DIR);
20384
+ return path39.resolve(dataDir ?? process.env.DATA_DIR ?? DEFAULT_DATA_DIR);
19825
20385
  }
19826
20386
  async function maybeAccountEnroll(opts = {}) {
19827
20387
  if (!process.stdin.isTTY || !process.stdout.isTTY) return;
@@ -20027,7 +20587,7 @@ async function runServeCommand(argv) {
20027
20587
 
20028
20588
  // src/cli/update.ts
20029
20589
  init_esm_shims();
20030
- import * as path38 from "path";
20590
+ import * as path40 from "path";
20031
20591
  function parse3(argv) {
20032
20592
  const out = { status: false, check: false, now: false, rollback: false };
20033
20593
  for (let i = 0; i < argv.length; i++) {
@@ -20056,7 +20616,7 @@ function restartHint() {
20056
20616
  }
20057
20617
  async function runUpdateCommand(argv) {
20058
20618
  const args = parse3(argv);
20059
- const dataDir = path38.resolve(args.dataDir ?? process.env.DATA_DIR ?? DEFAULT_DATA_DIR);
20619
+ const dataDir = path40.resolve(args.dataDir ?? process.env.DATA_DIR ?? DEFAULT_DATA_DIR);
20060
20620
  const controller = new UpdateController({ dataDir, isIdle: () => true });
20061
20621
  if (args.rollback) {
20062
20622
  const restored = controller.rollbackNow();
@@ -20104,17 +20664,17 @@ async function runUpdateCommand(argv) {
20104
20664
 
20105
20665
  // src/cli/sessionMeta.ts
20106
20666
  init_esm_shims();
20107
- import * as fs30 from "fs";
20108
- import * as path39 from "path";
20667
+ import * as fs32 from "fs";
20668
+ import * as path41 from "path";
20109
20669
  function updateSessionMetaFile(stateDir2, sessionId, patch) {
20110
- const file = path39.join(stateDir2, `${sessionId}_state.json`);
20670
+ const file = path41.join(stateDir2, `${sessionId}_state.json`);
20111
20671
  try {
20112
- const raw = fs30.readFileSync(file, "utf8");
20672
+ const raw = fs32.readFileSync(file, "utf8");
20113
20673
  const state = JSON.parse(raw);
20114
20674
  if (patch.name !== void 0) state.name = patch.name;
20115
20675
  if (patch.nameSource !== void 0) state.nameSource = patch.nameSource;
20116
20676
  if (patch.taskCount !== void 0) state.taskCount = patch.taskCount;
20117
- fs30.writeFileSync(file, JSON.stringify(state, null, 2), "utf8");
20677
+ fs32.writeFileSync(file, JSON.stringify(state, null, 2), "utf8");
20118
20678
  return true;
20119
20679
  } catch {
20120
20680
  return false;
@@ -20123,9 +20683,9 @@ function updateSessionMetaFile(stateDir2, sessionId, patch) {
20123
20683
 
20124
20684
  // src/cli/debug.ts
20125
20685
  init_esm_shims();
20126
- import * as fs31 from "fs";
20686
+ import * as fs33 from "fs";
20127
20687
  import * as os8 from "os";
20128
- import * as path40 from "path";
20688
+ import * as path42 from "path";
20129
20689
  var LOG_NAME = "zqagent-debug.log";
20130
20690
  var TRUTHY = /^(1|true|yes|on)$/i;
20131
20691
  function bootRef() {
@@ -20136,9 +20696,9 @@ var logFile = null;
20136
20696
  var startedAt = Date.now();
20137
20697
  function resolveLogFile() {
20138
20698
  const explicit = process.env.ZQAGENT_DEBUG_LOG;
20139
- if (explicit) return path40.resolve(explicit);
20699
+ if (explicit) return path42.resolve(explicit);
20140
20700
  const dir = process.env.ZQAGENT_DEBUG_DIR;
20141
- return path40.join(dir ? path40.resolve(dir) : os8.tmpdir(), LOG_NAME);
20701
+ return path42.join(dir ? path42.resolve(dir) : os8.tmpdir(), LOG_NAME);
20142
20702
  }
20143
20703
  function enableDebug(argv = process.argv) {
20144
20704
  if (enabled) return true;
@@ -20154,8 +20714,8 @@ function enableDebug(argv = process.argv) {
20154
20714
  enabled = true;
20155
20715
  logFile = resolveLogFile();
20156
20716
  try {
20157
- fs31.mkdirSync(path40.dirname(logFile), { recursive: true });
20158
- fs31.writeFileSync(logFile, "");
20717
+ fs33.mkdirSync(path42.dirname(logFile), { recursive: true });
20718
+ fs33.writeFileSync(logFile, "");
20159
20719
  } catch {
20160
20720
  logFile = null;
20161
20721
  }
@@ -20172,7 +20732,7 @@ function dlog(stage, message = "") {
20172
20732
  const line = `[+${String(Date.now() - startedAt).padStart(6)}ms] [${stage}] ${message}`;
20173
20733
  if (logFile) {
20174
20734
  try {
20175
- fs31.appendFileSync(logFile, line + "\n");
20735
+ fs33.appendFileSync(logFile, line + "\n");
20176
20736
  } catch {
20177
20737
  }
20178
20738
  }
@@ -20332,18 +20892,18 @@ if (enableDebug(process.argv)) {
20332
20892
  logEnvironment();
20333
20893
  }
20334
20894
  var MODEL = process.env.OPENAI_MODEL ?? "gpt-4o";
20335
- var WORKSPACE = path41.resolve(process.env.WORKSPACE ?? ".");
20895
+ var WORKSPACE = path43.resolve(process.env.WORKSPACE ?? ".");
20336
20896
  var MAX_ITERATIONS = parseMaxIterations(process.env.MAX_ITERATIONS, 300);
20337
20897
  var STATE_DIR = stateDir();
20338
20898
  function listSessions() {
20339
20899
  try {
20340
- const files = fs32.readdirSync(STATE_DIR);
20900
+ const files = fs34.readdirSync(STATE_DIR);
20341
20901
  const sessions = [];
20342
20902
  for (const f of files) {
20343
20903
  if (!f.endsWith("_state.json")) continue;
20344
20904
  try {
20345
20905
  const parsed = JSON.parse(
20346
- fs32.readFileSync(path41.join(STATE_DIR, f), "utf8")
20906
+ fs34.readFileSync(path43.join(STATE_DIR, f), "utf8")
20347
20907
  );
20348
20908
  if (parsed && typeof parsed.sessionId === "string") {
20349
20909
  sessions.push(parsed);
@@ -20377,11 +20937,11 @@ function printLastAssistantMessage(messages) {
20377
20937
  out += renderer.flush();
20378
20938
  cliWrite(out + "\n");
20379
20939
  }
20380
- var AGENTS_FILE = path41.join(WORKSPACE, ".agent", "agents.json");
20940
+ var AGENTS_FILE = path43.join(WORKSPACE, ".agent", "agents.json");
20381
20941
  function loadAgentsFromFile() {
20382
20942
  try {
20383
- if (!fs32.existsSync(AGENTS_FILE)) return [];
20384
- const raw = fs32.readFileSync(AGENTS_FILE, "utf8");
20943
+ if (!fs34.existsSync(AGENTS_FILE)) return [];
20944
+ const raw = fs34.readFileSync(AGENTS_FILE, "utf8");
20385
20945
  const parsed = JSON.parse(raw);
20386
20946
  if (!Array.isArray(parsed)) return [];
20387
20947
  return parsed.filter(
@@ -20393,8 +20953,8 @@ function loadAgentsFromFile() {
20393
20953
  }
20394
20954
  function saveAgentsToFile(defs) {
20395
20955
  try {
20396
- fs32.mkdirSync(path41.dirname(AGENTS_FILE), { recursive: true });
20397
- fs32.writeFileSync(AGENTS_FILE, JSON.stringify(defs, null, 2) + "\n", "utf8");
20956
+ fs34.mkdirSync(path43.dirname(AGENTS_FILE), { recursive: true });
20957
+ fs34.writeFileSync(AGENTS_FILE, JSON.stringify(defs, null, 2) + "\n", "utf8");
20398
20958
  } catch (err) {
20399
20959
  cliErr(`\u26A0\uFE0F \u4FDD\u5B58 agents \u914D\u7F6E\u5931\u8D25: ${err}`);
20400
20960
  }
@@ -20403,12 +20963,12 @@ var agentRegistry = new AgentRegistry([
20403
20963
  ...BUILTIN_AGENTS,
20404
20964
  ...loadAgentsFromFile()
20405
20965
  ]);
20406
- var MCP_FILE = path41.join(WORKSPACE, ".agent", "mcp.json");
20407
- var COMMAND_TOOLS_FILE = path41.join(WORKSPACE, ".agent", "tools.json");
20966
+ var MCP_FILE = path43.join(WORKSPACE, ".agent", "mcp.json");
20967
+ var COMMAND_TOOLS_FILE = path43.join(WORKSPACE, ".agent", "tools.json");
20408
20968
  function loadJsonArray(file, validate) {
20409
20969
  try {
20410
- if (!fs32.existsSync(file)) return [];
20411
- const parsed = JSON.parse(fs32.readFileSync(file, "utf8"));
20970
+ if (!fs34.existsSync(file)) return [];
20971
+ const parsed = JSON.parse(fs34.readFileSync(file, "utf8"));
20412
20972
  if (!Array.isArray(parsed)) return [];
20413
20973
  if (!validate) return parsed;
20414
20974
  return parsed.filter((v) => validate(v));
@@ -20418,8 +20978,8 @@ function loadJsonArray(file, validate) {
20418
20978
  }
20419
20979
  function saveJsonFile(file, data) {
20420
20980
  try {
20421
- fs32.mkdirSync(path41.dirname(file), { recursive: true });
20422
- fs32.writeFileSync(file, JSON.stringify(data, null, 2) + "\n", "utf8");
20981
+ fs34.mkdirSync(path43.dirname(file), { recursive: true });
20982
+ fs34.writeFileSync(file, JSON.stringify(data, null, 2) + "\n", "utf8");
20423
20983
  } catch (err) {
20424
20984
  cliErr(`\u26A0\uFE0F \u4FDD\u5B58\u914D\u7F6E\u5931\u8D25: ${err}`);
20425
20985
  }
@@ -20520,7 +21080,7 @@ function pruneEmptySessions(keepSessionId) {
20520
21080
  if (!isEmptySession(s) || s.sessionId === keepSessionId) continue;
20521
21081
  for (const suffix of ["_state.json", ".jsonl"]) {
20522
21082
  try {
20523
- fs32.unlinkSync(path41.join(STATE_DIR, `${s.sessionId}${suffix}`));
21083
+ fs34.unlinkSync(path43.join(STATE_DIR, `${s.sessionId}${suffix}`));
20524
21084
  } catch {
20525
21085
  }
20526
21086
  }
@@ -20710,6 +21270,7 @@ function buildEngine(sessionId, savedState) {
20710
21270
  // 引擎内置表(lookupContextWindow 兜底,见 TurnEngine._contextWindow)
20711
21271
  contextWindow: parseOptionalPositiveNumber(process.env.CONTEXT_WINDOW) ?? modelContextWindow(conn?.modelMeta, model),
20712
21272
  thresholdPct: parseRatio(process.env.COMPACTION_THRESHOLD_PCT),
21273
+ softThresholdPct: parseRatio(process.env.COMPACTION_SOFT_THRESHOLD_PCT),
20713
21274
  capTokens: parseOptionalPositiveNumber(process.env.COMPACTION_CAP_TOKENS) ?? null,
20714
21275
  // 系统提示词按当前 agent 组装(agent prompt + 叙述规则 + 环境信息)
20715
21276
  instructions: agentRegistry.buildSystemPrompt(
@@ -21052,7 +21613,7 @@ async function offerTerminalSetup(engine, canUseAgent) {
21052
21613
  }
21053
21614
  var KNOWN_SUBCOMMANDS = /* @__PURE__ */ new Set(["link", "serve", "update"]);
21054
21615
  function cliVersion() {
21055
- const env3 = "0.2.5"?.trim();
21616
+ const env3 = "0.2.7"?.trim();
21056
21617
  if (env3) return env3;
21057
21618
  try {
21058
21619
  const req2 = createRequire4(import.meta.url);
@@ -21256,8 +21817,8 @@ async function main() {
21256
21817
  }
21257
21818
  });
21258
21819
  const savedState = resumeId ? FileLogHook.loadState(resumeId, STATE_DIR) : null;
21259
- const metaStateFile = path41.join(STATE_DIR, `${SESSION_ID}_state.json`);
21260
- const stateForMeta = savedState ?? (fs32.existsSync(metaStateFile) ? FileLogHook.loadState(SESSION_ID, STATE_DIR) : null);
21820
+ const metaStateFile = path43.join(STATE_DIR, `${SESSION_ID}_state.json`);
21821
+ const stateForMeta = savedState ?? (fs34.existsSync(metaStateFile) ? FileLogHook.loadState(SESSION_ID, STATE_DIR) : null);
21261
21822
  sessionMeta = {
21262
21823
  sessionId: SESSION_ID,
21263
21824
  name: stateForMeta?.name,