@zqagent/core 0.2.15 → 0.2.20
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/README.oss.en.md +13 -0
- package/README.oss.md +11 -0
- package/README.oss.zh-TW.md +11 -0
- package/dist/cli/index.cjs +1832 -130
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +1833 -131
- package/dist/cli/index.js.map +1 -1
- package/dist/{explore-COqHKDoK.d.cts → explore-BgRhj6SP.d.cts} +10 -0
- package/dist/{explore-B_bIPzmB.d.ts → explore-RtZtb-V6.d.ts} +10 -0
- package/dist/index.cjs +31 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +31 -1
- package/dist/index.js.map +1 -1
- package/dist/server/index.cjs +1766 -105
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.js +1772 -111
- package/dist/server/index.js.map +1 -1
- package/dist/tools/explore.cjs +15 -0
- package/dist/tools/explore.cjs.map +1 -1
- package/dist/tools/explore.d.cts +1 -1
- package/dist/tools/explore.d.ts +1 -1
- package/dist/tools/explore.js +15 -0
- package/dist/tools/explore.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -568,11 +568,27 @@ var init_wire = __esm({
|
|
|
568
568
|
|
|
569
569
|
// src/cli/index.ts
|
|
570
570
|
init_cjs_shims();
|
|
571
|
+
|
|
572
|
+
// src/cli/quietDeprecations.ts
|
|
573
|
+
init_cjs_shims();
|
|
574
|
+
var IGNORED_WARNING_CODES = /* @__PURE__ */ new Set(["DEP0040"]);
|
|
575
|
+
function shouldSilenceWarning(warning) {
|
|
576
|
+
const code = warning?.code;
|
|
577
|
+
return typeof code === "string" && IGNORED_WARNING_CODES.has(code);
|
|
578
|
+
}
|
|
579
|
+
var listeners = process.listeners("warning");
|
|
580
|
+
process.removeAllListeners("warning");
|
|
581
|
+
process.on("warning", (warning) => {
|
|
582
|
+
if (shouldSilenceWarning(warning)) return;
|
|
583
|
+
for (const listener of listeners) listener(warning);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// src/cli/index.ts
|
|
571
587
|
var import_config = require("dotenv/config");
|
|
572
588
|
var fs34 = __toESM(require("fs"), 1);
|
|
573
|
-
var
|
|
589
|
+
var readline4 = __toESM(require("readline"), 1);
|
|
574
590
|
var path43 = __toESM(require("path"), 1);
|
|
575
|
-
var
|
|
591
|
+
var import_child_process2 = require("child_process");
|
|
576
592
|
var import_util = require("util");
|
|
577
593
|
|
|
578
594
|
// src/index.ts
|
|
@@ -731,15 +747,27 @@ function networkErrorHints(message, errorType = "") {
|
|
|
731
747
|
hints.push("\u2464 IPv6 \u6296\u52A8\u53EF\u8BD5\uFF1ANODE_OPTIONS=--dns-result-order=ipv4first");
|
|
732
748
|
return hints;
|
|
733
749
|
}
|
|
750
|
+
function authErrorHints(message, code = "") {
|
|
751
|
+
const text = `${message} ${code}`;
|
|
752
|
+
if (!/401|403|unauthorized|forbidden|invalid[_\s]*api[_\s]*key|api[\s_-]*key\s*(?:无效|invalid)|管理令牌无效/i.test(text)) {
|
|
753
|
+
return [];
|
|
754
|
+
}
|
|
755
|
+
return [
|
|
756
|
+
"\u51ED\u636E\u65E0\u6548\uFF1A\u82E5\u7528\u7684\u662F\u300C\u5FD7\u524D\u5927\u6A21\u578B\uFF08\u5185\u7F6E\uFF09\u300D\uFF0C\u5728\u8282\u70B9\u8BBE\u7F6E\u7684\u6A21\u578B\u9875\u70B9\u300C\u5237\u65B0\u5185\u7F6E\u6A21\u578B\u300D\u91CD\u65B0\u9886\u53D6 Key\uFF08\u5728\u63A7\u5236\u53F0\u91CD\u7F6E\u8FC7 Key \u540E\u9700\u8981\u8FD9\u4E00\u6B65\uFF09\uFF1B\u82E5\u662F\u81EA\u586B\u7684\u4F9B\u5E94\u5546\uFF0C\u8BF7\u68C0\u67E5 Key \u662F\u5426\u6B63\u786E\u6216\u5DF2\u8FC7\u671F"
|
|
757
|
+
];
|
|
758
|
+
}
|
|
734
759
|
function paramErrorHints(message, code = "") {
|
|
735
760
|
const text = `${message} ${code}`;
|
|
736
761
|
const hints = [];
|
|
737
|
-
if (/not[_\s]*(activated|enabled|purchased)|arrearage|quota|subscription|insufficient
|
|
762
|
+
if (/not[_\s]*(activated|enabled|purchased)|arrearage|quota|subscription|insufficient|未开通|欠费|额度|余额不足/i.test(
|
|
738
763
|
text
|
|
739
764
|
)) {
|
|
740
765
|
hints.push(
|
|
741
766
|
"\u8BE5\u6A21\u578B\u5728\u6B64 **API Key/\u8D26\u53F7** \u4E0B\u672A\u5F00\u901A\uFF08\u6216\u8D26\u53F7\u6B20\u8D39\u3001\u989D\u5EA6\u7528\u5C3D\uFF09\uFF1A\u5230\u7F51\u5173\u63A7\u5236\u53F0\u5F00\u901A\u8BE5\u6A21\u578B\uFF0C\u6216\u6362\u4E00\u4E2A\u5DF2\u5F00\u901A\u7684\u6A21\u578B"
|
|
742
767
|
);
|
|
768
|
+
hints.push(
|
|
769
|
+
"\u5982\u679C\u4F60\u7528\u7684\u662F\u300C\u5FD7\u524D\u5927\u6A21\u578B\uFF08\u5185\u7F6E\uFF09\u300D\uFF1A\u5230 https://hub.cd-zq.com \u67E5\u770B\u4F59\u989D\u5E76\u5145\u503C\uFF1B\u4E5F\u53EF\u4EE5\u5148\u5207\u6362\u5230\u4F60\u81EA\u5DF1\u914D\u7F6E\u7684\u5176\u5B83\u6A21\u578B\u7EE7\u7EED\uFF08\u8282\u70B9\u8BBE\u7F6E \u2192 \u6A21\u578B \u2192 \u8BBE\u4E3A\u9ED8\u8BA4\uFF09"
|
|
770
|
+
);
|
|
743
771
|
}
|
|
744
772
|
if (/invalid[_\s]*parameter|invalidparameter|required[_\s]+parameter|missing[_\s]+parameter|invalid[_\s]*model|model[_\s]*not[_\s]*found|not[_\s]*support|unsupported/i.test(
|
|
745
773
|
text
|
|
@@ -1097,13 +1125,13 @@ function textOf(content) {
|
|
|
1097
1125
|
}
|
|
1098
1126
|
return content != null ? String(content) : "";
|
|
1099
1127
|
}
|
|
1100
|
-
function extractUserMessages(span,
|
|
1128
|
+
function extractUserMessages(span, clip2 = USER_MESSAGE_CLIP) {
|
|
1101
1129
|
const out = [];
|
|
1102
1130
|
for (const m of span) {
|
|
1103
1131
|
if (m.role !== "user") continue;
|
|
1104
1132
|
const text = textOf(m.content).replace(/\s+/g, " ").trim();
|
|
1105
1133
|
if (!text) continue;
|
|
1106
|
-
out.push(text.length >
|
|
1134
|
+
out.push(text.length > clip2 ? text.slice(0, clip2 - 1) + "\u2026" : text);
|
|
1107
1135
|
}
|
|
1108
1136
|
return out;
|
|
1109
1137
|
}
|
|
@@ -1937,6 +1965,21 @@ var TurnEngine = class _TurnEngine {
|
|
|
1937
1965
|
queueSteering(text) {
|
|
1938
1966
|
this._steering.push(text);
|
|
1939
1967
|
}
|
|
1968
|
+
/**
|
|
1969
|
+
* 撤销一条尚未注入的 steering。
|
|
1970
|
+
*
|
|
1971
|
+
* 按文本删第一条匹配项:调用方(SessionManager)已按索引删掉了自己那份队列,
|
|
1972
|
+
* 这里只需保证引擎不再注入同样的内容。用文本而非索引,是因为引擎会在迭代
|
|
1973
|
+
* 间隙批量清空 `_steering`,索引可能已经漂移;文本相同则删哪条都等价。
|
|
1974
|
+
*
|
|
1975
|
+
* @returns 是否真的删掉了(已注入的项返回 false,调用方据此提示用户)
|
|
1976
|
+
*/
|
|
1977
|
+
removeSteering(text) {
|
|
1978
|
+
const i = this._steering.indexOf(text);
|
|
1979
|
+
if (i < 0) return false;
|
|
1980
|
+
this._steering.splice(i, 1);
|
|
1981
|
+
return true;
|
|
1982
|
+
}
|
|
1940
1983
|
// ===================== 主入口 =====================
|
|
1941
1984
|
/**
|
|
1942
1985
|
* 运行一轮 Turn
|
|
@@ -4967,6 +5010,9 @@ var ConsoleHook = class {
|
|
|
4967
5010
|
for (const hint of networkErrorHints(error2, errorType)) {
|
|
4968
5011
|
cliLog(` \u{1F4A1} ${hint}`);
|
|
4969
5012
|
}
|
|
5013
|
+
for (const hint of authErrorHints(error2, errorType)) {
|
|
5014
|
+
cliLog(` \u{1F4A1} ${hint}`);
|
|
5015
|
+
}
|
|
4970
5016
|
for (const hint of paramErrorHints(error2)) {
|
|
4971
5017
|
cliLog(` \u{1F4A1} ${hint}`);
|
|
4972
5018
|
}
|
|
@@ -9960,6 +10006,15 @@ function commonLegacyFiles() {
|
|
|
9960
10006
|
return [path17.join(stateDir(), "data", "node-llm.json")];
|
|
9961
10007
|
}
|
|
9962
10008
|
|
|
10009
|
+
// src/server/llmIds.ts
|
|
10010
|
+
init_cjs_shims();
|
|
10011
|
+
var GLOBAL_PROVIDER_ID = "__env__";
|
|
10012
|
+
var BUILTIN_PROVIDER_ID = "__zqai__";
|
|
10013
|
+
function isBuiltinProvider(id) {
|
|
10014
|
+
return id === BUILTIN_PROVIDER_ID;
|
|
10015
|
+
}
|
|
10016
|
+
var FALLBACK_MODEL = "gpt-4o";
|
|
10017
|
+
|
|
9963
10018
|
// src/cli/connection.ts
|
|
9964
10019
|
var PROVIDERS = Object.fromEntries(
|
|
9965
10020
|
LLM_PRESETS.map((p) => [
|
|
@@ -10074,6 +10129,7 @@ function useConnection(id) {
|
|
|
10074
10129
|
return true;
|
|
10075
10130
|
}
|
|
10076
10131
|
function removeConnection(id) {
|
|
10132
|
+
if (id === BUILTIN_PROVIDER_ID) return false;
|
|
10077
10133
|
const f = read();
|
|
10078
10134
|
const next = f.providers.filter((p) => p.id !== id);
|
|
10079
10135
|
if (next.length === f.providers.length) return false;
|
|
@@ -10357,7 +10413,7 @@ var GLYPHS = {
|
|
|
10357
10413
|
T: ["\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588", " \u2588\u2588 ", " \u2588\u2588 ", " \u2588\u2588 ", " \u2588\u2588 ", " "]
|
|
10358
10414
|
};
|
|
10359
10415
|
var GLYPH_ROWS = 6;
|
|
10360
|
-
var GLYPH_COLS =
|
|
10416
|
+
var GLYPH_COLS = 11;
|
|
10361
10417
|
var GRADIENT = [
|
|
10362
10418
|
[34, 211, 238],
|
|
10363
10419
|
[99, 102, 241],
|
|
@@ -10514,13 +10570,14 @@ function renderTerminalTutorial() {
|
|
|
10514
10570
|
// src/cli/link.ts
|
|
10515
10571
|
init_cjs_shims();
|
|
10516
10572
|
var path27 = __toESM(require("path"), 1);
|
|
10517
|
-
var
|
|
10573
|
+
var readline3 = __toESM(require("readline"), 1);
|
|
10518
10574
|
|
|
10519
10575
|
// src/server/store.ts
|
|
10520
10576
|
init_cjs_shims();
|
|
10521
10577
|
var fs17 = __toESM(require("fs"), 1);
|
|
10522
10578
|
var fsp = __toESM(require("fs/promises"), 1);
|
|
10523
10579
|
var path19 = __toESM(require("path"), 1);
|
|
10580
|
+
var readline = __toESM(require("readline"), 1);
|
|
10524
10581
|
var import_crypto = require("crypto");
|
|
10525
10582
|
|
|
10526
10583
|
// src/server/dbgTrace.ts
|
|
@@ -10740,6 +10797,170 @@ var Store = class {
|
|
|
10740
10797
|
}
|
|
10741
10798
|
}
|
|
10742
10799
|
}
|
|
10800
|
+
/**
|
|
10801
|
+
* 读取事件「一页」:从文件末尾向前,按字节预算取最近的事件。
|
|
10802
|
+
*
|
|
10803
|
+
* - `before` 省略:取最新的 maxBytes 字节
|
|
10804
|
+
* - `before` 指定:只取 seq < before 的事件(向前翻页)
|
|
10805
|
+
*
|
|
10806
|
+
* 为什么不能复用 loadEventsTail:后者只能从文件末尾读固定窗口,
|
|
10807
|
+
* 无法「向前翻页」,而控制台需要滚到顶部继续加载更早的对话。
|
|
10808
|
+
* 这里按块从后往前扫,读到预算或文件头即停;翻了多页之后每次
|
|
10809
|
+
* before 都紧邻上一页,实际只需读 1~2 块。
|
|
10810
|
+
*
|
|
10811
|
+
* 返回按 seq 升序;`hasMore` 表示本页最早一条之前还有更早的事件。
|
|
10812
|
+
*/
|
|
10813
|
+
loadEventPage(tenantId, sessionId, opts) {
|
|
10814
|
+
const p = path19.join(this.sessionDir(tenantId, sessionId), "events.jsonl");
|
|
10815
|
+
const { before, maxBytes } = opts;
|
|
10816
|
+
const __t0 = dbgNow();
|
|
10817
|
+
let fd = null;
|
|
10818
|
+
try {
|
|
10819
|
+
const size = fs17.statSync(p).size;
|
|
10820
|
+
if (size === 0) return { events: [], hasMore: false };
|
|
10821
|
+
fd = fs17.openSync(p, "r");
|
|
10822
|
+
const CHUNK = 256 * 1024;
|
|
10823
|
+
let pos = size;
|
|
10824
|
+
const kept = [];
|
|
10825
|
+
let bytes = 0;
|
|
10826
|
+
let hasMore = false;
|
|
10827
|
+
let done = false;
|
|
10828
|
+
while (pos > 0 && !done) {
|
|
10829
|
+
const start = Math.max(0, pos - CHUNK);
|
|
10830
|
+
const len = pos - start;
|
|
10831
|
+
const buf = Buffer.allocUnsafe(len);
|
|
10832
|
+
const read2 = fs17.readSync(fd, buf, 0, len, start);
|
|
10833
|
+
const rows = buf.subarray(0, read2).toString("utf-8").split("\n");
|
|
10834
|
+
if (start > 0) rows.shift();
|
|
10835
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
10836
|
+
const line = rows[i];
|
|
10837
|
+
if (!line) continue;
|
|
10838
|
+
let entry;
|
|
10839
|
+
try {
|
|
10840
|
+
entry = JSON.parse(line);
|
|
10841
|
+
} catch {
|
|
10842
|
+
continue;
|
|
10843
|
+
}
|
|
10844
|
+
if (!Number.isFinite(Number(entry.seq))) continue;
|
|
10845
|
+
if (before !== void 0 && Number(entry.seq) >= before) continue;
|
|
10846
|
+
const sz = Buffer.byteLength(line, "utf-8") + 1;
|
|
10847
|
+
if (bytes > 0 && bytes + sz > maxBytes) {
|
|
10848
|
+
hasMore = true;
|
|
10849
|
+
done = true;
|
|
10850
|
+
break;
|
|
10851
|
+
}
|
|
10852
|
+
bytes += sz;
|
|
10853
|
+
kept.unshift(entry);
|
|
10854
|
+
}
|
|
10855
|
+
pos = start;
|
|
10856
|
+
}
|
|
10857
|
+
dbgMs(
|
|
10858
|
+
"store.loadEventPage",
|
|
10859
|
+
__t0,
|
|
10860
|
+
// [dbg]
|
|
10861
|
+
`size=${size} before=${before ?? "-"} out=${kept.length} bytes\u2248${bytes} more=${hasMore}`
|
|
10862
|
+
);
|
|
10863
|
+
return { events: kept, hasMore };
|
|
10864
|
+
} catch {
|
|
10865
|
+
return { events: [], hasMore: false };
|
|
10866
|
+
} finally {
|
|
10867
|
+
if (fd !== null) {
|
|
10868
|
+
try {
|
|
10869
|
+
fs17.closeSync(fd);
|
|
10870
|
+
} catch {
|
|
10871
|
+
}
|
|
10872
|
+
}
|
|
10873
|
+
}
|
|
10874
|
+
}
|
|
10875
|
+
// ===================== 轮次索引 =====================
|
|
10876
|
+
/** 轮次索引文件路径 */
|
|
10877
|
+
turnsFile(tenantId, sessionId) {
|
|
10878
|
+
return path19.join(this.sessionDir(tenantId, sessionId), "turns.ndjson");
|
|
10879
|
+
}
|
|
10880
|
+
/**
|
|
10881
|
+
* 追加一轮的最新快照(同一 startSeq 后写者胜出)。
|
|
10882
|
+
*
|
|
10883
|
+
* 走与 session.json 相同的每文件写链:索引按「开轮 → 补充消息 → 收轮」
|
|
10884
|
+
* 顺序追加,乱序会让列表读到旧的 running 版本。
|
|
10885
|
+
*/
|
|
10886
|
+
appendTurnIndex(tenantId, sessionId, entry) {
|
|
10887
|
+
const dir = this.sessionDir(tenantId, sessionId);
|
|
10888
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
10889
|
+
const file = this.turnsFile(tenantId, sessionId);
|
|
10890
|
+
const line = JSON.stringify(entry) + "\n";
|
|
10891
|
+
const prev = this.writeChains.get(file) ?? Promise.resolve();
|
|
10892
|
+
const next = prev.then(() => fsp.appendFile(file, line, "utf-8")).catch((err) => {
|
|
10893
|
+
console.error(`[store] appendTurnIndex \u5931\u8D25 ${sessionId}: ${String(err)}`);
|
|
10894
|
+
});
|
|
10895
|
+
this.writeChains.set(file, next);
|
|
10896
|
+
return next;
|
|
10897
|
+
}
|
|
10898
|
+
/**
|
|
10899
|
+
* 读取轮次索引(已按 startSeq 去重取最后一版,升序)。
|
|
10900
|
+
*
|
|
10901
|
+
* 文件很小(一轮约 0.5KB,1000 轮 ≈ 500KB),同步读即可,
|
|
10902
|
+
* 会话详情打开路径不会因历史长度变慢。
|
|
10903
|
+
*/
|
|
10904
|
+
loadTurnIndex(tenantId, sessionId) {
|
|
10905
|
+
let raw;
|
|
10906
|
+
try {
|
|
10907
|
+
raw = fs17.readFileSync(this.turnsFile(tenantId, sessionId), "utf-8");
|
|
10908
|
+
} catch {
|
|
10909
|
+
return [];
|
|
10910
|
+
}
|
|
10911
|
+
const byStart = /* @__PURE__ */ new Map();
|
|
10912
|
+
for (const line of raw.split("\n")) {
|
|
10913
|
+
if (!line) continue;
|
|
10914
|
+
try {
|
|
10915
|
+
const e = JSON.parse(line);
|
|
10916
|
+
if (!Number.isFinite(Number(e?.startSeq))) continue;
|
|
10917
|
+
byStart.set(Number(e.startSeq), e);
|
|
10918
|
+
} catch {
|
|
10919
|
+
}
|
|
10920
|
+
}
|
|
10921
|
+
return [...byStart.values()].sort((a, b) => a.startSeq - b.startSeq);
|
|
10922
|
+
}
|
|
10923
|
+
/** 整文件替换轮次索引(重建用,tmp + rename 原子写) */
|
|
10924
|
+
saveTurnIndex(tenantId, sessionId, entries) {
|
|
10925
|
+
const dir = this.sessionDir(tenantId, sessionId);
|
|
10926
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
10927
|
+
const file = this.turnsFile(tenantId, sessionId);
|
|
10928
|
+
const snapshot = entries.map((e) => JSON.stringify(e)).join("\n") + (entries.length ? "\n" : "");
|
|
10929
|
+
const tmp = `${file}.${(0, import_crypto.randomUUID)()}.tmp`;
|
|
10930
|
+
const prev = this.writeChains.get(file) ?? Promise.resolve();
|
|
10931
|
+
const next = prev.then(() => fsp.writeFile(tmp, snapshot, "utf-8")).then(() => fsp.rename(tmp, file)).catch((err) => {
|
|
10932
|
+
console.error(`[store] saveTurnIndex \u5931\u8D25 ${sessionId}: ${String(err)}`);
|
|
10933
|
+
void fsp.rm(tmp, { force: true });
|
|
10934
|
+
});
|
|
10935
|
+
this.writeChains.set(file, next);
|
|
10936
|
+
return next;
|
|
10937
|
+
}
|
|
10938
|
+
/**
|
|
10939
|
+
* 流式读取整份事件日志(重建索引用)。
|
|
10940
|
+
*
|
|
10941
|
+
* 不能用 loadEvents(readFileSync + 逐行 JSON.parse):大会话几十 MB,
|
|
10942
|
+
* 会长时间阻塞事件循环,导致 SSE 心跳 / MQTT 保活断开。
|
|
10943
|
+
*/
|
|
10944
|
+
async readEventsAsync(tenantId, sessionId) {
|
|
10945
|
+
const p = path19.join(this.sessionDir(tenantId, sessionId), "events.jsonl");
|
|
10946
|
+
const out = [];
|
|
10947
|
+
try {
|
|
10948
|
+
const rl2 = readline.createInterface({
|
|
10949
|
+
input: fs17.createReadStream(p, { encoding: "utf-8" }),
|
|
10950
|
+
crlfDelay: Infinity
|
|
10951
|
+
});
|
|
10952
|
+
for await (const line of rl2) {
|
|
10953
|
+
if (!line) continue;
|
|
10954
|
+
try {
|
|
10955
|
+
out.push(JSON.parse(line));
|
|
10956
|
+
} catch {
|
|
10957
|
+
}
|
|
10958
|
+
}
|
|
10959
|
+
} catch {
|
|
10960
|
+
return out;
|
|
10961
|
+
}
|
|
10962
|
+
return out;
|
|
10963
|
+
}
|
|
10743
10964
|
// ===================== 节点身份 & 管理员密钥 =====================
|
|
10744
10965
|
/** 节点稳定标识:{root}/node.json(首次启动生成后长期固定) */
|
|
10745
10966
|
loadNodeId() {
|
|
@@ -10798,7 +11019,23 @@ var Store = class {
|
|
|
10798
11019
|
legacyFiles: [path19.join(this.root, "node-llm.json")]
|
|
10799
11020
|
});
|
|
10800
11021
|
const old = new Map(cur.providers.map((p) => [p.id, p]));
|
|
10801
|
-
const
|
|
11022
|
+
const src = [...cfg.providers];
|
|
11023
|
+
const prevBuiltin = cur.providers.find((p) => p.id === BUILTIN_PROVIDER_ID);
|
|
11024
|
+
if (prevBuiltin && !src.some((p) => p.id === BUILTIN_PROVIDER_ID)) {
|
|
11025
|
+
src.unshift({
|
|
11026
|
+
id: prevBuiltin.id,
|
|
11027
|
+
name: prevBuiltin.name,
|
|
11028
|
+
type: prevBuiltin.type,
|
|
11029
|
+
baseURL: prevBuiltin.baseURL,
|
|
11030
|
+
model: prevBuiltin.model,
|
|
11031
|
+
apiKey: decryptKey(prevBuiltin.apiKeyEnc, file) ?? void 0,
|
|
11032
|
+
reasoningEffort: prevBuiltin.reasoningEffort ?? null,
|
|
11033
|
+
...prevBuiltin.models ? { models: prevBuiltin.models } : {},
|
|
11034
|
+
...prevBuiltin.nonChat ? { nonChat: prevBuiltin.nonChat } : {},
|
|
11035
|
+
...prevBuiltin.modelMeta ? { modelMeta: prevBuiltin.modelMeta } : {}
|
|
11036
|
+
});
|
|
11037
|
+
}
|
|
11038
|
+
const providers = src.map((p) => {
|
|
10802
11039
|
const prev = old.get(p.id);
|
|
10803
11040
|
const apiKeyEnc = p.apiKey ? encryptKey(p.apiKey, file) : prev?.apiKeyEnc ?? "";
|
|
10804
11041
|
return {
|
|
@@ -10939,7 +11176,7 @@ function defaultNodeLabel() {
|
|
|
10939
11176
|
// src/cli/account-enroll.ts
|
|
10940
11177
|
init_cjs_shims();
|
|
10941
11178
|
var path24 = __toESM(require("path"), 1);
|
|
10942
|
-
var
|
|
11179
|
+
var readline2 = __toESM(require("readline/promises"), 1);
|
|
10943
11180
|
|
|
10944
11181
|
// src/server/projects.ts
|
|
10945
11182
|
init_cjs_shims();
|
|
@@ -10989,7 +11226,6 @@ function loadServerMcpConfigs(tenant) {
|
|
|
10989
11226
|
|
|
10990
11227
|
// src/server/nodeLlm.ts
|
|
10991
11228
|
init_cjs_shims();
|
|
10992
|
-
var GLOBAL_PROVIDER_ID = "__env__";
|
|
10993
11229
|
function envProvider() {
|
|
10994
11230
|
const apiKey = process.env.OPENAI_API_KEY;
|
|
10995
11231
|
if (!apiKey) return null;
|
|
@@ -11032,19 +11268,26 @@ function nodeLlmView(cfg) {
|
|
|
11032
11268
|
global: true
|
|
11033
11269
|
}
|
|
11034
11270
|
] : [],
|
|
11035
|
-
...cfg.providers.map((p) =>
|
|
11036
|
-
|
|
11037
|
-
|
|
11038
|
-
|
|
11039
|
-
|
|
11040
|
-
|
|
11041
|
-
|
|
11042
|
-
|
|
11043
|
-
|
|
11044
|
-
|
|
11045
|
-
|
|
11046
|
-
|
|
11047
|
-
|
|
11271
|
+
...cfg.providers.map((p) => {
|
|
11272
|
+
const builtin = isBuiltinProvider(p.id);
|
|
11273
|
+
return {
|
|
11274
|
+
id: p.id,
|
|
11275
|
+
name: p.name,
|
|
11276
|
+
type: p.type ?? "",
|
|
11277
|
+
baseURL: p.baseURL,
|
|
11278
|
+
model: p.model,
|
|
11279
|
+
hasApiKey: Boolean(p.apiKey),
|
|
11280
|
+
models: p.models ?? [],
|
|
11281
|
+
nonChat: p.nonChat ?? [],
|
|
11282
|
+
modelMeta: p.modelMeta ?? [],
|
|
11283
|
+
reasoningEffort: p.reasoningEffort ?? null,
|
|
11284
|
+
global: false,
|
|
11285
|
+
/** 平台内置(接入 Hub 后自动下发,用户不用关心 Key) */
|
|
11286
|
+
builtin,
|
|
11287
|
+
/** 不可编辑的字段:控制台据此把 baseURL / key 显示为只读 */
|
|
11288
|
+
...builtin ? { locked: ["baseURL", "apiKey"] } : {}
|
|
11289
|
+
};
|
|
11290
|
+
})
|
|
11048
11291
|
];
|
|
11049
11292
|
return {
|
|
11050
11293
|
defaultProviderId: cfg.defaultProviderId ?? null,
|
|
@@ -11057,27 +11300,42 @@ function applyNodeLlmPatch(cfg, raw) {
|
|
|
11057
11300
|
const next = { providers: cfg.providers, defaultProviderId: cfg.defaultProviderId };
|
|
11058
11301
|
if (Array.isArray(raw?.providers)) {
|
|
11059
11302
|
const old = new Map(cfg.providers.map((p) => [p.id, p]));
|
|
11060
|
-
|
|
11061
|
-
|
|
11062
|
-
|
|
11063
|
-
|
|
11064
|
-
|
|
11065
|
-
|
|
11066
|
-
|
|
11067
|
-
|
|
11068
|
-
|
|
11069
|
-
|
|
11070
|
-
|
|
11071
|
-
|
|
11072
|
-
|
|
11073
|
-
|
|
11074
|
-
|
|
11075
|
-
|
|
11076
|
-
|
|
11077
|
-
|
|
11078
|
-
|
|
11079
|
-
|
|
11080
|
-
|
|
11303
|
+
const incoming = raw.providers.filter((x) => Boolean(x) && typeof x === "object").filter((x) => x.global !== true && String(x.id ?? "") !== GLOBAL_PROVIDER_ID);
|
|
11304
|
+
const prevBuiltin = cfg.providers.find((p) => isBuiltinProvider(p.id));
|
|
11305
|
+
const builtinPatch = incoming.find((x) => isBuiltinProvider(String(x.id ?? "")));
|
|
11306
|
+
const keptBuiltin = prevBuiltin ? [
|
|
11307
|
+
{
|
|
11308
|
+
...prevBuiltin,
|
|
11309
|
+
...builtinPatch?.model != null ? { model: String(builtinPatch.model).trim() || prevBuiltin.model } : {},
|
|
11310
|
+
...normalizeModelList(builtinPatch?.models) ? { models: normalizeModelList(builtinPatch?.models) } : {},
|
|
11311
|
+
...builtinPatch?.reasoningEffort !== void 0 ? { reasoningEffort: normReasoningEffort(builtinPatch.reasoningEffort) } : {},
|
|
11312
|
+
...typeof builtinPatch?.name === "string" && builtinPatch.name.trim() ? { name: builtinPatch.name.trim() } : {}
|
|
11313
|
+
}
|
|
11314
|
+
] : [];
|
|
11315
|
+
next.providers = [
|
|
11316
|
+
...keptBuiltin,
|
|
11317
|
+
...incoming.filter((x) => !isBuiltinProvider(String(x.id ?? ""))).map((x) => {
|
|
11318
|
+
const id = String(x.id ?? "").trim();
|
|
11319
|
+
if (!id) throw new ApiError(400, "bad_request", "\u4F9B\u5E94\u5546 id \u4E0D\u80FD\u4E3A\u7A7A");
|
|
11320
|
+
const prev = old.get(id);
|
|
11321
|
+
const apiKey = String(x.apiKey ?? "").trim();
|
|
11322
|
+
const models = normalizeModelList(x.models) ?? prev?.models;
|
|
11323
|
+
const nonChat = normalizeModelList(x.nonChat) ?? prev?.nonChat;
|
|
11324
|
+
const modelMeta = normalizeModelMeta(x.modelMeta) ?? prev?.modelMeta;
|
|
11325
|
+
return {
|
|
11326
|
+
id,
|
|
11327
|
+
name: String(x.name ?? prev?.name ?? id).trim(),
|
|
11328
|
+
type: x.type == null ? prev?.type : String(x.type),
|
|
11329
|
+
baseURL: String(x.baseURL ?? prev?.baseURL ?? "").trim(),
|
|
11330
|
+
model: String(x.model ?? prev?.model ?? "").trim(),
|
|
11331
|
+
apiKey: apiKey || prev?.apiKey,
|
|
11332
|
+
reasoningEffort: x.reasoningEffort === void 0 ? prev?.reasoningEffort ?? null : normReasoningEffort(x.reasoningEffort),
|
|
11333
|
+
...models ? { models } : {},
|
|
11334
|
+
...nonChat ? { nonChat } : {},
|
|
11335
|
+
...modelMeta ? { modelMeta } : {}
|
|
11336
|
+
};
|
|
11337
|
+
})
|
|
11338
|
+
];
|
|
11081
11339
|
}
|
|
11082
11340
|
if (typeof raw?.defaultProviderId === "string") {
|
|
11083
11341
|
const id = raw.defaultProviderId.trim();
|
|
@@ -11097,6 +11355,9 @@ function nodeDefaultProvider(store) {
|
|
|
11097
11355
|
if (cfg.providers.length === 0) return null;
|
|
11098
11356
|
return cfg.providers.find((p) => p.id === cfg.defaultProviderId) ?? cfg.providers[0] ?? null;
|
|
11099
11357
|
}
|
|
11358
|
+
function defaultTenantModel(store) {
|
|
11359
|
+
return process.env.OPENAI_MODEL || process.env.MODEL || nodeDefaultProvider(store)?.model || FALLBACK_MODEL;
|
|
11360
|
+
}
|
|
11100
11361
|
function resolveSessionProvider(store, tenant, providerId) {
|
|
11101
11362
|
const own = tenant.provider ?? {};
|
|
11102
11363
|
if (providerId) {
|
|
@@ -11126,6 +11387,208 @@ function resolveSessionProvider(store, tenant, providerId) {
|
|
|
11126
11387
|
return { ...own, model: tenant.model };
|
|
11127
11388
|
}
|
|
11128
11389
|
|
|
11390
|
+
// src/server/builtinLlm.ts
|
|
11391
|
+
init_cjs_shims();
|
|
11392
|
+
var FETCH_TIMEOUT_MS = 8e3;
|
|
11393
|
+
var RETRY_AFTER_MS = 5 * 6e4;
|
|
11394
|
+
function hubBase(cred) {
|
|
11395
|
+
return (cred.hubUrl ?? "").replace(/\/+$/, "");
|
|
11396
|
+
}
|
|
11397
|
+
function asProvider(grant) {
|
|
11398
|
+
const baseURL = String(grant.baseURL ?? "").trim();
|
|
11399
|
+
const apiKey = String(grant.apiKey ?? "").trim();
|
|
11400
|
+
const model = String(grant.model ?? "").trim();
|
|
11401
|
+
if (!baseURL || !apiKey) return null;
|
|
11402
|
+
return {
|
|
11403
|
+
id: BUILTIN_PROVIDER_ID,
|
|
11404
|
+
name: String(grant.name ?? "").trim() || "\u5FD7\u524D\u5927\u6A21\u578B\uFF08\u5185\u7F6E\uFF09",
|
|
11405
|
+
type: "builtin",
|
|
11406
|
+
baseURL,
|
|
11407
|
+
model,
|
|
11408
|
+
apiKey
|
|
11409
|
+
};
|
|
11410
|
+
}
|
|
11411
|
+
async function enrichModels(p) {
|
|
11412
|
+
try {
|
|
11413
|
+
const r = await Promise.race([
|
|
11414
|
+
fetchModels(p.baseURL, p.apiKey ?? "", p.model),
|
|
11415
|
+
new Promise((resolve25) => {
|
|
11416
|
+
const t = setTimeout(() => resolve25(null), FETCH_TIMEOUT_MS);
|
|
11417
|
+
t.unref?.();
|
|
11418
|
+
})
|
|
11419
|
+
]);
|
|
11420
|
+
if (!r?.models?.length) return p;
|
|
11421
|
+
return {
|
|
11422
|
+
...p,
|
|
11423
|
+
// 控制面没给默认型号时,取供应商返回的第一个,避免模型为空让用户卡住
|
|
11424
|
+
model: p.model || r.models[0],
|
|
11425
|
+
models: r.models,
|
|
11426
|
+
...r.nonChat?.length ? { nonChat: r.nonChat } : {},
|
|
11427
|
+
...r.modelMeta?.length ? { modelMeta: r.modelMeta } : {}
|
|
11428
|
+
};
|
|
11429
|
+
} catch {
|
|
11430
|
+
return p;
|
|
11431
|
+
}
|
|
11432
|
+
}
|
|
11433
|
+
function saveBuiltin(store, provider) {
|
|
11434
|
+
const cfg = store.loadNodeLlm();
|
|
11435
|
+
const others = cfg.providers.filter((p) => p.id !== BUILTIN_PROVIDER_ID);
|
|
11436
|
+
const prev = cfg.providers.find((p) => p.id === BUILTIN_PROVIDER_ID);
|
|
11437
|
+
const merged = {
|
|
11438
|
+
...provider,
|
|
11439
|
+
// 用户选过的型号/候选列表/推理强度/显示名都保留;只有托管字段(baseURL/key)强制以控制面为准
|
|
11440
|
+
...prev?.model ? { model: prev.model } : {},
|
|
11441
|
+
...prev?.models?.length ? { models: prev.models } : {},
|
|
11442
|
+
...prev?.reasoningEffort != null ? { reasoningEffort: prev.reasoningEffort } : {},
|
|
11443
|
+
...prev?.name ? { name: prev.name } : {}
|
|
11444
|
+
};
|
|
11445
|
+
store.saveNodeLlm({
|
|
11446
|
+
providers: [merged, ...others],
|
|
11447
|
+
// 之前没有默认且用户也没指定时,让内置模型成为默认——「开箱即用」
|
|
11448
|
+
defaultProviderId: cfg.defaultProviderId ?? BUILTIN_PROVIDER_ID
|
|
11449
|
+
});
|
|
11450
|
+
adoptBuiltinModelForTenants(store, merged.model);
|
|
11451
|
+
}
|
|
11452
|
+
var LEGACY_DEFAULT_MODELS = /* @__PURE__ */ new Set([FALLBACK_MODEL, "deepseek-v4-flash"]);
|
|
11453
|
+
function adoptBuiltinModelForTenants(store, model) {
|
|
11454
|
+
if (!model) return;
|
|
11455
|
+
for (const id of store.listTenants()) {
|
|
11456
|
+
const tenant = store.loadTenant(id);
|
|
11457
|
+
if (!tenant || !LEGACY_DEFAULT_MODELS.has(tenant.model)) continue;
|
|
11458
|
+
if (tenant.provider?.apiKey) continue;
|
|
11459
|
+
tenant.model = model;
|
|
11460
|
+
store.saveTenant(tenant);
|
|
11461
|
+
}
|
|
11462
|
+
}
|
|
11463
|
+
async function ensureBuiltinProvider(store, opts = {}) {
|
|
11464
|
+
const cred = store.loadFleetCredential();
|
|
11465
|
+
if (!cred?.fleetToken || !cred.fleetSecret) {
|
|
11466
|
+
return { status: "disabled", reason: "\u8282\u70B9\u672A\u63A5\u5165 Hub" };
|
|
11467
|
+
}
|
|
11468
|
+
const hub = hubBase(cred);
|
|
11469
|
+
if (!hub) return { status: "disabled", reason: "\u7F3A\u5C11 Hub \u5730\u5740" };
|
|
11470
|
+
const local = store.loadNodeLlm().providers.find((p) => p.id === BUILTIN_PROVIDER_ID);
|
|
11471
|
+
if (local && !opts.force) {
|
|
11472
|
+
adoptBuiltinModelForTenants(store, local.model);
|
|
11473
|
+
return { status: "ready", provider: local, refreshed: false };
|
|
11474
|
+
}
|
|
11475
|
+
const cached3 = cred.builtinLlm ? asProvider(cred.builtinLlm) : null;
|
|
11476
|
+
let res;
|
|
11477
|
+
try {
|
|
11478
|
+
res = await fetch(`${hub}/api/hub/node/builtin-llm`, {
|
|
11479
|
+
headers: {
|
|
11480
|
+
Authorization: `Bearer ${cred.fleetToken}`,
|
|
11481
|
+
"X-Fleet-Secret": cred.fleetSecret,
|
|
11482
|
+
"X-Node-Id": cred.nodeId
|
|
11483
|
+
},
|
|
11484
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
11485
|
+
});
|
|
11486
|
+
} catch (e) {
|
|
11487
|
+
if (cached3) {
|
|
11488
|
+
const provider2 = await enrichModels(cached3);
|
|
11489
|
+
saveBuiltin(store, provider2);
|
|
11490
|
+
return { status: "ready", provider: provider2, refreshed: true };
|
|
11491
|
+
}
|
|
11492
|
+
return { status: "error", reason: `\u8FDE\u63A5 Hub \u5931\u8D25\uFF1A${e.message}` };
|
|
11493
|
+
}
|
|
11494
|
+
let body = null;
|
|
11495
|
+
try {
|
|
11496
|
+
body = await res.json();
|
|
11497
|
+
} catch {
|
|
11498
|
+
}
|
|
11499
|
+
if (!res.ok) {
|
|
11500
|
+
const msg = body?.error?.message ?? `HTTP ${res.status}`;
|
|
11501
|
+
if (res.status === 401 || res.status === 403) {
|
|
11502
|
+
return { status: "unavailable", reason: msg };
|
|
11503
|
+
}
|
|
11504
|
+
if (cached3) {
|
|
11505
|
+
const provider2 = await enrichModels(cached3);
|
|
11506
|
+
saveBuiltin(store, provider2);
|
|
11507
|
+
return { status: "ready", provider: provider2, refreshed: true };
|
|
11508
|
+
}
|
|
11509
|
+
return { status: "error", reason: msg };
|
|
11510
|
+
}
|
|
11511
|
+
const grant = body?.data?.builtinLlm;
|
|
11512
|
+
if (!grant) {
|
|
11513
|
+
const why = String(body?.data?.reason ?? "").trim();
|
|
11514
|
+
return { status: "unavailable", reason: why || "\u63A7\u5236\u9762\u672A\u4E0B\u53D1\u5185\u7F6E\u6A21\u578B" };
|
|
11515
|
+
}
|
|
11516
|
+
const parsed = asProvider(grant);
|
|
11517
|
+
if (!parsed) return { status: "unavailable", reason: "\u5185\u7F6E\u6A21\u578B\u914D\u7F6E\u4E0D\u5B8C\u6574" };
|
|
11518
|
+
const provider = await enrichModels(parsed);
|
|
11519
|
+
saveBuiltin(store, provider);
|
|
11520
|
+
return { status: "ready", provider, refreshed: true };
|
|
11521
|
+
}
|
|
11522
|
+
async function fetchLlmBalance(store) {
|
|
11523
|
+
const cred = store.loadFleetCredential();
|
|
11524
|
+
const hub = cred ? hubBase(cred) : "";
|
|
11525
|
+
if (!cred?.fleetToken || !cred.fleetSecret || !hub) {
|
|
11526
|
+
return { available: null, currency: "CNY", reason: "\u8282\u70B9\u672A\u63A5\u5165 Hub" };
|
|
11527
|
+
}
|
|
11528
|
+
try {
|
|
11529
|
+
const res = await fetch(`${hub}/api/hub/node/balance`, {
|
|
11530
|
+
headers: {
|
|
11531
|
+
Authorization: `Bearer ${cred.fleetToken}`,
|
|
11532
|
+
"X-Fleet-Secret": cred.fleetSecret,
|
|
11533
|
+
"X-Node-Id": cred.nodeId
|
|
11534
|
+
},
|
|
11535
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
11536
|
+
});
|
|
11537
|
+
const body = await res.json().catch(() => null);
|
|
11538
|
+
if (!res.ok) {
|
|
11539
|
+
return {
|
|
11540
|
+
available: null,
|
|
11541
|
+
currency: "CNY",
|
|
11542
|
+
reason: body?.data ? "" : `HTTP ${res.status}`
|
|
11543
|
+
};
|
|
11544
|
+
}
|
|
11545
|
+
const d = body?.data ?? {};
|
|
11546
|
+
const available = typeof d.available === "number" ? d.available : null;
|
|
11547
|
+
return {
|
|
11548
|
+
available,
|
|
11549
|
+
currency: typeof d.currency === "string" && d.currency ? d.currency : "CNY",
|
|
11550
|
+
...typeof d.reserved === "number" ? { reserved: d.reserved } : {},
|
|
11551
|
+
// Hub 没配 HUB_PUBLIC_URL 时退回用接入时的 hubUrl
|
|
11552
|
+
topupUrl: typeof d.topupUrl === "string" && d.topupUrl ? d.topupUrl : `${hub}/topup`,
|
|
11553
|
+
...available === null && typeof d.reason === "string" ? { reason: d.reason } : {}
|
|
11554
|
+
};
|
|
11555
|
+
} catch (e) {
|
|
11556
|
+
return {
|
|
11557
|
+
available: null,
|
|
11558
|
+
currency: "CNY",
|
|
11559
|
+
reason: `\u8FDE\u63A5 Hub \u5931\u8D25\uFF1A${e.message}`
|
|
11560
|
+
};
|
|
11561
|
+
}
|
|
11562
|
+
}
|
|
11563
|
+
var AUTH_RETRY_THROTTLE_MS = 6e4;
|
|
11564
|
+
var lastAuthRetryAt = 0;
|
|
11565
|
+
function healBuiltinOnAuthFailure(store, message) {
|
|
11566
|
+
if (!/401|unauthorized|invalid[_\s]*api[_\s]*key|api[\s_-]*key\s*(?:无效|invalid)/i.test(message)) return;
|
|
11567
|
+
if (!store.loadFleetCredential()) return;
|
|
11568
|
+
if (!store.loadNodeLlm().providers.some((p) => p.id === BUILTIN_PROVIDER_ID)) return;
|
|
11569
|
+
if (Date.now() - lastAuthRetryAt < AUTH_RETRY_THROTTLE_MS) return;
|
|
11570
|
+
lastAuthRetryAt = Date.now();
|
|
11571
|
+
void ensureBuiltinProvider(store, { force: true }).catch(() => {
|
|
11572
|
+
});
|
|
11573
|
+
}
|
|
11574
|
+
var lastFailureAt = 0;
|
|
11575
|
+
function scheduleBuiltinProvider(store, log = () => {
|
|
11576
|
+
}) {
|
|
11577
|
+
if (Date.now() - lastFailureAt < RETRY_AFTER_MS) return;
|
|
11578
|
+
void ensureBuiltinProvider(store).then((r) => {
|
|
11579
|
+
if (r.status === "ready") {
|
|
11580
|
+
if (r.refreshed) log(`[builtin-llm] \u5DF2\u4E0B\u53D1\u5185\u7F6E\u6A21\u578B\uFF1A${r.provider.name}\uFF08${r.provider.model}\uFF09`);
|
|
11581
|
+
return;
|
|
11582
|
+
}
|
|
11583
|
+
if (r.status === "disabled") return;
|
|
11584
|
+
lastFailureAt = Date.now();
|
|
11585
|
+
if (r.status === "unavailable") log(`[builtin-llm] \u6682\u65E0\u5185\u7F6E\u6A21\u578B\uFF1A${r.reason}`);
|
|
11586
|
+
else log(`[builtin-llm] \u62C9\u53D6\u5185\u7F6E\u6A21\u578B\u5931\u8D25\uFF1A${r.reason}`);
|
|
11587
|
+
}).catch(() => {
|
|
11588
|
+
lastFailureAt = Date.now();
|
|
11589
|
+
});
|
|
11590
|
+
}
|
|
11591
|
+
|
|
11129
11592
|
// src/server/usage.ts
|
|
11130
11593
|
init_cjs_shims();
|
|
11131
11594
|
var fs19 = __toESM(require("fs"), 1);
|
|
@@ -11197,6 +11660,19 @@ function addAgg(into, tokens, costUsd) {
|
|
|
11197
11660
|
function utcDay(iso) {
|
|
11198
11661
|
return iso.slice(0, 10);
|
|
11199
11662
|
}
|
|
11663
|
+
function sumAgg(values) {
|
|
11664
|
+
const out = zeroAgg();
|
|
11665
|
+
for (const v of values) {
|
|
11666
|
+
out.calls += v.calls;
|
|
11667
|
+
out.input += v.input;
|
|
11668
|
+
out.output += v.output;
|
|
11669
|
+
out.cacheRead += v.cacheRead;
|
|
11670
|
+
out.cacheWrite += v.cacheWrite;
|
|
11671
|
+
out.costUsd += v.costUsd;
|
|
11672
|
+
out.unpriced += v.unpriced;
|
|
11673
|
+
}
|
|
11674
|
+
return out;
|
|
11675
|
+
}
|
|
11200
11676
|
function monthOf(iso) {
|
|
11201
11677
|
return iso.slice(0, 7);
|
|
11202
11678
|
}
|
|
@@ -11472,6 +11948,23 @@ var UsageLedger = class {
|
|
|
11472
11948
|
}
|
|
11473
11949
|
return all;
|
|
11474
11950
|
}
|
|
11951
|
+
/**
|
|
11952
|
+
* 按「项目 × 天」聚合(供控制面把消耗拆到每个项目)。
|
|
11953
|
+
*
|
|
11954
|
+
* 汇总文件里只存了按项目、按天两个独立维度,拿不到交叉项,因此这里从
|
|
11955
|
+
* 账本明细现算——调用方(节点上报)只取最近 40 天,读的是 2 个月文件,
|
|
11956
|
+
* 5 分钟一次的成本可忽略。
|
|
11957
|
+
*/
|
|
11958
|
+
projectByDay(opts) {
|
|
11959
|
+
const out = {};
|
|
11960
|
+
for (const rec of this.allRecords({ from: opts.from, to: opts.to })) {
|
|
11961
|
+
const day = utcDay(rec.ts);
|
|
11962
|
+
const days = out[rec.projectId] ?? (out[rec.projectId] = {});
|
|
11963
|
+
const agg = days[day] ?? (days[day] = zeroAgg());
|
|
11964
|
+
addAgg(agg, rec, rec.costUsd);
|
|
11965
|
+
}
|
|
11966
|
+
return out;
|
|
11967
|
+
}
|
|
11475
11968
|
summary(opts) {
|
|
11476
11969
|
this.ensureInit();
|
|
11477
11970
|
const currency = (opts.currency ?? "USD").toUpperCase();
|
|
@@ -11771,22 +12264,157 @@ var EVENT_BUFFER_MAX = 2e3;
|
|
|
11771
12264
|
var EVENTS_ACK_MAX_BYTES = 512 * 1024;
|
|
11772
12265
|
var APPROVAL_TIMEOUT_MS = (Number(process.env.APPROVAL_TIMEOUT_SECONDS) || 15 * 60) * 1e3;
|
|
11773
12266
|
var LAST_USER_MESSAGE_MAX = 200;
|
|
11774
|
-
|
|
11775
|
-
|
|
11776
|
-
|
|
11777
|
-
|
|
11778
|
-
|
|
12267
|
+
var TURN_TEXT_MAX = 500;
|
|
12268
|
+
var TURN_REPLY_MAX_BYTES = 256 * 1024;
|
|
12269
|
+
var TURN_REPLY_MAX_TEXT = 256 * 1024;
|
|
12270
|
+
var TURNS_DEFAULT_LIMIT = 20;
|
|
12271
|
+
function clip(text, max = LAST_USER_MESSAGE_MAX) {
|
|
12272
|
+
const s = text ?? "";
|
|
12273
|
+
return s.length > max ? `${s.slice(0, max)}\u2026` : s;
|
|
12274
|
+
}
|
|
12275
|
+
function asNum(v) {
|
|
12276
|
+
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
12277
|
+
}
|
|
12278
|
+
function newTurnEntry(ev, input) {
|
|
12279
|
+
return {
|
|
12280
|
+
v: 1,
|
|
12281
|
+
startSeq: ev.seq,
|
|
12282
|
+
endSeq: ev.seq,
|
|
12283
|
+
startedAt: ev.ts,
|
|
12284
|
+
endedAt: null,
|
|
12285
|
+
status: "running",
|
|
12286
|
+
inputs: [{ seq: ev.seq, ts: ev.ts, text: clip(input, TURN_TEXT_MAX) }],
|
|
12287
|
+
replyPreview: "",
|
|
12288
|
+
producedFiles: [],
|
|
12289
|
+
llmCalls: 0,
|
|
12290
|
+
toolCalls: 0,
|
|
12291
|
+
tokensIn: 0,
|
|
12292
|
+
tokensOut: 0
|
|
12293
|
+
};
|
|
11779
12294
|
}
|
|
11780
|
-
function
|
|
11781
|
-
|
|
11782
|
-
|
|
11783
|
-
|
|
11784
|
-
|
|
11785
|
-
|
|
12295
|
+
function applyTurnEvent(turn, ev, toolSeen) {
|
|
12296
|
+
const d = ev.data ?? {};
|
|
12297
|
+
turn.endSeq = ev.seq;
|
|
12298
|
+
switch (ev.type) {
|
|
12299
|
+
case "queue_steering": {
|
|
12300
|
+
turn.inputs.push({
|
|
12301
|
+
seq: ev.seq,
|
|
12302
|
+
ts: ev.ts,
|
|
12303
|
+
text: clip(String(d["text"] ?? ""), TURN_TEXT_MAX)
|
|
12304
|
+
});
|
|
12305
|
+
break;
|
|
12306
|
+
}
|
|
12307
|
+
case "assistant_message": {
|
|
12308
|
+
turn.llmCalls += 1;
|
|
12309
|
+
const text = String(d["text"] ?? "");
|
|
12310
|
+
if (text) {
|
|
12311
|
+
turn.replyPreview = clip(text);
|
|
12312
|
+
turn.replySeq = ev.seq;
|
|
12313
|
+
}
|
|
12314
|
+
const u = d["usage"];
|
|
12315
|
+
if (u && typeof u === "object") {
|
|
12316
|
+
const usage = u;
|
|
12317
|
+
turn.tokensIn += asNum(usage["input"]);
|
|
12318
|
+
turn.tokensOut += asNum(usage["output"]);
|
|
12319
|
+
}
|
|
12320
|
+
break;
|
|
12321
|
+
}
|
|
12322
|
+
case "tool_started":
|
|
12323
|
+
case "tool_finished": {
|
|
12324
|
+
const key = String(d["reqId"] ?? "") || `${String(d["name"] ?? "")}:${ev.seq}`;
|
|
12325
|
+
if (!toolSeen.has(key)) {
|
|
12326
|
+
toolSeen.add(key);
|
|
12327
|
+
turn.toolCalls += 1;
|
|
12328
|
+
}
|
|
12329
|
+
break;
|
|
12330
|
+
}
|
|
12331
|
+
case "error": {
|
|
12332
|
+
turn.error = clip(String(d["message"] ?? d["error"] ?? ""));
|
|
12333
|
+
break;
|
|
12334
|
+
}
|
|
12335
|
+
default:
|
|
12336
|
+
break;
|
|
12337
|
+
}
|
|
11786
12338
|
}
|
|
11787
|
-
|
|
11788
|
-
|
|
11789
|
-
|
|
12339
|
+
function closeTurnEntry(turn, ev) {
|
|
12340
|
+
const d = ev.data ?? {};
|
|
12341
|
+
turn.endSeq = ev.seq;
|
|
12342
|
+
turn.endedAt = ev.ts;
|
|
12343
|
+
const status = String(d["status"] ?? "");
|
|
12344
|
+
turn.status = ev.type === "interrupted" ? status || "interrupted" : status || "completed";
|
|
12345
|
+
const files = d["produced_files"];
|
|
12346
|
+
if (Array.isArray(files)) {
|
|
12347
|
+
turn.producedFiles = files.map((x) => String(x ?? "")).filter(Boolean);
|
|
12348
|
+
}
|
|
12349
|
+
if (typeof d["iterations"] === "number") turn.iterations = d["iterations"];
|
|
12350
|
+
const startMs = Date.parse(turn.startedAt);
|
|
12351
|
+
const endMs = Date.parse(ev.ts);
|
|
12352
|
+
if (Number.isFinite(startMs) && Number.isFinite(endMs) && endMs >= startMs) {
|
|
12353
|
+
turn.durationMs = endMs - startMs;
|
|
12354
|
+
}
|
|
12355
|
+
}
|
|
12356
|
+
function buildTurnIndex(events) {
|
|
12357
|
+
const out = [];
|
|
12358
|
+
let cur = null;
|
|
12359
|
+
let toolSeen = /* @__PURE__ */ new Set();
|
|
12360
|
+
for (const ev of events) {
|
|
12361
|
+
if (ev.type === "turn_start") {
|
|
12362
|
+
cur = newTurnEntry(ev, String(ev.data?.["input"] ?? ""));
|
|
12363
|
+
toolSeen = /* @__PURE__ */ new Set();
|
|
12364
|
+
out.push(cur);
|
|
12365
|
+
continue;
|
|
12366
|
+
}
|
|
12367
|
+
if (!cur) continue;
|
|
12368
|
+
if (ev.type === "turn_end" || ev.type === "interrupted") {
|
|
12369
|
+
closeTurnEntry(cur, ev);
|
|
12370
|
+
cur = null;
|
|
12371
|
+
continue;
|
|
12372
|
+
}
|
|
12373
|
+
applyTurnEvent(cur, ev, toolSeen);
|
|
12374
|
+
}
|
|
12375
|
+
for (const t of out) {
|
|
12376
|
+
if (!t.endedAt && t.error) t.status = "error";
|
|
12377
|
+
}
|
|
12378
|
+
return out;
|
|
12379
|
+
}
|
|
12380
|
+
function summaryFromEntry(t) {
|
|
12381
|
+
return {
|
|
12382
|
+
startSeq: t.startSeq,
|
|
12383
|
+
endSeq: t.endSeq,
|
|
12384
|
+
startedAt: t.startedAt,
|
|
12385
|
+
endedAt: t.endedAt,
|
|
12386
|
+
status: t.status,
|
|
12387
|
+
input: t.inputs[0]?.text ?? "",
|
|
12388
|
+
reply: t.replyPreview,
|
|
12389
|
+
llmCalls: t.llmCalls,
|
|
12390
|
+
toolCalls: t.toolCalls,
|
|
12391
|
+
tokensIn: t.tokensIn,
|
|
12392
|
+
tokensOut: t.tokensOut,
|
|
12393
|
+
iterations: t.iterations,
|
|
12394
|
+
durationMs: t.durationMs,
|
|
12395
|
+
error: t.error,
|
|
12396
|
+
inputs: t.inputs,
|
|
12397
|
+
userCount: t.inputs.length,
|
|
12398
|
+
replySeq: t.replySeq,
|
|
12399
|
+
producedFiles: t.producedFiles
|
|
12400
|
+
};
|
|
12401
|
+
}
|
|
12402
|
+
function isAffirmative(answer) {
|
|
12403
|
+
const a = answer.trim().toLowerCase();
|
|
12404
|
+
if (!a) return false;
|
|
12405
|
+
if (["y", "yes", "approve", "approved", "ok", "true", "a", "1"].includes(a)) return true;
|
|
12406
|
+
return /^(yes|approve|同意|批准|允许|确定|可以|好的|确认)/.test(a);
|
|
12407
|
+
}
|
|
12408
|
+
function defer() {
|
|
12409
|
+
let resolve25;
|
|
12410
|
+
const promise = new Promise((r) => {
|
|
12411
|
+
resolve25 = r;
|
|
12412
|
+
});
|
|
12413
|
+
return { promise, resolve: resolve25 };
|
|
12414
|
+
}
|
|
12415
|
+
var SessionManager = class {
|
|
12416
|
+
constructor(store, approvalTimeoutMs = APPROVAL_TIMEOUT_MS, opts = {}) {
|
|
12417
|
+
this.store = store;
|
|
11790
12418
|
this.approvalTimeoutMs = approvalTimeoutMs;
|
|
11791
12419
|
this.opts = opts;
|
|
11792
12420
|
}
|
|
@@ -11795,6 +12423,10 @@ var SessionManager = class {
|
|
|
11795
12423
|
opts;
|
|
11796
12424
|
runtimes = /* @__PURE__ */ new Map();
|
|
11797
12425
|
// sessionId → runtime
|
|
12426
|
+
/** 轮次索引的内存副本(运行中的轮比磁盘索引新;重启后为空) */
|
|
12427
|
+
turnIndexCache = /* @__PURE__ */ new Map();
|
|
12428
|
+
/** 进行中的索引重建任务(并发请求合并到同一个) */
|
|
12429
|
+
turnIndexBuilds = /* @__PURE__ */ new Map();
|
|
11798
12430
|
/** 全局通知 SSE 订阅者(控制台通知中心) */
|
|
11799
12431
|
notificationSubs = /* @__PURE__ */ new Set();
|
|
11800
12432
|
/**
|
|
@@ -11849,6 +12481,17 @@ var SessionManager = class {
|
|
|
11849
12481
|
rt.status = status;
|
|
11850
12482
|
this._emitNotify(rt, "session_status", this._statusData(rt));
|
|
11851
12483
|
}
|
|
12484
|
+
/**
|
|
12485
|
+
* 通知里的「最近一条用户消息」:截断到 LAST_USER_MESSAGE_MAX。
|
|
12486
|
+
*
|
|
12487
|
+
* 三处共用(session_status / turn_completed / turn_stopped),避免各自写一遍
|
|
12488
|
+
* 截断规则后长度不一致——控制台弹窗与任务列表会显示同一句话的两个版本。
|
|
12489
|
+
*/
|
|
12490
|
+
_lastMessage(rt) {
|
|
12491
|
+
const text = (rt.lastInput ?? "").trim();
|
|
12492
|
+
if (!text) return null;
|
|
12493
|
+
return text.length > LAST_USER_MESSAGE_MAX ? `${text.slice(0, LAST_USER_MESSAGE_MAX)}\u2026` : text;
|
|
12494
|
+
}
|
|
11852
12495
|
/**
|
|
11853
12496
|
* session_status 通知载荷:状态 + 最近一条用户消息(截断)。
|
|
11854
12497
|
*
|
|
@@ -11857,10 +12500,24 @@ var SessionManager = class {
|
|
|
11857
12500
|
*/
|
|
11858
12501
|
_statusData(rt) {
|
|
11859
12502
|
const data = { status: rt.status };
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
12503
|
+
if (rt.lastStartedAt) data.lastStartedAt = rt.lastStartedAt;
|
|
12504
|
+
const msg = this._lastMessage(rt);
|
|
12505
|
+
if (msg) data.lastUserMessage = msg;
|
|
12506
|
+
return data;
|
|
12507
|
+
}
|
|
12508
|
+
/**
|
|
12509
|
+
* 任务结束类通知(完成 / 达到迭代上限)的载荷:在 turn_end 数据之外补上
|
|
12510
|
+
* 「会话名 + 最后一条用户消息」。
|
|
12511
|
+
*
|
|
12512
|
+
* 控制台右下角的完成弹窗要说明「哪个会话、用户让它做了什么」。这些字段
|
|
12513
|
+
* 以节点侧为准随通知一起下发,前端就不必依赖快照(nodeInfo / session_status)
|
|
12514
|
+
* 是否已经到达——刚打开页面或通知乱序时才不会退化成只剩一句「任务已完成」。
|
|
12515
|
+
*/
|
|
12516
|
+
_turnEndData(rt, eventData, extra) {
|
|
12517
|
+
const data = { ...eventData, ...extra };
|
|
12518
|
+
if (rt.name) data.sessionName = rt.name;
|
|
12519
|
+
const msg = this._lastMessage(rt);
|
|
12520
|
+
if (msg) data.lastUserMessage = msg;
|
|
11864
12521
|
return data;
|
|
11865
12522
|
}
|
|
11866
12523
|
/** 触发通知回调(无 handler 时为空操作,异常隔离不冒泡) */
|
|
@@ -12017,6 +12674,7 @@ data: ${JSON.stringify({ sessionId, seq: entry.seq, ts: entry.ts, data: entry.da
|
|
|
12017
12674
|
this.runtimes.delete(sessionId);
|
|
12018
12675
|
}
|
|
12019
12676
|
this.store.deleteSession(tenantId, sessionId);
|
|
12677
|
+
this.turnIndexCache.delete(sessionId);
|
|
12020
12678
|
this._publishNotify(tenantId, {
|
|
12021
12679
|
kind: "session_status",
|
|
12022
12680
|
sessionId,
|
|
@@ -12060,6 +12718,7 @@ data: ${JSON.stringify({ sessionId, seq: entry.seq, ts: entry.ts, data: entry.da
|
|
|
12060
12718
|
throw new ApiError(409, "busy", "\u4F1A\u8BDD\u6B63\u5728\u8FD0\u884C\u4E2D");
|
|
12061
12719
|
}
|
|
12062
12720
|
rt.engine.queueSteering(input);
|
|
12721
|
+
if (rt.pendingSteers.length === 0) rt.steerBaseInput = rt.lastInput;
|
|
12063
12722
|
rt.pendingSteers.push(input);
|
|
12064
12723
|
this._recordLastInput(rt, input);
|
|
12065
12724
|
return "steered";
|
|
@@ -12092,9 +12751,42 @@ data: ${JSON.stringify({ sessionId, seq: entry.seq, ts: entry.ts, data: entry.da
|
|
|
12092
12751
|
const rt = this._getOrLoadRuntime(tenant, sessionId);
|
|
12093
12752
|
if (!rt.engine) throw new ApiError(409, "not_running", "\u4F1A\u8BDD\u672A\u5728\u8FD0\u884C\u4E2D");
|
|
12094
12753
|
rt.engine.queueSteering(text);
|
|
12754
|
+
if (rt.pendingSteers.length === 0) rt.steerBaseInput = rt.lastInput;
|
|
12095
12755
|
rt.pendingSteers.push(text);
|
|
12096
12756
|
this._recordLastInput(rt, text);
|
|
12097
12757
|
}
|
|
12758
|
+
/**
|
|
12759
|
+
* 撤销一条尚未注入的补充信息(用户反悔)。
|
|
12760
|
+
*
|
|
12761
|
+
* 队列里的项还没进聊天流,此时只能靠中断整个任务来「收回」代价太大。
|
|
12762
|
+
* index 取自 GET /state 的 pendingSteers(与前端展示顺序一致),两侧同时删:
|
|
12763
|
+
* 只删前端那份,引擎下一迭代仍会把它注入对话;只删引擎那份,队列里会残留
|
|
12764
|
+
* 一条永远不注入的条目。
|
|
12765
|
+
*
|
|
12766
|
+
* 若撤的正是「最近用户指令」,把它还原成队列里的上一条、或本轮被覆盖前的
|
|
12767
|
+
* 指令——否则工作台还会显示这条已经被取消的内容。
|
|
12768
|
+
*
|
|
12769
|
+
* @returns 剩余待注入队列(前端据此刷新提示区)
|
|
12770
|
+
*/
|
|
12771
|
+
unsteer(tenant, sessionId, index) {
|
|
12772
|
+
const rt = this._getOrLoadRuntime(tenant, sessionId);
|
|
12773
|
+
if (!rt.engine || !rt.runTask) {
|
|
12774
|
+
throw new ApiError(409, "not_running", "\u4F1A\u8BDD\u672A\u5728\u8FD0\u884C\u4E2D");
|
|
12775
|
+
}
|
|
12776
|
+
if (!Number.isInteger(index) || index < 0 || index >= rt.pendingSteers.length) {
|
|
12777
|
+
throw new ApiError(409, "already_injected", "\u8FD9\u6761\u8865\u5145\u4FE1\u606F\u5DF2\u8FDB\u5165\u5BF9\u8BDD\uFF0C\u65E0\u6CD5\u64A4\u9500");
|
|
12778
|
+
}
|
|
12779
|
+
const [removed] = rt.pendingSteers.splice(index, 1);
|
|
12780
|
+
if (removed !== void 0) {
|
|
12781
|
+
rt.engine.removeSteering(removed);
|
|
12782
|
+
if (removed === rt.lastInput) {
|
|
12783
|
+
rt.lastInput = rt.pendingSteers.length > 0 ? rt.pendingSteers[rt.pendingSteers.length - 1] : rt.steerBaseInput;
|
|
12784
|
+
this._persist(rt);
|
|
12785
|
+
this._emitNotify(rt, "session_status", this._statusData(rt));
|
|
12786
|
+
}
|
|
12787
|
+
}
|
|
12788
|
+
return [...rt.pendingSteers];
|
|
12789
|
+
}
|
|
12098
12790
|
/**
|
|
12099
12791
|
* 已提交、尚未注入当前任务的补充信息。
|
|
12100
12792
|
*
|
|
@@ -12527,44 +13219,214 @@ data: ${JSON.stringify({ sessionId, seq: entry.seq, ts: entry.ts, data: entry.da
|
|
|
12527
13219
|
status: rt.status
|
|
12528
13220
|
};
|
|
12529
13221
|
}
|
|
12530
|
-
/**
|
|
12531
|
-
|
|
13222
|
+
/**
|
|
13223
|
+
* 事件分页(向前 / 向后两种方向)。
|
|
13224
|
+
*
|
|
13225
|
+
* - `since`:取 seq > since(实时增量 / 重连补齐)
|
|
13226
|
+
* - `before`:取 seq < before(控制台滚到顶部加载更早的对话)
|
|
13227
|
+
* - `maxBytes`:单次回执的字节预算(默认沿用 EVENTS_ACK_MAX_BYTES)
|
|
13228
|
+
*
|
|
13229
|
+
* 两者同时给出时按 `since`(向后)处理。`hasMore` 只对向前翻页有意义:
|
|
13230
|
+
* 表示本页最早一条之前还有更早的事件。
|
|
13231
|
+
*
|
|
13232
|
+
* 向前翻页一律走磁盘:内存缓冲只保留最近 EVENT_BUFFER_MAX 条,
|
|
13233
|
+
* 更早的事件只在 events.jsonl 里。
|
|
13234
|
+
*/
|
|
13235
|
+
eventsPage(tenantId, sessionId, opts = {}) {
|
|
12532
13236
|
const __t0 = dbgNow();
|
|
13237
|
+
const maxBytes = opts.maxBytes && opts.maxBytes > 0 ? opts.maxBytes : EVENTS_ACK_MAX_BYTES;
|
|
12533
13238
|
const rt = this.runtimes.get(sessionId);
|
|
13239
|
+
if (opts.before !== void 0) {
|
|
13240
|
+
const since2 = opts.since ?? 0;
|
|
13241
|
+
const page = this.store.loadEventPage(tenantId, sessionId, {
|
|
13242
|
+
before: opts.before,
|
|
13243
|
+
maxBytes
|
|
13244
|
+
});
|
|
13245
|
+
const ev2 = since2 > 0 ? page.events.filter((e) => e.seq > since2) : page.events;
|
|
13246
|
+
dbgMs(
|
|
13247
|
+
"session.eventsPage(before/\u78C1\u76D8)",
|
|
13248
|
+
__t0,
|
|
13249
|
+
// [dbg]
|
|
13250
|
+
`sid=${sessionId.slice(0, 8)} since=${since2} before=${opts.before} out=${ev2.length} more=${page.hasMore}`
|
|
13251
|
+
);
|
|
13252
|
+
return {
|
|
13253
|
+
events: ev2,
|
|
13254
|
+
latestSeq: rt?.seq ?? page.events.at(-1)?.seq ?? 0,
|
|
13255
|
+
earliestSeq: ev2[0]?.seq ?? 0,
|
|
13256
|
+
hasMore: page.hasMore
|
|
13257
|
+
};
|
|
13258
|
+
}
|
|
13259
|
+
const since = opts.since ?? 0;
|
|
12534
13260
|
if (rt) {
|
|
12535
|
-
const ev2 = this._boundedTail(rt.events.filter((e) => e.seq > since));
|
|
13261
|
+
const ev2 = this._boundedTail(rt.events.filter((e) => e.seq > since), maxBytes);
|
|
12536
13262
|
dbgMs(
|
|
12537
|
-
"session.
|
|
13263
|
+
"session.eventsPage(\u5185\u5B58)",
|
|
12538
13264
|
__t0,
|
|
12539
13265
|
// [dbg]
|
|
12540
13266
|
`sid=${sessionId.slice(0, 8)} since=${since} in=${rt.events.length} out=${ev2.length}`
|
|
12541
13267
|
);
|
|
12542
|
-
return {
|
|
13268
|
+
return {
|
|
13269
|
+
events: ev2,
|
|
13270
|
+
latestSeq: rt.seq,
|
|
13271
|
+
earliestSeq: ev2[0]?.seq ?? 0,
|
|
13272
|
+
// 内存缓冲被截断(或 since 早于缓冲起点)时,磁盘上可能还有更早的
|
|
13273
|
+
hasMore: !!ev2.length && ev2[0].seq > (rt.events[0]?.seq ?? 0)
|
|
13274
|
+
};
|
|
12543
13275
|
}
|
|
12544
13276
|
const all = this.store.loadEventsTail(tenantId, sessionId, EVENTS_ACK_MAX_BYTES * 2);
|
|
12545
|
-
const ev = this._boundedTail(
|
|
13277
|
+
const ev = this._boundedTail(
|
|
13278
|
+
all.filter((e) => e.seq > since),
|
|
13279
|
+
maxBytes
|
|
13280
|
+
);
|
|
12546
13281
|
dbgMs(
|
|
12547
|
-
"session.
|
|
13282
|
+
"session.eventsPage(\u78C1\u76D8)",
|
|
12548
13283
|
__t0,
|
|
12549
13284
|
// [dbg]
|
|
12550
13285
|
`sid=${sessionId.slice(0, 8)} since=${since} loaded=${all.length} out=${ev.length}`
|
|
12551
13286
|
);
|
|
12552
13287
|
return {
|
|
12553
13288
|
events: ev,
|
|
12554
|
-
latestSeq: all.at(-1)?.seq ?? 0
|
|
13289
|
+
latestSeq: all.at(-1)?.seq ?? 0,
|
|
13290
|
+
earliestSeq: ev[0]?.seq ?? 0,
|
|
13291
|
+
// 读的是文件尾部窗口:窗口已满说明更早的事件还在文件里
|
|
13292
|
+
hasMore: all.length > 0 && ev.length > 0 && ev[0].seq > all[0].seq
|
|
13293
|
+
};
|
|
13294
|
+
}
|
|
13295
|
+
/**
|
|
13296
|
+
* 兼容旧调用:只按 since 取增量。
|
|
13297
|
+
* @deprecated 新代码用 eventsPage(支持 before 向前翻页)
|
|
13298
|
+
*/
|
|
13299
|
+
eventsSince(tenantId, sessionId, since) {
|
|
13300
|
+
const r = this.eventsPage(tenantId, sessionId, { since });
|
|
13301
|
+
return { events: r.events, latestSeq: r.latestSeq };
|
|
13302
|
+
}
|
|
13303
|
+
/**
|
|
13304
|
+
* 「按 turn 汇总」的轻量列表:只回标题与统计,点开某一轮再单独拉该轮回复。
|
|
13305
|
+
*
|
|
13306
|
+
* 数据来自 turns.ndjson 索引(派生自事件流),读取成本与会话历史长度无关:
|
|
13307
|
+
* 老会话首次访问时后台/本请求内重建一次,之后每页只读几 KB。
|
|
13308
|
+
*
|
|
13309
|
+
* - `before`:只返回 startSeq < before 的轮次(向前翻页)
|
|
13310
|
+
* - `turns` 按时间升序;`hasMore` 表示更早还有轮次
|
|
13311
|
+
*/
|
|
13312
|
+
async sessionTurns(tenantId, sessionId, opts = {}) {
|
|
13313
|
+
const limit = Math.min(Math.max(opts.limit ?? TURNS_DEFAULT_LIMIT, 1), 100);
|
|
13314
|
+
const entries = await this._ensureTurnIndex(tenantId, sessionId);
|
|
13315
|
+
const before = opts.before;
|
|
13316
|
+
const endIdx = before === void 0 ? entries.length : entries.findIndex((t) => t.startSeq >= before);
|
|
13317
|
+
const end = endIdx < 0 ? entries.length : endIdx;
|
|
13318
|
+
const start = Math.max(0, end - limit);
|
|
13319
|
+
const picked = entries.slice(start, end);
|
|
13320
|
+
return {
|
|
13321
|
+
turns: picked.map(summaryFromEntry),
|
|
13322
|
+
hasMore: start > 0,
|
|
13323
|
+
earliestSeq: picked[0]?.startSeq ?? 0,
|
|
13324
|
+
total: entries.length,
|
|
13325
|
+
userTotal: entries.reduce((n, t) => n + t.inputs.length, 0)
|
|
12555
13326
|
};
|
|
12556
13327
|
}
|
|
13328
|
+
/**
|
|
13329
|
+
* 某一轮的用户消息与最终回复(含产出文件)。
|
|
13330
|
+
*
|
|
13331
|
+
* 只读两条索引记录 + 尾部一个事件页,不会因该轮工具输出很大而变慢:
|
|
13332
|
+
* 回复正文紧邻 turn_end,按 replySeq 精确读即可。
|
|
13333
|
+
*/
|
|
13334
|
+
async sessionTurnReply(tenantId, sessionId, startSeq) {
|
|
13335
|
+
const entries = await this._ensureTurnIndex(tenantId, sessionId);
|
|
13336
|
+
const turn = entries.find((t) => t.startSeq === startSeq);
|
|
13337
|
+
if (!turn) throw new ApiError(404, "not_found", "\u8F6E\u6B21\u4E0D\u5B58\u5728");
|
|
13338
|
+
let reply = null;
|
|
13339
|
+
let truncated = false;
|
|
13340
|
+
if (turn.replySeq !== void 0) {
|
|
13341
|
+
const page = this.store.loadEventPage(tenantId, sessionId, {
|
|
13342
|
+
before: turn.replySeq + 1,
|
|
13343
|
+
maxBytes: TURN_REPLY_MAX_BYTES
|
|
13344
|
+
});
|
|
13345
|
+
const ev = page.events.find((e) => e.seq === turn.replySeq);
|
|
13346
|
+
if (ev && ev.type === "assistant_message") {
|
|
13347
|
+
const text = String(ev.data?.["text"] ?? "");
|
|
13348
|
+
if (text.length > TURN_REPLY_MAX_TEXT) {
|
|
13349
|
+
reply = { seq: ev.seq, ts: ev.ts, text: text.slice(0, TURN_REPLY_MAX_TEXT) };
|
|
13350
|
+
truncated = true;
|
|
13351
|
+
} else {
|
|
13352
|
+
reply = { seq: ev.seq, ts: ev.ts, text };
|
|
13353
|
+
}
|
|
13354
|
+
}
|
|
13355
|
+
}
|
|
13356
|
+
return {
|
|
13357
|
+
startSeq: turn.startSeq,
|
|
13358
|
+
endSeq: turn.endSeq,
|
|
13359
|
+
status: turn.status,
|
|
13360
|
+
startedAt: turn.startedAt,
|
|
13361
|
+
endedAt: turn.endedAt,
|
|
13362
|
+
userMessages: turn.inputs,
|
|
13363
|
+
reply,
|
|
13364
|
+
truncated,
|
|
13365
|
+
producedFiles: turn.producedFiles
|
|
13366
|
+
};
|
|
13367
|
+
}
|
|
13368
|
+
/**
|
|
13369
|
+
* 取轮次索引;缺失时从事件流重建(同一会话的并发请求共用一个重建任务)。
|
|
13370
|
+
*
|
|
13371
|
+
* 索引是派生数据:损坏/落后都只需重建,不影响会话本身。
|
|
13372
|
+
*/
|
|
13373
|
+
/**
|
|
13374
|
+
* 取轮次索引;缺失/落后时从事件流重建(同一会话的并发请求共用一个重建任务)。
|
|
13375
|
+
*
|
|
13376
|
+
* 索引是派生数据:损坏或落后都只需重建,不影响会话本身。
|
|
13377
|
+
*/
|
|
13378
|
+
async _ensureTurnIndex(tenantId, sessionId) {
|
|
13379
|
+
let entries = this._mergeTurnIndex(tenantId, sessionId);
|
|
13380
|
+
if (entries.length > 0 && !this.runtimes.has(sessionId)) {
|
|
13381
|
+
const last = entries[entries.length - 1];
|
|
13382
|
+
const tail = this.store.loadEventsTail(tenantId, sessionId, 64 * 1024);
|
|
13383
|
+
const newTurnStarted = tail.some((e) => e.type === "turn_start" && e.seq > last.endSeq);
|
|
13384
|
+
if (newTurnStarted || last.status === "running") entries = [];
|
|
13385
|
+
}
|
|
13386
|
+
if (entries.length > 0) return entries;
|
|
13387
|
+
const pending = this.turnIndexBuilds.get(sessionId);
|
|
13388
|
+
if (pending) return pending;
|
|
13389
|
+
const task = this._rebuildTurnIndex(tenantId, sessionId).finally(() => {
|
|
13390
|
+
this.turnIndexBuilds.delete(sessionId);
|
|
13391
|
+
});
|
|
13392
|
+
this.turnIndexBuilds.set(sessionId, task);
|
|
13393
|
+
return task;
|
|
13394
|
+
}
|
|
13395
|
+
/** 合并磁盘索引与内存索引(内存版本更新,同一 startSeq 以内存为准) */
|
|
13396
|
+
_mergeTurnIndex(tenantId, sessionId) {
|
|
13397
|
+
const byStart = /* @__PURE__ */ new Map();
|
|
13398
|
+
for (const e of this.store.loadTurnIndex(tenantId, sessionId)) byStart.set(e.startSeq, e);
|
|
13399
|
+
for (const e of this.turnIndexCache.get(sessionId) ?? []) byStart.set(e.startSeq, e);
|
|
13400
|
+
return [...byStart.values()].sort((a, b) => a.startSeq - b.startSeq);
|
|
13401
|
+
}
|
|
13402
|
+
/** 从 events.jsonl 顺序流式重建轮次索引(一次性,之后增量维护) */
|
|
13403
|
+
async _rebuildTurnIndex(tenantId, sessionId) {
|
|
13404
|
+
const __t0 = dbgNow();
|
|
13405
|
+
const events = await this.store.readEventsAsync(tenantId, sessionId);
|
|
13406
|
+
const entries = buildTurnIndex(events);
|
|
13407
|
+
if (entries.length > 0) {
|
|
13408
|
+
await this.store.saveTurnIndex(tenantId, sessionId, entries);
|
|
13409
|
+
if (this.runtimes.has(sessionId)) this.turnIndexCache.set(sessionId, entries);
|
|
13410
|
+
}
|
|
13411
|
+
dbgMs(
|
|
13412
|
+
"session._rebuildTurnIndex",
|
|
13413
|
+
__t0,
|
|
13414
|
+
// [dbg]
|
|
13415
|
+
`sid=${sessionId.slice(0, 8)} events=${events.length} turns=${entries.length}`
|
|
13416
|
+
);
|
|
13417
|
+
return entries;
|
|
13418
|
+
}
|
|
12557
13419
|
/**
|
|
12558
13420
|
* 按字节预算截取事件「尾部」(至少保留最后一条),确保 MQTT 回执远小于单包上限。
|
|
12559
13421
|
* 供 get_events 使用:会话历史可任意大,但 MQTT 链路不能承载大包。
|
|
12560
13422
|
*/
|
|
12561
|
-
_boundedTail(events) {
|
|
13423
|
+
_boundedTail(events, maxBytes = EVENTS_ACK_MAX_BYTES) {
|
|
12562
13424
|
const __t0 = dbgNow();
|
|
12563
13425
|
let total = 0;
|
|
12564
13426
|
let i = events.length;
|
|
12565
13427
|
while (i > 0) {
|
|
12566
13428
|
const size = Buffer.byteLength(JSON.stringify(events[i - 1]));
|
|
12567
|
-
if (total > 0 && total + size >
|
|
13429
|
+
if (total > 0 && total + size > maxBytes) break;
|
|
12568
13430
|
total += size;
|
|
12569
13431
|
i -= 1;
|
|
12570
13432
|
}
|
|
@@ -12684,6 +13546,7 @@ data: ${JSON.stringify(entry.data)}
|
|
|
12684
13546
|
nameSource: rec.nameSource,
|
|
12685
13547
|
taskCount: rec.taskCount ?? 0,
|
|
12686
13548
|
lastCompletedAt: rec.lastCompletedAt,
|
|
13549
|
+
lastStartedAt: rec.lastStartedAt,
|
|
12687
13550
|
naming: false,
|
|
12688
13551
|
turnStartedAt: 0
|
|
12689
13552
|
};
|
|
@@ -12701,10 +13564,11 @@ data: ${JSON.stringify(entry.data)}
|
|
|
12701
13564
|
}
|
|
12702
13565
|
/** 装配引擎并启动一轮 Turn(供 submitTurn / submitOrSteer 复用) */
|
|
12703
13566
|
_startTurn(rt, input) {
|
|
13567
|
+
const engine = this._buildEngine(rt.tenant, rt, true);
|
|
12704
13568
|
rt.lastInput = input;
|
|
12705
|
-
this._setStatus(rt, "running");
|
|
12706
13569
|
rt.turnStartedAt = Date.now();
|
|
12707
|
-
|
|
13570
|
+
rt.lastStartedAt = new Date(rt.turnStartedAt).toISOString();
|
|
13571
|
+
this._setStatus(rt, "running");
|
|
12708
13572
|
rt.engine = engine;
|
|
12709
13573
|
this._persist(rt);
|
|
12710
13574
|
rt.runTask = this._drive(rt, engine.run(input));
|
|
@@ -12763,18 +13627,18 @@ data: ${JSON.stringify(entry.data)}
|
|
|
12763
13627
|
this._setStatus(rt, "completed");
|
|
12764
13628
|
rt.taskCount += 1;
|
|
12765
13629
|
rt.lastCompletedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12766
|
-
this._emitNotify(
|
|
12767
|
-
|
|
12768
|
-
|
|
12769
|
-
durationMs
|
|
12770
|
-
|
|
13630
|
+
this._emitNotify(
|
|
13631
|
+
rt,
|
|
13632
|
+
"turn_completed",
|
|
13633
|
+
this._turnEndData(rt, event.data, { summary, durationMs })
|
|
13634
|
+
);
|
|
12771
13635
|
} else if (status === "max_iterations_exceeded") {
|
|
12772
13636
|
this._setStatus(rt, "stopped");
|
|
12773
|
-
this._emitNotify(
|
|
12774
|
-
|
|
12775
|
-
|
|
12776
|
-
durationMs
|
|
12777
|
-
|
|
13637
|
+
this._emitNotify(
|
|
13638
|
+
rt,
|
|
13639
|
+
"turn_stopped",
|
|
13640
|
+
this._turnEndData(rt, event.data, { summary, durationMs })
|
|
13641
|
+
);
|
|
12778
13642
|
}
|
|
12779
13643
|
this._persist(rt);
|
|
12780
13644
|
if (status === "completed" && engine && shouldAutoName(rt.taskCount, rt.nameSource)) {
|
|
@@ -12785,7 +13649,8 @@ data: ${JSON.stringify(entry.data)}
|
|
|
12785
13649
|
case "error" /* ERROR */:
|
|
12786
13650
|
this._setStatus(rt, "error");
|
|
12787
13651
|
this._persist(rt);
|
|
12788
|
-
this._emitNotify(rt, "error", event.data);
|
|
13652
|
+
this._emitNotify(rt, "error", this._turnEndData(rt, event.data, {}));
|
|
13653
|
+
healBuiltinOnAuthFailure(this.store, String(event.data["error"] ?? ""));
|
|
12789
13654
|
break;
|
|
12790
13655
|
case "interrupted" /* INTERRUPTED */:
|
|
12791
13656
|
this._setStatus(rt, "interrupted");
|
|
@@ -12811,9 +13676,17 @@ data: ${JSON.stringify(entry.data)}
|
|
|
12811
13676
|
this._setStatus(rt, "error");
|
|
12812
13677
|
this._persist(rt);
|
|
12813
13678
|
} finally {
|
|
13679
|
+
if (rt.turnIndex) {
|
|
13680
|
+
const turn = rt.turnIndex;
|
|
13681
|
+
if (!turn.endedAt && turn.error) turn.status = "error";
|
|
13682
|
+
this._appendTurnIndex(rt, turn);
|
|
13683
|
+
rt.turnIndex = void 0;
|
|
13684
|
+
rt.turnToolKeys = void 0;
|
|
13685
|
+
}
|
|
12814
13686
|
rt.runTask = null;
|
|
12815
13687
|
rt.engine = null;
|
|
12816
13688
|
rt.pendingSteers = [];
|
|
13689
|
+
rt.steerBaseInput = void 0;
|
|
12817
13690
|
}
|
|
12818
13691
|
}
|
|
12819
13692
|
_buffer(rt, event) {
|
|
@@ -12826,6 +13699,7 @@ data: ${JSON.stringify(entry.data)}
|
|
|
12826
13699
|
rt.events.push(entry);
|
|
12827
13700
|
if (rt.events.length > EVENT_BUFFER_MAX) rt.events.shift();
|
|
12828
13701
|
void this.store.appendEvent(rt.tenantId, rt.sessionId, entry);
|
|
13702
|
+
this._trackTurnIndex(rt, entry);
|
|
12829
13703
|
for (const res of rt.subscribers) this._sseSend(res, entry);
|
|
12830
13704
|
this._broadcastSessionEvent(rt.tenantId, rt.sessionId, entry);
|
|
12831
13705
|
try {
|
|
@@ -12833,6 +13707,42 @@ data: ${JSON.stringify(entry.data)}
|
|
|
12833
13707
|
} catch {
|
|
12834
13708
|
}
|
|
12835
13709
|
}
|
|
13710
|
+
/**
|
|
13711
|
+
* 维护运行中轮次的索引(in-memory 记账 + 关键节点落盘)。
|
|
13712
|
+
*
|
|
13713
|
+
* 落盘只在「开轮 / 补充消息 / 收轮」发生,delta 事件仅推进 endSeq 与统计,
|
|
13714
|
+
* 因此索引写入量是每轮 2~3 行,与会话事件量无关。
|
|
13715
|
+
*/
|
|
13716
|
+
_trackTurnIndex(rt, ev) {
|
|
13717
|
+
if (ev.type === "turn_start") {
|
|
13718
|
+
const turn2 = newTurnEntry(ev, String(ev.data?.["input"] ?? ""));
|
|
13719
|
+
rt.turnIndex = turn2;
|
|
13720
|
+
rt.turnToolKeys = /* @__PURE__ */ new Set();
|
|
13721
|
+
this._appendTurnIndex(rt, turn2);
|
|
13722
|
+
return;
|
|
13723
|
+
}
|
|
13724
|
+
const turn = rt.turnIndex;
|
|
13725
|
+
if (!turn) return;
|
|
13726
|
+
if (ev.type === "turn_end" || ev.type === "interrupted") {
|
|
13727
|
+
closeTurnEntry(turn, ev);
|
|
13728
|
+
this._appendTurnIndex(rt, turn);
|
|
13729
|
+
rt.turnIndex = void 0;
|
|
13730
|
+
rt.turnToolKeys = void 0;
|
|
13731
|
+
return;
|
|
13732
|
+
}
|
|
13733
|
+
if (!rt.turnToolKeys) rt.turnToolKeys = /* @__PURE__ */ new Set();
|
|
13734
|
+
const inputsBefore = turn.inputs.length;
|
|
13735
|
+
applyTurnEvent(turn, ev, rt.turnToolKeys);
|
|
13736
|
+
if (turn.inputs.length !== inputsBefore) this._appendTurnIndex(rt, turn);
|
|
13737
|
+
}
|
|
13738
|
+
/** 写入一版轮次索引(内存副本 + 异步落盘) */
|
|
13739
|
+
_appendTurnIndex(rt, turn) {
|
|
13740
|
+
const cached3 = this.turnIndexCache.get(rt.sessionId) ?? [];
|
|
13741
|
+
const i = cached3.findIndex((t) => t.startSeq === turn.startSeq);
|
|
13742
|
+
const next = i >= 0 ? [...cached3.slice(0, i), turn, ...cached3.slice(i + 1)] : [...cached3, turn];
|
|
13743
|
+
this.turnIndexCache.set(rt.sessionId, next);
|
|
13744
|
+
void this.store.appendTurnIndex(rt.tenantId, rt.sessionId, turn);
|
|
13745
|
+
}
|
|
12836
13746
|
/** 回收内存 run 任务(释放引用交给 GC;磁盘状态保持不动) */
|
|
12837
13747
|
_recycleRunTask(rt) {
|
|
12838
13748
|
rt.runTask = null;
|
|
@@ -12870,6 +13780,7 @@ data: ${JSON.stringify(entry.data)}
|
|
|
12870
13780
|
nameSource: rt.nameSource,
|
|
12871
13781
|
taskCount: rt.taskCount,
|
|
12872
13782
|
lastCompletedAt: rt.lastCompletedAt,
|
|
13783
|
+
lastStartedAt: rt.lastStartedAt,
|
|
12873
13784
|
modelOverridden: rt.modelOverridden,
|
|
12874
13785
|
providerId: rt.providerId,
|
|
12875
13786
|
permissionMode: rt.permissionMode,
|
|
@@ -13163,7 +14074,6 @@ var ApiError = class extends Error {
|
|
|
13163
14074
|
};
|
|
13164
14075
|
|
|
13165
14076
|
// src/server/projects.ts
|
|
13166
|
-
var DEFAULT_MODEL = () => process.env.OPENAI_MODEL || process.env.MODEL || "gpt-4o";
|
|
13167
14077
|
function toProjectSummary(store, tenant) {
|
|
13168
14078
|
return {
|
|
13169
14079
|
id: tenant.id,
|
|
@@ -13273,6 +14183,7 @@ function allSessionStates(store, manager) {
|
|
|
13273
14183
|
name: s.name ?? null,
|
|
13274
14184
|
status: s.status,
|
|
13275
14185
|
lastUserMessage: lastUserMessage(s),
|
|
14186
|
+
lastStartedAt: s.lastStartedAt ?? null,
|
|
13276
14187
|
lastCompletedAt: lastCompletedAt(s)
|
|
13277
14188
|
});
|
|
13278
14189
|
}
|
|
@@ -13302,7 +14213,7 @@ function createProject(store, input) {
|
|
|
13302
14213
|
id,
|
|
13303
14214
|
name: explicitName || defaultName,
|
|
13304
14215
|
apiKey: generateApiKey(),
|
|
13305
|
-
model: typeof raw.model === "string" && raw.model.trim() ? raw.model.trim() :
|
|
14216
|
+
model: typeof raw.model === "string" && raw.model.trim() ? raw.model.trim() : defaultTenantModel(store),
|
|
13306
14217
|
provider: {
|
|
13307
14218
|
apiKey: typeof raw.providerApiKey === "string" && raw.providerApiKey.trim() ? raw.providerApiKey.trim() : void 0,
|
|
13308
14219
|
baseURL: typeof raw.baseURL === "string" && raw.baseURL.trim() ? raw.baseURL.trim() : void 0
|
|
@@ -13327,7 +14238,7 @@ function buildNodeInfo(store, opts) {
|
|
|
13327
14238
|
nodeId: opts.nodeId ?? ensureNodeId(store),
|
|
13328
14239
|
platform: process.platform,
|
|
13329
14240
|
mode: process.env.NODE_MODE ?? "standalone",
|
|
13330
|
-
version: "0.2.
|
|
14241
|
+
version: "0.2.20",
|
|
13331
14242
|
admin: opts.admin,
|
|
13332
14243
|
projects: opts.projects
|
|
13333
14244
|
};
|
|
@@ -13449,7 +14360,7 @@ async function maybeAccountEnroll(opts = {}) {
|
|
|
13449
14360
|
cliErr(`\u63A5\u5165\u68C0\u67E5\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
|
|
13450
14361
|
return;
|
|
13451
14362
|
}
|
|
13452
|
-
const rl2 =
|
|
14363
|
+
const rl2 = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
13453
14364
|
try {
|
|
13454
14365
|
await runWizard(rl2, store, opts);
|
|
13455
14366
|
} catch (e) {
|
|
@@ -13543,7 +14454,7 @@ var path25 = __toESM(require("path"), 1);
|
|
|
13543
14454
|
var import_node_child_process4 = require("child_process");
|
|
13544
14455
|
var import_node_module3 = require("module");
|
|
13545
14456
|
function currentVersion() {
|
|
13546
|
-
const injected = "0.2.
|
|
14457
|
+
const injected = "0.2.20"?.trim();
|
|
13547
14458
|
if (injected) return injected;
|
|
13548
14459
|
try {
|
|
13549
14460
|
const req2 = (0, import_node_module3.createRequire)(importMetaUrl);
|
|
@@ -14259,7 +15170,7 @@ async function runLinkCommand(argv, ctx = {}) {
|
|
|
14259
15170
|
const getRl = () => {
|
|
14260
15171
|
if (ctx.rl) return asPrompter(ctx.rl);
|
|
14261
15172
|
if (!interactive) return null;
|
|
14262
|
-
ownedRl =
|
|
15173
|
+
ownedRl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
14263
15174
|
return asPrompter(ownedRl);
|
|
14264
15175
|
};
|
|
14265
15176
|
const closeOwnedRl = () => {
|
|
@@ -14879,9 +15790,133 @@ function makeDirIn(parentInput, name) {
|
|
|
14879
15790
|
init_cjs_shims();
|
|
14880
15791
|
var fs23 = __toESM(require("fs"), 1);
|
|
14881
15792
|
var path30 = __toESM(require("path"), 1);
|
|
15793
|
+
|
|
15794
|
+
// src/server/zipArchive.ts
|
|
15795
|
+
init_cjs_shims();
|
|
15796
|
+
var zlib = __toESM(require("zlib"), 1);
|
|
15797
|
+
var MAX_ZIP_ENTRIES = 2e4;
|
|
15798
|
+
var LOCAL_SIG = 67324752;
|
|
15799
|
+
var CENTRAL_SIG = 33639248;
|
|
15800
|
+
var EOCD_SIG = 101010256;
|
|
15801
|
+
var FLAG_UTF8 = 2048;
|
|
15802
|
+
var METHOD_STORE = 0;
|
|
15803
|
+
var METHOD_DEFLATE = 8;
|
|
15804
|
+
var CRC_TABLE = (() => {
|
|
15805
|
+
const table = new Int32Array(256);
|
|
15806
|
+
for (let n = 0; n < 256; n++) {
|
|
15807
|
+
let c = n;
|
|
15808
|
+
for (let k = 0; k < 8; k++) {
|
|
15809
|
+
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
15810
|
+
}
|
|
15811
|
+
table[n] = c;
|
|
15812
|
+
}
|
|
15813
|
+
return table;
|
|
15814
|
+
})();
|
|
15815
|
+
function crc32(buf) {
|
|
15816
|
+
let c = -1;
|
|
15817
|
+
for (let i = 0; i < buf.length; i++) {
|
|
15818
|
+
c = CRC_TABLE[(c ^ buf[i]) & 255] ^ c >>> 8;
|
|
15819
|
+
}
|
|
15820
|
+
return (c ^ -1) >>> 0;
|
|
15821
|
+
}
|
|
15822
|
+
function dosDateTime(d) {
|
|
15823
|
+
const year = Math.max(1980, d.getFullYear());
|
|
15824
|
+
return {
|
|
15825
|
+
time: d.getHours() << 11 | d.getMinutes() << 5 | d.getSeconds() >> 1,
|
|
15826
|
+
date: year - 1980 << 9 | d.getMonth() + 1 << 5 | d.getDate()
|
|
15827
|
+
};
|
|
15828
|
+
}
|
|
15829
|
+
function buildZip(entries) {
|
|
15830
|
+
if (entries.length > MAX_ZIP_ENTRIES) {
|
|
15831
|
+
throw new Error(`\u5F52\u6863\u6761\u76EE\u8FC7\u591A\uFF08${entries.length} > ${MAX_ZIP_ENTRIES}\uFF09`);
|
|
15832
|
+
}
|
|
15833
|
+
const chunks = [];
|
|
15834
|
+
const central = [];
|
|
15835
|
+
let offset = 0;
|
|
15836
|
+
for (const e of entries) {
|
|
15837
|
+
const name = Buffer.from(e.name, "utf-8");
|
|
15838
|
+
const isDir = e.name.endsWith("/");
|
|
15839
|
+
const raw = isDir ? Buffer.alloc(0) : e.data ?? Buffer.alloc(0);
|
|
15840
|
+
let method = METHOD_STORE;
|
|
15841
|
+
let body = raw;
|
|
15842
|
+
if (!isDir && raw.length > 0) {
|
|
15843
|
+
const deflated = zlib.deflateRawSync(raw, { level: 6 });
|
|
15844
|
+
if (deflated.length < raw.length) {
|
|
15845
|
+
method = METHOD_DEFLATE;
|
|
15846
|
+
body = deflated;
|
|
15847
|
+
}
|
|
15848
|
+
}
|
|
15849
|
+
const crc = isDir ? 0 : crc32(raw);
|
|
15850
|
+
const { time, date } = dosDateTime(e.mtime ?? /* @__PURE__ */ new Date());
|
|
15851
|
+
const local = Buffer.alloc(30);
|
|
15852
|
+
local.writeUInt32LE(LOCAL_SIG, 0);
|
|
15853
|
+
local.writeUInt16LE(20, 4);
|
|
15854
|
+
local.writeUInt16LE(FLAG_UTF8, 6);
|
|
15855
|
+
local.writeUInt16LE(method, 8);
|
|
15856
|
+
local.writeUInt16LE(time, 10);
|
|
15857
|
+
local.writeUInt16LE(date, 12);
|
|
15858
|
+
local.writeUInt32LE(crc, 14);
|
|
15859
|
+
local.writeUInt32LE(body.length, 18);
|
|
15860
|
+
local.writeUInt32LE(raw.length, 22);
|
|
15861
|
+
local.writeUInt16LE(name.length, 26);
|
|
15862
|
+
local.writeUInt16LE(0, 28);
|
|
15863
|
+
chunks.push(local, name);
|
|
15864
|
+
if (body.length > 0) chunks.push(body);
|
|
15865
|
+
central.push({
|
|
15866
|
+
name,
|
|
15867
|
+
method,
|
|
15868
|
+
crc,
|
|
15869
|
+
csize: body.length,
|
|
15870
|
+
usize: raw.length,
|
|
15871
|
+
offset,
|
|
15872
|
+
time,
|
|
15873
|
+
date,
|
|
15874
|
+
isDir
|
|
15875
|
+
});
|
|
15876
|
+
offset += local.length + name.length + body.length;
|
|
15877
|
+
}
|
|
15878
|
+
const cdStart = offset;
|
|
15879
|
+
for (const c of central) {
|
|
15880
|
+
const head = Buffer.alloc(46);
|
|
15881
|
+
head.writeUInt32LE(CENTRAL_SIG, 0);
|
|
15882
|
+
head.writeUInt16LE(20, 4);
|
|
15883
|
+
head.writeUInt16LE(20, 6);
|
|
15884
|
+
head.writeUInt16LE(FLAG_UTF8, 8);
|
|
15885
|
+
head.writeUInt16LE(c.method, 10);
|
|
15886
|
+
head.writeUInt16LE(c.time, 12);
|
|
15887
|
+
head.writeUInt16LE(c.date, 14);
|
|
15888
|
+
head.writeUInt32LE(c.crc, 16);
|
|
15889
|
+
head.writeUInt32LE(c.csize, 20);
|
|
15890
|
+
head.writeUInt32LE(c.usize, 24);
|
|
15891
|
+
head.writeUInt16LE(c.name.length, 28);
|
|
15892
|
+
head.writeUInt16LE(0, 30);
|
|
15893
|
+
head.writeUInt16LE(0, 32);
|
|
15894
|
+
head.writeUInt16LE(0, 34);
|
|
15895
|
+
head.writeUInt16LE(0, 36);
|
|
15896
|
+
head.writeUInt32LE(c.isDir ? 16 : 0, 38);
|
|
15897
|
+
head.writeUInt32LE(c.offset, 42);
|
|
15898
|
+
chunks.push(head, c.name);
|
|
15899
|
+
offset += head.length + c.name.length;
|
|
15900
|
+
}
|
|
15901
|
+
const eocd = Buffer.alloc(22);
|
|
15902
|
+
eocd.writeUInt32LE(EOCD_SIG, 0);
|
|
15903
|
+
eocd.writeUInt16LE(0, 4);
|
|
15904
|
+
eocd.writeUInt16LE(0, 6);
|
|
15905
|
+
eocd.writeUInt16LE(central.length, 8);
|
|
15906
|
+
eocd.writeUInt16LE(central.length, 10);
|
|
15907
|
+
eocd.writeUInt32LE(offset - cdStart, 12);
|
|
15908
|
+
eocd.writeUInt32LE(cdStart, 16);
|
|
15909
|
+
eocd.writeUInt16LE(0, 20);
|
|
15910
|
+
chunks.push(eocd);
|
|
15911
|
+
return Buffer.concat(chunks);
|
|
15912
|
+
}
|
|
15913
|
+
|
|
15914
|
+
// src/server/projectFiles.ts
|
|
14882
15915
|
var MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
14883
15916
|
var MAX_DOWNLOAD_BYTES = 16 * 1024 * 1024;
|
|
15917
|
+
var MAX_BINARY_BYTES = 2.5 * 1024 * 1024;
|
|
14884
15918
|
var MAX_ENTRIES2 = 2e3;
|
|
15919
|
+
var MAX_ZIP_BYTES = 100 * 1024 * 1024;
|
|
14885
15920
|
function toPosix(p) {
|
|
14886
15921
|
return p.split(path30.sep).join("/");
|
|
14887
15922
|
}
|
|
@@ -15028,6 +16063,26 @@ function writeProjectFile(workspaceRoot, relPath, content) {
|
|
|
15028
16063
|
}
|
|
15029
16064
|
return { path: toPosix(path30.relative(base, file)), size: buf.length };
|
|
15030
16065
|
}
|
|
16066
|
+
function writeProjectFileBase64(workspaceRoot, relPath, base64) {
|
|
16067
|
+
const base = projectRoot(workspaceRoot);
|
|
16068
|
+
const rel = (relPath ?? "").trim();
|
|
16069
|
+
if (!rel) throw new Error("\u6587\u4EF6\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A");
|
|
16070
|
+
const file = resolveWithin(base, rel);
|
|
16071
|
+
const b64 = String(base64 ?? "").replace(/\s+/g, "");
|
|
16072
|
+
if (!b64) throw new Error("\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A");
|
|
16073
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(b64)) throw new Error("\u5185\u5BB9\u4E0D\u662F\u5408\u6CD5\u7684 base64");
|
|
16074
|
+
if (Math.floor(b64.length * 3 / 4) > MAX_BINARY_BYTES) {
|
|
16075
|
+
throw new Error(`\u5185\u5BB9\u8D85\u8FC7 ${MAX_BINARY_BYTES / (1024 * 1024)}MB \u4E0A\u9650\uFF0C\u65E0\u6CD5\u4FDD\u5B58`);
|
|
16076
|
+
}
|
|
16077
|
+
const buf = Buffer.from(b64, "base64");
|
|
16078
|
+
try {
|
|
16079
|
+
fs23.mkdirSync(path30.dirname(file), { recursive: true });
|
|
16080
|
+
fs23.writeFileSync(file, buf);
|
|
16081
|
+
} catch (e) {
|
|
16082
|
+
throw new Error(`\u5199\u5165\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
|
|
16083
|
+
}
|
|
16084
|
+
return { path: toPosix(path30.relative(base, file)), size: buf.length };
|
|
16085
|
+
}
|
|
15031
16086
|
function createProjectDir(workspaceRoot, relPath) {
|
|
15032
16087
|
const base = projectRoot(workspaceRoot);
|
|
15033
16088
|
const rel = (relPath ?? "").trim();
|
|
@@ -15101,6 +16156,170 @@ function searchProjectFiles(workspaceRoot, query) {
|
|
|
15101
16156
|
walk(base, "");
|
|
15102
16157
|
return out;
|
|
15103
16158
|
}
|
|
16159
|
+
function deleteProjectPaths(workspaceRoot, relPaths) {
|
|
16160
|
+
const base = projectRoot(workspaceRoot);
|
|
16161
|
+
const out = { deleted: [], failed: [] };
|
|
16162
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16163
|
+
for (const raw of relPaths ?? []) {
|
|
16164
|
+
const rel = (raw ?? "").trim().replace(/^[/\\]+/, "");
|
|
16165
|
+
if (!rel) {
|
|
16166
|
+
out.failed.push({ path: String(raw ?? ""), error: "\u4E0D\u80FD\u5220\u9664\u9879\u76EE\u6839\u76EE\u5F55" });
|
|
16167
|
+
continue;
|
|
16168
|
+
}
|
|
16169
|
+
if (seen.has(rel)) continue;
|
|
16170
|
+
seen.add(rel);
|
|
16171
|
+
let abs;
|
|
16172
|
+
try {
|
|
16173
|
+
abs = resolveWithin(base, rel);
|
|
16174
|
+
} catch (e) {
|
|
16175
|
+
out.failed.push({ path: rel, error: e instanceof Error ? e.message : String(e) });
|
|
16176
|
+
continue;
|
|
16177
|
+
}
|
|
16178
|
+
if (path30.resolve(abs) === path30.resolve(base)) {
|
|
16179
|
+
out.failed.push({ path: rel, error: "\u4E0D\u80FD\u5220\u9664\u9879\u76EE\u6839\u76EE\u5F55" });
|
|
16180
|
+
continue;
|
|
16181
|
+
}
|
|
16182
|
+
if (!fs23.existsSync(abs)) {
|
|
16183
|
+
out.failed.push({ path: rel, error: "\u6587\u4EF6\u4E0D\u5B58\u5728" });
|
|
16184
|
+
continue;
|
|
16185
|
+
}
|
|
16186
|
+
try {
|
|
16187
|
+
fs23.rmSync(abs, { recursive: true, force: true });
|
|
16188
|
+
out.deleted.push(toPosix(path30.relative(base, abs)));
|
|
16189
|
+
} catch (e) {
|
|
16190
|
+
out.failed.push({ path: rel, error: e instanceof Error ? e.message : String(e) });
|
|
16191
|
+
}
|
|
16192
|
+
}
|
|
16193
|
+
return out;
|
|
16194
|
+
}
|
|
16195
|
+
function formatBytes(n) {
|
|
16196
|
+
if (n >= 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
16197
|
+
if (n >= 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
16198
|
+
return `${n} B`;
|
|
16199
|
+
}
|
|
16200
|
+
function renameProjectPath(workspaceRoot, relPath, newName) {
|
|
16201
|
+
const base = projectRoot(workspaceRoot);
|
|
16202
|
+
const rel = (relPath ?? "").trim().replace(/^[/\\]+/, "");
|
|
16203
|
+
if (!rel) throw new Error("\u4E0D\u80FD\u91CD\u547D\u540D\u9879\u76EE\u6839\u76EE\u5F55");
|
|
16204
|
+
const abs = resolveWithin(base, rel);
|
|
16205
|
+
const name = (newName ?? "").trim();
|
|
16206
|
+
if (!name) throw new Error("\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
|
|
16207
|
+
if (name === "." || name === "..") throw new Error("\u540D\u79F0\u975E\u6CD5");
|
|
16208
|
+
if (/[/\\]/.test(name)) throw new Error("\u540D\u79F0\u4E0D\u80FD\u5305\u542B\u8DEF\u5F84\u5206\u9694\u7B26");
|
|
16209
|
+
if (!fs23.existsSync(abs)) throw new Error("\u6587\u4EF6\u4E0D\u5B58\u5728");
|
|
16210
|
+
const parent = path30.dirname(abs);
|
|
16211
|
+
const target = path30.join(parent, name);
|
|
16212
|
+
if (!isWithinRoot(base, target)) throw new Error("\u8DEF\u5F84\u8D8A\u754C\uFF08\u8D85\u51FA\u9879\u76EE\u5DE5\u4F5C\u533A\uFF09");
|
|
16213
|
+
if (fs23.existsSync(target)) {
|
|
16214
|
+
let same = false;
|
|
16215
|
+
try {
|
|
16216
|
+
const a = fs23.statSync(target);
|
|
16217
|
+
const b = fs23.statSync(abs);
|
|
16218
|
+
same = a.ino === b.ino && a.dev === b.dev;
|
|
16219
|
+
} catch {
|
|
16220
|
+
same = false;
|
|
16221
|
+
}
|
|
16222
|
+
if (!same) throw new Error(`\u5DF2\u5B58\u5728\u540C\u540D\u6587\u4EF6\u6216\u76EE\u5F55: ${name}`);
|
|
16223
|
+
}
|
|
16224
|
+
if (path30.resolve(target) === path30.resolve(abs)) {
|
|
16225
|
+
return { from: toPosix(path30.relative(base, abs)), path: toPosix(path30.relative(base, abs)) };
|
|
16226
|
+
}
|
|
16227
|
+
try {
|
|
16228
|
+
fs23.renameSync(abs, target);
|
|
16229
|
+
} catch (e) {
|
|
16230
|
+
throw new Error(`\u91CD\u547D\u540D\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
|
|
16231
|
+
}
|
|
16232
|
+
return { from: toPosix(path30.relative(base, abs)), path: toPosix(path30.relative(base, target)) };
|
|
16233
|
+
}
|
|
16234
|
+
function zipProjectPaths(workspaceRoot, relPaths, opts = {}) {
|
|
16235
|
+
const base = projectRoot(workspaceRoot);
|
|
16236
|
+
const maxBytes = opts.maxBytes && opts.maxBytes > 0 ? opts.maxBytes : MAX_ZIP_BYTES;
|
|
16237
|
+
const rels = (relPaths ?? []).map((x) => (x ?? "").trim().replace(/^[/\\]+/, ""));
|
|
16238
|
+
if (rels.length === 0) throw new Error("\u8BF7\u9009\u62E9\u8981\u538B\u7F29\u7684\u6587\u4EF6\u6216\u76EE\u5F55");
|
|
16239
|
+
if (rels.some((r) => !r)) throw new Error("\u4E0D\u80FD\u538B\u7F29\u9879\u76EE\u6839\u76EE\u5F55");
|
|
16240
|
+
const targets = rels.map((rel) => ({ rel, abs: resolveWithin(base, rel) }));
|
|
16241
|
+
const single = targets.length === 1 ? targets[0].abs : "";
|
|
16242
|
+
const items = [];
|
|
16243
|
+
let total = 0;
|
|
16244
|
+
const overLimit = () => new Error(
|
|
16245
|
+
`\u5171 ${formatBytes(total)}\uFF0C\u8D85\u8FC7 ${formatBytes(maxBytes)} \u4E0A\u9650\uFF0C\u4E0D\u652F\u6301\u538B\u7F29\u4E0B\u8F7D`
|
|
16246
|
+
);
|
|
16247
|
+
const walk = (absDir) => {
|
|
16248
|
+
let dirents;
|
|
16249
|
+
try {
|
|
16250
|
+
dirents = fs23.readdirSync(absDir, { withFileTypes: true });
|
|
16251
|
+
} catch {
|
|
16252
|
+
return 0;
|
|
16253
|
+
}
|
|
16254
|
+
dirents.sort((a, b) => a.name.localeCompare(b.name));
|
|
16255
|
+
let files = 0;
|
|
16256
|
+
for (const d of dirents) {
|
|
16257
|
+
if (d.isSymbolicLink()) continue;
|
|
16258
|
+
const child = path30.join(absDir, d.name);
|
|
16259
|
+
if (d.isDirectory()) {
|
|
16260
|
+
files += walk(child);
|
|
16261
|
+
continue;
|
|
16262
|
+
}
|
|
16263
|
+
if (!d.isFile()) continue;
|
|
16264
|
+
let st;
|
|
16265
|
+
try {
|
|
16266
|
+
st = fs23.statSync(child);
|
|
16267
|
+
} catch {
|
|
16268
|
+
continue;
|
|
16269
|
+
}
|
|
16270
|
+
total += st.size;
|
|
16271
|
+
if (total > maxBytes) throw overLimit();
|
|
16272
|
+
if (items.length >= MAX_ZIP_ENTRIES) {
|
|
16273
|
+
throw new Error(`\u5F52\u6863\u6761\u76EE\u8FC7\u591A\uFF08\u8D85\u8FC7 ${MAX_ZIP_ENTRIES} \u9879\uFF09\uFF0C\u8BF7\u7F29\u5C0F\u8303\u56F4\u540E\u518D\u8BD5`);
|
|
16274
|
+
}
|
|
16275
|
+
items.push({ abs: child, dir: false, size: st.size, mtime: st.mtime });
|
|
16276
|
+
files++;
|
|
16277
|
+
}
|
|
16278
|
+
if (files === 0) {
|
|
16279
|
+
let mtime = /* @__PURE__ */ new Date();
|
|
16280
|
+
try {
|
|
16281
|
+
mtime = fs23.statSync(absDir).mtime;
|
|
16282
|
+
} catch {
|
|
16283
|
+
}
|
|
16284
|
+
items.push({ abs: absDir, dir: true, size: 0, mtime });
|
|
16285
|
+
}
|
|
16286
|
+
return files;
|
|
16287
|
+
};
|
|
16288
|
+
for (const t of targets) {
|
|
16289
|
+
let st;
|
|
16290
|
+
try {
|
|
16291
|
+
st = fs23.statSync(t.abs);
|
|
16292
|
+
} catch {
|
|
16293
|
+
throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${t.rel}`);
|
|
16294
|
+
}
|
|
16295
|
+
if (st.isDirectory()) {
|
|
16296
|
+
walk(t.abs);
|
|
16297
|
+
continue;
|
|
16298
|
+
}
|
|
16299
|
+
total += st.size;
|
|
16300
|
+
if (total > maxBytes) throw overLimit();
|
|
16301
|
+
items.push({ abs: t.abs, dir: false, size: st.size, mtime: st.mtime });
|
|
16302
|
+
}
|
|
16303
|
+
const nameOf = (abs) => {
|
|
16304
|
+
const rel = toPosix(path30.relative(single || base, abs));
|
|
16305
|
+
return rel || path30.posix.basename(toPosix(abs));
|
|
16306
|
+
};
|
|
16307
|
+
const zipEntries = items.map((it) => {
|
|
16308
|
+
const name2 = nameOf(it.abs);
|
|
16309
|
+
if (it.dir) return { name: `${name2}/`, mtime: it.mtime };
|
|
16310
|
+
return { name: name2, data: fs23.readFileSync(it.abs), mtime: it.mtime };
|
|
16311
|
+
});
|
|
16312
|
+
const buf = buildZip(zipEntries);
|
|
16313
|
+
const first = targets[0].rel;
|
|
16314
|
+
const name = single ? `${path30.posix.basename(toPosix(first))}.zip` : "archive.zip";
|
|
16315
|
+
return {
|
|
16316
|
+
name,
|
|
16317
|
+
size: buf.length,
|
|
16318
|
+
total,
|
|
16319
|
+
entries: zipEntries.length,
|
|
16320
|
+
base64: buf.toString("base64")
|
|
16321
|
+
};
|
|
16322
|
+
}
|
|
15104
16323
|
|
|
15105
16324
|
// src/server/projectOps.ts
|
|
15106
16325
|
init_cjs_shims();
|
|
@@ -15430,6 +16649,7 @@ function projectStats(manager, tenantId) {
|
|
|
15430
16649
|
|
|
15431
16650
|
// src/server/nodeHost.ts
|
|
15432
16651
|
init_cjs_shims();
|
|
16652
|
+
var import_child_process = require("child_process");
|
|
15433
16653
|
var fs24 = __toESM(require("fs"), 1);
|
|
15434
16654
|
var os9 = __toESM(require("os"), 1);
|
|
15435
16655
|
function diskInfo(pathStr) {
|
|
@@ -15444,6 +16664,64 @@ function diskInfo(pathStr) {
|
|
|
15444
16664
|
return null;
|
|
15445
16665
|
}
|
|
15446
16666
|
}
|
|
16667
|
+
var PSEUDO_FS = /^(devfs|tmpfs|devtmpfs|proc|procfs|sysfs|none|udev|shm|mqueue|devpts|hugetlbfs|debugfs|tracefs|securityfs|pstore|bpf|configfs|fusectl|nsfs|autofs|efivarfs|ramfs|map)$/;
|
|
16668
|
+
function isInternalMount(mount) {
|
|
16669
|
+
if (mount === "/") return false;
|
|
16670
|
+
const prefixes = ["/proc", "/sys", "/run", "/snap"];
|
|
16671
|
+
if (process.platform === "darwin") prefixes.push("/System/Volumes");
|
|
16672
|
+
return prefixes.some((p) => mount === p || mount.startsWith(`${p}/`));
|
|
16673
|
+
}
|
|
16674
|
+
function readOnlyVolumes() {
|
|
16675
|
+
const out = /* @__PURE__ */ new Set();
|
|
16676
|
+
const res = (0, import_child_process.spawnSync)("mount", [], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
|
|
16677
|
+
if (res.error || res.status !== 0 || !res.stdout) return out;
|
|
16678
|
+
for (const line of res.stdout.split("\n")) {
|
|
16679
|
+
const m = line.match(/^.+? on (.+?) \(([^)]*)\)$/);
|
|
16680
|
+
if (!m || m[1] === "/") continue;
|
|
16681
|
+
if (m[2].split(",").some((opt) => opt.trim() === "read-only")) out.add(m[1]);
|
|
16682
|
+
}
|
|
16683
|
+
return out;
|
|
16684
|
+
}
|
|
16685
|
+
function diskList(workspacePath) {
|
|
16686
|
+
const out = [];
|
|
16687
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16688
|
+
const push = (path44, total, free) => {
|
|
16689
|
+
if (!path44 || seen.has(path44) || !Number.isFinite(total) || total <= 0) return;
|
|
16690
|
+
seen.add(path44);
|
|
16691
|
+
out.push({ path: path44, total, free });
|
|
16692
|
+
};
|
|
16693
|
+
if (process.platform === "win32") {
|
|
16694
|
+
for (const letter of "CDEFGHIJKLMNOPQRSTUVWXYZ") {
|
|
16695
|
+
const root = `${letter}:\\`;
|
|
16696
|
+
const d = diskInfo(root);
|
|
16697
|
+
if (d) push(root, d.total, d.free);
|
|
16698
|
+
}
|
|
16699
|
+
} else {
|
|
16700
|
+
const readonly = process.platform === "darwin" ? readOnlyVolumes() : /* @__PURE__ */ new Set();
|
|
16701
|
+
const res = (0, import_child_process.spawnSync)("df", ["-kP"], {
|
|
16702
|
+
encoding: "utf-8",
|
|
16703
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
16704
|
+
});
|
|
16705
|
+
if (!res.error && res.status === 0 && res.stdout) {
|
|
16706
|
+
for (const line of res.stdout.split("\n").slice(1)) {
|
|
16707
|
+
const m = line.trim().match(/^(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s+(.+)$/);
|
|
16708
|
+
if (!m || PSEUDO_FS.test(m[1])) continue;
|
|
16709
|
+
if (/^\/dev\/loop\d+$/.test(m[1])) continue;
|
|
16710
|
+
if (isInternalMount(m[5]) || readonly.has(m[5])) continue;
|
|
16711
|
+
push(m[5], Number(m[2]) * 1024, Number(m[4]) * 1024);
|
|
16712
|
+
}
|
|
16713
|
+
}
|
|
16714
|
+
}
|
|
16715
|
+
const primary = diskInfo(workspacePath);
|
|
16716
|
+
if (primary) {
|
|
16717
|
+
const covered = out.some((d) => {
|
|
16718
|
+
const root = d.path.endsWith("/") || d.path.endsWith("\\") ? d.path : `${d.path}/`;
|
|
16719
|
+
return workspacePath === d.path || workspacePath.startsWith(root);
|
|
16720
|
+
});
|
|
16721
|
+
if (!covered) push(primary.path, primary.total, primary.free);
|
|
16722
|
+
}
|
|
16723
|
+
return out.sort((a, b) => b.total - a.total);
|
|
16724
|
+
}
|
|
15447
16725
|
function networkInfo() {
|
|
15448
16726
|
const out = [];
|
|
15449
16727
|
const ifaces = os9.networkInterfaces();
|
|
@@ -15499,6 +16777,7 @@ function overview(store, manager) {
|
|
|
15499
16777
|
function buildNodeHost(store, manager, diskPath) {
|
|
15500
16778
|
const cpus2 = os9.cpus();
|
|
15501
16779
|
const ov = overview(store, manager);
|
|
16780
|
+
const disks = diskList(diskPath || process.cwd());
|
|
15502
16781
|
return {
|
|
15503
16782
|
nodeId: store.loadNodeId() ?? "",
|
|
15504
16783
|
hostname: os9.hostname(),
|
|
@@ -15506,7 +16785,7 @@ function buildNodeHost(store, manager, diskPath) {
|
|
|
15506
16785
|
arch: process.arch,
|
|
15507
16786
|
release: os9.release(),
|
|
15508
16787
|
uptimeSec: Math.round(os9.uptime()),
|
|
15509
|
-
version: "0.2.
|
|
16788
|
+
version: "0.2.20",
|
|
15510
16789
|
mode: process.env.NODE_MODE ?? "standalone",
|
|
15511
16790
|
cpu: {
|
|
15512
16791
|
count: cpus2.length,
|
|
@@ -15516,6 +16795,7 @@ function buildNodeHost(store, manager, diskPath) {
|
|
|
15516
16795
|
memory: { total: os9.totalmem(), free: os9.freemem() },
|
|
15517
16796
|
network: networkInfo(),
|
|
15518
16797
|
disk: diskInfo(diskPath || process.cwd()),
|
|
16798
|
+
disks,
|
|
15519
16799
|
...ov
|
|
15520
16800
|
};
|
|
15521
16801
|
}
|
|
@@ -15649,6 +16929,7 @@ function parseApprovalDecision(raw) {
|
|
|
15649
16929
|
}
|
|
15650
16930
|
function createApp(store, manager, opts = {}) {
|
|
15651
16931
|
setupServerHandlers(manager);
|
|
16932
|
+
const updater = opts.updater ?? null;
|
|
15652
16933
|
const routes = [
|
|
15653
16934
|
// ---------- 会话管理 ----------
|
|
15654
16935
|
{
|
|
@@ -15790,6 +17071,18 @@ function createApp(store, manager, opts = {}) {
|
|
|
15790
17071
|
ok(res, { accepted: true });
|
|
15791
17072
|
}
|
|
15792
17073
|
},
|
|
17074
|
+
{
|
|
17075
|
+
method: "POST",
|
|
17076
|
+
pattern: ["sessions", ":id", "steer", "remove"],
|
|
17077
|
+
// 撤销未注入的补充信息
|
|
17078
|
+
fn: (_req, res, { tenant, params, body }) => {
|
|
17079
|
+
const index = Number(body?.index);
|
|
17080
|
+
if (!Number.isInteger(index))
|
|
17081
|
+
throw new ApiError(400, "bad_request", "index \u5FC5\u987B\u662F\u6574\u6570");
|
|
17082
|
+
const pendingSteers = manager.unsteer(tenant, params["id"], index);
|
|
17083
|
+
ok(res, { accepted: true, pendingSteers });
|
|
17084
|
+
}
|
|
17085
|
+
},
|
|
15793
17086
|
{
|
|
15794
17087
|
method: "POST",
|
|
15795
17088
|
pattern: ["sessions", ":id", "interrupt"],
|
|
@@ -16320,6 +17613,30 @@ function createApp(store, manager, opts = {}) {
|
|
|
16320
17613
|
}
|
|
16321
17614
|
}
|
|
16322
17615
|
},
|
|
17616
|
+
{
|
|
17617
|
+
// 二进制写入(图片编辑保存):body = { path, base64 }
|
|
17618
|
+
method: "POST",
|
|
17619
|
+
pattern: ["files", "binary"],
|
|
17620
|
+
fn: (_req, res, { tenant, body }) => {
|
|
17621
|
+
const raw = body ?? {};
|
|
17622
|
+
try {
|
|
17623
|
+
ok(
|
|
17624
|
+
res,
|
|
17625
|
+
writeProjectFileBase64(
|
|
17626
|
+
tenant.workspaceRoot ?? null,
|
|
17627
|
+
String(raw.path ?? ""),
|
|
17628
|
+
String(raw.base64 ?? "")
|
|
17629
|
+
)
|
|
17630
|
+
);
|
|
17631
|
+
} catch (e) {
|
|
17632
|
+
throw new ApiError(
|
|
17633
|
+
400,
|
|
17634
|
+
"bad_request",
|
|
17635
|
+
e instanceof Error ? e.message : String(e)
|
|
17636
|
+
);
|
|
17637
|
+
}
|
|
17638
|
+
}
|
|
17639
|
+
},
|
|
16323
17640
|
// ---------- 项目终端(在工作区执行命令) ----------
|
|
16324
17641
|
{
|
|
16325
17642
|
method: "POST",
|
|
@@ -16670,6 +17987,21 @@ function createApp(store, manager, opts = {}) {
|
|
|
16670
17987
|
}
|
|
16671
17988
|
}
|
|
16672
17989
|
},
|
|
17990
|
+
{
|
|
17991
|
+
method: "POST",
|
|
17992
|
+
pattern: ["files", "delete"],
|
|
17993
|
+
// 删除文件 / 目录(目录递归);逐条返回失败原因
|
|
17994
|
+
fn: (_req, res, { tenant, body }) => {
|
|
17995
|
+
const raw = body ?? {};
|
|
17996
|
+
const paths = Array.isArray(raw.paths) ? raw.paths.map((x) => String(x)) : [];
|
|
17997
|
+
if (paths.length === 0) throw new ApiError(400, "bad_request", "paths \u4E0D\u80FD\u4E3A\u7A7A");
|
|
17998
|
+
try {
|
|
17999
|
+
ok(res, deleteProjectPaths(tenant.workspaceRoot ?? null, paths));
|
|
18000
|
+
} catch (e) {
|
|
18001
|
+
throw new ApiError(400, "bad_request", e instanceof Error ? e.message : String(e));
|
|
18002
|
+
}
|
|
18003
|
+
}
|
|
18004
|
+
},
|
|
16673
18005
|
{
|
|
16674
18006
|
method: "POST",
|
|
16675
18007
|
pattern: ["files", "create"],
|
|
@@ -16689,6 +18021,41 @@ function createApp(store, manager, opts = {}) {
|
|
|
16689
18021
|
}
|
|
16690
18022
|
}
|
|
16691
18023
|
},
|
|
18024
|
+
{
|
|
18025
|
+
method: "POST",
|
|
18026
|
+
pattern: ["files", "rename"],
|
|
18027
|
+
// 重命名(只改同目录下的名字)
|
|
18028
|
+
fn: (_req, res, { tenant, body }) => {
|
|
18029
|
+
const raw = body ?? {};
|
|
18030
|
+
try {
|
|
18031
|
+
ok(
|
|
18032
|
+
res,
|
|
18033
|
+
renameProjectPath(
|
|
18034
|
+
tenant.workspaceRoot ?? null,
|
|
18035
|
+
String(raw.path ?? ""),
|
|
18036
|
+
String(raw.name ?? "")
|
|
18037
|
+
)
|
|
18038
|
+
);
|
|
18039
|
+
} catch (e) {
|
|
18040
|
+
throw new ApiError(400, "bad_request", e instanceof Error ? e.message : String(e));
|
|
18041
|
+
}
|
|
18042
|
+
}
|
|
18043
|
+
},
|
|
18044
|
+
{
|
|
18045
|
+
method: "POST",
|
|
18046
|
+
pattern: ["files", "zip"],
|
|
18047
|
+
// 压缩下载(多选一起打包;超上限由实现拒绝)
|
|
18048
|
+
fn: (_req, res, { tenant, body }) => {
|
|
18049
|
+
const raw = body ?? {};
|
|
18050
|
+
const paths = Array.isArray(raw.paths) ? raw.paths.map((x) => String(x)) : [];
|
|
18051
|
+
if (paths.length === 0) throw new ApiError(400, "bad_request", "paths \u4E0D\u80FD\u4E3A\u7A7A");
|
|
18052
|
+
try {
|
|
18053
|
+
ok(res, zipProjectPaths(tenant.workspaceRoot ?? null, paths));
|
|
18054
|
+
} catch (e) {
|
|
18055
|
+
throw new ApiError(400, "bad_request", e instanceof Error ? e.message : String(e));
|
|
18056
|
+
}
|
|
18057
|
+
}
|
|
18058
|
+
},
|
|
16692
18059
|
// ---------- 项目(= 租户)管理:控制台多项目视图 ----------
|
|
16693
18060
|
// 仅管理员密钥可用;项目级与会话级接口仍用项目自己的 tenant API Key
|
|
16694
18061
|
// (或管理员密钥 + X-Project-Id 头),互不暴露明文密钥。
|
|
@@ -16747,6 +18114,29 @@ function createApp(store, manager, opts = {}) {
|
|
|
16747
18114
|
ok(res, nodeLlmView(next));
|
|
16748
18115
|
}
|
|
16749
18116
|
},
|
|
18117
|
+
// 大模型账户余额(控制台「余额不足 → 查看余额并充值」用;由节点向 Hub 代查)
|
|
18118
|
+
{
|
|
18119
|
+
method: "GET",
|
|
18120
|
+
pattern: ["node", "llm", "balance"],
|
|
18121
|
+
adminOnly: true,
|
|
18122
|
+
fn: async (_req, res) => {
|
|
18123
|
+
ok(res, await fetchLlmBalance(store));
|
|
18124
|
+
}
|
|
18125
|
+
},
|
|
18126
|
+
// 重新拉取平台内置模型凭据(用户点了「刷新内置模型」,或 Key 在控制台被重置)
|
|
18127
|
+
{
|
|
18128
|
+
method: "POST",
|
|
18129
|
+
pattern: ["node", "llm", "builtin-refresh"],
|
|
18130
|
+
adminOnly: true,
|
|
18131
|
+
fn: async (_req, res) => {
|
|
18132
|
+
const r = await ensureBuiltinProvider(store, { force: true });
|
|
18133
|
+
if (r.status !== "ready") {
|
|
18134
|
+
ok(res, { ok: false, reason: r.reason });
|
|
18135
|
+
return;
|
|
18136
|
+
}
|
|
18137
|
+
ok(res, { ok: true, view: nodeLlmView(store.loadNodeLlm()) });
|
|
18138
|
+
}
|
|
18139
|
+
},
|
|
16750
18140
|
// ---------- 用量(账本 + 汇总 + 价格) ----------
|
|
16751
18141
|
{
|
|
16752
18142
|
method: "GET",
|
|
@@ -16898,6 +18288,34 @@ function createApp(store, manager, opts = {}) {
|
|
|
16898
18288
|
}
|
|
16899
18289
|
}
|
|
16900
18290
|
},
|
|
18291
|
+
// ---------- 节点自更新(状态 / 检查 / 安装) ----------
|
|
18292
|
+
// action=install 先回执、后台安装并退出进程(由服务管理器拉起新版本),
|
|
18293
|
+
// 因此回执成功只代表「已受理」,是否升级成功要用 action=status 再确认。
|
|
18294
|
+
{
|
|
18295
|
+
method: "POST",
|
|
18296
|
+
pattern: ["node", "self-update"],
|
|
18297
|
+
projectOptional: true,
|
|
18298
|
+
fn: async (req2, res, { body }) => {
|
|
18299
|
+
const b = body ?? {};
|
|
18300
|
+
const action = String(b.action ?? "status");
|
|
18301
|
+
if (!updater) {
|
|
18302
|
+
throw new ApiError(400, "bad_request", "\u81EA\u66F4\u65B0\u4E0D\u53EF\u7528\uFF1A\u8282\u70B9\u672A\u4EE5 serve \u6A21\u5F0F\u8FD0\u884C");
|
|
18303
|
+
}
|
|
18304
|
+
if (action === "status") {
|
|
18305
|
+
ok(res, updater.status());
|
|
18306
|
+
return;
|
|
18307
|
+
}
|
|
18308
|
+
if (action === "check") {
|
|
18309
|
+
ok(res, await updater.inspect());
|
|
18310
|
+
return;
|
|
18311
|
+
}
|
|
18312
|
+
if (action === "install") {
|
|
18313
|
+
ok(res, await updater.requestInstall(b.force === true));
|
|
18314
|
+
return;
|
|
18315
|
+
}
|
|
18316
|
+
throw new ApiError(400, "bad_request", `\u672A\u77E5\u7684 action\uFF1A${action}\uFF08\u53EF\u9009 status / check / install\uFF09`);
|
|
18317
|
+
}
|
|
18318
|
+
},
|
|
16901
18319
|
// ---------- 节点级会话状态(跨项目;只含标识与 status) ----------
|
|
16902
18320
|
// 供节点首页 / 工作台建立初始视图,后续变化由 session_status 通知增量推送。
|
|
16903
18321
|
{
|
|
@@ -17060,7 +18478,7 @@ function createApp(store, manager, opts = {}) {
|
|
|
17060
18478
|
// 阻止反向代理(nginx 等)缓冲 SSE 流
|
|
17061
18479
|
"X-Accel-Buffering": "no"
|
|
17062
18480
|
});
|
|
17063
|
-
const
|
|
18481
|
+
const sinceRaw2 = new URL(
|
|
17064
18482
|
req2.url,
|
|
17065
18483
|
"http://localhost"
|
|
17066
18484
|
).searchParams.get("since");
|
|
@@ -17068,19 +18486,51 @@ function createApp(store, manager, opts = {}) {
|
|
|
17068
18486
|
tenant.id,
|
|
17069
18487
|
params["id"],
|
|
17070
18488
|
res,
|
|
17071
|
-
|
|
18489
|
+
sinceRaw2 ? Number(sinceRaw2) : 0
|
|
17072
18490
|
);
|
|
17073
18491
|
return;
|
|
17074
18492
|
}
|
|
17075
|
-
const
|
|
17076
|
-
|
|
18493
|
+
const q = new URL(req2.url, "http://localhost").searchParams;
|
|
18494
|
+
const sinceRaw = q.get("since");
|
|
18495
|
+
const beforeRaw = q.get("before");
|
|
18496
|
+
const maxBytesRaw = q.get("maxBytes");
|
|
18497
|
+
ok(
|
|
18498
|
+
res,
|
|
18499
|
+
manager.eventsPage(tenant.id, params["id"], {
|
|
18500
|
+
since: sinceRaw === null ? void 0 : Number(sinceRaw) || 0,
|
|
18501
|
+
before: beforeRaw === null ? void 0 : Number(beforeRaw) || 0,
|
|
18502
|
+
maxBytes: maxBytesRaw === null ? void 0 : Number(maxBytesRaw) || void 0
|
|
18503
|
+
})
|
|
17077
18504
|
);
|
|
17078
|
-
|
|
17079
|
-
|
|
17080
|
-
|
|
17081
|
-
|
|
18505
|
+
}
|
|
18506
|
+
},
|
|
18507
|
+
{
|
|
18508
|
+
method: "GET",
|
|
18509
|
+
pattern: ["sessions", ":id", "turns"],
|
|
18510
|
+
// 轮次汇总(会话详情的列表)
|
|
18511
|
+
fn: async (req2, res, { tenant, params }) => {
|
|
18512
|
+
const q = new URL(req2.url, "http://localhost").searchParams;
|
|
18513
|
+
const beforeRaw = q.get("before");
|
|
18514
|
+
const limitRaw = q.get("limit");
|
|
18515
|
+
ok(
|
|
18516
|
+
res,
|
|
18517
|
+
await manager.sessionTurns(tenant.id, params["id"], {
|
|
18518
|
+
before: beforeRaw === null ? void 0 : Number(beforeRaw) || 0,
|
|
18519
|
+
limit: limitRaw === null ? void 0 : Number(limitRaw) || void 0
|
|
18520
|
+
})
|
|
17082
18521
|
);
|
|
17083
|
-
|
|
18522
|
+
}
|
|
18523
|
+
},
|
|
18524
|
+
{
|
|
18525
|
+
method: "GET",
|
|
18526
|
+
// 单轮详情:用户消息(含补充) + 最终回复 + 产出文件
|
|
18527
|
+
pattern: ["sessions", ":id", "turns", ":seq", "reply"],
|
|
18528
|
+
fn: async (req2, res, { tenant, params }) => {
|
|
18529
|
+
const startSeq = Number(params["seq"]);
|
|
18530
|
+
if (!Number.isFinite(startSeq) || startSeq <= 0) {
|
|
18531
|
+
throw new ApiError(400, "bad_request", "\u65E0\u6548\u7684\u8F6E\u6B21\u5E8F\u53F7");
|
|
18532
|
+
}
|
|
18533
|
+
ok(res, await manager.sessionTurnReply(tenant.id, params["id"], startSeq));
|
|
17084
18534
|
}
|
|
17085
18535
|
}
|
|
17086
18536
|
];
|
|
@@ -17113,7 +18563,7 @@ function createApp(store, manager, opts = {}) {
|
|
|
17113
18563
|
json(res, 200, {
|
|
17114
18564
|
status: "ok",
|
|
17115
18565
|
// 版本让运维/探针能确认线上跑的是哪个构建(更新与回滚据此判断)
|
|
17116
|
-
version: "0.2.
|
|
18566
|
+
version: "0.2.20",
|
|
17117
18567
|
features: { mcp: hasServerMcpConfig() }
|
|
17118
18568
|
});
|
|
17119
18569
|
return;
|
|
@@ -17386,6 +18836,24 @@ async function dispatchFleetCommand(deps, env3) {
|
|
|
17386
18836
|
switch (env3.type) {
|
|
17387
18837
|
case "ping":
|
|
17388
18838
|
return ok2({ pong: true, ts: Date.now() });
|
|
18839
|
+
case "self_update": {
|
|
18840
|
+
const updater = deps.getUpdater?.() ?? null;
|
|
18841
|
+
if (!updater) {
|
|
18842
|
+
return fail("\u81EA\u66F4\u65B0\u4E0D\u53EF\u7528\uFF1A\u8282\u70B9\u672A\u4EE5 serve \u6A21\u5F0F\u8FD0\u884C\uFF08\u7F3A\u5C11\u66F4\u65B0\u63A7\u5236\u5668\uFF09");
|
|
18843
|
+
}
|
|
18844
|
+
const action = String(payload.action ?? "status");
|
|
18845
|
+
if (action === "status") {
|
|
18846
|
+
return ok2(updater.status());
|
|
18847
|
+
}
|
|
18848
|
+
if (action === "check") {
|
|
18849
|
+
return ok2(await updater.inspect());
|
|
18850
|
+
}
|
|
18851
|
+
if (action === "install") {
|
|
18852
|
+
const started = await updater.requestInstall(payload.force === true);
|
|
18853
|
+
return ok2(started);
|
|
18854
|
+
}
|
|
18855
|
+
return fail(`\u672A\u77E5\u7684 action\uFF1A${action}\uFF08\u53EF\u9009 status / check / install\uFF09`);
|
|
18856
|
+
}
|
|
17389
18857
|
case "get_node_info":
|
|
17390
18858
|
return ok2(
|
|
17391
18859
|
buildNodeInfo(store, {
|
|
@@ -17435,6 +18903,22 @@ async function dispatchFleetCommand(deps, env3) {
|
|
|
17435
18903
|
store.saveNodeLlm(next);
|
|
17436
18904
|
return ok2(nodeLlmView(next));
|
|
17437
18905
|
}
|
|
18906
|
+
/*
|
|
18907
|
+
* 重新拉取平台内置模型(志前 API)。
|
|
18908
|
+
*
|
|
18909
|
+
* 用途:用户在控制台点了「刷新」、或内置模型的 Key 在控制台被重置后需要换新。
|
|
18910
|
+
* 走写链串行——它会改写 connections.json,不能与 update_node_llm 并发。
|
|
18911
|
+
*/
|
|
18912
|
+
// 余额查询:只读、幂等,可进只读并发池
|
|
18913
|
+
case "get_llm_balance":
|
|
18914
|
+
return ok2(await fetchLlmBalance(store));
|
|
18915
|
+
case "refresh_builtin_llm": {
|
|
18916
|
+
const r = await ensureBuiltinProvider(store, { force: true });
|
|
18917
|
+
if (r.status !== "ready") {
|
|
18918
|
+
return ok2({ ok: false, reason: r.reason });
|
|
18919
|
+
}
|
|
18920
|
+
return ok2({ ok: true, view: nodeLlmView(store.loadNodeLlm()) });
|
|
18921
|
+
}
|
|
17438
18922
|
case "get_usage_summary": {
|
|
17439
18923
|
const ledger = manager.usageLedger();
|
|
17440
18924
|
if (!ledger) return ok2({ total: null, groups: [] });
|
|
@@ -17508,8 +18992,22 @@ async function dispatchFleetCommand(deps, env3) {
|
|
|
17508
18992
|
);
|
|
17509
18993
|
}
|
|
17510
18994
|
case "get_events": {
|
|
17511
|
-
const since =
|
|
17512
|
-
|
|
18995
|
+
const since = payload.since === void 0 ? void 0 : Number(payload.since) || 0;
|
|
18996
|
+
const before = payload.before === void 0 ? void 0 : Number(payload.before) || 0;
|
|
18997
|
+
const maxBytes = Number(payload.maxBytes) || void 0;
|
|
18998
|
+
return ok2(
|
|
18999
|
+
manager.eventsPage(project().id, sessionId(), { since, before, maxBytes })
|
|
19000
|
+
);
|
|
19001
|
+
}
|
|
19002
|
+
case "get_session_turns": {
|
|
19003
|
+
const before = payload.before === void 0 ? void 0 : Number(payload.before) || 0;
|
|
19004
|
+
const limit = Number(payload.limit) || void 0;
|
|
19005
|
+
return ok2(await manager.sessionTurns(project().id, sessionId(), { before, limit }));
|
|
19006
|
+
}
|
|
19007
|
+
case "get_session_turn_reply": {
|
|
19008
|
+
const startSeq = Number(payload.startSeq) || 0;
|
|
19009
|
+
if (startSeq <= 0) throw new ApiError(400, "bad_request", "\u65E0\u6548\u7684\u8F6E\u6B21\u5E8F\u53F7");
|
|
19010
|
+
return ok2(await manager.sessionTurnReply(project().id, sessionId(), startSeq));
|
|
17513
19011
|
}
|
|
17514
19012
|
case "get_config":
|
|
17515
19013
|
return ok2(configView2(project()));
|
|
@@ -17570,6 +19068,16 @@ async function dispatchFleetCommand(deps, env3) {
|
|
|
17570
19068
|
)
|
|
17571
19069
|
);
|
|
17572
19070
|
}
|
|
19071
|
+
case "write_file_base64": {
|
|
19072
|
+
const t = project();
|
|
19073
|
+
return ok2(
|
|
19074
|
+
writeProjectFileBase64(
|
|
19075
|
+
t.workspaceRoot ?? null,
|
|
19076
|
+
String(payload.path ?? ""),
|
|
19077
|
+
String(payload.base64 ?? "")
|
|
19078
|
+
)
|
|
19079
|
+
);
|
|
19080
|
+
}
|
|
17573
19081
|
case "create_dir": {
|
|
17574
19082
|
const t = project();
|
|
17575
19083
|
return ok2(createProjectDir(t.workspaceRoot ?? null, String(payload.path ?? "")));
|
|
@@ -17584,10 +19092,35 @@ async function dispatchFleetCommand(deps, env3) {
|
|
|
17584
19092
|
)
|
|
17585
19093
|
);
|
|
17586
19094
|
}
|
|
19095
|
+
case "delete_files": {
|
|
19096
|
+
const t = project();
|
|
19097
|
+
const raw = payload.paths;
|
|
19098
|
+
const paths = Array.isArray(raw) ? raw.map((x) => String(x)) : [];
|
|
19099
|
+
if (paths.length === 0) throw new ApiError(400, "bad_request", "paths \u4E0D\u80FD\u4E3A\u7A7A");
|
|
19100
|
+
return ok2(deleteProjectPaths(t.workspaceRoot ?? null, paths));
|
|
19101
|
+
}
|
|
17587
19102
|
case "search_files": {
|
|
17588
19103
|
const t = project();
|
|
17589
19104
|
return ok2(searchProjectFiles(t.workspaceRoot ?? null, String(payload.query ?? "")));
|
|
17590
19105
|
}
|
|
19106
|
+
case "rename_path": {
|
|
19107
|
+
const t = project();
|
|
19108
|
+
return ok2(
|
|
19109
|
+
renameProjectPath(
|
|
19110
|
+
t.workspaceRoot ?? null,
|
|
19111
|
+
String(payload.path ?? ""),
|
|
19112
|
+
String(payload.name ?? "")
|
|
19113
|
+
)
|
|
19114
|
+
);
|
|
19115
|
+
}
|
|
19116
|
+
case "zip_download": {
|
|
19117
|
+
const t = project();
|
|
19118
|
+
const raw = payload.paths;
|
|
19119
|
+
const paths = Array.isArray(raw) ? raw.map((x) => String(x)) : [];
|
|
19120
|
+
if (paths.length === 0) throw new ApiError(400, "bad_request", "paths \u4E0D\u80FD\u4E3A\u7A7A");
|
|
19121
|
+
const maxBytes = typeof payload.maxBytes === "number" ? payload.maxBytes : void 0;
|
|
19122
|
+
return ok2(zipProjectPaths(t.workspaceRoot ?? null, paths, { maxBytes }));
|
|
19123
|
+
}
|
|
17591
19124
|
case "list_tools": {
|
|
17592
19125
|
const t = project();
|
|
17593
19126
|
return ok2(buildToolCatalog(t.workspaceRoot ?? process.cwd()));
|
|
@@ -17810,6 +19343,13 @@ async function dispatchFleetCommand(deps, env3) {
|
|
|
17810
19343
|
manager.steer(project(), sessionId(), text);
|
|
17811
19344
|
return ok2({ accepted: true });
|
|
17812
19345
|
}
|
|
19346
|
+
case "unsteer": {
|
|
19347
|
+
const index = Number(payload.index);
|
|
19348
|
+
if (!Number.isInteger(index)) {
|
|
19349
|
+
throw new ApiError(400, "bad_request", "index \u5FC5\u987B\u662F\u6574\u6570");
|
|
19350
|
+
}
|
|
19351
|
+
return ok2({ accepted: true, pendingSteers: manager.unsteer(project(), sessionId(), index) });
|
|
19352
|
+
}
|
|
17813
19353
|
case "interrupt":
|
|
17814
19354
|
manager.interrupt(project(), sessionId());
|
|
17815
19355
|
return ok2({ accepted: true });
|
|
@@ -17933,6 +19473,22 @@ function toWireAck(ack) {
|
|
|
17933
19473
|
data: ack.data === void 0 ? void 0 : utf8Encode(JSON.stringify(ack.data))
|
|
17934
19474
|
};
|
|
17935
19475
|
}
|
|
19476
|
+
var ACK_JSON_MAX_BYTES = MAX_FRAME_BYTES - 64 * 1024;
|
|
19477
|
+
function mb(bytes) {
|
|
19478
|
+
return (bytes / (1024 * 1024)).toFixed(1);
|
|
19479
|
+
}
|
|
19480
|
+
function guardAckSize(ack, json2) {
|
|
19481
|
+
if (!ack.ok || ack.data === void 0) return ack;
|
|
19482
|
+
const bytes = Buffer.byteLength(json2, "utf8");
|
|
19483
|
+
if (bytes <= ACK_JSON_MAX_BYTES) return ack;
|
|
19484
|
+
return {
|
|
19485
|
+
reqId: ack.reqId,
|
|
19486
|
+
type: ack.type,
|
|
19487
|
+
ok: false,
|
|
19488
|
+
// 带上实际大小:用户据此能判断「差一点」还是「差很多」,而不是只看到「失败」
|
|
19489
|
+
error: `\u7ED3\u679C\u8FC7\u5927\uFF08${mb(bytes)} MB\uFF09\uFF0C\u8D85\u8FC7\u5355\u5E27\u4F20\u8F93\u4E0A\u9650\uFF08${mb(ACK_JSON_MAX_BYTES)} MB\uFF09\uFF0C\u8BF7\u7F29\u5C0F\u8303\u56F4\u540E\u91CD\u8BD5`
|
|
19490
|
+
};
|
|
19491
|
+
}
|
|
17936
19492
|
function fromWireCommand(env3) {
|
|
17937
19493
|
let payload = {};
|
|
17938
19494
|
if (env3.payload && env3.payload.length > 0) {
|
|
@@ -18338,6 +19894,7 @@ var READONLY_CMD_TYPES = /* @__PURE__ */ new Set([
|
|
|
18338
19894
|
"get_node_llm",
|
|
18339
19895
|
"list_node_llm_models",
|
|
18340
19896
|
"get_usage_summary",
|
|
19897
|
+
"get_llm_balance",
|
|
18341
19898
|
"get_usage_records",
|
|
18342
19899
|
"get_usage_pricing",
|
|
18343
19900
|
"list_projects",
|
|
@@ -18345,6 +19902,8 @@ var READONLY_CMD_TYPES = /* @__PURE__ */ new Set([
|
|
|
18345
19902
|
"get_session_states",
|
|
18346
19903
|
"get_state",
|
|
18347
19904
|
"get_events",
|
|
19905
|
+
"get_session_turns",
|
|
19906
|
+
"get_session_turn_reply",
|
|
18348
19907
|
"get_config",
|
|
18349
19908
|
"list_models",
|
|
18350
19909
|
"list_dirs",
|
|
@@ -18594,6 +20153,10 @@ var FleetNodeBridge = class {
|
|
|
18594
20153
|
_dispatchEnvelope(env3) {
|
|
18595
20154
|
const run = () => this._dispatch(env3);
|
|
18596
20155
|
dbgMark(`recv:${env3.type}`, `reqId=${env3.reqId}`);
|
|
20156
|
+
if (env3.type === "self_update" && String(env3.payload?.action ?? "status") !== "install") {
|
|
20157
|
+
this._runReadonly(run);
|
|
20158
|
+
return;
|
|
20159
|
+
}
|
|
18597
20160
|
if (READONLY_CMD_TYPES.has(env3.type) || this._isReadonlyBatch(env3)) {
|
|
18598
20161
|
this._runReadonly(run);
|
|
18599
20162
|
return;
|
|
@@ -18620,7 +20183,7 @@ var FleetNodeBridge = class {
|
|
|
18620
20183
|
_invalidateSessionCache() {
|
|
18621
20184
|
this.writeEpoch += 1;
|
|
18622
20185
|
for (const key of [...this.cmdResultCache.keys()]) {
|
|
18623
|
-
if (/^(get_state|get_events|list_sessions|get_session_states)\|/.test(key)) {
|
|
20186
|
+
if (/^(get_state|get_events|get_session_turns|get_session_turn_reply|list_sessions|get_session_states)\|/.test(key)) {
|
|
18624
20187
|
this.cmdResultCache.delete(key);
|
|
18625
20188
|
}
|
|
18626
20189
|
}
|
|
@@ -18756,11 +20319,17 @@ var FleetNodeBridge = class {
|
|
|
18756
20319
|
label: this.opts.deps.store.loadNodeLabel() ?? void 0,
|
|
18757
20320
|
platform: process.platform,
|
|
18758
20321
|
mode: process.env.NODE_MODE ?? "standalone",
|
|
18759
|
-
version: "0.2.
|
|
20322
|
+
version: "0.2.20",
|
|
18760
20323
|
ip: primaryIp()
|
|
18761
20324
|
};
|
|
18762
20325
|
}
|
|
18763
|
-
/**
|
|
20326
|
+
/**
|
|
20327
|
+
* 用量汇总(供控制面账户级聚合):总量 + 最近 40 天按天 + 最近 40 天按项目。
|
|
20328
|
+
*
|
|
20329
|
+
* 按项目这一维是为控制台「今日 / 最近 30 天消耗拆到每个项目」服务的:
|
|
20330
|
+
* 账本里的汇总只存了按天、按项目两个独立维度,交叉项只能现算,因此这里
|
|
20331
|
+
* 从明细聚合后随 info 一起上报(Hub 按 (节点, 项目, 天) 覆盖写入,天然幂等)。
|
|
20332
|
+
*/
|
|
18764
20333
|
_usagePayload() {
|
|
18765
20334
|
const ledger = this.opts.deps.manager.usageLedger();
|
|
18766
20335
|
if (!ledger) return void 0;
|
|
@@ -18768,11 +20337,22 @@ var FleetNodeBridge = class {
|
|
|
18768
20337
|
const days = {};
|
|
18769
20338
|
const keys = Object.keys(r.byDay).sort().slice(-40);
|
|
18770
20339
|
for (const k of keys) days[k] = r.byDay[k];
|
|
20340
|
+
const from = keys[0] ?? "";
|
|
20341
|
+
const to = keys[keys.length - 1] ?? "";
|
|
20342
|
+
const projects = {};
|
|
20343
|
+
if (from && to) {
|
|
20344
|
+
for (const [projectId, byDay] of Object.entries(
|
|
20345
|
+
ledger.projectByDay({ from, to })
|
|
20346
|
+
)) {
|
|
20347
|
+
projects[projectId] = { total: sumAgg(Object.values(byDay)), byDay };
|
|
20348
|
+
}
|
|
20349
|
+
}
|
|
18771
20350
|
return {
|
|
18772
20351
|
since: r.since,
|
|
18773
20352
|
total: r.total,
|
|
18774
20353
|
legacy: r.legacy ?? null,
|
|
18775
|
-
byDay: days
|
|
20354
|
+
byDay: days,
|
|
20355
|
+
projects
|
|
18776
20356
|
};
|
|
18777
20357
|
}
|
|
18778
20358
|
/**
|
|
@@ -18807,6 +20387,8 @@ var FleetNodeBridge = class {
|
|
|
18807
20387
|
name: s.name ?? null,
|
|
18808
20388
|
status: s.status,
|
|
18809
20389
|
lastUserMessage: lastUserMessage(s),
|
|
20390
|
+
// 控制面按它排「最近在跑什么」,运行中的长任务才不会被排到后面
|
|
20391
|
+
lastStartedAt: s.lastStartedAt ?? null,
|
|
18810
20392
|
// 控制面的聚合邮件靠它判断「这台机器最近有没有在干活」;
|
|
18811
20393
|
// 状态跃迁可能被 info 节流跳过,完成时间不会。
|
|
18812
20394
|
lastCompletedAt: s.lastCompletedAt ?? null
|
|
@@ -18835,7 +20417,7 @@ var FleetNodeBridge = class {
|
|
|
18835
20417
|
label: this.opts.deps.store.loadNodeLabel() ?? void 0,
|
|
18836
20418
|
platform: process.platform,
|
|
18837
20419
|
mode: process.env.NODE_MODE ?? "standalone",
|
|
18838
|
-
version: "0.2.
|
|
20420
|
+
version: "0.2.20",
|
|
18839
20421
|
ip: primaryIp(),
|
|
18840
20422
|
projects,
|
|
18841
20423
|
totals,
|
|
@@ -18997,7 +20579,13 @@ var FleetNodeBridge = class {
|
|
|
18997
20579
|
const stamped = { ...ack, reqId: env3.reqId, type: env3.type };
|
|
18998
20580
|
const json2 = JSON.stringify(stamped);
|
|
18999
20581
|
const __t1 = dbgNow();
|
|
19000
|
-
|
|
20582
|
+
const payload = guardAckSize(stamped, json2);
|
|
20583
|
+
if (payload !== stamped) {
|
|
20584
|
+
console.warn(
|
|
20585
|
+
`[fleet] ${env3.type} \u56DE\u6267 ${(json2.length / 1024 / 1024).toFixed(1)}MB \u8D85\u8FC7\u5355\u5E27\u4E0A\u9650\uFF0C\u5DF2\u964D\u7EA7\u4E3A\u9519\u8BEF\u56DE\u6267`
|
|
20586
|
+
);
|
|
20587
|
+
}
|
|
20588
|
+
this._send({ kind: "ack", clientId: env3.clientId, ack: payload });
|
|
19001
20589
|
dbgMs(
|
|
19002
20590
|
"publishAck",
|
|
19003
20591
|
__t0,
|
|
@@ -20376,7 +21964,7 @@ function verifySignature(data, signatureBase64, publicKey) {
|
|
|
20376
21964
|
}
|
|
20377
21965
|
|
|
20378
21966
|
// src/update/downloader.ts
|
|
20379
|
-
var
|
|
21967
|
+
var MAX_BINARY_BYTES2 = 400 * 1024 * 1024;
|
|
20380
21968
|
var MANIFEST_TIMEOUT_MS = 15e3;
|
|
20381
21969
|
var DOWNLOAD_TIMEOUT_MS2 = 10 * 6e4;
|
|
20382
21970
|
async function fetchManifest(url, publicKey, opts = {}) {
|
|
@@ -20422,7 +22010,7 @@ async function downloadBinary(bin, destPath, log = () => {
|
|
|
20422
22010
|
if (!res.ok) return { error: `\u4E0B\u8F7D\u5931\u8D25 HTTP ${res.status}` };
|
|
20423
22011
|
if (!res.body) return { error: "\u4E0B\u8F7D\u54CD\u5E94\u6CA1\u6709 body" };
|
|
20424
22012
|
const expected = bin.size && bin.size > 0 ? bin.size : void 0;
|
|
20425
|
-
const limit = expected ? Math.max(expected * 2, 1024 * 1024) :
|
|
22013
|
+
const limit = expected ? Math.max(expected * 2, 1024 * 1024) : MAX_BINARY_BYTES2;
|
|
20426
22014
|
const hash = (0, import_node_crypto5.createHash)("sha256");
|
|
20427
22015
|
let bytes = 0;
|
|
20428
22016
|
try {
|
|
@@ -20629,6 +22217,31 @@ function nextIntervalMs() {
|
|
|
20629
22217
|
const jitter = base * 0.15 * (Math.random() * 2 - 1);
|
|
20630
22218
|
return Math.max(6e4, Math.round(base + jitter));
|
|
20631
22219
|
}
|
|
22220
|
+
function evaluateManifest(base, manifest) {
|
|
22221
|
+
if (!parseVersion(base.currentVersion) || compareVersions(manifest.version, base.currentVersion) <= 0) {
|
|
22222
|
+
return {
|
|
22223
|
+
...base,
|
|
22224
|
+
updateAvailable: false,
|
|
22225
|
+
latestVersion: manifest.version,
|
|
22226
|
+
reason: `\u5DF2\u662F\u6700\u65B0\u7248\u672C\uFF08v${base.currentVersion}\uFF09`
|
|
22227
|
+
};
|
|
22228
|
+
}
|
|
22229
|
+
if (manifest.minimumVersion && compareVersions(base.currentVersion, manifest.minimumVersion) < 0) {
|
|
22230
|
+
return {
|
|
22231
|
+
...base,
|
|
22232
|
+
updateAvailable: false,
|
|
22233
|
+
latestVersion: manifest.version,
|
|
22234
|
+
manualUpgradeRequired: manifest.minimumVersion,
|
|
22235
|
+
reason: `\u5F53\u524D\u7248\u672C\u8FC7\u4F4E\uFF0C\u9700\u624B\u52A8\u5347\u7EA7\u5230 >= ${manifest.minimumVersion}`
|
|
22236
|
+
};
|
|
22237
|
+
}
|
|
22238
|
+
return {
|
|
22239
|
+
...base,
|
|
22240
|
+
updateAvailable: true,
|
|
22241
|
+
latestVersion: manifest.version,
|
|
22242
|
+
...manifest.notes ? { notes: manifest.notes } : {}
|
|
22243
|
+
};
|
|
22244
|
+
}
|
|
20632
22245
|
var UpdateController = class {
|
|
20633
22246
|
constructor(opts, currentVersion2 = currentVersion()) {
|
|
20634
22247
|
this.opts = opts;
|
|
@@ -20717,6 +22330,77 @@ var UpdateController = class {
|
|
|
20717
22330
|
}
|
|
20718
22331
|
this.schedule(nextIntervalMs());
|
|
20719
22332
|
}
|
|
22333
|
+
/**
|
|
22334
|
+
* 只读检查:拉清单比对版本,不下载、不改 state。
|
|
22335
|
+
*
|
|
22336
|
+
* 供远端 `self_update --action check` 与 UI 展示用;与定时器 `check()` 不同,
|
|
22337
|
+
* 这里不加 `checking` 锁、不写 `lastCheckAt`,因此不会干扰自动更新流程。
|
|
22338
|
+
*/
|
|
22339
|
+
async inspect() {
|
|
22340
|
+
const base = {
|
|
22341
|
+
currentVersion: this.currentVersion,
|
|
22342
|
+
channel: updaterChannel(),
|
|
22343
|
+
manifestUrl: this.manifestUrl()
|
|
22344
|
+
};
|
|
22345
|
+
const key = this.publicKey;
|
|
22346
|
+
const cap = selfUpdateCapability(Boolean(key));
|
|
22347
|
+
if (!cap.ok) {
|
|
22348
|
+
return { ...base, updateAvailable: false, reason: `\u81EA\u66F4\u65B0\u4E0D\u53EF\u7528\uFF1A${cap.reason}` };
|
|
22349
|
+
}
|
|
22350
|
+
const res = await fetchManifest(base.manifestUrl, key, {
|
|
22351
|
+
allowInsecureUrl: env("ZQAGENT_UPDATE_INSECURE") === "1"
|
|
22352
|
+
});
|
|
22353
|
+
if (!res.manifest) {
|
|
22354
|
+
return {
|
|
22355
|
+
...base,
|
|
22356
|
+
updateAvailable: false,
|
|
22357
|
+
reason: `\u66F4\u65B0\u68C0\u67E5\u5931\u8D25\uFF1A${res.error ?? "\u6E05\u5355\u6821\u9A8C\u5931\u8D25"}`
|
|
22358
|
+
};
|
|
22359
|
+
}
|
|
22360
|
+
return evaluateManifest(base, res.manifest);
|
|
22361
|
+
}
|
|
22362
|
+
/**
|
|
22363
|
+
* 远端触发安装:先同步给出结论(受理/无需更新),安装本身在后台跑。
|
|
22364
|
+
*
|
|
22365
|
+
* 必须「先回执、后安装」:安装成功后进程会 `exit(70)` 交给 systemd 拉起新版本,
|
|
22366
|
+
* 如果等到安装完成再回执,回执永远发不出去(表现为网关超时)。
|
|
22367
|
+
*
|
|
22368
|
+
* 复用定时器同一条 `check()` 路径:验签、下载、自检、失败计数、安装后自检与
|
|
22369
|
+
* 回滚保护全都一致,只是把 `installMode` 设为 `force` 时不等待空闲。
|
|
22370
|
+
*/
|
|
22371
|
+
async requestInstall(force = false) {
|
|
22372
|
+
const fromVersion = this.currentVersion;
|
|
22373
|
+
const cap = selfUpdateCapability(Boolean(this.publicKey));
|
|
22374
|
+
if (!cap.ok) {
|
|
22375
|
+
return { accepted: false, fromVersion, message: `\u81EA\u66F4\u65B0\u4E0D\u53EF\u7528\uFF1A${cap.reason}` };
|
|
22376
|
+
}
|
|
22377
|
+
const info = await this.inspect();
|
|
22378
|
+
if (!info.updateAvailable) {
|
|
22379
|
+
return {
|
|
22380
|
+
accepted: false,
|
|
22381
|
+
fromVersion,
|
|
22382
|
+
...info.latestVersion ? { toVersion: info.latestVersion } : {},
|
|
22383
|
+
message: info.reason ?? "\u5DF2\u662F\u6700\u65B0\u7248\u672C"
|
|
22384
|
+
};
|
|
22385
|
+
}
|
|
22386
|
+
if (this.checking || this.installing) {
|
|
22387
|
+
return {
|
|
22388
|
+
accepted: true,
|
|
22389
|
+
fromVersion,
|
|
22390
|
+
toVersion: info.latestVersion,
|
|
22391
|
+
message: `\u5DF2\u6709\u66F4\u65B0\u4EFB\u52A1\u5728\u8FDB\u884C\u4E2D\uFF08\u76EE\u6807 v${info.latestVersion}\uFF09\uFF0C\u53EF\u5728\u7A0D\u540E\u7528 status \u67E5\u770B\u7ED3\u679C`
|
|
22392
|
+
};
|
|
22393
|
+
}
|
|
22394
|
+
void this.check(force, false, true).catch((err) => {
|
|
22395
|
+
this.recordFailure(info.latestVersion, `\u5B89\u88C5\u5931\u8D25\uFF1A${err.message}`);
|
|
22396
|
+
});
|
|
22397
|
+
return {
|
|
22398
|
+
accepted: true,
|
|
22399
|
+
fromVersion,
|
|
22400
|
+
toVersion: info.latestVersion,
|
|
22401
|
+
message: force ? `\u5F00\u59CB\u5B89\u88C5 v${info.latestVersion}\uFF08\u7ACB\u5373\u91CD\u542F\uFF09` : `\u5F00\u59CB\u5B89\u88C5 v${info.latestVersion}\uFF08\u7B49\u5F85\u8282\u70B9\u7A7A\u95F2\u540E\u91CD\u542F\uFF09`
|
|
22402
|
+
};
|
|
22403
|
+
}
|
|
20720
22404
|
/**
|
|
20721
22405
|
* 检查并准备更新;`installMode` 为 true 时下载后立即安装(不再等空闲)。
|
|
20722
22406
|
* 返回可读结论,供 CLI `update --check` 使用。
|
|
@@ -20945,7 +22629,9 @@ function seedDemoTenant(store) {
|
|
|
20945
22629
|
// 未显式指定时随机生成:固定默认密钥一旦随 HOST=0.0.0.0 暴露到公网,等于公开凭据。
|
|
20946
22630
|
// 只在首次创建时生成并落盘,后续启动沿用磁盘上的值(启动日志会打印完整密钥)。
|
|
20947
22631
|
apiKey: env2("TENANT_API_KEY") ?? generateApiKey(),
|
|
20948
|
-
|
|
22632
|
+
// 不再硬编码具体厂商型号:志前 API 只认 deepseek-flash,写死 deepseek-v4-flash
|
|
22633
|
+
// 会让新装节点的第一次请求 404。默认值统一走 defaultTenantModel(见 nodeLlm.ts)。
|
|
22634
|
+
model: defaultTenantModel(store),
|
|
20949
22635
|
provider: {
|
|
20950
22636
|
apiKey: env2("OPENAI_API_KEY"),
|
|
20951
22637
|
baseURL: env2("OPENAI_BASE_URL")
|
|
@@ -21085,6 +22771,8 @@ function startServer(opts = {}) {
|
|
|
21085
22771
|
const store = new Store(dataDir);
|
|
21086
22772
|
const demo = seedDemoTenant(store);
|
|
21087
22773
|
syncTenantModels(store);
|
|
22774
|
+
const nodeModel = nodeDefaultProvider(store)?.model;
|
|
22775
|
+
if (nodeModel) adoptBuiltinModelForTenants(store, nodeModel);
|
|
21088
22776
|
const usageLedger = new UsageLedger(dataDir);
|
|
21089
22777
|
usageLedger.init();
|
|
21090
22778
|
const manager = new SessionManager(store, void 0, { usage: usageLedger });
|
|
@@ -21116,6 +22804,7 @@ function startServer(opts = {}) {
|
|
|
21116
22804
|
const notificationHub = createNotificationHub();
|
|
21117
22805
|
let fleet = null;
|
|
21118
22806
|
let fleetCred = null;
|
|
22807
|
+
let updater = null;
|
|
21119
22808
|
function connectFleet(cred) {
|
|
21120
22809
|
const localId = ensureNodeId(store);
|
|
21121
22810
|
const nodeId = (cred.nodeId ?? "").trim() || localId;
|
|
@@ -21133,6 +22822,8 @@ function startServer(opts = {}) {
|
|
|
21133
22822
|
deps: {
|
|
21134
22823
|
store,
|
|
21135
22824
|
manager,
|
|
22825
|
+
// 闭包推迟求值:桥先于 UpdateController 创建,指令到达时 updater 已就绪
|
|
22826
|
+
getUpdater: () => updater,
|
|
21136
22827
|
applyFleetCred: async (cred2) => {
|
|
21137
22828
|
store.saveFleetCredential(cred2);
|
|
21138
22829
|
setTimeout(() => connectFleet(cred2), 300);
|
|
@@ -21225,8 +22916,12 @@ function startServer(opts = {}) {
|
|
|
21225
22916
|
if (initialCred) {
|
|
21226
22917
|
connectFleet(initialCred);
|
|
21227
22918
|
} else if (opts.autoEnroll) {
|
|
21228
|
-
void fleetController.enroll(opts.autoEnroll).then((st) =>
|
|
22919
|
+
void fleetController.enroll(opts.autoEnroll).then((st) => {
|
|
22920
|
+
console.log(`[fleet] \u5DF2\u81EA\u52A8\u63A5\u5165\uFF0C\u8282\u70B9 ${st.nodeId}\uFF08${st.gatewayUrl}\uFF09`);
|
|
22921
|
+
scheduleBuiltinProvider(store, (m) => console.log(m));
|
|
22922
|
+
}).catch((e) => console.warn(`[fleet] \u81EA\u52A8\u63A5\u5165\u5931\u8D25\uFF08\u53EF\u7A0D\u540E /link \u91CD\u8BD5\uFF09: ${e instanceof Error ? e.message : e}`));
|
|
21229
22923
|
}
|
|
22924
|
+
if (initialCred) scheduleBuiltinProvider(store, (m) => console.log(m));
|
|
21230
22925
|
const pairingCodes = new PairingCodeStore();
|
|
21231
22926
|
const qq = new QqManager({
|
|
21232
22927
|
store,
|
|
@@ -21283,16 +22978,21 @@ function startServer(opts = {}) {
|
|
|
21283
22978
|
scheduleFleetInfoRefresh();
|
|
21284
22979
|
}
|
|
21285
22980
|
});
|
|
22981
|
+
updater = new UpdateController({
|
|
22982
|
+
dataDir,
|
|
22983
|
+
isIdle: () => manager.isIdle(),
|
|
22984
|
+
prepareExit: closeServer
|
|
22985
|
+
});
|
|
21286
22986
|
const server = createApp(store, manager, {
|
|
21287
22987
|
secretBox,
|
|
21288
22988
|
notificationHub,
|
|
21289
22989
|
qq,
|
|
21290
22990
|
adminKey,
|
|
21291
22991
|
fleet: fleetController,
|
|
21292
|
-
host
|
|
22992
|
+
host,
|
|
22993
|
+
updater
|
|
21293
22994
|
});
|
|
21294
22995
|
let closed = false;
|
|
21295
|
-
let updater = null;
|
|
21296
22996
|
function closeServer() {
|
|
21297
22997
|
if (closed) return;
|
|
21298
22998
|
closed = true;
|
|
@@ -21325,13 +23025,6 @@ function startServer(opts = {}) {
|
|
|
21325
23025
|
}
|
|
21326
23026
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
21327
23027
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
21328
|
-
if (opts.autoUpdate) {
|
|
21329
|
-
updater = new UpdateController({
|
|
21330
|
-
dataDir,
|
|
21331
|
-
isIdle: () => manager.isIdle(),
|
|
21332
|
-
prepareExit: closeServer
|
|
21333
|
-
});
|
|
21334
|
-
}
|
|
21335
23028
|
server.timeout = 0;
|
|
21336
23029
|
server.keepAliveTimeout = 65e3;
|
|
21337
23030
|
server.headersTimeout = 66e3;
|
|
@@ -21351,7 +23044,7 @@ function startServer(opts = {}) {
|
|
|
21351
23044
|
} else {
|
|
21352
23045
|
printReady(info, qq);
|
|
21353
23046
|
}
|
|
21354
|
-
updater?.start();
|
|
23047
|
+
if (opts.autoUpdate) updater?.start();
|
|
21355
23048
|
resolve25(info);
|
|
21356
23049
|
}, reject);
|
|
21357
23050
|
});
|
|
@@ -21803,7 +23496,7 @@ ${envDump()}`);
|
|
|
21803
23496
|
}
|
|
21804
23497
|
|
|
21805
23498
|
// src/cli/index.ts
|
|
21806
|
-
var execAsync2 = (0, import_util.promisify)(
|
|
23499
|
+
var execAsync2 = (0, import_util.promisify)(import_child_process2.exec);
|
|
21807
23500
|
if (enableDebug(process.argv)) {
|
|
21808
23501
|
logEnvironment();
|
|
21809
23502
|
}
|
|
@@ -22036,7 +23729,7 @@ function cycleMode(engine) {
|
|
|
22036
23729
|
engine.permissions.mode = next;
|
|
22037
23730
|
return next;
|
|
22038
23731
|
}
|
|
22039
|
-
var rl =
|
|
23732
|
+
var rl = readline4.createInterface({
|
|
22040
23733
|
input: process.stdin,
|
|
22041
23734
|
output: process.stdout
|
|
22042
23735
|
});
|
|
@@ -22282,7 +23975,10 @@ async function connectWizard() {
|
|
|
22282
23975
|
break;
|
|
22283
23976
|
}
|
|
22284
23977
|
cliLog(`\u274C \u81EA\u68C0\u5931\u8D25: ${probe.error}${probe.code ? ` | ${probe.code}` : ""}`);
|
|
22285
|
-
for (const hint of
|
|
23978
|
+
for (const hint of [
|
|
23979
|
+
...authErrorHints(probe.error ?? "", probe.code ?? ""),
|
|
23980
|
+
...paramErrorHints(probe.error ?? "", probe.code ?? "")
|
|
23981
|
+
]) {
|
|
22286
23982
|
cliLog(` \u{1F4A1} ${hint}`);
|
|
22287
23983
|
}
|
|
22288
23984
|
const keep = (await question("\u91CD\u65B0\u9009\u62E9\u6A21\u578B (\u56DE\u8F66\u91CD\u9009) / \u8F93\u5165 y \u4ECD\u4F7F\u7528\u8BE5\u6A21\u578B: ")).trim().toLowerCase();
|
|
@@ -22516,7 +24212,7 @@ async function offerTerminalSetup(engine, canUseAgent) {
|
|
|
22516
24212
|
}
|
|
22517
24213
|
var KNOWN_SUBCOMMANDS = /* @__PURE__ */ new Set(["link", "serve", "update", "rename"]);
|
|
22518
24214
|
function cliVersion() {
|
|
22519
|
-
const env3 = "0.2.
|
|
24215
|
+
const env3 = "0.2.20"?.trim();
|
|
22520
24216
|
if (env3) return env3;
|
|
22521
24217
|
try {
|
|
22522
24218
|
const req2 = (0, import_module.createRequire)(importMetaUrl);
|
|
@@ -22779,8 +24475,8 @@ async function main() {
|
|
|
22779
24475
|
|
|
22780
24476
|
\u{1F4CB} \u4F1A\u8BDDID: ${SESSION_ID}`);
|
|
22781
24477
|
cliLog(`\u{1F4BE} \u72B6\u6001\u5DF2\u4FDD\u5B58\uFF0C\u53EF\u901A\u8FC7\u4EE5\u4E0B\u547D\u4EE4\u6062\u590D:`);
|
|
22782
|
-
cliLog(`
|
|
22783
|
-
cliLog(` \u6216
|
|
24478
|
+
cliLog(` zqagent --resume ${SESSION_ID}`);
|
|
24479
|
+
cliLog(` \u6216 zqagent --r ${SESSION_ID}
|
|
22784
24480
|
`);
|
|
22785
24481
|
rl.close();
|
|
22786
24482
|
process.exit(0);
|
|
@@ -23029,6 +24725,12 @@ async function main() {
|
|
|
23029
24725
|
cliLog(`\u274C \u672A\u627E\u5230\u4F9B\u5E94\u5546: ${arg}`);
|
|
23030
24726
|
break;
|
|
23031
24727
|
}
|
|
24728
|
+
if (target.id === BUILTIN_PROVIDER_ID) {
|
|
24729
|
+
cliLog(
|
|
24730
|
+
"\u274C \u5E73\u53F0\u5185\u7F6E\u6A21\u578B\u4E0D\u53EF\u5220\u9664\uFF08\u63A5\u5165 Hub \u540E\u81EA\u52A8\u914D\u7F6E\uFF0CKey \u7531\u5E73\u53F0\u6258\u7BA1\uFF09\u3002\n \u5982\u9700\u6539\u7528\u5176\u5B83\u6A21\u578B\uFF1A/connect \u9009\u62E9\u6216\u6DFB\u52A0\u4F9B\u5E94\u5546\u540E\u8BBE\u4E3A\u9ED8\u8BA4\u3002"
|
|
24731
|
+
);
|
|
24732
|
+
break;
|
|
24733
|
+
}
|
|
23032
24734
|
if (removeConnection(target.id)) {
|
|
23033
24735
|
currentConnection = loadConnection();
|
|
23034
24736
|
engine = buildEngine(SESSION_ID);
|