@zqagent/core 0.2.10 → 0.2.12

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.
@@ -570,7 +570,7 @@ var init_wire = __esm({
570
570
  init_cjs_shims();
571
571
  var import_config = require("dotenv/config");
572
572
  var fs34 = __toESM(require("fs"), 1);
573
- var readline2 = __toESM(require("readline"), 1);
573
+ var readline3 = __toESM(require("readline"), 1);
574
574
  var path42 = __toESM(require("path"), 1);
575
575
  var import_child_process = require("child_process");
576
576
  var import_util = require("util");
@@ -10094,6 +10094,38 @@ function countRawEscapes(chunk) {
10094
10094
  for (const ch of cleaned) if (ch === "\x1B") escCount++;
10095
10095
  return escCount;
10096
10096
  }
10097
+ function patchReadlineHistoryNav(rl2) {
10098
+ const iface = rl2;
10099
+ const orig = iface._ttyWrite;
10100
+ let draft = null;
10101
+ iface._ttyWrite = function(s, key) {
10102
+ const k = key;
10103
+ const name = k?.name;
10104
+ const plainArrow = (name === "up" || name === "down") && !k?.ctrl && !k?.meta && !k?.shift;
10105
+ if (plainArrow) {
10106
+ if (name === "up" && iface.historyIndex === -1) {
10107
+ draft = { line: iface.line, cursor: iface.cursor };
10108
+ }
10109
+ if (iface.cursor > 0) {
10110
+ iface._moveCursor(-iface.cursor);
10111
+ }
10112
+ }
10113
+ const prevHistoryIndex = iface.historyIndex;
10114
+ orig.call(rl2, s, key);
10115
+ if (plainArrow && name === "down" && prevHistoryIndex >= 0 && iface.historyIndex === -1) {
10116
+ const saved = draft;
10117
+ draft = null;
10118
+ if (saved) {
10119
+ iface.line = saved.line;
10120
+ iface.cursor = Math.min(saved.cursor, saved.line.length);
10121
+ iface._refreshLine?.();
10122
+ }
10123
+ }
10124
+ };
10125
+ rl2.on("line", () => {
10126
+ draft = null;
10127
+ });
10128
+ }
10097
10129
  var PASTE_PLACEHOLDER_PREFIX = "\u7C98\u8D34\u6587\u672C #";
10098
10130
  var InputRouter = class {
10099
10131
  rl;
@@ -10481,7 +10513,8 @@ function renderTerminalTutorial() {
10481
10513
 
10482
10514
  // src/cli/link.ts
10483
10515
  init_cjs_shims();
10484
- var path21 = __toESM(require("path"), 1);
10516
+ var path27 = __toESM(require("path"), 1);
10517
+ var readline2 = __toESM(require("readline"), 1);
10485
10518
 
10486
10519
  // src/server/store.ts
10487
10520
  init_cjs_shims();
@@ -10889,858 +10922,500 @@ async function hubEnrollByAccount(baseUrl, input) {
10889
10922
  return { hubUrl: baseUrl.replace(/\/$/, ""), ...data };
10890
10923
  }
10891
10924
 
10892
- // src/cli/serve-instances.ts
10925
+ // src/cli/account-enroll.ts
10893
10926
  init_cjs_shims();
10894
- var import_node_child_process4 = require("child_process");
10895
- var fs18 = __toESM(require("fs"), 1);
10896
- var path20 = __toESM(require("path"), 1);
10897
- var DEFAULT_SERVE_HOST = "127.0.0.1";
10898
- var DEFAULT_SERVE_PORT = 8787;
10899
- function serveSubcommandIndex(argv) {
10900
- for (let i = 1; i < argv.length; i++) {
10901
- if (argv[i].startsWith("-")) continue;
10902
- return argv[i] === "serve" ? i : -1;
10903
- }
10904
- return -1;
10905
- }
10906
- function parseServeArgs(argv) {
10907
- const at = serveSubcommandIndex(argv);
10908
- if (at < 0) return null;
10909
- let dataDir = null;
10910
- let host = DEFAULT_SERVE_HOST;
10911
- let port = DEFAULT_SERVE_PORT;
10912
- for (let i = at + 1; i < argv.length; i++) {
10913
- const arg = argv[i];
10914
- if (arg === "--data-dir" && argv[i + 1]) {
10915
- dataDir = argv[++i];
10916
- } else if (arg.startsWith("--data-dir=")) {
10917
- dataDir = arg.slice("--data-dir=".length);
10918
- } else if (arg === "--host" && argv[i + 1]) {
10919
- host = argv[++i];
10920
- } else if (arg.startsWith("--host=")) {
10921
- host = arg.slice("--host=".length);
10922
- } else if (arg === "--port" && argv[i + 1]) {
10923
- port = Number(argv[++i]);
10924
- } else if (arg.startsWith("--port=")) {
10925
- port = Number(arg.slice("--port=".length));
10926
- }
10927
- }
10928
- if (!Number.isFinite(port) || port <= 0) port = DEFAULT_SERVE_PORT;
10929
- if (host === "0.0.0.0" || host === "::" || host === "") host = DEFAULT_SERVE_HOST;
10930
- return { dataDir, host, port };
10927
+ var path24 = __toESM(require("path"), 1);
10928
+ var readline = __toESM(require("readline/promises"), 1);
10929
+
10930
+ // src/server/projects.ts
10931
+ init_cjs_shims();
10932
+ var path23 = __toESM(require("path"), 1);
10933
+
10934
+ // src/server/session.ts
10935
+ init_cjs_shims();
10936
+ var path22 = __toESM(require("path"), 1);
10937
+ var import_async_hooks = require("async_hooks");
10938
+
10939
+ // src/server/mcpConfig.ts
10940
+ init_cjs_shims();
10941
+ var import_node_fs = __toESM(require("fs"), 1);
10942
+ var import_node_path = __toESM(require("path"), 1);
10943
+ function isValidMcpConfig(v) {
10944
+ return !!v && typeof v === "object" && typeof v.name === "string" && (v.type === "stdio" || v.type === "http");
10931
10945
  }
10932
- function buildHotCandidates(explicitDataDir, discovered, fallback) {
10933
- const out = explicitDataDir ? [{ dataDir: path20.resolve(explicitDataDir), ...fallback }] : [{ dataDir: path20.resolve(DEFAULT_DATA_DIR), ...fallback }];
10934
- for (const inst of discovered) {
10935
- if (!out.some((o) => o.dataDir === inst.dataDir)) out.push(inst);
10946
+ function readConfigFile(file) {
10947
+ if (!file) return [];
10948
+ try {
10949
+ if (!import_node_fs.default.existsSync(file)) return [];
10950
+ const raw = JSON.parse(import_node_fs.default.readFileSync(file, "utf8"));
10951
+ const list = Array.isArray(raw) ? raw : raw && typeof raw === "object" && Array.isArray(raw.servers) ? raw.servers : [];
10952
+ return list.filter(isValidMcpConfig);
10953
+ } catch (err) {
10954
+ console.error(`[mcp] \u8BFB\u53D6\u914D\u7F6E\u5931\u8D25 ${file}: ${err.message}`);
10955
+ return [];
10936
10956
  }
10937
- return out;
10938
- }
10939
- function isWindowsAbsolute(p) {
10940
- return /^[A-Za-z]:[\\/]/.test(p) || /^\\\\/.test(p);
10941
10957
  }
10942
- function isAbsolutePath(p) {
10943
- return path20.isAbsolute(p) || isWindowsAbsolute(p);
10958
+ function hasServerMcpConfig() {
10959
+ if (readConfigFile(process.env.MCP_CONFIG_PATH).length > 0) return true;
10960
+ const workspace = process.env.WORKSPACE;
10961
+ if (!workspace) return false;
10962
+ return readConfigFile(import_node_path.default.join(workspace, ".agent", "mcp.json")).length > 0;
10944
10963
  }
10945
- function normalizePath(p) {
10946
- return isWindowsAbsolute(p) ? path20.win32.normalize(p) : path20.normalize(p);
10964
+ function loadServerMcpConfigs(tenant) {
10965
+ const fromEnv = readConfigFile(process.env.MCP_CONFIG_PATH);
10966
+ if (fromEnv.length > 0) return fromEnv;
10967
+ if (Array.isArray(tenant.mcpServers) && tenant.mcpServers.length > 0) {
10968
+ return tenant.mcpServers.filter(isValidMcpConfig);
10969
+ }
10970
+ const workspace = tenant.workspaceRoot ?? process.env.WORKSPACE;
10971
+ if (workspace)
10972
+ return readConfigFile(import_node_path.default.join(workspace, ".agent", "mcp.json"));
10973
+ return [];
10947
10974
  }
10948
- function parseServeCommandLine(line) {
10949
- if (!/(?:^|\s)["']?serve["']?(?=\s|$)/.test(line)) return null;
10950
- const m = /--data-dir(?:=|\s+)(?:"([^"]+)"|'([^']+)'|(.+?))(?=\s+--\w|\s*$)/.exec(line);
10951
- const rawDataDir = (m?.[1] ?? m?.[2] ?? m?.[3] ?? "").trim();
10952
- if (!rawDataDir || !isAbsolutePath(rawDataDir)) return null;
10953
- const host = /--host(?:=|\s+)(?:"([^"]+)"|'([^']+)'|(\S+))/.exec(line);
10954
- const rawHost = (host?.[1] ?? host?.[2] ?? host?.[3] ?? DEFAULT_SERVE_HOST).trim();
10955
- const portMatch = /--port(?:=|\s+)(\d+)/.exec(line);
10956
- const port = Number(portMatch?.[1] ?? DEFAULT_SERVE_PORT);
10975
+
10976
+ // src/server/nodeLlm.ts
10977
+ init_cjs_shims();
10978
+ var GLOBAL_PROVIDER_ID = "__env__";
10979
+ function envProvider() {
10980
+ const apiKey = process.env.OPENAI_API_KEY;
10981
+ if (!apiKey) return null;
10957
10982
  return {
10958
- dataDir: normalizePath(rawDataDir),
10959
- host: rawHost === "0.0.0.0" || rawHost === "::" || rawHost === "" ? DEFAULT_SERVE_HOST : rawHost,
10960
- port: Number.isFinite(port) && port > 0 ? port : DEFAULT_SERVE_PORT
10983
+ id: GLOBAL_PROVIDER_ID,
10984
+ name: process.env.OPENAI_MODEL || process.env.MODEL || "\u5168\u5C40 LLM",
10985
+ type: "env",
10986
+ baseURL: process.env.OPENAI_BASE_URL ?? "",
10987
+ model: process.env.OPENAI_MODEL || process.env.MODEL || "",
10988
+ apiKey
10961
10989
  };
10962
10990
  }
10963
- function processCwd(pid) {
10964
- try {
10965
- return fs18.readlinkSync(`/proc/${pid}/cwd`);
10966
- } catch {
10967
- return null;
10968
- }
10991
+ function normReasoningEffort(v) {
10992
+ return v === "low" || v === "medium" || v === "high" ? v : null;
10969
10993
  }
10970
- function processEnv(pid) {
10971
- const out = {};
10972
- try {
10973
- const raw = fs18.readFileSync(`/proc/${pid}/environ`, "utf-8");
10974
- for (const entry of raw.split("\0")) {
10975
- const eq = entry.indexOf("=");
10976
- if (eq > 0) out[entry.slice(0, eq)] = entry.slice(eq + 1);
10977
- }
10978
- } catch {
10994
+ function normalizeModelList(v) {
10995
+ if (!Array.isArray(v)) return void 0;
10996
+ const out = [];
10997
+ const seen = /* @__PURE__ */ new Set();
10998
+ for (const x of v) {
10999
+ const s = String(x ?? "").trim();
11000
+ if (!s || seen.has(s)) continue;
11001
+ seen.add(s);
11002
+ out.push(s);
11003
+ if (out.length >= 500) break;
10979
11004
  }
10980
11005
  return out;
10981
11006
  }
10982
- function resolveDataDir(rawDataDir, cwd, env3) {
10983
- const pick = rawDataDir ?? env3.DATA_DIR ?? null;
10984
- if (pick) return path20.isAbsolute(pick) ? path20.normalize(pick) : cwd ? path20.resolve(cwd, pick) : null;
10985
- return cwd ? path20.resolve(cwd, DEFAULT_DATA_DIR) : null;
11007
+ function nodeLlmView(cfg) {
11008
+ const g = envProvider();
11009
+ const providers = [
11010
+ ...g ? [
11011
+ {
11012
+ id: g.id,
11013
+ name: g.name,
11014
+ type: g.type,
11015
+ baseURL: g.baseURL,
11016
+ model: g.model,
11017
+ hasApiKey: true,
11018
+ global: true
11019
+ }
11020
+ ] : [],
11021
+ ...cfg.providers.map((p) => ({
11022
+ id: p.id,
11023
+ name: p.name,
11024
+ type: p.type ?? "",
11025
+ baseURL: p.baseURL,
11026
+ model: p.model,
11027
+ hasApiKey: Boolean(p.apiKey),
11028
+ models: p.models ?? [],
11029
+ nonChat: p.nonChat ?? [],
11030
+ modelMeta: p.modelMeta ?? [],
11031
+ reasoningEffort: p.reasoningEffort ?? null,
11032
+ global: false
11033
+ }))
11034
+ ];
11035
+ return {
11036
+ defaultProviderId: cfg.defaultProviderId ?? null,
11037
+ providers,
11038
+ /** 未显式设置默认时,是否由环境变量全局配置兜底 */
11039
+ globalIsDefault: !cfg.defaultProviderId && Boolean(g)
11040
+ };
10986
11041
  }
10987
- function discoverFromProc() {
10988
- const out = [];
10989
- let pids;
10990
- try {
10991
- pids = fs18.readdirSync("/proc");
10992
- } catch {
10993
- return out;
11042
+ function applyNodeLlmPatch(cfg, raw) {
11043
+ const next = { providers: cfg.providers, defaultProviderId: cfg.defaultProviderId };
11044
+ if (Array.isArray(raw?.providers)) {
11045
+ const old = new Map(cfg.providers.map((p) => [p.id, p]));
11046
+ next.providers = raw.providers.filter((x) => Boolean(x) && typeof x === "object").filter((x) => x.global !== true && String(x.id ?? "") !== GLOBAL_PROVIDER_ID).map((x) => {
11047
+ const id = String(x.id ?? "").trim();
11048
+ if (!id) throw new ApiError(400, "bad_request", "\u4F9B\u5E94\u5546 id \u4E0D\u80FD\u4E3A\u7A7A");
11049
+ const prev = old.get(id);
11050
+ const apiKey = String(x.apiKey ?? "").trim();
11051
+ const models = normalizeModelList(x.models) ?? prev?.models;
11052
+ const nonChat = normalizeModelList(x.nonChat) ?? prev?.nonChat;
11053
+ const modelMeta = normalizeModelMeta(x.modelMeta) ?? prev?.modelMeta;
11054
+ return {
11055
+ id,
11056
+ name: String(x.name ?? prev?.name ?? id).trim(),
11057
+ type: x.type == null ? prev?.type : String(x.type),
11058
+ baseURL: String(x.baseURL ?? prev?.baseURL ?? "").trim(),
11059
+ model: String(x.model ?? prev?.model ?? "").trim(),
11060
+ apiKey: apiKey || prev?.apiKey,
11061
+ reasoningEffort: x.reasoningEffort === void 0 ? prev?.reasoningEffort ?? null : normReasoningEffort(x.reasoningEffort),
11062
+ ...models ? { models } : {},
11063
+ ...nonChat ? { nonChat } : {},
11064
+ ...modelMeta ? { modelMeta } : {}
11065
+ };
11066
+ });
10994
11067
  }
10995
- for (const name of pids) {
10996
- if (!/^\d+$/.test(name)) continue;
10997
- const pid = Number(name);
10998
- if (pid === process.pid) continue;
10999
- let argv;
11000
- try {
11001
- argv = fs18.readFileSync(`/proc/${pid}/cmdline`, "utf-8").split("\0").filter(Boolean);
11002
- } catch {
11003
- continue;
11004
- }
11005
- const parsed = parseServeArgs(argv);
11006
- if (!parsed) continue;
11007
- const dataDir = resolveDataDir(parsed.dataDir, processCwd(pid), processEnv(pid));
11008
- if (!dataDir) continue;
11009
- out.push({ dataDir, host: parsed.host, port: parsed.port });
11068
+ if (typeof raw?.defaultProviderId === "string") {
11069
+ const id = raw.defaultProviderId.trim();
11070
+ next.defaultProviderId = id || void 0;
11071
+ } else if (raw?.defaultProviderId === null) {
11072
+ next.defaultProviderId = void 0;
11010
11073
  }
11011
- return out;
11074
+ return next;
11012
11075
  }
11013
- function discoverFromPs() {
11014
- const out = [];
11015
- const res = (0, import_node_child_process4.spawnSync)("ps", ["-axo", "command="], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
11016
- if (res.status !== 0 || !res.stdout) return out;
11017
- for (const line of res.stdout.split("\n")) {
11018
- const inst = parseServeCommandLine(line);
11019
- if (inst) out.push(inst);
11020
- }
11021
- return out;
11076
+ function findNodeProvider(store, id) {
11077
+ if (!id) return null;
11078
+ if (id === GLOBAL_PROVIDER_ID) return envProvider();
11079
+ return store.loadNodeLlm().providers.find((p) => p.id === id) ?? null;
11022
11080
  }
11023
- function discoverFromWindows() {
11024
- const out = [];
11025
- const script = `[Console]::OutputEncoding=[Text.Encoding]::UTF8; Get-CimInstance Win32_Process -Filter "Name='zqagent.exe' OR Name='node.exe'" | ForEach-Object { $_.CommandLine }`;
11026
- const res = (0, import_node_child_process4.spawnSync)(
11027
- "powershell.exe",
11028
- ["-NoProfile", "-NonInteractive", "-Command", script],
11029
- { encoding: "utf-8", timeout: 15e3, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] }
11030
- );
11031
- if (res.status !== 0 || !res.stdout) return out;
11032
- for (const line of res.stdout.replace(/^\uFEFF/, "").split(/\r?\n/)) {
11033
- const inst = parseServeCommandLine(line);
11034
- if (inst) out.push(inst);
11035
- }
11036
- return out;
11081
+ function nodeDefaultProvider(store) {
11082
+ const cfg = store.loadNodeLlm();
11083
+ if (cfg.providers.length === 0) return null;
11084
+ return cfg.providers.find((p) => p.id === cfg.defaultProviderId) ?? cfg.providers[0] ?? null;
11037
11085
  }
11038
- function discoverRunningServeInstances() {
11039
- const found = process.platform === "linux" ? discoverFromProc() : process.platform === "win32" ? discoverFromWindows() : discoverFromPs();
11040
- const seen = /* @__PURE__ */ new Set();
11041
- return found.filter((inst) => {
11042
- if (seen.has(inst.dataDir)) return false;
11043
- seen.add(inst.dataDir);
11044
- return true;
11045
- });
11086
+ function resolveSessionProvider(store, tenant, providerId) {
11087
+ const own = tenant.provider ?? {};
11088
+ if (providerId) {
11089
+ const picked = findNodeProvider(store, providerId);
11090
+ if (picked) {
11091
+ return {
11092
+ apiKey: picked.apiKey || own.apiKey,
11093
+ baseURL: picked.baseURL || own.baseURL,
11094
+ model: picked.model,
11095
+ providerId,
11096
+ reasoningEffort: picked.reasoningEffort ?? null,
11097
+ ...picked.modelMeta ? { modelMeta: picked.modelMeta } : {}
11098
+ };
11099
+ }
11100
+ }
11101
+ if (own.apiKey) return { ...own, model: tenant.model };
11102
+ const def = nodeDefaultProvider(store);
11103
+ if (def?.apiKey) {
11104
+ return {
11105
+ apiKey: def.apiKey,
11106
+ baseURL: own.baseURL || def.baseURL,
11107
+ model: def.model || tenant.model,
11108
+ reasoningEffort: def.reasoningEffort ?? null,
11109
+ ...def.modelMeta ? { modelMeta: def.modelMeta } : {}
11110
+ };
11111
+ }
11112
+ return { ...own, model: tenant.model };
11046
11113
  }
11047
11114
 
11048
- // src/cli/link.ts
11049
- function expandHome(p) {
11050
- const home = process.env.HOME || process.env.USERPROFILE;
11051
- if (!home) return p;
11052
- if (p === "~") return home;
11053
- if (p.startsWith("~/") || p.startsWith("~\\")) return path21.join(home, p.slice(2));
11054
- return p;
11115
+ // src/server/usage.ts
11116
+ init_cjs_shims();
11117
+ var fs19 = __toESM(require("fs"), 1);
11118
+ var path21 = __toESM(require("path"), 1);
11119
+ var LEDGER_RETENTION_MONTHS = 12;
11120
+ var USAGE_SCHEMA = 1;
11121
+ var DEFAULT_PRICING = {
11122
+ schema: USAGE_SCHEMA,
11123
+ base: "USD",
11124
+ rate: { CNY: 7.2 },
11125
+ vendors: {
11126
+ openai: {
11127
+ "gpt-4o": { input: 2.5, output: 10, cacheRead: 1.25 },
11128
+ "gpt-4o-mini": { input: 0.15, output: 0.6, cacheRead: 0.075 },
11129
+ "gpt-4.1": { input: 2, output: 8, cacheRead: 0.5 }
11130
+ },
11131
+ deepseek: {
11132
+ "deepseek-flash": {
11133
+ input: 0.27,
11134
+ output: 1.1,
11135
+ cacheRead: 0.027,
11136
+ cacheWrite: 0.27
11137
+ },
11138
+ "deepseek-reasoner": {
11139
+ input: 0.55,
11140
+ output: 2.19,
11141
+ cacheRead: 0.055,
11142
+ cacheWrite: 0.55
11143
+ },
11144
+ "deepseek-*": {
11145
+ input: 0.27,
11146
+ output: 1.1,
11147
+ cacheRead: 0.027,
11148
+ cacheWrite: 0.27
11149
+ }
11150
+ },
11151
+ qwen: {
11152
+ "qwen-plus": { input: 0.4, output: 1.2 },
11153
+ "qwen-turbo": { input: 0.05, output: 0.2 }
11154
+ },
11155
+ zhipu: { "glm-4-plus": { input: 0.7, output: 0.7 } },
11156
+ moonshot: { "moonshot-v1-8k": { input: 1.68, output: 1.68 } },
11157
+ xiaomi: {},
11158
+ custom: {},
11159
+ env: {}
11160
+ },
11161
+ fallback: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
11162
+ };
11163
+ function zeroAgg() {
11164
+ return {
11165
+ calls: 0,
11166
+ input: 0,
11167
+ output: 0,
11168
+ cacheRead: 0,
11169
+ cacheWrite: 0,
11170
+ costUsd: 0,
11171
+ unpriced: 0
11172
+ };
11055
11173
  }
11056
- function installDataDirHint() {
11057
- if (process.platform === "darwin") return "$HOME/Library/Application Support/zqagent";
11058
- if (process.platform === "win32") return "%LOCALAPPDATA%\\zqagent";
11059
- return "/var/lib/zqagent\uFF08\u9700 root\uFF1B\u666E\u901A\u7528\u6237\u7528 ~/.zqagent_data\uFF09";
11174
+ function addAgg(into, tokens, costUsd) {
11175
+ into.calls += 1;
11176
+ into.input += tokens.input;
11177
+ into.output += tokens.output;
11178
+ into.cacheRead += tokens.cacheRead;
11179
+ into.cacheWrite += tokens.cacheWrite;
11180
+ if (costUsd == null) into.unpriced += 1;
11181
+ else into.costUsd += costUsd;
11060
11182
  }
11061
- function parseLinkArgs(argv) {
11062
- const out = {};
11063
- for (let i = 0; i < argv.length; i++) {
11064
- const a = argv[i];
11065
- const val = argv[i + 1];
11066
- if (a === "--hub") {
11067
- out.hubUrl = val;
11068
- i++;
11069
- } else if (a === "--code") {
11070
- out.code = val;
11071
- i++;
11072
- } else if (a === "--data-dir") {
11073
- out.dataDir = val;
11074
- i++;
11075
- } else if (a === "--label") {
11076
- out.label = val;
11077
- i++;
11078
- } else if (a === "--host") {
11079
- out.host = val;
11080
- i++;
11081
- } else if (a === "--port") {
11082
- out.port = Number(val);
11083
- i++;
11084
- } else if (a === "-h" || a === "--help") {
11085
- out.help = true;
11086
- }
11183
+ function utcDay(iso) {
11184
+ return iso.slice(0, 10);
11185
+ }
11186
+ function monthOf(iso) {
11187
+ return iso.slice(0, 7);
11188
+ }
11189
+ function retainedMonths(now = /* @__PURE__ */ new Date()) {
11190
+ const out = /* @__PURE__ */ new Set();
11191
+ const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
11192
+ for (let i = 0; i < LEDGER_RETENTION_MONTHS; i += 1) {
11193
+ out.add(d.toISOString().slice(0, 7));
11194
+ d.setUTCMonth(d.getUTCMonth() - 1);
11087
11195
  }
11088
11196
  return out;
11089
11197
  }
11090
- function ensureNodeId(store) {
11091
- let id = store.loadNodeId();
11092
- if (!id) {
11093
- id = newId();
11094
- store.saveNodeId(id);
11095
- }
11096
- return id;
11198
+ function atomicWrite(file, content) {
11199
+ fs19.mkdirSync(path21.dirname(file), { recursive: true });
11200
+ const tmp = `${file}.tmp`;
11201
+ fs19.writeFileSync(tmp, content, "utf-8");
11202
+ fs19.renameSync(tmp, file);
11097
11203
  }
11098
- async function probeServer(host, port, adminKey) {
11099
- const ctrl = new AbortController();
11100
- const timer = setTimeout(() => ctrl.abort(), 1500);
11101
- try {
11102
- const res = await fetch(`http://${host}:${port}/api/v1/fleet/status`, {
11103
- headers: { Authorization: `Bearer ${adminKey}` },
11104
- signal: ctrl.signal
11105
- });
11106
- return res.ok;
11107
- } catch {
11108
- return false;
11109
- } finally {
11110
- clearTimeout(timer);
11204
+ var UsageLedger = class {
11205
+ constructor(root) {
11206
+ this.root = root;
11207
+ this.dir = path21.join(root, "usage");
11111
11208
  }
11112
- }
11113
- async function enrollViaServer(host, port, adminKey, opts) {
11114
- const res = await fetch(`http://${host}:${port}/api/v1/fleet/enroll`, {
11115
- method: "POST",
11116
- headers: {
11117
- "Content-Type": "application/json",
11118
- Authorization: `Bearer ${adminKey}`
11119
- },
11120
- body: JSON.stringify({ hubUrl: opts.hubUrl, code: opts.code, label: opts.label })
11121
- });
11122
- const text = await res.text();
11123
- let payload = null;
11124
- try {
11125
- payload = text ? JSON.parse(text) : null;
11126
- } catch {
11209
+ root;
11210
+ dir;
11211
+ rollup;
11212
+ pricing;
11213
+ rollupDirty = false;
11214
+ rollupTimer = null;
11215
+ inited = false;
11216
+ get ledgerDir() {
11217
+ return this.dir;
11127
11218
  }
11128
- if (!res.ok) {
11129
- throw new Error(payload?.error?.message ?? `HTTP ${res.status}`);
11219
+ ledgerFile(month) {
11220
+ return path21.join(this.ledgerDir, `ledger-${month}.jsonl`);
11130
11221
  }
11131
- return payload?.data;
11132
- }
11133
- async function linkNode(opts) {
11134
- const rawDataDir = opts.dataDir?.trim() || process.env.DATA_DIR?.trim() || void 0;
11135
- const explicitDataDir = rawDataDir ? expandHome(rawDataDir) : void 0;
11136
- const host = opts.host?.trim() || process.env.HOST?.trim() || DEFAULT_SERVE_HOST;
11137
- const port = opts.port ?? Number(process.env.PORT ?? DEFAULT_SERVE_PORT);
11138
- const running = discoverRunningServeInstances();
11139
- const candidates = buildHotCandidates(explicitDataDir, running, { host, port });
11140
- for (const inst of candidates) {
11141
- const store2 = new Store(inst.dataDir);
11142
- const adminKey = process.env.ADMIN_API_KEY ?? store2.loadAdminKey();
11143
- if (!adminKey) continue;
11144
- if (!await probeServer(inst.host, inst.port, adminKey)) continue;
11145
- const status = await enrollViaServer(inst.host, inst.port, adminKey, opts);
11146
- return { mode: "hot", ...status, dataDir: inst.dataDir };
11222
+ get rollupFile() {
11223
+ return path21.join(this.ledgerDir, "rollup.json");
11147
11224
  }
11148
- const dataDir = path21.resolve(explicitDataDir ?? DEFAULT_DATA_DIR);
11149
- const store = new Store(dataDir);
11150
- const nodeId = ensureNodeId(store);
11151
- const cred = await hubEnrollByCode(opts.hubUrl, {
11152
- code: opts.code,
11153
- nodeId,
11154
- label: opts.label
11155
- });
11156
- try {
11157
- store.saveFleetCredential({
11158
- ...cred,
11159
- label: opts.label ?? cred.label,
11160
- enrolledAt: (/* @__PURE__ */ new Date()).toISOString()
11161
- });
11162
- } catch (e) {
11163
- const err = e;
11164
- if (err.code === "EACCES" || err.code === "EPERM") {
11165
- throw new Error(
11166
- `\u65E0\u6CD5\u5199\u5165\u6570\u636E\u76EE\u5F55 ${dataDir}\uFF08${err.code}: ${err.message}\uFF09
11167
- \u8BE5\u76EE\u5F55\u5F53\u524D\u7528\u6237\u6CA1\u6709\u5199\u6743\u9650\u3002\u4E24\u79CD\u6539\u6CD5\uFF1A
11168
- 1) \u53BB\u6389 --data-dir\uFF0C\u8BA9 link \u81EA\u52A8\u53D1\u73B0\u672C\u673A\u8FD0\u884C\u7684\u8282\u70B9\uFF08\u63A8\u8350\uFF09\uFF1B
11169
- 2) \u7528\u5F53\u524D\u7528\u6237\u53EF\u5199\u7684\u76EE\u5F55\uFF0C\u672C\u5E73\u53F0\u5B89\u88C5\u811A\u672C\u9ED8\u8BA4\uFF1A${installDataDirHint()}
11170
- \uFF08\u63A5\u5165\u7801\u4E00\u6B21\u6027\uFF0C\u9700\u8981\u91CD\u65B0\u751F\u6210\uFF09`
11171
- );
11172
- }
11173
- throw e;
11225
+ get pricingFile() {
11226
+ return path21.join(this.ledgerDir, "pricing.json");
11174
11227
  }
11175
- const otherRunningDirs = [
11176
- ...new Set(running.map((r) => r.dataDir).filter((d) => d !== dataDir))
11177
- ];
11178
- return {
11179
- mode: "saved",
11180
- nodeId,
11181
- gatewayUrl: cred.gatewayUrl,
11182
- label: opts.label,
11183
- dataDir,
11184
- explicitDataDir: Boolean(explicitDataDir),
11185
- runningServiceDirs: otherRunningDirs
11186
- };
11187
- }
11188
- function printLinkHelp() {
11189
- cliLog("\u7528\u6CD5: zqagent link --hub <url> --code <\u6CE8\u518C\u7801> [--label \u540D\u79F0] [--data-dir \u76EE\u5F55]");
11190
- cliLog(" \u5728 Hub \u7528\u6237\u4E2D\u5FC3\u751F\u6210\u4E00\u6B21\u6027\u6CE8\u518C\u7801\u540E\uFF0C\u4E8E\u8282\u70B9\u673A\u5668\u6267\u884C\u672C\u547D\u4EE4\u5373\u53EF\u63A5\u5165\u3002");
11191
- cliLog(" \u5DF2\u6709\u672C\u673A server \u65F6\u70ED\u63A5\u5165\uFF08\u514D\u91CD\u542F\uFF0C\u4F1A\u81EA\u52A8\u53D1\u73B0\u8FD0\u884C\u4E2D\u670D\u52A1\u7684 --data-dir\uFF09\uFF1B");
11192
- cliLog(" \u5426\u5219\u5199\u5165 <\u6570\u636E\u76EE\u5F55>/fleet.json\uFF0C\u4E0B\u6B21\u542F\u52A8\u81EA\u52A8\u63A5\u5165\u3002");
11193
- }
11194
- async function runLinkCommand(argv) {
11195
- const args = parseLinkArgs(argv);
11196
- if (args.help) {
11197
- printLinkHelp();
11198
- return true;
11199
- }
11200
- const hubUrl = (args.hubUrl ?? process.env.HUB_URL ?? "").trim().replace(/\/$/, "");
11201
- const code = (args.code ?? "").trim();
11202
- if (!hubUrl || !code) {
11203
- cliErr("\u7F3A\u5C11 --hub \u6216 --code\u3002");
11204
- printLinkHelp();
11205
- return false;
11228
+ // ===================== 初始化 =====================
11229
+ init(now = /* @__PURE__ */ new Date()) {
11230
+ if (this.inited) return;
11231
+ fs19.mkdirSync(this.ledgerDir, { recursive: true });
11232
+ this.pricing = this.loadPricing();
11233
+ this.rollup = this.loadRollup(now);
11234
+ this.pruneLedgers(now);
11235
+ this.inited = true;
11206
11236
  }
11207
- try {
11208
- const r = await linkNode({
11209
- hubUrl,
11210
- code,
11211
- dataDir: args.dataDir,
11212
- label: args.label,
11213
- host: args.host,
11214
- port: args.port
11215
- });
11216
- if (r.mode === "hot") {
11217
- cliLog("\u2705 \u5DF2\u63A5\u5165 Hub\uFF08\u70ED\u63A5\u5165\uFF0C\u514D\u91CD\u542F\uFF09");
11218
- cliLog(` \u8282\u70B9: ${r.nodeId}${r.label ? ` (${r.label})` : ""}`);
11219
- cliLog(` \u7F51\u5173: ${r.gatewayUrl ?? "\uFF08\u672A\u4E0B\u53D1\uFF09"} \u5728\u7EBF: ${r.online ? "\u662F" : "\u8FDE\u63A5\u4E2D\u2026"}`);
11220
- cliLog(` \u6570\u636E\u76EE\u5F55: ${r.dataDir}`);
11221
- } else {
11222
- cliLog("\u2705 \u63A5\u5165\u51ED\u636E\u5DF2\u4FDD\u5B58\uFF08\u672C\u673A\u6682\u65E0\u8FD0\u884C\u4E2D\u7684 server\uFF09");
11223
- cliLog(` \u8282\u70B9: ${r.nodeId}`);
11224
- cliLog(` \u7F51\u5173: ${r.gatewayUrl ?? "\uFF08\u672A\u4E0B\u53D1\uFF09"}`);
11225
- cliLog(` \u6570\u636E\u76EE\u5F55: ${r.dataDir}`);
11226
- cliLog(" \u4E0B\u6B21\u542F\u52A8 server \u5373\u81EA\u52A8\u63A5\u5165\uFF1B\u6216\u8FD0\u884C zqagent serve \u5E38\u9A7B\u4E3A\u8282\u70B9\u3002");
11227
- if (r.runningServiceDirs.length > 0) {
11228
- cliLog("");
11229
- cliLog("\u26A0\uFE0F \u672C\u673A\u6709\u6B63\u5728\u8FD0\u884C\u7684\u8282\u70B9\uFF0C\u4F46\u6570\u636E\u76EE\u5F55\u4E0E\u672C\u6B21\u5199\u5165\u7684\u4E0D\u4E00\u81F4\uFF1A");
11230
- cliLog(` \u672C\u6B21\u5199\u5165: ${r.dataDir}`);
11231
- for (const d of r.runningServiceDirs) cliLog(` \u670D\u52A1\u4F7F\u7528: ${d}`);
11232
- cliLog(" \u6B63\u5728\u8FD0\u884C\u7684\u670D\u52A1\u8BFB\u4E0D\u5230\u8FD9\u4EFD\u51ED\u636E\uFF0CHub \u4F1A\u4E00\u76F4\u663E\u793A\u672A\u63A5\u5165\u3002");
11233
- cliLog(" \u8BF7\u786E\u8BA4\u670D\u52A1\u7684\u6570\u636E\u76EE\u5F55\u540E\u91CD\u65B0\u6267\u884C\uFF08\u63A5\u5165\u7801\u4E00\u6B21\u6027\uFF0C\u9700\u8981\u91CD\u65B0\u751F\u6210\uFF09\uFF1A");
11234
- cliLog(` zqagent link --hub <Hub\u5730\u5740> --code <\u65B0\u63A5\u5165\u7801> --data-dir "${r.runningServiceDirs[0]}"`);
11235
- } else if (!r.explicitDataDir) {
11236
- cliLog("");
11237
- cliLog("\u26A0\uFE0F \u672A\u6307\u5B9A --data-dir\uFF0C\u51ED\u636E\u5199\u5165\u4E86\u5F53\u524D\u76EE\u5F55\u4E0B\u7684 .zqagent_data\u3002");
11238
- cliLog(" \u82E5\u672C\u673A\u670D\u52A1\u662F\u7531\u5B89\u88C5\u811A\u672C\uFF08systemd / launchd\uFF09\u542F\u52A8\u7684\uFF0C\u5B83\u8BFB\u4E0D\u5230\u8FD9\u4EFD\u51ED\u636E\uFF0C");
11239
- cliLog(" Hub \u4F1A\u4E00\u76F4\u663E\u793A\u672A\u5728\u7EBF\u3002\u8BF7\u7528\u670D\u52A1\u7684\u6570\u636E\u76EE\u5F55\u91CD\u65B0\u6267\u884C\uFF1A");
11240
- cliLog(" zqagent link --hub <Hub\u5730\u5740> --code <\u65B0\u63A5\u5165\u7801> --data-dir <\u670D\u52A1\u6570\u636E\u76EE\u5F55>");
11241
- cliLog(` \uFF08\u672C\u5E73\u53F0\u5B89\u88C5\u811A\u672C\u9ED8\u8BA4\uFF1A${installDataDirHint()}\uFF1B\u63A5\u5165\u7801\u4E00\u6B21\u6027\uFF0C\u9700\u8981\u91CD\u65B0\u751F\u6210\uFF09`);
11237
+ loadPricing() {
11238
+ try {
11239
+ const raw = fs19.readFileSync(this.pricingFile, "utf-8");
11240
+ const parsed = JSON.parse(raw);
11241
+ const vendors = {};
11242
+ for (const v of /* @__PURE__ */ new Set([
11243
+ ...Object.keys(DEFAULT_PRICING.vendors),
11244
+ ...Object.keys(parsed.vendors ?? {})
11245
+ ])) {
11246
+ vendors[v] = {
11247
+ ...DEFAULT_PRICING.vendors[v] ?? {},
11248
+ ...(parsed.vendors ?? {})[v] ?? {}
11249
+ };
11250
+ }
11251
+ return {
11252
+ schema: USAGE_SCHEMA,
11253
+ base: "USD",
11254
+ rate: { ...DEFAULT_PRICING.rate, ...parsed.rate ?? {} },
11255
+ vendors,
11256
+ fallback: { ...DEFAULT_PRICING.fallback, ...parsed.fallback ?? {} }
11257
+ };
11258
+ } catch {
11259
+ try {
11260
+ atomicWrite(this.pricingFile, JSON.stringify(DEFAULT_PRICING, null, 2));
11261
+ } catch {
11242
11262
  }
11263
+ return JSON.parse(JSON.stringify(DEFAULT_PRICING));
11243
11264
  }
11244
- return true;
11245
- } catch (e) {
11246
- cliErr(`\u63A5\u5165\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
11247
- return false;
11248
11265
  }
11249
- }
11250
-
11251
- // src/cli/serve.ts
11252
- init_cjs_shims();
11253
-
11254
- // src/server/runtime.ts
11255
- init_cjs_shims();
11256
- var fs31 = __toESM(require("fs"), 1);
11257
- var path37 = __toESM(require("path"), 1);
11258
-
11259
- // src/server/session.ts
11260
- init_cjs_shims();
11261
- var path24 = __toESM(require("path"), 1);
11262
- var import_async_hooks = require("async_hooks");
11263
-
11264
- // src/server/mcpConfig.ts
11265
- init_cjs_shims();
11266
- var import_node_fs = __toESM(require("fs"), 1);
11267
- var import_node_path = __toESM(require("path"), 1);
11268
- function isValidMcpConfig(v) {
11269
- return !!v && typeof v === "object" && typeof v.name === "string" && (v.type === "stdio" || v.type === "http");
11270
- }
11271
- function readConfigFile(file) {
11272
- if (!file) return [];
11273
- try {
11274
- if (!import_node_fs.default.existsSync(file)) return [];
11275
- const raw = JSON.parse(import_node_fs.default.readFileSync(file, "utf8"));
11276
- const list = Array.isArray(raw) ? raw : raw && typeof raw === "object" && Array.isArray(raw.servers) ? raw.servers : [];
11277
- return list.filter(isValidMcpConfig);
11278
- } catch (err) {
11279
- console.error(`[mcp] \u8BFB\u53D6\u914D\u7F6E\u5931\u8D25 ${file}: ${err.message}`);
11280
- return [];
11266
+ loadRollup(now) {
11267
+ try {
11268
+ const raw = fs19.readFileSync(this.rollupFile, "utf-8");
11269
+ const parsed = JSON.parse(raw);
11270
+ return {
11271
+ schema: USAGE_SCHEMA,
11272
+ since: parsed.since ?? now.toISOString(),
11273
+ legacy: parsed.legacy,
11274
+ total: { ...zeroAgg(), ...parsed.total },
11275
+ byDay: parsed.byDay ?? {},
11276
+ byModel: parsed.byModel ?? {},
11277
+ byProject: parsed.byProject ?? {}
11278
+ };
11279
+ } catch {
11280
+ return {
11281
+ schema: USAGE_SCHEMA,
11282
+ since: now.toISOString(),
11283
+ total: zeroAgg(),
11284
+ byDay: {},
11285
+ byModel: {},
11286
+ byProject: {}
11287
+ };
11288
+ }
11281
11289
  }
11282
- }
11283
- function hasServerMcpConfig() {
11284
- if (readConfigFile(process.env.MCP_CONFIG_PATH).length > 0) return true;
11285
- const workspace = process.env.WORKSPACE;
11286
- if (!workspace) return false;
11287
- return readConfigFile(import_node_path.default.join(workspace, ".agent", "mcp.json")).length > 0;
11288
- }
11289
- function loadServerMcpConfigs(tenant) {
11290
- const fromEnv = readConfigFile(process.env.MCP_CONFIG_PATH);
11291
- if (fromEnv.length > 0) return fromEnv;
11292
- if (Array.isArray(tenant.mcpServers) && tenant.mcpServers.length > 0) {
11293
- return tenant.mcpServers.filter(isValidMcpConfig);
11290
+ /** 删除超出保留期的账本文件,并裁剪 byDay */
11291
+ pruneLedgers(now) {
11292
+ const keep = retainedMonths(now);
11293
+ let changed = false;
11294
+ try {
11295
+ for (const f of fs19.readdirSync(this.ledgerDir)) {
11296
+ const m = /^ledger-(\d{4}-\d{2})\.jsonl$/.exec(f);
11297
+ if (m && !keep.has(m[1])) {
11298
+ fs19.unlinkSync(path21.join(this.ledgerDir, f));
11299
+ }
11300
+ }
11301
+ } catch {
11302
+ }
11303
+ for (const day of Object.keys(this.rollup.byDay)) {
11304
+ if (!keep.has(day.slice(0, 7))) {
11305
+ delete this.rollup.byDay[day];
11306
+ changed = true;
11307
+ }
11308
+ }
11309
+ if (changed) this.saveRollupNow();
11294
11310
  }
11295
- const workspace = tenant.workspaceRoot ?? process.env.WORKSPACE;
11296
- if (workspace)
11297
- return readConfigFile(import_node_path.default.join(workspace, ".agent", "mcp.json"));
11298
- return [];
11299
- }
11300
-
11301
- // src/server/nodeLlm.ts
11302
- init_cjs_shims();
11303
- var GLOBAL_PROVIDER_ID = "__env__";
11304
- function envProvider() {
11305
- const apiKey = process.env.OPENAI_API_KEY;
11306
- if (!apiKey) return null;
11307
- return {
11308
- id: GLOBAL_PROVIDER_ID,
11309
- name: process.env.OPENAI_MODEL || process.env.MODEL || "\u5168\u5C40 LLM",
11310
- type: "env",
11311
- baseURL: process.env.OPENAI_BASE_URL ?? "",
11312
- model: process.env.OPENAI_MODEL || process.env.MODEL || "",
11313
- apiKey
11314
- };
11315
- }
11316
- function normReasoningEffort(v) {
11317
- return v === "low" || v === "medium" || v === "high" ? v : null;
11318
- }
11319
- function normalizeModelList(v) {
11320
- if (!Array.isArray(v)) return void 0;
11321
- const out = [];
11322
- const seen = /* @__PURE__ */ new Set();
11323
- for (const x of v) {
11324
- const s = String(x ?? "").trim();
11325
- if (!s || seen.has(s)) continue;
11326
- seen.add(s);
11327
- out.push(s);
11328
- if (out.length >= 500) break;
11311
+ // ===================== 价格 =====================
11312
+ getPricing() {
11313
+ return this.pricing;
11329
11314
  }
11330
- return out;
11331
- }
11332
- function nodeLlmView(cfg) {
11333
- const g = envProvider();
11334
- const providers = [
11335
- ...g ? [
11336
- {
11337
- id: g.id,
11338
- name: g.name,
11339
- type: g.type,
11340
- baseURL: g.baseURL,
11341
- model: g.model,
11342
- hasApiKey: true,
11343
- global: true
11315
+ /** 更新价格/汇率(整表覆盖式合并) */
11316
+ updatePricing(patch) {
11317
+ this.pricing = {
11318
+ schema: USAGE_SCHEMA,
11319
+ base: "USD",
11320
+ rate: { ...this.pricing.rate, ...patch.rate ?? {} },
11321
+ vendors: { ...this.pricing.vendors, ...patch.vendors ?? {} },
11322
+ fallback: { ...this.pricing.fallback, ...patch.fallback ?? {} }
11323
+ };
11324
+ atomicWrite(this.pricingFile, JSON.stringify(this.pricing, null, 2));
11325
+ return this.pricing;
11326
+ }
11327
+ priceFor(vendor, model) {
11328
+ const table = this.pricing.vendors[vendor];
11329
+ if (table) {
11330
+ if (table[model]) return table[model];
11331
+ for (const key of Object.keys(table)) {
11332
+ if (key.endsWith("*") && model.startsWith(key.slice(0, -1)))
11333
+ return table[key];
11344
11334
  }
11345
- ] : [],
11346
- ...cfg.providers.map((p) => ({
11347
- id: p.id,
11348
- name: p.name,
11349
- type: p.type ?? "",
11350
- baseURL: p.baseURL,
11351
- model: p.model,
11352
- hasApiKey: Boolean(p.apiKey),
11353
- models: p.models ?? [],
11354
- nonChat: p.nonChat ?? [],
11355
- modelMeta: p.modelMeta ?? [],
11356
- reasoningEffort: p.reasoningEffort ?? null,
11357
- global: false
11358
- }))
11359
- ];
11360
- return {
11361
- defaultProviderId: cfg.defaultProviderId ?? null,
11362
- providers,
11363
- /** 未显式设置默认时,是否由环境变量全局配置兜底 */
11364
- globalIsDefault: !cfg.defaultProviderId && Boolean(g)
11365
- };
11366
- }
11367
- function applyNodeLlmPatch(cfg, raw) {
11368
- const next = { providers: cfg.providers, defaultProviderId: cfg.defaultProviderId };
11369
- if (Array.isArray(raw?.providers)) {
11370
- const old = new Map(cfg.providers.map((p) => [p.id, p]));
11371
- next.providers = raw.providers.filter((x) => Boolean(x) && typeof x === "object").filter((x) => x.global !== true && String(x.id ?? "") !== GLOBAL_PROVIDER_ID).map((x) => {
11372
- const id = String(x.id ?? "").trim();
11373
- if (!id) throw new ApiError(400, "bad_request", "\u4F9B\u5E94\u5546 id \u4E0D\u80FD\u4E3A\u7A7A");
11374
- const prev = old.get(id);
11375
- const apiKey = String(x.apiKey ?? "").trim();
11376
- const models = normalizeModelList(x.models) ?? prev?.models;
11377
- const nonChat = normalizeModelList(x.nonChat) ?? prev?.nonChat;
11378
- const modelMeta = normalizeModelMeta(x.modelMeta) ?? prev?.modelMeta;
11379
- return {
11380
- id,
11381
- name: String(x.name ?? prev?.name ?? id).trim(),
11382
- type: x.type == null ? prev?.type : String(x.type),
11383
- baseURL: String(x.baseURL ?? prev?.baseURL ?? "").trim(),
11384
- model: String(x.model ?? prev?.model ?? "").trim(),
11385
- apiKey: apiKey || prev?.apiKey,
11386
- reasoningEffort: x.reasoningEffort === void 0 ? prev?.reasoningEffort ?? null : normReasoningEffort(x.reasoningEffort),
11387
- ...models ? { models } : {},
11388
- ...nonChat ? { nonChat } : {},
11389
- ...modelMeta ? { modelMeta } : {}
11390
- };
11391
- });
11392
- }
11393
- if (typeof raw?.defaultProviderId === "string") {
11394
- const id = raw.defaultProviderId.trim();
11395
- next.defaultProviderId = id || void 0;
11396
- } else if (raw?.defaultProviderId === null) {
11397
- next.defaultProviderId = void 0;
11398
- }
11399
- return next;
11400
- }
11401
- function findNodeProvider(store, id) {
11402
- if (!id) return null;
11403
- if (id === GLOBAL_PROVIDER_ID) return envProvider();
11404
- return store.loadNodeLlm().providers.find((p) => p.id === id) ?? null;
11405
- }
11406
- function nodeDefaultProvider(store) {
11407
- const cfg = store.loadNodeLlm();
11408
- if (cfg.providers.length === 0) return null;
11409
- return cfg.providers.find((p) => p.id === cfg.defaultProviderId) ?? cfg.providers[0] ?? null;
11410
- }
11411
- function resolveSessionProvider(store, tenant, providerId) {
11412
- const own = tenant.provider ?? {};
11413
- if (providerId) {
11414
- const picked = findNodeProvider(store, providerId);
11415
- if (picked) {
11416
- return {
11417
- apiKey: picked.apiKey || own.apiKey,
11418
- baseURL: picked.baseURL || own.baseURL,
11419
- model: picked.model,
11420
- providerId,
11421
- reasoningEffort: picked.reasoningEffort ?? null,
11422
- ...picked.modelMeta ? { modelMeta: picked.modelMeta } : {}
11423
- };
11424
11335
  }
11425
- }
11426
- if (own.apiKey) return { ...own, model: tenant.model };
11427
- const def = nodeDefaultProvider(store);
11428
- if (def?.apiKey) {
11429
- return {
11430
- apiKey: def.apiKey,
11431
- baseURL: own.baseURL || def.baseURL,
11432
- model: def.model || tenant.model,
11433
- reasoningEffort: def.reasoningEffort ?? null,
11434
- ...def.modelMeta ? { modelMeta: def.modelMeta } : {}
11435
- };
11436
- }
11437
- return { ...own, model: tenant.model };
11438
- }
11439
-
11440
- // src/server/usage.ts
11441
- init_cjs_shims();
11442
- var fs20 = __toESM(require("fs"), 1);
11443
- var path23 = __toESM(require("path"), 1);
11444
- var LEDGER_RETENTION_MONTHS = 12;
11445
- var USAGE_SCHEMA = 1;
11446
- var DEFAULT_PRICING = {
11447
- schema: USAGE_SCHEMA,
11448
- base: "USD",
11449
- rate: { CNY: 7.2 },
11450
- vendors: {
11451
- openai: {
11452
- "gpt-4o": { input: 2.5, output: 10, cacheRead: 1.25 },
11453
- "gpt-4o-mini": { input: 0.15, output: 0.6, cacheRead: 0.075 },
11454
- "gpt-4.1": { input: 2, output: 8, cacheRead: 0.5 }
11455
- },
11456
- deepseek: {
11457
- "deepseek-flash": {
11458
- input: 0.27,
11459
- output: 1.1,
11460
- cacheRead: 0.027,
11461
- cacheWrite: 0.27
11462
- },
11463
- "deepseek-reasoner": {
11464
- input: 0.55,
11465
- output: 2.19,
11466
- cacheRead: 0.055,
11467
- cacheWrite: 0.55
11468
- },
11469
- "deepseek-*": {
11470
- input: 0.27,
11471
- output: 1.1,
11472
- cacheRead: 0.027,
11473
- cacheWrite: 0.27
11336
+ for (const v of Object.values(this.pricing.vendors)) {
11337
+ if (v[model]) return v[model];
11338
+ }
11339
+ for (const v of Object.values(this.pricing.vendors)) {
11340
+ for (const key of Object.keys(v)) {
11341
+ if (key.endsWith("*") && model.startsWith(key.slice(0, -1)))
11342
+ return v[key];
11474
11343
  }
11475
- },
11476
- qwen: {
11477
- "qwen-plus": { input: 0.4, output: 1.2 },
11478
- "qwen-turbo": { input: 0.05, output: 0.2 }
11479
- },
11480
- zhipu: { "glm-4-plus": { input: 0.7, output: 0.7 } },
11481
- moonshot: { "moonshot-v1-8k": { input: 1.68, output: 1.68 } },
11482
- xiaomi: {},
11483
- custom: {},
11484
- env: {}
11485
- },
11486
- fallback: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
11487
- };
11488
- function zeroAgg() {
11489
- return {
11490
- calls: 0,
11491
- input: 0,
11492
- output: 0,
11493
- cacheRead: 0,
11494
- cacheWrite: 0,
11495
- costUsd: 0,
11496
- unpriced: 0
11497
- };
11498
- }
11499
- function addAgg(into, tokens, costUsd) {
11500
- into.calls += 1;
11501
- into.input += tokens.input;
11502
- into.output += tokens.output;
11503
- into.cacheRead += tokens.cacheRead;
11504
- into.cacheWrite += tokens.cacheWrite;
11505
- if (costUsd == null) into.unpriced += 1;
11506
- else into.costUsd += costUsd;
11507
- }
11508
- function utcDay(iso) {
11509
- return iso.slice(0, 10);
11510
- }
11511
- function monthOf(iso) {
11512
- return iso.slice(0, 7);
11513
- }
11514
- function retainedMonths(now = /* @__PURE__ */ new Date()) {
11515
- const out = /* @__PURE__ */ new Set();
11516
- const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
11517
- for (let i = 0; i < LEDGER_RETENTION_MONTHS; i += 1) {
11518
- out.add(d.toISOString().slice(0, 7));
11519
- d.setUTCMonth(d.getUTCMonth() - 1);
11344
+ }
11345
+ return null;
11520
11346
  }
11521
- return out;
11522
- }
11523
- function atomicWrite(file, content) {
11524
- fs20.mkdirSync(path23.dirname(file), { recursive: true });
11525
- const tmp = `${file}.tmp`;
11526
- fs20.writeFileSync(tmp, content, "utf-8");
11527
- fs20.renameSync(tmp, file);
11528
- }
11529
- var UsageLedger = class {
11530
- constructor(root) {
11531
- this.root = root;
11532
- this.dir = path23.join(root, "usage");
11347
+ /** 成本(USD):input 已含 cacheRead,故按非缓存部分计价 */
11348
+ costOf(vendor, model, t) {
11349
+ const p = this.priceFor(vendor, model);
11350
+ if (!p) return null;
11351
+ const cached3 = Math.min(t.cacheRead, t.input);
11352
+ const fresh = Math.max(0, t.input - cached3);
11353
+ const M = 1e6;
11354
+ const cost = (fresh * p.input + cached3 * (p.cacheRead ?? p.input) + t.output * p.output + t.cacheWrite * (p.cacheWrite ?? 0)) / M;
11355
+ return Number.isFinite(cost) ? cost : null;
11533
11356
  }
11534
- root;
11535
- dir;
11536
- rollup;
11537
- pricing;
11538
- rollupDirty = false;
11539
- rollupTimer = null;
11540
- inited = false;
11541
- get ledgerDir() {
11542
- return this.dir;
11357
+ convert(usd, currency) {
11358
+ if (!currency || currency === "USD") return usd;
11359
+ const r = this.pricing.rate[currency];
11360
+ return typeof r === "number" && r > 0 ? usd * r : usd;
11543
11361
  }
11544
- ledgerFile(month) {
11545
- return path23.join(this.ledgerDir, `ledger-${month}.jsonl`);
11362
+ // ===================== 记账 =====================
11363
+ /** 追加一条用量(串行 + appendFileSync;同步返回,便于在事件回调里调用) */
11364
+ record(input) {
11365
+ this.ensureInit();
11366
+ const costUsd = this.costOf(input.vendor, input.model, input);
11367
+ const rec = {
11368
+ ...input,
11369
+ id: input.id ?? `${input.sessionId}:${input.ts}:${Math.random().toString(36).slice(2, 8)}`,
11370
+ costUsd,
11371
+ priced: costUsd != null
11372
+ };
11373
+ const line = JSON.stringify(rec) + "\n";
11374
+ try {
11375
+ fs19.appendFileSync(this.ledgerFile(monthOf(rec.ts)), line);
11376
+ } catch (e) {
11377
+ console.error(`[usage] \u5199\u8D26\u672C\u5931\u8D25: ${String(e)}`);
11378
+ }
11379
+ addAgg(this.rollup.total, rec, costUsd);
11380
+ const day = this.rollup.byDay[utcDay(rec.ts)] ?? (this.rollup.byDay[utcDay(rec.ts)] = zeroAgg());
11381
+ addAgg(day, rec, costUsd);
11382
+ const mk = `${rec.vendor}/${rec.model}`;
11383
+ const bm = this.rollup.byModel[mk] ?? (this.rollup.byModel[mk] = zeroAgg());
11384
+ addAgg(bm, rec, costUsd);
11385
+ const bp = this.rollup.byProject[rec.projectId] ?? (this.rollup.byProject[rec.projectId] = zeroAgg());
11386
+ addAgg(bp, rec, costUsd);
11387
+ this.scheduleRollupSave();
11388
+ return rec;
11546
11389
  }
11547
- get rollupFile() {
11548
- return path23.join(this.ledgerDir, "rollup.json");
11390
+ /** 写入历史基线(仅一次;迁移自既有消息 usage) */
11391
+ seedLegacy(agg, throughTs) {
11392
+ this.ensureInit();
11393
+ if (this.rollup.legacy) return;
11394
+ this.rollup.legacy = { ...agg, throughTs };
11395
+ if (throughTs < this.rollup.since) this.rollup.since = throughTs;
11396
+ this.rollup.total.calls += agg.calls;
11397
+ this.rollup.total.input += agg.input;
11398
+ this.rollup.total.output += agg.output;
11399
+ this.rollup.total.cacheRead += agg.cacheRead;
11400
+ this.rollup.total.cacheWrite += agg.cacheWrite;
11401
+ this.rollup.total.costUsd += agg.costUsd;
11402
+ this.rollup.total.unpriced += agg.unpriced;
11403
+ this.saveRollupNow();
11549
11404
  }
11550
- get pricingFile() {
11551
- return path23.join(this.ledgerDir, "pricing.json");
11405
+ ensureInit() {
11406
+ if (!this.inited) this.init();
11552
11407
  }
11553
- // ===================== 初始化 =====================
11554
- init(now = /* @__PURE__ */ new Date()) {
11555
- if (this.inited) return;
11556
- fs20.mkdirSync(this.ledgerDir, { recursive: true });
11557
- this.pricing = this.loadPricing();
11558
- this.rollup = this.loadRollup(now);
11559
- this.pruneLedgers(now);
11560
- this.inited = true;
11408
+ scheduleRollupSave() {
11409
+ this.rollupDirty = true;
11410
+ if (this.rollupTimer) return;
11411
+ this.rollupTimer = setTimeout(() => {
11412
+ this.rollupTimer = null;
11413
+ if (this.rollupDirty) this.saveRollupNow();
11414
+ }, 1500);
11415
+ this.rollupTimer.unref?.();
11561
11416
  }
11562
- loadPricing() {
11563
- try {
11564
- const raw = fs20.readFileSync(this.pricingFile, "utf-8");
11565
- const parsed = JSON.parse(raw);
11566
- const vendors = {};
11567
- for (const v of /* @__PURE__ */ new Set([
11568
- ...Object.keys(DEFAULT_PRICING.vendors),
11569
- ...Object.keys(parsed.vendors ?? {})
11570
- ])) {
11571
- vendors[v] = {
11572
- ...DEFAULT_PRICING.vendors[v] ?? {},
11573
- ...(parsed.vendors ?? {})[v] ?? {}
11574
- };
11575
- }
11576
- return {
11577
- schema: USAGE_SCHEMA,
11578
- base: "USD",
11579
- rate: { ...DEFAULT_PRICING.rate, ...parsed.rate ?? {} },
11580
- vendors,
11581
- fallback: { ...DEFAULT_PRICING.fallback, ...parsed.fallback ?? {} }
11582
- };
11583
- } catch {
11584
- try {
11585
- atomicWrite(this.pricingFile, JSON.stringify(DEFAULT_PRICING, null, 2));
11586
- } catch {
11587
- }
11588
- return JSON.parse(JSON.stringify(DEFAULT_PRICING));
11589
- }
11590
- }
11591
- loadRollup(now) {
11592
- try {
11593
- const raw = fs20.readFileSync(this.rollupFile, "utf-8");
11594
- const parsed = JSON.parse(raw);
11595
- return {
11596
- schema: USAGE_SCHEMA,
11597
- since: parsed.since ?? now.toISOString(),
11598
- legacy: parsed.legacy,
11599
- total: { ...zeroAgg(), ...parsed.total },
11600
- byDay: parsed.byDay ?? {},
11601
- byModel: parsed.byModel ?? {},
11602
- byProject: parsed.byProject ?? {}
11603
- };
11604
- } catch {
11605
- return {
11606
- schema: USAGE_SCHEMA,
11607
- since: now.toISOString(),
11608
- total: zeroAgg(),
11609
- byDay: {},
11610
- byModel: {},
11611
- byProject: {}
11612
- };
11613
- }
11614
- }
11615
- /** 删除超出保留期的账本文件,并裁剪 byDay */
11616
- pruneLedgers(now) {
11617
- const keep = retainedMonths(now);
11618
- let changed = false;
11619
- try {
11620
- for (const f of fs20.readdirSync(this.ledgerDir)) {
11621
- const m = /^ledger-(\d{4}-\d{2})\.jsonl$/.exec(f);
11622
- if (m && !keep.has(m[1])) {
11623
- fs20.unlinkSync(path23.join(this.ledgerDir, f));
11624
- }
11625
- }
11626
- } catch {
11627
- }
11628
- for (const day of Object.keys(this.rollup.byDay)) {
11629
- if (!keep.has(day.slice(0, 7))) {
11630
- delete this.rollup.byDay[day];
11631
- changed = true;
11632
- }
11633
- }
11634
- if (changed) this.saveRollupNow();
11635
- }
11636
- // ===================== 价格 =====================
11637
- getPricing() {
11638
- return this.pricing;
11639
- }
11640
- /** 更新价格/汇率(整表覆盖式合并) */
11641
- updatePricing(patch) {
11642
- this.pricing = {
11643
- schema: USAGE_SCHEMA,
11644
- base: "USD",
11645
- rate: { ...this.pricing.rate, ...patch.rate ?? {} },
11646
- vendors: { ...this.pricing.vendors, ...patch.vendors ?? {} },
11647
- fallback: { ...this.pricing.fallback, ...patch.fallback ?? {} }
11648
- };
11649
- atomicWrite(this.pricingFile, JSON.stringify(this.pricing, null, 2));
11650
- return this.pricing;
11651
- }
11652
- priceFor(vendor, model) {
11653
- const table = this.pricing.vendors[vendor];
11654
- if (table) {
11655
- if (table[model]) return table[model];
11656
- for (const key of Object.keys(table)) {
11657
- if (key.endsWith("*") && model.startsWith(key.slice(0, -1)))
11658
- return table[key];
11659
- }
11660
- }
11661
- for (const v of Object.values(this.pricing.vendors)) {
11662
- if (v[model]) return v[model];
11663
- }
11664
- for (const v of Object.values(this.pricing.vendors)) {
11665
- for (const key of Object.keys(v)) {
11666
- if (key.endsWith("*") && model.startsWith(key.slice(0, -1)))
11667
- return v[key];
11668
- }
11669
- }
11670
- return null;
11671
- }
11672
- /** 成本(USD):input 已含 cacheRead,故按非缓存部分计价 */
11673
- costOf(vendor, model, t) {
11674
- const p = this.priceFor(vendor, model);
11675
- if (!p) return null;
11676
- const cached3 = Math.min(t.cacheRead, t.input);
11677
- const fresh = Math.max(0, t.input - cached3);
11678
- const M = 1e6;
11679
- const cost = (fresh * p.input + cached3 * (p.cacheRead ?? p.input) + t.output * p.output + t.cacheWrite * (p.cacheWrite ?? 0)) / M;
11680
- return Number.isFinite(cost) ? cost : null;
11681
- }
11682
- convert(usd, currency) {
11683
- if (!currency || currency === "USD") return usd;
11684
- const r = this.pricing.rate[currency];
11685
- return typeof r === "number" && r > 0 ? usd * r : usd;
11686
- }
11687
- // ===================== 记账 =====================
11688
- /** 追加一条用量(串行 + appendFileSync;同步返回,便于在事件回调里调用) */
11689
- record(input) {
11690
- this.ensureInit();
11691
- const costUsd = this.costOf(input.vendor, input.model, input);
11692
- const rec = {
11693
- ...input,
11694
- id: input.id ?? `${input.sessionId}:${input.ts}:${Math.random().toString(36).slice(2, 8)}`,
11695
- costUsd,
11696
- priced: costUsd != null
11697
- };
11698
- const line = JSON.stringify(rec) + "\n";
11699
- try {
11700
- fs20.appendFileSync(this.ledgerFile(monthOf(rec.ts)), line);
11701
- } catch (e) {
11702
- console.error(`[usage] \u5199\u8D26\u672C\u5931\u8D25: ${String(e)}`);
11703
- }
11704
- addAgg(this.rollup.total, rec, costUsd);
11705
- const day = this.rollup.byDay[utcDay(rec.ts)] ?? (this.rollup.byDay[utcDay(rec.ts)] = zeroAgg());
11706
- addAgg(day, rec, costUsd);
11707
- const mk = `${rec.vendor}/${rec.model}`;
11708
- const bm = this.rollup.byModel[mk] ?? (this.rollup.byModel[mk] = zeroAgg());
11709
- addAgg(bm, rec, costUsd);
11710
- const bp = this.rollup.byProject[rec.projectId] ?? (this.rollup.byProject[rec.projectId] = zeroAgg());
11711
- addAgg(bp, rec, costUsd);
11712
- this.scheduleRollupSave();
11713
- return rec;
11714
- }
11715
- /** 写入历史基线(仅一次;迁移自既有消息 usage) */
11716
- seedLegacy(agg, throughTs) {
11717
- this.ensureInit();
11718
- if (this.rollup.legacy) return;
11719
- this.rollup.legacy = { ...agg, throughTs };
11720
- if (throughTs < this.rollup.since) this.rollup.since = throughTs;
11721
- this.rollup.total.calls += agg.calls;
11722
- this.rollup.total.input += agg.input;
11723
- this.rollup.total.output += agg.output;
11724
- this.rollup.total.cacheRead += agg.cacheRead;
11725
- this.rollup.total.cacheWrite += agg.cacheWrite;
11726
- this.rollup.total.costUsd += agg.costUsd;
11727
- this.rollup.total.unpriced += agg.unpriced;
11728
- this.saveRollupNow();
11729
- }
11730
- ensureInit() {
11731
- if (!this.inited) this.init();
11732
- }
11733
- scheduleRollupSave() {
11734
- this.rollupDirty = true;
11735
- if (this.rollupTimer) return;
11736
- this.rollupTimer = setTimeout(() => {
11737
- this.rollupTimer = null;
11738
- if (this.rollupDirty) this.saveRollupNow();
11739
- }, 1500);
11740
- this.rollupTimer.unref?.();
11741
- }
11742
- saveRollupNow() {
11743
- this.rollupDirty = false;
11417
+ saveRollupNow() {
11418
+ this.rollupDirty = false;
11744
11419
  try {
11745
11420
  atomicWrite(this.rollupFile, JSON.stringify(this.rollup, null, 2));
11746
11421
  } catch (e) {
@@ -11869,10 +11544,10 @@ var UsageLedger = class {
11869
11544
  const rows = [];
11870
11545
  for (const month of months) {
11871
11546
  const file = this.ledgerFile(month);
11872
- if (!fs20.existsSync(file)) continue;
11547
+ if (!fs19.existsSync(file)) continue;
11873
11548
  let content = "";
11874
11549
  try {
11875
- content = fs20.readFileSync(file, "utf-8");
11550
+ content = fs19.readFileSync(file, "utf-8");
11876
11551
  } catch {
11877
11552
  continue;
11878
11553
  }
@@ -11981,10 +11656,10 @@ var UsageLedger = class {
11981
11656
  const seen = /* @__PURE__ */ new Set();
11982
11657
  for (const month of this.monthsInRange("", "")) {
11983
11658
  const file = this.ledgerFile(month);
11984
- if (!fs20.existsSync(file)) continue;
11659
+ if (!fs19.existsSync(file)) continue;
11985
11660
  let content = "";
11986
11661
  try {
11987
- content = fs20.readFileSync(file, "utf-8");
11662
+ content = fs19.readFileSync(file, "utf-8");
11988
11663
  } catch {
11989
11664
  continue;
11990
11665
  }
@@ -13295,7 +12970,7 @@ data: ${JSON.stringify(entry.data)}
13295
12970
  softThresholdPct: parseRatio(process.env.COMPACTION_SOFT_THRESHOLD_PCT),
13296
12971
  capTokens: parseOptionalPositiveNumber(process.env.COMPACTION_CAP_TOKENS) ?? null,
13297
12972
  // 超预算工具结果按租户/工作区隔离落盘(多租户共用一个状态目录会混内容)
13298
- toolResultDir: path24.join(workspace, ".agent", "tool-results")
12973
+ toolResultDir: path22.join(workspace, ".agent", "tool-results")
13299
12974
  });
13300
12975
  engine.setApprover(this._makeApprover(rt));
13301
12976
  const mcpConfigs = loadServerMcpConfigs(tenant);
@@ -13469,132 +13144,912 @@ var ApiError = class extends Error {
13469
13144
  this.status = status;
13470
13145
  this.code = code;
13471
13146
  }
13472
- status;
13473
- code;
13474
- };
13475
-
13476
- // src/server/routes.ts
13477
- init_cjs_shims();
13478
- var import_http = require("http");
13479
- var path29 = __toESM(require("path"), 1);
13147
+ status;
13148
+ code;
13149
+ };
13150
+
13151
+ // src/server/projects.ts
13152
+ var DEFAULT_MODEL = () => process.env.OPENAI_MODEL || process.env.MODEL || "gpt-4o";
13153
+ function toProjectSummary(store, tenant) {
13154
+ return {
13155
+ id: tenant.id,
13156
+ name: tenant.name ?? tenant.id,
13157
+ model: tenant.model,
13158
+ baseURL: tenant.provider?.baseURL ?? "",
13159
+ hasApiKey: Boolean(tenant.provider?.apiKey),
13160
+ workspaceRoot: tenant.workspaceRoot ?? null,
13161
+ sessionCount: store.listSessions(tenant.id).length
13162
+ };
13163
+ }
13164
+ function providerViews(tenant) {
13165
+ return {
13166
+ activeProviderId: tenant.activeProviderId ?? null,
13167
+ providers: (tenant.llmProviders ?? []).map((p) => ({
13168
+ id: p.id,
13169
+ name: p.name,
13170
+ type: p.type ?? "",
13171
+ baseURL: p.baseURL,
13172
+ model: p.model,
13173
+ models: p.models ?? [],
13174
+ modelMeta: p.modelMeta ?? [],
13175
+ hasApiKey: Boolean(p.apiKey)
13176
+ }))
13177
+ };
13178
+ }
13179
+ function applyProvidersPatch(tenant, raw) {
13180
+ if (Array.isArray(raw.providers)) {
13181
+ const existing = new Map((tenant.llmProviders ?? []).map((p) => [p.id, p]));
13182
+ const next = [];
13183
+ for (const item of raw.providers) {
13184
+ if (!item || typeof item !== "object") continue;
13185
+ const o = item;
13186
+ const id = String(o.id ?? "").trim() || newId();
13187
+ const prev = existing.get(id);
13188
+ const apiKey = typeof o.apiKey === "string" && o.apiKey.trim() ? o.apiKey.trim() : prev?.apiKey;
13189
+ const models = normalizeModelList(o.models) ?? prev?.models;
13190
+ const modelMeta = normalizeModelMeta(o.modelMeta) ?? prev?.modelMeta;
13191
+ next.push({
13192
+ id,
13193
+ name: String(o.name ?? "").trim() || id,
13194
+ type: typeof o.type === "string" ? o.type : prev?.type,
13195
+ baseURL: String(o.baseURL ?? "").trim(),
13196
+ model: String(o.model ?? "").trim(),
13197
+ ...apiKey ? { apiKey } : {},
13198
+ ...models ? { models } : {},
13199
+ ...modelMeta ? { modelMeta } : {}
13200
+ });
13201
+ }
13202
+ tenant.llmProviders = next;
13203
+ }
13204
+ if (typeof raw.activeProviderId === "string") {
13205
+ const activeId = raw.activeProviderId.trim();
13206
+ tenant.activeProviderId = activeId || void 0;
13207
+ const entry = (tenant.llmProviders ?? []).find((p) => p.id === activeId);
13208
+ if (entry) {
13209
+ tenant.provider = tenant.provider ?? {};
13210
+ tenant.provider.baseURL = entry.baseURL || void 0;
13211
+ if (entry.apiKey) tenant.provider.apiKey = entry.apiKey;
13212
+ if (entry.model) tenant.model = entry.model;
13213
+ }
13214
+ }
13215
+ }
13216
+ function allProjectSummaries(store) {
13217
+ return store.listTenants().map((id) => store.loadTenant(id)).filter((t) => t !== null).map((t) => toProjectSummary(store, t));
13218
+ }
13219
+ function lastUserMessage(rec) {
13220
+ const sent = (rec.lastInput ?? "").trim();
13221
+ if (sent) {
13222
+ return sent.length > LAST_USER_MESSAGE_MAX ? `${sent.slice(0, LAST_USER_MESSAGE_MAX)}\u2026` : sent;
13223
+ }
13224
+ const msgs = rec.messages;
13225
+ for (let i = msgs.length - 1; i >= 0; i--) {
13226
+ const m = msgs[i];
13227
+ if (!m || m.role !== "user" || typeof m.content !== "string") continue;
13228
+ const text = m.content.trim();
13229
+ if (!text) continue;
13230
+ return text.length > LAST_USER_MESSAGE_MAX ? `${text.slice(0, LAST_USER_MESSAGE_MAX)}\u2026` : text;
13231
+ }
13232
+ return null;
13233
+ }
13234
+ function lastCompletedAt(rec) {
13235
+ if (rec.lastCompletedAt) return rec.lastCompletedAt;
13236
+ if (rec.status !== "completed") return null;
13237
+ const msgs = rec.messages;
13238
+ for (let i = msgs.length - 1; i >= 0; i--) {
13239
+ const m = msgs[i];
13240
+ if (!m || m.role !== "assistant") continue;
13241
+ if (typeof m.ts === "number" && Number.isFinite(m.ts)) {
13242
+ return new Date(m.ts).toISOString();
13243
+ }
13244
+ return null;
13245
+ }
13246
+ return null;
13247
+ }
13248
+ function allSessionStates(store, manager) {
13249
+ const out = [];
13250
+ for (const id of store.listTenants()) {
13251
+ const t = store.loadTenant(id);
13252
+ if (!t) continue;
13253
+ try {
13254
+ for (const s of manager.listSessions(t.id)) {
13255
+ out.push({
13256
+ projectId: t.id,
13257
+ projectName: t.name ?? t.id,
13258
+ sessionId: s.sessionId,
13259
+ name: s.name ?? null,
13260
+ status: s.status,
13261
+ lastUserMessage: lastUserMessage(s),
13262
+ lastCompletedAt: lastCompletedAt(s)
13263
+ });
13264
+ }
13265
+ } catch {
13266
+ }
13267
+ }
13268
+ return out;
13269
+ }
13270
+ function ensureNodeId(store) {
13271
+ let id = store.loadNodeId();
13272
+ if (!id) {
13273
+ id = newId();
13274
+ store.saveNodeId(id);
13275
+ }
13276
+ return id;
13277
+ }
13278
+ function createProject(store, input) {
13279
+ const raw = input ?? {};
13280
+ const id = String(raw.id ?? "").trim() || newId();
13281
+ if (store.loadTenant(id)) {
13282
+ throw new ApiError(409, "project_exists", `\u9879\u76EE ${id} \u5DF2\u5B58\u5728`);
13283
+ }
13284
+ const workspaceRoot = typeof raw.workspaceRoot === "string" && raw.workspaceRoot.trim() ? path23.resolve(raw.workspaceRoot.trim()) : void 0;
13285
+ const explicitName = typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : "";
13286
+ const defaultName = workspaceRoot ? path23.basename(workspaceRoot) : id;
13287
+ const tenant = {
13288
+ id,
13289
+ name: explicitName || defaultName,
13290
+ apiKey: generateApiKey(),
13291
+ model: typeof raw.model === "string" && raw.model.trim() ? raw.model.trim() : DEFAULT_MODEL(),
13292
+ provider: {
13293
+ apiKey: typeof raw.providerApiKey === "string" && raw.providerApiKey.trim() ? raw.providerApiKey.trim() : void 0,
13294
+ baseURL: typeof raw.baseURL === "string" && raw.baseURL.trim() ? raw.baseURL.trim() : void 0
13295
+ },
13296
+ workspaceRoot,
13297
+ allowedCommands: Array.isArray(raw.allowedCommands) ? raw.allowedCommands.map((c) => String(c)) : []
13298
+ };
13299
+ const def = nodeDefaultProvider(store);
13300
+ if (def) {
13301
+ if (!tenant.provider?.apiKey && def.apiKey) tenant.provider = { ...tenant.provider, apiKey: def.apiKey };
13302
+ if (!tenant.provider?.baseURL && def.baseURL) tenant.provider = { ...tenant.provider, baseURL: def.baseURL };
13303
+ if (!(typeof raw.model === "string" && raw.model.trim()) && def.model) tenant.model = def.model;
13304
+ const nodeCfg = store.loadNodeLlm();
13305
+ tenant.llmProviders = nodeCfg.providers.map((p) => ({ ...p }));
13306
+ tenant.activeProviderId = nodeCfg.defaultProviderId ?? nodeCfg.providers[0]?.id;
13307
+ }
13308
+ store.saveTenant(tenant);
13309
+ return tenant;
13310
+ }
13311
+ function buildNodeInfo(store, opts) {
13312
+ return {
13313
+ nodeId: opts.nodeId ?? ensureNodeId(store),
13314
+ platform: process.platform,
13315
+ mode: process.env.NODE_MODE ?? "standalone",
13316
+ version: "0.2.12",
13317
+ admin: opts.admin,
13318
+ projects: opts.projects
13319
+ };
13320
+ }
13321
+
13322
+ // src/server/fleet-config.ts
13323
+ init_cjs_shims();
13324
+ function fleetConfigFromEnv(store) {
13325
+ const fleetToken = process.env.FLEET_TOKEN;
13326
+ const fleetSecret = process.env.FLEET_SECRET;
13327
+ if (!fleetToken || !fleetSecret) return null;
13328
+ return {
13329
+ hubUrl: process.env.HUB_URL,
13330
+ fleetToken,
13331
+ fleetSecret,
13332
+ nodeId: ensureNodeId(store),
13333
+ gatewayUrl: process.env.FLEET_GATEWAY_URL
13334
+ };
13335
+ }
13336
+ function resolveFleetConfig(store) {
13337
+ const saved = store.loadFleetCredential();
13338
+ if (saved?.fleetToken && saved.fleetSecret) {
13339
+ return { ...saved, nodeId: saved.nodeId || ensureNodeId(store) };
13340
+ }
13341
+ return fleetConfigFromEnv(store);
13342
+ }
13343
+
13344
+ // src/cli/account-enroll.ts
13345
+ var SKIP_ANSWERS = /* @__PURE__ */ new Set(["-", "skip", "none", "no"]);
13346
+ function resolveDefaultHub(hubUrl, env3 = process.env) {
13347
+ return (hubUrl ?? env3.HUB_URL ?? DEFAULT_HUB_URL).trim().replace(/\/$/, "");
13348
+ }
13349
+ function parseHubAnswer(answer, fallback) {
13350
+ const a = answer.trim();
13351
+ if (SKIP_ANSWERS.has(a.toLowerCase())) return null;
13352
+ return (a || fallback).replace(/\/$/, "");
13353
+ }
13354
+ async function promptHubUrl(rl2, hubUrl) {
13355
+ const fallback = resolveDefaultHub(hubUrl);
13356
+ const answer = await rl2.question(`Hub \u5730\u5740 [${fallback}]\uFF1A`);
13357
+ return parseHubAnswer(answer, fallback);
13358
+ }
13359
+ async function promptAccountLogin(rl2, hubUrl) {
13360
+ cliLog("\u767B\u5F55\u65B9\u5F0F\uFF1A1) \u90AE\u7BB1 + \u5BC6\u7801 2) \u624B\u673A\u53F7 + \u5BC6\u7801 3) \u624B\u673A\u53F7 + \u77ED\u4FE1\u9A8C\u8BC1\u7801");
13361
+ const mode = (await rl2.question("\u8BF7\u9009\u62E9 [1]\uFF1A")).trim() || "1";
13362
+ const input = {};
13363
+ if (mode === "1") {
13364
+ input.email = (await rl2.question("\u90AE\u7BB1\uFF1A")).trim();
13365
+ input.password = await askHidden(rl2, "\u5BC6\u7801\uFF1A");
13366
+ if (!input.email || !input.password) {
13367
+ cliErr("\u90AE\u7BB1\u4E0E\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
13368
+ return null;
13369
+ }
13370
+ } else if (mode === "2") {
13371
+ input.phone = (await rl2.question("\u624B\u673A\u53F7\uFF1A")).trim();
13372
+ input.password = await askHidden(rl2, "\u5BC6\u7801\uFF1A");
13373
+ if (!input.phone || !input.password) {
13374
+ cliErr("\u624B\u673A\u53F7\u4E0E\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
13375
+ return null;
13376
+ }
13377
+ } else if (mode === "3") {
13378
+ input.phone = (await rl2.question("\u624B\u673A\u53F7\uFF1A")).trim();
13379
+ if (!input.phone) {
13380
+ cliErr("\u624B\u673A\u53F7\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
13381
+ return null;
13382
+ }
13383
+ try {
13384
+ const sent = await hubSendSms(resolveDefaultHub(hubUrl), {
13385
+ phone: input.phone,
13386
+ purpose: "login"
13387
+ });
13388
+ if (sent.debugCode) cliLog(`\uFF08\u8C03\u8BD5\u6A21\u5F0F\u9A8C\u8BC1\u7801\uFF1A${sent.debugCode}\uFF09`);
13389
+ else cliLog("\u9A8C\u8BC1\u7801\u5DF2\u53D1\u9001\uFF0C5 \u5206\u949F\u5185\u6709\u6548\u3002");
13390
+ } catch (e) {
13391
+ cliErr(`\u77ED\u4FE1\u9A8C\u8BC1\u7801\u53D1\u9001\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
13392
+ cliLog(" \u53EF\u6539\u7528\u65B9\u5F0F 1/2\uFF08\u5BC6\u7801\u767B\u5F55\uFF09\uFF0C\u6216\u5728\u7F51\u9875\u7AEF\u751F\u6210\u63A5\u5165\u7801\u540E\u6267\u884C\uFF1A");
13393
+ cliLog(" zqagent link --hub <Hub\u5730\u5740> --code <\u63A5\u5165\u7801>");
13394
+ return null;
13395
+ }
13396
+ input.code = (await rl2.question("\u77ED\u4FE1\u9A8C\u8BC1\u7801\uFF1A")).trim();
13397
+ if (!input.code) {
13398
+ cliErr("\u9A8C\u8BC1\u7801\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
13399
+ return null;
13400
+ }
13401
+ } else {
13402
+ cliErr("\u672A\u77E5\u7684\u767B\u5F55\u65B9\u5F0F\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
13403
+ return null;
13404
+ }
13405
+ return input;
13406
+ }
13407
+ async function promptLinkAccount(rl2, hubUrl, opts = {}) {
13408
+ const fallback = resolveDefaultHub(hubUrl);
13409
+ let resolved = fallback;
13410
+ if (opts.askHub !== false) {
13411
+ const answer = await promptHubUrl(rl2, fallback);
13412
+ if (!answer) {
13413
+ cliLog("\u5DF2\u8DF3\u8FC7\u63A5\u5165\uFF1A\u8282\u70B9\u4FDD\u6301\u7EAF\u672C\u5730\u8FD0\u884C\u3002");
13414
+ return null;
13415
+ }
13416
+ resolved = answer;
13417
+ }
13418
+ const auth = await promptAccountLogin(rl2, resolved);
13419
+ if (!auth) return null;
13420
+ return { hubUrl: resolved, auth };
13421
+ }
13422
+ async function promptPassword(rl2, prompt = "\u5BC6\u7801\uFF1A") {
13423
+ return askHidden(rl2, prompt);
13424
+ }
13425
+ function resolveDataDir(dataDir) {
13426
+ return path24.resolve(dataDir ?? process.env.DATA_DIR ?? DEFAULT_DATA_DIR);
13427
+ }
13428
+ async function maybeAccountEnroll(opts = {}) {
13429
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return;
13430
+ let store;
13431
+ try {
13432
+ store = new Store(resolveDataDir(opts.dataDir));
13433
+ if (resolveFleetConfig(store)) return;
13434
+ } catch (e) {
13435
+ cliErr(`\u63A5\u5165\u68C0\u67E5\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
13436
+ return;
13437
+ }
13438
+ const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
13439
+ try {
13440
+ await runWizard(rl2, store, opts);
13441
+ } catch (e) {
13442
+ cliErr(`\u63A5\u5165\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
13443
+ cliLog(" \u53EF\u7A0D\u540E\u5728\u63A7\u5236\u53F0\u751F\u6210\u63A5\u5165\u7801\uFF0C\u518D\u6267\u884C\uFF1Azqagent link --hub <Hub\u5730\u5740> --code <\u63A5\u5165\u7801>");
13444
+ } finally {
13445
+ rl2.close();
13446
+ }
13447
+ }
13448
+ async function runWizard(rl2, store, opts) {
13449
+ cliLog("");
13450
+ cliLog("\u672C\u673A\u5C1A\u672A\u63A5\u5165 Hub\u3002\u53EF\u7528 Hub \u8D26\u53F7\u63A5\u5165\uFF08\u8282\u70B9\u9ED8\u8BA4\u8FDB\u5165\u300C\u672A\u5206\u7EC4\u300D\uFF09\uFF1B\u8F93\u5165 - \u8DF3\u8FC7\u4FDD\u6301\u7EAF\u672C\u5730\u3002");
13451
+ const prompted = await promptLinkAccount(rl2, opts.hubUrl);
13452
+ if (!prompted) return;
13453
+ const nodeId = ensureNodeId(store);
13454
+ const cred = await hubEnrollByAccount(prompted.hubUrl, {
13455
+ ...prompted.auth,
13456
+ nodeId,
13457
+ label: opts.label
13458
+ });
13459
+ store.saveFleetCredential({
13460
+ ...cred,
13461
+ label: opts.label ?? cred.label,
13462
+ enrolledAt: (/* @__PURE__ */ new Date()).toISOString()
13463
+ });
13464
+ cliLog(`\u2705 \u5DF2\u63A5\u5165 Hub\uFF1A\u8282\u70B9 ${cred.nodeId}\uFF08\u7F51\u5173 ${cred.gatewayUrl ?? "\u672A\u4E0B\u53D1"}\uFF09`);
13465
+ cliLog(" \u51ED\u636E\u5DF2\u4FDD\u5B58\u5230 fleet.json\uFF0C\u672C\u6B21\u542F\u52A8\u5373\u8FDE\u63A5 Hub\u3002");
13466
+ }
13467
+ async function askHidden(rl2, prompt) {
13468
+ if (!process.stdin.isTTY) return (await rl2.question(prompt)).trim();
13469
+ return new Promise((resolve24) => {
13470
+ const saved = process.stdin.listeners("data");
13471
+ process.stdin.removeAllListeners("data");
13472
+ cliWrite(prompt);
13473
+ rl2.pause();
13474
+ process.stdin.setRawMode(true);
13475
+ process.stdin.resume();
13476
+ let buf = "";
13477
+ const cleanup = () => {
13478
+ process.stdin.removeListener("data", onData);
13479
+ for (const listener of saved) {
13480
+ process.stdin.on("data", listener);
13481
+ }
13482
+ process.stdin.setRawMode(true);
13483
+ rl2.resume();
13484
+ };
13485
+ const onData = (chunk) => {
13486
+ const s = chunk.toString().replace(/\x1b\[[0-9;?]*[A-Za-z~]/g, "").replace(/\x1bO[A-Za-z]/g, "");
13487
+ for (const ch of s) {
13488
+ if (ch === "\r" || ch === "\n") {
13489
+ cleanup();
13490
+ cliWrite("\n");
13491
+ resolve24(buf);
13492
+ return;
13493
+ }
13494
+ if (ch === "") {
13495
+ cleanup();
13496
+ cliWrite("\n");
13497
+ resolve24("");
13498
+ return;
13499
+ }
13500
+ if (ch === "\x7F" || ch === "\b") {
13501
+ if (buf.length > 0) {
13502
+ buf = buf.slice(0, -1);
13503
+ cliWrite("\b \b");
13504
+ }
13505
+ } else if (ch >= " ") {
13506
+ buf += ch;
13507
+ cliWrite("*");
13508
+ }
13509
+ }
13510
+ };
13511
+ process.stdin.on("data", onData);
13512
+ });
13513
+ }
13514
+
13515
+ // src/cli/serve-instances.ts
13516
+ init_cjs_shims();
13517
+ var import_node_child_process5 = require("child_process");
13518
+ var fs21 = __toESM(require("fs"), 1);
13519
+ var os5 = __toESM(require("os"), 1);
13520
+ var path26 = __toESM(require("path"), 1);
13521
+
13522
+ // src/update/platform.ts
13523
+ init_cjs_shims();
13524
+ var fs20 = __toESM(require("fs"), 1);
13525
+ var os4 = __toESM(require("os"), 1);
13526
+ var path25 = __toESM(require("path"), 1);
13527
+ var import_node_child_process4 = require("child_process");
13528
+ var import_node_module3 = require("module");
13529
+ function currentVersion() {
13530
+ const injected = "0.2.12"?.trim();
13531
+ if (injected) return injected;
13532
+ try {
13533
+ const req2 = (0, import_node_module3.createRequire)(importMetaUrl);
13534
+ const pkg = req2("../../package.json");
13535
+ if (pkg?.version) return pkg.version;
13536
+ } catch {
13537
+ }
13538
+ return "dev";
13539
+ }
13540
+ function isSeaRuntime() {
13541
+ if (process.env.ZQAGENT_SEA === "1") return true;
13542
+ const base = path25.basename(process.execPath).toLowerCase().replace(/\.exe$/, "");
13543
+ return base !== "node" && base !== "nodejs";
13544
+ }
13545
+ var DEFAULT_SERVICE_NAME = "zqagent";
13546
+ var DEFAULT_SERVICE_LABEL = "com.zqagent.node";
13547
+ function serviceManagerFromCgroup(cgroup, serviceName = DEFAULT_SERVICE_NAME) {
13548
+ const unitName = `${serviceName}.service`;
13549
+ for (const line of cgroup.split("\n")) {
13550
+ const idx = line.lastIndexOf(":");
13551
+ if (idx < 0) continue;
13552
+ const unitPath = line.slice(idx + 1).trim();
13553
+ if (unitPath.split("/").includes(unitName)) return "systemd";
13554
+ }
13555
+ return null;
13556
+ }
13557
+ function launchdDomain() {
13558
+ const uid = typeof process.getuid === "function" ? process.getuid() : -1;
13559
+ return uid === 0 ? "system" : `gui/${uid}`;
13560
+ }
13561
+ function launchdLabel() {
13562
+ return process.env.ZQAGENT_SERVICE_LABEL?.trim() || DEFAULT_SERVICE_LABEL;
13563
+ }
13564
+ function launchdPlistPath() {
13565
+ const label = launchdLabel();
13566
+ const uid = typeof process.getuid === "function" ? process.getuid() : -1;
13567
+ if (uid === 0) return `/Library/LaunchDaemons/${label}.plist`;
13568
+ return path25.join(os4.homedir(), "Library", "LaunchAgents", `${label}.plist`);
13569
+ }
13570
+ function serviceManagerFromLaunchctl(output, pid, execPath, sea = true) {
13571
+ const pidMatch = /^\s*pid = (\d+)\s*$/m.exec(output);
13572
+ if (pidMatch && Number(pidMatch[1]) === pid) return "launchd";
13573
+ const program = /^\s*program = (.+?)\s*$/m.exec(output);
13574
+ if (program) {
13575
+ const shown = program[1].replace(/^"|"$/g, "");
13576
+ if (path25.resolve(shown) === path25.resolve(execPath)) return "launchd";
13577
+ }
13578
+ if (!sea && /^\s*state = /m.test(output)) return "launchd";
13579
+ return null;
13580
+ }
13581
+ function parseLaunchdEnvironment(xml) {
13582
+ const out = {};
13583
+ const marker = xml.indexOf("<key>EnvironmentVariables</key>");
13584
+ if (marker < 0) return out;
13585
+ const open = xml.indexOf("<dict>", marker);
13586
+ if (open < 0) return out;
13587
+ const close = xml.indexOf("</dict>", open);
13588
+ if (close < 0) return out;
13589
+ const body = xml.slice(open + "<dict>".length, close);
13590
+ for (const m of body.matchAll(/<key>([^<]+)<\/key>\s*<string>([^<]*)<\/string>/g)) {
13591
+ out[m[1]] = m[2];
13592
+ }
13593
+ return out;
13594
+ }
13595
+ function detectServiceManager() {
13596
+ if (process.platform === "darwin") return detectLaunchd();
13597
+ if (process.platform !== "linux") return null;
13598
+ const serviceName = process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;
13599
+ try {
13600
+ return serviceManagerFromCgroup(
13601
+ fs20.readFileSync("/proc/self/cgroup", "utf8"),
13602
+ serviceName
13603
+ );
13604
+ } catch {
13605
+ return null;
13606
+ }
13607
+ }
13608
+ function detectLaunchd() {
13609
+ try {
13610
+ const res = (0, import_node_child_process4.spawnSync)(
13611
+ "launchctl",
13612
+ ["print", `${launchdDomain()}/${launchdLabel()}`],
13613
+ { encoding: "utf8", timeout: 3e3 }
13614
+ );
13615
+ if (res.status !== 0 || !res.stdout) return null;
13616
+ return serviceManagerFromLaunchctl(
13617
+ res.stdout,
13618
+ process.pid,
13619
+ process.execPath,
13620
+ isSeaRuntime()
13621
+ );
13622
+ } catch {
13623
+ return null;
13624
+ }
13625
+ }
13626
+ function serviceManager() {
13627
+ const v = process.env.ZQAGENT_SERVICE_MANAGER?.trim().toLowerCase();
13628
+ if (v === "systemd" || v === "launchd") return v;
13629
+ return detectServiceManager();
13630
+ }
13631
+ function parseServiceEnvironment(raw) {
13632
+ const out = {};
13633
+ for (const token of raw.trim().split(/\s+/)) {
13634
+ const idx = token.indexOf("=");
13635
+ if (idx > 0) out[token.slice(0, idx)] = token.slice(idx + 1);
13636
+ }
13637
+ return out;
13638
+ }
13639
+ var cachedServiceEnv = null;
13640
+ function readServiceEnvironment() {
13641
+ const serviceName = process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;
13642
+ try {
13643
+ const res = (0, import_node_child_process4.spawnSync)(
13644
+ "systemctl",
13645
+ ["show", serviceName, "--property=Environment", "--value", "--no-pager"],
13646
+ { encoding: "utf8", timeout: 3e3 }
13647
+ );
13648
+ if (res.status !== 0 || !res.stdout) return {};
13649
+ return parseServiceEnvironment(res.stdout);
13650
+ } catch {
13651
+ return {};
13652
+ }
13653
+ }
13654
+ function serviceEnvValue(key) {
13655
+ if (process.platform === "darwin") {
13656
+ if (serviceManager() !== "launchd") return void 0;
13657
+ cachedLaunchdEnv ??= readLaunchdEnvironment();
13658
+ return cachedLaunchdEnv[key];
13659
+ }
13660
+ if (process.platform !== "linux") return void 0;
13661
+ if (serviceManager() !== "systemd") return void 0;
13662
+ cachedServiceEnv ??= readServiceEnvironment();
13663
+ return cachedServiceEnv[key];
13664
+ }
13665
+ var cachedLaunchdEnv = null;
13666
+ function readLaunchdEnvironment() {
13667
+ try {
13668
+ return parseLaunchdEnvironment(fs20.readFileSync(launchdPlistPath(), "utf8"));
13669
+ } catch {
13670
+ return {};
13671
+ }
13672
+ }
13673
+ function selfUpdateCapability(hasPublicKey) {
13674
+ if (!isSeaRuntime()) {
13675
+ return {
13676
+ ok: false,
13677
+ reason: "\u5F53\u524D\u4E0D\u662F\u5355\u6587\u4EF6\uFF08SEA\uFF09\u5F62\u6001\uFF1A\u8BF7\u7528 npm \u5347\u7EA7\uFF08npm i -g @zqagent/core@latest\uFF09"
13678
+ };
13679
+ }
13680
+ if (!serviceManager()) {
13681
+ return {
13682
+ ok: false,
13683
+ reason: "\u672A\u88AB systemd/launchd \u6258\u7BA1\uFF1A\u8BF7\u7528 deploy/service/install.sh \u5B89\u88C5\uFF0C\u6216\u624B\u52A8\u5347\u7EA7"
13684
+ };
13685
+ }
13686
+ if (!hasPublicKey) {
13687
+ return { ok: false, reason: "\u672A\u914D\u7F6E ZQAGENT_UPDATE_PUBKEY\uFF0C\u65E0\u6CD5\u9A8C\u7B7E\uFF0C\u5DF2\u7981\u7528\u81EA\u52A8\u66F4\u65B0" };
13688
+ }
13689
+ return { ok: true };
13690
+ }
13480
13691
 
13481
- // src/server/projects.ts
13482
- init_cjs_shims();
13483
- var path25 = __toESM(require("path"), 1);
13484
- var DEFAULT_MODEL = () => process.env.OPENAI_MODEL || process.env.MODEL || "gpt-4o";
13485
- function toProjectSummary(store, tenant) {
13486
- return {
13487
- id: tenant.id,
13488
- name: tenant.name ?? tenant.id,
13489
- model: tenant.model,
13490
- baseURL: tenant.provider?.baseURL ?? "",
13491
- hasApiKey: Boolean(tenant.provider?.apiKey),
13492
- workspaceRoot: tenant.workspaceRoot ?? null,
13493
- sessionCount: store.listSessions(tenant.id).length
13692
+ // src/cli/serve-instances.ts
13693
+ var DEFAULT_SERVE_HOST = "127.0.0.1";
13694
+ var DEFAULT_SERVE_PORT = 8787;
13695
+ var DEFAULT_SERVICE_NAME2 = "zqagent";
13696
+ var DEFAULT_SERVICE_LABEL2 = "com.zqagent.node";
13697
+ function serveSubcommandIndex(argv) {
13698
+ for (let i = 1; i < argv.length; i++) {
13699
+ if (argv[i].startsWith("-")) continue;
13700
+ return argv[i] === "serve" ? i : -1;
13701
+ }
13702
+ return -1;
13703
+ }
13704
+ function parseServeArgs(argv) {
13705
+ const at = serveSubcommandIndex(argv);
13706
+ if (at < 0) return null;
13707
+ let dataDir = null;
13708
+ let host = DEFAULT_SERVE_HOST;
13709
+ let port = DEFAULT_SERVE_PORT;
13710
+ for (let i = at + 1; i < argv.length; i++) {
13711
+ const arg = argv[i];
13712
+ if (arg === "--data-dir" && argv[i + 1]) {
13713
+ dataDir = argv[++i];
13714
+ } else if (arg.startsWith("--data-dir=")) {
13715
+ dataDir = arg.slice("--data-dir=".length);
13716
+ } else if (arg === "--host" && argv[i + 1]) {
13717
+ host = argv[++i];
13718
+ } else if (arg.startsWith("--host=")) {
13719
+ host = arg.slice("--host=".length);
13720
+ } else if (arg === "--port" && argv[i + 1]) {
13721
+ port = Number(argv[++i]);
13722
+ } else if (arg.startsWith("--port=")) {
13723
+ port = Number(arg.slice("--port=".length));
13724
+ }
13725
+ }
13726
+ if (!Number.isFinite(port) || port <= 0) port = DEFAULT_SERVE_PORT;
13727
+ if (host === "0.0.0.0" || host === "::" || host === "") host = DEFAULT_SERVE_HOST;
13728
+ return { dataDir, host, port };
13729
+ }
13730
+ function buildHotCandidates(explicitDataDir, discovered, fallback) {
13731
+ const out = [];
13732
+ const push = (inst) => {
13733
+ if (!out.some((o) => o.dataDir === inst.dataDir)) out.push(inst);
13494
13734
  };
13735
+ if (explicitDataDir) push({ dataDir: path26.resolve(explicitDataDir), ...fallback });
13736
+ for (const inst of discovered) if (inst.managed) push(inst);
13737
+ for (const inst of discovered) if (!inst.managed) push(inst);
13738
+ if (!explicitDataDir) push({ dataDir: path26.resolve(DEFAULT_DATA_DIR), ...fallback });
13739
+ return out;
13495
13740
  }
13496
- function providerViews(tenant) {
13741
+ function isWindowsAbsolute(p) {
13742
+ return /^[A-Za-z]:[\\/]/.test(p) || /^\\\\/.test(p);
13743
+ }
13744
+ function isAbsolutePath(p) {
13745
+ return path26.isAbsolute(p) || isWindowsAbsolute(p);
13746
+ }
13747
+ function normalizePath(p) {
13748
+ return isWindowsAbsolute(p) ? path26.win32.normalize(p) : path26.normalize(p);
13749
+ }
13750
+ function parseServeCommandLine(line) {
13751
+ if (!/(?:^|\s)["']?serve["']?(?=\s|$)/.test(line)) return null;
13752
+ const m = /--data-dir(?:=|\s+)(?:"([^"]+)"|'([^']+)'|(.+?))(?=\s+--\w|\s*$)/.exec(line);
13753
+ const rawDataDir = (m?.[1] ?? m?.[2] ?? m?.[3] ?? "").trim();
13754
+ if (!rawDataDir || !isAbsolutePath(rawDataDir)) return null;
13755
+ const host = /--host(?:=|\s+)(?:"([^"]+)"|'([^']+)'|(\S+))/.exec(line);
13756
+ const rawHost = (host?.[1] ?? host?.[2] ?? host?.[3] ?? DEFAULT_SERVE_HOST).trim();
13757
+ const portMatch = /--port(?:=|\s+)(\d+)/.exec(line);
13758
+ const port = Number(portMatch?.[1] ?? DEFAULT_SERVE_PORT);
13497
13759
  return {
13498
- activeProviderId: tenant.activeProviderId ?? null,
13499
- providers: (tenant.llmProviders ?? []).map((p) => ({
13500
- id: p.id,
13501
- name: p.name,
13502
- type: p.type ?? "",
13503
- baseURL: p.baseURL,
13504
- model: p.model,
13505
- models: p.models ?? [],
13506
- modelMeta: p.modelMeta ?? [],
13507
- hasApiKey: Boolean(p.apiKey)
13508
- }))
13760
+ dataDir: normalizePath(rawDataDir),
13761
+ host: rawHost === "0.0.0.0" || rawHost === "::" || rawHost === "" ? DEFAULT_SERVE_HOST : rawHost,
13762
+ port: Number.isFinite(port) && port > 0 ? port : DEFAULT_SERVE_PORT
13509
13763
  };
13510
13764
  }
13511
- function applyProvidersPatch(tenant, raw) {
13512
- if (Array.isArray(raw.providers)) {
13513
- const existing = new Map((tenant.llmProviders ?? []).map((p) => [p.id, p]));
13514
- const next = [];
13515
- for (const item of raw.providers) {
13516
- if (!item || typeof item !== "object") continue;
13517
- const o = item;
13518
- const id = String(o.id ?? "").trim() || newId();
13519
- const prev = existing.get(id);
13520
- const apiKey = typeof o.apiKey === "string" && o.apiKey.trim() ? o.apiKey.trim() : prev?.apiKey;
13521
- const models = normalizeModelList(o.models) ?? prev?.models;
13522
- const modelMeta = normalizeModelMeta(o.modelMeta) ?? prev?.modelMeta;
13523
- next.push({
13524
- id,
13525
- name: String(o.name ?? "").trim() || id,
13526
- type: typeof o.type === "string" ? o.type : prev?.type,
13527
- baseURL: String(o.baseURL ?? "").trim(),
13528
- model: String(o.model ?? "").trim(),
13529
- ...apiKey ? { apiKey } : {},
13530
- ...models ? { models } : {},
13531
- ...modelMeta ? { modelMeta } : {}
13532
- });
13765
+ function processCwd(pid) {
13766
+ try {
13767
+ return fs21.readlinkSync(`/proc/${pid}/cwd`);
13768
+ } catch {
13769
+ return null;
13770
+ }
13771
+ }
13772
+ function processEnv(pid) {
13773
+ const out = {};
13774
+ try {
13775
+ const raw = fs21.readFileSync(`/proc/${pid}/environ`, "utf-8");
13776
+ for (const entry of raw.split("\0")) {
13777
+ const eq = entry.indexOf("=");
13778
+ if (eq > 0) out[entry.slice(0, eq)] = entry.slice(eq + 1);
13533
13779
  }
13534
- tenant.llmProviders = next;
13780
+ } catch {
13535
13781
  }
13536
- if (typeof raw.activeProviderId === "string") {
13537
- const activeId = raw.activeProviderId.trim();
13538
- tenant.activeProviderId = activeId || void 0;
13539
- const entry = (tenant.llmProviders ?? []).find((p) => p.id === activeId);
13540
- if (entry) {
13541
- tenant.provider = tenant.provider ?? {};
13542
- tenant.provider.baseURL = entry.baseURL || void 0;
13543
- if (entry.apiKey) tenant.provider.apiKey = entry.apiKey;
13544
- if (entry.model) tenant.model = entry.model;
13782
+ return out;
13783
+ }
13784
+ function resolveDataDir2(rawDataDir, cwd, env3) {
13785
+ const pick = rawDataDir ?? env3.DATA_DIR ?? null;
13786
+ if (pick) return path26.isAbsolute(pick) ? path26.normalize(pick) : cwd ? path26.resolve(cwd, pick) : null;
13787
+ return cwd ? path26.resolve(cwd, DEFAULT_DATA_DIR) : null;
13788
+ }
13789
+ function isManagedBySystemd(pid, env3) {
13790
+ if (env3.ZQAGENT_SERVICE_MANAGER?.trim()) return true;
13791
+ try {
13792
+ const cgroup = fs21.readFileSync(`/proc/${pid}/cgroup`, "utf-8");
13793
+ const serviceName = env3.ZQAGENT_SERVICE_NAME?.trim() || "zqagent";
13794
+ return serviceManagerFromCgroup(cgroup, serviceName) !== null;
13795
+ } catch {
13796
+ return false;
13797
+ }
13798
+ }
13799
+ function discoverFromProc() {
13800
+ const out = [];
13801
+ let pids;
13802
+ try {
13803
+ pids = fs21.readdirSync("/proc");
13804
+ } catch {
13805
+ return out;
13806
+ }
13807
+ for (const name of pids) {
13808
+ if (!/^\d+$/.test(name)) continue;
13809
+ const pid = Number(name);
13810
+ if (pid === process.pid) continue;
13811
+ let argv;
13812
+ let env3;
13813
+ try {
13814
+ argv = fs21.readFileSync(`/proc/${pid}/cmdline`, "utf-8").split("\0").filter(Boolean);
13815
+ env3 = processEnv(pid);
13816
+ } catch {
13817
+ continue;
13545
13818
  }
13819
+ const parsed = parseServeArgs(argv);
13820
+ if (!parsed) continue;
13821
+ const dataDir = resolveDataDir2(parsed.dataDir, processCwd(pid), env3);
13822
+ if (!dataDir) continue;
13823
+ out.push({
13824
+ dataDir,
13825
+ host: parsed.host,
13826
+ port: parsed.port,
13827
+ managed: isManagedBySystemd(pid, env3)
13828
+ });
13546
13829
  }
13830
+ return out;
13547
13831
  }
13548
- function allProjectSummaries(store) {
13549
- return store.listTenants().map((id) => store.loadTenant(id)).filter((t) => t !== null).map((t) => toProjectSummary(store, t));
13832
+ function discoverFromPs() {
13833
+ const out = [];
13834
+ const res = (0, import_node_child_process5.spawnSync)("ps", ["-axo", "command="], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
13835
+ if (res.status !== 0 || !res.stdout) return out;
13836
+ for (const line of res.stdout.split("\n")) {
13837
+ const inst = parseServeCommandLine(line);
13838
+ if (inst) out.push(inst);
13839
+ }
13840
+ return out;
13550
13841
  }
13551
- function lastUserMessage(rec) {
13552
- const sent = (rec.lastInput ?? "").trim();
13553
- if (sent) {
13554
- return sent.length > LAST_USER_MESSAGE_MAX ? `${sent.slice(0, LAST_USER_MESSAGE_MAX)}\u2026` : sent;
13842
+ function discoverFromWindows() {
13843
+ const out = [];
13844
+ const script = `[Console]::OutputEncoding=[Text.Encoding]::UTF8; Get-CimInstance Win32_Process -Filter "Name='zqagent.exe' OR Name='node.exe'" | ForEach-Object { $_.CommandLine }`;
13845
+ const res = (0, import_node_child_process5.spawnSync)(
13846
+ "powershell.exe",
13847
+ ["-NoProfile", "-NonInteractive", "-Command", script],
13848
+ { encoding: "utf-8", timeout: 15e3, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] }
13849
+ );
13850
+ if (res.status !== 0 || !res.stdout) return out;
13851
+ for (const line of res.stdout.replace(/^\uFEFF/, "").split(/\r?\n/)) {
13852
+ const inst = parseServeCommandLine(line);
13853
+ if (inst) out.push(inst);
13555
13854
  }
13556
- const msgs = rec.messages;
13557
- for (let i = msgs.length - 1; i >= 0; i--) {
13558
- const m = msgs[i];
13559
- if (!m || m.role !== "user" || typeof m.content !== "string") continue;
13560
- const text = m.content.trim();
13561
- if (!text) continue;
13562
- return text.length > LAST_USER_MESSAGE_MAX ? `${text.slice(0, LAST_USER_MESSAGE_MAX)}\u2026` : text;
13855
+ return out;
13856
+ }
13857
+ function decodeXmlEntities(s) {
13858
+ return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
13859
+ }
13860
+ function parseLaunchdProgramArguments(xml) {
13861
+ const marker = xml.indexOf("<key>ProgramArguments</key>");
13862
+ if (marker < 0) return [];
13863
+ const open = xml.indexOf("<array>", marker);
13864
+ if (open < 0) return [];
13865
+ const close = xml.indexOf("</array>", open);
13866
+ if (close < 0) return [];
13867
+ return [...xml.slice(open + "<array>".length, close).matchAll(/<string>([^<]*)<\/string>/g)].map(
13868
+ (m) => decodeXmlEntities(m[1])
13869
+ );
13870
+ }
13871
+ function launchdServiceInstances() {
13872
+ if (process.platform !== "darwin") return [];
13873
+ const label = process.env.ZQAGENT_SERVICE_LABEL?.trim() || DEFAULT_SERVICE_LABEL2;
13874
+ const plists = [
13875
+ `/Library/LaunchDaemons/${label}.plist`,
13876
+ path26.join(os5.homedir(), "Library", "LaunchAgents", `${label}.plist`)
13877
+ ];
13878
+ const out = [];
13879
+ for (const plist of plists) {
13880
+ let xml;
13881
+ try {
13882
+ xml = fs21.readFileSync(plist, "utf-8");
13883
+ } catch {
13884
+ continue;
13885
+ }
13886
+ const parsed = parseServeArgs(parseLaunchdProgramArguments(xml));
13887
+ if (!parsed?.dataDir || !isAbsolutePath(parsed.dataDir)) continue;
13888
+ out.push({
13889
+ dataDir: normalizePath(parsed.dataDir),
13890
+ host: parsed.host,
13891
+ port: parsed.port,
13892
+ managed: true
13893
+ });
13563
13894
  }
13564
- return null;
13895
+ return out;
13565
13896
  }
13566
- function lastCompletedAt(rec) {
13567
- if (rec.lastCompletedAt) return rec.lastCompletedAt;
13568
- if (rec.status !== "completed") return null;
13569
- const msgs = rec.messages;
13570
- for (let i = msgs.length - 1; i >= 0; i--) {
13571
- const m = msgs[i];
13572
- if (!m || m.role !== "assistant") continue;
13573
- if (typeof m.ts === "number" && Number.isFinite(m.ts)) {
13574
- return new Date(m.ts).toISOString();
13897
+ function parseSystemdUnit(text) {
13898
+ let dataDir = null;
13899
+ let host = DEFAULT_SERVE_HOST;
13900
+ let port = DEFAULT_SERVE_PORT;
13901
+ for (const line of text.split("\n")) {
13902
+ const trimmed = line.trim();
13903
+ if (trimmed.startsWith("WorkingDirectory=")) {
13904
+ const v = trimmed.slice("WorkingDirectory=".length).trim();
13905
+ if (v && isAbsolutePath(v)) dataDir = v;
13906
+ } else if (trimmed.startsWith("ExecStart=")) {
13907
+ const tokens = trimmed.slice("ExecStart=".length).trim().split(/\s+/).map((t) => t.replace(/^["']|["']$/g, ""));
13908
+ const parsed = parseServeArgs(tokens);
13909
+ if (!parsed) continue;
13910
+ if (parsed.dataDir && isAbsolutePath(parsed.dataDir)) dataDir = parsed.dataDir;
13911
+ host = parsed.host;
13912
+ port = parsed.port;
13913
+ }
13914
+ }
13915
+ if (!dataDir) return null;
13916
+ return { dataDir: normalizePath(dataDir), host, port, managed: true };
13917
+ }
13918
+ function systemdServiceInstances() {
13919
+ if (process.platform !== "linux") return [];
13920
+ const unit = process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME2;
13921
+ const configHome = process.env.XDG_CONFIG_HOME?.trim() || path26.join(os5.homedir(), ".config");
13922
+ const units = [
13923
+ `/etc/systemd/system/${unit}.service`,
13924
+ path26.join(configHome, "systemd", "user", `${unit}.service`)
13925
+ ];
13926
+ const out = [];
13927
+ for (const file of units) {
13928
+ let text;
13929
+ try {
13930
+ text = fs21.readFileSync(file, "utf-8");
13931
+ } catch {
13932
+ continue;
13575
13933
  }
13576
- return null;
13934
+ const inst = parseSystemdUnit(text);
13935
+ if (inst) out.push(inst);
13936
+ }
13937
+ return out;
13938
+ }
13939
+ function windowsServiceInstances() {
13940
+ if (process.platform !== "win32") return [];
13941
+ const appdata = process.env.APPDATA?.trim();
13942
+ if (!appdata) return [];
13943
+ const base = path26.join(appdata, "zqagent");
13944
+ try {
13945
+ const raw = fs21.readFileSync(path26.join(base, "runtime", "service.json"), "utf-8");
13946
+ const v = JSON.parse(raw);
13947
+ const dataDir = v.dataDir && isAbsolutePath(v.dataDir) ? v.dataDir : base;
13948
+ const port = Number(v.port);
13949
+ return [
13950
+ {
13951
+ dataDir: normalizePath(dataDir),
13952
+ host: DEFAULT_SERVE_HOST,
13953
+ port: Number.isFinite(port) && port > 0 ? port : DEFAULT_SERVE_PORT,
13954
+ managed: true
13955
+ }
13956
+ ];
13957
+ } catch {
13958
+ return [];
13959
+ }
13960
+ }
13961
+ function mergeServeInstances(discovered, managed) {
13962
+ const out = [];
13963
+ const push = (inst) => {
13964
+ if (!out.some((o) => o.dataDir === inst.dataDir)) out.push(inst);
13965
+ };
13966
+ for (const inst of discovered) {
13967
+ const hit = managed.find((m) => m.dataDir === inst.dataDir);
13968
+ push(hit ? { ...inst, managed: true } : inst);
13577
13969
  }
13578
- return null;
13970
+ for (const inst of managed) push(inst);
13971
+ return out;
13579
13972
  }
13580
- function allSessionStates(store, manager) {
13581
- const out = [];
13582
- for (const id of store.listTenants()) {
13583
- const t = store.loadTenant(id);
13584
- if (!t) continue;
13585
- try {
13586
- for (const s of manager.listSessions(t.id)) {
13587
- out.push({
13588
- projectId: t.id,
13589
- projectName: t.name ?? t.id,
13590
- sessionId: s.sessionId,
13591
- name: s.name ?? null,
13592
- status: s.status,
13593
- lastUserMessage: lastUserMessage(s),
13594
- lastCompletedAt: lastCompletedAt(s)
13595
- });
13596
- }
13597
- } catch {
13973
+ function discoverRunningServeInstances() {
13974
+ const found = process.platform === "linux" ? discoverFromProc() : process.platform === "win32" ? discoverFromWindows() : discoverFromPs();
13975
+ const declared = process.platform === "linux" ? systemdServiceInstances() : process.platform === "win32" ? windowsServiceInstances() : launchdServiceInstances();
13976
+ return mergeServeInstances(found, declared);
13977
+ }
13978
+
13979
+ // src/cli/link.ts
13980
+ function expandHome(p) {
13981
+ const home = process.env.HOME || process.env.USERPROFILE;
13982
+ if (!home) return p;
13983
+ if (p === "~") return home;
13984
+ if (p.startsWith("~/") || p.startsWith("~\\")) return path27.join(home, p.slice(2));
13985
+ return p;
13986
+ }
13987
+ function installDataDirHint() {
13988
+ if (process.platform === "darwin") return "$HOME/Library/Application Support/zqagent";
13989
+ if (process.platform === "win32") return "%APPDATA%\\zqagent";
13990
+ return "/var/lib/zqagent\uFF08\u9700 root\uFF1B\u666E\u901A\u7528\u6237\u7528 ~/.zqagent_data\uFF09";
13991
+ }
13992
+ function buildAccountAuth(flags, env3 = process.env) {
13993
+ const username = (flags.username ?? env3.ZQAGENT_HUB_USERNAME ?? "").trim();
13994
+ let email = (flags.email ?? "").trim();
13995
+ let phone = (flags.phone ?? "").trim();
13996
+ if (username && !email && !phone) {
13997
+ if (username.includes("@")) email = username;
13998
+ else phone = username;
13999
+ }
14000
+ if (!email && !phone) return null;
14001
+ const password = flags.password ?? env3.ZQAGENT_HUB_PASSWORD ?? "";
14002
+ const smsCode = (flags.smsCode ?? env3.ZQAGENT_HUB_SMS_CODE ?? "").trim();
14003
+ return {
14004
+ email: email ? email.toLowerCase() : void 0,
14005
+ phone: phone || void 0,
14006
+ password: password || void 0,
14007
+ smsCode: smsCode || void 0
14008
+ };
14009
+ }
14010
+ function isCompleteAccount(account) {
14011
+ return Boolean(account && (account.password || account.smsCode));
14012
+ }
14013
+ function parseLinkArgs(argv) {
14014
+ const out = {};
14015
+ for (let i = 0; i < argv.length; i++) {
14016
+ const a = argv[i];
14017
+ const val = argv[i + 1];
14018
+ if (a === "--hub") {
14019
+ out.hubUrl = val;
14020
+ i++;
14021
+ } else if (a === "--code") {
14022
+ out.code = val;
14023
+ i++;
14024
+ } else if (a === "--username" || a === "--user") {
14025
+ out.username = val;
14026
+ i++;
14027
+ } else if (a === "--email") {
14028
+ out.email = val;
14029
+ i++;
14030
+ } else if (a === "--phone") {
14031
+ out.phone = val;
14032
+ i++;
14033
+ } else if (a === "--password") {
14034
+ out.password = val;
14035
+ i++;
14036
+ } else if (a === "--sms-code") {
14037
+ out.smsCode = val;
14038
+ i++;
14039
+ } else if (a === "--data-dir") {
14040
+ out.dataDir = val;
14041
+ i++;
14042
+ } else if (a === "--label") {
14043
+ out.label = val;
14044
+ i++;
14045
+ } else if (a === "--host") {
14046
+ out.host = val;
14047
+ i++;
14048
+ } else if (a === "--port") {
14049
+ out.port = Number(val);
14050
+ i++;
14051
+ } else if (a === "-h" || a === "--help") {
14052
+ out.help = true;
13598
14053
  }
13599
14054
  }
13600
14055
  return out;
@@ -13607,50 +14062,285 @@ function ensureNodeId2(store) {
13607
14062
  }
13608
14063
  return id;
13609
14064
  }
13610
- function createProject(store, input) {
13611
- const raw = input ?? {};
13612
- const id = String(raw.id ?? "").trim() || newId();
13613
- if (store.loadTenant(id)) {
13614
- throw new ApiError(409, "project_exists", `\u9879\u76EE ${id} \u5DF2\u5B58\u5728`);
14065
+ async function probeServer(host, port, adminKey) {
14066
+ const ctrl = new AbortController();
14067
+ const timer = setTimeout(() => ctrl.abort(), 1500);
14068
+ try {
14069
+ const res = await fetch(`http://${host}:${port}/api/v1/fleet/status`, {
14070
+ headers: { Authorization: `Bearer ${adminKey}` },
14071
+ signal: ctrl.signal
14072
+ });
14073
+ return res.ok;
14074
+ } catch {
14075
+ return false;
14076
+ } finally {
14077
+ clearTimeout(timer);
13615
14078
  }
13616
- const workspaceRoot = typeof raw.workspaceRoot === "string" && raw.workspaceRoot.trim() ? path25.resolve(raw.workspaceRoot.trim()) : void 0;
13617
- const explicitName = typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : "";
13618
- const defaultName = workspaceRoot ? path25.basename(workspaceRoot) : id;
13619
- const tenant = {
13620
- id,
13621
- name: explicitName || defaultName,
13622
- apiKey: generateApiKey(),
13623
- model: typeof raw.model === "string" && raw.model.trim() ? raw.model.trim() : DEFAULT_MODEL(),
13624
- provider: {
13625
- apiKey: typeof raw.providerApiKey === "string" && raw.providerApiKey.trim() ? raw.providerApiKey.trim() : void 0,
13626
- baseURL: typeof raw.baseURL === "string" && raw.baseURL.trim() ? raw.baseURL.trim() : void 0
14079
+ }
14080
+ async function enrollViaServer(host, port, adminKey, opts) {
14081
+ const res = await fetch(`http://${host}:${port}/api/v1/fleet/enroll`, {
14082
+ method: "POST",
14083
+ headers: {
14084
+ "Content-Type": "application/json",
14085
+ Authorization: `Bearer ${adminKey}`
13627
14086
  },
13628
- workspaceRoot,
13629
- allowedCommands: Array.isArray(raw.allowedCommands) ? raw.allowedCommands.map((c) => String(c)) : []
13630
- };
13631
- const def = nodeDefaultProvider(store);
13632
- if (def) {
13633
- if (!tenant.provider?.apiKey && def.apiKey) tenant.provider = { ...tenant.provider, apiKey: def.apiKey };
13634
- if (!tenant.provider?.baseURL && def.baseURL) tenant.provider = { ...tenant.provider, baseURL: def.baseURL };
13635
- if (!(typeof raw.model === "string" && raw.model.trim()) && def.model) tenant.model = def.model;
13636
- const nodeCfg = store.loadNodeLlm();
13637
- tenant.llmProviders = nodeCfg.providers.map((p) => ({ ...p }));
13638
- tenant.activeProviderId = nodeCfg.defaultProviderId ?? nodeCfg.providers[0]?.id;
14087
+ body: JSON.stringify({
14088
+ hubUrl: opts.hubUrl,
14089
+ code: opts.code,
14090
+ email: opts.account?.email,
14091
+ phone: opts.account?.phone,
14092
+ password: opts.account?.password,
14093
+ smsCode: opts.account?.smsCode,
14094
+ label: opts.label
14095
+ })
14096
+ });
14097
+ const text = await res.text();
14098
+ let payload = null;
14099
+ try {
14100
+ payload = text ? JSON.parse(text) : null;
14101
+ } catch {
13639
14102
  }
13640
- store.saveTenant(tenant);
13641
- return tenant;
14103
+ if (!res.ok) {
14104
+ throw new Error(payload?.error?.message ?? `HTTP ${res.status}`);
14105
+ }
14106
+ return payload?.data;
13642
14107
  }
13643
- function buildNodeInfo(store, opts) {
14108
+ function hotWarnings(inst, running) {
14109
+ const warnings = [];
14110
+ const managed = running.filter((r) => r.managed && r.dataDir !== inst.dataDir);
14111
+ if (!inst.managed && managed.length > 0) {
14112
+ warnings.push(
14113
+ `\u51ED\u636E\u5199\u5165\u4E86\u975E\u6258\u7BA1\u5B9E\u4F8B ${inst.dataDir}\uFF0C\u4F46\u672C\u673A\u6258\u7BA1\u670D\u52A1\u4F7F\u7528\u7684\u662F ${managed.map((m) => m.dataDir).join("\u3001")}\u3002`
14114
+ );
14115
+ warnings.push(
14116
+ "\u6258\u7BA1\u670D\u52A1\u8BFB\u4E0D\u5230\u8FD9\u4EFD\u51ED\u636E\uFF08\u91CD\u542F\u540E\u4E5F\u4E0D\u4F1A\u81EA\u52A8\u4E0A\u7EBF\uFF09\uFF0C\u8BF7\u7528 --data-dir \u6307\u5411\u5B83\u7684\u6570\u636E\u76EE\u5F55\u91CD\u65B0\u63A5\u5165\u3002"
14117
+ );
14118
+ }
14119
+ const others = [...new Set(running.map((r) => r.dataDir).filter((d) => d !== inst.dataDir))];
14120
+ if (others.length > 0) {
14121
+ warnings.push(`\u672C\u673A\u8FD8\u6709\u5176\u5B83\u8FD0\u884C\u4E2D\u7684\u8282\u70B9\u5B9E\u4F8B\uFF08\u672A\u5199\u5165\u51ED\u636E\uFF09\uFF1A${others.join("\u3001")}\u3002`);
14122
+ }
14123
+ return warnings;
14124
+ }
14125
+ async function linkNode(opts) {
14126
+ if (!opts.code && !isCompleteAccount(opts.account ?? null)) {
14127
+ throw new Error("\u7F3A\u5C11\u63A5\u5165\u7801\uFF0C\u6216 Hub \u8D26\u53F7\u5BC6\u7801/\u77ED\u4FE1\u9A8C\u8BC1\u7801");
14128
+ }
14129
+ const hubUrl = opts.hubUrl.trim().replace(/\/$/, "");
14130
+ if (!hubUrl) throw new Error("\u7F3A\u5C11 Hub \u5730\u5740");
14131
+ const rawDataDir = opts.dataDir?.trim() || process.env.DATA_DIR?.trim() || void 0;
14132
+ const explicitDataDir = rawDataDir ? expandHome(rawDataDir) : void 0;
14133
+ const host = opts.host?.trim() || process.env.HOST?.trim() || DEFAULT_SERVE_HOST;
14134
+ const port = opts.port ?? Number(process.env.PORT ?? DEFAULT_SERVE_PORT);
14135
+ const running = discoverRunningServeInstances();
14136
+ const candidates = buildHotCandidates(explicitDataDir, running, { host, port });
14137
+ for (const inst of candidates) {
14138
+ const store2 = new Store(inst.dataDir);
14139
+ const adminKey = process.env.ADMIN_API_KEY ?? store2.loadAdminKey();
14140
+ if (!adminKey) continue;
14141
+ if (!await probeServer(inst.host, inst.port, adminKey)) continue;
14142
+ const status = await enrollViaServer(inst.host, inst.port, adminKey, { ...opts, hubUrl });
14143
+ return {
14144
+ mode: "hot",
14145
+ ...status,
14146
+ dataDir: inst.dataDir,
14147
+ managed: inst.managed,
14148
+ warnings: hotWarnings(inst, running)
14149
+ };
14150
+ }
14151
+ const managedDir = running.find((r) => r.managed)?.dataDir;
14152
+ const dataDir = path27.resolve(explicitDataDir ?? managedDir ?? DEFAULT_DATA_DIR);
14153
+ const targetedService = !explicitDataDir && Boolean(managedDir) && dataDir === managedDir;
14154
+ const store = new Store(dataDir);
14155
+ const nodeId = ensureNodeId2(store);
14156
+ const account = opts.account;
14157
+ const cred = account && isCompleteAccount(account) ? await hubEnrollByAccount(hubUrl, {
14158
+ email: account.email,
14159
+ phone: account.phone,
14160
+ password: account.password,
14161
+ code: account.smsCode,
14162
+ nodeId,
14163
+ label: opts.label
14164
+ }) : await hubEnrollByCode(hubUrl, {
14165
+ code: opts.code,
14166
+ nodeId,
14167
+ label: opts.label
14168
+ });
14169
+ try {
14170
+ store.saveFleetCredential({
14171
+ ...cred,
14172
+ label: opts.label ?? cred.label,
14173
+ enrolledAt: (/* @__PURE__ */ new Date()).toISOString()
14174
+ });
14175
+ } catch (e) {
14176
+ const err = e;
14177
+ if (err.code === "EACCES" || err.code === "EPERM") {
14178
+ throw new Error(
14179
+ `\u65E0\u6CD5\u5199\u5165\u6570\u636E\u76EE\u5F55 ${dataDir}\uFF08${err.code}: ${err.message}\uFF09
14180
+ \u8BE5\u76EE\u5F55\u5F53\u524D\u7528\u6237\u6CA1\u6709\u5199\u6743\u9650\u3002\u4E24\u79CD\u6539\u6CD5\uFF1A
14181
+ 1) \u53BB\u6389 --data-dir\uFF0C\u8BA9 link \u81EA\u52A8\u53D1\u73B0\u672C\u673A\u8FD0\u884C\u7684\u8282\u70B9\uFF08\u63A8\u8350\uFF09\uFF1B
14182
+ 2) \u7528\u5F53\u524D\u7528\u6237\u53EF\u5199\u7684\u76EE\u5F55\uFF0C\u672C\u5E73\u53F0\u5B89\u88C5\u811A\u672C\u9ED8\u8BA4\uFF1A${installDataDirHint()}
14183
+ \uFF08\u63A5\u5165\u7801\u4E00\u6B21\u6027\uFF0C\u9700\u8981\u91CD\u65B0\u751F\u6210\uFF1B\u8D26\u53F7\u65B9\u5F0F\u53EF\u91CD\u590D\u4F7F\u7528\uFF09`
14184
+ );
14185
+ }
14186
+ throw e;
14187
+ }
14188
+ const otherRunningDirs = [
14189
+ ...new Set(running.map((r) => r.dataDir).filter((d) => d !== dataDir))
14190
+ ];
13644
14191
  return {
13645
- nodeId: opts.nodeId ?? ensureNodeId2(store),
13646
- platform: process.platform,
13647
- mode: process.env.NODE_MODE ?? "standalone",
13648
- version: "0.2.10",
13649
- admin: opts.admin,
13650
- projects: opts.projects
14192
+ mode: "saved",
14193
+ nodeId,
14194
+ gatewayUrl: cred.gatewayUrl,
14195
+ label: opts.label,
14196
+ dataDir,
14197
+ explicitDataDir: Boolean(explicitDataDir),
14198
+ runningServiceDirs: otherRunningDirs,
14199
+ targetedService
14200
+ };
14201
+ }
14202
+ function printLinkHelp() {
14203
+ cliLog("\u7528\u6CD5: zqagent link [--hub <url>] [--username <\u90AE\u7BB1/\u624B\u673A\u53F7> --password <\u5BC6\u7801>] [--label \u540D\u79F0]");
14204
+ cliLog(" zqagent link [--hub <url>] --code <\u4E00\u6B21\u6027\u63A5\u5165\u7801> [--label \u540D\u79F0]");
14205
+ cliLog(" \u65B9\u5F0F\u4E00\uFF08\u63A8\u8350\uFF09\uFF1A\u7528 Hub \u5DF2\u6CE8\u518C\u7684\u8D26\u53F7\u76F4\u63A5\u63A5\u5165\uFF1A");
14206
+ cliLog(" zqagent link --hub https://hub.cd-zq.com --username you@example.com --password '<\u5BC6\u7801>'");
14207
+ cliLog(" zqagent link --hub https://hub.cd-zq.com --username 13800000000 --password '<\u5BC6\u7801>'");
14208
+ cliLog(" zqagent link --hub https://hub.cd-zq.com --username 13800000000 --sms-code <\u9A8C\u8BC1\u7801>");
14209
+ cliLog(" \u65B9\u5F0F\u4E8C\uFF1A\u5728 Hub\u300C\u8282\u70B9\u7BA1\u7406\u300D\u751F\u6210\u4E00\u6B21\u6027\u63A5\u5165\u7801\uFF0815 \u5206\u949F\u6709\u6548\uFF09\uFF1A");
14210
+ cliLog(" zqagent link --hub https://hub.cd-zq.com --code <\u63A5\u5165\u7801>");
14211
+ cliLog(" \u4E0D\u5E26\u51ED\u636E\u53C2\u6570\u65F6\u8FDB\u5165\u4EA4\u4E92\u5411\u5BFC\uFF08\u9700\u7EC8\u7AEF\uFF09\uFF1B\u5BC6\u7801\u4E5F\u53EF\u7528 ZQAGENT_HUB_PASSWORD \u63D0\u4F9B\u3002");
14212
+ cliLog(" \u672C\u673A\u5DF2\u88C5\u8282\u70B9\u670D\u52A1\uFF08install.sh / \u684C\u9762\u7AEF\uFF09\u65F6\u4F1A\u81EA\u52A8\u53D1\u73B0\u5B83\u7684\u6570\u636E\u76EE\u5F55\u5E76\u70ED\u63A5\u5165\uFF0C");
14213
+ cliLog(" \u4E0D\u9700\u8981\u624B\u52A8\u6307\u5B9A --data-dir\uFF1B\u670D\u52A1\u6CA1\u5728\u8FD0\u884C\u65F6\u4F1A\u5199\u5165\u5B83\u7684\u76EE\u5F55\uFF0C\u4E0B\u6B21\u542F\u52A8\u5373\u63A5\u5165\u3002");
14214
+ cliLog(" \u4EC5\u5F53\u672C\u673A\u5B8C\u5168\u6CA1\u6709\u670D\u52A1\u65F6\u624D\u9700\u8981 --data-dir \u6307\u5B9A\u76EE\u5F55\uFF08\u4E0B\u6B21\u542F\u52A8 serve \u65F6\u751F\u6548\uFF09\u3002");
14215
+ }
14216
+ async function runLinkCommand(argv, ctx = {}) {
14217
+ const args = parseLinkArgs(argv);
14218
+ if (args.help) {
14219
+ printLinkHelp();
14220
+ return true;
14221
+ }
14222
+ const explicitHub = Boolean((args.hubUrl ?? process.env.HUB_URL ?? "").trim());
14223
+ let hubUrl = (args.hubUrl ?? process.env.HUB_URL ?? DEFAULT_HUB_URL).trim().replace(/\/$/, "");
14224
+ const code = (args.code ?? "").trim();
14225
+ let account = buildAccountAuth(args);
14226
+ const interactive = ctx.interactive ?? (process.stdin.isTTY && process.stdout.isTTY);
14227
+ if (code && account) {
14228
+ cliErr("--code \u4E0E Hub \u8D26\u53F7\uFF08--username/--email/--phone/--password\uFF09\u53EA\u80FD\u4E8C\u9009\u4E00\u3002");
14229
+ return false;
14230
+ }
14231
+ if (!account && (args.password || args.smsCode)) {
14232
+ cliErr("\u63D0\u4F9B\u4E86\u5BC6\u7801/\u77ED\u4FE1\u9A8C\u8BC1\u7801\uFF0C\u4F46\u7F3A\u5C11\u8D26\u53F7\uFF1A\u8BF7\u52A0 --username\u3001--email \u6216 --phone\u3002");
14233
+ return false;
14234
+ }
14235
+ const asPrompter = (rl2) => ({
14236
+ question: (q) => new Promise((resolve24) => rl2.question(q, resolve24)),
14237
+ pause: () => rl2.pause(),
14238
+ resume: () => rl2.resume()
14239
+ });
14240
+ let ownedRl = null;
14241
+ const getRl = () => {
14242
+ if (ctx.rl) return asPrompter(ctx.rl);
14243
+ if (!interactive) return null;
14244
+ ownedRl = readline2.createInterface({ input: process.stdin, output: process.stdout });
14245
+ return asPrompter(ownedRl);
13651
14246
  };
14247
+ const closeOwnedRl = () => {
14248
+ if (ownedRl) ownedRl.close();
14249
+ };
14250
+ try {
14251
+ if (!code && !isCompleteAccount(account)) {
14252
+ const rl2 = getRl();
14253
+ if (!rl2) {
14254
+ cliErr("\u7F3A\u5C11\u63A5\u5165\u51ED\u636E\uFF1A\u8BF7\u7528 --username/--password\uFF0C\u6216 --code <\u63A5\u5165\u7801>\u3002");
14255
+ printLinkHelp();
14256
+ return false;
14257
+ }
14258
+ if (account) {
14259
+ const password = await promptPassword(rl2);
14260
+ if (!password) {
14261
+ cliErr("\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u5DF2\u53D6\u6D88\u63A5\u5165\u3002");
14262
+ return false;
14263
+ }
14264
+ account = { ...account, password };
14265
+ } else {
14266
+ const prompted = await promptLinkAccount(rl2, hubUrl, { askHub: !explicitHub });
14267
+ if (!prompted) return true;
14268
+ hubUrl = prompted.hubUrl;
14269
+ account = {
14270
+ email: prompted.auth.email,
14271
+ phone: prompted.auth.phone,
14272
+ password: prompted.auth.password,
14273
+ smsCode: prompted.auth.code
14274
+ };
14275
+ }
14276
+ }
14277
+ const r = await linkNode({
14278
+ hubUrl,
14279
+ code: code || void 0,
14280
+ account: account ?? void 0,
14281
+ dataDir: args.dataDir,
14282
+ label: args.label,
14283
+ host: args.host,
14284
+ port: args.port
14285
+ });
14286
+ if (r.mode === "hot") {
14287
+ cliLog("\u2705 \u5DF2\u63A5\u5165 Hub\uFF08\u70ED\u63A5\u5165\uFF0C\u514D\u91CD\u542F\uFF09");
14288
+ cliLog(` \u8282\u70B9: ${r.nodeId}${r.label ? ` (${r.label})` : ""}`);
14289
+ cliLog(` \u7F51\u5173: ${r.gatewayUrl ?? "\uFF08\u672A\u4E0B\u53D1\uFF09"} \u5728\u7EBF: ${r.online ? "\u662F" : "\u8FDE\u63A5\u4E2D\u2026"}`);
14290
+ cliLog(` \u6570\u636E\u76EE\u5F55: ${r.dataDir}${r.managed ? "\uFF08\u6258\u7BA1\u670D\u52A1\uFF0C\u5F00\u673A\u81EA\u542F\uFF09" : ""}`);
14291
+ if (r.warnings.length > 0) {
14292
+ cliLog("");
14293
+ for (const w of r.warnings) cliLog(`\u26A0\uFE0F ${w}`);
14294
+ }
14295
+ } else {
14296
+ cliLog("\u2705 \u63A5\u5165\u51ED\u636E\u5DF2\u4FDD\u5B58\uFF08\u672C\u673A\u6682\u65E0\u8FD0\u884C\u4E2D\u7684 server\uFF09");
14297
+ cliLog(` \u8282\u70B9: ${r.nodeId}`);
14298
+ cliLog(` \u7F51\u5173: ${r.gatewayUrl ?? "\uFF08\u672A\u4E0B\u53D1\uFF09"}`);
14299
+ cliLog(` \u6570\u636E\u76EE\u5F55: ${r.dataDir}`);
14300
+ cliLog(" \u4E0B\u6B21\u542F\u52A8 server \u5373\u81EA\u52A8\u63A5\u5165\uFF1B\u6216\u8FD0\u884C zqagent serve \u5E38\u9A7B\u4E3A\u8282\u70B9\u3002");
14301
+ if (r.targetedService) {
14302
+ cliLog("");
14303
+ cliLog("\u2705 \u51ED\u636E\u5DF2\u5199\u5165\u672C\u673A\u6258\u7BA1\u670D\u52A1\u7684\u6570\u636E\u76EE\u5F55\uFF0C\u670D\u52A1\u542F\u52A8\u65F6\u81EA\u52A8\u63A5\u5165\uFF0C\u65E0\u9700 --data-dir\u3002");
14304
+ cliLog(" \u7ACB\u5373\u751F\u6548\uFF1A\u91CD\u542F\u8BE5\u670D\u52A1\uFF08Linux: systemctl restart zqagent\uFF1B");
14305
+ cliLog(" macOS: launchctl kickstart -k gui/$(id -u)/com.zqagent.node\uFF09\u3002");
14306
+ } else if (r.runningServiceDirs.length > 0) {
14307
+ cliLog("");
14308
+ cliLog("\u26A0\uFE0F \u672C\u673A\u6709\u6B63\u5728\u8FD0\u884C\u7684\u8282\u70B9\uFF0C\u4F46\u6570\u636E\u76EE\u5F55\u4E0E\u672C\u6B21\u5199\u5165\u7684\u4E0D\u4E00\u81F4\uFF1A");
14309
+ cliLog(` \u672C\u6B21\u5199\u5165: ${r.dataDir}`);
14310
+ for (const d of r.runningServiceDirs) cliLog(` \u670D\u52A1\u4F7F\u7528: ${d}`);
14311
+ cliLog(" \u6B63\u5728\u8FD0\u884C\u7684\u670D\u52A1\u8BFB\u4E0D\u5230\u8FD9\u4EFD\u51ED\u636E\uFF0CHub \u4F1A\u4E00\u76F4\u663E\u793A\u672A\u63A5\u5165\u3002");
14312
+ cliLog(" \u8BF7\u786E\u8BA4\u670D\u52A1\u7684\u6570\u636E\u76EE\u5F55\u540E\u91CD\u65B0\u6267\u884C\uFF08\u63A5\u5165\u7801\u4E00\u6B21\u6027\uFF1B\u8D26\u53F7\u65B9\u5F0F\u53EF\u91CD\u590D\u4F7F\u7528\uFF09\uFF1A");
14313
+ cliLog(` zqagent link --hub <Hub\u5730\u5740> --username <\u90AE\u7BB1/\u624B\u673A\u53F7> --password '<\u5BC6\u7801>' --data-dir "${r.runningServiceDirs[0]}"`);
14314
+ } else if (!r.explicitDataDir) {
14315
+ cliLog("");
14316
+ cliLog("\u26A0\uFE0F \u672A\u53D1\u73B0\u672C\u673A\u8282\u70B9\u670D\u52A1\uFF0C\u51ED\u636E\u5199\u5165\u4E86\u5F53\u524D\u76EE\u5F55\u4E0B\u7684 .zqagent_data\u3002");
14317
+ cliLog(" \u5982\u679C\u8FD9\u53F0\u673A\u5668\u6253\u7B97\u957F\u671F\u8FD0\u884C\u8282\u70B9\uFF0C\u8BF7\u5148\u5B89\u88C5\u5F00\u673A\u670D\u52A1\uFF0C\u4E4B\u540E link \u4F1A\u81EA\u52A8\u5199\u5165\u5B83\u7684\u76EE\u5F55\uFF1A");
14318
+ cliLog(" curl -fsSL https://agent.cd-zq.com/install.sh | sh");
14319
+ cliLog(` \uFF08\u670D\u52A1\u9ED8\u8BA4\u6570\u636E\u76EE\u5F55\uFF1A${installDataDirHint()}\uFF09`);
14320
+ }
14321
+ }
14322
+ return true;
14323
+ } catch (e) {
14324
+ cliErr(`\u63A5\u5165\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
14325
+ return false;
14326
+ } finally {
14327
+ closeOwnedRl();
14328
+ }
13652
14329
  }
13653
14330
 
14331
+ // src/cli/serve.ts
14332
+ init_cjs_shims();
14333
+
14334
+ // src/server/runtime.ts
14335
+ init_cjs_shims();
14336
+ var fs31 = __toESM(require("fs"), 1);
14337
+ var path38 = __toESM(require("path"), 1);
14338
+
14339
+ // src/server/routes.ts
14340
+ init_cjs_shims();
14341
+ var import_http = require("http");
14342
+ var path31 = __toESM(require("path"), 1);
14343
+
13654
14344
  // src/server/auth.ts
13655
14345
  init_cjs_shims();
13656
14346
  var crypto4 = __toESM(require("crypto"), 1);
@@ -13824,34 +14514,34 @@ async function handleHubProxy(req2, res) {
13824
14514
 
13825
14515
  // src/server/fsBrowser.ts
13826
14516
  init_cjs_shims();
13827
- var fs21 = __toESM(require("fs"), 1);
13828
- var os4 = __toESM(require("os"), 1);
13829
- var path26 = __toESM(require("path"), 1);
14517
+ var fs22 = __toESM(require("fs"), 1);
14518
+ var os6 = __toESM(require("os"), 1);
14519
+ var path28 = __toESM(require("path"), 1);
13830
14520
  var MAX_ENTRIES = 2e3;
13831
14521
  var DEFAULT_SEARCH_LIMIT = 200;
13832
14522
  var MAX_SEARCH_LIMIT = 1e3;
13833
14523
  function listDirs(input) {
13834
- const base = (input ?? "").trim() || os4.homedir();
13835
- const target = path26.resolve(base);
14524
+ const base = (input ?? "").trim() || os6.homedir();
14525
+ const target = path28.resolve(base);
13836
14526
  let st;
13837
14527
  try {
13838
- st = fs21.statSync(target);
14528
+ st = fs22.statSync(target);
13839
14529
  } catch {
13840
14530
  throw new Error(`\u8DEF\u5F84\u4E0D\u5B58\u5728: ${target}`);
13841
14531
  }
13842
14532
  if (!st.isDirectory()) throw new Error(`\u4E0D\u662F\u76EE\u5F55: ${target}`);
13843
14533
  let dirents;
13844
14534
  try {
13845
- dirents = fs21.readdirSync(target, { withFileTypes: true });
14535
+ dirents = fs22.readdirSync(target, { withFileTypes: true });
13846
14536
  } catch {
13847
14537
  throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF08\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09: ${target}`);
13848
14538
  }
13849
14539
  const entries = dirents.map((d) => {
13850
- const full = path26.join(target, d.name);
14540
+ const full = path28.join(target, d.name);
13851
14541
  let isDir = d.isDirectory();
13852
14542
  if (d.isSymbolicLink()) {
13853
14543
  try {
13854
- isDir = fs21.statSync(full).isDirectory();
14544
+ isDir = fs22.statSync(full).isDirectory();
13855
14545
  } catch {
13856
14546
  }
13857
14547
  }
@@ -13860,7 +14550,7 @@ function listDirs(input) {
13860
14550
  entries.sort(
13861
14551
  (a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1
13862
14552
  );
13863
- const parentDir = path26.dirname(target);
14553
+ const parentDir = path28.dirname(target);
13864
14554
  return {
13865
14555
  path: target,
13866
14556
  parent: parentDir !== target ? parentDir : null,
@@ -13891,7 +14581,7 @@ var NAME_CACHE_MAX_DIRS = 4;
13891
14581
  var NAME_CACHE_MAX_ENTRIES = 5e4;
13892
14582
  var nameCache = /* @__PURE__ */ new Map();
13893
14583
  function invalidateDirNames(target) {
13894
- nameCache.delete(path26.resolve(target));
14584
+ nameCache.delete(path28.resolve(target));
13895
14585
  }
13896
14586
  async function loadDirNames(target) {
13897
14587
  const now = Date.now();
@@ -13903,7 +14593,7 @@ async function loadDirNames(target) {
13903
14593
  }
13904
14594
  let dirents;
13905
14595
  try {
13906
- dirents = await fs21.promises.readdir(target, { withFileTypes: true });
14596
+ dirents = await fs22.promises.readdir(target, { withFileTypes: true });
13907
14597
  } catch {
13908
14598
  throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF08\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09: ${target}`);
13909
14599
  }
@@ -13926,22 +14616,22 @@ async function loadDirNames(target) {
13926
14616
  return entries;
13927
14617
  }
13928
14618
  async function toDirEntry(dir, e) {
13929
- const full = path26.join(dir, e.name);
14619
+ const full = path28.join(dir, e.name);
13930
14620
  let isDir = e.isDir;
13931
14621
  if (e.isLink) {
13932
14622
  try {
13933
- isDir = (await fs21.promises.stat(full)).isDirectory();
14623
+ isDir = (await fs22.promises.stat(full)).isDirectory();
13934
14624
  } catch {
13935
14625
  }
13936
14626
  }
13937
14627
  return { name: e.name, path: full, type: isDir ? "dir" : "file" };
13938
14628
  }
13939
14629
  async function searchDirs(input, query, limit) {
13940
- const base = (input ?? "").trim() || os4.homedir();
13941
- const target = path26.resolve(base);
14630
+ const base = (input ?? "").trim() || os6.homedir();
14631
+ const target = path28.resolve(base);
13942
14632
  let st;
13943
14633
  try {
13944
- st = await fs21.promises.stat(target);
14634
+ st = await fs22.promises.stat(target);
13945
14635
  } catch {
13946
14636
  throw new Error(`\u8DEF\u5F84\u4E0D\u5B58\u5728: ${target}`);
13947
14637
  }
@@ -13971,7 +14661,7 @@ async function searchDirs(input, query, limit) {
13971
14661
  );
13972
14662
  const hit = scored.slice(0, max);
13973
14663
  const entries = await Promise.all(hit.map(({ entry }) => toDirEntry(target, entry)));
13974
- const parentDir = path26.dirname(target);
14664
+ const parentDir = path28.dirname(target);
13975
14665
  return {
13976
14666
  path: target,
13977
14667
  parent: parentDir !== target ? parentDir : null,
@@ -13982,52 +14672,52 @@ async function searchDirs(input, query, limit) {
13982
14672
  };
13983
14673
  }
13984
14674
  function makeDirIn(parentInput, name) {
13985
- const parent = path26.resolve((parentInput ?? "").trim() || os4.homedir());
14675
+ const parent = path28.resolve((parentInput ?? "").trim() || os6.homedir());
13986
14676
  const clean = (name ?? "").trim();
13987
14677
  if (!clean) throw new Error("\u76EE\u5F55\u540D\u4E0D\u80FD\u4E3A\u7A7A");
13988
14678
  if (clean === "." || clean === "..") throw new Error("\u76EE\u5F55\u540D\u975E\u6CD5");
13989
- const target = path26.resolve(parent, clean);
13990
- const rel = path26.relative(parent, target);
13991
- if (!rel || rel.startsWith("..") || path26.isAbsolute(rel)) {
14679
+ const target = path28.resolve(parent, clean);
14680
+ const rel = path28.relative(parent, target);
14681
+ if (!rel || rel.startsWith("..") || path28.isAbsolute(rel)) {
13992
14682
  throw new Error("\u76EE\u5F55\u540D\u4E0D\u80FD\u8D8A\u51FA\u5F53\u524D\u76EE\u5F55");
13993
14683
  }
13994
14684
  let st = null;
13995
14685
  try {
13996
- st = fs21.statSync(target);
14686
+ st = fs22.statSync(target);
13997
14687
  } catch {
13998
14688
  st = null;
13999
14689
  }
14000
14690
  if (st && !st.isDirectory()) throw new Error(`\u5DF2\u5B58\u5728\u540C\u540D\u6587\u4EF6: ${target}`);
14001
14691
  try {
14002
- fs21.mkdirSync(target, { recursive: true });
14692
+ fs22.mkdirSync(target, { recursive: true });
14003
14693
  } catch (e) {
14004
14694
  throw new Error(`\u521B\u5EFA\u76EE\u5F55\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
14005
14695
  }
14006
14696
  invalidateDirNames(parent);
14007
- return { name: path26.basename(target), path: target, type: "dir" };
14697
+ return { name: path28.basename(target), path: target, type: "dir" };
14008
14698
  }
14009
14699
 
14010
14700
  // src/server/projectFiles.ts
14011
14701
  init_cjs_shims();
14012
- var fs22 = __toESM(require("fs"), 1);
14013
- var path27 = __toESM(require("path"), 1);
14702
+ var fs23 = __toESM(require("fs"), 1);
14703
+ var path29 = __toESM(require("path"), 1);
14014
14704
  var MAX_FILE_BYTES = 2 * 1024 * 1024;
14015
14705
  var MAX_DOWNLOAD_BYTES = 16 * 1024 * 1024;
14016
14706
  var MAX_ENTRIES2 = 2e3;
14017
14707
  function toPosix(p) {
14018
- return p.split(path27.sep).join("/");
14708
+ return p.split(path29.sep).join("/");
14019
14709
  }
14020
14710
  function projectRoot(workspaceRoot) {
14021
14711
  const r = (workspaceRoot ?? "").trim();
14022
14712
  if (!r) {
14023
14713
  throw new Error("\u9879\u76EE\u672A\u8BBE\u7F6E\u5DE5\u4F5C\u533A\u76EE\u5F55\uFF08workspaceRoot\uFF09\uFF0C\u65E0\u6CD5\u6D4F\u89C8\u6587\u4EF6");
14024
14714
  }
14025
- const abs = path27.resolve(r);
14026
- if (!fs22.existsSync(abs)) {
14715
+ const abs = path29.resolve(r);
14716
+ if (!fs23.existsSync(abs)) {
14027
14717
  throw new Error(`\u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728: ${abs}`);
14028
14718
  }
14029
14719
  try {
14030
- return fs22.realpathSync(abs);
14720
+ return fs23.realpathSync(abs);
14031
14721
  } catch {
14032
14722
  return abs;
14033
14723
  }
@@ -14045,37 +14735,37 @@ function listProjectFiles(workspaceRoot, relPath) {
14045
14735
  const dir = resolveWithin(base, relPath ?? "");
14046
14736
  let st;
14047
14737
  try {
14048
- st = fs22.statSync(dir);
14738
+ st = fs23.statSync(dir);
14049
14739
  } catch {
14050
- throw new Error(`\u76EE\u5F55\u4E0D\u5B58\u5728: ${toPosix(path27.relative(base, dir)) || "/"}`);
14740
+ throw new Error(`\u76EE\u5F55\u4E0D\u5B58\u5728: ${toPosix(path29.relative(base, dir)) || "/"}`);
14051
14741
  }
14052
14742
  if (!st.isDirectory()) throw new Error("\u76EE\u6807\u4E0D\u662F\u76EE\u5F55");
14053
14743
  let dirents;
14054
14744
  try {
14055
- dirents = fs22.readdirSync(dir, { withFileTypes: true });
14745
+ dirents = fs23.readdirSync(dir, { withFileTypes: true });
14056
14746
  } catch {
14057
14747
  throw new Error("\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF08\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09");
14058
14748
  }
14059
14749
  const entries = dirents.map((d) => {
14060
- const full = path27.join(dir, d.name);
14750
+ const full = path29.join(dir, d.name);
14061
14751
  let isDir = d.isDirectory();
14062
14752
  if (d.isSymbolicLink()) {
14063
14753
  try {
14064
- isDir = fs22.statSync(full).isDirectory();
14754
+ isDir = fs23.statSync(full).isDirectory();
14065
14755
  } catch {
14066
14756
  }
14067
14757
  }
14068
14758
  let size = 0;
14069
14759
  let mtime = "";
14070
14760
  try {
14071
- const s = fs22.statSync(full);
14761
+ const s = fs23.statSync(full);
14072
14762
  size = s.size;
14073
14763
  mtime = s.mtime.toISOString();
14074
14764
  } catch {
14075
14765
  }
14076
14766
  return {
14077
14767
  name: d.name,
14078
- path: toPosix(path27.relative(base, full)),
14768
+ path: toPosix(path29.relative(base, full)),
14079
14769
  type: isDir ? "dir" : "file",
14080
14770
  size,
14081
14771
  mtime
@@ -14084,12 +14774,12 @@ function listProjectFiles(workspaceRoot, relPath) {
14084
14774
  entries.sort(
14085
14775
  (a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1
14086
14776
  );
14087
- const relDir = toPosix(path27.relative(base, dir));
14088
- const parentAbs = path27.dirname(dir);
14777
+ const relDir = toPosix(path29.relative(base, dir));
14778
+ const parentAbs = path29.dirname(dir);
14089
14779
  return {
14090
14780
  root: base,
14091
14781
  path: relDir,
14092
- parent: isWithinRoot(base, parentAbs) ? toPosix(path27.relative(base, parentAbs)) : null,
14782
+ parent: isWithinRoot(base, parentAbs) ? toPosix(path29.relative(base, parentAbs)) : null,
14093
14783
  entries: entries.slice(0, MAX_ENTRIES2)
14094
14784
  };
14095
14785
  }
@@ -14098,17 +14788,17 @@ function readProjectFile(workspaceRoot, relPath) {
14098
14788
  const file = resolveWithin(base, relPath ?? "");
14099
14789
  let st;
14100
14790
  try {
14101
- st = fs22.statSync(file);
14791
+ st = fs23.statSync(file);
14102
14792
  } catch {
14103
14793
  throw new Error("\u6587\u4EF6\u4E0D\u5B58\u5728");
14104
14794
  }
14105
14795
  if (!st.isFile()) throw new Error("\u76EE\u6807\u4E0D\u662F\u6587\u4EF6");
14106
- const buf = fs22.readFileSync(file);
14796
+ const buf = fs23.readFileSync(file);
14107
14797
  const binary = isBinary(buf);
14108
14798
  const truncated = buf.length > MAX_FILE_BYTES;
14109
14799
  const slice = truncated ? buf.subarray(0, MAX_FILE_BYTES) : buf;
14110
14800
  return {
14111
- path: toPosix(path27.relative(base, file)),
14801
+ path: toPosix(path29.relative(base, file)),
14112
14802
  size: st.size,
14113
14803
  binary,
14114
14804
  truncated,
@@ -14120,7 +14810,7 @@ function downloadProjectFile(workspaceRoot, relPath) {
14120
14810
  const file = resolveWithin(base, relPath ?? "");
14121
14811
  let st;
14122
14812
  try {
14123
- st = fs22.statSync(file);
14813
+ st = fs23.statSync(file);
14124
14814
  } catch {
14125
14815
  throw new Error("\u6587\u4EF6\u4E0D\u5B58\u5728");
14126
14816
  }
@@ -14128,10 +14818,10 @@ function downloadProjectFile(workspaceRoot, relPath) {
14128
14818
  if (st.size > MAX_DOWNLOAD_BYTES) {
14129
14819
  throw new Error(`\u6587\u4EF6\u8D85\u8FC7\u4E0B\u8F7D\u4E0A\u9650\uFF08${MAX_DOWNLOAD_BYTES / (1024 * 1024)}MB\uFF09`);
14130
14820
  }
14131
- const buf = fs22.readFileSync(file);
14821
+ const buf = fs23.readFileSync(file);
14132
14822
  return {
14133
- path: toPosix(path27.relative(base, file)),
14134
- name: path27.basename(file),
14823
+ path: toPosix(path29.relative(base, file)),
14824
+ name: path29.basename(file),
14135
14825
  size: buf.length,
14136
14826
  base64: buf.toString("base64")
14137
14827
  };
@@ -14153,12 +14843,12 @@ function writeProjectFile(workspaceRoot, relPath, content) {
14153
14843
  throw new Error("\u5185\u5BB9\u8D85\u8FC7 2MB \u4E0A\u9650\uFF0C\u65E0\u6CD5\u4FDD\u5B58");
14154
14844
  }
14155
14845
  try {
14156
- fs22.mkdirSync(path27.dirname(file), { recursive: true });
14157
- fs22.writeFileSync(file, buf);
14846
+ fs23.mkdirSync(path29.dirname(file), { recursive: true });
14847
+ fs23.writeFileSync(file, buf);
14158
14848
  } catch (e) {
14159
14849
  throw new Error(`\u5199\u5165\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
14160
14850
  }
14161
- return { path: toPosix(path27.relative(base, file)), size: buf.length };
14851
+ return { path: toPosix(path29.relative(base, file)), size: buf.length };
14162
14852
  }
14163
14853
  function createProjectDir(workspaceRoot, relPath) {
14164
14854
  const base = projectRoot(workspaceRoot);
@@ -14166,26 +14856,26 @@ function createProjectDir(workspaceRoot, relPath) {
14166
14856
  if (!rel) throw new Error("\u76EE\u5F55\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A");
14167
14857
  const dir = resolveWithin(base, rel);
14168
14858
  try {
14169
- fs22.mkdirSync(dir, { recursive: true });
14859
+ fs23.mkdirSync(dir, { recursive: true });
14170
14860
  } catch (e) {
14171
14861
  throw new Error(`\u521B\u5EFA\u76EE\u5F55\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
14172
14862
  }
14173
- return { path: toPosix(path27.relative(base, dir)) };
14863
+ return { path: toPosix(path29.relative(base, dir)) };
14174
14864
  }
14175
14865
  function createProjectFile(workspaceRoot, relPath, content = "") {
14176
14866
  const base = projectRoot(workspaceRoot);
14177
14867
  const rel = (relPath ?? "").trim();
14178
14868
  if (!rel) throw new Error("\u6587\u4EF6\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A");
14179
14869
  const file = resolveWithin(base, rel);
14180
- if (fs22.existsSync(file)) throw new Error("\u6587\u4EF6\u5DF2\u5B58\u5728");
14870
+ if (fs23.existsSync(file)) throw new Error("\u6587\u4EF6\u5DF2\u5B58\u5728");
14181
14871
  const buf = Buffer.from(String(content ?? ""), "utf-8");
14182
14872
  try {
14183
- fs22.mkdirSync(path27.dirname(file), { recursive: true });
14184
- fs22.writeFileSync(file, buf);
14873
+ fs23.mkdirSync(path29.dirname(file), { recursive: true });
14874
+ fs23.writeFileSync(file, buf);
14185
14875
  } catch (e) {
14186
14876
  throw new Error(`\u521B\u5EFA\u6587\u4EF6\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
14187
14877
  }
14188
- return { path: toPosix(path27.relative(base, file)), size: buf.length };
14878
+ return { path: toPosix(path29.relative(base, file)), size: buf.length };
14189
14879
  }
14190
14880
  function searchProjectFiles(workspaceRoot, query) {
14191
14881
  const base = projectRoot(workspaceRoot);
@@ -14208,7 +14898,7 @@ function searchProjectFiles(workspaceRoot, query) {
14208
14898
  if (out.length >= LIMIT) return;
14209
14899
  let dirents;
14210
14900
  try {
14211
- dirents = fs22.readdirSync(dir, { withFileTypes: true });
14901
+ dirents = fs23.readdirSync(dir, { withFileTypes: true });
14212
14902
  } catch {
14213
14903
  return;
14214
14904
  }
@@ -14219,7 +14909,7 @@ function searchProjectFiles(workspaceRoot, query) {
14219
14909
  let isDir = d.isDirectory();
14220
14910
  if (d.isSymbolicLink()) {
14221
14911
  try {
14222
- isDir = fs22.statSync(path27.join(dir, d.name)).isDirectory();
14912
+ isDir = fs23.statSync(path29.join(dir, d.name)).isDirectory();
14223
14913
  } catch {
14224
14914
  isDir = false;
14225
14915
  }
@@ -14227,7 +14917,7 @@ function searchProjectFiles(workspaceRoot, query) {
14227
14917
  if (d.name.toLowerCase().includes(q)) {
14228
14918
  out.push({ path: rel, type: isDir ? "dir" : "file" });
14229
14919
  }
14230
- if (isDir) walk(path27.join(dir, d.name), rel);
14920
+ if (isDir) walk(path29.join(dir, d.name), rel);
14231
14921
  }
14232
14922
  };
14233
14923
  walk(base, "");
@@ -14236,8 +14926,8 @@ function searchProjectFiles(workspaceRoot, query) {
14236
14926
 
14237
14927
  // src/server/projectOps.ts
14238
14928
  init_cjs_shims();
14239
- var os5 = __toESM(require("os"), 1);
14240
- var path28 = __toESM(require("path"), 1);
14929
+ var os7 = __toESM(require("os"), 1);
14930
+ var path30 = __toESM(require("path"), 1);
14241
14931
  var MAX_STDOUT2 = 2e4;
14242
14932
  var MAX_STDERR2 = 1e4;
14243
14933
  var MAX_BUFFER3 = 1024 * 1024 * 5;
@@ -14251,7 +14941,7 @@ function resolveInvocation2(spec, prepared) {
14251
14941
  }
14252
14942
  function workRoot(workspaceRoot) {
14253
14943
  const r = (workspaceRoot ?? "").trim();
14254
- return r ? path28.resolve(r) : os5.homedir();
14944
+ return r ? path30.resolve(r) : os7.homedir();
14255
14945
  }
14256
14946
  async function runProjectCommand(workspaceRoot, command, timeoutSeconds) {
14257
14947
  const cmd = String(command ?? "");
@@ -14562,11 +15252,11 @@ function projectStats(manager, tenantId) {
14562
15252
 
14563
15253
  // src/server/nodeHost.ts
14564
15254
  init_cjs_shims();
14565
- var fs23 = __toESM(require("fs"), 1);
14566
- var os6 = __toESM(require("os"), 1);
15255
+ var fs24 = __toESM(require("fs"), 1);
15256
+ var os8 = __toESM(require("os"), 1);
14567
15257
  function diskInfo(pathStr) {
14568
15258
  try {
14569
- const st = fs23.statfsSync(pathStr);
15259
+ const st = fs24.statfsSync(pathStr);
14570
15260
  return {
14571
15261
  path: pathStr,
14572
15262
  total: Number(st.blocks) * Number(st.bsize),
@@ -14578,7 +15268,7 @@ function diskInfo(pathStr) {
14578
15268
  }
14579
15269
  function networkInfo() {
14580
15270
  const out = [];
14581
- const ifaces = os6.networkInterfaces();
15271
+ const ifaces = os8.networkInterfaces();
14582
15272
  for (const [name, list] of Object.entries(ifaces)) {
14583
15273
  for (const info of list ?? []) {
14584
15274
  out.push({
@@ -14629,23 +15319,23 @@ function overview(store, manager) {
14629
15319
  return { projects, sessions: { total, running, waiting, items }, usage: { tokensIn, tokensOut } };
14630
15320
  }
14631
15321
  function buildNodeHost(store, manager, diskPath) {
14632
- const cpus2 = os6.cpus();
15322
+ const cpus2 = os8.cpus();
14633
15323
  const ov = overview(store, manager);
14634
15324
  return {
14635
15325
  nodeId: store.loadNodeId() ?? "",
14636
- hostname: os6.hostname(),
15326
+ hostname: os8.hostname(),
14637
15327
  platform: process.platform,
14638
15328
  arch: process.arch,
14639
- release: os6.release(),
14640
- uptimeSec: Math.round(os6.uptime()),
14641
- version: "0.2.10",
15329
+ release: os8.release(),
15330
+ uptimeSec: Math.round(os8.uptime()),
15331
+ version: "0.2.12",
14642
15332
  mode: process.env.NODE_MODE ?? "standalone",
14643
15333
  cpu: {
14644
15334
  count: cpus2.length,
14645
15335
  model: cpus2[0]?.model ?? "",
14646
- loadAvg: os6.loadavg().map((n) => Math.round(n * 100) / 100)
15336
+ loadAvg: os8.loadavg().map((n) => Math.round(n * 100) / 100)
14647
15337
  },
14648
- memory: { total: os6.totalmem(), free: os6.freemem() },
15338
+ memory: { total: os8.totalmem(), free: os8.freemem() },
14649
15339
  network: networkInfo(),
14650
15340
  disk: diskInfo(diskPath || process.cwd()),
14651
15341
  ...ov
@@ -15008,7 +15698,7 @@ function createApp(store, manager, opts = {}) {
15008
15698
  ok(res, opts.fleet.status());
15009
15699
  }
15010
15700
  },
15011
- // 运行时热接入:凭一次性注册码接入 Hub(落盘 fleet.json + 立即连接,免重启)
15701
+ // 运行时热接入:凭一次性注册码或 Hub 账号接入(落盘 fleet.json + 立即连接,免重启)
15012
15702
  {
15013
15703
  method: "POST",
15014
15704
  pattern: ["fleet", "enroll"],
@@ -15020,15 +15710,42 @@ function createApp(store, manager, opts = {}) {
15020
15710
  const raw = body ?? {};
15021
15711
  const hubUrl = String(raw.hubUrl ?? "").trim().replace(/\/$/, "");
15022
15712
  const code = String(raw.code ?? "").trim();
15023
- if (!hubUrl || !code) {
15024
- throw new ApiError(400, "bad_request", "hubUrl \u4E0E code \u5FC5\u586B");
15713
+ const email = String(raw.email ?? "").trim();
15714
+ const phone = String(raw.phone ?? "").trim();
15715
+ const password = String(raw.password ?? "");
15716
+ const smsCode = String(raw.smsCode ?? "").trim();
15717
+ const hasAccount = Boolean(email || phone);
15718
+ if (!hubUrl) {
15719
+ throw new ApiError(400, "bad_request", "hubUrl \u5FC5\u586B");
15720
+ }
15721
+ if (code && hasAccount) {
15722
+ throw new ApiError(400, "bad_request", "code \u4E0E\u8D26\u53F7\u51ED\u636E\u53EA\u80FD\u4E8C\u9009\u4E00");
15723
+ }
15724
+ if (!code && !hasAccount) {
15725
+ throw new ApiError(400, "bad_request", "\u9700\u8981 code\uFF0C\u6216 email/phone + password/smsCode");
15726
+ }
15727
+ if (hasAccount && !password && !smsCode) {
15728
+ throw new ApiError(400, "bad_request", "\u8D26\u53F7\u63A5\u5165\u9700\u8981 password \u6216 smsCode");
15025
15729
  }
15026
15730
  const label = typeof raw.label === "string" ? raw.label.trim() : void 0;
15027
15731
  try {
15028
- ok(res, await opts.fleet.enroll({ hubUrl, code, label }));
15732
+ ok(
15733
+ res,
15734
+ await opts.fleet.enroll({
15735
+ hubUrl,
15736
+ code: code || void 0,
15737
+ account: hasAccount ? {
15738
+ email: email || void 0,
15739
+ phone: phone || void 0,
15740
+ password: password || void 0,
15741
+ smsCode: smsCode || void 0
15742
+ } : void 0,
15743
+ label
15744
+ })
15745
+ );
15029
15746
  } catch (e) {
15030
15747
  const code2 = e.code;
15031
- const status = code2 === "code_expired" ? 410 : code2 === "code_used" || code2 === "node_taken" ? 409 : code2 === "not_found" ? 404 : code2 === "plan_limit" || code2 === "subscription_required" ? 402 : 400;
15748
+ const status = code2 === "code_expired" ? 410 : code2 === "unauthorized" ? 401 : code2 === "account_disabled" ? 403 : code2 === "code_used" || code2 === "node_taken" ? 409 : code2 === "not_found" ? 404 : code2 === "plan_limit" || code2 === "subscription_required" ? 402 : 400;
15032
15749
  const msg = e instanceof Error ? e.message : String(e);
15033
15750
  throw new ApiError(
15034
15751
  status,
@@ -15213,7 +15930,7 @@ function createApp(store, manager, opts = {}) {
15213
15930
  applyProvidersPatch(tenant, raw);
15214
15931
  if (typeof raw.workspaceRoot === "string") {
15215
15932
  const ws = raw.workspaceRoot.trim();
15216
- const next = ws ? path29.resolve(ws) : void 0;
15933
+ const next = ws ? path31.resolve(ws) : void 0;
15217
15934
  if (tenant.workspaceRoot) {
15218
15935
  if (next !== tenant.workspaceRoot) {
15219
15936
  throw new ApiError(
@@ -16200,7 +16917,7 @@ function createApp(store, manager, opts = {}) {
16200
16917
  json(res, 200, {
16201
16918
  status: "ok",
16202
16919
  // 版本让运维/探针能确认线上跑的是哪个构建(更新与回滚据此判断)
16203
- version: "0.2.10",
16920
+ version: "0.2.12",
16204
16921
  features: { mcp: hasServerMcpConfig() }
16205
16922
  });
16206
16923
  return;
@@ -16295,7 +17012,7 @@ function createApp(store, manager, opts = {}) {
16295
17012
 
16296
17013
  // src/server/fleet/FleetNodeBridge.ts
16297
17014
  init_cjs_shims();
16298
- var os7 = __toESM(require("os"), 1);
17015
+ var os9 = __toESM(require("os"), 1);
16299
17016
  var import_node_perf_hooks = require("perf_hooks");
16300
17017
 
16301
17018
  // src/server/fleet/FleetCommands.ts
@@ -17489,7 +18206,7 @@ function withTimeout(p, ms) {
17489
18206
  }
17490
18207
  function primaryIp() {
17491
18208
  let v6;
17492
- for (const infos of Object.values(os7.networkInterfaces())) {
18209
+ for (const infos of Object.values(os9.networkInterfaces())) {
17493
18210
  for (const info of infos ?? []) {
17494
18211
  if (info.internal) continue;
17495
18212
  if (info.family === "IPv4") return info.address;
@@ -17503,7 +18220,7 @@ function primaryIp() {
17503
18220
  var FleetNodeBridge = class {
17504
18221
  constructor(opts) {
17505
18222
  this.opts = opts;
17506
- this.nodeId = opts.nodeId ?? ensureNodeId2(opts.deps.store);
18223
+ this.nodeId = opts.nodeId ?? ensureNodeId(opts.deps.store);
17507
18224
  }
17508
18225
  opts;
17509
18226
  /** 是否成功连接过(区分「一直连不上」与「连上后断线」) */
@@ -17838,7 +18555,7 @@ var FleetNodeBridge = class {
17838
18555
  label: this.opts.deps.store.loadNodeLabel() ?? void 0,
17839
18556
  platform: process.platform,
17840
18557
  mode: process.env.NODE_MODE ?? "standalone",
17841
- version: "0.2.10",
18558
+ version: "0.2.12",
17842
18559
  ip: primaryIp()
17843
18560
  };
17844
18561
  }
@@ -17893,7 +18610,7 @@ var FleetNodeBridge = class {
17893
18610
  label: this.opts.deps.store.loadNodeLabel() ?? void 0,
17894
18611
  platform: process.platform,
17895
18612
  mode: process.env.NODE_MODE ?? "standalone",
17896
- version: "0.2.10",
18613
+ version: "0.2.12",
17897
18614
  ip: primaryIp(),
17898
18615
  projects,
17899
18616
  totals,
@@ -18081,28 +18798,6 @@ var FleetNodeBridge = class {
18081
18798
  }
18082
18799
  };
18083
18800
 
18084
- // src/server/fleet-config.ts
18085
- init_cjs_shims();
18086
- function fleetConfigFromEnv(store) {
18087
- const fleetToken = process.env.FLEET_TOKEN;
18088
- const fleetSecret = process.env.FLEET_SECRET;
18089
- if (!fleetToken || !fleetSecret) return null;
18090
- return {
18091
- hubUrl: process.env.HUB_URL,
18092
- fleetToken,
18093
- fleetSecret,
18094
- nodeId: ensureNodeId2(store),
18095
- gatewayUrl: process.env.FLEET_GATEWAY_URL
18096
- };
18097
- }
18098
- function resolveFleetConfig(store) {
18099
- const saved = store.loadFleetCredential();
18100
- if (saved?.fleetToken && saved.fleetSecret) {
18101
- return { ...saved, nodeId: saved.nodeId || ensureNodeId2(store) };
18102
- }
18103
- return fleetConfigFromEnv(store);
18104
- }
18105
-
18106
18801
  // src/server/notify/index.ts
18107
18802
  init_cjs_shims();
18108
18803
 
@@ -18662,8 +19357,8 @@ init_cjs_shims();
18662
19357
 
18663
19358
  // src/server/qq/bindings.ts
18664
19359
  init_cjs_shims();
18665
- var fs24 = __toESM(require("fs"), 1);
18666
- var path30 = __toESM(require("path"), 1);
19360
+ var fs25 = __toESM(require("fs"), 1);
19361
+ var path32 = __toESM(require("path"), 1);
18667
19362
  function conversationKey(target) {
18668
19363
  switch (target.kind) {
18669
19364
  case "c2c":
@@ -18702,12 +19397,12 @@ var QqBindingsStore = class _QqBindingsStore {
18702
19397
  cache = null;
18703
19398
  /** 基于 DATA_DIR 构造 */
18704
19399
  static in(dataDir) {
18705
- return new _QqBindingsStore(path30.join(dataDir, "qq", "bindings.json"));
19400
+ return new _QqBindingsStore(path32.join(dataDir, "qq", "bindings.json"));
18706
19401
  }
18707
19402
  load() {
18708
19403
  if (this.cache) return this.cache;
18709
19404
  try {
18710
- const raw = JSON.parse(fs24.readFileSync(this.file, "utf-8"));
19405
+ const raw = JSON.parse(fs25.readFileSync(this.file, "utf-8"));
18711
19406
  this.cache = {
18712
19407
  conversations: raw.conversations ?? {}
18713
19408
  };
@@ -18739,8 +19434,8 @@ var QqBindingsStore = class _QqBindingsStore {
18739
19434
  return null;
18740
19435
  }
18741
19436
  persist() {
18742
- fs24.mkdirSync(path30.dirname(this.file), { recursive: true });
18743
- fs24.writeFileSync(this.file, JSON.stringify(this.cache, null, 2), "utf-8");
19437
+ fs25.mkdirSync(path32.dirname(this.file), { recursive: true });
19438
+ fs25.writeFileSync(this.file, JSON.stringify(this.cache, null, 2), "utf-8");
18744
19439
  }
18745
19440
  };
18746
19441
 
@@ -19152,8 +19847,8 @@ ${lines.join("\n")}`, msgId);
19152
19847
 
19153
19848
  // src/server/qq/QqManager.ts
19154
19849
  init_cjs_shims();
19155
- var fs25 = __toESM(require("fs"), 1);
19156
- var path31 = __toESM(require("path"), 1);
19850
+ var fs26 = __toESM(require("fs"), 1);
19851
+ var path33 = __toESM(require("path"), 1);
19157
19852
  var QqManager = class {
19158
19853
  constructor(opts) {
19159
19854
  this.opts = opts;
@@ -19162,7 +19857,7 @@ var QqManager = class {
19162
19857
  adapter = null;
19163
19858
  config = null;
19164
19859
  get configFile() {
19165
- return path31.join(this.opts.dataDir, "qq", "config.json");
19860
+ return path33.join(this.opts.dataDir, "qq", "config.json");
19166
19861
  }
19167
19862
  /** 启动:读配置(env 优先,其次文件),有则建连接 */
19168
19863
  start() {
@@ -19200,7 +19895,7 @@ var QqManager = class {
19200
19895
  /** 清空配置:删除文件 + 断开 */
19201
19896
  clearConfig() {
19202
19897
  try {
19203
- fs25.rmSync(this.configFile, { force: true });
19898
+ fs26.rmSync(this.configFile, { force: true });
19204
19899
  } catch {
19205
19900
  }
19206
19901
  this.stop();
@@ -19242,7 +19937,7 @@ var QqManager = class {
19242
19937
  }
19243
19938
  _readConfigFile() {
19244
19939
  try {
19245
- const raw = JSON.parse(fs25.readFileSync(this.configFile, "utf-8"));
19940
+ const raw = JSON.parse(fs26.readFileSync(this.configFile, "utf-8"));
19246
19941
  if (raw.appId && raw.appSecret) {
19247
19942
  return {
19248
19943
  appId: raw.appId,
@@ -19255,8 +19950,8 @@ var QqManager = class {
19255
19950
  return null;
19256
19951
  }
19257
19952
  _persistConfig(cfg) {
19258
- fs25.mkdirSync(path31.dirname(this.configFile), { recursive: true });
19259
- fs25.writeFileSync(this.configFile, JSON.stringify(cfg, null, 2), "utf-8");
19953
+ fs26.mkdirSync(path33.dirname(this.configFile), { recursive: true });
19954
+ fs26.writeFileSync(this.configFile, JSON.stringify(cfg, null, 2), "utf-8");
19260
19955
  }
19261
19956
  };
19262
19957
 
@@ -19305,16 +20000,16 @@ var PairingCodeStore = class {
19305
20000
  // src/update/controller.ts
19306
20001
  init_cjs_shims();
19307
20002
  var fs30 = __toESM(require("fs"), 1);
19308
- var path36 = __toESM(require("path"), 1);
20003
+ var path37 = __toESM(require("path"), 1);
19309
20004
 
19310
20005
  // src/update/downloader.ts
19311
20006
  init_cjs_shims();
19312
20007
  var import_node_crypto5 = require("crypto");
19313
- var fs26 = __toESM(require("fs"), 1);
20008
+ var fs27 = __toESM(require("fs"), 1);
19314
20009
  var import_node_stream2 = require("stream");
19315
20010
  var import_promises2 = require("stream/promises");
19316
- var import_node_child_process5 = require("child_process");
19317
- var path32 = __toESM(require("path"), 1);
20011
+ var import_node_child_process6 = require("child_process");
20012
+ var path34 = __toESM(require("path"), 1);
19318
20013
 
19319
20014
  // src/update/manifest.ts
19320
20015
  init_cjs_shims();
@@ -19480,267 +20175,97 @@ async function fetchManifest(url, publicKey, opts = {}) {
19480
20175
  }
19481
20176
  let parsed;
19482
20177
  try {
19483
- parsed = JSON.parse(raw.toString("utf-8"));
19484
- } catch (err) {
19485
- return { error: `\u6E05\u5355\u4E0D\u662F\u5408\u6CD5 JSON\uFF1A${err.message}` };
19486
- }
19487
- const { manifest, error: error2 } = parseManifest(parsed, opts);
19488
- if (!manifest) return { error: error2 };
19489
- return { manifest, raw };
19490
- }
19491
- async function downloadBinary(bin, destPath, log = () => {
19492
- }) {
19493
- const partPath = `${destPath}.part`;
19494
- fs26.mkdirSync(path32.dirname(destPath), { recursive: true });
19495
- fs26.rmSync(partPath, { force: true });
19496
- let res;
19497
- try {
19498
- res = await fetch(bin.url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS2) });
19499
- } catch (err) {
19500
- return { error: `\u4E0B\u8F7D\u5931\u8D25\uFF1A${err.message}` };
19501
- }
19502
- if (!res.ok) return { error: `\u4E0B\u8F7D\u5931\u8D25 HTTP ${res.status}` };
19503
- if (!res.body) return { error: "\u4E0B\u8F7D\u54CD\u5E94\u6CA1\u6709 body" };
19504
- const expected = bin.size && bin.size > 0 ? bin.size : void 0;
19505
- const limit = expected ? Math.max(expected * 2, 1024 * 1024) : MAX_BINARY_BYTES;
19506
- const hash = (0, import_node_crypto5.createHash)("sha256");
19507
- let bytes = 0;
19508
- try {
19509
- const source = import_node_stream2.Readable.fromWeb(res.body);
19510
- await (0, import_promises2.pipeline)(
19511
- source,
19512
- async function* (chunks) {
19513
- for await (const chunk of chunks) {
19514
- bytes += chunk.length;
19515
- if (bytes > limit) throw new Error(`\u4EA7\u7269\u8D85\u8FC7\u5927\u5C0F\u4E0A\u9650\uFF08>${limit} \u5B57\u8282\uFF09`);
19516
- hash.update(chunk);
19517
- yield chunk;
19518
- }
19519
- },
19520
- fs26.createWriteStream(partPath, { mode: 493 })
19521
- );
19522
- } catch (err) {
19523
- fs26.rmSync(partPath, { force: true });
19524
- return { error: `\u4E0B\u8F7D\u4E2D\u65AD\uFF1A${err.message}` };
19525
- }
19526
- const actual = hash.digest("hex");
19527
- if (actual !== bin.sha256) {
19528
- fs26.rmSync(partPath, { force: true });
19529
- return { error: `SHA-256 \u4E0D\u5339\u914D\uFF08\u671F\u671B ${bin.sha256.slice(0, 12)}\u2026\uFF0C\u5B9E\u9645 ${actual.slice(0, 12)}\u2026\uFF09` };
19530
- }
19531
- if (expected && bytes !== expected) {
19532
- fs26.rmSync(partPath, { force: true });
19533
- return { error: `\u4EA7\u7269\u5927\u5C0F\u4E0D\u5339\u914D\uFF08\u671F\u671B ${expected}\uFF0C\u5B9E\u9645 ${bytes}\uFF09` };
19534
- }
19535
- fs26.rmSync(destPath, { force: true });
19536
- fs26.renameSync(partPath, destPath);
19537
- fs26.chmodSync(destPath, 493);
19538
- log(`\u5DF2\u4E0B\u8F7D ${path32.basename(destPath)}\uFF08${Math.round(bytes / 1024 / 1024)}MB\uFF0C\u6821\u9A8C\u901A\u8FC7\uFF09`);
19539
- return { bytes };
19540
- }
19541
- function selfCheck(binPath, expectedVersion) {
19542
- try {
19543
- const r = (0, import_node_child_process5.spawnSync)(binPath, ["--version"], {
19544
- timeout: 2e4,
19545
- encoding: "utf-8",
19546
- env: { ...process.env, ZQAGENT_AUTO_UPDATE: "0" }
19547
- });
19548
- if (r.error) return { ok: false, error: `\u6267\u884C\u81EA\u68C0\u5931\u8D25\uFF1A${r.error.message}` };
19549
- if (r.status !== 0) return { ok: false, error: `\u81EA\u68C0\u9000\u51FA\u7801 ${r.status}\uFF1A${(r.stderr || "").trim()}` };
19550
- const out = `${r.stdout ?? ""}${r.stderr ?? ""}`;
19551
- if (!out.includes(expectedVersion)) {
19552
- return { ok: false, error: `\u81EA\u68C0\u7248\u672C\u4E0D\u7B26\uFF08\u671F\u671B ${expectedVersion}\uFF0C\u8F93\u51FA\uFF1A${out.trim().slice(0, 120)}\uFF09` };
19553
- }
19554
- return { ok: true };
19555
- } catch (err) {
19556
- return { ok: false, error: `\u81EA\u68C0\u5F02\u5E38\uFF1A${err.message}` };
19557
- }
19558
- }
19559
-
19560
- // src/update/platform.ts
19561
- init_cjs_shims();
19562
- var fs27 = __toESM(require("fs"), 1);
19563
- var os8 = __toESM(require("os"), 1);
19564
- var path33 = __toESM(require("path"), 1);
19565
- var import_node_child_process6 = require("child_process");
19566
- var import_node_module3 = require("module");
19567
- function currentVersion() {
19568
- const injected = "0.2.10"?.trim();
19569
- if (injected) return injected;
19570
- try {
19571
- const req2 = (0, import_node_module3.createRequire)(importMetaUrl);
19572
- const pkg = req2("../../package.json");
19573
- if (pkg?.version) return pkg.version;
19574
- } catch {
19575
- }
19576
- return "dev";
19577
- }
19578
- function isSeaRuntime() {
19579
- if (process.env.ZQAGENT_SEA === "1") return true;
19580
- const base = path33.basename(process.execPath).toLowerCase().replace(/\.exe$/, "");
19581
- return base !== "node" && base !== "nodejs";
19582
- }
19583
- var DEFAULT_SERVICE_NAME = "zqagent";
19584
- var DEFAULT_SERVICE_LABEL = "com.zqagent.node";
19585
- function serviceManagerFromCgroup(cgroup, serviceName = DEFAULT_SERVICE_NAME) {
19586
- const unitName = `${serviceName}.service`;
19587
- for (const line of cgroup.split("\n")) {
19588
- const idx = line.lastIndexOf(":");
19589
- if (idx < 0) continue;
19590
- const unitPath = line.slice(idx + 1).trim();
19591
- if (unitPath.split("/").includes(unitName)) return "systemd";
19592
- }
19593
- return null;
19594
- }
19595
- function launchdDomain() {
19596
- const uid = typeof process.getuid === "function" ? process.getuid() : -1;
19597
- return uid === 0 ? "system" : `gui/${uid}`;
19598
- }
19599
- function launchdLabel() {
19600
- return process.env.ZQAGENT_SERVICE_LABEL?.trim() || DEFAULT_SERVICE_LABEL;
19601
- }
19602
- function launchdPlistPath() {
19603
- const label = launchdLabel();
19604
- const uid = typeof process.getuid === "function" ? process.getuid() : -1;
19605
- if (uid === 0) return `/Library/LaunchDaemons/${label}.plist`;
19606
- return path33.join(os8.homedir(), "Library", "LaunchAgents", `${label}.plist`);
19607
- }
19608
- function serviceManagerFromLaunchctl(output, pid, execPath, sea = true) {
19609
- const pidMatch = /^\s*pid = (\d+)\s*$/m.exec(output);
19610
- if (pidMatch && Number(pidMatch[1]) === pid) return "launchd";
19611
- const program = /^\s*program = (.+?)\s*$/m.exec(output);
19612
- if (program) {
19613
- const shown = program[1].replace(/^"|"$/g, "");
19614
- if (path33.resolve(shown) === path33.resolve(execPath)) return "launchd";
19615
- }
19616
- if (!sea && /^\s*state = /m.test(output)) return "launchd";
19617
- return null;
19618
- }
19619
- function parseLaunchdEnvironment(xml) {
19620
- const out = {};
19621
- const marker = xml.indexOf("<key>EnvironmentVariables</key>");
19622
- if (marker < 0) return out;
19623
- const open = xml.indexOf("<dict>", marker);
19624
- if (open < 0) return out;
19625
- const close = xml.indexOf("</dict>", open);
19626
- if (close < 0) return out;
19627
- const body = xml.slice(open + "<dict>".length, close);
19628
- for (const m of body.matchAll(/<key>([^<]+)<\/key>\s*<string>([^<]*)<\/string>/g)) {
19629
- out[m[1]] = m[2];
19630
- }
19631
- return out;
19632
- }
19633
- function detectServiceManager() {
19634
- if (process.platform === "darwin") return detectLaunchd();
19635
- if (process.platform !== "linux") return null;
19636
- const serviceName = process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;
19637
- try {
19638
- return serviceManagerFromCgroup(
19639
- fs27.readFileSync("/proc/self/cgroup", "utf8"),
19640
- serviceName
19641
- );
19642
- } catch {
19643
- return null;
19644
- }
19645
- }
19646
- function detectLaunchd() {
19647
- try {
19648
- const res = (0, import_node_child_process6.spawnSync)(
19649
- "launchctl",
19650
- ["print", `${launchdDomain()}/${launchdLabel()}`],
19651
- { encoding: "utf8", timeout: 3e3 }
19652
- );
19653
- if (res.status !== 0 || !res.stdout) return null;
19654
- return serviceManagerFromLaunchctl(
19655
- res.stdout,
19656
- process.pid,
19657
- process.execPath,
19658
- isSeaRuntime()
19659
- );
19660
- } catch {
19661
- return null;
19662
- }
19663
- }
19664
- function serviceManager() {
19665
- const v = process.env.ZQAGENT_SERVICE_MANAGER?.trim().toLowerCase();
19666
- if (v === "systemd" || v === "launchd") return v;
19667
- return detectServiceManager();
19668
- }
19669
- function parseServiceEnvironment(raw) {
19670
- const out = {};
19671
- for (const token of raw.trim().split(/\s+/)) {
19672
- const idx = token.indexOf("=");
19673
- if (idx > 0) out[token.slice(0, idx)] = token.slice(idx + 1);
19674
- }
19675
- return out;
19676
- }
19677
- var cachedServiceEnv = null;
19678
- function readServiceEnvironment() {
19679
- const serviceName = process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;
19680
- try {
19681
- const res = (0, import_node_child_process6.spawnSync)(
19682
- "systemctl",
19683
- ["show", serviceName, "--property=Environment", "--value", "--no-pager"],
19684
- { encoding: "utf8", timeout: 3e3 }
19685
- );
19686
- if (res.status !== 0 || !res.stdout) return {};
19687
- return parseServiceEnvironment(res.stdout);
19688
- } catch {
19689
- return {};
20178
+ parsed = JSON.parse(raw.toString("utf-8"));
20179
+ } catch (err) {
20180
+ return { error: `\u6E05\u5355\u4E0D\u662F\u5408\u6CD5 JSON\uFF1A${err.message}` };
19690
20181
  }
20182
+ const { manifest, error: error2 } = parseManifest(parsed, opts);
20183
+ if (!manifest) return { error: error2 };
20184
+ return { manifest, raw };
19691
20185
  }
19692
- function serviceEnvValue(key) {
19693
- if (process.platform === "darwin") {
19694
- if (serviceManager() !== "launchd") return void 0;
19695
- cachedLaunchdEnv ??= readLaunchdEnvironment();
19696
- return cachedLaunchdEnv[key];
20186
+ async function downloadBinary(bin, destPath, log = () => {
20187
+ }) {
20188
+ const partPath = `${destPath}.part`;
20189
+ fs27.mkdirSync(path34.dirname(destPath), { recursive: true });
20190
+ fs27.rmSync(partPath, { force: true });
20191
+ let res;
20192
+ try {
20193
+ res = await fetch(bin.url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS2) });
20194
+ } catch (err) {
20195
+ return { error: `\u4E0B\u8F7D\u5931\u8D25\uFF1A${err.message}` };
19697
20196
  }
19698
- if (process.platform !== "linux") return void 0;
19699
- if (serviceManager() !== "systemd") return void 0;
19700
- cachedServiceEnv ??= readServiceEnvironment();
19701
- return cachedServiceEnv[key];
19702
- }
19703
- var cachedLaunchdEnv = null;
19704
- function readLaunchdEnvironment() {
20197
+ if (!res.ok) return { error: `\u4E0B\u8F7D\u5931\u8D25 HTTP ${res.status}` };
20198
+ if (!res.body) return { error: "\u4E0B\u8F7D\u54CD\u5E94\u6CA1\u6709 body" };
20199
+ const expected = bin.size && bin.size > 0 ? bin.size : void 0;
20200
+ const limit = expected ? Math.max(expected * 2, 1024 * 1024) : MAX_BINARY_BYTES;
20201
+ const hash = (0, import_node_crypto5.createHash)("sha256");
20202
+ let bytes = 0;
19705
20203
  try {
19706
- return parseLaunchdEnvironment(fs27.readFileSync(launchdPlistPath(), "utf8"));
19707
- } catch {
19708
- return {};
20204
+ const source = import_node_stream2.Readable.fromWeb(res.body);
20205
+ await (0, import_promises2.pipeline)(
20206
+ source,
20207
+ async function* (chunks) {
20208
+ for await (const chunk of chunks) {
20209
+ bytes += chunk.length;
20210
+ if (bytes > limit) throw new Error(`\u4EA7\u7269\u8D85\u8FC7\u5927\u5C0F\u4E0A\u9650\uFF08>${limit} \u5B57\u8282\uFF09`);
20211
+ hash.update(chunk);
20212
+ yield chunk;
20213
+ }
20214
+ },
20215
+ fs27.createWriteStream(partPath, { mode: 493 })
20216
+ );
20217
+ } catch (err) {
20218
+ fs27.rmSync(partPath, { force: true });
20219
+ return { error: `\u4E0B\u8F7D\u4E2D\u65AD\uFF1A${err.message}` };
19709
20220
  }
19710
- }
19711
- function selfUpdateCapability(hasPublicKey) {
19712
- if (!isSeaRuntime()) {
19713
- return {
19714
- ok: false,
19715
- reason: "\u5F53\u524D\u4E0D\u662F\u5355\u6587\u4EF6\uFF08SEA\uFF09\u5F62\u6001\uFF1A\u8BF7\u7528 npm \u5347\u7EA7\uFF08npm i -g @zqagent/core@latest\uFF09"
19716
- };
20221
+ const actual = hash.digest("hex");
20222
+ if (actual !== bin.sha256) {
20223
+ fs27.rmSync(partPath, { force: true });
20224
+ return { error: `SHA-256 \u4E0D\u5339\u914D\uFF08\u671F\u671B ${bin.sha256.slice(0, 12)}\u2026\uFF0C\u5B9E\u9645 ${actual.slice(0, 12)}\u2026\uFF09` };
19717
20225
  }
19718
- if (!serviceManager()) {
19719
- return {
19720
- ok: false,
19721
- reason: "\u672A\u88AB systemd/launchd \u6258\u7BA1\uFF1A\u8BF7\u7528 deploy/service/install.sh \u5B89\u88C5\uFF0C\u6216\u624B\u52A8\u5347\u7EA7"
19722
- };
20226
+ if (expected && bytes !== expected) {
20227
+ fs27.rmSync(partPath, { force: true });
20228
+ return { error: `\u4EA7\u7269\u5927\u5C0F\u4E0D\u5339\u914D\uFF08\u671F\u671B ${expected}\uFF0C\u5B9E\u9645 ${bytes}\uFF09` };
19723
20229
  }
19724
- if (!hasPublicKey) {
19725
- return { ok: false, reason: "\u672A\u914D\u7F6E ZQAGENT_UPDATE_PUBKEY\uFF0C\u65E0\u6CD5\u9A8C\u7B7E\uFF0C\u5DF2\u7981\u7528\u81EA\u52A8\u66F4\u65B0" };
20230
+ fs27.rmSync(destPath, { force: true });
20231
+ fs27.renameSync(partPath, destPath);
20232
+ fs27.chmodSync(destPath, 493);
20233
+ log(`\u5DF2\u4E0B\u8F7D ${path34.basename(destPath)}\uFF08${Math.round(bytes / 1024 / 1024)}MB\uFF0C\u6821\u9A8C\u901A\u8FC7\uFF09`);
20234
+ return { bytes };
20235
+ }
20236
+ function selfCheck(binPath, expectedVersion) {
20237
+ try {
20238
+ const r = (0, import_node_child_process6.spawnSync)(binPath, ["--version"], {
20239
+ timeout: 2e4,
20240
+ encoding: "utf-8",
20241
+ env: { ...process.env, ZQAGENT_AUTO_UPDATE: "0" }
20242
+ });
20243
+ if (r.error) return { ok: false, error: `\u6267\u884C\u81EA\u68C0\u5931\u8D25\uFF1A${r.error.message}` };
20244
+ if (r.status !== 0) return { ok: false, error: `\u81EA\u68C0\u9000\u51FA\u7801 ${r.status}\uFF1A${(r.stderr || "").trim()}` };
20245
+ const out = `${r.stdout ?? ""}${r.stderr ?? ""}`;
20246
+ if (!out.includes(expectedVersion)) {
20247
+ return { ok: false, error: `\u81EA\u68C0\u7248\u672C\u4E0D\u7B26\uFF08\u671F\u671B ${expectedVersion}\uFF0C\u8F93\u51FA\uFF1A${out.trim().slice(0, 120)}\uFF09` };
20248
+ }
20249
+ return { ok: true };
20250
+ } catch (err) {
20251
+ return { ok: false, error: `\u81EA\u68C0\u5F02\u5E38\uFF1A${err.message}` };
19726
20252
  }
19727
- return { ok: true };
19728
20253
  }
19729
20254
 
19730
20255
  // src/update/installer.ts
19731
20256
  init_cjs_shims();
19732
20257
  var fs29 = __toESM(require("fs"), 1);
19733
- var path35 = __toESM(require("path"), 1);
20258
+ var path36 = __toESM(require("path"), 1);
19734
20259
 
19735
20260
  // src/update/state.ts
19736
20261
  init_cjs_shims();
19737
20262
  var fs28 = __toESM(require("fs"), 1);
19738
- var path34 = __toESM(require("path"), 1);
20263
+ var path35 = __toESM(require("path"), 1);
19739
20264
  function updatesDir(dataDir) {
19740
- return path34.join(dataDir, "updates");
20265
+ return path35.join(dataDir, "updates");
19741
20266
  }
19742
20267
  function statePath(dataDir) {
19743
- return path34.join(updatesDir(dataDir), "state.json");
20268
+ return path35.join(updatesDir(dataDir), "state.json");
19744
20269
  }
19745
20270
  function loadState(dataDir) {
19746
20271
  try {
@@ -19767,12 +20292,12 @@ function patchState(dataDir, patch) {
19767
20292
 
19768
20293
  // src/update/installer.ts
19769
20294
  function backupDir(dataDir) {
19770
- return path35.join(updatesDir(dataDir), "backup");
20295
+ return path36.join(updatesDir(dataDir), "backup");
19771
20296
  }
19772
20297
  function backupCurrent(dataDir, execPath, version) {
19773
20298
  const dir = backupDir(dataDir);
19774
20299
  fs29.mkdirSync(dir, { recursive: true });
19775
- const target = path35.join(dir, `zqagent-${version}${process.platform === "win32" ? ".exe" : ""}`);
20300
+ const target = path36.join(dir, `zqagent-${version}${process.platform === "win32" ? ".exe" : ""}`);
19776
20301
  fs29.copyFileSync(execPath, target);
19777
20302
  fs29.chmodSync(target, 493);
19778
20303
  return target;
@@ -19804,7 +20329,7 @@ function installStaged(opts) {
19804
20329
  const from = currentVersion();
19805
20330
  log(`\u6B63\u5728\u5B89\u88C5 v${opts.version}\uFF08\u5F53\u524D v${from}\uFF09`);
19806
20331
  const backupPath = backupCurrent(opts.dataDir, opts.execPath, from);
19807
- log(`\u5DF2\u5907\u4EFD\u5F53\u524D\u7248\u672C\u5230 ${path35.basename(backupPath)}`);
20332
+ log(`\u5DF2\u5907\u4EFD\u5F53\u524D\u7248\u672C\u5230 ${path36.basename(backupPath)}`);
19808
20333
  replaceExecutable(opts.execPath, opts.stagedPath);
19809
20334
  return patchState(opts.dataDir, {
19810
20335
  stagedVersion: void 0,
@@ -19848,7 +20373,7 @@ function rollback(dataDir, execPath, log = () => {
19848
20373
  }
19849
20374
  function readRollbackTarget(dataDir) {
19850
20375
  try {
19851
- const raw = fs29.readFileSync(path35.join(updatesDir(dataDir), "state.json"), "utf-8");
20376
+ const raw = fs29.readFileSync(path36.join(updatesDir(dataDir), "state.json"), "utf-8");
19852
20377
  const s = JSON.parse(raw);
19853
20378
  if (s.backupPath && s.backupVersion) return s;
19854
20379
  } catch {
@@ -20025,7 +20550,7 @@ var UpdateController = class {
20025
20550
  if (!bin) return `\u6E05\u5355\u6CA1\u6709\u5F53\u524D\u5E73\u53F0\uFF08${platformKey()}\uFF09\u7684\u4EA7\u7269`;
20026
20551
  let stagedPath = state.stagedPath;
20027
20552
  if (!(state.stagedVersion === manifest.version && stagedPath && fs30.existsSync(stagedPath))) {
20028
- stagedPath = path36.join(
20553
+ stagedPath = path37.join(
20029
20554
  updatesDir(this.opts.dataDir),
20030
20555
  "staged",
20031
20556
  `zqagent-${manifest.version}${process.platform === "win32" ? ".exe" : ""}`
@@ -20200,7 +20725,7 @@ function seedDemoTenant(store) {
20200
20725
  apiKey: env2("OPENAI_API_KEY"),
20201
20726
  baseURL: env2("OPENAI_BASE_URL")
20202
20727
  },
20203
- workspaceRoot: workspace ? path37.resolve(workspace) : void 0,
20728
+ workspaceRoot: workspace ? path38.resolve(workspace) : void 0,
20204
20729
  allowedCommands: []
20205
20730
  };
20206
20731
  store.saveTenant(demo);
@@ -20254,9 +20779,9 @@ function syncTenantModels(store) {
20254
20779
  tenant.model = model;
20255
20780
  changed = true;
20256
20781
  }
20257
- if (workspace && tenant.workspaceRoot !== path37.resolve(workspace)) {
20258
- console.log(`[sync] \u79DF\u6237 ${id} workspaceRoot \u5DF2\u540C\u6B65: ${tenant.workspaceRoot ?? "(\u6C99\u7BB1)"} \u2192 ${path37.resolve(workspace)}`);
20259
- tenant.workspaceRoot = path37.resolve(workspace);
20782
+ if (workspace && tenant.workspaceRoot !== path38.resolve(workspace)) {
20783
+ console.log(`[sync] \u79DF\u6237 ${id} workspaceRoot \u5DF2\u540C\u6B65: ${tenant.workspaceRoot ?? "(\u6C99\u7BB1)"} \u2192 ${path38.resolve(workspace)}`);
20784
+ tenant.workspaceRoot = path38.resolve(workspace);
20260
20785
  changed = true;
20261
20786
  }
20262
20787
  if (apiKey && tenant.provider.apiKey !== apiKey) {
@@ -20317,7 +20842,7 @@ function listenWithPortFallback(server, startPort, host, dataDir, limit = PORT_F
20317
20842
  }
20318
20843
  try {
20319
20844
  fs31.mkdirSync(dataDir, { recursive: true });
20320
- fs31.writeFileSync(path37.join(dataDir, "server-port"), `${actualPort}
20845
+ fs31.writeFileSync(path38.join(dataDir, "server-port"), `${actualPort}
20321
20846
  `);
20322
20847
  } catch {
20323
20848
  }
@@ -20328,7 +20853,7 @@ function listenWithPortFallback(server, startPort, host, dataDir, limit = PORT_F
20328
20853
  });
20329
20854
  }
20330
20855
  function startServer(opts = {}) {
20331
- const dataDir = path37.resolve(opts.dataDir ?? env2("DATA_DIR") ?? DEFAULT_DATA_DIR);
20856
+ const dataDir = path38.resolve(opts.dataDir ?? env2("DATA_DIR") ?? DEFAULT_DATA_DIR);
20332
20857
  const port = opts.port ?? Number(env2("PORT") ?? 8787);
20333
20858
  const host = opts.host ?? env2("HOST") ?? "127.0.0.1";
20334
20859
  exitWhenOrphaned();
@@ -20358,7 +20883,7 @@ function startServer(opts = {}) {
20358
20883
  }
20359
20884
  const adminKey = ensureAdminKey();
20360
20885
  const secretKey = env2("SECRET_KEY");
20361
- const secretBox = secretKey ? SecretBox.fromHex(secretKey) : SecretBox.fromFile(path37.join(dataDir, "secret.key"));
20886
+ const secretBox = secretKey ? SecretBox.fromHex(secretKey) : SecretBox.fromFile(path38.join(dataDir, "secret.key"));
20362
20887
  if (secretKey && !secretBox) {
20363
20888
  console.warn("[crypto] SECRET_KEY \u975E\u6CD5\uFF08\u9700 64 \u4F4D hex\uFF09\uFF0C\u901A\u77E5\u51ED\u636E\u5C06\u9000\u5316\u4E3A\u660E\u6587");
20364
20889
  }
@@ -20367,7 +20892,7 @@ function startServer(opts = {}) {
20367
20892
  let fleet = null;
20368
20893
  let fleetCred = null;
20369
20894
  function connectFleet(cred) {
20370
- const localId = ensureNodeId2(store);
20895
+ const localId = ensureNodeId(store);
20371
20896
  const nodeId = (cred.nodeId ?? "").trim() || localId;
20372
20897
  if (nodeId !== localId) store.saveNodeId(nodeId);
20373
20898
  fleet?.close();
@@ -20395,7 +20920,7 @@ function startServer(opts = {}) {
20395
20920
  function fleetStatus() {
20396
20921
  return {
20397
20922
  linked: fleet !== null,
20398
- nodeId: ensureNodeId2(store),
20923
+ nodeId: ensureNodeId(store),
20399
20924
  hubUrl: fleetCred?.hubUrl,
20400
20925
  gatewayUrl: fleetCred?.gatewayUrl ?? env2("FLEET_GATEWAY_URL"),
20401
20926
  online: fleet?.isConnected() ?? false,
@@ -20407,8 +20932,15 @@ function startServer(opts = {}) {
20407
20932
  const fleetController = {
20408
20933
  status: fleetStatus,
20409
20934
  async enroll(input) {
20410
- const nodeId = ensureNodeId2(store);
20411
- const cred = await hubEnrollByCode(input.hubUrl, {
20935
+ const nodeId = ensureNodeId(store);
20936
+ const cred = input.account ? await hubEnrollByAccount(input.hubUrl, {
20937
+ email: input.account.email,
20938
+ phone: input.account.phone,
20939
+ password: input.account.password,
20940
+ code: input.account.smsCode,
20941
+ nodeId,
20942
+ label: input.label
20943
+ }) : await hubEnrollByCode(input.hubUrl, {
20412
20944
  code: input.code,
20413
20945
  nodeId,
20414
20946
  label: input.label
@@ -20434,7 +20966,7 @@ function startServer(opts = {}) {
20434
20966
  gatewayUrl: fleetCred.gatewayUrl ?? env2("FLEET_GATEWAY_URL"),
20435
20967
  fleetToken: fleetCred.fleetToken,
20436
20968
  fleetSecret: fleetCred.fleetSecret,
20437
- nodeId: ensureNodeId2(store)
20969
+ nodeId: ensureNodeId(store)
20438
20970
  };
20439
20971
  }
20440
20972
  };
@@ -20587,7 +21119,7 @@ function printReady(info, qq) {
20587
21119
  `zqagent server \u76D1\u542C http://${info.host}:${info.port}`,
20588
21120
  `\u6570\u636E\u76EE\u5F55: ${info.dataDir}`,
20589
21121
  `\u5BA1\u6279\u6302\u8D77\u8D85\u65F6: ${env2("APPROVAL_TIMEOUT_SECONDS") ?? 900}s\uFF08\u8D85\u65F6\u540E\u56DE\u6536\u5185\u5B58\u4EFB\u52A1\uFF0C\u53EF\u7ECF resume \u6062\u590D\uFF09`,
20590
- env2("ZQAGENT_LOG_ADMIN_KEY") === "0" ? `\u7BA1\u7406\u5458\u5BC6\u94A5: ${maskKey(info.adminKey)}\uFF08\u5DF2\u8131\u654F\uFF0C\u5B8C\u6574\u503C\u89C1 ${path37.join(info.dataDir, "admin.json")}\uFF09` : `\u7BA1\u7406\u5458\u5BC6\u94A5: ${info.adminKey}\uFF08\u63A7\u5236\u53F0\u591A\u9879\u76EE\u5165\u53E3\uFF1A/api/v1/projects\uFF1B\u9879\u76EE\u7EA7\u63A5\u53E3\u52A0 X-Project-Id \u5934\uFF09`
21122
+ env2("ZQAGENT_LOG_ADMIN_KEY") === "0" ? `\u7BA1\u7406\u5458\u5BC6\u94A5: ${maskKey(info.adminKey)}\uFF08\u5DF2\u8131\u654F\uFF0C\u5B8C\u6574\u503C\u89C1 ${path38.join(info.dataDir, "admin.json")}\uFF09` : `\u7BA1\u7406\u5458\u5BC6\u94A5: ${info.adminKey}\uFF08\u63A7\u5236\u53F0\u591A\u9879\u76EE\u5165\u53E3\uFF1A/api/v1/projects\uFF1B\u9879\u76EE\u7EA7\u63A5\u53E3\u52A0 X-Project-Id \u5934\uFF09`
20591
21123
  ];
20592
21124
  if (info.fleetNodeId) {
20593
21125
  lines.push(
@@ -20606,152 +21138,6 @@ function printReady(info, qq) {
20606
21138
  for (const l of lines) console.log(l);
20607
21139
  }
20608
21140
 
20609
- // src/cli/account-enroll.ts
20610
- init_cjs_shims();
20611
- var path38 = __toESM(require("path"), 1);
20612
- var readline = __toESM(require("readline/promises"), 1);
20613
- var SKIP_ANSWERS = /* @__PURE__ */ new Set(["-", "skip", "none", "no"]);
20614
- function resolveDefaultHub(hubUrl, env3 = process.env) {
20615
- return (hubUrl ?? env3.HUB_URL ?? DEFAULT_HUB_URL).trim().replace(/\/$/, "");
20616
- }
20617
- function parseHubAnswer(answer, fallback) {
20618
- const a = answer.trim();
20619
- if (SKIP_ANSWERS.has(a.toLowerCase())) return null;
20620
- return (a || fallback).replace(/\/$/, "");
20621
- }
20622
- function resolveDataDir2(dataDir) {
20623
- return path38.resolve(dataDir ?? process.env.DATA_DIR ?? DEFAULT_DATA_DIR);
20624
- }
20625
- async function maybeAccountEnroll(opts = {}) {
20626
- if (!process.stdin.isTTY || !process.stdout.isTTY) return;
20627
- let store;
20628
- try {
20629
- store = new Store(resolveDataDir2(opts.dataDir));
20630
- if (resolveFleetConfig(store)) return;
20631
- } catch (e) {
20632
- cliErr(`\u63A5\u5165\u68C0\u67E5\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
20633
- return;
20634
- }
20635
- const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
20636
- try {
20637
- await runWizard(rl2, store, opts);
20638
- } catch (e) {
20639
- cliErr(`\u63A5\u5165\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
20640
- cliLog(" \u53EF\u7A0D\u540E\u5728\u63A7\u5236\u53F0\u751F\u6210\u63A5\u5165\u7801\uFF0C\u518D\u6267\u884C\uFF1Azqagent link --hub <Hub\u5730\u5740> --code <\u63A5\u5165\u7801>");
20641
- } finally {
20642
- rl2.close();
20643
- }
20644
- }
20645
- async function runWizard(rl2, store, opts) {
20646
- cliLog("");
20647
- cliLog("\u672C\u673A\u5C1A\u672A\u63A5\u5165 Hub\u3002\u53EF\u7528 Hub \u8D26\u53F7\u63A5\u5165\uFF08\u8282\u70B9\u9ED8\u8BA4\u8FDB\u5165\u300C\u672A\u5206\u7EC4\u300D\uFF09\uFF1B\u8F93\u5165 - \u8DF3\u8FC7\u4FDD\u6301\u7EAF\u672C\u5730\u3002");
20648
- const fallback = resolveDefaultHub(opts.hubUrl);
20649
- const answer = await rl2.question(`Hub \u5730\u5740 [${fallback}]\uFF1A`);
20650
- const hubUrl = parseHubAnswer(answer, fallback);
20651
- if (!hubUrl) {
20652
- cliLog("\u5DF2\u8DF3\u8FC7\u63A5\u5165\uFF1A\u8282\u70B9\u4FDD\u6301\u7EAF\u672C\u5730\u8FD0\u884C\u3002");
20653
- return;
20654
- }
20655
- cliLog("\u767B\u5F55\u65B9\u5F0F\uFF1A1) \u90AE\u7BB1 + \u5BC6\u7801 2) \u624B\u673A\u53F7 + \u5BC6\u7801 3) \u624B\u673A\u53F7 + \u77ED\u4FE1\u9A8C\u8BC1\u7801");
20656
- const mode = (await rl2.question("\u8BF7\u9009\u62E9 [1]\uFF1A")).trim() || "1";
20657
- const input = {};
20658
- if (mode === "1") {
20659
- input.email = (await rl2.question("\u90AE\u7BB1\uFF1A")).trim();
20660
- input.password = await askHidden(rl2, "\u5BC6\u7801\uFF1A");
20661
- if (!input.email || !input.password) {
20662
- cliErr("\u90AE\u7BB1\u4E0E\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
20663
- return;
20664
- }
20665
- } else if (mode === "2") {
20666
- input.phone = (await rl2.question("\u624B\u673A\u53F7\uFF1A")).trim();
20667
- input.password = await askHidden(rl2, "\u5BC6\u7801\uFF1A");
20668
- if (!input.phone || !input.password) {
20669
- cliErr("\u624B\u673A\u53F7\u4E0E\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
20670
- return;
20671
- }
20672
- } else if (mode === "3") {
20673
- input.phone = (await rl2.question("\u624B\u673A\u53F7\uFF1A")).trim();
20674
- if (!input.phone) {
20675
- cliErr("\u624B\u673A\u53F7\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
20676
- return;
20677
- }
20678
- try {
20679
- const sent = await hubSendSms(hubUrl, { phone: input.phone, purpose: "login" });
20680
- if (sent.debugCode) cliLog(`\uFF08\u8C03\u8BD5\u6A21\u5F0F\u9A8C\u8BC1\u7801\uFF1A${sent.debugCode}\uFF09`);
20681
- else cliLog("\u9A8C\u8BC1\u7801\u5DF2\u53D1\u9001\uFF0C5 \u5206\u949F\u5185\u6709\u6548\u3002");
20682
- } catch (e) {
20683
- cliErr(`\u77ED\u4FE1\u9A8C\u8BC1\u7801\u53D1\u9001\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
20684
- cliLog(" \u53EF\u6539\u7528\u65B9\u5F0F 1/2\uFF08\u5BC6\u7801\u767B\u5F55\uFF09\uFF0C\u6216\u5728\u7F51\u9875\u7AEF\u751F\u6210\u63A5\u5165\u7801\u540E\u6267\u884C\uFF1A");
20685
- cliLog(" zqagent link --hub <Hub\u5730\u5740> --code <\u63A5\u5165\u7801>");
20686
- return;
20687
- }
20688
- input.code = (await rl2.question("\u77ED\u4FE1\u9A8C\u8BC1\u7801\uFF1A")).trim();
20689
- if (!input.code) {
20690
- cliErr("\u9A8C\u8BC1\u7801\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
20691
- return;
20692
- }
20693
- } else {
20694
- cliErr("\u672A\u77E5\u7684\u767B\u5F55\u65B9\u5F0F\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
20695
- return;
20696
- }
20697
- const nodeId = ensureNodeId2(store);
20698
- const cred = await hubEnrollByAccount(hubUrl, { ...input, nodeId, label: opts.label });
20699
- store.saveFleetCredential({
20700
- ...cred,
20701
- label: opts.label ?? cred.label,
20702
- enrolledAt: (/* @__PURE__ */ new Date()).toISOString()
20703
- });
20704
- cliLog(`\u2705 \u5DF2\u63A5\u5165 Hub\uFF1A\u8282\u70B9 ${cred.nodeId}\uFF08\u7F51\u5173 ${cred.gatewayUrl ?? "\u672A\u4E0B\u53D1"}\uFF09`);
20705
- cliLog(" \u51ED\u636E\u5DF2\u4FDD\u5B58\u5230 fleet.json\uFF0C\u672C\u6B21\u542F\u52A8\u5373\u8FDE\u63A5 Hub\u3002");
20706
- }
20707
- async function askHidden(rl2, prompt) {
20708
- if (!process.stdin.isTTY) return (await rl2.question(prompt)).trim();
20709
- return new Promise((resolve24) => {
20710
- const saved = process.stdin.listeners("data");
20711
- process.stdin.removeAllListeners("data");
20712
- cliWrite(prompt);
20713
- rl2.pause();
20714
- process.stdin.setRawMode(true);
20715
- process.stdin.resume();
20716
- let buf = "";
20717
- const cleanup = () => {
20718
- process.stdin.removeListener("data", onData);
20719
- for (const listener of saved) {
20720
- process.stdin.on("data", listener);
20721
- }
20722
- process.stdin.setRawMode(true);
20723
- rl2.resume();
20724
- };
20725
- const onData = (chunk) => {
20726
- const s = chunk.toString().replace(/\x1b\[[0-9;?]*[A-Za-z~]/g, "").replace(/\x1bO[A-Za-z]/g, "");
20727
- for (const ch of s) {
20728
- if (ch === "\r" || ch === "\n") {
20729
- cleanup();
20730
- cliWrite("\n");
20731
- resolve24(buf);
20732
- return;
20733
- }
20734
- if (ch === "") {
20735
- cleanup();
20736
- cliWrite("\n");
20737
- resolve24("");
20738
- return;
20739
- }
20740
- if (ch === "\x7F" || ch === "\b") {
20741
- if (buf.length > 0) {
20742
- buf = buf.slice(0, -1);
20743
- cliWrite("\b \b");
20744
- }
20745
- } else if (ch >= " ") {
20746
- buf += ch;
20747
- cliWrite("*");
20748
- }
20749
- }
20750
- };
20751
- process.stdin.on("data", onData);
20752
- });
20753
- }
20754
-
20755
21141
  // src/cli/serve.ts
20756
21142
  function parse2(argv) {
20757
21143
  const out = {};
@@ -20764,6 +21150,21 @@ function parse2(argv) {
20764
21150
  } else if (a === "--enroll-code" || a === "--code") {
20765
21151
  out.code = v;
20766
21152
  i++;
21153
+ } else if (a === "--username" || a === "--user") {
21154
+ out.username = v;
21155
+ i++;
21156
+ } else if (a === "--email") {
21157
+ out.email = v;
21158
+ i++;
21159
+ } else if (a === "--phone") {
21160
+ out.phone = v;
21161
+ i++;
21162
+ } else if (a === "--password") {
21163
+ out.password = v;
21164
+ i++;
21165
+ } else if (a === "--sms-code") {
21166
+ out.smsCode = v;
21167
+ i++;
20767
21168
  } else if (a === "--label") {
20768
21169
  out.label = v;
20769
21170
  i++;
@@ -20784,7 +21185,32 @@ async function runServeCommand(argv) {
20784
21185
  const args = parse2(argv);
20785
21186
  const hubUrl = (args.hubUrl ?? process.env.HUB_URL ?? "").trim();
20786
21187
  const code = (args.code ?? process.env.HUB_ENROLL_CODE ?? "").trim();
20787
- if (!(hubUrl && code)) {
21188
+ const account = buildAccountAuth(args);
21189
+ if (code && account) {
21190
+ cliErr("--enroll-code \u4E0E Hub \u8D26\u53F7\u53C2\u6570\uFF08--username/--password \u7B49\uFF09\u53EA\u80FD\u4E8C\u9009\u4E00\u3002");
21191
+ return;
21192
+ }
21193
+ if (account && !isCompleteAccount(account) && !process.stdin.isTTY) {
21194
+ cliErr("\u8D26\u53F7\u53C2\u6570\u7F3A\u5C11\u5BC6\u7801\u6216\u77ED\u4FE1\u9A8C\u8BC1\u7801\uFF1A\u975E\u4EA4\u4E92\u73AF\u5883\u8BF7\u8865 --password \u6216 --sms-code\u3002");
21195
+ }
21196
+ if (!code && isCompleteAccount(account)) {
21197
+ try {
21198
+ const r = await linkNode({
21199
+ hubUrl: resolveDefaultHub(args.hubUrl || void 0),
21200
+ account,
21201
+ dataDir: args.dataDir,
21202
+ label: args.label,
21203
+ host: args.host,
21204
+ port: args.port
21205
+ });
21206
+ cliLog(
21207
+ r.mode === "hot" ? `\u2705 \u5DF2\u901A\u8FC7 Hub \u8D26\u53F7\u63A5\u5165\uFF08\u8282\u70B9 ${r.nodeId}\uFF09` : `\u2705 \u5DF2\u4FDD\u5B58 Hub \u63A5\u5165\u51ED\u636E\uFF08\u8282\u70B9 ${r.nodeId}\uFF09`
21208
+ );
21209
+ } catch (e) {
21210
+ cliErr(`Hub \u8D26\u53F7\u63A5\u5165\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
21211
+ }
21212
+ }
21213
+ if (!(hubUrl && code) && !isCompleteAccount(account)) {
20788
21214
  await maybeAccountEnroll({ dataDir: args.dataDir, hubUrl: args.hubUrl, label: args.label });
20789
21215
  }
20790
21216
  const handle = startServer({
@@ -20923,7 +21349,7 @@ function updateSessionMetaFile(stateDir2, sessionId, patch) {
20923
21349
  // src/cli/debug.ts
20924
21350
  init_cjs_shims();
20925
21351
  var fs33 = __toESM(require("fs"), 1);
20926
- var os9 = __toESM(require("os"), 1);
21352
+ var os10 = __toESM(require("os"), 1);
20927
21353
  var path41 = __toESM(require("path"), 1);
20928
21354
  var LOG_NAME = "zqagent-debug.log";
20929
21355
  var TRUTHY = /^(1|true|yes|on)$/i;
@@ -20937,7 +21363,7 @@ function resolveLogFile() {
20937
21363
  const explicit = process.env.ZQAGENT_DEBUG_LOG;
20938
21364
  if (explicit) return path41.resolve(explicit);
20939
21365
  const dir = process.env.ZQAGENT_DEBUG_DIR;
20940
- return path41.join(dir ? path41.resolve(dir) : os9.tmpdir(), LOG_NAME);
21366
+ return path41.join(dir ? path41.resolve(dir) : os10.tmpdir(), LOG_NAME);
20941
21367
  }
20942
21368
  function enableDebug(argv = process.argv) {
20943
21369
  if (enabled) return true;
@@ -21359,7 +21785,7 @@ function cycleMode(engine) {
21359
21785
  engine.permissions.mode = next;
21360
21786
  return next;
21361
21787
  }
21362
- var rl = readline2.createInterface({
21788
+ var rl = readline3.createInterface({
21363
21789
  input: process.stdin,
21364
21790
  output: process.stdout
21365
21791
  });
@@ -21375,20 +21801,7 @@ var _exitHandler = null;
21375
21801
  rl.on("SIGINT", () => {
21376
21802
  _exitHandler?.();
21377
21803
  });
21378
- function patchReadlineHistoryNav() {
21379
- const iface = rl;
21380
- const orig = iface._ttyWrite;
21381
- iface._ttyWrite = function(s, key) {
21382
- const k = key;
21383
- if (k && (k.name === "up" || k.name === "down") && !k.ctrl && !k.meta && !k.shift) {
21384
- if (iface.cursor > 0) {
21385
- iface._moveCursor(-iface.cursor);
21386
- }
21387
- }
21388
- orig.call(rl, s, key);
21389
- };
21390
- }
21391
- patchReadlineHistoryNav();
21804
+ patchReadlineHistoryNav(rl);
21392
21805
  dlog("init", "\u6A21\u5757\u521D\u59CB\u5316\u5B8C\u6210\uFF08readline / \u8F93\u5165\u8DEF\u7531 / \u5386\u53F2\u5BFC\u822A\u5C31\u7EEA\uFF09");
21393
21806
  function question(prompt) {
21394
21807
  return inputRouter.ask(prompt);
@@ -21852,7 +22265,7 @@ async function offerTerminalSetup(engine, canUseAgent) {
21852
22265
  }
21853
22266
  var KNOWN_SUBCOMMANDS = /* @__PURE__ */ new Set(["link", "serve", "update"]);
21854
22267
  function cliVersion() {
21855
- const env3 = "0.2.10"?.trim();
22268
+ const env3 = "0.2.12"?.trim();
21856
22269
  if (env3) return env3;
21857
22270
  try {
21858
22271
  const req2 = (0, import_module.createRequire)(importMetaUrl);
@@ -21870,11 +22283,13 @@ function printCliUsage() {
21870
22283
  cliLog(" zqagent serve [\u9009\u9879] \u4EE5 headless \u65B9\u5F0F\u5E38\u9A7B\u4E3A\u53EF\u88AB Hub \u7BA1\u7406\u7684\u8282\u70B9");
21871
22284
  cliLog(" zqagent update [--status|--check|--now|--rollback]");
21872
22285
  cliLog(" \u67E5\u770B/\u624B\u52A8\u89E6\u53D1\u8282\u70B9\u81EA\u52A8\u66F4\u65B0");
21873
- cliLog(" zqagent link --hub <url> --code <\u63A5\u5165\u7801> [--label \u540D\u79F0]");
21874
- cliLog(" \u628A\u672C\u673A\u8282\u70B9\u63A5\u5165 Hub\uFF08\u672C\u673A\u6709 server \u65F6\u70ED\u63A5\u5165\uFF09");
22286
+ cliLog(" zqagent link [--hub <url>] [--username <\u90AE\u7BB1/\u624B\u673A\u53F7> --password <\u5BC6\u7801>]");
22287
+ cliLog(" [--code <\u63A5\u5165\u7801>] [--label \u540D\u79F0]");
22288
+ cliLog(" \u628A\u672C\u673A\u8282\u70B9\u63A5\u5165 Hub\uFF08\u8D26\u53F7\u76F4\u8FDE\u6216\u63A5\u5165\u7801\uFF1B\u672C\u673A\u6709 server \u65F6\u70ED\u63A5\u5165\uFF09");
21875
22289
  cliLog("");
21876
22290
  cliLog("serve \u9009\u9879\uFF1A--port <n> --host <addr> --data-dir <dir>");
21877
22291
  cliLog(" --hub <url> --enroll-code <\u7801> --label <\u540D\u79F0> \uFF08\u542F\u52A8\u5373\u63A5\u5165\uFF09");
22292
+ cliLog(" --hub <url> --username <\u90AE\u7BB1/\u624B\u673A\u53F7> --password <\u5BC6\u7801>\uFF08\u8D26\u53F7\u63A5\u5165\uFF09");
21878
22293
  cliLog("\u5176\u5B83\u9009\u9879\uFF1A--resume <\u4F1A\u8BDDID> --debug --help/-h --version/-v");
21879
22294
  }
21880
22295
  async function main() {
@@ -21891,7 +22306,10 @@ async function main() {
21891
22306
  process.exit(0);
21892
22307
  }
21893
22308
  if (subcommand === "link") {
21894
- const ok2 = await runLinkCommand(process.argv.slice(3));
22309
+ const ok2 = await runLinkCommand(process.argv.slice(3), {
22310
+ rl,
22311
+ interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY)
22312
+ });
21895
22313
  rl.close();
21896
22314
  process.exit(ok2 ? 0 : 1);
21897
22315
  }
@@ -22792,7 +23210,11 @@ async function main() {
22792
23210
  case "/link": {
22793
23211
  const code = parts[1];
22794
23212
  const hub = parts[2] ?? process.env.HUB_URL;
22795
- if (!code || !hub) {
23213
+ if (!code) {
23214
+ await runLinkCommand([], { rl, interactive: true });
23215
+ break;
23216
+ }
23217
+ if (!hub) {
22796
23218
  cliLog("\u7528\u6CD5: /link <\u6CE8\u518C\u7801> [Hub\u5730\u5740]\uFF08Hub\u5730\u5740\u4E5F\u53EF\u7531 HUB_URL \u73AF\u5883\u53D8\u91CF\u63D0\u4F9B\uFF09");
22797
23219
  break;
22798
23220
  }