@zqagent/core 0.2.16 → 0.2.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -566,6 +566,15 @@ import * as path5 from "path";
566
566
  import * as readline from "readline";
567
567
  import { randomUUID } from "crypto";
568
568
 
569
+ // src/server/llmIds.ts
570
+ init_esm_shims();
571
+ var GLOBAL_PROVIDER_ID = "__env__";
572
+ var BUILTIN_PROVIDER_ID = "__zqai__";
573
+ function isBuiltinProvider(id) {
574
+ return id === BUILTIN_PROVIDER_ID;
575
+ }
576
+ var FALLBACK_MODEL = "gpt-4o";
577
+
569
578
  // src/server/dbgTrace.ts
570
579
  init_esm_shims();
571
580
  var DBG_FLEET = (() => {
@@ -1703,7 +1712,23 @@ var Store = class {
1703
1712
  legacyFiles: [path5.join(this.root, "node-llm.json")]
1704
1713
  });
1705
1714
  const old = new Map(cur.providers.map((p) => [p.id, p]));
1706
- const providers = cfg.providers.map((p) => {
1715
+ const src = [...cfg.providers];
1716
+ const prevBuiltin = cur.providers.find((p) => p.id === BUILTIN_PROVIDER_ID);
1717
+ if (prevBuiltin && !src.some((p) => p.id === BUILTIN_PROVIDER_ID)) {
1718
+ src.unshift({
1719
+ id: prevBuiltin.id,
1720
+ name: prevBuiltin.name,
1721
+ type: prevBuiltin.type,
1722
+ baseURL: prevBuiltin.baseURL,
1723
+ model: prevBuiltin.model,
1724
+ apiKey: decryptKey(prevBuiltin.apiKeyEnc, file) ?? void 0,
1725
+ reasoningEffort: prevBuiltin.reasoningEffort ?? null,
1726
+ ...prevBuiltin.models ? { models: prevBuiltin.models } : {},
1727
+ ...prevBuiltin.nonChat ? { nonChat: prevBuiltin.nonChat } : {},
1728
+ ...prevBuiltin.modelMeta ? { modelMeta: prevBuiltin.modelMeta } : {}
1729
+ });
1730
+ }
1731
+ const providers = src.map((p) => {
1707
1732
  const prev = old.get(p.id);
1708
1733
  const apiKeyEnc = p.apiKey ? encryptKey(p.apiKey, file) : prev?.apiKeyEnc ?? "";
1709
1734
  return {
@@ -9231,7 +9256,6 @@ function registerMediaTool(registry, workspace, opts = {}) {
9231
9256
 
9232
9257
  // src/server/nodeLlm.ts
9233
9258
  init_esm_shims();
9234
- var GLOBAL_PROVIDER_ID = "__env__";
9235
9259
  function envProvider() {
9236
9260
  const apiKey = process.env.OPENAI_API_KEY;
9237
9261
  if (!apiKey) return null;
@@ -9274,19 +9298,26 @@ function nodeLlmView(cfg) {
9274
9298
  global: true
9275
9299
  }
9276
9300
  ] : [],
9277
- ...cfg.providers.map((p) => ({
9278
- id: p.id,
9279
- name: p.name,
9280
- type: p.type ?? "",
9281
- baseURL: p.baseURL,
9282
- model: p.model,
9283
- hasApiKey: Boolean(p.apiKey),
9284
- models: p.models ?? [],
9285
- nonChat: p.nonChat ?? [],
9286
- modelMeta: p.modelMeta ?? [],
9287
- reasoningEffort: p.reasoningEffort ?? null,
9288
- global: false
9289
- }))
9301
+ ...cfg.providers.map((p) => {
9302
+ const builtin = isBuiltinProvider(p.id);
9303
+ return {
9304
+ id: p.id,
9305
+ name: p.name,
9306
+ type: p.type ?? "",
9307
+ baseURL: p.baseURL,
9308
+ model: p.model,
9309
+ hasApiKey: Boolean(p.apiKey),
9310
+ models: p.models ?? [],
9311
+ nonChat: p.nonChat ?? [],
9312
+ modelMeta: p.modelMeta ?? [],
9313
+ reasoningEffort: p.reasoningEffort ?? null,
9314
+ global: false,
9315
+ /** 平台内置(接入 Hub 后自动下发,用户不用关心 Key) */
9316
+ builtin,
9317
+ /** 不可编辑的字段:控制台据此把 baseURL / key 显示为只读 */
9318
+ ...builtin ? { locked: ["baseURL", "apiKey"] } : {}
9319
+ };
9320
+ })
9290
9321
  ];
9291
9322
  return {
9292
9323
  defaultProviderId: cfg.defaultProviderId ?? null,
@@ -9299,27 +9330,42 @@ function applyNodeLlmPatch(cfg, raw) {
9299
9330
  const next = { providers: cfg.providers, defaultProviderId: cfg.defaultProviderId };
9300
9331
  if (Array.isArray(raw?.providers)) {
9301
9332
  const old = new Map(cfg.providers.map((p) => [p.id, p]));
9302
- next.providers = raw.providers.filter((x) => Boolean(x) && typeof x === "object").filter((x) => x.global !== true && String(x.id ?? "") !== GLOBAL_PROVIDER_ID).map((x) => {
9303
- const id = String(x.id ?? "").trim();
9304
- if (!id) throw new ApiError(400, "bad_request", "\u4F9B\u5E94\u5546 id \u4E0D\u80FD\u4E3A\u7A7A");
9305
- const prev = old.get(id);
9306
- const apiKey = String(x.apiKey ?? "").trim();
9307
- const models = normalizeModelList(x.models) ?? prev?.models;
9308
- const nonChat = normalizeModelList(x.nonChat) ?? prev?.nonChat;
9309
- const modelMeta = normalizeModelMeta(x.modelMeta) ?? prev?.modelMeta;
9310
- return {
9311
- id,
9312
- name: String(x.name ?? prev?.name ?? id).trim(),
9313
- type: x.type == null ? prev?.type : String(x.type),
9314
- baseURL: String(x.baseURL ?? prev?.baseURL ?? "").trim(),
9315
- model: String(x.model ?? prev?.model ?? "").trim(),
9316
- apiKey: apiKey || prev?.apiKey,
9317
- reasoningEffort: x.reasoningEffort === void 0 ? prev?.reasoningEffort ?? null : normReasoningEffort(x.reasoningEffort),
9318
- ...models ? { models } : {},
9319
- ...nonChat ? { nonChat } : {},
9320
- ...modelMeta ? { modelMeta } : {}
9321
- };
9322
- });
9333
+ const incoming = raw.providers.filter((x) => Boolean(x) && typeof x === "object").filter((x) => x.global !== true && String(x.id ?? "") !== GLOBAL_PROVIDER_ID);
9334
+ const prevBuiltin = cfg.providers.find((p) => isBuiltinProvider(p.id));
9335
+ const builtinPatch = incoming.find((x) => isBuiltinProvider(String(x.id ?? "")));
9336
+ const keptBuiltin = prevBuiltin ? [
9337
+ {
9338
+ ...prevBuiltin,
9339
+ ...builtinPatch?.model != null ? { model: String(builtinPatch.model).trim() || prevBuiltin.model } : {},
9340
+ ...normalizeModelList(builtinPatch?.models) ? { models: normalizeModelList(builtinPatch?.models) } : {},
9341
+ ...builtinPatch?.reasoningEffort !== void 0 ? { reasoningEffort: normReasoningEffort(builtinPatch.reasoningEffort) } : {},
9342
+ ...typeof builtinPatch?.name === "string" && builtinPatch.name.trim() ? { name: builtinPatch.name.trim() } : {}
9343
+ }
9344
+ ] : [];
9345
+ next.providers = [
9346
+ ...keptBuiltin,
9347
+ ...incoming.filter((x) => !isBuiltinProvider(String(x.id ?? ""))).map((x) => {
9348
+ const id = String(x.id ?? "").trim();
9349
+ if (!id) throw new ApiError(400, "bad_request", "\u4F9B\u5E94\u5546 id \u4E0D\u80FD\u4E3A\u7A7A");
9350
+ const prev = old.get(id);
9351
+ const apiKey = String(x.apiKey ?? "").trim();
9352
+ const models = normalizeModelList(x.models) ?? prev?.models;
9353
+ const nonChat = normalizeModelList(x.nonChat) ?? prev?.nonChat;
9354
+ const modelMeta = normalizeModelMeta(x.modelMeta) ?? prev?.modelMeta;
9355
+ return {
9356
+ id,
9357
+ name: String(x.name ?? prev?.name ?? id).trim(),
9358
+ type: x.type == null ? prev?.type : String(x.type),
9359
+ baseURL: String(x.baseURL ?? prev?.baseURL ?? "").trim(),
9360
+ model: String(x.model ?? prev?.model ?? "").trim(),
9361
+ apiKey: apiKey || prev?.apiKey,
9362
+ reasoningEffort: x.reasoningEffort === void 0 ? prev?.reasoningEffort ?? null : normReasoningEffort(x.reasoningEffort),
9363
+ ...models ? { models } : {},
9364
+ ...nonChat ? { nonChat } : {},
9365
+ ...modelMeta ? { modelMeta } : {}
9366
+ };
9367
+ })
9368
+ ];
9323
9369
  }
9324
9370
  if (typeof raw?.defaultProviderId === "string") {
9325
9371
  const id = raw.defaultProviderId.trim();
@@ -9339,6 +9385,9 @@ function nodeDefaultProvider(store) {
9339
9385
  if (cfg.providers.length === 0) return null;
9340
9386
  return cfg.providers.find((p) => p.id === cfg.defaultProviderId) ?? cfg.providers[0] ?? null;
9341
9387
  }
9388
+ function defaultTenantModel(store) {
9389
+ return process.env.OPENAI_MODEL || process.env.MODEL || nodeDefaultProvider(store)?.model || FALLBACK_MODEL;
9390
+ }
9342
9391
  function resolveSessionProvider(store, tenant, providerId) {
9343
9392
  const own = tenant.provider ?? {};
9344
9393
  if (providerId) {
@@ -9368,6 +9417,208 @@ function resolveSessionProvider(store, tenant, providerId) {
9368
9417
  return { ...own, model: tenant.model };
9369
9418
  }
9370
9419
 
9420
+ // src/server/builtinLlm.ts
9421
+ init_esm_shims();
9422
+ var FETCH_TIMEOUT_MS = 8e3;
9423
+ var RETRY_AFTER_MS = 5 * 6e4;
9424
+ function hubBase(cred) {
9425
+ return (cred.hubUrl ?? "").replace(/\/+$/, "");
9426
+ }
9427
+ function asProvider(grant) {
9428
+ const baseURL = String(grant.baseURL ?? "").trim();
9429
+ const apiKey = String(grant.apiKey ?? "").trim();
9430
+ const model = String(grant.model ?? "").trim();
9431
+ if (!baseURL || !apiKey) return null;
9432
+ return {
9433
+ id: BUILTIN_PROVIDER_ID,
9434
+ name: String(grant.name ?? "").trim() || "\u5FD7\u524D\u5927\u6A21\u578B\uFF08\u5185\u7F6E\uFF09",
9435
+ type: "builtin",
9436
+ baseURL,
9437
+ model,
9438
+ apiKey
9439
+ };
9440
+ }
9441
+ async function enrichModels(p) {
9442
+ try {
9443
+ const r = await Promise.race([
9444
+ fetchModels(p.baseURL, p.apiKey ?? "", p.model),
9445
+ new Promise((resolve20) => {
9446
+ const t = setTimeout(() => resolve20(null), FETCH_TIMEOUT_MS);
9447
+ t.unref?.();
9448
+ })
9449
+ ]);
9450
+ if (!r?.models?.length) return p;
9451
+ return {
9452
+ ...p,
9453
+ // 控制面没给默认型号时,取供应商返回的第一个,避免模型为空让用户卡住
9454
+ model: p.model || r.models[0],
9455
+ models: r.models,
9456
+ ...r.nonChat?.length ? { nonChat: r.nonChat } : {},
9457
+ ...r.modelMeta?.length ? { modelMeta: r.modelMeta } : {}
9458
+ };
9459
+ } catch {
9460
+ return p;
9461
+ }
9462
+ }
9463
+ function saveBuiltin(store, provider) {
9464
+ const cfg = store.loadNodeLlm();
9465
+ const others = cfg.providers.filter((p) => p.id !== BUILTIN_PROVIDER_ID);
9466
+ const prev = cfg.providers.find((p) => p.id === BUILTIN_PROVIDER_ID);
9467
+ const merged = {
9468
+ ...provider,
9469
+ // 用户选过的型号/候选列表/推理强度/显示名都保留;只有托管字段(baseURL/key)强制以控制面为准
9470
+ ...prev?.model ? { model: prev.model } : {},
9471
+ ...prev?.models?.length ? { models: prev.models } : {},
9472
+ ...prev?.reasoningEffort != null ? { reasoningEffort: prev.reasoningEffort } : {},
9473
+ ...prev?.name ? { name: prev.name } : {}
9474
+ };
9475
+ store.saveNodeLlm({
9476
+ providers: [merged, ...others],
9477
+ // 之前没有默认且用户也没指定时,让内置模型成为默认——「开箱即用」
9478
+ defaultProviderId: cfg.defaultProviderId ?? BUILTIN_PROVIDER_ID
9479
+ });
9480
+ adoptBuiltinModelForTenants(store, merged.model);
9481
+ }
9482
+ var LEGACY_DEFAULT_MODELS = /* @__PURE__ */ new Set([FALLBACK_MODEL, "deepseek-v4-flash"]);
9483
+ function adoptBuiltinModelForTenants(store, model) {
9484
+ if (!model) return;
9485
+ for (const id of store.listTenants()) {
9486
+ const tenant = store.loadTenant(id);
9487
+ if (!tenant || !LEGACY_DEFAULT_MODELS.has(tenant.model)) continue;
9488
+ if (tenant.provider?.apiKey) continue;
9489
+ tenant.model = model;
9490
+ store.saveTenant(tenant);
9491
+ }
9492
+ }
9493
+ async function ensureBuiltinProvider(store, opts = {}) {
9494
+ const cred = store.loadFleetCredential();
9495
+ if (!cred?.fleetToken || !cred.fleetSecret) {
9496
+ return { status: "disabled", reason: "\u8282\u70B9\u672A\u63A5\u5165 Hub" };
9497
+ }
9498
+ const hub = hubBase(cred);
9499
+ if (!hub) return { status: "disabled", reason: "\u7F3A\u5C11 Hub \u5730\u5740" };
9500
+ const local = store.loadNodeLlm().providers.find((p) => p.id === BUILTIN_PROVIDER_ID);
9501
+ if (local && !opts.force) {
9502
+ adoptBuiltinModelForTenants(store, local.model);
9503
+ return { status: "ready", provider: local, refreshed: false };
9504
+ }
9505
+ const cached3 = cred.builtinLlm ? asProvider(cred.builtinLlm) : null;
9506
+ let res;
9507
+ try {
9508
+ res = await fetch(`${hub}/api/hub/node/builtin-llm`, {
9509
+ headers: {
9510
+ Authorization: `Bearer ${cred.fleetToken}`,
9511
+ "X-Fleet-Secret": cred.fleetSecret,
9512
+ "X-Node-Id": cred.nodeId
9513
+ },
9514
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
9515
+ });
9516
+ } catch (e) {
9517
+ if (cached3) {
9518
+ const provider2 = await enrichModels(cached3);
9519
+ saveBuiltin(store, provider2);
9520
+ return { status: "ready", provider: provider2, refreshed: true };
9521
+ }
9522
+ return { status: "error", reason: `\u8FDE\u63A5 Hub \u5931\u8D25\uFF1A${e.message}` };
9523
+ }
9524
+ let body = null;
9525
+ try {
9526
+ body = await res.json();
9527
+ } catch {
9528
+ }
9529
+ if (!res.ok) {
9530
+ const msg = body?.error?.message ?? `HTTP ${res.status}`;
9531
+ if (res.status === 401 || res.status === 403) {
9532
+ return { status: "unavailable", reason: msg };
9533
+ }
9534
+ if (cached3) {
9535
+ const provider2 = await enrichModels(cached3);
9536
+ saveBuiltin(store, provider2);
9537
+ return { status: "ready", provider: provider2, refreshed: true };
9538
+ }
9539
+ return { status: "error", reason: msg };
9540
+ }
9541
+ const grant = body?.data?.builtinLlm;
9542
+ if (!grant) {
9543
+ const why = String(body?.data?.reason ?? "").trim();
9544
+ return { status: "unavailable", reason: why || "\u63A7\u5236\u9762\u672A\u4E0B\u53D1\u5185\u7F6E\u6A21\u578B" };
9545
+ }
9546
+ const parsed = asProvider(grant);
9547
+ if (!parsed) return { status: "unavailable", reason: "\u5185\u7F6E\u6A21\u578B\u914D\u7F6E\u4E0D\u5B8C\u6574" };
9548
+ const provider = await enrichModels(parsed);
9549
+ saveBuiltin(store, provider);
9550
+ return { status: "ready", provider, refreshed: true };
9551
+ }
9552
+ async function fetchLlmBalance(store) {
9553
+ const cred = store.loadFleetCredential();
9554
+ const hub = cred ? hubBase(cred) : "";
9555
+ if (!cred?.fleetToken || !cred.fleetSecret || !hub) {
9556
+ return { available: null, currency: "CNY", reason: "\u8282\u70B9\u672A\u63A5\u5165 Hub" };
9557
+ }
9558
+ try {
9559
+ const res = await fetch(`${hub}/api/hub/node/balance`, {
9560
+ headers: {
9561
+ Authorization: `Bearer ${cred.fleetToken}`,
9562
+ "X-Fleet-Secret": cred.fleetSecret,
9563
+ "X-Node-Id": cred.nodeId
9564
+ },
9565
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
9566
+ });
9567
+ const body = await res.json().catch(() => null);
9568
+ if (!res.ok) {
9569
+ return {
9570
+ available: null,
9571
+ currency: "CNY",
9572
+ reason: body?.data ? "" : `HTTP ${res.status}`
9573
+ };
9574
+ }
9575
+ const d = body?.data ?? {};
9576
+ const available = typeof d.available === "number" ? d.available : null;
9577
+ return {
9578
+ available,
9579
+ currency: typeof d.currency === "string" && d.currency ? d.currency : "CNY",
9580
+ ...typeof d.reserved === "number" ? { reserved: d.reserved } : {},
9581
+ // Hub 没配 HUB_PUBLIC_URL 时退回用接入时的 hubUrl
9582
+ topupUrl: typeof d.topupUrl === "string" && d.topupUrl ? d.topupUrl : `${hub}/topup`,
9583
+ ...available === null && typeof d.reason === "string" ? { reason: d.reason } : {}
9584
+ };
9585
+ } catch (e) {
9586
+ return {
9587
+ available: null,
9588
+ currency: "CNY",
9589
+ reason: `\u8FDE\u63A5 Hub \u5931\u8D25\uFF1A${e.message}`
9590
+ };
9591
+ }
9592
+ }
9593
+ var AUTH_RETRY_THROTTLE_MS = 6e4;
9594
+ var lastAuthRetryAt = 0;
9595
+ function healBuiltinOnAuthFailure(store, message) {
9596
+ if (!/401|unauthorized|invalid[_\s]*api[_\s]*key|api[\s_-]*key\s*(?:无效|invalid)/i.test(message)) return;
9597
+ if (!store.loadFleetCredential()) return;
9598
+ if (!store.loadNodeLlm().providers.some((p) => p.id === BUILTIN_PROVIDER_ID)) return;
9599
+ if (Date.now() - lastAuthRetryAt < AUTH_RETRY_THROTTLE_MS) return;
9600
+ lastAuthRetryAt = Date.now();
9601
+ void ensureBuiltinProvider(store, { force: true }).catch(() => {
9602
+ });
9603
+ }
9604
+ var lastFailureAt = 0;
9605
+ function scheduleBuiltinProvider(store, log = () => {
9606
+ }) {
9607
+ if (Date.now() - lastFailureAt < RETRY_AFTER_MS) return;
9608
+ void ensureBuiltinProvider(store).then((r) => {
9609
+ if (r.status === "ready") {
9610
+ if (r.refreshed) log(`[builtin-llm] \u5DF2\u4E0B\u53D1\u5185\u7F6E\u6A21\u578B\uFF1A${r.provider.name}\uFF08${r.provider.model}\uFF09`);
9611
+ return;
9612
+ }
9613
+ if (r.status === "disabled") return;
9614
+ lastFailureAt = Date.now();
9615
+ if (r.status === "unavailable") log(`[builtin-llm] \u6682\u65E0\u5185\u7F6E\u6A21\u578B\uFF1A${r.reason}`);
9616
+ else log(`[builtin-llm] \u62C9\u53D6\u5185\u7F6E\u6A21\u578B\u5931\u8D25\uFF1A${r.reason}`);
9617
+ }).catch(() => {
9618
+ lastFailureAt = Date.now();
9619
+ });
9620
+ }
9621
+
9371
9622
  // src/server/usage.ts
9372
9623
  init_esm_shims();
9373
9624
  import * as fs18 from "fs";
@@ -11429,6 +11680,7 @@ data: ${JSON.stringify(entry.data)}
11429
11680
  this._setStatus(rt, "error");
11430
11681
  this._persist(rt);
11431
11682
  this._emitNotify(rt, "error", this._turnEndData(rt, event.data, {}));
11683
+ healBuiltinOnAuthFailure(this.store, String(event.data["error"] ?? ""));
11432
11684
  break;
11433
11685
  case "interrupted" /* INTERRUPTED */:
11434
11686
  this._setStatus(rt, "interrupted");
@@ -11859,7 +12111,6 @@ import * as path26 from "path";
11859
12111
  // src/server/projects.ts
11860
12112
  init_esm_shims();
11861
12113
  import * as path22 from "path";
11862
- var DEFAULT_MODEL = () => process.env.OPENAI_MODEL || process.env.MODEL || "gpt-4o";
11863
12114
  function toProjectSummary(store, tenant) {
11864
12115
  return {
11865
12116
  id: tenant.id,
@@ -11999,7 +12250,7 @@ function createProject(store, input) {
11999
12250
  id,
12000
12251
  name: explicitName || defaultName,
12001
12252
  apiKey: generateApiKey(),
12002
- model: typeof raw.model === "string" && raw.model.trim() ? raw.model.trim() : DEFAULT_MODEL(),
12253
+ model: typeof raw.model === "string" && raw.model.trim() ? raw.model.trim() : defaultTenantModel(store),
12003
12254
  provider: {
12004
12255
  apiKey: typeof raw.providerApiKey === "string" && raw.providerApiKey.trim() ? raw.providerApiKey.trim() : void 0,
12005
12256
  baseURL: typeof raw.baseURL === "string" && raw.baseURL.trim() ? raw.baseURL.trim() : void 0
@@ -12024,7 +12275,7 @@ function buildNodeInfo(store, opts) {
12024
12275
  nodeId: opts.nodeId ?? ensureNodeId(store),
12025
12276
  platform: process.platform,
12026
12277
  mode: process.env.NODE_MODE ?? "standalone",
12027
- version: "0.2.16",
12278
+ version: "0.2.20",
12028
12279
  admin: opts.admin,
12029
12280
  projects: opts.projects
12030
12281
  };
@@ -12390,10 +12641,133 @@ function makeDirIn(parentInput, name) {
12390
12641
  init_esm_shims();
12391
12642
  import * as fs20 from "fs";
12392
12643
  import * as path24 from "path";
12644
+
12645
+ // src/server/zipArchive.ts
12646
+ init_esm_shims();
12647
+ import * as zlib from "zlib";
12648
+ var MAX_ZIP_ENTRIES = 2e4;
12649
+ var LOCAL_SIG = 67324752;
12650
+ var CENTRAL_SIG = 33639248;
12651
+ var EOCD_SIG = 101010256;
12652
+ var FLAG_UTF8 = 2048;
12653
+ var METHOD_STORE = 0;
12654
+ var METHOD_DEFLATE = 8;
12655
+ var CRC_TABLE = (() => {
12656
+ const table = new Int32Array(256);
12657
+ for (let n = 0; n < 256; n++) {
12658
+ let c = n;
12659
+ for (let k = 0; k < 8; k++) {
12660
+ c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
12661
+ }
12662
+ table[n] = c;
12663
+ }
12664
+ return table;
12665
+ })();
12666
+ function crc32(buf) {
12667
+ let c = -1;
12668
+ for (let i = 0; i < buf.length; i++) {
12669
+ c = CRC_TABLE[(c ^ buf[i]) & 255] ^ c >>> 8;
12670
+ }
12671
+ return (c ^ -1) >>> 0;
12672
+ }
12673
+ function dosDateTime(d) {
12674
+ const year = Math.max(1980, d.getFullYear());
12675
+ return {
12676
+ time: d.getHours() << 11 | d.getMinutes() << 5 | d.getSeconds() >> 1,
12677
+ date: year - 1980 << 9 | d.getMonth() + 1 << 5 | d.getDate()
12678
+ };
12679
+ }
12680
+ function buildZip(entries) {
12681
+ if (entries.length > MAX_ZIP_ENTRIES) {
12682
+ throw new Error(`\u5F52\u6863\u6761\u76EE\u8FC7\u591A\uFF08${entries.length} > ${MAX_ZIP_ENTRIES}\uFF09`);
12683
+ }
12684
+ const chunks = [];
12685
+ const central = [];
12686
+ let offset = 0;
12687
+ for (const e of entries) {
12688
+ const name = Buffer.from(e.name, "utf-8");
12689
+ const isDir = e.name.endsWith("/");
12690
+ const raw = isDir ? Buffer.alloc(0) : e.data ?? Buffer.alloc(0);
12691
+ let method = METHOD_STORE;
12692
+ let body = raw;
12693
+ if (!isDir && raw.length > 0) {
12694
+ const deflated = zlib.deflateRawSync(raw, { level: 6 });
12695
+ if (deflated.length < raw.length) {
12696
+ method = METHOD_DEFLATE;
12697
+ body = deflated;
12698
+ }
12699
+ }
12700
+ const crc = isDir ? 0 : crc32(raw);
12701
+ const { time, date } = dosDateTime(e.mtime ?? /* @__PURE__ */ new Date());
12702
+ const local = Buffer.alloc(30);
12703
+ local.writeUInt32LE(LOCAL_SIG, 0);
12704
+ local.writeUInt16LE(20, 4);
12705
+ local.writeUInt16LE(FLAG_UTF8, 6);
12706
+ local.writeUInt16LE(method, 8);
12707
+ local.writeUInt16LE(time, 10);
12708
+ local.writeUInt16LE(date, 12);
12709
+ local.writeUInt32LE(crc, 14);
12710
+ local.writeUInt32LE(body.length, 18);
12711
+ local.writeUInt32LE(raw.length, 22);
12712
+ local.writeUInt16LE(name.length, 26);
12713
+ local.writeUInt16LE(0, 28);
12714
+ chunks.push(local, name);
12715
+ if (body.length > 0) chunks.push(body);
12716
+ central.push({
12717
+ name,
12718
+ method,
12719
+ crc,
12720
+ csize: body.length,
12721
+ usize: raw.length,
12722
+ offset,
12723
+ time,
12724
+ date,
12725
+ isDir
12726
+ });
12727
+ offset += local.length + name.length + body.length;
12728
+ }
12729
+ const cdStart = offset;
12730
+ for (const c of central) {
12731
+ const head = Buffer.alloc(46);
12732
+ head.writeUInt32LE(CENTRAL_SIG, 0);
12733
+ head.writeUInt16LE(20, 4);
12734
+ head.writeUInt16LE(20, 6);
12735
+ head.writeUInt16LE(FLAG_UTF8, 8);
12736
+ head.writeUInt16LE(c.method, 10);
12737
+ head.writeUInt16LE(c.time, 12);
12738
+ head.writeUInt16LE(c.date, 14);
12739
+ head.writeUInt32LE(c.crc, 16);
12740
+ head.writeUInt32LE(c.csize, 20);
12741
+ head.writeUInt32LE(c.usize, 24);
12742
+ head.writeUInt16LE(c.name.length, 28);
12743
+ head.writeUInt16LE(0, 30);
12744
+ head.writeUInt16LE(0, 32);
12745
+ head.writeUInt16LE(0, 34);
12746
+ head.writeUInt16LE(0, 36);
12747
+ head.writeUInt32LE(c.isDir ? 16 : 0, 38);
12748
+ head.writeUInt32LE(c.offset, 42);
12749
+ chunks.push(head, c.name);
12750
+ offset += head.length + c.name.length;
12751
+ }
12752
+ const eocd = Buffer.alloc(22);
12753
+ eocd.writeUInt32LE(EOCD_SIG, 0);
12754
+ eocd.writeUInt16LE(0, 4);
12755
+ eocd.writeUInt16LE(0, 6);
12756
+ eocd.writeUInt16LE(central.length, 8);
12757
+ eocd.writeUInt16LE(central.length, 10);
12758
+ eocd.writeUInt32LE(offset - cdStart, 12);
12759
+ eocd.writeUInt32LE(cdStart, 16);
12760
+ eocd.writeUInt16LE(0, 20);
12761
+ chunks.push(eocd);
12762
+ return Buffer.concat(chunks);
12763
+ }
12764
+
12765
+ // src/server/projectFiles.ts
12393
12766
  var MAX_FILE_BYTES = 2 * 1024 * 1024;
12394
12767
  var MAX_DOWNLOAD_BYTES = 16 * 1024 * 1024;
12395
12768
  var MAX_BINARY_BYTES = 2.5 * 1024 * 1024;
12396
12769
  var MAX_ENTRIES2 = 2e3;
12770
+ var MAX_ZIP_BYTES = 100 * 1024 * 1024;
12397
12771
  function toPosix(p) {
12398
12772
  return p.split(path24.sep).join("/");
12399
12773
  }
@@ -12669,6 +13043,134 @@ function deleteProjectPaths(workspaceRoot, relPaths) {
12669
13043
  }
12670
13044
  return out;
12671
13045
  }
13046
+ function formatBytes(n) {
13047
+ if (n >= 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(1)} MB`;
13048
+ if (n >= 1024) return `${(n / 1024).toFixed(1)} KB`;
13049
+ return `${n} B`;
13050
+ }
13051
+ function renameProjectPath(workspaceRoot, relPath, newName) {
13052
+ const base = projectRoot(workspaceRoot);
13053
+ const rel = (relPath ?? "").trim().replace(/^[/\\]+/, "");
13054
+ if (!rel) throw new Error("\u4E0D\u80FD\u91CD\u547D\u540D\u9879\u76EE\u6839\u76EE\u5F55");
13055
+ const abs = resolveWithin(base, rel);
13056
+ const name = (newName ?? "").trim();
13057
+ if (!name) throw new Error("\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
13058
+ if (name === "." || name === "..") throw new Error("\u540D\u79F0\u975E\u6CD5");
13059
+ if (/[/\\]/.test(name)) throw new Error("\u540D\u79F0\u4E0D\u80FD\u5305\u542B\u8DEF\u5F84\u5206\u9694\u7B26");
13060
+ if (!fs20.existsSync(abs)) throw new Error("\u6587\u4EF6\u4E0D\u5B58\u5728");
13061
+ const parent = path24.dirname(abs);
13062
+ const target = path24.join(parent, name);
13063
+ if (!isWithinRoot(base, target)) throw new Error("\u8DEF\u5F84\u8D8A\u754C\uFF08\u8D85\u51FA\u9879\u76EE\u5DE5\u4F5C\u533A\uFF09");
13064
+ if (fs20.existsSync(target)) {
13065
+ let same = false;
13066
+ try {
13067
+ const a = fs20.statSync(target);
13068
+ const b = fs20.statSync(abs);
13069
+ same = a.ino === b.ino && a.dev === b.dev;
13070
+ } catch {
13071
+ same = false;
13072
+ }
13073
+ if (!same) throw new Error(`\u5DF2\u5B58\u5728\u540C\u540D\u6587\u4EF6\u6216\u76EE\u5F55: ${name}`);
13074
+ }
13075
+ if (path24.resolve(target) === path24.resolve(abs)) {
13076
+ return { from: toPosix(path24.relative(base, abs)), path: toPosix(path24.relative(base, abs)) };
13077
+ }
13078
+ try {
13079
+ fs20.renameSync(abs, target);
13080
+ } catch (e) {
13081
+ throw new Error(`\u91CD\u547D\u540D\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
13082
+ }
13083
+ return { from: toPosix(path24.relative(base, abs)), path: toPosix(path24.relative(base, target)) };
13084
+ }
13085
+ function zipProjectPaths(workspaceRoot, relPaths, opts = {}) {
13086
+ const base = projectRoot(workspaceRoot);
13087
+ const maxBytes = opts.maxBytes && opts.maxBytes > 0 ? opts.maxBytes : MAX_ZIP_BYTES;
13088
+ const rels = (relPaths ?? []).map((x) => (x ?? "").trim().replace(/^[/\\]+/, ""));
13089
+ if (rels.length === 0) throw new Error("\u8BF7\u9009\u62E9\u8981\u538B\u7F29\u7684\u6587\u4EF6\u6216\u76EE\u5F55");
13090
+ if (rels.some((r) => !r)) throw new Error("\u4E0D\u80FD\u538B\u7F29\u9879\u76EE\u6839\u76EE\u5F55");
13091
+ const targets = rels.map((rel) => ({ rel, abs: resolveWithin(base, rel) }));
13092
+ const single = targets.length === 1 ? targets[0].abs : "";
13093
+ const items = [];
13094
+ let total = 0;
13095
+ const overLimit = () => new Error(
13096
+ `\u5171 ${formatBytes(total)}\uFF0C\u8D85\u8FC7 ${formatBytes(maxBytes)} \u4E0A\u9650\uFF0C\u4E0D\u652F\u6301\u538B\u7F29\u4E0B\u8F7D`
13097
+ );
13098
+ const walk = (absDir) => {
13099
+ let dirents;
13100
+ try {
13101
+ dirents = fs20.readdirSync(absDir, { withFileTypes: true });
13102
+ } catch {
13103
+ return 0;
13104
+ }
13105
+ dirents.sort((a, b) => a.name.localeCompare(b.name));
13106
+ let files = 0;
13107
+ for (const d of dirents) {
13108
+ if (d.isSymbolicLink()) continue;
13109
+ const child = path24.join(absDir, d.name);
13110
+ if (d.isDirectory()) {
13111
+ files += walk(child);
13112
+ continue;
13113
+ }
13114
+ if (!d.isFile()) continue;
13115
+ let st;
13116
+ try {
13117
+ st = fs20.statSync(child);
13118
+ } catch {
13119
+ continue;
13120
+ }
13121
+ total += st.size;
13122
+ if (total > maxBytes) throw overLimit();
13123
+ if (items.length >= MAX_ZIP_ENTRIES) {
13124
+ throw new Error(`\u5F52\u6863\u6761\u76EE\u8FC7\u591A\uFF08\u8D85\u8FC7 ${MAX_ZIP_ENTRIES} \u9879\uFF09\uFF0C\u8BF7\u7F29\u5C0F\u8303\u56F4\u540E\u518D\u8BD5`);
13125
+ }
13126
+ items.push({ abs: child, dir: false, size: st.size, mtime: st.mtime });
13127
+ files++;
13128
+ }
13129
+ if (files === 0) {
13130
+ let mtime = /* @__PURE__ */ new Date();
13131
+ try {
13132
+ mtime = fs20.statSync(absDir).mtime;
13133
+ } catch {
13134
+ }
13135
+ items.push({ abs: absDir, dir: true, size: 0, mtime });
13136
+ }
13137
+ return files;
13138
+ };
13139
+ for (const t of targets) {
13140
+ let st;
13141
+ try {
13142
+ st = fs20.statSync(t.abs);
13143
+ } catch {
13144
+ throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${t.rel}`);
13145
+ }
13146
+ if (st.isDirectory()) {
13147
+ walk(t.abs);
13148
+ continue;
13149
+ }
13150
+ total += st.size;
13151
+ if (total > maxBytes) throw overLimit();
13152
+ items.push({ abs: t.abs, dir: false, size: st.size, mtime: st.mtime });
13153
+ }
13154
+ const nameOf = (abs) => {
13155
+ const rel = toPosix(path24.relative(single || base, abs));
13156
+ return rel || path24.posix.basename(toPosix(abs));
13157
+ };
13158
+ const zipEntries = items.map((it) => {
13159
+ const name2 = nameOf(it.abs);
13160
+ if (it.dir) return { name: `${name2}/`, mtime: it.mtime };
13161
+ return { name: name2, data: fs20.readFileSync(it.abs), mtime: it.mtime };
13162
+ });
13163
+ const buf = buildZip(zipEntries);
13164
+ const first = targets[0].rel;
13165
+ const name = single ? `${path24.posix.basename(toPosix(first))}.zip` : "archive.zip";
13166
+ return {
13167
+ name,
13168
+ size: buf.length,
13169
+ total,
13170
+ entries: zipEntries.length,
13171
+ base64: buf.toString("base64")
13172
+ };
13173
+ }
12672
13174
 
12673
13175
  // src/server/projectOps.ts
12674
13176
  init_esm_shims();
@@ -12998,6 +13500,7 @@ function projectStats(manager, tenantId) {
12998
13500
 
12999
13501
  // src/server/nodeHost.ts
13000
13502
  init_esm_shims();
13503
+ import { spawnSync as spawnSync2 } from "child_process";
13001
13504
  import * as fs21 from "fs";
13002
13505
  import * as os6 from "os";
13003
13506
  function diskInfo(pathStr) {
@@ -13012,6 +13515,64 @@ function diskInfo(pathStr) {
13012
13515
  return null;
13013
13516
  }
13014
13517
  }
13518
+ var PSEUDO_FS = /^(devfs|tmpfs|devtmpfs|proc|procfs|sysfs|none|udev|shm|mqueue|devpts|hugetlbfs|debugfs|tracefs|securityfs|pstore|bpf|configfs|fusectl|nsfs|autofs|efivarfs|ramfs|map)$/;
13519
+ function isInternalMount(mount) {
13520
+ if (mount === "/") return false;
13521
+ const prefixes = ["/proc", "/sys", "/run", "/snap"];
13522
+ if (process.platform === "darwin") prefixes.push("/System/Volumes");
13523
+ return prefixes.some((p) => mount === p || mount.startsWith(`${p}/`));
13524
+ }
13525
+ function readOnlyVolumes() {
13526
+ const out = /* @__PURE__ */ new Set();
13527
+ const res = spawnSync2("mount", [], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
13528
+ if (res.error || res.status !== 0 || !res.stdout) return out;
13529
+ for (const line of res.stdout.split("\n")) {
13530
+ const m = line.match(/^.+? on (.+?) \(([^)]*)\)$/);
13531
+ if (!m || m[1] === "/") continue;
13532
+ if (m[2].split(",").some((opt) => opt.trim() === "read-only")) out.add(m[1]);
13533
+ }
13534
+ return out;
13535
+ }
13536
+ function diskList(workspacePath) {
13537
+ const out = [];
13538
+ const seen = /* @__PURE__ */ new Set();
13539
+ const push = (path37, total, free) => {
13540
+ if (!path37 || seen.has(path37) || !Number.isFinite(total) || total <= 0) return;
13541
+ seen.add(path37);
13542
+ out.push({ path: path37, total, free });
13543
+ };
13544
+ if (process.platform === "win32") {
13545
+ for (const letter of "CDEFGHIJKLMNOPQRSTUVWXYZ") {
13546
+ const root = `${letter}:\\`;
13547
+ const d = diskInfo(root);
13548
+ if (d) push(root, d.total, d.free);
13549
+ }
13550
+ } else {
13551
+ const readonly = process.platform === "darwin" ? readOnlyVolumes() : /* @__PURE__ */ new Set();
13552
+ const res = spawnSync2("df", ["-kP"], {
13553
+ encoding: "utf-8",
13554
+ stdio: ["ignore", "pipe", "ignore"]
13555
+ });
13556
+ if (!res.error && res.status === 0 && res.stdout) {
13557
+ for (const line of res.stdout.split("\n").slice(1)) {
13558
+ const m = line.trim().match(/^(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s+(.+)$/);
13559
+ if (!m || PSEUDO_FS.test(m[1])) continue;
13560
+ if (/^\/dev\/loop\d+$/.test(m[1])) continue;
13561
+ if (isInternalMount(m[5]) || readonly.has(m[5])) continue;
13562
+ push(m[5], Number(m[2]) * 1024, Number(m[4]) * 1024);
13563
+ }
13564
+ }
13565
+ }
13566
+ const primary = diskInfo(workspacePath);
13567
+ if (primary) {
13568
+ const covered = out.some((d) => {
13569
+ const root = d.path.endsWith("/") || d.path.endsWith("\\") ? d.path : `${d.path}/`;
13570
+ return workspacePath === d.path || workspacePath.startsWith(root);
13571
+ });
13572
+ if (!covered) push(primary.path, primary.total, primary.free);
13573
+ }
13574
+ return out.sort((a, b) => b.total - a.total);
13575
+ }
13015
13576
  function networkInfo() {
13016
13577
  const out = [];
13017
13578
  const ifaces = os6.networkInterfaces();
@@ -13067,6 +13628,7 @@ function overview(store, manager) {
13067
13628
  function buildNodeHost(store, manager, diskPath) {
13068
13629
  const cpus2 = os6.cpus();
13069
13630
  const ov = overview(store, manager);
13631
+ const disks = diskList(diskPath || process.cwd());
13070
13632
  return {
13071
13633
  nodeId: store.loadNodeId() ?? "",
13072
13634
  hostname: os6.hostname(),
@@ -13074,7 +13636,7 @@ function buildNodeHost(store, manager, diskPath) {
13074
13636
  arch: process.arch,
13075
13637
  release: os6.release(),
13076
13638
  uptimeSec: Math.round(os6.uptime()),
13077
- version: "0.2.16",
13639
+ version: "0.2.20",
13078
13640
  mode: process.env.NODE_MODE ?? "standalone",
13079
13641
  cpu: {
13080
13642
  count: cpus2.length,
@@ -13084,6 +13646,7 @@ function buildNodeHost(store, manager, diskPath) {
13084
13646
  memory: { total: os6.totalmem(), free: os6.freemem() },
13085
13647
  network: networkInfo(),
13086
13648
  disk: diskInfo(diskPath || process.cwd()),
13649
+ disks,
13087
13650
  ...ov
13088
13651
  };
13089
13652
  }
@@ -14309,6 +14872,41 @@ function createApp(store, manager, opts = {}) {
14309
14872
  }
14310
14873
  }
14311
14874
  },
14875
+ {
14876
+ method: "POST",
14877
+ pattern: ["files", "rename"],
14878
+ // 重命名(只改同目录下的名字)
14879
+ fn: (_req, res, { tenant, body }) => {
14880
+ const raw = body ?? {};
14881
+ try {
14882
+ ok(
14883
+ res,
14884
+ renameProjectPath(
14885
+ tenant.workspaceRoot ?? null,
14886
+ String(raw.path ?? ""),
14887
+ String(raw.name ?? "")
14888
+ )
14889
+ );
14890
+ } catch (e) {
14891
+ throw new ApiError(400, "bad_request", e instanceof Error ? e.message : String(e));
14892
+ }
14893
+ }
14894
+ },
14895
+ {
14896
+ method: "POST",
14897
+ pattern: ["files", "zip"],
14898
+ // 压缩下载(多选一起打包;超上限由实现拒绝)
14899
+ fn: (_req, res, { tenant, body }) => {
14900
+ const raw = body ?? {};
14901
+ const paths = Array.isArray(raw.paths) ? raw.paths.map((x) => String(x)) : [];
14902
+ if (paths.length === 0) throw new ApiError(400, "bad_request", "paths \u4E0D\u80FD\u4E3A\u7A7A");
14903
+ try {
14904
+ ok(res, zipProjectPaths(tenant.workspaceRoot ?? null, paths));
14905
+ } catch (e) {
14906
+ throw new ApiError(400, "bad_request", e instanceof Error ? e.message : String(e));
14907
+ }
14908
+ }
14909
+ },
14312
14910
  // ---------- 项目(= 租户)管理:控制台多项目视图 ----------
14313
14911
  // 仅管理员密钥可用;项目级与会话级接口仍用项目自己的 tenant API Key
14314
14912
  // (或管理员密钥 + X-Project-Id 头),互不暴露明文密钥。
@@ -14367,6 +14965,29 @@ function createApp(store, manager, opts = {}) {
14367
14965
  ok(res, nodeLlmView(next));
14368
14966
  }
14369
14967
  },
14968
+ // 大模型账户余额(控制台「余额不足 → 查看余额并充值」用;由节点向 Hub 代查)
14969
+ {
14970
+ method: "GET",
14971
+ pattern: ["node", "llm", "balance"],
14972
+ adminOnly: true,
14973
+ fn: async (_req, res) => {
14974
+ ok(res, await fetchLlmBalance(store));
14975
+ }
14976
+ },
14977
+ // 重新拉取平台内置模型凭据(用户点了「刷新内置模型」,或 Key 在控制台被重置)
14978
+ {
14979
+ method: "POST",
14980
+ pattern: ["node", "llm", "builtin-refresh"],
14981
+ adminOnly: true,
14982
+ fn: async (_req, res) => {
14983
+ const r = await ensureBuiltinProvider(store, { force: true });
14984
+ if (r.status !== "ready") {
14985
+ ok(res, { ok: false, reason: r.reason });
14986
+ return;
14987
+ }
14988
+ ok(res, { ok: true, view: nodeLlmView(store.loadNodeLlm()) });
14989
+ }
14990
+ },
14370
14991
  // ---------- 用量(账本 + 汇总 + 价格) ----------
14371
14992
  {
14372
14993
  method: "GET",
@@ -14793,7 +15414,7 @@ function createApp(store, manager, opts = {}) {
14793
15414
  json(res, 200, {
14794
15415
  status: "ok",
14795
15416
  // 版本让运维/探针能确认线上跑的是哪个构建(更新与回滚据此判断)
14796
- version: "0.2.16",
15417
+ version: "0.2.20",
14797
15418
  features: { mcp: hasServerMcpConfig() }
14798
15419
  });
14799
15420
  return;
@@ -15133,6 +15754,22 @@ async function dispatchFleetCommand(deps, env3) {
15133
15754
  store.saveNodeLlm(next);
15134
15755
  return ok2(nodeLlmView(next));
15135
15756
  }
15757
+ /*
15758
+ * 重新拉取平台内置模型(志前 API)。
15759
+ *
15760
+ * 用途:用户在控制台点了「刷新」、或内置模型的 Key 在控制台被重置后需要换新。
15761
+ * 走写链串行——它会改写 connections.json,不能与 update_node_llm 并发。
15762
+ */
15763
+ // 余额查询:只读、幂等,可进只读并发池
15764
+ case "get_llm_balance":
15765
+ return ok2(await fetchLlmBalance(store));
15766
+ case "refresh_builtin_llm": {
15767
+ const r = await ensureBuiltinProvider(store, { force: true });
15768
+ if (r.status !== "ready") {
15769
+ return ok2({ ok: false, reason: r.reason });
15770
+ }
15771
+ return ok2({ ok: true, view: nodeLlmView(store.loadNodeLlm()) });
15772
+ }
15136
15773
  case "get_usage_summary": {
15137
15774
  const ledger = manager.usageLedger();
15138
15775
  if (!ledger) return ok2({ total: null, groups: [] });
@@ -15317,6 +15954,24 @@ async function dispatchFleetCommand(deps, env3) {
15317
15954
  const t = project();
15318
15955
  return ok2(searchProjectFiles(t.workspaceRoot ?? null, String(payload.query ?? "")));
15319
15956
  }
15957
+ case "rename_path": {
15958
+ const t = project();
15959
+ return ok2(
15960
+ renameProjectPath(
15961
+ t.workspaceRoot ?? null,
15962
+ String(payload.path ?? ""),
15963
+ String(payload.name ?? "")
15964
+ )
15965
+ );
15966
+ }
15967
+ case "zip_download": {
15968
+ const t = project();
15969
+ const raw = payload.paths;
15970
+ const paths = Array.isArray(raw) ? raw.map((x) => String(x)) : [];
15971
+ if (paths.length === 0) throw new ApiError(400, "bad_request", "paths \u4E0D\u80FD\u4E3A\u7A7A");
15972
+ const maxBytes = typeof payload.maxBytes === "number" ? payload.maxBytes : void 0;
15973
+ return ok2(zipProjectPaths(t.workspaceRoot ?? null, paths, { maxBytes }));
15974
+ }
15320
15975
  case "list_tools": {
15321
15976
  const t = project();
15322
15977
  return ok2(buildToolCatalog(t.workspaceRoot ?? process.cwd()));
@@ -15669,6 +16324,22 @@ function toWireAck(ack) {
15669
16324
  data: ack.data === void 0 ? void 0 : utf8Encode(JSON.stringify(ack.data))
15670
16325
  };
15671
16326
  }
16327
+ var ACK_JSON_MAX_BYTES = MAX_FRAME_BYTES - 64 * 1024;
16328
+ function mb(bytes) {
16329
+ return (bytes / (1024 * 1024)).toFixed(1);
16330
+ }
16331
+ function guardAckSize(ack, json2) {
16332
+ if (!ack.ok || ack.data === void 0) return ack;
16333
+ const bytes = Buffer.byteLength(json2, "utf8");
16334
+ if (bytes <= ACK_JSON_MAX_BYTES) return ack;
16335
+ return {
16336
+ reqId: ack.reqId,
16337
+ type: ack.type,
16338
+ ok: false,
16339
+ // 带上实际大小:用户据此能判断「差一点」还是「差很多」,而不是只看到「失败」
16340
+ error: `\u7ED3\u679C\u8FC7\u5927\uFF08${mb(bytes)} MB\uFF09\uFF0C\u8D85\u8FC7\u5355\u5E27\u4F20\u8F93\u4E0A\u9650\uFF08${mb(ACK_JSON_MAX_BYTES)} MB\uFF09\uFF0C\u8BF7\u7F29\u5C0F\u8303\u56F4\u540E\u91CD\u8BD5`
16341
+ };
16342
+ }
15672
16343
  function fromWireCommand(env3) {
15673
16344
  let payload = {};
15674
16345
  if (env3.payload && env3.payload.length > 0) {
@@ -16139,6 +16810,7 @@ var READONLY_CMD_TYPES = /* @__PURE__ */ new Set([
16139
16810
  "get_node_llm",
16140
16811
  "list_node_llm_models",
16141
16812
  "get_usage_summary",
16813
+ "get_llm_balance",
16142
16814
  "get_usage_records",
16143
16815
  "get_usage_pricing",
16144
16816
  "list_projects",
@@ -16563,7 +17235,7 @@ var FleetNodeBridge = class {
16563
17235
  label: this.opts.deps.store.loadNodeLabel() ?? void 0,
16564
17236
  platform: process.platform,
16565
17237
  mode: process.env.NODE_MODE ?? "standalone",
16566
- version: "0.2.16",
17238
+ version: "0.2.20",
16567
17239
  ip: primaryIp()
16568
17240
  };
16569
17241
  }
@@ -16661,7 +17333,7 @@ var FleetNodeBridge = class {
16661
17333
  label: this.opts.deps.store.loadNodeLabel() ?? void 0,
16662
17334
  platform: process.platform,
16663
17335
  mode: process.env.NODE_MODE ?? "standalone",
16664
- version: "0.2.16",
17336
+ version: "0.2.20",
16665
17337
  ip: primaryIp(),
16666
17338
  projects,
16667
17339
  totals,
@@ -16823,7 +17495,13 @@ var FleetNodeBridge = class {
16823
17495
  const stamped = { ...ack, reqId: env3.reqId, type: env3.type };
16824
17496
  const json2 = JSON.stringify(stamped);
16825
17497
  const __t1 = dbgNow();
16826
- this._send({ kind: "ack", clientId: env3.clientId, ack: stamped });
17498
+ const payload = guardAckSize(stamped, json2);
17499
+ if (payload !== stamped) {
17500
+ console.warn(
17501
+ `[fleet] ${env3.type} \u56DE\u6267 ${(json2.length / 1024 / 1024).toFixed(1)}MB \u8D85\u8FC7\u5355\u5E27\u4E0A\u9650\uFF0C\u5DF2\u964D\u7EA7\u4E3A\u9519\u8BEF\u56DE\u6267`
17502
+ );
17503
+ }
17504
+ this._send({ kind: "ack", clientId: env3.clientId, ack: payload });
16827
17505
  dbgMs(
16828
17506
  "publishAck",
16829
17507
  __t0,
@@ -18147,7 +18825,7 @@ import { createHash as createHash3 } from "crypto";
18147
18825
  import * as fs24 from "fs";
18148
18826
  import { Readable as Readable2 } from "stream";
18149
18827
  import { pipeline as pipeline2 } from "stream/promises";
18150
- import { spawnSync as spawnSync2 } from "child_process";
18828
+ import { spawnSync as spawnSync3 } from "child_process";
18151
18829
  import * as path29 from "path";
18152
18830
 
18153
18831
  // src/update/manifest.ts
@@ -18374,7 +19052,7 @@ async function downloadBinary(bin, destPath, log = () => {
18374
19052
  }
18375
19053
  function selfCheck(binPath, expectedVersion) {
18376
19054
  try {
18377
- const r = spawnSync2(binPath, ["--version"], {
19055
+ const r = spawnSync3(binPath, ["--version"], {
18378
19056
  timeout: 2e4,
18379
19057
  encoding: "utf-8",
18380
19058
  env: { ...process.env, ZQAGENT_AUTO_UPDATE: "0" }
@@ -18396,10 +19074,10 @@ init_esm_shims();
18396
19074
  import * as fs25 from "fs";
18397
19075
  import * as os9 from "os";
18398
19076
  import * as path30 from "path";
18399
- import { spawnSync as spawnSync3 } from "child_process";
19077
+ import { spawnSync as spawnSync4 } from "child_process";
18400
19078
  import { createRequire as createRequire3 } from "module";
18401
19079
  function currentVersion() {
18402
- const injected = "0.2.16"?.trim();
19080
+ const injected = "0.2.20"?.trim();
18403
19081
  if (injected) return injected;
18404
19082
  try {
18405
19083
  const req2 = createRequire3(import.meta.url);
@@ -18479,7 +19157,7 @@ function detectServiceManager() {
18479
19157
  }
18480
19158
  function detectLaunchd() {
18481
19159
  try {
18482
- const res = spawnSync3(
19160
+ const res = spawnSync4(
18483
19161
  "launchctl",
18484
19162
  ["print", `${launchdDomain()}/${launchdLabel()}`],
18485
19163
  { encoding: "utf8", timeout: 3e3 }
@@ -18512,7 +19190,7 @@ var cachedServiceEnv = null;
18512
19190
  function readServiceEnvironment() {
18513
19191
  const serviceName = process.env.ZQAGENT_SERVICE_NAME?.trim() || DEFAULT_SERVICE_NAME;
18514
19192
  try {
18515
- const res = spawnSync3(
19193
+ const res = spawnSync4(
18516
19194
  "systemctl",
18517
19195
  ["show", serviceName, "--property=Environment", "--value", "--no-pager"],
18518
19196
  { encoding: "utf8", timeout: 3e3 }
@@ -19125,7 +19803,9 @@ function seedDemoTenant(store) {
19125
19803
  // 未显式指定时随机生成:固定默认密钥一旦随 HOST=0.0.0.0 暴露到公网,等于公开凭据。
19126
19804
  // 只在首次创建时生成并落盘,后续启动沿用磁盘上的值(启动日志会打印完整密钥)。
19127
19805
  apiKey: env2("TENANT_API_KEY") ?? generateApiKey(),
19128
- model: env2("MODEL") ?? env2("OPENAI_MODEL") ?? "deepseek-v4-flash",
19806
+ // 不再硬编码具体厂商型号:志前 API 只认 deepseek-flash,写死 deepseek-v4-flash
19807
+ // 会让新装节点的第一次请求 404。默认值统一走 defaultTenantModel(见 nodeLlm.ts)。
19808
+ model: defaultTenantModel(store),
19129
19809
  provider: {
19130
19810
  apiKey: env2("OPENAI_API_KEY"),
19131
19811
  baseURL: env2("OPENAI_BASE_URL")
@@ -19265,6 +19945,8 @@ function startServer(opts = {}) {
19265
19945
  const store = new Store(dataDir);
19266
19946
  const demo = seedDemoTenant(store);
19267
19947
  syncTenantModels(store);
19948
+ const nodeModel = nodeDefaultProvider(store)?.model;
19949
+ if (nodeModel) adoptBuiltinModelForTenants(store, nodeModel);
19268
19950
  const usageLedger = new UsageLedger(dataDir);
19269
19951
  usageLedger.init();
19270
19952
  const manager = new SessionManager(store, void 0, { usage: usageLedger });
@@ -19408,8 +20090,12 @@ function startServer(opts = {}) {
19408
20090
  if (initialCred) {
19409
20091
  connectFleet(initialCred);
19410
20092
  } else if (opts.autoEnroll) {
19411
- void fleetController.enroll(opts.autoEnroll).then((st) => console.log(`[fleet] \u5DF2\u81EA\u52A8\u63A5\u5165\uFF0C\u8282\u70B9 ${st.nodeId}\uFF08${st.gatewayUrl}\uFF09`)).catch((e) => console.warn(`[fleet] \u81EA\u52A8\u63A5\u5165\u5931\u8D25\uFF08\u53EF\u7A0D\u540E /link \u91CD\u8BD5\uFF09: ${e instanceof Error ? e.message : e}`));
20093
+ void fleetController.enroll(opts.autoEnroll).then((st) => {
20094
+ console.log(`[fleet] \u5DF2\u81EA\u52A8\u63A5\u5165\uFF0C\u8282\u70B9 ${st.nodeId}\uFF08${st.gatewayUrl}\uFF09`);
20095
+ scheduleBuiltinProvider(store, (m) => console.log(m));
20096
+ }).catch((e) => console.warn(`[fleet] \u81EA\u52A8\u63A5\u5165\u5931\u8D25\uFF08\u53EF\u7A0D\u540E /link \u91CD\u8BD5\uFF09: ${e instanceof Error ? e.message : e}`));
19412
20097
  }
20098
+ if (initialCred) scheduleBuiltinProvider(store, (m) => console.log(m));
19413
20099
  const pairingCodes = new PairingCodeStore();
19414
20100
  const qq = new QqManager({
19415
20101
  store,
@@ -19749,7 +20435,7 @@ import * as readline3 from "readline";
19749
20435
 
19750
20436
  // src/cli/serve-instances.ts
19751
20437
  init_esm_shims();
19752
- import { spawnSync as spawnSync4 } from "child_process";
20438
+ import { spawnSync as spawnSync5 } from "child_process";
19753
20439
  import * as fs30 from "fs";
19754
20440
  import * as os10 from "os";
19755
20441
  import * as path36 from "path";