@zqagent/core 0.2.11 → 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.
package/dist/cli/index.js CHANGED
@@ -553,7 +553,7 @@ var init_wire = __esm({
553
553
  init_esm_shims();
554
554
  import "dotenv/config";
555
555
  import * as fs34 from "fs";
556
- import * as readline2 from "readline";
556
+ import * as readline3 from "readline";
557
557
  import * as path43 from "path";
558
558
  import { exec } from "child_process";
559
559
  import { promisify } from "util";
@@ -10496,7 +10496,8 @@ function renderTerminalTutorial() {
10496
10496
 
10497
10497
  // src/cli/link.ts
10498
10498
  init_esm_shims();
10499
- import * as path23 from "path";
10499
+ import * as path28 from "path";
10500
+ import * as readline2 from "readline";
10500
10501
 
10501
10502
  // src/server/store.ts
10502
10503
  init_esm_shims();
@@ -10904,1358 +10905,657 @@ async function hubEnrollByAccount(baseUrl, input) {
10904
10905
  return { hubUrl: baseUrl.replace(/\/$/, ""), ...data };
10905
10906
  }
10906
10907
 
10907
- // src/cli/serve-instances.ts
10908
+ // src/cli/account-enroll.ts
10908
10909
  init_esm_shims();
10909
- import { spawnSync as spawnSync3 } from "child_process";
10910
- import * as fs19 from "fs";
10911
- import * as os5 from "os";
10912
- import * as path22 from "path";
10910
+ import * as path25 from "path";
10911
+ import * as readline from "readline/promises";
10913
10912
 
10914
- // src/update/platform.ts
10913
+ // src/server/projects.ts
10915
10914
  init_esm_shims();
10916
- import * as fs18 from "fs";
10917
- import * as os4 from "os";
10918
- import * as path21 from "path";
10919
- import { spawnSync as spawnSync2 } from "child_process";
10920
- import { createRequire as createRequire3 } from "module";
10921
- function currentVersion() {
10922
- const injected = "0.2.11"?.trim();
10923
- if (injected) return injected;
10915
+ import * as path24 from "path";
10916
+
10917
+ // src/server/session.ts
10918
+ init_esm_shims();
10919
+ import * as path23 from "path";
10920
+ import { AsyncLocalStorage } from "async_hooks";
10921
+
10922
+ // src/server/mcpConfig.ts
10923
+ init_esm_shims();
10924
+ import fs18 from "fs";
10925
+ import path21 from "path";
10926
+ function isValidMcpConfig(v) {
10927
+ return !!v && typeof v === "object" && typeof v.name === "string" && (v.type === "stdio" || v.type === "http");
10928
+ }
10929
+ function readConfigFile(file) {
10930
+ if (!file) return [];
10924
10931
  try {
10925
- const req2 = createRequire3(import.meta.url);
10926
- const pkg = req2("../../package.json");
10927
- if (pkg?.version) return pkg.version;
10928
- } catch {
10932
+ if (!fs18.existsSync(file)) return [];
10933
+ const raw = JSON.parse(fs18.readFileSync(file, "utf8"));
10934
+ const list = Array.isArray(raw) ? raw : raw && typeof raw === "object" && Array.isArray(raw.servers) ? raw.servers : [];
10935
+ return list.filter(isValidMcpConfig);
10936
+ } catch (err) {
10937
+ console.error(`[mcp] \u8BFB\u53D6\u914D\u7F6E\u5931\u8D25 ${file}: ${err.message}`);
10938
+ return [];
10929
10939
  }
10930
- return "dev";
10931
10940
  }
10932
- function isSeaRuntime() {
10933
- if (process.env.ZQAGENT_SEA === "1") return true;
10934
- const base = path21.basename(process.execPath).toLowerCase().replace(/\.exe$/, "");
10935
- return base !== "node" && base !== "nodejs";
10941
+ function hasServerMcpConfig() {
10942
+ if (readConfigFile(process.env.MCP_CONFIG_PATH).length > 0) return true;
10943
+ const workspace = process.env.WORKSPACE;
10944
+ if (!workspace) return false;
10945
+ return readConfigFile(path21.join(workspace, ".agent", "mcp.json")).length > 0;
10936
10946
  }
10937
- var DEFAULT_SERVICE_NAME = "zqagent";
10938
- var DEFAULT_SERVICE_LABEL = "com.zqagent.node";
10939
- function serviceManagerFromCgroup(cgroup, serviceName = DEFAULT_SERVICE_NAME) {
10940
- const unitName = `${serviceName}.service`;
10941
- for (const line of cgroup.split("\n")) {
10942
- const idx = line.lastIndexOf(":");
10943
- if (idx < 0) continue;
10944
- const unitPath = line.slice(idx + 1).trim();
10945
- if (unitPath.split("/").includes(unitName)) return "systemd";
10947
+ function loadServerMcpConfigs(tenant) {
10948
+ const fromEnv = readConfigFile(process.env.MCP_CONFIG_PATH);
10949
+ if (fromEnv.length > 0) return fromEnv;
10950
+ if (Array.isArray(tenant.mcpServers) && tenant.mcpServers.length > 0) {
10951
+ return tenant.mcpServers.filter(isValidMcpConfig);
10946
10952
  }
10947
- return null;
10948
- }
10949
- function launchdDomain() {
10950
- const uid = typeof process.getuid === "function" ? process.getuid() : -1;
10951
- return uid === 0 ? "system" : `gui/${uid}`;
10952
- }
10953
- function launchdLabel() {
10954
- return process.env.ZQAGENT_SERVICE_LABEL?.trim() || DEFAULT_SERVICE_LABEL;
10953
+ const workspace = tenant.workspaceRoot ?? process.env.WORKSPACE;
10954
+ if (workspace)
10955
+ return readConfigFile(path21.join(workspace, ".agent", "mcp.json"));
10956
+ return [];
10955
10957
  }
10956
- function launchdPlistPath() {
10957
- const label = launchdLabel();
10958
- const uid = typeof process.getuid === "function" ? process.getuid() : -1;
10959
- if (uid === 0) return `/Library/LaunchDaemons/${label}.plist`;
10960
- return path21.join(os4.homedir(), "Library", "LaunchAgents", `${label}.plist`);
10958
+
10959
+ // src/server/nodeLlm.ts
10960
+ init_esm_shims();
10961
+ var GLOBAL_PROVIDER_ID = "__env__";
10962
+ function envProvider() {
10963
+ const apiKey = process.env.OPENAI_API_KEY;
10964
+ if (!apiKey) return null;
10965
+ return {
10966
+ id: GLOBAL_PROVIDER_ID,
10967
+ name: process.env.OPENAI_MODEL || process.env.MODEL || "\u5168\u5C40 LLM",
10968
+ type: "env",
10969
+ baseURL: process.env.OPENAI_BASE_URL ?? "",
10970
+ model: process.env.OPENAI_MODEL || process.env.MODEL || "",
10971
+ apiKey
10972
+ };
10961
10973
  }
10962
- function serviceManagerFromLaunchctl(output, pid, execPath, sea = true) {
10963
- const pidMatch = /^\s*pid = (\d+)\s*$/m.exec(output);
10964
- if (pidMatch && Number(pidMatch[1]) === pid) return "launchd";
10965
- const program = /^\s*program = (.+?)\s*$/m.exec(output);
10966
- if (program) {
10967
- const shown = program[1].replace(/^"|"$/g, "");
10968
- if (path21.resolve(shown) === path21.resolve(execPath)) return "launchd";
10969
- }
10970
- if (!sea && /^\s*state = /m.test(output)) return "launchd";
10971
- return null;
10974
+ function normReasoningEffort(v) {
10975
+ return v === "low" || v === "medium" || v === "high" ? v : null;
10972
10976
  }
10973
- function parseLaunchdEnvironment(xml) {
10974
- const out = {};
10975
- const marker = xml.indexOf("<key>EnvironmentVariables</key>");
10976
- if (marker < 0) return out;
10977
- const open = xml.indexOf("<dict>", marker);
10978
- if (open < 0) return out;
10979
- const close = xml.indexOf("</dict>", open);
10980
- if (close < 0) return out;
10981
- const body = xml.slice(open + "<dict>".length, close);
10982
- for (const m of body.matchAll(/<key>([^<]+)<\/key>\s*<string>([^<]*)<\/string>/g)) {
10983
- out[m[1]] = m[2];
10977
+ function normalizeModelList(v) {
10978
+ if (!Array.isArray(v)) return void 0;
10979
+ const out = [];
10980
+ const seen = /* @__PURE__ */ new Set();
10981
+ for (const x of v) {
10982
+ const s = String(x ?? "").trim();
10983
+ if (!s || seen.has(s)) continue;
10984
+ seen.add(s);
10985
+ out.push(s);
10986
+ if (out.length >= 500) break;
10984
10987
  }
10985
10988
  return out;
10986
10989
  }
10987
- function detectServiceManager() {
10988
- if (process.platform === "darwin") return detectLaunchd();
10989
- if (process.platform !== "linux") return null;
10990
- const serviceName = process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;
10991
- try {
10992
- return serviceManagerFromCgroup(
10993
- fs18.readFileSync("/proc/self/cgroup", "utf8"),
10994
- serviceName
10995
- );
10996
- } catch {
10997
- return null;
10998
- }
10990
+ function nodeLlmView(cfg) {
10991
+ const g = envProvider();
10992
+ const providers = [
10993
+ ...g ? [
10994
+ {
10995
+ id: g.id,
10996
+ name: g.name,
10997
+ type: g.type,
10998
+ baseURL: g.baseURL,
10999
+ model: g.model,
11000
+ hasApiKey: true,
11001
+ global: true
11002
+ }
11003
+ ] : [],
11004
+ ...cfg.providers.map((p) => ({
11005
+ id: p.id,
11006
+ name: p.name,
11007
+ type: p.type ?? "",
11008
+ baseURL: p.baseURL,
11009
+ model: p.model,
11010
+ hasApiKey: Boolean(p.apiKey),
11011
+ models: p.models ?? [],
11012
+ nonChat: p.nonChat ?? [],
11013
+ modelMeta: p.modelMeta ?? [],
11014
+ reasoningEffort: p.reasoningEffort ?? null,
11015
+ global: false
11016
+ }))
11017
+ ];
11018
+ return {
11019
+ defaultProviderId: cfg.defaultProviderId ?? null,
11020
+ providers,
11021
+ /** 未显式设置默认时,是否由环境变量全局配置兜底 */
11022
+ globalIsDefault: !cfg.defaultProviderId && Boolean(g)
11023
+ };
10999
11024
  }
11000
- function detectLaunchd() {
11001
- try {
11002
- const res = spawnSync2(
11003
- "launchctl",
11004
- ["print", `${launchdDomain()}/${launchdLabel()}`],
11005
- { encoding: "utf8", timeout: 3e3 }
11006
- );
11007
- if (res.status !== 0 || !res.stdout) return null;
11008
- return serviceManagerFromLaunchctl(
11009
- res.stdout,
11010
- process.pid,
11011
- process.execPath,
11012
- isSeaRuntime()
11013
- );
11014
- } catch {
11015
- return null;
11025
+ function applyNodeLlmPatch(cfg, raw) {
11026
+ const next = { providers: cfg.providers, defaultProviderId: cfg.defaultProviderId };
11027
+ if (Array.isArray(raw?.providers)) {
11028
+ const old = new Map(cfg.providers.map((p) => [p.id, p]));
11029
+ next.providers = raw.providers.filter((x) => Boolean(x) && typeof x === "object").filter((x) => x.global !== true && String(x.id ?? "") !== GLOBAL_PROVIDER_ID).map((x) => {
11030
+ const id = String(x.id ?? "").trim();
11031
+ if (!id) throw new ApiError(400, "bad_request", "\u4F9B\u5E94\u5546 id \u4E0D\u80FD\u4E3A\u7A7A");
11032
+ const prev = old.get(id);
11033
+ const apiKey = String(x.apiKey ?? "").trim();
11034
+ const models = normalizeModelList(x.models) ?? prev?.models;
11035
+ const nonChat = normalizeModelList(x.nonChat) ?? prev?.nonChat;
11036
+ const modelMeta = normalizeModelMeta(x.modelMeta) ?? prev?.modelMeta;
11037
+ return {
11038
+ id,
11039
+ name: String(x.name ?? prev?.name ?? id).trim(),
11040
+ type: x.type == null ? prev?.type : String(x.type),
11041
+ baseURL: String(x.baseURL ?? prev?.baseURL ?? "").trim(),
11042
+ model: String(x.model ?? prev?.model ?? "").trim(),
11043
+ apiKey: apiKey || prev?.apiKey,
11044
+ reasoningEffort: x.reasoningEffort === void 0 ? prev?.reasoningEffort ?? null : normReasoningEffort(x.reasoningEffort),
11045
+ ...models ? { models } : {},
11046
+ ...nonChat ? { nonChat } : {},
11047
+ ...modelMeta ? { modelMeta } : {}
11048
+ };
11049
+ });
11016
11050
  }
11017
- }
11018
- function serviceManager() {
11019
- const v = process.env.ZQAGENT_SERVICE_MANAGER?.trim().toLowerCase();
11020
- if (v === "systemd" || v === "launchd") return v;
11021
- return detectServiceManager();
11022
- }
11023
- function parseServiceEnvironment(raw) {
11024
- const out = {};
11025
- for (const token of raw.trim().split(/\s+/)) {
11026
- const idx = token.indexOf("=");
11027
- if (idx > 0) out[token.slice(0, idx)] = token.slice(idx + 1);
11051
+ if (typeof raw?.defaultProviderId === "string") {
11052
+ const id = raw.defaultProviderId.trim();
11053
+ next.defaultProviderId = id || void 0;
11054
+ } else if (raw?.defaultProviderId === null) {
11055
+ next.defaultProviderId = void 0;
11028
11056
  }
11029
- return out;
11057
+ return next;
11030
11058
  }
11031
- var cachedServiceEnv = null;
11032
- function readServiceEnvironment() {
11033
- const serviceName = process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;
11034
- try {
11035
- const res = spawnSync2(
11036
- "systemctl",
11037
- ["show", serviceName, "--property=Environment", "--value", "--no-pager"],
11038
- { encoding: "utf8", timeout: 3e3 }
11039
- );
11040
- if (res.status !== 0 || !res.stdout) return {};
11041
- return parseServiceEnvironment(res.stdout);
11042
- } catch {
11043
- return {};
11044
- }
11059
+ function findNodeProvider(store, id) {
11060
+ if (!id) return null;
11061
+ if (id === GLOBAL_PROVIDER_ID) return envProvider();
11062
+ return store.loadNodeLlm().providers.find((p) => p.id === id) ?? null;
11045
11063
  }
11046
- function serviceEnvValue(key) {
11047
- if (process.platform === "darwin") {
11048
- if (serviceManager() !== "launchd") return void 0;
11049
- cachedLaunchdEnv ??= readLaunchdEnvironment();
11050
- return cachedLaunchdEnv[key];
11051
- }
11052
- if (process.platform !== "linux") return void 0;
11053
- if (serviceManager() !== "systemd") return void 0;
11054
- cachedServiceEnv ??= readServiceEnvironment();
11055
- return cachedServiceEnv[key];
11056
- }
11057
- var cachedLaunchdEnv = null;
11058
- function readLaunchdEnvironment() {
11059
- try {
11060
- return parseLaunchdEnvironment(fs18.readFileSync(launchdPlistPath(), "utf8"));
11061
- } catch {
11062
- return {};
11063
- }
11064
+ function nodeDefaultProvider(store) {
11065
+ const cfg = store.loadNodeLlm();
11066
+ if (cfg.providers.length === 0) return null;
11067
+ return cfg.providers.find((p) => p.id === cfg.defaultProviderId) ?? cfg.providers[0] ?? null;
11064
11068
  }
11065
- function selfUpdateCapability(hasPublicKey) {
11066
- if (!isSeaRuntime()) {
11067
- return {
11068
- ok: false,
11069
- 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"
11070
- };
11069
+ function resolveSessionProvider(store, tenant, providerId) {
11070
+ const own = tenant.provider ?? {};
11071
+ if (providerId) {
11072
+ const picked = findNodeProvider(store, providerId);
11073
+ if (picked) {
11074
+ return {
11075
+ apiKey: picked.apiKey || own.apiKey,
11076
+ baseURL: picked.baseURL || own.baseURL,
11077
+ model: picked.model,
11078
+ providerId,
11079
+ reasoningEffort: picked.reasoningEffort ?? null,
11080
+ ...picked.modelMeta ? { modelMeta: picked.modelMeta } : {}
11081
+ };
11082
+ }
11071
11083
  }
11072
- if (!serviceManager()) {
11084
+ if (own.apiKey) return { ...own, model: tenant.model };
11085
+ const def = nodeDefaultProvider(store);
11086
+ if (def?.apiKey) {
11073
11087
  return {
11074
- ok: false,
11075
- reason: "\u672A\u88AB systemd/launchd \u6258\u7BA1\uFF1A\u8BF7\u7528 deploy/service/install.sh \u5B89\u88C5\uFF0C\u6216\u624B\u52A8\u5347\u7EA7"
11088
+ apiKey: def.apiKey,
11089
+ baseURL: own.baseURL || def.baseURL,
11090
+ model: def.model || tenant.model,
11091
+ reasoningEffort: def.reasoningEffort ?? null,
11092
+ ...def.modelMeta ? { modelMeta: def.modelMeta } : {}
11076
11093
  };
11077
11094
  }
11078
- if (!hasPublicKey) {
11079
- return { ok: false, reason: "\u672A\u914D\u7F6E ZQAGENT_UPDATE_PUBKEY\uFF0C\u65E0\u6CD5\u9A8C\u7B7E\uFF0C\u5DF2\u7981\u7528\u81EA\u52A8\u66F4\u65B0" };
11080
- }
11081
- return { ok: true };
11095
+ return { ...own, model: tenant.model };
11082
11096
  }
11083
11097
 
11084
- // src/cli/serve-instances.ts
11085
- var DEFAULT_SERVE_HOST = "127.0.0.1";
11086
- var DEFAULT_SERVE_PORT = 8787;
11087
- var DEFAULT_SERVICE_NAME2 = "zqagent";
11088
- var DEFAULT_SERVICE_LABEL2 = "com.zqagent.node";
11089
- function serveSubcommandIndex(argv) {
11090
- for (let i = 1; i < argv.length; i++) {
11091
- if (argv[i].startsWith("-")) continue;
11092
- return argv[i] === "serve" ? i : -1;
11093
- }
11094
- return -1;
11095
- }
11096
- function parseServeArgs(argv) {
11097
- const at = serveSubcommandIndex(argv);
11098
- if (at < 0) return null;
11099
- let dataDir = null;
11100
- let host = DEFAULT_SERVE_HOST;
11101
- let port = DEFAULT_SERVE_PORT;
11102
- for (let i = at + 1; i < argv.length; i++) {
11103
- const arg = argv[i];
11104
- if (arg === "--data-dir" && argv[i + 1]) {
11105
- dataDir = argv[++i];
11106
- } else if (arg.startsWith("--data-dir=")) {
11107
- dataDir = arg.slice("--data-dir=".length);
11108
- } else if (arg === "--host" && argv[i + 1]) {
11109
- host = argv[++i];
11110
- } else if (arg.startsWith("--host=")) {
11111
- host = arg.slice("--host=".length);
11112
- } else if (arg === "--port" && argv[i + 1]) {
11113
- port = Number(argv[++i]);
11114
- } else if (arg.startsWith("--port=")) {
11115
- port = Number(arg.slice("--port=".length));
11116
- }
11117
- }
11118
- if (!Number.isFinite(port) || port <= 0) port = DEFAULT_SERVE_PORT;
11119
- if (host === "0.0.0.0" || host === "::" || host === "") host = DEFAULT_SERVE_HOST;
11120
- return { dataDir, host, port };
11121
- }
11122
- function buildHotCandidates(explicitDataDir, discovered, fallback) {
11123
- const out = [];
11124
- const push = (inst) => {
11125
- if (!out.some((o) => o.dataDir === inst.dataDir)) out.push(inst);
11098
+ // src/server/usage.ts
11099
+ init_esm_shims();
11100
+ import * as fs19 from "fs";
11101
+ import * as path22 from "path";
11102
+ var LEDGER_RETENTION_MONTHS = 12;
11103
+ var USAGE_SCHEMA = 1;
11104
+ var DEFAULT_PRICING = {
11105
+ schema: USAGE_SCHEMA,
11106
+ base: "USD",
11107
+ rate: { CNY: 7.2 },
11108
+ vendors: {
11109
+ openai: {
11110
+ "gpt-4o": { input: 2.5, output: 10, cacheRead: 1.25 },
11111
+ "gpt-4o-mini": { input: 0.15, output: 0.6, cacheRead: 0.075 },
11112
+ "gpt-4.1": { input: 2, output: 8, cacheRead: 0.5 }
11113
+ },
11114
+ deepseek: {
11115
+ "deepseek-flash": {
11116
+ input: 0.27,
11117
+ output: 1.1,
11118
+ cacheRead: 0.027,
11119
+ cacheWrite: 0.27
11120
+ },
11121
+ "deepseek-reasoner": {
11122
+ input: 0.55,
11123
+ output: 2.19,
11124
+ cacheRead: 0.055,
11125
+ cacheWrite: 0.55
11126
+ },
11127
+ "deepseek-*": {
11128
+ input: 0.27,
11129
+ output: 1.1,
11130
+ cacheRead: 0.027,
11131
+ cacheWrite: 0.27
11132
+ }
11133
+ },
11134
+ qwen: {
11135
+ "qwen-plus": { input: 0.4, output: 1.2 },
11136
+ "qwen-turbo": { input: 0.05, output: 0.2 }
11137
+ },
11138
+ zhipu: { "glm-4-plus": { input: 0.7, output: 0.7 } },
11139
+ moonshot: { "moonshot-v1-8k": { input: 1.68, output: 1.68 } },
11140
+ xiaomi: {},
11141
+ custom: {},
11142
+ env: {}
11143
+ },
11144
+ fallback: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
11145
+ };
11146
+ function zeroAgg() {
11147
+ return {
11148
+ calls: 0,
11149
+ input: 0,
11150
+ output: 0,
11151
+ cacheRead: 0,
11152
+ cacheWrite: 0,
11153
+ costUsd: 0,
11154
+ unpriced: 0
11126
11155
  };
11127
- if (explicitDataDir) push({ dataDir: path22.resolve(explicitDataDir), ...fallback });
11128
- for (const inst of discovered) if (inst.managed) push(inst);
11129
- for (const inst of discovered) if (!inst.managed) push(inst);
11130
- if (!explicitDataDir) push({ dataDir: path22.resolve(DEFAULT_DATA_DIR), ...fallback });
11131
- return out;
11132
- }
11133
- function isWindowsAbsolute(p) {
11134
- return /^[A-Za-z]:[\\/]/.test(p) || /^\\\\/.test(p);
11135
- }
11136
- function isAbsolutePath(p) {
11137
- return path22.isAbsolute(p) || isWindowsAbsolute(p);
11138
11156
  }
11139
- function normalizePath(p) {
11140
- return isWindowsAbsolute(p) ? path22.win32.normalize(p) : path22.normalize(p);
11157
+ function addAgg(into, tokens, costUsd) {
11158
+ into.calls += 1;
11159
+ into.input += tokens.input;
11160
+ into.output += tokens.output;
11161
+ into.cacheRead += tokens.cacheRead;
11162
+ into.cacheWrite += tokens.cacheWrite;
11163
+ if (costUsd == null) into.unpriced += 1;
11164
+ else into.costUsd += costUsd;
11141
11165
  }
11142
- function parseServeCommandLine(line) {
11143
- if (!/(?:^|\s)["']?serve["']?(?=\s|$)/.test(line)) return null;
11144
- const m = /--data-dir(?:=|\s+)(?:"([^"]+)"|'([^']+)'|(.+?))(?=\s+--\w|\s*$)/.exec(line);
11145
- const rawDataDir = (m?.[1] ?? m?.[2] ?? m?.[3] ?? "").trim();
11146
- if (!rawDataDir || !isAbsolutePath(rawDataDir)) return null;
11147
- const host = /--host(?:=|\s+)(?:"([^"]+)"|'([^']+)'|(\S+))/.exec(line);
11148
- const rawHost = (host?.[1] ?? host?.[2] ?? host?.[3] ?? DEFAULT_SERVE_HOST).trim();
11149
- const portMatch = /--port(?:=|\s+)(\d+)/.exec(line);
11150
- const port = Number(portMatch?.[1] ?? DEFAULT_SERVE_PORT);
11151
- return {
11152
- dataDir: normalizePath(rawDataDir),
11153
- host: rawHost === "0.0.0.0" || rawHost === "::" || rawHost === "" ? DEFAULT_SERVE_HOST : rawHost,
11154
- port: Number.isFinite(port) && port > 0 ? port : DEFAULT_SERVE_PORT
11155
- };
11166
+ function utcDay(iso) {
11167
+ return iso.slice(0, 10);
11156
11168
  }
11157
- function processCwd(pid) {
11158
- try {
11159
- return fs19.readlinkSync(`/proc/${pid}/cwd`);
11160
- } catch {
11161
- return null;
11162
- }
11169
+ function monthOf(iso) {
11170
+ return iso.slice(0, 7);
11163
11171
  }
11164
- function processEnv(pid) {
11165
- const out = {};
11166
- try {
11167
- const raw = fs19.readFileSync(`/proc/${pid}/environ`, "utf-8");
11168
- for (const entry of raw.split("\0")) {
11169
- const eq = entry.indexOf("=");
11170
- if (eq > 0) out[entry.slice(0, eq)] = entry.slice(eq + 1);
11171
- }
11172
- } catch {
11172
+ function retainedMonths(now = /* @__PURE__ */ new Date()) {
11173
+ const out = /* @__PURE__ */ new Set();
11174
+ const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
11175
+ for (let i = 0; i < LEDGER_RETENTION_MONTHS; i += 1) {
11176
+ out.add(d.toISOString().slice(0, 7));
11177
+ d.setUTCMonth(d.getUTCMonth() - 1);
11173
11178
  }
11174
11179
  return out;
11175
11180
  }
11176
- function resolveDataDir(rawDataDir, cwd, env3) {
11177
- const pick = rawDataDir ?? env3.DATA_DIR ?? null;
11178
- if (pick) return path22.isAbsolute(pick) ? path22.normalize(pick) : cwd ? path22.resolve(cwd, pick) : null;
11179
- return cwd ? path22.resolve(cwd, DEFAULT_DATA_DIR) : null;
11181
+ function atomicWrite(file, content) {
11182
+ fs19.mkdirSync(path22.dirname(file), { recursive: true });
11183
+ const tmp = `${file}.tmp`;
11184
+ fs19.writeFileSync(tmp, content, "utf-8");
11185
+ fs19.renameSync(tmp, file);
11180
11186
  }
11181
- function isManagedBySystemd(pid, env3) {
11182
- if (env3.ZQAGENT_SERVICE_MANAGER?.trim()) return true;
11183
- try {
11184
- const cgroup = fs19.readFileSync(`/proc/${pid}/cgroup`, "utf-8");
11185
- const serviceName = env3.ZQAGENT_SERVICE_NAME?.trim() || "zqagent";
11186
- return serviceManagerFromCgroup(cgroup, serviceName) !== null;
11187
- } catch {
11188
- return false;
11187
+ var UsageLedger = class {
11188
+ constructor(root) {
11189
+ this.root = root;
11190
+ this.dir = path22.join(root, "usage");
11189
11191
  }
11190
- }
11191
- function discoverFromProc() {
11192
- const out = [];
11193
- let pids;
11194
- try {
11195
- pids = fs19.readdirSync("/proc");
11196
- } catch {
11197
- return out;
11192
+ root;
11193
+ dir;
11194
+ rollup;
11195
+ pricing;
11196
+ rollupDirty = false;
11197
+ rollupTimer = null;
11198
+ inited = false;
11199
+ get ledgerDir() {
11200
+ return this.dir;
11198
11201
  }
11199
- for (const name of pids) {
11200
- if (!/^\d+$/.test(name)) continue;
11201
- const pid = Number(name);
11202
- if (pid === process.pid) continue;
11203
- let argv;
11204
- let env3;
11205
- try {
11206
- argv = fs19.readFileSync(`/proc/${pid}/cmdline`, "utf-8").split("\0").filter(Boolean);
11207
- env3 = processEnv(pid);
11208
- } catch {
11209
- continue;
11210
- }
11211
- const parsed = parseServeArgs(argv);
11212
- if (!parsed) continue;
11213
- const dataDir = resolveDataDir(parsed.dataDir, processCwd(pid), env3);
11214
- if (!dataDir) continue;
11215
- out.push({
11216
- dataDir,
11217
- host: parsed.host,
11218
- port: parsed.port,
11219
- managed: isManagedBySystemd(pid, env3)
11220
- });
11202
+ ledgerFile(month) {
11203
+ return path22.join(this.ledgerDir, `ledger-${month}.jsonl`);
11221
11204
  }
11222
- return out;
11223
- }
11224
- function discoverFromPs() {
11225
- const out = [];
11226
- const res = spawnSync3("ps", ["-axo", "command="], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
11227
- if (res.status !== 0 || !res.stdout) return out;
11228
- for (const line of res.stdout.split("\n")) {
11229
- const inst = parseServeCommandLine(line);
11230
- if (inst) out.push(inst);
11205
+ get rollupFile() {
11206
+ return path22.join(this.ledgerDir, "rollup.json");
11231
11207
  }
11232
- return out;
11233
- }
11234
- function discoverFromWindows() {
11235
- const out = [];
11236
- const script = `[Console]::OutputEncoding=[Text.Encoding]::UTF8; Get-CimInstance Win32_Process -Filter "Name='zqagent.exe' OR Name='node.exe'" | ForEach-Object { $_.CommandLine }`;
11237
- const res = spawnSync3(
11238
- "powershell.exe",
11239
- ["-NoProfile", "-NonInteractive", "-Command", script],
11240
- { encoding: "utf-8", timeout: 15e3, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] }
11241
- );
11242
- if (res.status !== 0 || !res.stdout) return out;
11243
- for (const line of res.stdout.replace(/^\uFEFF/, "").split(/\r?\n/)) {
11244
- const inst = parseServeCommandLine(line);
11245
- if (inst) out.push(inst);
11208
+ get pricingFile() {
11209
+ return path22.join(this.ledgerDir, "pricing.json");
11246
11210
  }
11247
- return out;
11248
- }
11249
- function decodeXmlEntities(s) {
11250
- return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
11251
- }
11252
- function parseLaunchdProgramArguments(xml) {
11253
- const marker = xml.indexOf("<key>ProgramArguments</key>");
11254
- if (marker < 0) return [];
11255
- const open = xml.indexOf("<array>", marker);
11256
- if (open < 0) return [];
11257
- const close = xml.indexOf("</array>", open);
11258
- if (close < 0) return [];
11259
- return [...xml.slice(open + "<array>".length, close).matchAll(/<string>([^<]*)<\/string>/g)].map(
11260
- (m) => decodeXmlEntities(m[1])
11261
- );
11262
- }
11263
- function launchdServiceInstances() {
11264
- if (process.platform !== "darwin") return [];
11265
- const label = process.env.ZQAGENT_SERVICE_LABEL?.trim() || DEFAULT_SERVICE_LABEL2;
11266
- const plists = [
11267
- `/Library/LaunchDaemons/${label}.plist`,
11268
- path22.join(os5.homedir(), "Library", "LaunchAgents", `${label}.plist`)
11269
- ];
11270
- const out = [];
11271
- for (const plist of plists) {
11272
- let xml;
11211
+ // ===================== 初始化 =====================
11212
+ init(now = /* @__PURE__ */ new Date()) {
11213
+ if (this.inited) return;
11214
+ fs19.mkdirSync(this.ledgerDir, { recursive: true });
11215
+ this.pricing = this.loadPricing();
11216
+ this.rollup = this.loadRollup(now);
11217
+ this.pruneLedgers(now);
11218
+ this.inited = true;
11219
+ }
11220
+ loadPricing() {
11273
11221
  try {
11274
- xml = fs19.readFileSync(plist, "utf-8");
11222
+ const raw = fs19.readFileSync(this.pricingFile, "utf-8");
11223
+ const parsed = JSON.parse(raw);
11224
+ const vendors = {};
11225
+ for (const v of /* @__PURE__ */ new Set([
11226
+ ...Object.keys(DEFAULT_PRICING.vendors),
11227
+ ...Object.keys(parsed.vendors ?? {})
11228
+ ])) {
11229
+ vendors[v] = {
11230
+ ...DEFAULT_PRICING.vendors[v] ?? {},
11231
+ ...(parsed.vendors ?? {})[v] ?? {}
11232
+ };
11233
+ }
11234
+ return {
11235
+ schema: USAGE_SCHEMA,
11236
+ base: "USD",
11237
+ rate: { ...DEFAULT_PRICING.rate, ...parsed.rate ?? {} },
11238
+ vendors,
11239
+ fallback: { ...DEFAULT_PRICING.fallback, ...parsed.fallback ?? {} }
11240
+ };
11275
11241
  } catch {
11276
- continue;
11242
+ try {
11243
+ atomicWrite(this.pricingFile, JSON.stringify(DEFAULT_PRICING, null, 2));
11244
+ } catch {
11245
+ }
11246
+ return JSON.parse(JSON.stringify(DEFAULT_PRICING));
11277
11247
  }
11278
- const parsed = parseServeArgs(parseLaunchdProgramArguments(xml));
11279
- if (!parsed?.dataDir || !isAbsolutePath(parsed.dataDir)) continue;
11280
- out.push({
11281
- dataDir: normalizePath(parsed.dataDir),
11282
- host: parsed.host,
11283
- port: parsed.port,
11284
- managed: true
11285
- });
11286
11248
  }
11287
- return out;
11288
- }
11289
- function parseSystemdUnit(text) {
11290
- let dataDir = null;
11291
- let host = DEFAULT_SERVE_HOST;
11292
- let port = DEFAULT_SERVE_PORT;
11293
- for (const line of text.split("\n")) {
11294
- const trimmed = line.trim();
11295
- if (trimmed.startsWith("WorkingDirectory=")) {
11296
- const v = trimmed.slice("WorkingDirectory=".length).trim();
11297
- if (v && isAbsolutePath(v)) dataDir = v;
11298
- } else if (trimmed.startsWith("ExecStart=")) {
11299
- const tokens = trimmed.slice("ExecStart=".length).trim().split(/\s+/).map((t) => t.replace(/^["']|["']$/g, ""));
11300
- const parsed = parseServeArgs(tokens);
11301
- if (!parsed) continue;
11302
- if (parsed.dataDir && isAbsolutePath(parsed.dataDir)) dataDir = parsed.dataDir;
11303
- host = parsed.host;
11304
- port = parsed.port;
11249
+ loadRollup(now) {
11250
+ try {
11251
+ const raw = fs19.readFileSync(this.rollupFile, "utf-8");
11252
+ const parsed = JSON.parse(raw);
11253
+ return {
11254
+ schema: USAGE_SCHEMA,
11255
+ since: parsed.since ?? now.toISOString(),
11256
+ legacy: parsed.legacy,
11257
+ total: { ...zeroAgg(), ...parsed.total },
11258
+ byDay: parsed.byDay ?? {},
11259
+ byModel: parsed.byModel ?? {},
11260
+ byProject: parsed.byProject ?? {}
11261
+ };
11262
+ } catch {
11263
+ return {
11264
+ schema: USAGE_SCHEMA,
11265
+ since: now.toISOString(),
11266
+ total: zeroAgg(),
11267
+ byDay: {},
11268
+ byModel: {},
11269
+ byProject: {}
11270
+ };
11305
11271
  }
11306
11272
  }
11307
- if (!dataDir) return null;
11308
- return { dataDir: normalizePath(dataDir), host, port, managed: true };
11309
- }
11310
- function systemdServiceInstances() {
11311
- if (process.platform !== "linux") return [];
11312
- const unit = process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME2;
11313
- const configHome = process.env.XDG_CONFIG_HOME?.trim() || path22.join(os5.homedir(), ".config");
11314
- const units = [
11315
- `/etc/systemd/system/${unit}.service`,
11316
- path22.join(configHome, "systemd", "user", `${unit}.service`)
11317
- ];
11318
- const out = [];
11319
- for (const file of units) {
11320
- let text;
11273
+ /** 删除超出保留期的账本文件,并裁剪 byDay */
11274
+ pruneLedgers(now) {
11275
+ const keep = retainedMonths(now);
11276
+ let changed = false;
11321
11277
  try {
11322
- text = fs19.readFileSync(file, "utf-8");
11278
+ for (const f of fs19.readdirSync(this.ledgerDir)) {
11279
+ const m = /^ledger-(\d{4}-\d{2})\.jsonl$/.exec(f);
11280
+ if (m && !keep.has(m[1])) {
11281
+ fs19.unlinkSync(path22.join(this.ledgerDir, f));
11282
+ }
11283
+ }
11323
11284
  } catch {
11324
- continue;
11325
11285
  }
11326
- const inst = parseSystemdUnit(text);
11327
- if (inst) out.push(inst);
11328
- }
11329
- return out;
11330
- }
11331
- function windowsServiceInstances() {
11332
- if (process.platform !== "win32") return [];
11333
- const appdata = process.env.APPDATA?.trim();
11334
- if (!appdata) return [];
11335
- const base = path22.join(appdata, "zqagent");
11336
- try {
11337
- const raw = fs19.readFileSync(path22.join(base, "runtime", "service.json"), "utf-8");
11338
- const v = JSON.parse(raw);
11339
- const dataDir = v.dataDir && isAbsolutePath(v.dataDir) ? v.dataDir : base;
11340
- const port = Number(v.port);
11341
- return [
11342
- {
11343
- dataDir: normalizePath(dataDir),
11344
- host: DEFAULT_SERVE_HOST,
11345
- port: Number.isFinite(port) && port > 0 ? port : DEFAULT_SERVE_PORT,
11346
- managed: true
11286
+ for (const day of Object.keys(this.rollup.byDay)) {
11287
+ if (!keep.has(day.slice(0, 7))) {
11288
+ delete this.rollup.byDay[day];
11289
+ changed = true;
11347
11290
  }
11348
- ];
11349
- } catch {
11350
- return [];
11351
- }
11352
- }
11353
- function mergeServeInstances(discovered, managed) {
11354
- const out = [];
11355
- const push = (inst) => {
11356
- if (!out.some((o) => o.dataDir === inst.dataDir)) out.push(inst);
11357
- };
11358
- for (const inst of discovered) {
11359
- const hit = managed.find((m) => m.dataDir === inst.dataDir);
11360
- push(hit ? { ...inst, managed: true } : inst);
11361
- }
11362
- for (const inst of managed) push(inst);
11363
- return out;
11364
- }
11365
- function discoverRunningServeInstances() {
11366
- const found = process.platform === "linux" ? discoverFromProc() : process.platform === "win32" ? discoverFromWindows() : discoverFromPs();
11367
- const declared = process.platform === "linux" ? systemdServiceInstances() : process.platform === "win32" ? windowsServiceInstances() : launchdServiceInstances();
11368
- return mergeServeInstances(found, declared);
11369
- }
11370
-
11371
- // src/cli/link.ts
11372
- function expandHome(p) {
11373
- const home = process.env.HOME || process.env.USERPROFILE;
11374
- if (!home) return p;
11375
- if (p === "~") return home;
11376
- if (p.startsWith("~/") || p.startsWith("~\\")) return path23.join(home, p.slice(2));
11377
- return p;
11378
- }
11379
- function installDataDirHint() {
11380
- if (process.platform === "darwin") return "$HOME/Library/Application Support/zqagent";
11381
- if (process.platform === "win32") return "%APPDATA%\\zqagent";
11382
- return "/var/lib/zqagent\uFF08\u9700 root\uFF1B\u666E\u901A\u7528\u6237\u7528 ~/.zqagent_data\uFF09";
11383
- }
11384
- function parseLinkArgs(argv) {
11385
- const out = {};
11386
- for (let i = 0; i < argv.length; i++) {
11387
- const a = argv[i];
11388
- const val = argv[i + 1];
11389
- if (a === "--hub") {
11390
- out.hubUrl = val;
11391
- i++;
11392
- } else if (a === "--code") {
11393
- out.code = val;
11394
- i++;
11395
- } else if (a === "--data-dir") {
11396
- out.dataDir = val;
11397
- i++;
11398
- } else if (a === "--label") {
11399
- out.label = val;
11400
- i++;
11401
- } else if (a === "--host") {
11402
- out.host = val;
11403
- i++;
11404
- } else if (a === "--port") {
11405
- out.port = Number(val);
11406
- i++;
11407
- } else if (a === "-h" || a === "--help") {
11408
- out.help = true;
11409
11291
  }
11292
+ if (changed) this.saveRollupNow();
11410
11293
  }
11411
- return out;
11412
- }
11413
- function ensureNodeId(store) {
11414
- let id = store.loadNodeId();
11415
- if (!id) {
11416
- id = newId();
11417
- store.saveNodeId(id);
11418
- }
11419
- return id;
11420
- }
11421
- async function probeServer(host, port, adminKey) {
11422
- const ctrl = new AbortController();
11423
- const timer = setTimeout(() => ctrl.abort(), 1500);
11424
- try {
11425
- const res = await fetch(`http://${host}:${port}/api/v1/fleet/status`, {
11426
- headers: { Authorization: `Bearer ${adminKey}` },
11427
- signal: ctrl.signal
11428
- });
11429
- return res.ok;
11430
- } catch {
11431
- return false;
11432
- } finally {
11433
- clearTimeout(timer);
11294
+ // ===================== 价格 =====================
11295
+ getPricing() {
11296
+ return this.pricing;
11434
11297
  }
11435
- }
11436
- async function enrollViaServer(host, port, adminKey, opts) {
11437
- const res = await fetch(`http://${host}:${port}/api/v1/fleet/enroll`, {
11438
- method: "POST",
11439
- headers: {
11440
- "Content-Type": "application/json",
11441
- Authorization: `Bearer ${adminKey}`
11442
- },
11443
- body: JSON.stringify({ hubUrl: opts.hubUrl, code: opts.code, label: opts.label })
11444
- });
11445
- const text = await res.text();
11446
- let payload = null;
11447
- try {
11448
- payload = text ? JSON.parse(text) : null;
11449
- } catch {
11298
+ /** 更新价格/汇率(整表覆盖式合并) */
11299
+ updatePricing(patch) {
11300
+ this.pricing = {
11301
+ schema: USAGE_SCHEMA,
11302
+ base: "USD",
11303
+ rate: { ...this.pricing.rate, ...patch.rate ?? {} },
11304
+ vendors: { ...this.pricing.vendors, ...patch.vendors ?? {} },
11305
+ fallback: { ...this.pricing.fallback, ...patch.fallback ?? {} }
11306
+ };
11307
+ atomicWrite(this.pricingFile, JSON.stringify(this.pricing, null, 2));
11308
+ return this.pricing;
11450
11309
  }
11451
- if (!res.ok) {
11452
- throw new Error(payload?.error?.message ?? `HTTP ${res.status}`);
11310
+ priceFor(vendor, model) {
11311
+ const table = this.pricing.vendors[vendor];
11312
+ if (table) {
11313
+ if (table[model]) return table[model];
11314
+ for (const key of Object.keys(table)) {
11315
+ if (key.endsWith("*") && model.startsWith(key.slice(0, -1)))
11316
+ return table[key];
11317
+ }
11318
+ }
11319
+ for (const v of Object.values(this.pricing.vendors)) {
11320
+ if (v[model]) return v[model];
11321
+ }
11322
+ for (const v of Object.values(this.pricing.vendors)) {
11323
+ for (const key of Object.keys(v)) {
11324
+ if (key.endsWith("*") && model.startsWith(key.slice(0, -1)))
11325
+ return v[key];
11326
+ }
11327
+ }
11328
+ return null;
11453
11329
  }
11454
- return payload?.data;
11455
- }
11456
- function hotWarnings(inst, running) {
11457
- const warnings = [];
11458
- const managed = running.filter((r) => r.managed && r.dataDir !== inst.dataDir);
11459
- if (!inst.managed && managed.length > 0) {
11460
- warnings.push(
11461
- `\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`
11462
- );
11463
- warnings.push(
11464
- "\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"
11465
- );
11330
+ /** 成本(USD):input 已含 cacheRead,故按非缓存部分计价 */
11331
+ costOf(vendor, model, t) {
11332
+ const p = this.priceFor(vendor, model);
11333
+ if (!p) return null;
11334
+ const cached3 = Math.min(t.cacheRead, t.input);
11335
+ const fresh = Math.max(0, t.input - cached3);
11336
+ const M = 1e6;
11337
+ const cost = (fresh * p.input + cached3 * (p.cacheRead ?? p.input) + t.output * p.output + t.cacheWrite * (p.cacheWrite ?? 0)) / M;
11338
+ return Number.isFinite(cost) ? cost : null;
11466
11339
  }
11467
- const others = [...new Set(running.map((r) => r.dataDir).filter((d) => d !== inst.dataDir))];
11468
- if (others.length > 0) {
11469
- 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`);
11340
+ convert(usd, currency) {
11341
+ if (!currency || currency === "USD") return usd;
11342
+ const r = this.pricing.rate[currency];
11343
+ return typeof r === "number" && r > 0 ? usd * r : usd;
11470
11344
  }
11471
- return warnings;
11472
- }
11473
- async function linkNode(opts) {
11474
- const rawDataDir = opts.dataDir?.trim() || process.env.DATA_DIR?.trim() || void 0;
11475
- const explicitDataDir = rawDataDir ? expandHome(rawDataDir) : void 0;
11476
- const host = opts.host?.trim() || process.env.HOST?.trim() || DEFAULT_SERVE_HOST;
11477
- const port = opts.port ?? Number(process.env.PORT ?? DEFAULT_SERVE_PORT);
11478
- const running = discoverRunningServeInstances();
11479
- const candidates = buildHotCandidates(explicitDataDir, running, { host, port });
11480
- for (const inst of candidates) {
11481
- const store2 = new Store(inst.dataDir);
11482
- const adminKey = process.env.ADMIN_API_KEY ?? store2.loadAdminKey();
11483
- if (!adminKey) continue;
11484
- if (!await probeServer(inst.host, inst.port, adminKey)) continue;
11485
- const status = await enrollViaServer(inst.host, inst.port, adminKey, opts);
11486
- return {
11487
- mode: "hot",
11488
- ...status,
11489
- dataDir: inst.dataDir,
11490
- managed: inst.managed,
11491
- warnings: hotWarnings(inst, running)
11345
+ // ===================== 记账 =====================
11346
+ /** 追加一条用量(串行 + appendFileSync;同步返回,便于在事件回调里调用) */
11347
+ record(input) {
11348
+ this.ensureInit();
11349
+ const costUsd = this.costOf(input.vendor, input.model, input);
11350
+ const rec = {
11351
+ ...input,
11352
+ id: input.id ?? `${input.sessionId}:${input.ts}:${Math.random().toString(36).slice(2, 8)}`,
11353
+ costUsd,
11354
+ priced: costUsd != null
11492
11355
  };
11493
- }
11494
- const managedDir = running.find((r) => r.managed)?.dataDir;
11495
- const dataDir = path23.resolve(explicitDataDir ?? managedDir ?? DEFAULT_DATA_DIR);
11496
- const targetedService = !explicitDataDir && Boolean(managedDir) && dataDir === managedDir;
11497
- const store = new Store(dataDir);
11498
- const nodeId = ensureNodeId(store);
11499
- const cred = await hubEnrollByCode(opts.hubUrl, {
11500
- code: opts.code,
11501
- nodeId,
11502
- label: opts.label
11503
- });
11504
- try {
11505
- store.saveFleetCredential({
11506
- ...cred,
11507
- label: opts.label ?? cred.label,
11508
- enrolledAt: (/* @__PURE__ */ new Date()).toISOString()
11509
- });
11510
- } catch (e) {
11511
- const err = e;
11512
- if (err.code === "EACCES" || err.code === "EPERM") {
11513
- throw new Error(
11514
- `\u65E0\u6CD5\u5199\u5165\u6570\u636E\u76EE\u5F55 ${dataDir}\uFF08${err.code}: ${err.message}\uFF09
11515
- \u8BE5\u76EE\u5F55\u5F53\u524D\u7528\u6237\u6CA1\u6709\u5199\u6743\u9650\u3002\u4E24\u79CD\u6539\u6CD5\uFF1A
11516
- 1) \u53BB\u6389 --data-dir\uFF0C\u8BA9 link \u81EA\u52A8\u53D1\u73B0\u672C\u673A\u8FD0\u884C\u7684\u8282\u70B9\uFF08\u63A8\u8350\uFF09\uFF1B
11517
- 2) \u7528\u5F53\u524D\u7528\u6237\u53EF\u5199\u7684\u76EE\u5F55\uFF0C\u672C\u5E73\u53F0\u5B89\u88C5\u811A\u672C\u9ED8\u8BA4\uFF1A${installDataDirHint()}
11518
- \uFF08\u63A5\u5165\u7801\u4E00\u6B21\u6027\uFF0C\u9700\u8981\u91CD\u65B0\u751F\u6210\uFF09`
11519
- );
11356
+ const line = JSON.stringify(rec) + "\n";
11357
+ try {
11358
+ fs19.appendFileSync(this.ledgerFile(monthOf(rec.ts)), line);
11359
+ } catch (e) {
11360
+ console.error(`[usage] \u5199\u8D26\u672C\u5931\u8D25: ${String(e)}`);
11520
11361
  }
11521
- throw e;
11362
+ addAgg(this.rollup.total, rec, costUsd);
11363
+ const day = this.rollup.byDay[utcDay(rec.ts)] ?? (this.rollup.byDay[utcDay(rec.ts)] = zeroAgg());
11364
+ addAgg(day, rec, costUsd);
11365
+ const mk = `${rec.vendor}/${rec.model}`;
11366
+ const bm = this.rollup.byModel[mk] ?? (this.rollup.byModel[mk] = zeroAgg());
11367
+ addAgg(bm, rec, costUsd);
11368
+ const bp = this.rollup.byProject[rec.projectId] ?? (this.rollup.byProject[rec.projectId] = zeroAgg());
11369
+ addAgg(bp, rec, costUsd);
11370
+ this.scheduleRollupSave();
11371
+ return rec;
11522
11372
  }
11523
- const otherRunningDirs = [
11524
- ...new Set(running.map((r) => r.dataDir).filter((d) => d !== dataDir))
11525
- ];
11526
- return {
11527
- mode: "saved",
11528
- nodeId,
11529
- gatewayUrl: cred.gatewayUrl,
11530
- label: opts.label,
11531
- dataDir,
11532
- explicitDataDir: Boolean(explicitDataDir),
11533
- runningServiceDirs: otherRunningDirs,
11534
- targetedService
11535
- };
11536
- }
11537
- function printLinkHelp() {
11538
- cliLog("\u7528\u6CD5: zqagent link --hub <url> --code <\u6CE8\u518C\u7801> [--label \u540D\u79F0]");
11539
- 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");
11540
- 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");
11541
- 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");
11542
- 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");
11543
- }
11544
- async function runLinkCommand(argv) {
11545
- const args = parseLinkArgs(argv);
11546
- if (args.help) {
11547
- printLinkHelp();
11548
- return true;
11373
+ /** 写入历史基线(仅一次;迁移自既有消息 usage) */
11374
+ seedLegacy(agg, throughTs) {
11375
+ this.ensureInit();
11376
+ if (this.rollup.legacy) return;
11377
+ this.rollup.legacy = { ...agg, throughTs };
11378
+ if (throughTs < this.rollup.since) this.rollup.since = throughTs;
11379
+ this.rollup.total.calls += agg.calls;
11380
+ this.rollup.total.input += agg.input;
11381
+ this.rollup.total.output += agg.output;
11382
+ this.rollup.total.cacheRead += agg.cacheRead;
11383
+ this.rollup.total.cacheWrite += agg.cacheWrite;
11384
+ this.rollup.total.costUsd += agg.costUsd;
11385
+ this.rollup.total.unpriced += agg.unpriced;
11386
+ this.saveRollupNow();
11549
11387
  }
11550
- const hubUrl = (args.hubUrl ?? process.env.HUB_URL ?? "").trim().replace(/\/$/, "");
11551
- const code = (args.code ?? "").trim();
11552
- if (!hubUrl || !code) {
11553
- cliErr("\u7F3A\u5C11 --hub \u6216 --code\u3002");
11554
- printLinkHelp();
11555
- return false;
11388
+ ensureInit() {
11389
+ if (!this.inited) this.init();
11556
11390
  }
11557
- try {
11558
- const r = await linkNode({
11559
- hubUrl,
11560
- code,
11561
- dataDir: args.dataDir,
11562
- label: args.label,
11563
- host: args.host,
11564
- port: args.port
11565
- });
11566
- if (r.mode === "hot") {
11567
- cliLog("\u2705 \u5DF2\u63A5\u5165 Hub\uFF08\u70ED\u63A5\u5165\uFF0C\u514D\u91CD\u542F\uFF09");
11568
- cliLog(` \u8282\u70B9: ${r.nodeId}${r.label ? ` (${r.label})` : ""}`);
11569
- cliLog(` \u7F51\u5173: ${r.gatewayUrl ?? "\uFF08\u672A\u4E0B\u53D1\uFF09"} \u5728\u7EBF: ${r.online ? "\u662F" : "\u8FDE\u63A5\u4E2D\u2026"}`);
11570
- cliLog(` \u6570\u636E\u76EE\u5F55: ${r.dataDir}${r.managed ? "\uFF08\u6258\u7BA1\u670D\u52A1\uFF0C\u5F00\u673A\u81EA\u542F\uFF09" : ""}`);
11571
- if (r.warnings.length > 0) {
11572
- cliLog("");
11573
- for (const w of r.warnings) cliLog(`\u26A0\uFE0F ${w}`);
11574
- }
11575
- } else {
11576
- cliLog("\u2705 \u63A5\u5165\u51ED\u636E\u5DF2\u4FDD\u5B58\uFF08\u672C\u673A\u6682\u65E0\u8FD0\u884C\u4E2D\u7684 server\uFF09");
11577
- cliLog(` \u8282\u70B9: ${r.nodeId}`);
11578
- cliLog(` \u7F51\u5173: ${r.gatewayUrl ?? "\uFF08\u672A\u4E0B\u53D1\uFF09"}`);
11579
- cliLog(` \u6570\u636E\u76EE\u5F55: ${r.dataDir}`);
11580
- cliLog(" \u4E0B\u6B21\u542F\u52A8 server \u5373\u81EA\u52A8\u63A5\u5165\uFF1B\u6216\u8FD0\u884C zqagent serve \u5E38\u9A7B\u4E3A\u8282\u70B9\u3002");
11581
- if (r.targetedService) {
11582
- cliLog("");
11583
- 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");
11584
- cliLog(" \u7ACB\u5373\u751F\u6548\uFF1A\u91CD\u542F\u8BE5\u670D\u52A1\uFF08Linux: systemctl restart zqagent\uFF1B");
11585
- cliLog(" macOS: launchctl kickstart -k gui/$(id -u)/com.zqagent.node\uFF09\u3002");
11586
- } else if (r.runningServiceDirs.length > 0) {
11587
- cliLog("");
11588
- 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");
11589
- cliLog(` \u672C\u6B21\u5199\u5165: ${r.dataDir}`);
11590
- for (const d of r.runningServiceDirs) cliLog(` \u670D\u52A1\u4F7F\u7528: ${d}`);
11591
- 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");
11592
- 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");
11593
- cliLog(` zqagent link --hub <Hub\u5730\u5740> --code <\u65B0\u63A5\u5165\u7801> --data-dir "${r.runningServiceDirs[0]}"`);
11594
- } else if (!r.explicitDataDir) {
11595
- cliLog("");
11596
- 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");
11597
- 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");
11598
- cliLog(" curl -fsSL https://agent.cd-zq.com/install.sh | sh");
11599
- cliLog(` \uFF08\u670D\u52A1\u9ED8\u8BA4\u6570\u636E\u76EE\u5F55\uFF1A${installDataDirHint()}\uFF09`);
11600
- }
11391
+ scheduleRollupSave() {
11392
+ this.rollupDirty = true;
11393
+ if (this.rollupTimer) return;
11394
+ this.rollupTimer = setTimeout(() => {
11395
+ this.rollupTimer = null;
11396
+ if (this.rollupDirty) this.saveRollupNow();
11397
+ }, 1500);
11398
+ this.rollupTimer.unref?.();
11399
+ }
11400
+ saveRollupNow() {
11401
+ this.rollupDirty = false;
11402
+ try {
11403
+ atomicWrite(this.rollupFile, JSON.stringify(this.rollup, null, 2));
11404
+ } catch (e) {
11405
+ console.error(`[usage] \u5199\u6C47\u603B\u5931\u8D25: ${String(e)}`);
11601
11406
  }
11602
- return true;
11603
- } catch (e) {
11604
- cliErr(`\u63A5\u5165\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
11605
- return false;
11606
11407
  }
11607
- }
11608
-
11609
- // src/cli/serve.ts
11610
- init_esm_shims();
11611
-
11612
- // src/server/runtime.ts
11613
- init_esm_shims();
11614
- import * as fs31 from "fs";
11615
- import * as path38 from "path";
11616
-
11617
- // src/server/session.ts
11618
- init_esm_shims();
11619
- import * as path26 from "path";
11620
- import { AsyncLocalStorage } from "async_hooks";
11621
-
11622
- // src/server/mcpConfig.ts
11623
- init_esm_shims();
11624
- import fs20 from "fs";
11625
- import path24 from "path";
11626
- function isValidMcpConfig(v) {
11627
- return !!v && typeof v === "object" && typeof v.name === "string" && (v.type === "stdio" || v.type === "http");
11628
- }
11629
- function readConfigFile(file) {
11630
- if (!file) return [];
11631
- try {
11632
- if (!fs20.existsSync(file)) return [];
11633
- const raw = JSON.parse(fs20.readFileSync(file, "utf8"));
11634
- const list = Array.isArray(raw) ? raw : raw && typeof raw === "object" && Array.isArray(raw.servers) ? raw.servers : [];
11635
- return list.filter(isValidMcpConfig);
11636
- } catch (err) {
11637
- console.error(`[mcp] \u8BFB\u53D6\u914D\u7F6E\u5931\u8D25 ${file}: ${err.message}`);
11638
- return [];
11408
+ /** 读汇总(含历史基线;总量口径) */
11409
+ getRollup() {
11410
+ this.ensureInit();
11411
+ return this.rollup;
11639
11412
  }
11640
- }
11641
- function hasServerMcpConfig() {
11642
- if (readConfigFile(process.env.MCP_CONFIG_PATH).length > 0) return true;
11643
- const workspace = process.env.WORKSPACE;
11644
- if (!workspace) return false;
11645
- return readConfigFile(path24.join(workspace, ".agent", "mcp.json")).length > 0;
11646
- }
11647
- function loadServerMcpConfigs(tenant) {
11648
- const fromEnv = readConfigFile(process.env.MCP_CONFIG_PATH);
11649
- if (fromEnv.length > 0) return fromEnv;
11650
- if (Array.isArray(tenant.mcpServers) && tenant.mcpServers.length > 0) {
11651
- return tenant.mcpServers.filter(isValidMcpConfig);
11413
+ // ===================== 查询 =====================
11414
+ /** 把一批记录聚合为 {total, byDay, byModel, byProject}(用于带 project/session 过滤的查询) */
11415
+ aggregate(rows) {
11416
+ const total = zeroAgg();
11417
+ const byDay = {};
11418
+ const byModel = {};
11419
+ const byProject = {};
11420
+ for (const rec of rows) {
11421
+ addAgg(total, rec, rec.costUsd);
11422
+ const d = byDay[utcDay(rec.ts)] ?? (byDay[utcDay(rec.ts)] = zeroAgg());
11423
+ addAgg(d, rec, rec.costUsd);
11424
+ const mk = `${rec.vendor}/${rec.model}`;
11425
+ const bm = byModel[mk] ?? (byModel[mk] = zeroAgg());
11426
+ addAgg(bm, rec, rec.costUsd);
11427
+ const bp = byProject[rec.projectId] ?? (byProject[rec.projectId] = zeroAgg());
11428
+ addAgg(bp, rec, rec.costUsd);
11429
+ }
11430
+ return { total, byDay, byModel, byProject };
11652
11431
  }
11653
- const workspace = tenant.workspaceRoot ?? process.env.WORKSPACE;
11654
- if (workspace)
11655
- return readConfigFile(path24.join(workspace, ".agent", "mcp.json"));
11656
- return [];
11657
- }
11658
-
11659
- // src/server/nodeLlm.ts
11660
- init_esm_shims();
11661
- var GLOBAL_PROVIDER_ID = "__env__";
11662
- function envProvider() {
11663
- const apiKey = process.env.OPENAI_API_KEY;
11664
- if (!apiKey) return null;
11665
- return {
11666
- id: GLOBAL_PROVIDER_ID,
11667
- name: process.env.OPENAI_MODEL || process.env.MODEL || "\u5168\u5C40 LLM",
11668
- type: "env",
11669
- baseURL: process.env.OPENAI_BASE_URL ?? "",
11670
- model: process.env.OPENAI_MODEL || process.env.MODEL || "",
11671
- apiKey
11672
- };
11673
- }
11674
- function normReasoningEffort(v) {
11675
- return v === "low" || v === "medium" || v === "high" ? v : null;
11676
- }
11677
- function normalizeModelList(v) {
11678
- if (!Array.isArray(v)) return void 0;
11679
- const out = [];
11680
- const seen = /* @__PURE__ */ new Set();
11681
- for (const x of v) {
11682
- const s = String(x ?? "").trim();
11683
- if (!s || seen.has(s)) continue;
11684
- seen.add(s);
11685
- out.push(s);
11686
- if (out.length >= 500) break;
11432
+ /** 取范围内全部明细(内部分页拉全,供过滤聚合/导出使用) */
11433
+ allRecords(opts) {
11434
+ const all = [];
11435
+ let offset = 0;
11436
+ for (; ; ) {
11437
+ const page = this.records({ ...opts, limit: 1e3, offset });
11438
+ all.push(...page.rows);
11439
+ offset += 1e3;
11440
+ if (offset >= page.total) break;
11441
+ }
11442
+ return all;
11687
11443
  }
11688
- return out;
11689
- }
11690
- function nodeLlmView(cfg) {
11691
- const g = envProvider();
11692
- const providers = [
11693
- ...g ? [
11694
- {
11695
- id: g.id,
11696
- name: g.name,
11697
- type: g.type,
11698
- baseURL: g.baseURL,
11699
- model: g.model,
11700
- hasApiKey: true,
11701
- global: true
11444
+ summary(opts) {
11445
+ this.ensureInit();
11446
+ const currency = (opts.currency ?? "USD").toUpperCase();
11447
+ const rate = this.convert(1, currency);
11448
+ const from = opts.from ?? "";
11449
+ const to = opts.to ?? "";
11450
+ const groupBy = opts.groupBy;
11451
+ if (opts.projectId || opts.sessionId) {
11452
+ const rows = this.allRecords({
11453
+ from: opts.from,
11454
+ to: opts.to,
11455
+ projectId: opts.projectId,
11456
+ sessionId: opts.sessionId
11457
+ });
11458
+ const agg = this.aggregate(rows);
11459
+ const groups2 = [];
11460
+ if (groupBy === "day") {
11461
+ for (const [k, v] of Object.entries(agg.byDay).sort(
11462
+ (a, b) => a[0].localeCompare(b[0])
11463
+ ))
11464
+ groups2.push({ key: k, agg: v });
11465
+ } else if (groupBy === "model") {
11466
+ for (const [k, v] of Object.entries(agg.byModel))
11467
+ groups2.push({ key: k, agg: v });
11468
+ groups2.sort(
11469
+ (a, b) => b.agg.costUsd - a.agg.costUsd || b.agg.input - a.agg.input
11470
+ );
11471
+ } else if (groupBy === "project") {
11472
+ for (const [k, v] of Object.entries(agg.byProject))
11473
+ groups2.push({ key: k, agg: v });
11702
11474
  }
11703
- ] : [],
11704
- ...cfg.providers.map((p) => ({
11705
- id: p.id,
11706
- name: p.name,
11707
- type: p.type ?? "",
11708
- baseURL: p.baseURL,
11709
- model: p.model,
11710
- hasApiKey: Boolean(p.apiKey),
11711
- models: p.models ?? [],
11712
- nonChat: p.nonChat ?? [],
11713
- modelMeta: p.modelMeta ?? [],
11714
- reasoningEffort: p.reasoningEffort ?? null,
11715
- global: false
11716
- }))
11717
- ];
11718
- return {
11719
- defaultProviderId: cfg.defaultProviderId ?? null,
11720
- providers,
11721
- /** 未显式设置默认时,是否由环境变量全局配置兜底 */
11722
- globalIsDefault: !cfg.defaultProviderId && Boolean(g)
11723
- };
11724
- }
11725
- function applyNodeLlmPatch(cfg, raw) {
11726
- const next = { providers: cfg.providers, defaultProviderId: cfg.defaultProviderId };
11727
- if (Array.isArray(raw?.providers)) {
11728
- const old = new Map(cfg.providers.map((p) => [p.id, p]));
11729
- next.providers = raw.providers.filter((x) => Boolean(x) && typeof x === "object").filter((x) => x.global !== true && String(x.id ?? "") !== GLOBAL_PROVIDER_ID).map((x) => {
11730
- const id = String(x.id ?? "").trim();
11731
- if (!id) throw new ApiError(400, "bad_request", "\u4F9B\u5E94\u5546 id \u4E0D\u80FD\u4E3A\u7A7A");
11732
- const prev = old.get(id);
11733
- const apiKey = String(x.apiKey ?? "").trim();
11734
- const models = normalizeModelList(x.models) ?? prev?.models;
11735
- const nonChat = normalizeModelList(x.nonChat) ?? prev?.nonChat;
11736
- const modelMeta = normalizeModelMeta(x.modelMeta) ?? prev?.modelMeta;
11737
- return {
11738
- id,
11739
- name: String(x.name ?? prev?.name ?? id).trim(),
11740
- type: x.type == null ? prev?.type : String(x.type),
11741
- baseURL: String(x.baseURL ?? prev?.baseURL ?? "").trim(),
11742
- model: String(x.model ?? prev?.model ?? "").trim(),
11743
- apiKey: apiKey || prev?.apiKey,
11744
- reasoningEffort: x.reasoningEffort === void 0 ? prev?.reasoningEffort ?? null : normReasoningEffort(x.reasoningEffort),
11745
- ...models ? { models } : {},
11746
- ...nonChat ? { nonChat } : {},
11747
- ...modelMeta ? { modelMeta } : {}
11748
- };
11749
- });
11750
- }
11751
- if (typeof raw?.defaultProviderId === "string") {
11752
- const id = raw.defaultProviderId.trim();
11753
- next.defaultProviderId = id || void 0;
11754
- } else if (raw?.defaultProviderId === null) {
11755
- next.defaultProviderId = void 0;
11756
- }
11757
- return next;
11758
- }
11759
- function findNodeProvider(store, id) {
11760
- if (!id) return null;
11761
- if (id === GLOBAL_PROVIDER_ID) return envProvider();
11762
- return store.loadNodeLlm().providers.find((p) => p.id === id) ?? null;
11763
- }
11764
- function nodeDefaultProvider(store) {
11765
- const cfg = store.loadNodeLlm();
11766
- if (cfg.providers.length === 0) return null;
11767
- return cfg.providers.find((p) => p.id === cfg.defaultProviderId) ?? cfg.providers[0] ?? null;
11768
- }
11769
- function resolveSessionProvider(store, tenant, providerId) {
11770
- const own = tenant.provider ?? {};
11771
- if (providerId) {
11772
- const picked = findNodeProvider(store, providerId);
11773
- if (picked) {
11774
11475
  return {
11775
- apiKey: picked.apiKey || own.apiKey,
11776
- baseURL: picked.baseURL || own.baseURL,
11777
- model: picked.model,
11778
- providerId,
11779
- reasoningEffort: picked.reasoningEffort ?? null,
11780
- ...picked.modelMeta ? { modelMeta: picked.modelMeta } : {}
11476
+ currency,
11477
+ rate,
11478
+ since: this.rollup.since,
11479
+ total: agg.total,
11480
+ groups: groups2
11781
11481
  };
11782
11482
  }
11783
- }
11784
- if (own.apiKey) return { ...own, model: tenant.model };
11785
- const def = nodeDefaultProvider(store);
11786
- if (def?.apiKey) {
11483
+ const groups = [];
11484
+ let total = { ...this.rollup.total };
11485
+ if (groupBy === "day") {
11486
+ const picked = Object.entries(this.rollup.byDay).filter(([k]) => (!from || k >= from) && (!to || k <= to)).sort((a, b) => a[0].localeCompare(b[0]));
11487
+ const agg = zeroAgg();
11488
+ for (const [k, v] of picked) {
11489
+ groups.push({ key: k, agg: v });
11490
+ agg.calls += v.calls;
11491
+ agg.input += v.input;
11492
+ agg.output += v.output;
11493
+ agg.cacheRead += v.cacheRead;
11494
+ agg.cacheWrite += v.cacheWrite;
11495
+ agg.costUsd += v.costUsd;
11496
+ agg.unpriced += v.unpriced;
11497
+ }
11498
+ total = agg;
11499
+ } else if (groupBy === "model") {
11500
+ for (const [k, v] of Object.entries(this.rollup.byModel))
11501
+ groups.push({ key: k, agg: v });
11502
+ groups.sort(
11503
+ (a, b) => b.agg.costUsd - a.agg.costUsd || b.agg.input - a.agg.input
11504
+ );
11505
+ } else if (groupBy === "project") {
11506
+ for (const [k, v] of Object.entries(this.rollup.byProject))
11507
+ groups.push({ key: k, agg: v });
11508
+ groups.sort(
11509
+ (a, b) => b.agg.costUsd - a.agg.costUsd || b.agg.input - a.agg.input
11510
+ );
11511
+ }
11787
11512
  return {
11788
- apiKey: def.apiKey,
11789
- baseURL: own.baseURL || def.baseURL,
11790
- model: def.model || tenant.model,
11791
- reasoningEffort: def.reasoningEffort ?? null,
11792
- ...def.modelMeta ? { modelMeta: def.modelMeta } : {}
11513
+ currency,
11514
+ rate,
11515
+ since: this.rollup.since,
11516
+ total,
11517
+ legacy: this.rollup.legacy,
11518
+ groups
11793
11519
  };
11794
11520
  }
11795
- return { ...own, model: tenant.model };
11796
- }
11797
-
11798
- // src/server/usage.ts
11799
- init_esm_shims();
11800
- import * as fs21 from "fs";
11801
- import * as path25 from "path";
11802
- var LEDGER_RETENTION_MONTHS = 12;
11803
- var USAGE_SCHEMA = 1;
11804
- var DEFAULT_PRICING = {
11805
- schema: USAGE_SCHEMA,
11806
- base: "USD",
11807
- rate: { CNY: 7.2 },
11808
- vendors: {
11809
- openai: {
11810
- "gpt-4o": { input: 2.5, output: 10, cacheRead: 1.25 },
11811
- "gpt-4o-mini": { input: 0.15, output: 0.6, cacheRead: 0.075 },
11812
- "gpt-4.1": { input: 2, output: 8, cacheRead: 0.5 }
11813
- },
11814
- deepseek: {
11815
- "deepseek-flash": {
11816
- input: 0.27,
11817
- output: 1.1,
11818
- cacheRead: 0.027,
11819
- cacheWrite: 0.27
11820
- },
11821
- "deepseek-reasoner": {
11822
- input: 0.55,
11823
- output: 2.19,
11824
- cacheRead: 0.055,
11825
- cacheWrite: 0.55
11826
- },
11827
- "deepseek-*": {
11828
- input: 0.27,
11829
- output: 1.1,
11830
- cacheRead: 0.027,
11831
- cacheWrite: 0.27
11832
- }
11833
- },
11834
- qwen: {
11835
- "qwen-plus": { input: 0.4, output: 1.2 },
11836
- "qwen-turbo": { input: 0.05, output: 0.2 }
11837
- },
11838
- zhipu: { "glm-4-plus": { input: 0.7, output: 0.7 } },
11839
- moonshot: { "moonshot-v1-8k": { input: 1.68, output: 1.68 } },
11840
- xiaomi: {},
11841
- custom: {},
11842
- env: {}
11843
- },
11844
- fallback: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
11845
- };
11846
- function zeroAgg() {
11847
- return {
11848
- calls: 0,
11849
- input: 0,
11850
- output: 0,
11851
- cacheRead: 0,
11852
- cacheWrite: 0,
11853
- costUsd: 0,
11854
- unpriced: 0
11855
- };
11856
- }
11857
- function addAgg(into, tokens, costUsd) {
11858
- into.calls += 1;
11859
- into.input += tokens.input;
11860
- into.output += tokens.output;
11861
- into.cacheRead += tokens.cacheRead;
11862
- into.cacheWrite += tokens.cacheWrite;
11863
- if (costUsd == null) into.unpriced += 1;
11864
- else into.costUsd += costUsd;
11865
- }
11866
- function utcDay(iso) {
11867
- return iso.slice(0, 10);
11868
- }
11869
- function monthOf(iso) {
11870
- return iso.slice(0, 7);
11871
- }
11872
- function retainedMonths(now = /* @__PURE__ */ new Date()) {
11873
- const out = /* @__PURE__ */ new Set();
11874
- const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
11875
- for (let i = 0; i < LEDGER_RETENTION_MONTHS; i += 1) {
11876
- out.add(d.toISOString().slice(0, 7));
11877
- d.setUTCMonth(d.getUTCMonth() - 1);
11878
- }
11879
- return out;
11880
- }
11881
- function atomicWrite(file, content) {
11882
- fs21.mkdirSync(path25.dirname(file), { recursive: true });
11883
- const tmp = `${file}.tmp`;
11884
- fs21.writeFileSync(tmp, content, "utf-8");
11885
- fs21.renameSync(tmp, file);
11886
- }
11887
- var UsageLedger = class {
11888
- constructor(root) {
11889
- this.root = root;
11890
- this.dir = path25.join(root, "usage");
11891
- }
11892
- root;
11893
- dir;
11894
- rollup;
11895
- pricing;
11896
- rollupDirty = false;
11897
- rollupTimer = null;
11898
- inited = false;
11899
- get ledgerDir() {
11900
- return this.dir;
11901
- }
11902
- ledgerFile(month) {
11903
- return path25.join(this.ledgerDir, `ledger-${month}.jsonl`);
11904
- }
11905
- get rollupFile() {
11906
- return path25.join(this.ledgerDir, "rollup.json");
11907
- }
11908
- get pricingFile() {
11909
- return path25.join(this.ledgerDir, "pricing.json");
11910
- }
11911
- // ===================== 初始化 =====================
11912
- init(now = /* @__PURE__ */ new Date()) {
11913
- if (this.inited) return;
11914
- fs21.mkdirSync(this.ledgerDir, { recursive: true });
11915
- this.pricing = this.loadPricing();
11916
- this.rollup = this.loadRollup(now);
11917
- this.pruneLedgers(now);
11918
- this.inited = true;
11919
- }
11920
- loadPricing() {
11921
- try {
11922
- const raw = fs21.readFileSync(this.pricingFile, "utf-8");
11923
- const parsed = JSON.parse(raw);
11924
- const vendors = {};
11925
- for (const v of /* @__PURE__ */ new Set([
11926
- ...Object.keys(DEFAULT_PRICING.vendors),
11927
- ...Object.keys(parsed.vendors ?? {})
11928
- ])) {
11929
- vendors[v] = {
11930
- ...DEFAULT_PRICING.vendors[v] ?? {},
11931
- ...(parsed.vendors ?? {})[v] ?? {}
11932
- };
11933
- }
11934
- return {
11935
- schema: USAGE_SCHEMA,
11936
- base: "USD",
11937
- rate: { ...DEFAULT_PRICING.rate, ...parsed.rate ?? {} },
11938
- vendors,
11939
- fallback: { ...DEFAULT_PRICING.fallback, ...parsed.fallback ?? {} }
11940
- };
11941
- } catch {
11521
+ /** 明细(流式读取范围内月份的账本文件) */
11522
+ records(opts) {
11523
+ this.ensureInit();
11524
+ const from = opts.from ?? "";
11525
+ const to = opts.to ?? "";
11526
+ const months = this.monthsInRange(from, to);
11527
+ const rows = [];
11528
+ for (const month of months) {
11529
+ const file = this.ledgerFile(month);
11530
+ if (!fs19.existsSync(file)) continue;
11531
+ let content = "";
11942
11532
  try {
11943
- atomicWrite(this.pricingFile, JSON.stringify(DEFAULT_PRICING, null, 2));
11533
+ content = fs19.readFileSync(file, "utf-8");
11944
11534
  } catch {
11535
+ continue;
11945
11536
  }
11946
- return JSON.parse(JSON.stringify(DEFAULT_PRICING));
11947
- }
11948
- }
11949
- loadRollup(now) {
11950
- try {
11951
- const raw = fs21.readFileSync(this.rollupFile, "utf-8");
11952
- const parsed = JSON.parse(raw);
11953
- return {
11954
- schema: USAGE_SCHEMA,
11955
- since: parsed.since ?? now.toISOString(),
11956
- legacy: parsed.legacy,
11957
- total: { ...zeroAgg(), ...parsed.total },
11958
- byDay: parsed.byDay ?? {},
11959
- byModel: parsed.byModel ?? {},
11960
- byProject: parsed.byProject ?? {}
11961
- };
11962
- } catch {
11963
- return {
11964
- schema: USAGE_SCHEMA,
11965
- since: now.toISOString(),
11966
- total: zeroAgg(),
11967
- byDay: {},
11968
- byModel: {},
11969
- byProject: {}
11970
- };
11971
- }
11972
- }
11973
- /** 删除超出保留期的账本文件,并裁剪 byDay */
11974
- pruneLedgers(now) {
11975
- const keep = retainedMonths(now);
11976
- let changed = false;
11977
- try {
11978
- for (const f of fs21.readdirSync(this.ledgerDir)) {
11979
- const m = /^ledger-(\d{4}-\d{2})\.jsonl$/.exec(f);
11980
- if (m && !keep.has(m[1])) {
11981
- fs21.unlinkSync(path25.join(this.ledgerDir, f));
11537
+ for (const line of content.split("\n")) {
11538
+ if (!line.trim()) continue;
11539
+ let rec;
11540
+ try {
11541
+ rec = JSON.parse(line);
11542
+ } catch {
11543
+ continue;
11982
11544
  }
11983
- }
11984
- } catch {
11985
- }
11986
- for (const day of Object.keys(this.rollup.byDay)) {
11987
- if (!keep.has(day.slice(0, 7))) {
11988
- delete this.rollup.byDay[day];
11989
- changed = true;
11990
- }
11991
- }
11992
- if (changed) this.saveRollupNow();
11993
- }
11994
- // ===================== 价格 =====================
11995
- getPricing() {
11996
- return this.pricing;
11997
- }
11998
- /** 更新价格/汇率(整表覆盖式合并) */
11999
- updatePricing(patch) {
12000
- this.pricing = {
12001
- schema: USAGE_SCHEMA,
12002
- base: "USD",
12003
- rate: { ...this.pricing.rate, ...patch.rate ?? {} },
12004
- vendors: { ...this.pricing.vendors, ...patch.vendors ?? {} },
12005
- fallback: { ...this.pricing.fallback, ...patch.fallback ?? {} }
12006
- };
12007
- atomicWrite(this.pricingFile, JSON.stringify(this.pricing, null, 2));
12008
- return this.pricing;
12009
- }
12010
- priceFor(vendor, model) {
12011
- const table = this.pricing.vendors[vendor];
12012
- if (table) {
12013
- if (table[model]) return table[model];
12014
- for (const key of Object.keys(table)) {
12015
- if (key.endsWith("*") && model.startsWith(key.slice(0, -1)))
12016
- return table[key];
12017
- }
12018
- }
12019
- for (const v of Object.values(this.pricing.vendors)) {
12020
- if (v[model]) return v[model];
12021
- }
12022
- for (const v of Object.values(this.pricing.vendors)) {
12023
- for (const key of Object.keys(v)) {
12024
- if (key.endsWith("*") && model.startsWith(key.slice(0, -1)))
12025
- return v[key];
11545
+ const day = utcDay(rec.ts);
11546
+ if (from && day < from) continue;
11547
+ if (to && day > to) continue;
11548
+ if (opts.sessionId && rec.sessionId !== opts.sessionId) continue;
11549
+ if (opts.projectId && rec.projectId !== opts.projectId) continue;
11550
+ if (opts.model && rec.model !== opts.model) continue;
11551
+ rows.push(rec);
12026
11552
  }
12027
11553
  }
12028
- return null;
12029
- }
12030
- /** 成本(USD):input 已含 cacheRead,故按非缓存部分计价 */
12031
- costOf(vendor, model, t) {
12032
- const p = this.priceFor(vendor, model);
12033
- if (!p) return null;
12034
- const cached3 = Math.min(t.cacheRead, t.input);
12035
- const fresh = Math.max(0, t.input - cached3);
12036
- const M = 1e6;
12037
- const cost = (fresh * p.input + cached3 * (p.cacheRead ?? p.input) + t.output * p.output + t.cacheWrite * (p.cacheWrite ?? 0)) / M;
12038
- return Number.isFinite(cost) ? cost : null;
12039
- }
12040
- convert(usd, currency) {
12041
- if (!currency || currency === "USD") return usd;
12042
- const r = this.pricing.rate[currency];
12043
- return typeof r === "number" && r > 0 ? usd * r : usd;
12044
- }
12045
- // ===================== 记账 =====================
12046
- /** 追加一条用量(串行 + appendFileSync;同步返回,便于在事件回调里调用) */
12047
- record(input) {
12048
- this.ensureInit();
12049
- const costUsd = this.costOf(input.vendor, input.model, input);
12050
- const rec = {
12051
- ...input,
12052
- id: input.id ?? `${input.sessionId}:${input.ts}:${Math.random().toString(36).slice(2, 8)}`,
12053
- costUsd,
12054
- priced: costUsd != null
12055
- };
12056
- const line = JSON.stringify(rec) + "\n";
12057
- try {
12058
- fs21.appendFileSync(this.ledgerFile(monthOf(rec.ts)), line);
12059
- } catch (e) {
12060
- console.error(`[usage] \u5199\u8D26\u672C\u5931\u8D25: ${String(e)}`);
12061
- }
12062
- addAgg(this.rollup.total, rec, costUsd);
12063
- const day = this.rollup.byDay[utcDay(rec.ts)] ?? (this.rollup.byDay[utcDay(rec.ts)] = zeroAgg());
12064
- addAgg(day, rec, costUsd);
12065
- const mk = `${rec.vendor}/${rec.model}`;
12066
- const bm = this.rollup.byModel[mk] ?? (this.rollup.byModel[mk] = zeroAgg());
12067
- addAgg(bm, rec, costUsd);
12068
- const bp = this.rollup.byProject[rec.projectId] ?? (this.rollup.byProject[rec.projectId] = zeroAgg());
12069
- addAgg(bp, rec, costUsd);
12070
- this.scheduleRollupSave();
12071
- return rec;
12072
- }
12073
- /** 写入历史基线(仅一次;迁移自既有消息 usage) */
12074
- seedLegacy(agg, throughTs) {
12075
- this.ensureInit();
12076
- if (this.rollup.legacy) return;
12077
- this.rollup.legacy = { ...agg, throughTs };
12078
- if (throughTs < this.rollup.since) this.rollup.since = throughTs;
12079
- this.rollup.total.calls += agg.calls;
12080
- this.rollup.total.input += agg.input;
12081
- this.rollup.total.output += agg.output;
12082
- this.rollup.total.cacheRead += agg.cacheRead;
12083
- this.rollup.total.cacheWrite += agg.cacheWrite;
12084
- this.rollup.total.costUsd += agg.costUsd;
12085
- this.rollup.total.unpriced += agg.unpriced;
12086
- this.saveRollupNow();
12087
- }
12088
- ensureInit() {
12089
- if (!this.inited) this.init();
12090
- }
12091
- scheduleRollupSave() {
12092
- this.rollupDirty = true;
12093
- if (this.rollupTimer) return;
12094
- this.rollupTimer = setTimeout(() => {
12095
- this.rollupTimer = null;
12096
- if (this.rollupDirty) this.saveRollupNow();
12097
- }, 1500);
12098
- this.rollupTimer.unref?.();
12099
- }
12100
- saveRollupNow() {
12101
- this.rollupDirty = false;
12102
- try {
12103
- atomicWrite(this.rollupFile, JSON.stringify(this.rollup, null, 2));
12104
- } catch (e) {
12105
- console.error(`[usage] \u5199\u6C47\u603B\u5931\u8D25: ${String(e)}`);
12106
- }
12107
- }
12108
- /** 读汇总(含历史基线;总量口径) */
12109
- getRollup() {
12110
- this.ensureInit();
12111
- return this.rollup;
12112
- }
12113
- // ===================== 查询 =====================
12114
- /** 把一批记录聚合为 {total, byDay, byModel, byProject}(用于带 project/session 过滤的查询) */
12115
- aggregate(rows) {
12116
- const total = zeroAgg();
12117
- const byDay = {};
12118
- const byModel = {};
12119
- const byProject = {};
12120
- for (const rec of rows) {
12121
- addAgg(total, rec, rec.costUsd);
12122
- const d = byDay[utcDay(rec.ts)] ?? (byDay[utcDay(rec.ts)] = zeroAgg());
12123
- addAgg(d, rec, rec.costUsd);
12124
- const mk = `${rec.vendor}/${rec.model}`;
12125
- const bm = byModel[mk] ?? (byModel[mk] = zeroAgg());
12126
- addAgg(bm, rec, rec.costUsd);
12127
- const bp = byProject[rec.projectId] ?? (byProject[rec.projectId] = zeroAgg());
12128
- addAgg(bp, rec, rec.costUsd);
12129
- }
12130
- return { total, byDay, byModel, byProject };
12131
- }
12132
- /** 取范围内全部明细(内部分页拉全,供过滤聚合/导出使用) */
12133
- allRecords(opts) {
12134
- const all = [];
12135
- let offset = 0;
12136
- for (; ; ) {
12137
- const page = this.records({ ...opts, limit: 1e3, offset });
12138
- all.push(...page.rows);
12139
- offset += 1e3;
12140
- if (offset >= page.total) break;
12141
- }
12142
- return all;
12143
- }
12144
- summary(opts) {
12145
- this.ensureInit();
12146
- const currency = (opts.currency ?? "USD").toUpperCase();
12147
- const rate = this.convert(1, currency);
12148
- const from = opts.from ?? "";
12149
- const to = opts.to ?? "";
12150
- const groupBy = opts.groupBy;
12151
- if (opts.projectId || opts.sessionId) {
12152
- const rows = this.allRecords({
12153
- from: opts.from,
12154
- to: opts.to,
12155
- projectId: opts.projectId,
12156
- sessionId: opts.sessionId
12157
- });
12158
- const agg = this.aggregate(rows);
12159
- const groups2 = [];
12160
- if (groupBy === "day") {
12161
- for (const [k, v] of Object.entries(agg.byDay).sort(
12162
- (a, b) => a[0].localeCompare(b[0])
12163
- ))
12164
- groups2.push({ key: k, agg: v });
12165
- } else if (groupBy === "model") {
12166
- for (const [k, v] of Object.entries(agg.byModel))
12167
- groups2.push({ key: k, agg: v });
12168
- groups2.sort(
12169
- (a, b) => b.agg.costUsd - a.agg.costUsd || b.agg.input - a.agg.input
12170
- );
12171
- } else if (groupBy === "project") {
12172
- for (const [k, v] of Object.entries(agg.byProject))
12173
- groups2.push({ key: k, agg: v });
12174
- }
12175
- return {
12176
- currency,
12177
- rate,
12178
- since: this.rollup.since,
12179
- total: agg.total,
12180
- groups: groups2
12181
- };
12182
- }
12183
- const groups = [];
12184
- let total = { ...this.rollup.total };
12185
- if (groupBy === "day") {
12186
- const picked = Object.entries(this.rollup.byDay).filter(([k]) => (!from || k >= from) && (!to || k <= to)).sort((a, b) => a[0].localeCompare(b[0]));
12187
- const agg = zeroAgg();
12188
- for (const [k, v] of picked) {
12189
- groups.push({ key: k, agg: v });
12190
- agg.calls += v.calls;
12191
- agg.input += v.input;
12192
- agg.output += v.output;
12193
- agg.cacheRead += v.cacheRead;
12194
- agg.cacheWrite += v.cacheWrite;
12195
- agg.costUsd += v.costUsd;
12196
- agg.unpriced += v.unpriced;
12197
- }
12198
- total = agg;
12199
- } else if (groupBy === "model") {
12200
- for (const [k, v] of Object.entries(this.rollup.byModel))
12201
- groups.push({ key: k, agg: v });
12202
- groups.sort(
12203
- (a, b) => b.agg.costUsd - a.agg.costUsd || b.agg.input - a.agg.input
12204
- );
12205
- } else if (groupBy === "project") {
12206
- for (const [k, v] of Object.entries(this.rollup.byProject))
12207
- groups.push({ key: k, agg: v });
12208
- groups.sort(
12209
- (a, b) => b.agg.costUsd - a.agg.costUsd || b.agg.input - a.agg.input
12210
- );
12211
- }
12212
- return {
12213
- currency,
12214
- rate,
12215
- since: this.rollup.since,
12216
- total,
12217
- legacy: this.rollup.legacy,
12218
- groups
12219
- };
12220
- }
12221
- /** 明细(流式读取范围内月份的账本文件) */
12222
- records(opts) {
12223
- this.ensureInit();
12224
- const from = opts.from ?? "";
12225
- const to = opts.to ?? "";
12226
- const months = this.monthsInRange(from, to);
12227
- const rows = [];
12228
- for (const month of months) {
12229
- const file = this.ledgerFile(month);
12230
- if (!fs21.existsSync(file)) continue;
12231
- let content = "";
12232
- try {
12233
- content = fs21.readFileSync(file, "utf-8");
12234
- } catch {
12235
- continue;
12236
- }
12237
- for (const line of content.split("\n")) {
12238
- if (!line.trim()) continue;
12239
- let rec;
12240
- try {
12241
- rec = JSON.parse(line);
12242
- } catch {
12243
- continue;
12244
- }
12245
- const day = utcDay(rec.ts);
12246
- if (from && day < from) continue;
12247
- if (to && day > to) continue;
12248
- if (opts.sessionId && rec.sessionId !== opts.sessionId) continue;
12249
- if (opts.projectId && rec.projectId !== opts.projectId) continue;
12250
- if (opts.model && rec.model !== opts.model) continue;
12251
- rows.push(rec);
12252
- }
12253
- }
12254
- rows.sort((a, b) => a.ts < b.ts ? 1 : -1);
12255
- const total = rows.length;
12256
- const offset = Math.max(0, opts.offset ?? 0);
12257
- const limit = Math.max(1, Math.min(1e3, opts.limit ?? 200));
12258
- return { rows: rows.slice(offset, offset + limit), total };
11554
+ rows.sort((a, b) => a.ts < b.ts ? 1 : -1);
11555
+ const total = rows.length;
11556
+ const offset = Math.max(0, opts.offset ?? 0);
11557
+ const limit = Math.max(1, Math.min(1e3, opts.limit ?? 200));
11558
+ return { rows: rows.slice(offset, offset + limit), total };
12259
11559
  }
12260
11560
  /** 导出 CSV(范围同 records) */
12261
11561
  exportCsv(opts) {
@@ -12339,10 +11639,10 @@ var UsageLedger = class {
12339
11639
  const seen = /* @__PURE__ */ new Set();
12340
11640
  for (const month of this.monthsInRange("", "")) {
12341
11641
  const file = this.ledgerFile(month);
12342
- if (!fs21.existsSync(file)) continue;
11642
+ if (!fs19.existsSync(file)) continue;
12343
11643
  let content = "";
12344
11644
  try {
12345
- content = fs21.readFileSync(file, "utf-8");
11645
+ content = fs19.readFileSync(file, "utf-8");
12346
11646
  } catch {
12347
11647
  continue;
12348
11648
  }
@@ -13653,7 +12953,7 @@ data: ${JSON.stringify(entry.data)}
13653
12953
  softThresholdPct: parseRatio(process.env.COMPACTION_SOFT_THRESHOLD_PCT),
13654
12954
  capTokens: parseOptionalPositiveNumber(process.env.COMPACTION_CAP_TOKENS) ?? null,
13655
12955
  // 超预算工具结果按租户/工作区隔离落盘(多租户共用一个状态目录会混内容)
13656
- toolResultDir: path26.join(workspace, ".agent", "tool-results")
12956
+ toolResultDir: path23.join(workspace, ".agent", "tool-results")
13657
12957
  });
13658
12958
  engine.setApprover(this._makeApprover(rt));
13659
12959
  const mcpConfigs = loadServerMcpConfigs(tenant);
@@ -13817,142 +13117,922 @@ data: ${JSON.stringify(entry.data)}
13817
13117
  if (rec.savedAt && rec.savedAt > throughTs) throughTs = rec.savedAt;
13818
13118
  }
13819
13119
  }
13820
- if (agg.calls > 0) ledger.seedLegacy(agg, throughTs);
13821
- return { seeded: agg.calls > 0, calls: agg.calls };
13120
+ if (agg.calls > 0) ledger.seedLegacy(agg, throughTs);
13121
+ return { seeded: agg.calls > 0, calls: agg.calls };
13122
+ }
13123
+ };
13124
+ var ApiError = class extends Error {
13125
+ constructor(status, code, message) {
13126
+ super(message);
13127
+ this.status = status;
13128
+ this.code = code;
13129
+ }
13130
+ status;
13131
+ code;
13132
+ };
13133
+
13134
+ // src/server/projects.ts
13135
+ var DEFAULT_MODEL = () => process.env.OPENAI_MODEL || process.env.MODEL || "gpt-4o";
13136
+ function toProjectSummary(store, tenant) {
13137
+ return {
13138
+ id: tenant.id,
13139
+ name: tenant.name ?? tenant.id,
13140
+ model: tenant.model,
13141
+ baseURL: tenant.provider?.baseURL ?? "",
13142
+ hasApiKey: Boolean(tenant.provider?.apiKey),
13143
+ workspaceRoot: tenant.workspaceRoot ?? null,
13144
+ sessionCount: store.listSessions(tenant.id).length
13145
+ };
13146
+ }
13147
+ function providerViews(tenant) {
13148
+ return {
13149
+ activeProviderId: tenant.activeProviderId ?? null,
13150
+ providers: (tenant.llmProviders ?? []).map((p) => ({
13151
+ id: p.id,
13152
+ name: p.name,
13153
+ type: p.type ?? "",
13154
+ baseURL: p.baseURL,
13155
+ model: p.model,
13156
+ models: p.models ?? [],
13157
+ modelMeta: p.modelMeta ?? [],
13158
+ hasApiKey: Boolean(p.apiKey)
13159
+ }))
13160
+ };
13161
+ }
13162
+ function applyProvidersPatch(tenant, raw) {
13163
+ if (Array.isArray(raw.providers)) {
13164
+ const existing = new Map((tenant.llmProviders ?? []).map((p) => [p.id, p]));
13165
+ const next = [];
13166
+ for (const item of raw.providers) {
13167
+ if (!item || typeof item !== "object") continue;
13168
+ const o = item;
13169
+ const id = String(o.id ?? "").trim() || newId();
13170
+ const prev = existing.get(id);
13171
+ const apiKey = typeof o.apiKey === "string" && o.apiKey.trim() ? o.apiKey.trim() : prev?.apiKey;
13172
+ const models = normalizeModelList(o.models) ?? prev?.models;
13173
+ const modelMeta = normalizeModelMeta(o.modelMeta) ?? prev?.modelMeta;
13174
+ next.push({
13175
+ id,
13176
+ name: String(o.name ?? "").trim() || id,
13177
+ type: typeof o.type === "string" ? o.type : prev?.type,
13178
+ baseURL: String(o.baseURL ?? "").trim(),
13179
+ model: String(o.model ?? "").trim(),
13180
+ ...apiKey ? { apiKey } : {},
13181
+ ...models ? { models } : {},
13182
+ ...modelMeta ? { modelMeta } : {}
13183
+ });
13184
+ }
13185
+ tenant.llmProviders = next;
13186
+ }
13187
+ if (typeof raw.activeProviderId === "string") {
13188
+ const activeId = raw.activeProviderId.trim();
13189
+ tenant.activeProviderId = activeId || void 0;
13190
+ const entry = (tenant.llmProviders ?? []).find((p) => p.id === activeId);
13191
+ if (entry) {
13192
+ tenant.provider = tenant.provider ?? {};
13193
+ tenant.provider.baseURL = entry.baseURL || void 0;
13194
+ if (entry.apiKey) tenant.provider.apiKey = entry.apiKey;
13195
+ if (entry.model) tenant.model = entry.model;
13196
+ }
13197
+ }
13198
+ }
13199
+ function allProjectSummaries(store) {
13200
+ return store.listTenants().map((id) => store.loadTenant(id)).filter((t) => t !== null).map((t) => toProjectSummary(store, t));
13201
+ }
13202
+ function lastUserMessage(rec) {
13203
+ const sent = (rec.lastInput ?? "").trim();
13204
+ if (sent) {
13205
+ return sent.length > LAST_USER_MESSAGE_MAX ? `${sent.slice(0, LAST_USER_MESSAGE_MAX)}\u2026` : sent;
13206
+ }
13207
+ const msgs = rec.messages;
13208
+ for (let i = msgs.length - 1; i >= 0; i--) {
13209
+ const m = msgs[i];
13210
+ if (!m || m.role !== "user" || typeof m.content !== "string") continue;
13211
+ const text = m.content.trim();
13212
+ if (!text) continue;
13213
+ return text.length > LAST_USER_MESSAGE_MAX ? `${text.slice(0, LAST_USER_MESSAGE_MAX)}\u2026` : text;
13214
+ }
13215
+ return null;
13216
+ }
13217
+ function lastCompletedAt(rec) {
13218
+ if (rec.lastCompletedAt) return rec.lastCompletedAt;
13219
+ if (rec.status !== "completed") return null;
13220
+ const msgs = rec.messages;
13221
+ for (let i = msgs.length - 1; i >= 0; i--) {
13222
+ const m = msgs[i];
13223
+ if (!m || m.role !== "assistant") continue;
13224
+ if (typeof m.ts === "number" && Number.isFinite(m.ts)) {
13225
+ return new Date(m.ts).toISOString();
13226
+ }
13227
+ return null;
13228
+ }
13229
+ return null;
13230
+ }
13231
+ function allSessionStates(store, manager) {
13232
+ const out = [];
13233
+ for (const id of store.listTenants()) {
13234
+ const t = store.loadTenant(id);
13235
+ if (!t) continue;
13236
+ try {
13237
+ for (const s of manager.listSessions(t.id)) {
13238
+ out.push({
13239
+ projectId: t.id,
13240
+ projectName: t.name ?? t.id,
13241
+ sessionId: s.sessionId,
13242
+ name: s.name ?? null,
13243
+ status: s.status,
13244
+ lastUserMessage: lastUserMessage(s),
13245
+ lastCompletedAt: lastCompletedAt(s)
13246
+ });
13247
+ }
13248
+ } catch {
13249
+ }
13250
+ }
13251
+ return out;
13252
+ }
13253
+ function ensureNodeId(store) {
13254
+ let id = store.loadNodeId();
13255
+ if (!id) {
13256
+ id = newId();
13257
+ store.saveNodeId(id);
13258
+ }
13259
+ return id;
13260
+ }
13261
+ function createProject(store, input) {
13262
+ const raw = input ?? {};
13263
+ const id = String(raw.id ?? "").trim() || newId();
13264
+ if (store.loadTenant(id)) {
13265
+ throw new ApiError(409, "project_exists", `\u9879\u76EE ${id} \u5DF2\u5B58\u5728`);
13266
+ }
13267
+ const workspaceRoot = typeof raw.workspaceRoot === "string" && raw.workspaceRoot.trim() ? path24.resolve(raw.workspaceRoot.trim()) : void 0;
13268
+ const explicitName = typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : "";
13269
+ const defaultName = workspaceRoot ? path24.basename(workspaceRoot) : id;
13270
+ const tenant = {
13271
+ id,
13272
+ name: explicitName || defaultName,
13273
+ apiKey: generateApiKey(),
13274
+ model: typeof raw.model === "string" && raw.model.trim() ? raw.model.trim() : DEFAULT_MODEL(),
13275
+ provider: {
13276
+ apiKey: typeof raw.providerApiKey === "string" && raw.providerApiKey.trim() ? raw.providerApiKey.trim() : void 0,
13277
+ baseURL: typeof raw.baseURL === "string" && raw.baseURL.trim() ? raw.baseURL.trim() : void 0
13278
+ },
13279
+ workspaceRoot,
13280
+ allowedCommands: Array.isArray(raw.allowedCommands) ? raw.allowedCommands.map((c) => String(c)) : []
13281
+ };
13282
+ const def = nodeDefaultProvider(store);
13283
+ if (def) {
13284
+ if (!tenant.provider?.apiKey && def.apiKey) tenant.provider = { ...tenant.provider, apiKey: def.apiKey };
13285
+ if (!tenant.provider?.baseURL && def.baseURL) tenant.provider = { ...tenant.provider, baseURL: def.baseURL };
13286
+ if (!(typeof raw.model === "string" && raw.model.trim()) && def.model) tenant.model = def.model;
13287
+ const nodeCfg = store.loadNodeLlm();
13288
+ tenant.llmProviders = nodeCfg.providers.map((p) => ({ ...p }));
13289
+ tenant.activeProviderId = nodeCfg.defaultProviderId ?? nodeCfg.providers[0]?.id;
13290
+ }
13291
+ store.saveTenant(tenant);
13292
+ return tenant;
13293
+ }
13294
+ function buildNodeInfo(store, opts) {
13295
+ return {
13296
+ nodeId: opts.nodeId ?? ensureNodeId(store),
13297
+ platform: process.platform,
13298
+ mode: process.env.NODE_MODE ?? "standalone",
13299
+ version: "0.2.12",
13300
+ admin: opts.admin,
13301
+ projects: opts.projects
13302
+ };
13303
+ }
13304
+
13305
+ // src/server/fleet-config.ts
13306
+ init_esm_shims();
13307
+ function fleetConfigFromEnv(store) {
13308
+ const fleetToken = process.env.FLEET_TOKEN;
13309
+ const fleetSecret = process.env.FLEET_SECRET;
13310
+ if (!fleetToken || !fleetSecret) return null;
13311
+ return {
13312
+ hubUrl: process.env.HUB_URL,
13313
+ fleetToken,
13314
+ fleetSecret,
13315
+ nodeId: ensureNodeId(store),
13316
+ gatewayUrl: process.env.FLEET_GATEWAY_URL
13317
+ };
13318
+ }
13319
+ function resolveFleetConfig(store) {
13320
+ const saved = store.loadFleetCredential();
13321
+ if (saved?.fleetToken && saved.fleetSecret) {
13322
+ return { ...saved, nodeId: saved.nodeId || ensureNodeId(store) };
13323
+ }
13324
+ return fleetConfigFromEnv(store);
13325
+ }
13326
+
13327
+ // src/cli/account-enroll.ts
13328
+ var SKIP_ANSWERS = /* @__PURE__ */ new Set(["-", "skip", "none", "no"]);
13329
+ function resolveDefaultHub(hubUrl, env3 = process.env) {
13330
+ return (hubUrl ?? env3.HUB_URL ?? DEFAULT_HUB_URL).trim().replace(/\/$/, "");
13331
+ }
13332
+ function parseHubAnswer(answer, fallback) {
13333
+ const a = answer.trim();
13334
+ if (SKIP_ANSWERS.has(a.toLowerCase())) return null;
13335
+ return (a || fallback).replace(/\/$/, "");
13336
+ }
13337
+ async function promptHubUrl(rl2, hubUrl) {
13338
+ const fallback = resolveDefaultHub(hubUrl);
13339
+ const answer = await rl2.question(`Hub \u5730\u5740 [${fallback}]\uFF1A`);
13340
+ return parseHubAnswer(answer, fallback);
13341
+ }
13342
+ async function promptAccountLogin(rl2, hubUrl) {
13343
+ 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");
13344
+ const mode = (await rl2.question("\u8BF7\u9009\u62E9 [1]\uFF1A")).trim() || "1";
13345
+ const input = {};
13346
+ if (mode === "1") {
13347
+ input.email = (await rl2.question("\u90AE\u7BB1\uFF1A")).trim();
13348
+ input.password = await askHidden(rl2, "\u5BC6\u7801\uFF1A");
13349
+ if (!input.email || !input.password) {
13350
+ cliErr("\u90AE\u7BB1\u4E0E\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
13351
+ return null;
13352
+ }
13353
+ } else if (mode === "2") {
13354
+ input.phone = (await rl2.question("\u624B\u673A\u53F7\uFF1A")).trim();
13355
+ input.password = await askHidden(rl2, "\u5BC6\u7801\uFF1A");
13356
+ if (!input.phone || !input.password) {
13357
+ cliErr("\u624B\u673A\u53F7\u4E0E\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
13358
+ return null;
13359
+ }
13360
+ } else if (mode === "3") {
13361
+ input.phone = (await rl2.question("\u624B\u673A\u53F7\uFF1A")).trim();
13362
+ if (!input.phone) {
13363
+ cliErr("\u624B\u673A\u53F7\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
13364
+ return null;
13365
+ }
13366
+ try {
13367
+ const sent = await hubSendSms(resolveDefaultHub(hubUrl), {
13368
+ phone: input.phone,
13369
+ purpose: "login"
13370
+ });
13371
+ if (sent.debugCode) cliLog(`\uFF08\u8C03\u8BD5\u6A21\u5F0F\u9A8C\u8BC1\u7801\uFF1A${sent.debugCode}\uFF09`);
13372
+ else cliLog("\u9A8C\u8BC1\u7801\u5DF2\u53D1\u9001\uFF0C5 \u5206\u949F\u5185\u6709\u6548\u3002");
13373
+ } catch (e) {
13374
+ cliErr(`\u77ED\u4FE1\u9A8C\u8BC1\u7801\u53D1\u9001\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
13375
+ 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");
13376
+ cliLog(" zqagent link --hub <Hub\u5730\u5740> --code <\u63A5\u5165\u7801>");
13377
+ return null;
13378
+ }
13379
+ input.code = (await rl2.question("\u77ED\u4FE1\u9A8C\u8BC1\u7801\uFF1A")).trim();
13380
+ if (!input.code) {
13381
+ cliErr("\u9A8C\u8BC1\u7801\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
13382
+ return null;
13383
+ }
13384
+ } else {
13385
+ cliErr("\u672A\u77E5\u7684\u767B\u5F55\u65B9\u5F0F\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
13386
+ return null;
13387
+ }
13388
+ return input;
13389
+ }
13390
+ async function promptLinkAccount(rl2, hubUrl, opts = {}) {
13391
+ const fallback = resolveDefaultHub(hubUrl);
13392
+ let resolved = fallback;
13393
+ if (opts.askHub !== false) {
13394
+ const answer = await promptHubUrl(rl2, fallback);
13395
+ if (!answer) {
13396
+ cliLog("\u5DF2\u8DF3\u8FC7\u63A5\u5165\uFF1A\u8282\u70B9\u4FDD\u6301\u7EAF\u672C\u5730\u8FD0\u884C\u3002");
13397
+ return null;
13398
+ }
13399
+ resolved = answer;
13400
+ }
13401
+ const auth = await promptAccountLogin(rl2, resolved);
13402
+ if (!auth) return null;
13403
+ return { hubUrl: resolved, auth };
13404
+ }
13405
+ async function promptPassword(rl2, prompt = "\u5BC6\u7801\uFF1A") {
13406
+ return askHidden(rl2, prompt);
13407
+ }
13408
+ function resolveDataDir(dataDir) {
13409
+ return path25.resolve(dataDir ?? process.env.DATA_DIR ?? DEFAULT_DATA_DIR);
13410
+ }
13411
+ async function maybeAccountEnroll(opts = {}) {
13412
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return;
13413
+ let store;
13414
+ try {
13415
+ store = new Store(resolveDataDir(opts.dataDir));
13416
+ if (resolveFleetConfig(store)) return;
13417
+ } catch (e) {
13418
+ cliErr(`\u63A5\u5165\u68C0\u67E5\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
13419
+ return;
13420
+ }
13421
+ const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
13422
+ try {
13423
+ await runWizard(rl2, store, opts);
13424
+ } catch (e) {
13425
+ cliErr(`\u63A5\u5165\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
13426
+ 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>");
13427
+ } finally {
13428
+ rl2.close();
13429
+ }
13430
+ }
13431
+ async function runWizard(rl2, store, opts) {
13432
+ cliLog("");
13433
+ 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");
13434
+ const prompted = await promptLinkAccount(rl2, opts.hubUrl);
13435
+ if (!prompted) return;
13436
+ const nodeId = ensureNodeId(store);
13437
+ const cred = await hubEnrollByAccount(prompted.hubUrl, {
13438
+ ...prompted.auth,
13439
+ nodeId,
13440
+ label: opts.label
13441
+ });
13442
+ store.saveFleetCredential({
13443
+ ...cred,
13444
+ label: opts.label ?? cred.label,
13445
+ enrolledAt: (/* @__PURE__ */ new Date()).toISOString()
13446
+ });
13447
+ cliLog(`\u2705 \u5DF2\u63A5\u5165 Hub\uFF1A\u8282\u70B9 ${cred.nodeId}\uFF08\u7F51\u5173 ${cred.gatewayUrl ?? "\u672A\u4E0B\u53D1"}\uFF09`);
13448
+ cliLog(" \u51ED\u636E\u5DF2\u4FDD\u5B58\u5230 fleet.json\uFF0C\u672C\u6B21\u542F\u52A8\u5373\u8FDE\u63A5 Hub\u3002");
13449
+ }
13450
+ async function askHidden(rl2, prompt) {
13451
+ if (!process.stdin.isTTY) return (await rl2.question(prompt)).trim();
13452
+ return new Promise((resolve24) => {
13453
+ const saved = process.stdin.listeners("data");
13454
+ process.stdin.removeAllListeners("data");
13455
+ cliWrite(prompt);
13456
+ rl2.pause();
13457
+ process.stdin.setRawMode(true);
13458
+ process.stdin.resume();
13459
+ let buf = "";
13460
+ const cleanup = () => {
13461
+ process.stdin.removeListener("data", onData);
13462
+ for (const listener of saved) {
13463
+ process.stdin.on("data", listener);
13464
+ }
13465
+ process.stdin.setRawMode(true);
13466
+ rl2.resume();
13467
+ };
13468
+ const onData = (chunk) => {
13469
+ const s = chunk.toString().replace(/\x1b\[[0-9;?]*[A-Za-z~]/g, "").replace(/\x1bO[A-Za-z]/g, "");
13470
+ for (const ch of s) {
13471
+ if (ch === "\r" || ch === "\n") {
13472
+ cleanup();
13473
+ cliWrite("\n");
13474
+ resolve24(buf);
13475
+ return;
13476
+ }
13477
+ if (ch === "") {
13478
+ cleanup();
13479
+ cliWrite("\n");
13480
+ resolve24("");
13481
+ return;
13482
+ }
13483
+ if (ch === "\x7F" || ch === "\b") {
13484
+ if (buf.length > 0) {
13485
+ buf = buf.slice(0, -1);
13486
+ cliWrite("\b \b");
13487
+ }
13488
+ } else if (ch >= " ") {
13489
+ buf += ch;
13490
+ cliWrite("*");
13491
+ }
13492
+ }
13493
+ };
13494
+ process.stdin.on("data", onData);
13495
+ });
13496
+ }
13497
+
13498
+ // src/cli/serve-instances.ts
13499
+ init_esm_shims();
13500
+ import { spawnSync as spawnSync3 } from "child_process";
13501
+ import * as fs21 from "fs";
13502
+ import * as os5 from "os";
13503
+ import * as path27 from "path";
13504
+
13505
+ // src/update/platform.ts
13506
+ init_esm_shims();
13507
+ import * as fs20 from "fs";
13508
+ import * as os4 from "os";
13509
+ import * as path26 from "path";
13510
+ import { spawnSync as spawnSync2 } from "child_process";
13511
+ import { createRequire as createRequire3 } from "module";
13512
+ function currentVersion() {
13513
+ const injected = "0.2.12"?.trim();
13514
+ if (injected) return injected;
13515
+ try {
13516
+ const req2 = createRequire3(import.meta.url);
13517
+ const pkg = req2("../../package.json");
13518
+ if (pkg?.version) return pkg.version;
13519
+ } catch {
13520
+ }
13521
+ return "dev";
13522
+ }
13523
+ function isSeaRuntime() {
13524
+ if (process.env.ZQAGENT_SEA === "1") return true;
13525
+ const base = path26.basename(process.execPath).toLowerCase().replace(/\.exe$/, "");
13526
+ return base !== "node" && base !== "nodejs";
13527
+ }
13528
+ var DEFAULT_SERVICE_NAME = "zqagent";
13529
+ var DEFAULT_SERVICE_LABEL = "com.zqagent.node";
13530
+ function serviceManagerFromCgroup(cgroup, serviceName = DEFAULT_SERVICE_NAME) {
13531
+ const unitName = `${serviceName}.service`;
13532
+ for (const line of cgroup.split("\n")) {
13533
+ const idx = line.lastIndexOf(":");
13534
+ if (idx < 0) continue;
13535
+ const unitPath = line.slice(idx + 1).trim();
13536
+ if (unitPath.split("/").includes(unitName)) return "systemd";
13537
+ }
13538
+ return null;
13539
+ }
13540
+ function launchdDomain() {
13541
+ const uid = typeof process.getuid === "function" ? process.getuid() : -1;
13542
+ return uid === 0 ? "system" : `gui/${uid}`;
13543
+ }
13544
+ function launchdLabel() {
13545
+ return process.env.ZQAGENT_SERVICE_LABEL?.trim() || DEFAULT_SERVICE_LABEL;
13546
+ }
13547
+ function launchdPlistPath() {
13548
+ const label = launchdLabel();
13549
+ const uid = typeof process.getuid === "function" ? process.getuid() : -1;
13550
+ if (uid === 0) return `/Library/LaunchDaemons/${label}.plist`;
13551
+ return path26.join(os4.homedir(), "Library", "LaunchAgents", `${label}.plist`);
13552
+ }
13553
+ function serviceManagerFromLaunchctl(output, pid, execPath, sea = true) {
13554
+ const pidMatch = /^\s*pid = (\d+)\s*$/m.exec(output);
13555
+ if (pidMatch && Number(pidMatch[1]) === pid) return "launchd";
13556
+ const program = /^\s*program = (.+?)\s*$/m.exec(output);
13557
+ if (program) {
13558
+ const shown = program[1].replace(/^"|"$/g, "");
13559
+ if (path26.resolve(shown) === path26.resolve(execPath)) return "launchd";
13560
+ }
13561
+ if (!sea && /^\s*state = /m.test(output)) return "launchd";
13562
+ return null;
13563
+ }
13564
+ function parseLaunchdEnvironment(xml) {
13565
+ const out = {};
13566
+ const marker = xml.indexOf("<key>EnvironmentVariables</key>");
13567
+ if (marker < 0) return out;
13568
+ const open = xml.indexOf("<dict>", marker);
13569
+ if (open < 0) return out;
13570
+ const close = xml.indexOf("</dict>", open);
13571
+ if (close < 0) return out;
13572
+ const body = xml.slice(open + "<dict>".length, close);
13573
+ for (const m of body.matchAll(/<key>([^<]+)<\/key>\s*<string>([^<]*)<\/string>/g)) {
13574
+ out[m[1]] = m[2];
13575
+ }
13576
+ return out;
13577
+ }
13578
+ function detectServiceManager() {
13579
+ if (process.platform === "darwin") return detectLaunchd();
13580
+ if (process.platform !== "linux") return null;
13581
+ const serviceName = process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;
13582
+ try {
13583
+ return serviceManagerFromCgroup(
13584
+ fs20.readFileSync("/proc/self/cgroup", "utf8"),
13585
+ serviceName
13586
+ );
13587
+ } catch {
13588
+ return null;
13589
+ }
13590
+ }
13591
+ function detectLaunchd() {
13592
+ try {
13593
+ const res = spawnSync2(
13594
+ "launchctl",
13595
+ ["print", `${launchdDomain()}/${launchdLabel()}`],
13596
+ { encoding: "utf8", timeout: 3e3 }
13597
+ );
13598
+ if (res.status !== 0 || !res.stdout) return null;
13599
+ return serviceManagerFromLaunchctl(
13600
+ res.stdout,
13601
+ process.pid,
13602
+ process.execPath,
13603
+ isSeaRuntime()
13604
+ );
13605
+ } catch {
13606
+ return null;
13607
+ }
13608
+ }
13609
+ function serviceManager() {
13610
+ const v = process.env.ZQAGENT_SERVICE_MANAGER?.trim().toLowerCase();
13611
+ if (v === "systemd" || v === "launchd") return v;
13612
+ return detectServiceManager();
13613
+ }
13614
+ function parseServiceEnvironment(raw) {
13615
+ const out = {};
13616
+ for (const token of raw.trim().split(/\s+/)) {
13617
+ const idx = token.indexOf("=");
13618
+ if (idx > 0) out[token.slice(0, idx)] = token.slice(idx + 1);
13619
+ }
13620
+ return out;
13621
+ }
13622
+ var cachedServiceEnv = null;
13623
+ function readServiceEnvironment() {
13624
+ const serviceName = process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;
13625
+ try {
13626
+ const res = spawnSync2(
13627
+ "systemctl",
13628
+ ["show", serviceName, "--property=Environment", "--value", "--no-pager"],
13629
+ { encoding: "utf8", timeout: 3e3 }
13630
+ );
13631
+ if (res.status !== 0 || !res.stdout) return {};
13632
+ return parseServiceEnvironment(res.stdout);
13633
+ } catch {
13634
+ return {};
13635
+ }
13636
+ }
13637
+ function serviceEnvValue(key) {
13638
+ if (process.platform === "darwin") {
13639
+ if (serviceManager() !== "launchd") return void 0;
13640
+ cachedLaunchdEnv ??= readLaunchdEnvironment();
13641
+ return cachedLaunchdEnv[key];
13642
+ }
13643
+ if (process.platform !== "linux") return void 0;
13644
+ if (serviceManager() !== "systemd") return void 0;
13645
+ cachedServiceEnv ??= readServiceEnvironment();
13646
+ return cachedServiceEnv[key];
13647
+ }
13648
+ var cachedLaunchdEnv = null;
13649
+ function readLaunchdEnvironment() {
13650
+ try {
13651
+ return parseLaunchdEnvironment(fs20.readFileSync(launchdPlistPath(), "utf8"));
13652
+ } catch {
13653
+ return {};
13654
+ }
13655
+ }
13656
+ function selfUpdateCapability(hasPublicKey) {
13657
+ if (!isSeaRuntime()) {
13658
+ return {
13659
+ ok: false,
13660
+ 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"
13661
+ };
13662
+ }
13663
+ if (!serviceManager()) {
13664
+ return {
13665
+ ok: false,
13666
+ reason: "\u672A\u88AB systemd/launchd \u6258\u7BA1\uFF1A\u8BF7\u7528 deploy/service/install.sh \u5B89\u88C5\uFF0C\u6216\u624B\u52A8\u5347\u7EA7"
13667
+ };
13668
+ }
13669
+ if (!hasPublicKey) {
13670
+ return { ok: false, reason: "\u672A\u914D\u7F6E ZQAGENT_UPDATE_PUBKEY\uFF0C\u65E0\u6CD5\u9A8C\u7B7E\uFF0C\u5DF2\u7981\u7528\u81EA\u52A8\u66F4\u65B0" };
13671
+ }
13672
+ return { ok: true };
13673
+ }
13674
+
13675
+ // src/cli/serve-instances.ts
13676
+ var DEFAULT_SERVE_HOST = "127.0.0.1";
13677
+ var DEFAULT_SERVE_PORT = 8787;
13678
+ var DEFAULT_SERVICE_NAME2 = "zqagent";
13679
+ var DEFAULT_SERVICE_LABEL2 = "com.zqagent.node";
13680
+ function serveSubcommandIndex(argv) {
13681
+ for (let i = 1; i < argv.length; i++) {
13682
+ if (argv[i].startsWith("-")) continue;
13683
+ return argv[i] === "serve" ? i : -1;
13684
+ }
13685
+ return -1;
13686
+ }
13687
+ function parseServeArgs(argv) {
13688
+ const at = serveSubcommandIndex(argv);
13689
+ if (at < 0) return null;
13690
+ let dataDir = null;
13691
+ let host = DEFAULT_SERVE_HOST;
13692
+ let port = DEFAULT_SERVE_PORT;
13693
+ for (let i = at + 1; i < argv.length; i++) {
13694
+ const arg = argv[i];
13695
+ if (arg === "--data-dir" && argv[i + 1]) {
13696
+ dataDir = argv[++i];
13697
+ } else if (arg.startsWith("--data-dir=")) {
13698
+ dataDir = arg.slice("--data-dir=".length);
13699
+ } else if (arg === "--host" && argv[i + 1]) {
13700
+ host = argv[++i];
13701
+ } else if (arg.startsWith("--host=")) {
13702
+ host = arg.slice("--host=".length);
13703
+ } else if (arg === "--port" && argv[i + 1]) {
13704
+ port = Number(argv[++i]);
13705
+ } else if (arg.startsWith("--port=")) {
13706
+ port = Number(arg.slice("--port=".length));
13707
+ }
13708
+ }
13709
+ if (!Number.isFinite(port) || port <= 0) port = DEFAULT_SERVE_PORT;
13710
+ if (host === "0.0.0.0" || host === "::" || host === "") host = DEFAULT_SERVE_HOST;
13711
+ return { dataDir, host, port };
13712
+ }
13713
+ function buildHotCandidates(explicitDataDir, discovered, fallback) {
13714
+ const out = [];
13715
+ const push = (inst) => {
13716
+ if (!out.some((o) => o.dataDir === inst.dataDir)) out.push(inst);
13717
+ };
13718
+ if (explicitDataDir) push({ dataDir: path27.resolve(explicitDataDir), ...fallback });
13719
+ for (const inst of discovered) if (inst.managed) push(inst);
13720
+ for (const inst of discovered) if (!inst.managed) push(inst);
13721
+ if (!explicitDataDir) push({ dataDir: path27.resolve(DEFAULT_DATA_DIR), ...fallback });
13722
+ return out;
13723
+ }
13724
+ function isWindowsAbsolute(p) {
13725
+ return /^[A-Za-z]:[\\/]/.test(p) || /^\\\\/.test(p);
13726
+ }
13727
+ function isAbsolutePath(p) {
13728
+ return path27.isAbsolute(p) || isWindowsAbsolute(p);
13729
+ }
13730
+ function normalizePath(p) {
13731
+ return isWindowsAbsolute(p) ? path27.win32.normalize(p) : path27.normalize(p);
13732
+ }
13733
+ function parseServeCommandLine(line) {
13734
+ if (!/(?:^|\s)["']?serve["']?(?=\s|$)/.test(line)) return null;
13735
+ const m = /--data-dir(?:=|\s+)(?:"([^"]+)"|'([^']+)'|(.+?))(?=\s+--\w|\s*$)/.exec(line);
13736
+ const rawDataDir = (m?.[1] ?? m?.[2] ?? m?.[3] ?? "").trim();
13737
+ if (!rawDataDir || !isAbsolutePath(rawDataDir)) return null;
13738
+ const host = /--host(?:=|\s+)(?:"([^"]+)"|'([^']+)'|(\S+))/.exec(line);
13739
+ const rawHost = (host?.[1] ?? host?.[2] ?? host?.[3] ?? DEFAULT_SERVE_HOST).trim();
13740
+ const portMatch = /--port(?:=|\s+)(\d+)/.exec(line);
13741
+ const port = Number(portMatch?.[1] ?? DEFAULT_SERVE_PORT);
13742
+ return {
13743
+ dataDir: normalizePath(rawDataDir),
13744
+ host: rawHost === "0.0.0.0" || rawHost === "::" || rawHost === "" ? DEFAULT_SERVE_HOST : rawHost,
13745
+ port: Number.isFinite(port) && port > 0 ? port : DEFAULT_SERVE_PORT
13746
+ };
13747
+ }
13748
+ function processCwd(pid) {
13749
+ try {
13750
+ return fs21.readlinkSync(`/proc/${pid}/cwd`);
13751
+ } catch {
13752
+ return null;
13753
+ }
13754
+ }
13755
+ function processEnv(pid) {
13756
+ const out = {};
13757
+ try {
13758
+ const raw = fs21.readFileSync(`/proc/${pid}/environ`, "utf-8");
13759
+ for (const entry of raw.split("\0")) {
13760
+ const eq = entry.indexOf("=");
13761
+ if (eq > 0) out[entry.slice(0, eq)] = entry.slice(eq + 1);
13762
+ }
13763
+ } catch {
13764
+ }
13765
+ return out;
13766
+ }
13767
+ function resolveDataDir2(rawDataDir, cwd, env3) {
13768
+ const pick = rawDataDir ?? env3.DATA_DIR ?? null;
13769
+ if (pick) return path27.isAbsolute(pick) ? path27.normalize(pick) : cwd ? path27.resolve(cwd, pick) : null;
13770
+ return cwd ? path27.resolve(cwd, DEFAULT_DATA_DIR) : null;
13771
+ }
13772
+ function isManagedBySystemd(pid, env3) {
13773
+ if (env3.ZQAGENT_SERVICE_MANAGER?.trim()) return true;
13774
+ try {
13775
+ const cgroup = fs21.readFileSync(`/proc/${pid}/cgroup`, "utf-8");
13776
+ const serviceName = env3.ZQAGENT_SERVICE_NAME?.trim() || "zqagent";
13777
+ return serviceManagerFromCgroup(cgroup, serviceName) !== null;
13778
+ } catch {
13779
+ return false;
13780
+ }
13781
+ }
13782
+ function discoverFromProc() {
13783
+ const out = [];
13784
+ let pids;
13785
+ try {
13786
+ pids = fs21.readdirSync("/proc");
13787
+ } catch {
13788
+ return out;
13789
+ }
13790
+ for (const name of pids) {
13791
+ if (!/^\d+$/.test(name)) continue;
13792
+ const pid = Number(name);
13793
+ if (pid === process.pid) continue;
13794
+ let argv;
13795
+ let env3;
13796
+ try {
13797
+ argv = fs21.readFileSync(`/proc/${pid}/cmdline`, "utf-8").split("\0").filter(Boolean);
13798
+ env3 = processEnv(pid);
13799
+ } catch {
13800
+ continue;
13801
+ }
13802
+ const parsed = parseServeArgs(argv);
13803
+ if (!parsed) continue;
13804
+ const dataDir = resolveDataDir2(parsed.dataDir, processCwd(pid), env3);
13805
+ if (!dataDir) continue;
13806
+ out.push({
13807
+ dataDir,
13808
+ host: parsed.host,
13809
+ port: parsed.port,
13810
+ managed: isManagedBySystemd(pid, env3)
13811
+ });
13822
13812
  }
13823
- };
13824
- var ApiError = class extends Error {
13825
- constructor(status, code, message) {
13826
- super(message);
13827
- this.status = status;
13828
- this.code = code;
13813
+ return out;
13814
+ }
13815
+ function discoverFromPs() {
13816
+ const out = [];
13817
+ const res = spawnSync3("ps", ["-axo", "command="], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
13818
+ if (res.status !== 0 || !res.stdout) return out;
13819
+ for (const line of res.stdout.split("\n")) {
13820
+ const inst = parseServeCommandLine(line);
13821
+ if (inst) out.push(inst);
13829
13822
  }
13830
- status;
13831
- code;
13832
- };
13833
-
13834
- // src/server/routes.ts
13835
- init_esm_shims();
13836
- import { createServer } from "http";
13837
- import * as path31 from "path";
13838
-
13839
- // src/server/projects.ts
13840
- init_esm_shims();
13841
- import * as path27 from "path";
13842
- var DEFAULT_MODEL = () => process.env.OPENAI_MODEL || process.env.MODEL || "gpt-4o";
13843
- function toProjectSummary(store, tenant) {
13844
- return {
13845
- id: tenant.id,
13846
- name: tenant.name ?? tenant.id,
13847
- model: tenant.model,
13848
- baseURL: tenant.provider?.baseURL ?? "",
13849
- hasApiKey: Boolean(tenant.provider?.apiKey),
13850
- workspaceRoot: tenant.workspaceRoot ?? null,
13851
- sessionCount: store.listSessions(tenant.id).length
13852
- };
13823
+ return out;
13853
13824
  }
13854
- function providerViews(tenant) {
13855
- return {
13856
- activeProviderId: tenant.activeProviderId ?? null,
13857
- providers: (tenant.llmProviders ?? []).map((p) => ({
13858
- id: p.id,
13859
- name: p.name,
13860
- type: p.type ?? "",
13861
- baseURL: p.baseURL,
13862
- model: p.model,
13863
- models: p.models ?? [],
13864
- modelMeta: p.modelMeta ?? [],
13865
- hasApiKey: Boolean(p.apiKey)
13866
- }))
13867
- };
13825
+ function discoverFromWindows() {
13826
+ const out = [];
13827
+ const script = `[Console]::OutputEncoding=[Text.Encoding]::UTF8; Get-CimInstance Win32_Process -Filter "Name='zqagent.exe' OR Name='node.exe'" | ForEach-Object { $_.CommandLine }`;
13828
+ const res = spawnSync3(
13829
+ "powershell.exe",
13830
+ ["-NoProfile", "-NonInteractive", "-Command", script],
13831
+ { encoding: "utf-8", timeout: 15e3, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] }
13832
+ );
13833
+ if (res.status !== 0 || !res.stdout) return out;
13834
+ for (const line of res.stdout.replace(/^\uFEFF/, "").split(/\r?\n/)) {
13835
+ const inst = parseServeCommandLine(line);
13836
+ if (inst) out.push(inst);
13837
+ }
13838
+ return out;
13868
13839
  }
13869
- function applyProvidersPatch(tenant, raw) {
13870
- if (Array.isArray(raw.providers)) {
13871
- const existing = new Map((tenant.llmProviders ?? []).map((p) => [p.id, p]));
13872
- const next = [];
13873
- for (const item of raw.providers) {
13874
- if (!item || typeof item !== "object") continue;
13875
- const o = item;
13876
- const id = String(o.id ?? "").trim() || newId();
13877
- const prev = existing.get(id);
13878
- const apiKey = typeof o.apiKey === "string" && o.apiKey.trim() ? o.apiKey.trim() : prev?.apiKey;
13879
- const models = normalizeModelList(o.models) ?? prev?.models;
13880
- const modelMeta = normalizeModelMeta(o.modelMeta) ?? prev?.modelMeta;
13881
- next.push({
13882
- id,
13883
- name: String(o.name ?? "").trim() || id,
13884
- type: typeof o.type === "string" ? o.type : prev?.type,
13885
- baseURL: String(o.baseURL ?? "").trim(),
13886
- model: String(o.model ?? "").trim(),
13887
- ...apiKey ? { apiKey } : {},
13888
- ...models ? { models } : {},
13889
- ...modelMeta ? { modelMeta } : {}
13890
- });
13840
+ function decodeXmlEntities(s) {
13841
+ return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
13842
+ }
13843
+ function parseLaunchdProgramArguments(xml) {
13844
+ const marker = xml.indexOf("<key>ProgramArguments</key>");
13845
+ if (marker < 0) return [];
13846
+ const open = xml.indexOf("<array>", marker);
13847
+ if (open < 0) return [];
13848
+ const close = xml.indexOf("</array>", open);
13849
+ if (close < 0) return [];
13850
+ return [...xml.slice(open + "<array>".length, close).matchAll(/<string>([^<]*)<\/string>/g)].map(
13851
+ (m) => decodeXmlEntities(m[1])
13852
+ );
13853
+ }
13854
+ function launchdServiceInstances() {
13855
+ if (process.platform !== "darwin") return [];
13856
+ const label = process.env.ZQAGENT_SERVICE_LABEL?.trim() || DEFAULT_SERVICE_LABEL2;
13857
+ const plists = [
13858
+ `/Library/LaunchDaemons/${label}.plist`,
13859
+ path27.join(os5.homedir(), "Library", "LaunchAgents", `${label}.plist`)
13860
+ ];
13861
+ const out = [];
13862
+ for (const plist of plists) {
13863
+ let xml;
13864
+ try {
13865
+ xml = fs21.readFileSync(plist, "utf-8");
13866
+ } catch {
13867
+ continue;
13891
13868
  }
13892
- tenant.llmProviders = next;
13869
+ const parsed = parseServeArgs(parseLaunchdProgramArguments(xml));
13870
+ if (!parsed?.dataDir || !isAbsolutePath(parsed.dataDir)) continue;
13871
+ out.push({
13872
+ dataDir: normalizePath(parsed.dataDir),
13873
+ host: parsed.host,
13874
+ port: parsed.port,
13875
+ managed: true
13876
+ });
13893
13877
  }
13894
- if (typeof raw.activeProviderId === "string") {
13895
- const activeId = raw.activeProviderId.trim();
13896
- tenant.activeProviderId = activeId || void 0;
13897
- const entry = (tenant.llmProviders ?? []).find((p) => p.id === activeId);
13898
- if (entry) {
13899
- tenant.provider = tenant.provider ?? {};
13900
- tenant.provider.baseURL = entry.baseURL || void 0;
13901
- if (entry.apiKey) tenant.provider.apiKey = entry.apiKey;
13902
- if (entry.model) tenant.model = entry.model;
13878
+ return out;
13879
+ }
13880
+ function parseSystemdUnit(text) {
13881
+ let dataDir = null;
13882
+ let host = DEFAULT_SERVE_HOST;
13883
+ let port = DEFAULT_SERVE_PORT;
13884
+ for (const line of text.split("\n")) {
13885
+ const trimmed = line.trim();
13886
+ if (trimmed.startsWith("WorkingDirectory=")) {
13887
+ const v = trimmed.slice("WorkingDirectory=".length).trim();
13888
+ if (v && isAbsolutePath(v)) dataDir = v;
13889
+ } else if (trimmed.startsWith("ExecStart=")) {
13890
+ const tokens = trimmed.slice("ExecStart=".length).trim().split(/\s+/).map((t) => t.replace(/^["']|["']$/g, ""));
13891
+ const parsed = parseServeArgs(tokens);
13892
+ if (!parsed) continue;
13893
+ if (parsed.dataDir && isAbsolutePath(parsed.dataDir)) dataDir = parsed.dataDir;
13894
+ host = parsed.host;
13895
+ port = parsed.port;
13903
13896
  }
13904
13897
  }
13898
+ if (!dataDir) return null;
13899
+ return { dataDir: normalizePath(dataDir), host, port, managed: true };
13905
13900
  }
13906
- function allProjectSummaries(store) {
13907
- return store.listTenants().map((id) => store.loadTenant(id)).filter((t) => t !== null).map((t) => toProjectSummary(store, t));
13901
+ function systemdServiceInstances() {
13902
+ if (process.platform !== "linux") return [];
13903
+ const unit = process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME2;
13904
+ const configHome = process.env.XDG_CONFIG_HOME?.trim() || path27.join(os5.homedir(), ".config");
13905
+ const units = [
13906
+ `/etc/systemd/system/${unit}.service`,
13907
+ path27.join(configHome, "systemd", "user", `${unit}.service`)
13908
+ ];
13909
+ const out = [];
13910
+ for (const file of units) {
13911
+ let text;
13912
+ try {
13913
+ text = fs21.readFileSync(file, "utf-8");
13914
+ } catch {
13915
+ continue;
13916
+ }
13917
+ const inst = parseSystemdUnit(text);
13918
+ if (inst) out.push(inst);
13919
+ }
13920
+ return out;
13921
+ }
13922
+ function windowsServiceInstances() {
13923
+ if (process.platform !== "win32") return [];
13924
+ const appdata = process.env.APPDATA?.trim();
13925
+ if (!appdata) return [];
13926
+ const base = path27.join(appdata, "zqagent");
13927
+ try {
13928
+ const raw = fs21.readFileSync(path27.join(base, "runtime", "service.json"), "utf-8");
13929
+ const v = JSON.parse(raw);
13930
+ const dataDir = v.dataDir && isAbsolutePath(v.dataDir) ? v.dataDir : base;
13931
+ const port = Number(v.port);
13932
+ return [
13933
+ {
13934
+ dataDir: normalizePath(dataDir),
13935
+ host: DEFAULT_SERVE_HOST,
13936
+ port: Number.isFinite(port) && port > 0 ? port : DEFAULT_SERVE_PORT,
13937
+ managed: true
13938
+ }
13939
+ ];
13940
+ } catch {
13941
+ return [];
13942
+ }
13943
+ }
13944
+ function mergeServeInstances(discovered, managed) {
13945
+ const out = [];
13946
+ const push = (inst) => {
13947
+ if (!out.some((o) => o.dataDir === inst.dataDir)) out.push(inst);
13948
+ };
13949
+ for (const inst of discovered) {
13950
+ const hit = managed.find((m) => m.dataDir === inst.dataDir);
13951
+ push(hit ? { ...inst, managed: true } : inst);
13952
+ }
13953
+ for (const inst of managed) push(inst);
13954
+ return out;
13955
+ }
13956
+ function discoverRunningServeInstances() {
13957
+ const found = process.platform === "linux" ? discoverFromProc() : process.platform === "win32" ? discoverFromWindows() : discoverFromPs();
13958
+ const declared = process.platform === "linux" ? systemdServiceInstances() : process.platform === "win32" ? windowsServiceInstances() : launchdServiceInstances();
13959
+ return mergeServeInstances(found, declared);
13960
+ }
13961
+
13962
+ // src/cli/link.ts
13963
+ function expandHome(p) {
13964
+ const home = process.env.HOME || process.env.USERPROFILE;
13965
+ if (!home) return p;
13966
+ if (p === "~") return home;
13967
+ if (p.startsWith("~/") || p.startsWith("~\\")) return path28.join(home, p.slice(2));
13968
+ return p;
13969
+ }
13970
+ function installDataDirHint() {
13971
+ if (process.platform === "darwin") return "$HOME/Library/Application Support/zqagent";
13972
+ if (process.platform === "win32") return "%APPDATA%\\zqagent";
13973
+ return "/var/lib/zqagent\uFF08\u9700 root\uFF1B\u666E\u901A\u7528\u6237\u7528 ~/.zqagent_data\uFF09";
13908
13974
  }
13909
- function lastUserMessage(rec) {
13910
- const sent = (rec.lastInput ?? "").trim();
13911
- if (sent) {
13912
- return sent.length > LAST_USER_MESSAGE_MAX ? `${sent.slice(0, LAST_USER_MESSAGE_MAX)}\u2026` : sent;
13913
- }
13914
- const msgs = rec.messages;
13915
- for (let i = msgs.length - 1; i >= 0; i--) {
13916
- const m = msgs[i];
13917
- if (!m || m.role !== "user" || typeof m.content !== "string") continue;
13918
- const text = m.content.trim();
13919
- if (!text) continue;
13920
- return text.length > LAST_USER_MESSAGE_MAX ? `${text.slice(0, LAST_USER_MESSAGE_MAX)}\u2026` : text;
13921
- }
13922
- return null;
13975
+ function buildAccountAuth(flags, env3 = process.env) {
13976
+ const username = (flags.username ?? env3.ZQAGENT_HUB_USERNAME ?? "").trim();
13977
+ let email = (flags.email ?? "").trim();
13978
+ let phone = (flags.phone ?? "").trim();
13979
+ if (username && !email && !phone) {
13980
+ if (username.includes("@")) email = username;
13981
+ else phone = username;
13982
+ }
13983
+ if (!email && !phone) return null;
13984
+ const password = flags.password ?? env3.ZQAGENT_HUB_PASSWORD ?? "";
13985
+ const smsCode = (flags.smsCode ?? env3.ZQAGENT_HUB_SMS_CODE ?? "").trim();
13986
+ return {
13987
+ email: email ? email.toLowerCase() : void 0,
13988
+ phone: phone || void 0,
13989
+ password: password || void 0,
13990
+ smsCode: smsCode || void 0
13991
+ };
13923
13992
  }
13924
- function lastCompletedAt(rec) {
13925
- if (rec.lastCompletedAt) return rec.lastCompletedAt;
13926
- if (rec.status !== "completed") return null;
13927
- const msgs = rec.messages;
13928
- for (let i = msgs.length - 1; i >= 0; i--) {
13929
- const m = msgs[i];
13930
- if (!m || m.role !== "assistant") continue;
13931
- if (typeof m.ts === "number" && Number.isFinite(m.ts)) {
13932
- return new Date(m.ts).toISOString();
13933
- }
13934
- return null;
13935
- }
13936
- return null;
13993
+ function isCompleteAccount(account) {
13994
+ return Boolean(account && (account.password || account.smsCode));
13937
13995
  }
13938
- function allSessionStates(store, manager) {
13939
- const out = [];
13940
- for (const id of store.listTenants()) {
13941
- const t = store.loadTenant(id);
13942
- if (!t) continue;
13943
- try {
13944
- for (const s of manager.listSessions(t.id)) {
13945
- out.push({
13946
- projectId: t.id,
13947
- projectName: t.name ?? t.id,
13948
- sessionId: s.sessionId,
13949
- name: s.name ?? null,
13950
- status: s.status,
13951
- lastUserMessage: lastUserMessage(s),
13952
- lastCompletedAt: lastCompletedAt(s)
13953
- });
13954
- }
13955
- } catch {
13996
+ function parseLinkArgs(argv) {
13997
+ const out = {};
13998
+ for (let i = 0; i < argv.length; i++) {
13999
+ const a = argv[i];
14000
+ const val = argv[i + 1];
14001
+ if (a === "--hub") {
14002
+ out.hubUrl = val;
14003
+ i++;
14004
+ } else if (a === "--code") {
14005
+ out.code = val;
14006
+ i++;
14007
+ } else if (a === "--username" || a === "--user") {
14008
+ out.username = val;
14009
+ i++;
14010
+ } else if (a === "--email") {
14011
+ out.email = val;
14012
+ i++;
14013
+ } else if (a === "--phone") {
14014
+ out.phone = val;
14015
+ i++;
14016
+ } else if (a === "--password") {
14017
+ out.password = val;
14018
+ i++;
14019
+ } else if (a === "--sms-code") {
14020
+ out.smsCode = val;
14021
+ i++;
14022
+ } else if (a === "--data-dir") {
14023
+ out.dataDir = val;
14024
+ i++;
14025
+ } else if (a === "--label") {
14026
+ out.label = val;
14027
+ i++;
14028
+ } else if (a === "--host") {
14029
+ out.host = val;
14030
+ i++;
14031
+ } else if (a === "--port") {
14032
+ out.port = Number(val);
14033
+ i++;
14034
+ } else if (a === "-h" || a === "--help") {
14035
+ out.help = true;
13956
14036
  }
13957
14037
  }
13958
14038
  return out;
@@ -13965,50 +14045,285 @@ function ensureNodeId2(store) {
13965
14045
  }
13966
14046
  return id;
13967
14047
  }
13968
- function createProject(store, input) {
13969
- const raw = input ?? {};
13970
- const id = String(raw.id ?? "").trim() || newId();
13971
- if (store.loadTenant(id)) {
13972
- throw new ApiError(409, "project_exists", `\u9879\u76EE ${id} \u5DF2\u5B58\u5728`);
14048
+ async function probeServer(host, port, adminKey) {
14049
+ const ctrl = new AbortController();
14050
+ const timer = setTimeout(() => ctrl.abort(), 1500);
14051
+ try {
14052
+ const res = await fetch(`http://${host}:${port}/api/v1/fleet/status`, {
14053
+ headers: { Authorization: `Bearer ${adminKey}` },
14054
+ signal: ctrl.signal
14055
+ });
14056
+ return res.ok;
14057
+ } catch {
14058
+ return false;
14059
+ } finally {
14060
+ clearTimeout(timer);
13973
14061
  }
13974
- const workspaceRoot = typeof raw.workspaceRoot === "string" && raw.workspaceRoot.trim() ? path27.resolve(raw.workspaceRoot.trim()) : void 0;
13975
- const explicitName = typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : "";
13976
- const defaultName = workspaceRoot ? path27.basename(workspaceRoot) : id;
13977
- const tenant = {
13978
- id,
13979
- name: explicitName || defaultName,
13980
- apiKey: generateApiKey(),
13981
- model: typeof raw.model === "string" && raw.model.trim() ? raw.model.trim() : DEFAULT_MODEL(),
13982
- provider: {
13983
- apiKey: typeof raw.providerApiKey === "string" && raw.providerApiKey.trim() ? raw.providerApiKey.trim() : void 0,
13984
- baseURL: typeof raw.baseURL === "string" && raw.baseURL.trim() ? raw.baseURL.trim() : void 0
14062
+ }
14063
+ async function enrollViaServer(host, port, adminKey, opts) {
14064
+ const res = await fetch(`http://${host}:${port}/api/v1/fleet/enroll`, {
14065
+ method: "POST",
14066
+ headers: {
14067
+ "Content-Type": "application/json",
14068
+ Authorization: `Bearer ${adminKey}`
13985
14069
  },
13986
- workspaceRoot,
13987
- allowedCommands: Array.isArray(raw.allowedCommands) ? raw.allowedCommands.map((c) => String(c)) : []
14070
+ body: JSON.stringify({
14071
+ hubUrl: opts.hubUrl,
14072
+ code: opts.code,
14073
+ email: opts.account?.email,
14074
+ phone: opts.account?.phone,
14075
+ password: opts.account?.password,
14076
+ smsCode: opts.account?.smsCode,
14077
+ label: opts.label
14078
+ })
14079
+ });
14080
+ const text = await res.text();
14081
+ let payload = null;
14082
+ try {
14083
+ payload = text ? JSON.parse(text) : null;
14084
+ } catch {
14085
+ }
14086
+ if (!res.ok) {
14087
+ throw new Error(payload?.error?.message ?? `HTTP ${res.status}`);
14088
+ }
14089
+ return payload?.data;
14090
+ }
14091
+ function hotWarnings(inst, running) {
14092
+ const warnings = [];
14093
+ const managed = running.filter((r) => r.managed && r.dataDir !== inst.dataDir);
14094
+ if (!inst.managed && managed.length > 0) {
14095
+ warnings.push(
14096
+ `\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`
14097
+ );
14098
+ warnings.push(
14099
+ "\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"
14100
+ );
14101
+ }
14102
+ const others = [...new Set(running.map((r) => r.dataDir).filter((d) => d !== inst.dataDir))];
14103
+ if (others.length > 0) {
14104
+ 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`);
14105
+ }
14106
+ return warnings;
14107
+ }
14108
+ async function linkNode(opts) {
14109
+ if (!opts.code && !isCompleteAccount(opts.account ?? null)) {
14110
+ throw new Error("\u7F3A\u5C11\u63A5\u5165\u7801\uFF0C\u6216 Hub \u8D26\u53F7\u5BC6\u7801/\u77ED\u4FE1\u9A8C\u8BC1\u7801");
14111
+ }
14112
+ const hubUrl = opts.hubUrl.trim().replace(/\/$/, "");
14113
+ if (!hubUrl) throw new Error("\u7F3A\u5C11 Hub \u5730\u5740");
14114
+ const rawDataDir = opts.dataDir?.trim() || process.env.DATA_DIR?.trim() || void 0;
14115
+ const explicitDataDir = rawDataDir ? expandHome(rawDataDir) : void 0;
14116
+ const host = opts.host?.trim() || process.env.HOST?.trim() || DEFAULT_SERVE_HOST;
14117
+ const port = opts.port ?? Number(process.env.PORT ?? DEFAULT_SERVE_PORT);
14118
+ const running = discoverRunningServeInstances();
14119
+ const candidates = buildHotCandidates(explicitDataDir, running, { host, port });
14120
+ for (const inst of candidates) {
14121
+ const store2 = new Store(inst.dataDir);
14122
+ const adminKey = process.env.ADMIN_API_KEY ?? store2.loadAdminKey();
14123
+ if (!adminKey) continue;
14124
+ if (!await probeServer(inst.host, inst.port, adminKey)) continue;
14125
+ const status = await enrollViaServer(inst.host, inst.port, adminKey, { ...opts, hubUrl });
14126
+ return {
14127
+ mode: "hot",
14128
+ ...status,
14129
+ dataDir: inst.dataDir,
14130
+ managed: inst.managed,
14131
+ warnings: hotWarnings(inst, running)
14132
+ };
14133
+ }
14134
+ const managedDir = running.find((r) => r.managed)?.dataDir;
14135
+ const dataDir = path28.resolve(explicitDataDir ?? managedDir ?? DEFAULT_DATA_DIR);
14136
+ const targetedService = !explicitDataDir && Boolean(managedDir) && dataDir === managedDir;
14137
+ const store = new Store(dataDir);
14138
+ const nodeId = ensureNodeId2(store);
14139
+ const account = opts.account;
14140
+ const cred = account && isCompleteAccount(account) ? await hubEnrollByAccount(hubUrl, {
14141
+ email: account.email,
14142
+ phone: account.phone,
14143
+ password: account.password,
14144
+ code: account.smsCode,
14145
+ nodeId,
14146
+ label: opts.label
14147
+ }) : await hubEnrollByCode(hubUrl, {
14148
+ code: opts.code,
14149
+ nodeId,
14150
+ label: opts.label
14151
+ });
14152
+ try {
14153
+ store.saveFleetCredential({
14154
+ ...cred,
14155
+ label: opts.label ?? cred.label,
14156
+ enrolledAt: (/* @__PURE__ */ new Date()).toISOString()
14157
+ });
14158
+ } catch (e) {
14159
+ const err = e;
14160
+ if (err.code === "EACCES" || err.code === "EPERM") {
14161
+ throw new Error(
14162
+ `\u65E0\u6CD5\u5199\u5165\u6570\u636E\u76EE\u5F55 ${dataDir}\uFF08${err.code}: ${err.message}\uFF09
14163
+ \u8BE5\u76EE\u5F55\u5F53\u524D\u7528\u6237\u6CA1\u6709\u5199\u6743\u9650\u3002\u4E24\u79CD\u6539\u6CD5\uFF1A
14164
+ 1) \u53BB\u6389 --data-dir\uFF0C\u8BA9 link \u81EA\u52A8\u53D1\u73B0\u672C\u673A\u8FD0\u884C\u7684\u8282\u70B9\uFF08\u63A8\u8350\uFF09\uFF1B
14165
+ 2) \u7528\u5F53\u524D\u7528\u6237\u53EF\u5199\u7684\u76EE\u5F55\uFF0C\u672C\u5E73\u53F0\u5B89\u88C5\u811A\u672C\u9ED8\u8BA4\uFF1A${installDataDirHint()}
14166
+ \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`
14167
+ );
14168
+ }
14169
+ throw e;
14170
+ }
14171
+ const otherRunningDirs = [
14172
+ ...new Set(running.map((r) => r.dataDir).filter((d) => d !== dataDir))
14173
+ ];
14174
+ return {
14175
+ mode: "saved",
14176
+ nodeId,
14177
+ gatewayUrl: cred.gatewayUrl,
14178
+ label: opts.label,
14179
+ dataDir,
14180
+ explicitDataDir: Boolean(explicitDataDir),
14181
+ runningServiceDirs: otherRunningDirs,
14182
+ targetedService
13988
14183
  };
13989
- const def = nodeDefaultProvider(store);
13990
- if (def) {
13991
- if (!tenant.provider?.apiKey && def.apiKey) tenant.provider = { ...tenant.provider, apiKey: def.apiKey };
13992
- if (!tenant.provider?.baseURL && def.baseURL) tenant.provider = { ...tenant.provider, baseURL: def.baseURL };
13993
- if (!(typeof raw.model === "string" && raw.model.trim()) && def.model) tenant.model = def.model;
13994
- const nodeCfg = store.loadNodeLlm();
13995
- tenant.llmProviders = nodeCfg.providers.map((p) => ({ ...p }));
13996
- tenant.activeProviderId = nodeCfg.defaultProviderId ?? nodeCfg.providers[0]?.id;
14184
+ }
14185
+ function printLinkHelp() {
14186
+ cliLog("\u7528\u6CD5: zqagent link [--hub <url>] [--username <\u90AE\u7BB1/\u624B\u673A\u53F7> --password <\u5BC6\u7801>] [--label \u540D\u79F0]");
14187
+ cliLog(" zqagent link [--hub <url>] --code <\u4E00\u6B21\u6027\u63A5\u5165\u7801> [--label \u540D\u79F0]");
14188
+ cliLog(" \u65B9\u5F0F\u4E00\uFF08\u63A8\u8350\uFF09\uFF1A\u7528 Hub \u5DF2\u6CE8\u518C\u7684\u8D26\u53F7\u76F4\u63A5\u63A5\u5165\uFF1A");
14189
+ cliLog(" zqagent link --hub https://hub.cd-zq.com --username you@example.com --password '<\u5BC6\u7801>'");
14190
+ cliLog(" zqagent link --hub https://hub.cd-zq.com --username 13800000000 --password '<\u5BC6\u7801>'");
14191
+ cliLog(" zqagent link --hub https://hub.cd-zq.com --username 13800000000 --sms-code <\u9A8C\u8BC1\u7801>");
14192
+ 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");
14193
+ cliLog(" zqagent link --hub https://hub.cd-zq.com --code <\u63A5\u5165\u7801>");
14194
+ 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");
14195
+ 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");
14196
+ 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");
14197
+ 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");
14198
+ }
14199
+ async function runLinkCommand(argv, ctx = {}) {
14200
+ const args = parseLinkArgs(argv);
14201
+ if (args.help) {
14202
+ printLinkHelp();
14203
+ return true;
14204
+ }
14205
+ const explicitHub = Boolean((args.hubUrl ?? process.env.HUB_URL ?? "").trim());
14206
+ let hubUrl = (args.hubUrl ?? process.env.HUB_URL ?? DEFAULT_HUB_URL).trim().replace(/\/$/, "");
14207
+ const code = (args.code ?? "").trim();
14208
+ let account = buildAccountAuth(args);
14209
+ const interactive = ctx.interactive ?? (process.stdin.isTTY && process.stdout.isTTY);
14210
+ if (code && account) {
14211
+ cliErr("--code \u4E0E Hub \u8D26\u53F7\uFF08--username/--email/--phone/--password\uFF09\u53EA\u80FD\u4E8C\u9009\u4E00\u3002");
14212
+ return false;
14213
+ }
14214
+ if (!account && (args.password || args.smsCode)) {
14215
+ 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");
14216
+ return false;
14217
+ }
14218
+ const asPrompter = (rl2) => ({
14219
+ question: (q) => new Promise((resolve24) => rl2.question(q, resolve24)),
14220
+ pause: () => rl2.pause(),
14221
+ resume: () => rl2.resume()
14222
+ });
14223
+ let ownedRl = null;
14224
+ const getRl = () => {
14225
+ if (ctx.rl) return asPrompter(ctx.rl);
14226
+ if (!interactive) return null;
14227
+ ownedRl = readline2.createInterface({ input: process.stdin, output: process.stdout });
14228
+ return asPrompter(ownedRl);
14229
+ };
14230
+ const closeOwnedRl = () => {
14231
+ if (ownedRl) ownedRl.close();
14232
+ };
14233
+ try {
14234
+ if (!code && !isCompleteAccount(account)) {
14235
+ const rl2 = getRl();
14236
+ if (!rl2) {
14237
+ cliErr("\u7F3A\u5C11\u63A5\u5165\u51ED\u636E\uFF1A\u8BF7\u7528 --username/--password\uFF0C\u6216 --code <\u63A5\u5165\u7801>\u3002");
14238
+ printLinkHelp();
14239
+ return false;
14240
+ }
14241
+ if (account) {
14242
+ const password = await promptPassword(rl2);
14243
+ if (!password) {
14244
+ cliErr("\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u5DF2\u53D6\u6D88\u63A5\u5165\u3002");
14245
+ return false;
14246
+ }
14247
+ account = { ...account, password };
14248
+ } else {
14249
+ const prompted = await promptLinkAccount(rl2, hubUrl, { askHub: !explicitHub });
14250
+ if (!prompted) return true;
14251
+ hubUrl = prompted.hubUrl;
14252
+ account = {
14253
+ email: prompted.auth.email,
14254
+ phone: prompted.auth.phone,
14255
+ password: prompted.auth.password,
14256
+ smsCode: prompted.auth.code
14257
+ };
14258
+ }
14259
+ }
14260
+ const r = await linkNode({
14261
+ hubUrl,
14262
+ code: code || void 0,
14263
+ account: account ?? void 0,
14264
+ dataDir: args.dataDir,
14265
+ label: args.label,
14266
+ host: args.host,
14267
+ port: args.port
14268
+ });
14269
+ if (r.mode === "hot") {
14270
+ cliLog("\u2705 \u5DF2\u63A5\u5165 Hub\uFF08\u70ED\u63A5\u5165\uFF0C\u514D\u91CD\u542F\uFF09");
14271
+ cliLog(` \u8282\u70B9: ${r.nodeId}${r.label ? ` (${r.label})` : ""}`);
14272
+ cliLog(` \u7F51\u5173: ${r.gatewayUrl ?? "\uFF08\u672A\u4E0B\u53D1\uFF09"} \u5728\u7EBF: ${r.online ? "\u662F" : "\u8FDE\u63A5\u4E2D\u2026"}`);
14273
+ cliLog(` \u6570\u636E\u76EE\u5F55: ${r.dataDir}${r.managed ? "\uFF08\u6258\u7BA1\u670D\u52A1\uFF0C\u5F00\u673A\u81EA\u542F\uFF09" : ""}`);
14274
+ if (r.warnings.length > 0) {
14275
+ cliLog("");
14276
+ for (const w of r.warnings) cliLog(`\u26A0\uFE0F ${w}`);
14277
+ }
14278
+ } else {
14279
+ cliLog("\u2705 \u63A5\u5165\u51ED\u636E\u5DF2\u4FDD\u5B58\uFF08\u672C\u673A\u6682\u65E0\u8FD0\u884C\u4E2D\u7684 server\uFF09");
14280
+ cliLog(` \u8282\u70B9: ${r.nodeId}`);
14281
+ cliLog(` \u7F51\u5173: ${r.gatewayUrl ?? "\uFF08\u672A\u4E0B\u53D1\uFF09"}`);
14282
+ cliLog(` \u6570\u636E\u76EE\u5F55: ${r.dataDir}`);
14283
+ cliLog(" \u4E0B\u6B21\u542F\u52A8 server \u5373\u81EA\u52A8\u63A5\u5165\uFF1B\u6216\u8FD0\u884C zqagent serve \u5E38\u9A7B\u4E3A\u8282\u70B9\u3002");
14284
+ if (r.targetedService) {
14285
+ cliLog("");
14286
+ 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");
14287
+ cliLog(" \u7ACB\u5373\u751F\u6548\uFF1A\u91CD\u542F\u8BE5\u670D\u52A1\uFF08Linux: systemctl restart zqagent\uFF1B");
14288
+ cliLog(" macOS: launchctl kickstart -k gui/$(id -u)/com.zqagent.node\uFF09\u3002");
14289
+ } else if (r.runningServiceDirs.length > 0) {
14290
+ cliLog("");
14291
+ 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");
14292
+ cliLog(` \u672C\u6B21\u5199\u5165: ${r.dataDir}`);
14293
+ for (const d of r.runningServiceDirs) cliLog(` \u670D\u52A1\u4F7F\u7528: ${d}`);
14294
+ 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");
14295
+ 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");
14296
+ cliLog(` zqagent link --hub <Hub\u5730\u5740> --username <\u90AE\u7BB1/\u624B\u673A\u53F7> --password '<\u5BC6\u7801>' --data-dir "${r.runningServiceDirs[0]}"`);
14297
+ } else if (!r.explicitDataDir) {
14298
+ cliLog("");
14299
+ 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");
14300
+ 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");
14301
+ cliLog(" curl -fsSL https://agent.cd-zq.com/install.sh | sh");
14302
+ cliLog(` \uFF08\u670D\u52A1\u9ED8\u8BA4\u6570\u636E\u76EE\u5F55\uFF1A${installDataDirHint()}\uFF09`);
14303
+ }
14304
+ }
14305
+ return true;
14306
+ } catch (e) {
14307
+ cliErr(`\u63A5\u5165\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
14308
+ return false;
14309
+ } finally {
14310
+ closeOwnedRl();
13997
14311
  }
13998
- store.saveTenant(tenant);
13999
- return tenant;
14000
- }
14001
- function buildNodeInfo(store, opts) {
14002
- return {
14003
- nodeId: opts.nodeId ?? ensureNodeId2(store),
14004
- platform: process.platform,
14005
- mode: process.env.NODE_MODE ?? "standalone",
14006
- version: "0.2.11",
14007
- admin: opts.admin,
14008
- projects: opts.projects
14009
- };
14010
14312
  }
14011
14313
 
14314
+ // src/cli/serve.ts
14315
+ init_esm_shims();
14316
+
14317
+ // src/server/runtime.ts
14318
+ init_esm_shims();
14319
+ import * as fs31 from "fs";
14320
+ import * as path39 from "path";
14321
+
14322
+ // src/server/routes.ts
14323
+ init_esm_shims();
14324
+ import { createServer } from "http";
14325
+ import * as path32 from "path";
14326
+
14012
14327
  // src/server/auth.ts
14013
14328
  init_esm_shims();
14014
14329
  import * as crypto4 from "crypto";
@@ -14184,13 +14499,13 @@ async function handleHubProxy(req2, res) {
14184
14499
  init_esm_shims();
14185
14500
  import * as fs22 from "fs";
14186
14501
  import * as os6 from "os";
14187
- import * as path28 from "path";
14502
+ import * as path29 from "path";
14188
14503
  var MAX_ENTRIES = 2e3;
14189
14504
  var DEFAULT_SEARCH_LIMIT = 200;
14190
14505
  var MAX_SEARCH_LIMIT = 1e3;
14191
14506
  function listDirs(input) {
14192
14507
  const base = (input ?? "").trim() || os6.homedir();
14193
- const target = path28.resolve(base);
14508
+ const target = path29.resolve(base);
14194
14509
  let st;
14195
14510
  try {
14196
14511
  st = fs22.statSync(target);
@@ -14205,7 +14520,7 @@ function listDirs(input) {
14205
14520
  throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF08\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09: ${target}`);
14206
14521
  }
14207
14522
  const entries = dirents.map((d) => {
14208
- const full = path28.join(target, d.name);
14523
+ const full = path29.join(target, d.name);
14209
14524
  let isDir = d.isDirectory();
14210
14525
  if (d.isSymbolicLink()) {
14211
14526
  try {
@@ -14218,7 +14533,7 @@ function listDirs(input) {
14218
14533
  entries.sort(
14219
14534
  (a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1
14220
14535
  );
14221
- const parentDir = path28.dirname(target);
14536
+ const parentDir = path29.dirname(target);
14222
14537
  return {
14223
14538
  path: target,
14224
14539
  parent: parentDir !== target ? parentDir : null,
@@ -14249,7 +14564,7 @@ var NAME_CACHE_MAX_DIRS = 4;
14249
14564
  var NAME_CACHE_MAX_ENTRIES = 5e4;
14250
14565
  var nameCache = /* @__PURE__ */ new Map();
14251
14566
  function invalidateDirNames(target) {
14252
- nameCache.delete(path28.resolve(target));
14567
+ nameCache.delete(path29.resolve(target));
14253
14568
  }
14254
14569
  async function loadDirNames(target) {
14255
14570
  const now = Date.now();
@@ -14284,7 +14599,7 @@ async function loadDirNames(target) {
14284
14599
  return entries;
14285
14600
  }
14286
14601
  async function toDirEntry(dir, e) {
14287
- const full = path28.join(dir, e.name);
14602
+ const full = path29.join(dir, e.name);
14288
14603
  let isDir = e.isDir;
14289
14604
  if (e.isLink) {
14290
14605
  try {
@@ -14296,7 +14611,7 @@ async function toDirEntry(dir, e) {
14296
14611
  }
14297
14612
  async function searchDirs(input, query, limit) {
14298
14613
  const base = (input ?? "").trim() || os6.homedir();
14299
- const target = path28.resolve(base);
14614
+ const target = path29.resolve(base);
14300
14615
  let st;
14301
14616
  try {
14302
14617
  st = await fs22.promises.stat(target);
@@ -14329,7 +14644,7 @@ async function searchDirs(input, query, limit) {
14329
14644
  );
14330
14645
  const hit = scored.slice(0, max);
14331
14646
  const entries = await Promise.all(hit.map(({ entry }) => toDirEntry(target, entry)));
14332
- const parentDir = path28.dirname(target);
14647
+ const parentDir = path29.dirname(target);
14333
14648
  return {
14334
14649
  path: target,
14335
14650
  parent: parentDir !== target ? parentDir : null,
@@ -14340,13 +14655,13 @@ async function searchDirs(input, query, limit) {
14340
14655
  };
14341
14656
  }
14342
14657
  function makeDirIn(parentInput, name) {
14343
- const parent = path28.resolve((parentInput ?? "").trim() || os6.homedir());
14658
+ const parent = path29.resolve((parentInput ?? "").trim() || os6.homedir());
14344
14659
  const clean = (name ?? "").trim();
14345
14660
  if (!clean) throw new Error("\u76EE\u5F55\u540D\u4E0D\u80FD\u4E3A\u7A7A");
14346
14661
  if (clean === "." || clean === "..") throw new Error("\u76EE\u5F55\u540D\u975E\u6CD5");
14347
- const target = path28.resolve(parent, clean);
14348
- const rel = path28.relative(parent, target);
14349
- if (!rel || rel.startsWith("..") || path28.isAbsolute(rel)) {
14662
+ const target = path29.resolve(parent, clean);
14663
+ const rel = path29.relative(parent, target);
14664
+ if (!rel || rel.startsWith("..") || path29.isAbsolute(rel)) {
14350
14665
  throw new Error("\u76EE\u5F55\u540D\u4E0D\u80FD\u8D8A\u51FA\u5F53\u524D\u76EE\u5F55");
14351
14666
  }
14352
14667
  let st = null;
@@ -14362,25 +14677,25 @@ function makeDirIn(parentInput, name) {
14362
14677
  throw new Error(`\u521B\u5EFA\u76EE\u5F55\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
14363
14678
  }
14364
14679
  invalidateDirNames(parent);
14365
- return { name: path28.basename(target), path: target, type: "dir" };
14680
+ return { name: path29.basename(target), path: target, type: "dir" };
14366
14681
  }
14367
14682
 
14368
14683
  // src/server/projectFiles.ts
14369
14684
  init_esm_shims();
14370
14685
  import * as fs23 from "fs";
14371
- import * as path29 from "path";
14686
+ import * as path30 from "path";
14372
14687
  var MAX_FILE_BYTES = 2 * 1024 * 1024;
14373
14688
  var MAX_DOWNLOAD_BYTES = 16 * 1024 * 1024;
14374
14689
  var MAX_ENTRIES2 = 2e3;
14375
14690
  function toPosix(p) {
14376
- return p.split(path29.sep).join("/");
14691
+ return p.split(path30.sep).join("/");
14377
14692
  }
14378
14693
  function projectRoot(workspaceRoot) {
14379
14694
  const r = (workspaceRoot ?? "").trim();
14380
14695
  if (!r) {
14381
14696
  throw new Error("\u9879\u76EE\u672A\u8BBE\u7F6E\u5DE5\u4F5C\u533A\u76EE\u5F55\uFF08workspaceRoot\uFF09\uFF0C\u65E0\u6CD5\u6D4F\u89C8\u6587\u4EF6");
14382
14697
  }
14383
- const abs = path29.resolve(r);
14698
+ const abs = path30.resolve(r);
14384
14699
  if (!fs23.existsSync(abs)) {
14385
14700
  throw new Error(`\u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728: ${abs}`);
14386
14701
  }
@@ -14405,7 +14720,7 @@ function listProjectFiles(workspaceRoot, relPath) {
14405
14720
  try {
14406
14721
  st = fs23.statSync(dir);
14407
14722
  } catch {
14408
- throw new Error(`\u76EE\u5F55\u4E0D\u5B58\u5728: ${toPosix(path29.relative(base, dir)) || "/"}`);
14723
+ throw new Error(`\u76EE\u5F55\u4E0D\u5B58\u5728: ${toPosix(path30.relative(base, dir)) || "/"}`);
14409
14724
  }
14410
14725
  if (!st.isDirectory()) throw new Error("\u76EE\u6807\u4E0D\u662F\u76EE\u5F55");
14411
14726
  let dirents;
@@ -14415,7 +14730,7 @@ function listProjectFiles(workspaceRoot, relPath) {
14415
14730
  throw new Error("\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF08\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09");
14416
14731
  }
14417
14732
  const entries = dirents.map((d) => {
14418
- const full = path29.join(dir, d.name);
14733
+ const full = path30.join(dir, d.name);
14419
14734
  let isDir = d.isDirectory();
14420
14735
  if (d.isSymbolicLink()) {
14421
14736
  try {
@@ -14433,7 +14748,7 @@ function listProjectFiles(workspaceRoot, relPath) {
14433
14748
  }
14434
14749
  return {
14435
14750
  name: d.name,
14436
- path: toPosix(path29.relative(base, full)),
14751
+ path: toPosix(path30.relative(base, full)),
14437
14752
  type: isDir ? "dir" : "file",
14438
14753
  size,
14439
14754
  mtime
@@ -14442,12 +14757,12 @@ function listProjectFiles(workspaceRoot, relPath) {
14442
14757
  entries.sort(
14443
14758
  (a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1
14444
14759
  );
14445
- const relDir = toPosix(path29.relative(base, dir));
14446
- const parentAbs = path29.dirname(dir);
14760
+ const relDir = toPosix(path30.relative(base, dir));
14761
+ const parentAbs = path30.dirname(dir);
14447
14762
  return {
14448
14763
  root: base,
14449
14764
  path: relDir,
14450
- parent: isWithinRoot(base, parentAbs) ? toPosix(path29.relative(base, parentAbs)) : null,
14765
+ parent: isWithinRoot(base, parentAbs) ? toPosix(path30.relative(base, parentAbs)) : null,
14451
14766
  entries: entries.slice(0, MAX_ENTRIES2)
14452
14767
  };
14453
14768
  }
@@ -14466,7 +14781,7 @@ function readProjectFile(workspaceRoot, relPath) {
14466
14781
  const truncated = buf.length > MAX_FILE_BYTES;
14467
14782
  const slice = truncated ? buf.subarray(0, MAX_FILE_BYTES) : buf;
14468
14783
  return {
14469
- path: toPosix(path29.relative(base, file)),
14784
+ path: toPosix(path30.relative(base, file)),
14470
14785
  size: st.size,
14471
14786
  binary,
14472
14787
  truncated,
@@ -14488,8 +14803,8 @@ function downloadProjectFile(workspaceRoot, relPath) {
14488
14803
  }
14489
14804
  const buf = fs23.readFileSync(file);
14490
14805
  return {
14491
- path: toPosix(path29.relative(base, file)),
14492
- name: path29.basename(file),
14806
+ path: toPosix(path30.relative(base, file)),
14807
+ name: path30.basename(file),
14493
14808
  size: buf.length,
14494
14809
  base64: buf.toString("base64")
14495
14810
  };
@@ -14511,12 +14826,12 @@ function writeProjectFile(workspaceRoot, relPath, content) {
14511
14826
  throw new Error("\u5185\u5BB9\u8D85\u8FC7 2MB \u4E0A\u9650\uFF0C\u65E0\u6CD5\u4FDD\u5B58");
14512
14827
  }
14513
14828
  try {
14514
- fs23.mkdirSync(path29.dirname(file), { recursive: true });
14829
+ fs23.mkdirSync(path30.dirname(file), { recursive: true });
14515
14830
  fs23.writeFileSync(file, buf);
14516
14831
  } catch (e) {
14517
14832
  throw new Error(`\u5199\u5165\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
14518
14833
  }
14519
- return { path: toPosix(path29.relative(base, file)), size: buf.length };
14834
+ return { path: toPosix(path30.relative(base, file)), size: buf.length };
14520
14835
  }
14521
14836
  function createProjectDir(workspaceRoot, relPath) {
14522
14837
  const base = projectRoot(workspaceRoot);
@@ -14528,7 +14843,7 @@ function createProjectDir(workspaceRoot, relPath) {
14528
14843
  } catch (e) {
14529
14844
  throw new Error(`\u521B\u5EFA\u76EE\u5F55\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
14530
14845
  }
14531
- return { path: toPosix(path29.relative(base, dir)) };
14846
+ return { path: toPosix(path30.relative(base, dir)) };
14532
14847
  }
14533
14848
  function createProjectFile(workspaceRoot, relPath, content = "") {
14534
14849
  const base = projectRoot(workspaceRoot);
@@ -14538,12 +14853,12 @@ function createProjectFile(workspaceRoot, relPath, content = "") {
14538
14853
  if (fs23.existsSync(file)) throw new Error("\u6587\u4EF6\u5DF2\u5B58\u5728");
14539
14854
  const buf = Buffer.from(String(content ?? ""), "utf-8");
14540
14855
  try {
14541
- fs23.mkdirSync(path29.dirname(file), { recursive: true });
14856
+ fs23.mkdirSync(path30.dirname(file), { recursive: true });
14542
14857
  fs23.writeFileSync(file, buf);
14543
14858
  } catch (e) {
14544
14859
  throw new Error(`\u521B\u5EFA\u6587\u4EF6\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
14545
14860
  }
14546
- return { path: toPosix(path29.relative(base, file)), size: buf.length };
14861
+ return { path: toPosix(path30.relative(base, file)), size: buf.length };
14547
14862
  }
14548
14863
  function searchProjectFiles(workspaceRoot, query) {
14549
14864
  const base = projectRoot(workspaceRoot);
@@ -14577,7 +14892,7 @@ function searchProjectFiles(workspaceRoot, query) {
14577
14892
  let isDir = d.isDirectory();
14578
14893
  if (d.isSymbolicLink()) {
14579
14894
  try {
14580
- isDir = fs23.statSync(path29.join(dir, d.name)).isDirectory();
14895
+ isDir = fs23.statSync(path30.join(dir, d.name)).isDirectory();
14581
14896
  } catch {
14582
14897
  isDir = false;
14583
14898
  }
@@ -14585,7 +14900,7 @@ function searchProjectFiles(workspaceRoot, query) {
14585
14900
  if (d.name.toLowerCase().includes(q)) {
14586
14901
  out.push({ path: rel, type: isDir ? "dir" : "file" });
14587
14902
  }
14588
- if (isDir) walk(path29.join(dir, d.name), rel);
14903
+ if (isDir) walk(path30.join(dir, d.name), rel);
14589
14904
  }
14590
14905
  };
14591
14906
  walk(base, "");
@@ -14595,7 +14910,7 @@ function searchProjectFiles(workspaceRoot, query) {
14595
14910
  // src/server/projectOps.ts
14596
14911
  init_esm_shims();
14597
14912
  import * as os7 from "os";
14598
- import * as path30 from "path";
14913
+ import * as path31 from "path";
14599
14914
  var MAX_STDOUT2 = 2e4;
14600
14915
  var MAX_STDERR2 = 1e4;
14601
14916
  var MAX_BUFFER3 = 1024 * 1024 * 5;
@@ -14609,7 +14924,7 @@ function resolveInvocation2(spec, prepared) {
14609
14924
  }
14610
14925
  function workRoot(workspaceRoot) {
14611
14926
  const r = (workspaceRoot ?? "").trim();
14612
- return r ? path30.resolve(r) : os7.homedir();
14927
+ return r ? path31.resolve(r) : os7.homedir();
14613
14928
  }
14614
14929
  async function runProjectCommand(workspaceRoot, command, timeoutSeconds) {
14615
14930
  const cmd = String(command ?? "");
@@ -14996,7 +15311,7 @@ function buildNodeHost(store, manager, diskPath) {
14996
15311
  arch: process.arch,
14997
15312
  release: os8.release(),
14998
15313
  uptimeSec: Math.round(os8.uptime()),
14999
- version: "0.2.11",
15314
+ version: "0.2.12",
15000
15315
  mode: process.env.NODE_MODE ?? "standalone",
15001
15316
  cpu: {
15002
15317
  count: cpus2.length,
@@ -15366,7 +15681,7 @@ function createApp(store, manager, opts = {}) {
15366
15681
  ok(res, opts.fleet.status());
15367
15682
  }
15368
15683
  },
15369
- // 运行时热接入:凭一次性注册码接入 Hub(落盘 fleet.json + 立即连接,免重启)
15684
+ // 运行时热接入:凭一次性注册码或 Hub 账号接入(落盘 fleet.json + 立即连接,免重启)
15370
15685
  {
15371
15686
  method: "POST",
15372
15687
  pattern: ["fleet", "enroll"],
@@ -15378,15 +15693,42 @@ function createApp(store, manager, opts = {}) {
15378
15693
  const raw = body ?? {};
15379
15694
  const hubUrl = String(raw.hubUrl ?? "").trim().replace(/\/$/, "");
15380
15695
  const code = String(raw.code ?? "").trim();
15381
- if (!hubUrl || !code) {
15382
- throw new ApiError(400, "bad_request", "hubUrl \u4E0E code \u5FC5\u586B");
15696
+ const email = String(raw.email ?? "").trim();
15697
+ const phone = String(raw.phone ?? "").trim();
15698
+ const password = String(raw.password ?? "");
15699
+ const smsCode = String(raw.smsCode ?? "").trim();
15700
+ const hasAccount = Boolean(email || phone);
15701
+ if (!hubUrl) {
15702
+ throw new ApiError(400, "bad_request", "hubUrl \u5FC5\u586B");
15703
+ }
15704
+ if (code && hasAccount) {
15705
+ throw new ApiError(400, "bad_request", "code \u4E0E\u8D26\u53F7\u51ED\u636E\u53EA\u80FD\u4E8C\u9009\u4E00");
15706
+ }
15707
+ if (!code && !hasAccount) {
15708
+ throw new ApiError(400, "bad_request", "\u9700\u8981 code\uFF0C\u6216 email/phone + password/smsCode");
15709
+ }
15710
+ if (hasAccount && !password && !smsCode) {
15711
+ throw new ApiError(400, "bad_request", "\u8D26\u53F7\u63A5\u5165\u9700\u8981 password \u6216 smsCode");
15383
15712
  }
15384
15713
  const label = typeof raw.label === "string" ? raw.label.trim() : void 0;
15385
15714
  try {
15386
- ok(res, await opts.fleet.enroll({ hubUrl, code, label }));
15715
+ ok(
15716
+ res,
15717
+ await opts.fleet.enroll({
15718
+ hubUrl,
15719
+ code: code || void 0,
15720
+ account: hasAccount ? {
15721
+ email: email || void 0,
15722
+ phone: phone || void 0,
15723
+ password: password || void 0,
15724
+ smsCode: smsCode || void 0
15725
+ } : void 0,
15726
+ label
15727
+ })
15728
+ );
15387
15729
  } catch (e) {
15388
15730
  const code2 = e.code;
15389
- 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;
15731
+ 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;
15390
15732
  const msg = e instanceof Error ? e.message : String(e);
15391
15733
  throw new ApiError(
15392
15734
  status,
@@ -15571,7 +15913,7 @@ function createApp(store, manager, opts = {}) {
15571
15913
  applyProvidersPatch(tenant, raw);
15572
15914
  if (typeof raw.workspaceRoot === "string") {
15573
15915
  const ws = raw.workspaceRoot.trim();
15574
- const next = ws ? path31.resolve(ws) : void 0;
15916
+ const next = ws ? path32.resolve(ws) : void 0;
15575
15917
  if (tenant.workspaceRoot) {
15576
15918
  if (next !== tenant.workspaceRoot) {
15577
15919
  throw new ApiError(
@@ -16558,7 +16900,7 @@ function createApp(store, manager, opts = {}) {
16558
16900
  json(res, 200, {
16559
16901
  status: "ok",
16560
16902
  // 版本让运维/探针能确认线上跑的是哪个构建(更新与回滚据此判断)
16561
- version: "0.2.11",
16903
+ version: "0.2.12",
16562
16904
  features: { mcp: hasServerMcpConfig() }
16563
16905
  });
16564
16906
  return;
@@ -17861,7 +18203,7 @@ function primaryIp() {
17861
18203
  var FleetNodeBridge = class {
17862
18204
  constructor(opts) {
17863
18205
  this.opts = opts;
17864
- this.nodeId = opts.nodeId ?? ensureNodeId2(opts.deps.store);
18206
+ this.nodeId = opts.nodeId ?? ensureNodeId(opts.deps.store);
17865
18207
  }
17866
18208
  opts;
17867
18209
  /** 是否成功连接过(区分「一直连不上」与「连上后断线」) */
@@ -18196,7 +18538,7 @@ var FleetNodeBridge = class {
18196
18538
  label: this.opts.deps.store.loadNodeLabel() ?? void 0,
18197
18539
  platform: process.platform,
18198
18540
  mode: process.env.NODE_MODE ?? "standalone",
18199
- version: "0.2.11",
18541
+ version: "0.2.12",
18200
18542
  ip: primaryIp()
18201
18543
  };
18202
18544
  }
@@ -18251,7 +18593,7 @@ var FleetNodeBridge = class {
18251
18593
  label: this.opts.deps.store.loadNodeLabel() ?? void 0,
18252
18594
  platform: process.platform,
18253
18595
  mode: process.env.NODE_MODE ?? "standalone",
18254
- version: "0.2.11",
18596
+ version: "0.2.12",
18255
18597
  ip: primaryIp(),
18256
18598
  projects,
18257
18599
  totals,
@@ -18439,28 +18781,6 @@ var FleetNodeBridge = class {
18439
18781
  }
18440
18782
  };
18441
18783
 
18442
- // src/server/fleet-config.ts
18443
- init_esm_shims();
18444
- function fleetConfigFromEnv(store) {
18445
- const fleetToken = process.env.FLEET_TOKEN;
18446
- const fleetSecret = process.env.FLEET_SECRET;
18447
- if (!fleetToken || !fleetSecret) return null;
18448
- return {
18449
- hubUrl: process.env.HUB_URL,
18450
- fleetToken,
18451
- fleetSecret,
18452
- nodeId: ensureNodeId2(store),
18453
- gatewayUrl: process.env.FLEET_GATEWAY_URL
18454
- };
18455
- }
18456
- function resolveFleetConfig(store) {
18457
- const saved = store.loadFleetCredential();
18458
- if (saved?.fleetToken && saved.fleetSecret) {
18459
- return { ...saved, nodeId: saved.nodeId || ensureNodeId2(store) };
18460
- }
18461
- return fleetConfigFromEnv(store);
18462
- }
18463
-
18464
18784
  // src/server/notify/index.ts
18465
18785
  init_esm_shims();
18466
18786
 
@@ -19021,7 +19341,7 @@ init_esm_shims();
19021
19341
  // src/server/qq/bindings.ts
19022
19342
  init_esm_shims();
19023
19343
  import * as fs25 from "fs";
19024
- import * as path32 from "path";
19344
+ import * as path33 from "path";
19025
19345
  function conversationKey(target) {
19026
19346
  switch (target.kind) {
19027
19347
  case "c2c":
@@ -19060,7 +19380,7 @@ var QqBindingsStore = class _QqBindingsStore {
19060
19380
  cache = null;
19061
19381
  /** 基于 DATA_DIR 构造 */
19062
19382
  static in(dataDir) {
19063
- return new _QqBindingsStore(path32.join(dataDir, "qq", "bindings.json"));
19383
+ return new _QqBindingsStore(path33.join(dataDir, "qq", "bindings.json"));
19064
19384
  }
19065
19385
  load() {
19066
19386
  if (this.cache) return this.cache;
@@ -19097,7 +19417,7 @@ var QqBindingsStore = class _QqBindingsStore {
19097
19417
  return null;
19098
19418
  }
19099
19419
  persist() {
19100
- fs25.mkdirSync(path32.dirname(this.file), { recursive: true });
19420
+ fs25.mkdirSync(path33.dirname(this.file), { recursive: true });
19101
19421
  fs25.writeFileSync(this.file, JSON.stringify(this.cache, null, 2), "utf-8");
19102
19422
  }
19103
19423
  };
@@ -19511,7 +19831,7 @@ ${lines.join("\n")}`, msgId);
19511
19831
  // src/server/qq/QqManager.ts
19512
19832
  init_esm_shims();
19513
19833
  import * as fs26 from "fs";
19514
- import * as path33 from "path";
19834
+ import * as path34 from "path";
19515
19835
  var QqManager = class {
19516
19836
  constructor(opts) {
19517
19837
  this.opts = opts;
@@ -19520,7 +19840,7 @@ var QqManager = class {
19520
19840
  adapter = null;
19521
19841
  config = null;
19522
19842
  get configFile() {
19523
- return path33.join(this.opts.dataDir, "qq", "config.json");
19843
+ return path34.join(this.opts.dataDir, "qq", "config.json");
19524
19844
  }
19525
19845
  /** 启动:读配置(env 优先,其次文件),有则建连接 */
19526
19846
  start() {
@@ -19613,7 +19933,7 @@ var QqManager = class {
19613
19933
  return null;
19614
19934
  }
19615
19935
  _persistConfig(cfg) {
19616
- fs26.mkdirSync(path33.dirname(this.configFile), { recursive: true });
19936
+ fs26.mkdirSync(path34.dirname(this.configFile), { recursive: true });
19617
19937
  fs26.writeFileSync(this.configFile, JSON.stringify(cfg, null, 2), "utf-8");
19618
19938
  }
19619
19939
  };
@@ -19663,7 +19983,7 @@ var PairingCodeStore = class {
19663
19983
  // src/update/controller.ts
19664
19984
  init_esm_shims();
19665
19985
  import * as fs30 from "fs";
19666
- import * as path37 from "path";
19986
+ import * as path38 from "path";
19667
19987
 
19668
19988
  // src/update/downloader.ts
19669
19989
  init_esm_shims();
@@ -19672,7 +19992,7 @@ import * as fs27 from "fs";
19672
19992
  import { Readable as Readable2 } from "stream";
19673
19993
  import { pipeline as pipeline2 } from "stream/promises";
19674
19994
  import { spawnSync as spawnSync4 } from "child_process";
19675
- import * as path34 from "path";
19995
+ import * as path35 from "path";
19676
19996
 
19677
19997
  // src/update/manifest.ts
19678
19998
  init_esm_shims();
@@ -19849,7 +20169,7 @@ async function fetchManifest(url, publicKey, opts = {}) {
19849
20169
  async function downloadBinary(bin, destPath, log = () => {
19850
20170
  }) {
19851
20171
  const partPath = `${destPath}.part`;
19852
- fs27.mkdirSync(path34.dirname(destPath), { recursive: true });
20172
+ fs27.mkdirSync(path35.dirname(destPath), { recursive: true });
19853
20173
  fs27.rmSync(partPath, { force: true });
19854
20174
  let res;
19855
20175
  try {
@@ -19893,7 +20213,7 @@ async function downloadBinary(bin, destPath, log = () => {
19893
20213
  fs27.rmSync(destPath, { force: true });
19894
20214
  fs27.renameSync(partPath, destPath);
19895
20215
  fs27.chmodSync(destPath, 493);
19896
- log(`\u5DF2\u4E0B\u8F7D ${path34.basename(destPath)}\uFF08${Math.round(bytes / 1024 / 1024)}MB\uFF0C\u6821\u9A8C\u901A\u8FC7\uFF09`);
20216
+ log(`\u5DF2\u4E0B\u8F7D ${path35.basename(destPath)}\uFF08${Math.round(bytes / 1024 / 1024)}MB\uFF0C\u6821\u9A8C\u901A\u8FC7\uFF09`);
19897
20217
  return { bytes };
19898
20218
  }
19899
20219
  function selfCheck(binPath, expectedVersion) {
@@ -19918,17 +20238,17 @@ function selfCheck(binPath, expectedVersion) {
19918
20238
  // src/update/installer.ts
19919
20239
  init_esm_shims();
19920
20240
  import * as fs29 from "fs";
19921
- import * as path36 from "path";
20241
+ import * as path37 from "path";
19922
20242
 
19923
20243
  // src/update/state.ts
19924
20244
  init_esm_shims();
19925
20245
  import * as fs28 from "fs";
19926
- import * as path35 from "path";
20246
+ import * as path36 from "path";
19927
20247
  function updatesDir(dataDir) {
19928
- return path35.join(dataDir, "updates");
20248
+ return path36.join(dataDir, "updates");
19929
20249
  }
19930
20250
  function statePath(dataDir) {
19931
- return path35.join(updatesDir(dataDir), "state.json");
20251
+ return path36.join(updatesDir(dataDir), "state.json");
19932
20252
  }
19933
20253
  function loadState(dataDir) {
19934
20254
  try {
@@ -19955,12 +20275,12 @@ function patchState(dataDir, patch) {
19955
20275
 
19956
20276
  // src/update/installer.ts
19957
20277
  function backupDir(dataDir) {
19958
- return path36.join(updatesDir(dataDir), "backup");
20278
+ return path37.join(updatesDir(dataDir), "backup");
19959
20279
  }
19960
20280
  function backupCurrent(dataDir, execPath, version) {
19961
20281
  const dir = backupDir(dataDir);
19962
20282
  fs29.mkdirSync(dir, { recursive: true });
19963
- const target = path36.join(dir, `zqagent-${version}${process.platform === "win32" ? ".exe" : ""}`);
20283
+ const target = path37.join(dir, `zqagent-${version}${process.platform === "win32" ? ".exe" : ""}`);
19964
20284
  fs29.copyFileSync(execPath, target);
19965
20285
  fs29.chmodSync(target, 493);
19966
20286
  return target;
@@ -19992,7 +20312,7 @@ function installStaged(opts) {
19992
20312
  const from = currentVersion();
19993
20313
  log(`\u6B63\u5728\u5B89\u88C5 v${opts.version}\uFF08\u5F53\u524D v${from}\uFF09`);
19994
20314
  const backupPath = backupCurrent(opts.dataDir, opts.execPath, from);
19995
- log(`\u5DF2\u5907\u4EFD\u5F53\u524D\u7248\u672C\u5230 ${path36.basename(backupPath)}`);
20315
+ log(`\u5DF2\u5907\u4EFD\u5F53\u524D\u7248\u672C\u5230 ${path37.basename(backupPath)}`);
19996
20316
  replaceExecutable(opts.execPath, opts.stagedPath);
19997
20317
  return patchState(opts.dataDir, {
19998
20318
  stagedVersion: void 0,
@@ -20036,7 +20356,7 @@ function rollback(dataDir, execPath, log = () => {
20036
20356
  }
20037
20357
  function readRollbackTarget(dataDir) {
20038
20358
  try {
20039
- const raw = fs29.readFileSync(path36.join(updatesDir(dataDir), "state.json"), "utf-8");
20359
+ const raw = fs29.readFileSync(path37.join(updatesDir(dataDir), "state.json"), "utf-8");
20040
20360
  const s = JSON.parse(raw);
20041
20361
  if (s.backupPath && s.backupVersion) return s;
20042
20362
  } catch {
@@ -20213,7 +20533,7 @@ var UpdateController = class {
20213
20533
  if (!bin) return `\u6E05\u5355\u6CA1\u6709\u5F53\u524D\u5E73\u53F0\uFF08${platformKey()}\uFF09\u7684\u4EA7\u7269`;
20214
20534
  let stagedPath = state.stagedPath;
20215
20535
  if (!(state.stagedVersion === manifest.version && stagedPath && fs30.existsSync(stagedPath))) {
20216
- stagedPath = path37.join(
20536
+ stagedPath = path38.join(
20217
20537
  updatesDir(this.opts.dataDir),
20218
20538
  "staged",
20219
20539
  `zqagent-${manifest.version}${process.platform === "win32" ? ".exe" : ""}`
@@ -20388,7 +20708,7 @@ function seedDemoTenant(store) {
20388
20708
  apiKey: env2("OPENAI_API_KEY"),
20389
20709
  baseURL: env2("OPENAI_BASE_URL")
20390
20710
  },
20391
- workspaceRoot: workspace ? path38.resolve(workspace) : void 0,
20711
+ workspaceRoot: workspace ? path39.resolve(workspace) : void 0,
20392
20712
  allowedCommands: []
20393
20713
  };
20394
20714
  store.saveTenant(demo);
@@ -20442,9 +20762,9 @@ function syncTenantModels(store) {
20442
20762
  tenant.model = model;
20443
20763
  changed = true;
20444
20764
  }
20445
- if (workspace && tenant.workspaceRoot !== path38.resolve(workspace)) {
20446
- console.log(`[sync] \u79DF\u6237 ${id} workspaceRoot \u5DF2\u540C\u6B65: ${tenant.workspaceRoot ?? "(\u6C99\u7BB1)"} \u2192 ${path38.resolve(workspace)}`);
20447
- tenant.workspaceRoot = path38.resolve(workspace);
20765
+ if (workspace && tenant.workspaceRoot !== path39.resolve(workspace)) {
20766
+ console.log(`[sync] \u79DF\u6237 ${id} workspaceRoot \u5DF2\u540C\u6B65: ${tenant.workspaceRoot ?? "(\u6C99\u7BB1)"} \u2192 ${path39.resolve(workspace)}`);
20767
+ tenant.workspaceRoot = path39.resolve(workspace);
20448
20768
  changed = true;
20449
20769
  }
20450
20770
  if (apiKey && tenant.provider.apiKey !== apiKey) {
@@ -20505,7 +20825,7 @@ function listenWithPortFallback(server, startPort, host, dataDir, limit = PORT_F
20505
20825
  }
20506
20826
  try {
20507
20827
  fs31.mkdirSync(dataDir, { recursive: true });
20508
- fs31.writeFileSync(path38.join(dataDir, "server-port"), `${actualPort}
20828
+ fs31.writeFileSync(path39.join(dataDir, "server-port"), `${actualPort}
20509
20829
  `);
20510
20830
  } catch {
20511
20831
  }
@@ -20516,7 +20836,7 @@ function listenWithPortFallback(server, startPort, host, dataDir, limit = PORT_F
20516
20836
  });
20517
20837
  }
20518
20838
  function startServer(opts = {}) {
20519
- const dataDir = path38.resolve(opts.dataDir ?? env2("DATA_DIR") ?? DEFAULT_DATA_DIR);
20839
+ const dataDir = path39.resolve(opts.dataDir ?? env2("DATA_DIR") ?? DEFAULT_DATA_DIR);
20520
20840
  const port = opts.port ?? Number(env2("PORT") ?? 8787);
20521
20841
  const host = opts.host ?? env2("HOST") ?? "127.0.0.1";
20522
20842
  exitWhenOrphaned();
@@ -20546,7 +20866,7 @@ function startServer(opts = {}) {
20546
20866
  }
20547
20867
  const adminKey = ensureAdminKey();
20548
20868
  const secretKey = env2("SECRET_KEY");
20549
- const secretBox = secretKey ? SecretBox.fromHex(secretKey) : SecretBox.fromFile(path38.join(dataDir, "secret.key"));
20869
+ const secretBox = secretKey ? SecretBox.fromHex(secretKey) : SecretBox.fromFile(path39.join(dataDir, "secret.key"));
20550
20870
  if (secretKey && !secretBox) {
20551
20871
  console.warn("[crypto] SECRET_KEY \u975E\u6CD5\uFF08\u9700 64 \u4F4D hex\uFF09\uFF0C\u901A\u77E5\u51ED\u636E\u5C06\u9000\u5316\u4E3A\u660E\u6587");
20552
20872
  }
@@ -20555,7 +20875,7 @@ function startServer(opts = {}) {
20555
20875
  let fleet = null;
20556
20876
  let fleetCred = null;
20557
20877
  function connectFleet(cred) {
20558
- const localId = ensureNodeId2(store);
20878
+ const localId = ensureNodeId(store);
20559
20879
  const nodeId = (cred.nodeId ?? "").trim() || localId;
20560
20880
  if (nodeId !== localId) store.saveNodeId(nodeId);
20561
20881
  fleet?.close();
@@ -20583,7 +20903,7 @@ function startServer(opts = {}) {
20583
20903
  function fleetStatus() {
20584
20904
  return {
20585
20905
  linked: fleet !== null,
20586
- nodeId: ensureNodeId2(store),
20906
+ nodeId: ensureNodeId(store),
20587
20907
  hubUrl: fleetCred?.hubUrl,
20588
20908
  gatewayUrl: fleetCred?.gatewayUrl ?? env2("FLEET_GATEWAY_URL"),
20589
20909
  online: fleet?.isConnected() ?? false,
@@ -20595,8 +20915,15 @@ function startServer(opts = {}) {
20595
20915
  const fleetController = {
20596
20916
  status: fleetStatus,
20597
20917
  async enroll(input) {
20598
- const nodeId = ensureNodeId2(store);
20599
- const cred = await hubEnrollByCode(input.hubUrl, {
20918
+ const nodeId = ensureNodeId(store);
20919
+ const cred = input.account ? await hubEnrollByAccount(input.hubUrl, {
20920
+ email: input.account.email,
20921
+ phone: input.account.phone,
20922
+ password: input.account.password,
20923
+ code: input.account.smsCode,
20924
+ nodeId,
20925
+ label: input.label
20926
+ }) : await hubEnrollByCode(input.hubUrl, {
20600
20927
  code: input.code,
20601
20928
  nodeId,
20602
20929
  label: input.label
@@ -20622,7 +20949,7 @@ function startServer(opts = {}) {
20622
20949
  gatewayUrl: fleetCred.gatewayUrl ?? env2("FLEET_GATEWAY_URL"),
20623
20950
  fleetToken: fleetCred.fleetToken,
20624
20951
  fleetSecret: fleetCred.fleetSecret,
20625
- nodeId: ensureNodeId2(store)
20952
+ nodeId: ensureNodeId(store)
20626
20953
  };
20627
20954
  }
20628
20955
  };
@@ -20775,7 +21102,7 @@ function printReady(info, qq) {
20775
21102
  `zqagent server \u76D1\u542C http://${info.host}:${info.port}`,
20776
21103
  `\u6570\u636E\u76EE\u5F55: ${info.dataDir}`,
20777
21104
  `\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`,
20778
- 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`
21105
+ env2("ZQAGENT_LOG_ADMIN_KEY") === "0" ? `\u7BA1\u7406\u5458\u5BC6\u94A5: ${maskKey(info.adminKey)}\uFF08\u5DF2\u8131\u654F\uFF0C\u5B8C\u6574\u503C\u89C1 ${path39.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`
20779
21106
  ];
20780
21107
  if (info.fleetNodeId) {
20781
21108
  lines.push(
@@ -20794,152 +21121,6 @@ function printReady(info, qq) {
20794
21121
  for (const l of lines) console.log(l);
20795
21122
  }
20796
21123
 
20797
- // src/cli/account-enroll.ts
20798
- init_esm_shims();
20799
- import * as path39 from "path";
20800
- import * as readline from "readline/promises";
20801
- var SKIP_ANSWERS = /* @__PURE__ */ new Set(["-", "skip", "none", "no"]);
20802
- function resolveDefaultHub(hubUrl, env3 = process.env) {
20803
- return (hubUrl ?? env3.HUB_URL ?? DEFAULT_HUB_URL).trim().replace(/\/$/, "");
20804
- }
20805
- function parseHubAnswer(answer, fallback) {
20806
- const a = answer.trim();
20807
- if (SKIP_ANSWERS.has(a.toLowerCase())) return null;
20808
- return (a || fallback).replace(/\/$/, "");
20809
- }
20810
- function resolveDataDir2(dataDir) {
20811
- return path39.resolve(dataDir ?? process.env.DATA_DIR ?? DEFAULT_DATA_DIR);
20812
- }
20813
- async function maybeAccountEnroll(opts = {}) {
20814
- if (!process.stdin.isTTY || !process.stdout.isTTY) return;
20815
- let store;
20816
- try {
20817
- store = new Store(resolveDataDir2(opts.dataDir));
20818
- if (resolveFleetConfig(store)) return;
20819
- } catch (e) {
20820
- cliErr(`\u63A5\u5165\u68C0\u67E5\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
20821
- return;
20822
- }
20823
- const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
20824
- try {
20825
- await runWizard(rl2, store, opts);
20826
- } catch (e) {
20827
- cliErr(`\u63A5\u5165\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
20828
- 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>");
20829
- } finally {
20830
- rl2.close();
20831
- }
20832
- }
20833
- async function runWizard(rl2, store, opts) {
20834
- cliLog("");
20835
- 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");
20836
- const fallback = resolveDefaultHub(opts.hubUrl);
20837
- const answer = await rl2.question(`Hub \u5730\u5740 [${fallback}]\uFF1A`);
20838
- const hubUrl = parseHubAnswer(answer, fallback);
20839
- if (!hubUrl) {
20840
- cliLog("\u5DF2\u8DF3\u8FC7\u63A5\u5165\uFF1A\u8282\u70B9\u4FDD\u6301\u7EAF\u672C\u5730\u8FD0\u884C\u3002");
20841
- return;
20842
- }
20843
- 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");
20844
- const mode = (await rl2.question("\u8BF7\u9009\u62E9 [1]\uFF1A")).trim() || "1";
20845
- const input = {};
20846
- if (mode === "1") {
20847
- input.email = (await rl2.question("\u90AE\u7BB1\uFF1A")).trim();
20848
- input.password = await askHidden(rl2, "\u5BC6\u7801\uFF1A");
20849
- if (!input.email || !input.password) {
20850
- cliErr("\u90AE\u7BB1\u4E0E\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
20851
- return;
20852
- }
20853
- } else if (mode === "2") {
20854
- input.phone = (await rl2.question("\u624B\u673A\u53F7\uFF1A")).trim();
20855
- input.password = await askHidden(rl2, "\u5BC6\u7801\uFF1A");
20856
- if (!input.phone || !input.password) {
20857
- cliErr("\u624B\u673A\u53F7\u4E0E\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
20858
- return;
20859
- }
20860
- } else if (mode === "3") {
20861
- input.phone = (await rl2.question("\u624B\u673A\u53F7\uFF1A")).trim();
20862
- if (!input.phone) {
20863
- cliErr("\u624B\u673A\u53F7\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
20864
- return;
20865
- }
20866
- try {
20867
- const sent = await hubSendSms(hubUrl, { phone: input.phone, purpose: "login" });
20868
- if (sent.debugCode) cliLog(`\uFF08\u8C03\u8BD5\u6A21\u5F0F\u9A8C\u8BC1\u7801\uFF1A${sent.debugCode}\uFF09`);
20869
- else cliLog("\u9A8C\u8BC1\u7801\u5DF2\u53D1\u9001\uFF0C5 \u5206\u949F\u5185\u6709\u6548\u3002");
20870
- } catch (e) {
20871
- cliErr(`\u77ED\u4FE1\u9A8C\u8BC1\u7801\u53D1\u9001\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
20872
- 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");
20873
- cliLog(" zqagent link --hub <Hub\u5730\u5740> --code <\u63A5\u5165\u7801>");
20874
- return;
20875
- }
20876
- input.code = (await rl2.question("\u77ED\u4FE1\u9A8C\u8BC1\u7801\uFF1A")).trim();
20877
- if (!input.code) {
20878
- cliErr("\u9A8C\u8BC1\u7801\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
20879
- return;
20880
- }
20881
- } else {
20882
- cliErr("\u672A\u77E5\u7684\u767B\u5F55\u65B9\u5F0F\uFF0C\u5DF2\u8DF3\u8FC7\u63A5\u5165\u3002");
20883
- return;
20884
- }
20885
- const nodeId = ensureNodeId2(store);
20886
- const cred = await hubEnrollByAccount(hubUrl, { ...input, nodeId, label: opts.label });
20887
- store.saveFleetCredential({
20888
- ...cred,
20889
- label: opts.label ?? cred.label,
20890
- enrolledAt: (/* @__PURE__ */ new Date()).toISOString()
20891
- });
20892
- cliLog(`\u2705 \u5DF2\u63A5\u5165 Hub\uFF1A\u8282\u70B9 ${cred.nodeId}\uFF08\u7F51\u5173 ${cred.gatewayUrl ?? "\u672A\u4E0B\u53D1"}\uFF09`);
20893
- cliLog(" \u51ED\u636E\u5DF2\u4FDD\u5B58\u5230 fleet.json\uFF0C\u672C\u6B21\u542F\u52A8\u5373\u8FDE\u63A5 Hub\u3002");
20894
- }
20895
- async function askHidden(rl2, prompt) {
20896
- if (!process.stdin.isTTY) return (await rl2.question(prompt)).trim();
20897
- return new Promise((resolve24) => {
20898
- const saved = process.stdin.listeners("data");
20899
- process.stdin.removeAllListeners("data");
20900
- cliWrite(prompt);
20901
- rl2.pause();
20902
- process.stdin.setRawMode(true);
20903
- process.stdin.resume();
20904
- let buf = "";
20905
- const cleanup = () => {
20906
- process.stdin.removeListener("data", onData);
20907
- for (const listener of saved) {
20908
- process.stdin.on("data", listener);
20909
- }
20910
- process.stdin.setRawMode(true);
20911
- rl2.resume();
20912
- };
20913
- const onData = (chunk) => {
20914
- const s = chunk.toString().replace(/\x1b\[[0-9;?]*[A-Za-z~]/g, "").replace(/\x1bO[A-Za-z]/g, "");
20915
- for (const ch of s) {
20916
- if (ch === "\r" || ch === "\n") {
20917
- cleanup();
20918
- cliWrite("\n");
20919
- resolve24(buf);
20920
- return;
20921
- }
20922
- if (ch === "") {
20923
- cleanup();
20924
- cliWrite("\n");
20925
- resolve24("");
20926
- return;
20927
- }
20928
- if (ch === "\x7F" || ch === "\b") {
20929
- if (buf.length > 0) {
20930
- buf = buf.slice(0, -1);
20931
- cliWrite("\b \b");
20932
- }
20933
- } else if (ch >= " ") {
20934
- buf += ch;
20935
- cliWrite("*");
20936
- }
20937
- }
20938
- };
20939
- process.stdin.on("data", onData);
20940
- });
20941
- }
20942
-
20943
21124
  // src/cli/serve.ts
20944
21125
  function parse2(argv) {
20945
21126
  const out = {};
@@ -20952,6 +21133,21 @@ function parse2(argv) {
20952
21133
  } else if (a === "--enroll-code" || a === "--code") {
20953
21134
  out.code = v;
20954
21135
  i++;
21136
+ } else if (a === "--username" || a === "--user") {
21137
+ out.username = v;
21138
+ i++;
21139
+ } else if (a === "--email") {
21140
+ out.email = v;
21141
+ i++;
21142
+ } else if (a === "--phone") {
21143
+ out.phone = v;
21144
+ i++;
21145
+ } else if (a === "--password") {
21146
+ out.password = v;
21147
+ i++;
21148
+ } else if (a === "--sms-code") {
21149
+ out.smsCode = v;
21150
+ i++;
20955
21151
  } else if (a === "--label") {
20956
21152
  out.label = v;
20957
21153
  i++;
@@ -20972,7 +21168,32 @@ async function runServeCommand(argv) {
20972
21168
  const args = parse2(argv);
20973
21169
  const hubUrl = (args.hubUrl ?? process.env.HUB_URL ?? "").trim();
20974
21170
  const code = (args.code ?? process.env.HUB_ENROLL_CODE ?? "").trim();
20975
- if (!(hubUrl && code)) {
21171
+ const account = buildAccountAuth(args);
21172
+ if (code && account) {
21173
+ cliErr("--enroll-code \u4E0E Hub \u8D26\u53F7\u53C2\u6570\uFF08--username/--password \u7B49\uFF09\u53EA\u80FD\u4E8C\u9009\u4E00\u3002");
21174
+ return;
21175
+ }
21176
+ if (account && !isCompleteAccount(account) && !process.stdin.isTTY) {
21177
+ 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");
21178
+ }
21179
+ if (!code && isCompleteAccount(account)) {
21180
+ try {
21181
+ const r = await linkNode({
21182
+ hubUrl: resolveDefaultHub(args.hubUrl || void 0),
21183
+ account,
21184
+ dataDir: args.dataDir,
21185
+ label: args.label,
21186
+ host: args.host,
21187
+ port: args.port
21188
+ });
21189
+ cliLog(
21190
+ 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`
21191
+ );
21192
+ } catch (e) {
21193
+ cliErr(`Hub \u8D26\u53F7\u63A5\u5165\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
21194
+ }
21195
+ }
21196
+ if (!(hubUrl && code) && !isCompleteAccount(account)) {
20976
21197
  await maybeAccountEnroll({ dataDir: args.dataDir, hubUrl: args.hubUrl, label: args.label });
20977
21198
  }
20978
21199
  const handle = startServer({
@@ -21547,7 +21768,7 @@ function cycleMode(engine) {
21547
21768
  engine.permissions.mode = next;
21548
21769
  return next;
21549
21770
  }
21550
- var rl = readline2.createInterface({
21771
+ var rl = readline3.createInterface({
21551
21772
  input: process.stdin,
21552
21773
  output: process.stdout
21553
21774
  });
@@ -22027,7 +22248,7 @@ async function offerTerminalSetup(engine, canUseAgent) {
22027
22248
  }
22028
22249
  var KNOWN_SUBCOMMANDS = /* @__PURE__ */ new Set(["link", "serve", "update"]);
22029
22250
  function cliVersion() {
22030
- const env3 = "0.2.11"?.trim();
22251
+ const env3 = "0.2.12"?.trim();
22031
22252
  if (env3) return env3;
22032
22253
  try {
22033
22254
  const req2 = createRequire4(import.meta.url);
@@ -22045,11 +22266,13 @@ function printCliUsage() {
22045
22266
  cliLog(" zqagent serve [\u9009\u9879] \u4EE5 headless \u65B9\u5F0F\u5E38\u9A7B\u4E3A\u53EF\u88AB Hub \u7BA1\u7406\u7684\u8282\u70B9");
22046
22267
  cliLog(" zqagent update [--status|--check|--now|--rollback]");
22047
22268
  cliLog(" \u67E5\u770B/\u624B\u52A8\u89E6\u53D1\u8282\u70B9\u81EA\u52A8\u66F4\u65B0");
22048
- cliLog(" zqagent link --hub <url> --code <\u63A5\u5165\u7801> [--label \u540D\u79F0]");
22049
- cliLog(" \u628A\u672C\u673A\u8282\u70B9\u63A5\u5165 Hub\uFF08\u672C\u673A\u6709 server \u65F6\u70ED\u63A5\u5165\uFF09");
22269
+ cliLog(" zqagent link [--hub <url>] [--username <\u90AE\u7BB1/\u624B\u673A\u53F7> --password <\u5BC6\u7801>]");
22270
+ cliLog(" [--code <\u63A5\u5165\u7801>] [--label \u540D\u79F0]");
22271
+ 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");
22050
22272
  cliLog("");
22051
22273
  cliLog("serve \u9009\u9879\uFF1A--port <n> --host <addr> --data-dir <dir>");
22052
22274
  cliLog(" --hub <url> --enroll-code <\u7801> --label <\u540D\u79F0> \uFF08\u542F\u52A8\u5373\u63A5\u5165\uFF09");
22275
+ cliLog(" --hub <url> --username <\u90AE\u7BB1/\u624B\u673A\u53F7> --password <\u5BC6\u7801>\uFF08\u8D26\u53F7\u63A5\u5165\uFF09");
22053
22276
  cliLog("\u5176\u5B83\u9009\u9879\uFF1A--resume <\u4F1A\u8BDDID> --debug --help/-h --version/-v");
22054
22277
  }
22055
22278
  async function main() {
@@ -22066,7 +22289,10 @@ async function main() {
22066
22289
  process.exit(0);
22067
22290
  }
22068
22291
  if (subcommand === "link") {
22069
- const ok2 = await runLinkCommand(process.argv.slice(3));
22292
+ const ok2 = await runLinkCommand(process.argv.slice(3), {
22293
+ rl,
22294
+ interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY)
22295
+ });
22070
22296
  rl.close();
22071
22297
  process.exit(ok2 ? 0 : 1);
22072
22298
  }
@@ -22967,7 +23193,11 @@ async function main() {
22967
23193
  case "/link": {
22968
23194
  const code = parts[1];
22969
23195
  const hub = parts[2] ?? process.env.HUB_URL;
22970
- if (!code || !hub) {
23196
+ if (!code) {
23197
+ await runLinkCommand([], { rl, interactive: true });
23198
+ break;
23199
+ }
23200
+ if (!hub) {
22971
23201
  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");
22972
23202
  break;
22973
23203
  }