@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.
package/dist/cli/index.js CHANGED
@@ -730,15 +730,27 @@ function networkErrorHints(message, errorType = "") {
730
730
  hints.push("\u2464 IPv6 \u6296\u52A8\u53EF\u8BD5\uFF1ANODE_OPTIONS=--dns-result-order=ipv4first");
731
731
  return hints;
732
732
  }
733
+ function authErrorHints(message, code = "") {
734
+ const text = `${message} ${code}`;
735
+ if (!/401|403|unauthorized|forbidden|invalid[_\s]*api[_\s]*key|api[\s_-]*key\s*(?:无效|invalid)|管理令牌无效/i.test(text)) {
736
+ return [];
737
+ }
738
+ return [
739
+ "\u51ED\u636E\u65E0\u6548\uFF1A\u82E5\u7528\u7684\u662F\u300C\u5FD7\u524D\u5927\u6A21\u578B\uFF08\u5185\u7F6E\uFF09\u300D\uFF0C\u5728\u8282\u70B9\u8BBE\u7F6E\u7684\u6A21\u578B\u9875\u70B9\u300C\u5237\u65B0\u5185\u7F6E\u6A21\u578B\u300D\u91CD\u65B0\u9886\u53D6 Key\uFF08\u5728\u63A7\u5236\u53F0\u91CD\u7F6E\u8FC7 Key \u540E\u9700\u8981\u8FD9\u4E00\u6B65\uFF09\uFF1B\u82E5\u662F\u81EA\u586B\u7684\u4F9B\u5E94\u5546\uFF0C\u8BF7\u68C0\u67E5 Key \u662F\u5426\u6B63\u786E\u6216\u5DF2\u8FC7\u671F"
740
+ ];
741
+ }
733
742
  function paramErrorHints(message, code = "") {
734
743
  const text = `${message} ${code}`;
735
744
  const hints = [];
736
- if (/not[_\s]*(activated|enabled|purchased)|arrearage|quota|subscription|insufficient|未开通|欠费|额度/i.test(
745
+ if (/not[_\s]*(activated|enabled|purchased)|arrearage|quota|subscription|insufficient|未开通|欠费|额度|余额不足/i.test(
737
746
  text
738
747
  )) {
739
748
  hints.push(
740
749
  "\u8BE5\u6A21\u578B\u5728\u6B64 **API Key/\u8D26\u53F7** \u4E0B\u672A\u5F00\u901A\uFF08\u6216\u8D26\u53F7\u6B20\u8D39\u3001\u989D\u5EA6\u7528\u5C3D\uFF09\uFF1A\u5230\u7F51\u5173\u63A7\u5236\u53F0\u5F00\u901A\u8BE5\u6A21\u578B\uFF0C\u6216\u6362\u4E00\u4E2A\u5DF2\u5F00\u901A\u7684\u6A21\u578B"
741
750
  );
751
+ hints.push(
752
+ "\u5982\u679C\u4F60\u7528\u7684\u662F\u300C\u5FD7\u524D\u5927\u6A21\u578B\uFF08\u5185\u7F6E\uFF09\u300D\uFF1A\u5230 https://hub.cd-zq.com \u67E5\u770B\u4F59\u989D\u5E76\u5145\u503C\uFF1B\u4E5F\u53EF\u4EE5\u5148\u5207\u6362\u5230\u4F60\u81EA\u5DF1\u914D\u7F6E\u7684\u5176\u5B83\u6A21\u578B\u7EE7\u7EED\uFF08\u8282\u70B9\u8BBE\u7F6E \u2192 \u6A21\u578B \u2192 \u8BBE\u4E3A\u9ED8\u8BA4\uFF09"
753
+ );
742
754
  }
743
755
  if (/invalid[_\s]*parameter|invalidparameter|required[_\s]+parameter|missing[_\s]+parameter|invalid[_\s]*model|model[_\s]*not[_\s]*found|not[_\s]*support|unsupported/i.test(
744
756
  text
@@ -4981,6 +4993,9 @@ var ConsoleHook = class {
4981
4993
  for (const hint of networkErrorHints(error2, errorType)) {
4982
4994
  cliLog(` \u{1F4A1} ${hint}`);
4983
4995
  }
4996
+ for (const hint of authErrorHints(error2, errorType)) {
4997
+ cliLog(` \u{1F4A1} ${hint}`);
4998
+ }
4984
4999
  for (const hint of paramErrorHints(error2)) {
4985
5000
  cliLog(` \u{1F4A1} ${hint}`);
4986
5001
  }
@@ -9974,6 +9989,15 @@ function commonLegacyFiles() {
9974
9989
  return [path18.join(stateDir(), "data", "node-llm.json")];
9975
9990
  }
9976
9991
 
9992
+ // src/server/llmIds.ts
9993
+ init_esm_shims();
9994
+ var GLOBAL_PROVIDER_ID = "__env__";
9995
+ var BUILTIN_PROVIDER_ID = "__zqai__";
9996
+ function isBuiltinProvider(id) {
9997
+ return id === BUILTIN_PROVIDER_ID;
9998
+ }
9999
+ var FALLBACK_MODEL = "gpt-4o";
10000
+
9977
10001
  // src/cli/connection.ts
9978
10002
  var PROVIDERS = Object.fromEntries(
9979
10003
  LLM_PRESETS.map((p) => [
@@ -10088,6 +10112,7 @@ function useConnection(id) {
10088
10112
  return true;
10089
10113
  }
10090
10114
  function removeConnection(id) {
10115
+ if (id === BUILTIN_PROVIDER_ID) return false;
10091
10116
  const f = read();
10092
10117
  const next = f.providers.filter((p) => p.id !== id);
10093
10118
  if (next.length === f.providers.length) return false;
@@ -10371,7 +10396,7 @@ var GLYPHS = {
10371
10396
  T: ["\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588", " \u2588\u2588 ", " \u2588\u2588 ", " \u2588\u2588 ", " \u2588\u2588 ", " "]
10372
10397
  };
10373
10398
  var GLYPH_ROWS = 6;
10374
- var GLYPH_COLS = 9;
10399
+ var GLYPH_COLS = 11;
10375
10400
  var GRADIENT = [
10376
10401
  [34, 211, 238],
10377
10402
  [99, 102, 241],
@@ -10977,7 +11002,23 @@ var Store = class {
10977
11002
  legacyFiles: [path20.join(this.root, "node-llm.json")]
10978
11003
  });
10979
11004
  const old = new Map(cur.providers.map((p) => [p.id, p]));
10980
- const providers = cfg.providers.map((p) => {
11005
+ const src = [...cfg.providers];
11006
+ const prevBuiltin = cur.providers.find((p) => p.id === BUILTIN_PROVIDER_ID);
11007
+ if (prevBuiltin && !src.some((p) => p.id === BUILTIN_PROVIDER_ID)) {
11008
+ src.unshift({
11009
+ id: prevBuiltin.id,
11010
+ name: prevBuiltin.name,
11011
+ type: prevBuiltin.type,
11012
+ baseURL: prevBuiltin.baseURL,
11013
+ model: prevBuiltin.model,
11014
+ apiKey: decryptKey(prevBuiltin.apiKeyEnc, file) ?? void 0,
11015
+ reasoningEffort: prevBuiltin.reasoningEffort ?? null,
11016
+ ...prevBuiltin.models ? { models: prevBuiltin.models } : {},
11017
+ ...prevBuiltin.nonChat ? { nonChat: prevBuiltin.nonChat } : {},
11018
+ ...prevBuiltin.modelMeta ? { modelMeta: prevBuiltin.modelMeta } : {}
11019
+ });
11020
+ }
11021
+ const providers = src.map((p) => {
10981
11022
  const prev = old.get(p.id);
10982
11023
  const apiKeyEnc = p.apiKey ? encryptKey(p.apiKey, file) : prev?.apiKeyEnc ?? "";
10983
11024
  return {
@@ -11168,7 +11209,6 @@ function loadServerMcpConfigs(tenant) {
11168
11209
 
11169
11210
  // src/server/nodeLlm.ts
11170
11211
  init_esm_shims();
11171
- var GLOBAL_PROVIDER_ID = "__env__";
11172
11212
  function envProvider() {
11173
11213
  const apiKey = process.env.OPENAI_API_KEY;
11174
11214
  if (!apiKey) return null;
@@ -11211,19 +11251,26 @@ function nodeLlmView(cfg) {
11211
11251
  global: true
11212
11252
  }
11213
11253
  ] : [],
11214
- ...cfg.providers.map((p) => ({
11215
- id: p.id,
11216
- name: p.name,
11217
- type: p.type ?? "",
11218
- baseURL: p.baseURL,
11219
- model: p.model,
11220
- hasApiKey: Boolean(p.apiKey),
11221
- models: p.models ?? [],
11222
- nonChat: p.nonChat ?? [],
11223
- modelMeta: p.modelMeta ?? [],
11224
- reasoningEffort: p.reasoningEffort ?? null,
11225
- global: false
11226
- }))
11254
+ ...cfg.providers.map((p) => {
11255
+ const builtin = isBuiltinProvider(p.id);
11256
+ return {
11257
+ id: p.id,
11258
+ name: p.name,
11259
+ type: p.type ?? "",
11260
+ baseURL: p.baseURL,
11261
+ model: p.model,
11262
+ hasApiKey: Boolean(p.apiKey),
11263
+ models: p.models ?? [],
11264
+ nonChat: p.nonChat ?? [],
11265
+ modelMeta: p.modelMeta ?? [],
11266
+ reasoningEffort: p.reasoningEffort ?? null,
11267
+ global: false,
11268
+ /** 平台内置(接入 Hub 后自动下发,用户不用关心 Key) */
11269
+ builtin,
11270
+ /** 不可编辑的字段:控制台据此把 baseURL / key 显示为只读 */
11271
+ ...builtin ? { locked: ["baseURL", "apiKey"] } : {}
11272
+ };
11273
+ })
11227
11274
  ];
11228
11275
  return {
11229
11276
  defaultProviderId: cfg.defaultProviderId ?? null,
@@ -11236,27 +11283,42 @@ function applyNodeLlmPatch(cfg, raw) {
11236
11283
  const next = { providers: cfg.providers, defaultProviderId: cfg.defaultProviderId };
11237
11284
  if (Array.isArray(raw?.providers)) {
11238
11285
  const old = new Map(cfg.providers.map((p) => [p.id, p]));
11239
- next.providers = raw.providers.filter((x) => Boolean(x) && typeof x === "object").filter((x) => x.global !== true && String(x.id ?? "") !== GLOBAL_PROVIDER_ID).map((x) => {
11240
- const id = String(x.id ?? "").trim();
11241
- if (!id) throw new ApiError(400, "bad_request", "\u4F9B\u5E94\u5546 id \u4E0D\u80FD\u4E3A\u7A7A");
11242
- const prev = old.get(id);
11243
- const apiKey = String(x.apiKey ?? "").trim();
11244
- const models = normalizeModelList(x.models) ?? prev?.models;
11245
- const nonChat = normalizeModelList(x.nonChat) ?? prev?.nonChat;
11246
- const modelMeta = normalizeModelMeta(x.modelMeta) ?? prev?.modelMeta;
11247
- return {
11248
- id,
11249
- name: String(x.name ?? prev?.name ?? id).trim(),
11250
- type: x.type == null ? prev?.type : String(x.type),
11251
- baseURL: String(x.baseURL ?? prev?.baseURL ?? "").trim(),
11252
- model: String(x.model ?? prev?.model ?? "").trim(),
11253
- apiKey: apiKey || prev?.apiKey,
11254
- reasoningEffort: x.reasoningEffort === void 0 ? prev?.reasoningEffort ?? null : normReasoningEffort(x.reasoningEffort),
11255
- ...models ? { models } : {},
11256
- ...nonChat ? { nonChat } : {},
11257
- ...modelMeta ? { modelMeta } : {}
11258
- };
11259
- });
11286
+ const incoming = raw.providers.filter((x) => Boolean(x) && typeof x === "object").filter((x) => x.global !== true && String(x.id ?? "") !== GLOBAL_PROVIDER_ID);
11287
+ const prevBuiltin = cfg.providers.find((p) => isBuiltinProvider(p.id));
11288
+ const builtinPatch = incoming.find((x) => isBuiltinProvider(String(x.id ?? "")));
11289
+ const keptBuiltin = prevBuiltin ? [
11290
+ {
11291
+ ...prevBuiltin,
11292
+ ...builtinPatch?.model != null ? { model: String(builtinPatch.model).trim() || prevBuiltin.model } : {},
11293
+ ...normalizeModelList(builtinPatch?.models) ? { models: normalizeModelList(builtinPatch?.models) } : {},
11294
+ ...builtinPatch?.reasoningEffort !== void 0 ? { reasoningEffort: normReasoningEffort(builtinPatch.reasoningEffort) } : {},
11295
+ ...typeof builtinPatch?.name === "string" && builtinPatch.name.trim() ? { name: builtinPatch.name.trim() } : {}
11296
+ }
11297
+ ] : [];
11298
+ next.providers = [
11299
+ ...keptBuiltin,
11300
+ ...incoming.filter((x) => !isBuiltinProvider(String(x.id ?? ""))).map((x) => {
11301
+ const id = String(x.id ?? "").trim();
11302
+ if (!id) throw new ApiError(400, "bad_request", "\u4F9B\u5E94\u5546 id \u4E0D\u80FD\u4E3A\u7A7A");
11303
+ const prev = old.get(id);
11304
+ const apiKey = String(x.apiKey ?? "").trim();
11305
+ const models = normalizeModelList(x.models) ?? prev?.models;
11306
+ const nonChat = normalizeModelList(x.nonChat) ?? prev?.nonChat;
11307
+ const modelMeta = normalizeModelMeta(x.modelMeta) ?? prev?.modelMeta;
11308
+ return {
11309
+ id,
11310
+ name: String(x.name ?? prev?.name ?? id).trim(),
11311
+ type: x.type == null ? prev?.type : String(x.type),
11312
+ baseURL: String(x.baseURL ?? prev?.baseURL ?? "").trim(),
11313
+ model: String(x.model ?? prev?.model ?? "").trim(),
11314
+ apiKey: apiKey || prev?.apiKey,
11315
+ reasoningEffort: x.reasoningEffort === void 0 ? prev?.reasoningEffort ?? null : normReasoningEffort(x.reasoningEffort),
11316
+ ...models ? { models } : {},
11317
+ ...nonChat ? { nonChat } : {},
11318
+ ...modelMeta ? { modelMeta } : {}
11319
+ };
11320
+ })
11321
+ ];
11260
11322
  }
11261
11323
  if (typeof raw?.defaultProviderId === "string") {
11262
11324
  const id = raw.defaultProviderId.trim();
@@ -11276,6 +11338,9 @@ function nodeDefaultProvider(store) {
11276
11338
  if (cfg.providers.length === 0) return null;
11277
11339
  return cfg.providers.find((p) => p.id === cfg.defaultProviderId) ?? cfg.providers[0] ?? null;
11278
11340
  }
11341
+ function defaultTenantModel(store) {
11342
+ return process.env.OPENAI_MODEL || process.env.MODEL || nodeDefaultProvider(store)?.model || FALLBACK_MODEL;
11343
+ }
11279
11344
  function resolveSessionProvider(store, tenant, providerId) {
11280
11345
  const own = tenant.provider ?? {};
11281
11346
  if (providerId) {
@@ -11305,6 +11370,208 @@ function resolveSessionProvider(store, tenant, providerId) {
11305
11370
  return { ...own, model: tenant.model };
11306
11371
  }
11307
11372
 
11373
+ // src/server/builtinLlm.ts
11374
+ init_esm_shims();
11375
+ var FETCH_TIMEOUT_MS = 8e3;
11376
+ var RETRY_AFTER_MS = 5 * 6e4;
11377
+ function hubBase(cred) {
11378
+ return (cred.hubUrl ?? "").replace(/\/+$/, "");
11379
+ }
11380
+ function asProvider(grant) {
11381
+ const baseURL = String(grant.baseURL ?? "").trim();
11382
+ const apiKey = String(grant.apiKey ?? "").trim();
11383
+ const model = String(grant.model ?? "").trim();
11384
+ if (!baseURL || !apiKey) return null;
11385
+ return {
11386
+ id: BUILTIN_PROVIDER_ID,
11387
+ name: String(grant.name ?? "").trim() || "\u5FD7\u524D\u5927\u6A21\u578B\uFF08\u5185\u7F6E\uFF09",
11388
+ type: "builtin",
11389
+ baseURL,
11390
+ model,
11391
+ apiKey
11392
+ };
11393
+ }
11394
+ async function enrichModels(p) {
11395
+ try {
11396
+ const r = await Promise.race([
11397
+ fetchModels(p.baseURL, p.apiKey ?? "", p.model),
11398
+ new Promise((resolve25) => {
11399
+ const t = setTimeout(() => resolve25(null), FETCH_TIMEOUT_MS);
11400
+ t.unref?.();
11401
+ })
11402
+ ]);
11403
+ if (!r?.models?.length) return p;
11404
+ return {
11405
+ ...p,
11406
+ // 控制面没给默认型号时,取供应商返回的第一个,避免模型为空让用户卡住
11407
+ model: p.model || r.models[0],
11408
+ models: r.models,
11409
+ ...r.nonChat?.length ? { nonChat: r.nonChat } : {},
11410
+ ...r.modelMeta?.length ? { modelMeta: r.modelMeta } : {}
11411
+ };
11412
+ } catch {
11413
+ return p;
11414
+ }
11415
+ }
11416
+ function saveBuiltin(store, provider) {
11417
+ const cfg = store.loadNodeLlm();
11418
+ const others = cfg.providers.filter((p) => p.id !== BUILTIN_PROVIDER_ID);
11419
+ const prev = cfg.providers.find((p) => p.id === BUILTIN_PROVIDER_ID);
11420
+ const merged = {
11421
+ ...provider,
11422
+ // 用户选过的型号/候选列表/推理强度/显示名都保留;只有托管字段(baseURL/key)强制以控制面为准
11423
+ ...prev?.model ? { model: prev.model } : {},
11424
+ ...prev?.models?.length ? { models: prev.models } : {},
11425
+ ...prev?.reasoningEffort != null ? { reasoningEffort: prev.reasoningEffort } : {},
11426
+ ...prev?.name ? { name: prev.name } : {}
11427
+ };
11428
+ store.saveNodeLlm({
11429
+ providers: [merged, ...others],
11430
+ // 之前没有默认且用户也没指定时,让内置模型成为默认——「开箱即用」
11431
+ defaultProviderId: cfg.defaultProviderId ?? BUILTIN_PROVIDER_ID
11432
+ });
11433
+ adoptBuiltinModelForTenants(store, merged.model);
11434
+ }
11435
+ var LEGACY_DEFAULT_MODELS = /* @__PURE__ */ new Set([FALLBACK_MODEL, "deepseek-v4-flash"]);
11436
+ function adoptBuiltinModelForTenants(store, model) {
11437
+ if (!model) return;
11438
+ for (const id of store.listTenants()) {
11439
+ const tenant = store.loadTenant(id);
11440
+ if (!tenant || !LEGACY_DEFAULT_MODELS.has(tenant.model)) continue;
11441
+ if (tenant.provider?.apiKey) continue;
11442
+ tenant.model = model;
11443
+ store.saveTenant(tenant);
11444
+ }
11445
+ }
11446
+ async function ensureBuiltinProvider(store, opts = {}) {
11447
+ const cred = store.loadFleetCredential();
11448
+ if (!cred?.fleetToken || !cred.fleetSecret) {
11449
+ return { status: "disabled", reason: "\u8282\u70B9\u672A\u63A5\u5165 Hub" };
11450
+ }
11451
+ const hub = hubBase(cred);
11452
+ if (!hub) return { status: "disabled", reason: "\u7F3A\u5C11 Hub \u5730\u5740" };
11453
+ const local = store.loadNodeLlm().providers.find((p) => p.id === BUILTIN_PROVIDER_ID);
11454
+ if (local && !opts.force) {
11455
+ adoptBuiltinModelForTenants(store, local.model);
11456
+ return { status: "ready", provider: local, refreshed: false };
11457
+ }
11458
+ const cached3 = cred.builtinLlm ? asProvider(cred.builtinLlm) : null;
11459
+ let res;
11460
+ try {
11461
+ res = await fetch(`${hub}/api/hub/node/builtin-llm`, {
11462
+ headers: {
11463
+ Authorization: `Bearer ${cred.fleetToken}`,
11464
+ "X-Fleet-Secret": cred.fleetSecret,
11465
+ "X-Node-Id": cred.nodeId
11466
+ },
11467
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
11468
+ });
11469
+ } catch (e) {
11470
+ if (cached3) {
11471
+ const provider2 = await enrichModels(cached3);
11472
+ saveBuiltin(store, provider2);
11473
+ return { status: "ready", provider: provider2, refreshed: true };
11474
+ }
11475
+ return { status: "error", reason: `\u8FDE\u63A5 Hub \u5931\u8D25\uFF1A${e.message}` };
11476
+ }
11477
+ let body = null;
11478
+ try {
11479
+ body = await res.json();
11480
+ } catch {
11481
+ }
11482
+ if (!res.ok) {
11483
+ const msg = body?.error?.message ?? `HTTP ${res.status}`;
11484
+ if (res.status === 401 || res.status === 403) {
11485
+ return { status: "unavailable", reason: msg };
11486
+ }
11487
+ if (cached3) {
11488
+ const provider2 = await enrichModels(cached3);
11489
+ saveBuiltin(store, provider2);
11490
+ return { status: "ready", provider: provider2, refreshed: true };
11491
+ }
11492
+ return { status: "error", reason: msg };
11493
+ }
11494
+ const grant = body?.data?.builtinLlm;
11495
+ if (!grant) {
11496
+ const why = String(body?.data?.reason ?? "").trim();
11497
+ return { status: "unavailable", reason: why || "\u63A7\u5236\u9762\u672A\u4E0B\u53D1\u5185\u7F6E\u6A21\u578B" };
11498
+ }
11499
+ const parsed = asProvider(grant);
11500
+ if (!parsed) return { status: "unavailable", reason: "\u5185\u7F6E\u6A21\u578B\u914D\u7F6E\u4E0D\u5B8C\u6574" };
11501
+ const provider = await enrichModels(parsed);
11502
+ saveBuiltin(store, provider);
11503
+ return { status: "ready", provider, refreshed: true };
11504
+ }
11505
+ async function fetchLlmBalance(store) {
11506
+ const cred = store.loadFleetCredential();
11507
+ const hub = cred ? hubBase(cred) : "";
11508
+ if (!cred?.fleetToken || !cred.fleetSecret || !hub) {
11509
+ return { available: null, currency: "CNY", reason: "\u8282\u70B9\u672A\u63A5\u5165 Hub" };
11510
+ }
11511
+ try {
11512
+ const res = await fetch(`${hub}/api/hub/node/balance`, {
11513
+ headers: {
11514
+ Authorization: `Bearer ${cred.fleetToken}`,
11515
+ "X-Fleet-Secret": cred.fleetSecret,
11516
+ "X-Node-Id": cred.nodeId
11517
+ },
11518
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
11519
+ });
11520
+ const body = await res.json().catch(() => null);
11521
+ if (!res.ok) {
11522
+ return {
11523
+ available: null,
11524
+ currency: "CNY",
11525
+ reason: body?.data ? "" : `HTTP ${res.status}`
11526
+ };
11527
+ }
11528
+ const d = body?.data ?? {};
11529
+ const available = typeof d.available === "number" ? d.available : null;
11530
+ return {
11531
+ available,
11532
+ currency: typeof d.currency === "string" && d.currency ? d.currency : "CNY",
11533
+ ...typeof d.reserved === "number" ? { reserved: d.reserved } : {},
11534
+ // Hub 没配 HUB_PUBLIC_URL 时退回用接入时的 hubUrl
11535
+ topupUrl: typeof d.topupUrl === "string" && d.topupUrl ? d.topupUrl : `${hub}/topup`,
11536
+ ...available === null && typeof d.reason === "string" ? { reason: d.reason } : {}
11537
+ };
11538
+ } catch (e) {
11539
+ return {
11540
+ available: null,
11541
+ currency: "CNY",
11542
+ reason: `\u8FDE\u63A5 Hub \u5931\u8D25\uFF1A${e.message}`
11543
+ };
11544
+ }
11545
+ }
11546
+ var AUTH_RETRY_THROTTLE_MS = 6e4;
11547
+ var lastAuthRetryAt = 0;
11548
+ function healBuiltinOnAuthFailure(store, message) {
11549
+ if (!/401|unauthorized|invalid[_\s]*api[_\s]*key|api[\s_-]*key\s*(?:无效|invalid)/i.test(message)) return;
11550
+ if (!store.loadFleetCredential()) return;
11551
+ if (!store.loadNodeLlm().providers.some((p) => p.id === BUILTIN_PROVIDER_ID)) return;
11552
+ if (Date.now() - lastAuthRetryAt < AUTH_RETRY_THROTTLE_MS) return;
11553
+ lastAuthRetryAt = Date.now();
11554
+ void ensureBuiltinProvider(store, { force: true }).catch(() => {
11555
+ });
11556
+ }
11557
+ var lastFailureAt = 0;
11558
+ function scheduleBuiltinProvider(store, log = () => {
11559
+ }) {
11560
+ if (Date.now() - lastFailureAt < RETRY_AFTER_MS) return;
11561
+ void ensureBuiltinProvider(store).then((r) => {
11562
+ if (r.status === "ready") {
11563
+ if (r.refreshed) log(`[builtin-llm] \u5DF2\u4E0B\u53D1\u5185\u7F6E\u6A21\u578B\uFF1A${r.provider.name}\uFF08${r.provider.model}\uFF09`);
11564
+ return;
11565
+ }
11566
+ if (r.status === "disabled") return;
11567
+ lastFailureAt = Date.now();
11568
+ if (r.status === "unavailable") log(`[builtin-llm] \u6682\u65E0\u5185\u7F6E\u6A21\u578B\uFF1A${r.reason}`);
11569
+ else log(`[builtin-llm] \u62C9\u53D6\u5185\u7F6E\u6A21\u578B\u5931\u8D25\uFF1A${r.reason}`);
11570
+ }).catch(() => {
11571
+ lastFailureAt = Date.now();
11572
+ });
11573
+ }
11574
+
11308
11575
  // src/server/usage.ts
11309
11576
  init_esm_shims();
11310
11577
  import * as fs19 from "fs";
@@ -13366,6 +13633,7 @@ data: ${JSON.stringify(entry.data)}
13366
13633
  this._setStatus(rt, "error");
13367
13634
  this._persist(rt);
13368
13635
  this._emitNotify(rt, "error", this._turnEndData(rt, event.data, {}));
13636
+ healBuiltinOnAuthFailure(this.store, String(event.data["error"] ?? ""));
13369
13637
  break;
13370
13638
  case "interrupted" /* INTERRUPTED */:
13371
13639
  this._setStatus(rt, "interrupted");
@@ -13789,7 +14057,6 @@ var ApiError = class extends Error {
13789
14057
  };
13790
14058
 
13791
14059
  // src/server/projects.ts
13792
- var DEFAULT_MODEL = () => process.env.OPENAI_MODEL || process.env.MODEL || "gpt-4o";
13793
14060
  function toProjectSummary(store, tenant) {
13794
14061
  return {
13795
14062
  id: tenant.id,
@@ -13929,7 +14196,7 @@ function createProject(store, input) {
13929
14196
  id,
13930
14197
  name: explicitName || defaultName,
13931
14198
  apiKey: generateApiKey(),
13932
- model: typeof raw.model === "string" && raw.model.trim() ? raw.model.trim() : DEFAULT_MODEL(),
14199
+ model: typeof raw.model === "string" && raw.model.trim() ? raw.model.trim() : defaultTenantModel(store),
13933
14200
  provider: {
13934
14201
  apiKey: typeof raw.providerApiKey === "string" && raw.providerApiKey.trim() ? raw.providerApiKey.trim() : void 0,
13935
14202
  baseURL: typeof raw.baseURL === "string" && raw.baseURL.trim() ? raw.baseURL.trim() : void 0
@@ -13954,7 +14221,7 @@ function buildNodeInfo(store, opts) {
13954
14221
  nodeId: opts.nodeId ?? ensureNodeId(store),
13955
14222
  platform: process.platform,
13956
14223
  mode: process.env.NODE_MODE ?? "standalone",
13957
- version: "0.2.16",
14224
+ version: "0.2.20",
13958
14225
  admin: opts.admin,
13959
14226
  projects: opts.projects
13960
14227
  };
@@ -14170,7 +14437,7 @@ import * as path26 from "path";
14170
14437
  import { spawnSync as spawnSync2 } from "child_process";
14171
14438
  import { createRequire as createRequire3 } from "module";
14172
14439
  function currentVersion() {
14173
- const injected = "0.2.16"?.trim();
14440
+ const injected = "0.2.20"?.trim();
14174
14441
  if (injected) return injected;
14175
14442
  try {
14176
14443
  const req2 = createRequire3(import.meta.url);
@@ -15506,10 +15773,133 @@ function makeDirIn(parentInput, name) {
15506
15773
  init_esm_shims();
15507
15774
  import * as fs23 from "fs";
15508
15775
  import * as path31 from "path";
15776
+
15777
+ // src/server/zipArchive.ts
15778
+ init_esm_shims();
15779
+ import * as zlib from "zlib";
15780
+ var MAX_ZIP_ENTRIES = 2e4;
15781
+ var LOCAL_SIG = 67324752;
15782
+ var CENTRAL_SIG = 33639248;
15783
+ var EOCD_SIG = 101010256;
15784
+ var FLAG_UTF8 = 2048;
15785
+ var METHOD_STORE = 0;
15786
+ var METHOD_DEFLATE = 8;
15787
+ var CRC_TABLE = (() => {
15788
+ const table = new Int32Array(256);
15789
+ for (let n = 0; n < 256; n++) {
15790
+ let c = n;
15791
+ for (let k = 0; k < 8; k++) {
15792
+ c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
15793
+ }
15794
+ table[n] = c;
15795
+ }
15796
+ return table;
15797
+ })();
15798
+ function crc32(buf) {
15799
+ let c = -1;
15800
+ for (let i = 0; i < buf.length; i++) {
15801
+ c = CRC_TABLE[(c ^ buf[i]) & 255] ^ c >>> 8;
15802
+ }
15803
+ return (c ^ -1) >>> 0;
15804
+ }
15805
+ function dosDateTime(d) {
15806
+ const year = Math.max(1980, d.getFullYear());
15807
+ return {
15808
+ time: d.getHours() << 11 | d.getMinutes() << 5 | d.getSeconds() >> 1,
15809
+ date: year - 1980 << 9 | d.getMonth() + 1 << 5 | d.getDate()
15810
+ };
15811
+ }
15812
+ function buildZip(entries) {
15813
+ if (entries.length > MAX_ZIP_ENTRIES) {
15814
+ throw new Error(`\u5F52\u6863\u6761\u76EE\u8FC7\u591A\uFF08${entries.length} > ${MAX_ZIP_ENTRIES}\uFF09`);
15815
+ }
15816
+ const chunks = [];
15817
+ const central = [];
15818
+ let offset = 0;
15819
+ for (const e of entries) {
15820
+ const name = Buffer.from(e.name, "utf-8");
15821
+ const isDir = e.name.endsWith("/");
15822
+ const raw = isDir ? Buffer.alloc(0) : e.data ?? Buffer.alloc(0);
15823
+ let method = METHOD_STORE;
15824
+ let body = raw;
15825
+ if (!isDir && raw.length > 0) {
15826
+ const deflated = zlib.deflateRawSync(raw, { level: 6 });
15827
+ if (deflated.length < raw.length) {
15828
+ method = METHOD_DEFLATE;
15829
+ body = deflated;
15830
+ }
15831
+ }
15832
+ const crc = isDir ? 0 : crc32(raw);
15833
+ const { time, date } = dosDateTime(e.mtime ?? /* @__PURE__ */ new Date());
15834
+ const local = Buffer.alloc(30);
15835
+ local.writeUInt32LE(LOCAL_SIG, 0);
15836
+ local.writeUInt16LE(20, 4);
15837
+ local.writeUInt16LE(FLAG_UTF8, 6);
15838
+ local.writeUInt16LE(method, 8);
15839
+ local.writeUInt16LE(time, 10);
15840
+ local.writeUInt16LE(date, 12);
15841
+ local.writeUInt32LE(crc, 14);
15842
+ local.writeUInt32LE(body.length, 18);
15843
+ local.writeUInt32LE(raw.length, 22);
15844
+ local.writeUInt16LE(name.length, 26);
15845
+ local.writeUInt16LE(0, 28);
15846
+ chunks.push(local, name);
15847
+ if (body.length > 0) chunks.push(body);
15848
+ central.push({
15849
+ name,
15850
+ method,
15851
+ crc,
15852
+ csize: body.length,
15853
+ usize: raw.length,
15854
+ offset,
15855
+ time,
15856
+ date,
15857
+ isDir
15858
+ });
15859
+ offset += local.length + name.length + body.length;
15860
+ }
15861
+ const cdStart = offset;
15862
+ for (const c of central) {
15863
+ const head = Buffer.alloc(46);
15864
+ head.writeUInt32LE(CENTRAL_SIG, 0);
15865
+ head.writeUInt16LE(20, 4);
15866
+ head.writeUInt16LE(20, 6);
15867
+ head.writeUInt16LE(FLAG_UTF8, 8);
15868
+ head.writeUInt16LE(c.method, 10);
15869
+ head.writeUInt16LE(c.time, 12);
15870
+ head.writeUInt16LE(c.date, 14);
15871
+ head.writeUInt32LE(c.crc, 16);
15872
+ head.writeUInt32LE(c.csize, 20);
15873
+ head.writeUInt32LE(c.usize, 24);
15874
+ head.writeUInt16LE(c.name.length, 28);
15875
+ head.writeUInt16LE(0, 30);
15876
+ head.writeUInt16LE(0, 32);
15877
+ head.writeUInt16LE(0, 34);
15878
+ head.writeUInt16LE(0, 36);
15879
+ head.writeUInt32LE(c.isDir ? 16 : 0, 38);
15880
+ head.writeUInt32LE(c.offset, 42);
15881
+ chunks.push(head, c.name);
15882
+ offset += head.length + c.name.length;
15883
+ }
15884
+ const eocd = Buffer.alloc(22);
15885
+ eocd.writeUInt32LE(EOCD_SIG, 0);
15886
+ eocd.writeUInt16LE(0, 4);
15887
+ eocd.writeUInt16LE(0, 6);
15888
+ eocd.writeUInt16LE(central.length, 8);
15889
+ eocd.writeUInt16LE(central.length, 10);
15890
+ eocd.writeUInt32LE(offset - cdStart, 12);
15891
+ eocd.writeUInt32LE(cdStart, 16);
15892
+ eocd.writeUInt16LE(0, 20);
15893
+ chunks.push(eocd);
15894
+ return Buffer.concat(chunks);
15895
+ }
15896
+
15897
+ // src/server/projectFiles.ts
15509
15898
  var MAX_FILE_BYTES = 2 * 1024 * 1024;
15510
15899
  var MAX_DOWNLOAD_BYTES = 16 * 1024 * 1024;
15511
15900
  var MAX_BINARY_BYTES = 2.5 * 1024 * 1024;
15512
15901
  var MAX_ENTRIES2 = 2e3;
15902
+ var MAX_ZIP_BYTES = 100 * 1024 * 1024;
15513
15903
  function toPosix(p) {
15514
15904
  return p.split(path31.sep).join("/");
15515
15905
  }
@@ -15785,6 +16175,134 @@ function deleteProjectPaths(workspaceRoot, relPaths) {
15785
16175
  }
15786
16176
  return out;
15787
16177
  }
16178
+ function formatBytes(n) {
16179
+ if (n >= 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(1)} MB`;
16180
+ if (n >= 1024) return `${(n / 1024).toFixed(1)} KB`;
16181
+ return `${n} B`;
16182
+ }
16183
+ function renameProjectPath(workspaceRoot, relPath, newName) {
16184
+ const base = projectRoot(workspaceRoot);
16185
+ const rel = (relPath ?? "").trim().replace(/^[/\\]+/, "");
16186
+ if (!rel) throw new Error("\u4E0D\u80FD\u91CD\u547D\u540D\u9879\u76EE\u6839\u76EE\u5F55");
16187
+ const abs = resolveWithin(base, rel);
16188
+ const name = (newName ?? "").trim();
16189
+ if (!name) throw new Error("\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
16190
+ if (name === "." || name === "..") throw new Error("\u540D\u79F0\u975E\u6CD5");
16191
+ if (/[/\\]/.test(name)) throw new Error("\u540D\u79F0\u4E0D\u80FD\u5305\u542B\u8DEF\u5F84\u5206\u9694\u7B26");
16192
+ if (!fs23.existsSync(abs)) throw new Error("\u6587\u4EF6\u4E0D\u5B58\u5728");
16193
+ const parent = path31.dirname(abs);
16194
+ const target = path31.join(parent, name);
16195
+ if (!isWithinRoot(base, target)) throw new Error("\u8DEF\u5F84\u8D8A\u754C\uFF08\u8D85\u51FA\u9879\u76EE\u5DE5\u4F5C\u533A\uFF09");
16196
+ if (fs23.existsSync(target)) {
16197
+ let same = false;
16198
+ try {
16199
+ const a = fs23.statSync(target);
16200
+ const b = fs23.statSync(abs);
16201
+ same = a.ino === b.ino && a.dev === b.dev;
16202
+ } catch {
16203
+ same = false;
16204
+ }
16205
+ if (!same) throw new Error(`\u5DF2\u5B58\u5728\u540C\u540D\u6587\u4EF6\u6216\u76EE\u5F55: ${name}`);
16206
+ }
16207
+ if (path31.resolve(target) === path31.resolve(abs)) {
16208
+ return { from: toPosix(path31.relative(base, abs)), path: toPosix(path31.relative(base, abs)) };
16209
+ }
16210
+ try {
16211
+ fs23.renameSync(abs, target);
16212
+ } catch (e) {
16213
+ throw new Error(`\u91CD\u547D\u540D\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
16214
+ }
16215
+ return { from: toPosix(path31.relative(base, abs)), path: toPosix(path31.relative(base, target)) };
16216
+ }
16217
+ function zipProjectPaths(workspaceRoot, relPaths, opts = {}) {
16218
+ const base = projectRoot(workspaceRoot);
16219
+ const maxBytes = opts.maxBytes && opts.maxBytes > 0 ? opts.maxBytes : MAX_ZIP_BYTES;
16220
+ const rels = (relPaths ?? []).map((x) => (x ?? "").trim().replace(/^[/\\]+/, ""));
16221
+ if (rels.length === 0) throw new Error("\u8BF7\u9009\u62E9\u8981\u538B\u7F29\u7684\u6587\u4EF6\u6216\u76EE\u5F55");
16222
+ if (rels.some((r) => !r)) throw new Error("\u4E0D\u80FD\u538B\u7F29\u9879\u76EE\u6839\u76EE\u5F55");
16223
+ const targets = rels.map((rel) => ({ rel, abs: resolveWithin(base, rel) }));
16224
+ const single = targets.length === 1 ? targets[0].abs : "";
16225
+ const items = [];
16226
+ let total = 0;
16227
+ const overLimit = () => new Error(
16228
+ `\u5171 ${formatBytes(total)}\uFF0C\u8D85\u8FC7 ${formatBytes(maxBytes)} \u4E0A\u9650\uFF0C\u4E0D\u652F\u6301\u538B\u7F29\u4E0B\u8F7D`
16229
+ );
16230
+ const walk = (absDir) => {
16231
+ let dirents;
16232
+ try {
16233
+ dirents = fs23.readdirSync(absDir, { withFileTypes: true });
16234
+ } catch {
16235
+ return 0;
16236
+ }
16237
+ dirents.sort((a, b) => a.name.localeCompare(b.name));
16238
+ let files = 0;
16239
+ for (const d of dirents) {
16240
+ if (d.isSymbolicLink()) continue;
16241
+ const child = path31.join(absDir, d.name);
16242
+ if (d.isDirectory()) {
16243
+ files += walk(child);
16244
+ continue;
16245
+ }
16246
+ if (!d.isFile()) continue;
16247
+ let st;
16248
+ try {
16249
+ st = fs23.statSync(child);
16250
+ } catch {
16251
+ continue;
16252
+ }
16253
+ total += st.size;
16254
+ if (total > maxBytes) throw overLimit();
16255
+ if (items.length >= MAX_ZIP_ENTRIES) {
16256
+ 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`);
16257
+ }
16258
+ items.push({ abs: child, dir: false, size: st.size, mtime: st.mtime });
16259
+ files++;
16260
+ }
16261
+ if (files === 0) {
16262
+ let mtime = /* @__PURE__ */ new Date();
16263
+ try {
16264
+ mtime = fs23.statSync(absDir).mtime;
16265
+ } catch {
16266
+ }
16267
+ items.push({ abs: absDir, dir: true, size: 0, mtime });
16268
+ }
16269
+ return files;
16270
+ };
16271
+ for (const t of targets) {
16272
+ let st;
16273
+ try {
16274
+ st = fs23.statSync(t.abs);
16275
+ } catch {
16276
+ throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${t.rel}`);
16277
+ }
16278
+ if (st.isDirectory()) {
16279
+ walk(t.abs);
16280
+ continue;
16281
+ }
16282
+ total += st.size;
16283
+ if (total > maxBytes) throw overLimit();
16284
+ items.push({ abs: t.abs, dir: false, size: st.size, mtime: st.mtime });
16285
+ }
16286
+ const nameOf = (abs) => {
16287
+ const rel = toPosix(path31.relative(single || base, abs));
16288
+ return rel || path31.posix.basename(toPosix(abs));
16289
+ };
16290
+ const zipEntries = items.map((it) => {
16291
+ const name2 = nameOf(it.abs);
16292
+ if (it.dir) return { name: `${name2}/`, mtime: it.mtime };
16293
+ return { name: name2, data: fs23.readFileSync(it.abs), mtime: it.mtime };
16294
+ });
16295
+ const buf = buildZip(zipEntries);
16296
+ const first = targets[0].rel;
16297
+ const name = single ? `${path31.posix.basename(toPosix(first))}.zip` : "archive.zip";
16298
+ return {
16299
+ name,
16300
+ size: buf.length,
16301
+ total,
16302
+ entries: zipEntries.length,
16303
+ base64: buf.toString("base64")
16304
+ };
16305
+ }
15788
16306
 
15789
16307
  // src/server/projectOps.ts
15790
16308
  init_esm_shims();
@@ -16114,6 +16632,7 @@ function projectStats(manager, tenantId) {
16114
16632
 
16115
16633
  // src/server/nodeHost.ts
16116
16634
  init_esm_shims();
16635
+ import { spawnSync as spawnSync4 } from "child_process";
16117
16636
  import * as fs24 from "fs";
16118
16637
  import * as os9 from "os";
16119
16638
  function diskInfo(pathStr) {
@@ -16128,6 +16647,64 @@ function diskInfo(pathStr) {
16128
16647
  return null;
16129
16648
  }
16130
16649
  }
16650
+ 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)$/;
16651
+ function isInternalMount(mount) {
16652
+ if (mount === "/") return false;
16653
+ const prefixes = ["/proc", "/sys", "/run", "/snap"];
16654
+ if (process.platform === "darwin") prefixes.push("/System/Volumes");
16655
+ return prefixes.some((p) => mount === p || mount.startsWith(`${p}/`));
16656
+ }
16657
+ function readOnlyVolumes() {
16658
+ const out = /* @__PURE__ */ new Set();
16659
+ const res = spawnSync4("mount", [], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
16660
+ if (res.error || res.status !== 0 || !res.stdout) return out;
16661
+ for (const line of res.stdout.split("\n")) {
16662
+ const m = line.match(/^.+? on (.+?) \(([^)]*)\)$/);
16663
+ if (!m || m[1] === "/") continue;
16664
+ if (m[2].split(",").some((opt) => opt.trim() === "read-only")) out.add(m[1]);
16665
+ }
16666
+ return out;
16667
+ }
16668
+ function diskList(workspacePath) {
16669
+ const out = [];
16670
+ const seen = /* @__PURE__ */ new Set();
16671
+ const push = (path45, total, free) => {
16672
+ if (!path45 || seen.has(path45) || !Number.isFinite(total) || total <= 0) return;
16673
+ seen.add(path45);
16674
+ out.push({ path: path45, total, free });
16675
+ };
16676
+ if (process.platform === "win32") {
16677
+ for (const letter of "CDEFGHIJKLMNOPQRSTUVWXYZ") {
16678
+ const root = `${letter}:\\`;
16679
+ const d = diskInfo(root);
16680
+ if (d) push(root, d.total, d.free);
16681
+ }
16682
+ } else {
16683
+ const readonly = process.platform === "darwin" ? readOnlyVolumes() : /* @__PURE__ */ new Set();
16684
+ const res = spawnSync4("df", ["-kP"], {
16685
+ encoding: "utf-8",
16686
+ stdio: ["ignore", "pipe", "ignore"]
16687
+ });
16688
+ if (!res.error && res.status === 0 && res.stdout) {
16689
+ for (const line of res.stdout.split("\n").slice(1)) {
16690
+ const m = line.trim().match(/^(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s+(.+)$/);
16691
+ if (!m || PSEUDO_FS.test(m[1])) continue;
16692
+ if (/^\/dev\/loop\d+$/.test(m[1])) continue;
16693
+ if (isInternalMount(m[5]) || readonly.has(m[5])) continue;
16694
+ push(m[5], Number(m[2]) * 1024, Number(m[4]) * 1024);
16695
+ }
16696
+ }
16697
+ }
16698
+ const primary = diskInfo(workspacePath);
16699
+ if (primary) {
16700
+ const covered = out.some((d) => {
16701
+ const root = d.path.endsWith("/") || d.path.endsWith("\\") ? d.path : `${d.path}/`;
16702
+ return workspacePath === d.path || workspacePath.startsWith(root);
16703
+ });
16704
+ if (!covered) push(primary.path, primary.total, primary.free);
16705
+ }
16706
+ return out.sort((a, b) => b.total - a.total);
16707
+ }
16131
16708
  function networkInfo() {
16132
16709
  const out = [];
16133
16710
  const ifaces = os9.networkInterfaces();
@@ -16183,6 +16760,7 @@ function overview(store, manager) {
16183
16760
  function buildNodeHost(store, manager, diskPath) {
16184
16761
  const cpus2 = os9.cpus();
16185
16762
  const ov = overview(store, manager);
16763
+ const disks = diskList(diskPath || process.cwd());
16186
16764
  return {
16187
16765
  nodeId: store.loadNodeId() ?? "",
16188
16766
  hostname: os9.hostname(),
@@ -16190,7 +16768,7 @@ function buildNodeHost(store, manager, diskPath) {
16190
16768
  arch: process.arch,
16191
16769
  release: os9.release(),
16192
16770
  uptimeSec: Math.round(os9.uptime()),
16193
- version: "0.2.16",
16771
+ version: "0.2.20",
16194
16772
  mode: process.env.NODE_MODE ?? "standalone",
16195
16773
  cpu: {
16196
16774
  count: cpus2.length,
@@ -16200,6 +16778,7 @@ function buildNodeHost(store, manager, diskPath) {
16200
16778
  memory: { total: os9.totalmem(), free: os9.freemem() },
16201
16779
  network: networkInfo(),
16202
16780
  disk: diskInfo(diskPath || process.cwd()),
16781
+ disks,
16203
16782
  ...ov
16204
16783
  };
16205
16784
  }
@@ -17425,6 +18004,41 @@ function createApp(store, manager, opts = {}) {
17425
18004
  }
17426
18005
  }
17427
18006
  },
18007
+ {
18008
+ method: "POST",
18009
+ pattern: ["files", "rename"],
18010
+ // 重命名(只改同目录下的名字)
18011
+ fn: (_req, res, { tenant, body }) => {
18012
+ const raw = body ?? {};
18013
+ try {
18014
+ ok(
18015
+ res,
18016
+ renameProjectPath(
18017
+ tenant.workspaceRoot ?? null,
18018
+ String(raw.path ?? ""),
18019
+ String(raw.name ?? "")
18020
+ )
18021
+ );
18022
+ } catch (e) {
18023
+ throw new ApiError(400, "bad_request", e instanceof Error ? e.message : String(e));
18024
+ }
18025
+ }
18026
+ },
18027
+ {
18028
+ method: "POST",
18029
+ pattern: ["files", "zip"],
18030
+ // 压缩下载(多选一起打包;超上限由实现拒绝)
18031
+ fn: (_req, res, { tenant, body }) => {
18032
+ const raw = body ?? {};
18033
+ const paths = Array.isArray(raw.paths) ? raw.paths.map((x) => String(x)) : [];
18034
+ if (paths.length === 0) throw new ApiError(400, "bad_request", "paths \u4E0D\u80FD\u4E3A\u7A7A");
18035
+ try {
18036
+ ok(res, zipProjectPaths(tenant.workspaceRoot ?? null, paths));
18037
+ } catch (e) {
18038
+ throw new ApiError(400, "bad_request", e instanceof Error ? e.message : String(e));
18039
+ }
18040
+ }
18041
+ },
17428
18042
  // ---------- 项目(= 租户)管理:控制台多项目视图 ----------
17429
18043
  // 仅管理员密钥可用;项目级与会话级接口仍用项目自己的 tenant API Key
17430
18044
  // (或管理员密钥 + X-Project-Id 头),互不暴露明文密钥。
@@ -17483,6 +18097,29 @@ function createApp(store, manager, opts = {}) {
17483
18097
  ok(res, nodeLlmView(next));
17484
18098
  }
17485
18099
  },
18100
+ // 大模型账户余额(控制台「余额不足 → 查看余额并充值」用;由节点向 Hub 代查)
18101
+ {
18102
+ method: "GET",
18103
+ pattern: ["node", "llm", "balance"],
18104
+ adminOnly: true,
18105
+ fn: async (_req, res) => {
18106
+ ok(res, await fetchLlmBalance(store));
18107
+ }
18108
+ },
18109
+ // 重新拉取平台内置模型凭据(用户点了「刷新内置模型」,或 Key 在控制台被重置)
18110
+ {
18111
+ method: "POST",
18112
+ pattern: ["node", "llm", "builtin-refresh"],
18113
+ adminOnly: true,
18114
+ fn: async (_req, res) => {
18115
+ const r = await ensureBuiltinProvider(store, { force: true });
18116
+ if (r.status !== "ready") {
18117
+ ok(res, { ok: false, reason: r.reason });
18118
+ return;
18119
+ }
18120
+ ok(res, { ok: true, view: nodeLlmView(store.loadNodeLlm()) });
18121
+ }
18122
+ },
17486
18123
  // ---------- 用量(账本 + 汇总 + 价格) ----------
17487
18124
  {
17488
18125
  method: "GET",
@@ -17909,7 +18546,7 @@ function createApp(store, manager, opts = {}) {
17909
18546
  json(res, 200, {
17910
18547
  status: "ok",
17911
18548
  // 版本让运维/探针能确认线上跑的是哪个构建(更新与回滚据此判断)
17912
- version: "0.2.16",
18549
+ version: "0.2.20",
17913
18550
  features: { mcp: hasServerMcpConfig() }
17914
18551
  });
17915
18552
  return;
@@ -18249,6 +18886,22 @@ async function dispatchFleetCommand(deps, env3) {
18249
18886
  store.saveNodeLlm(next);
18250
18887
  return ok2(nodeLlmView(next));
18251
18888
  }
18889
+ /*
18890
+ * 重新拉取平台内置模型(志前 API)。
18891
+ *
18892
+ * 用途:用户在控制台点了「刷新」、或内置模型的 Key 在控制台被重置后需要换新。
18893
+ * 走写链串行——它会改写 connections.json,不能与 update_node_llm 并发。
18894
+ */
18895
+ // 余额查询:只读、幂等,可进只读并发池
18896
+ case "get_llm_balance":
18897
+ return ok2(await fetchLlmBalance(store));
18898
+ case "refresh_builtin_llm": {
18899
+ const r = await ensureBuiltinProvider(store, { force: true });
18900
+ if (r.status !== "ready") {
18901
+ return ok2({ ok: false, reason: r.reason });
18902
+ }
18903
+ return ok2({ ok: true, view: nodeLlmView(store.loadNodeLlm()) });
18904
+ }
18252
18905
  case "get_usage_summary": {
18253
18906
  const ledger = manager.usageLedger();
18254
18907
  if (!ledger) return ok2({ total: null, groups: [] });
@@ -18433,6 +19086,24 @@ async function dispatchFleetCommand(deps, env3) {
18433
19086
  const t = project();
18434
19087
  return ok2(searchProjectFiles(t.workspaceRoot ?? null, String(payload.query ?? "")));
18435
19088
  }
19089
+ case "rename_path": {
19090
+ const t = project();
19091
+ return ok2(
19092
+ renameProjectPath(
19093
+ t.workspaceRoot ?? null,
19094
+ String(payload.path ?? ""),
19095
+ String(payload.name ?? "")
19096
+ )
19097
+ );
19098
+ }
19099
+ case "zip_download": {
19100
+ const t = project();
19101
+ const raw = payload.paths;
19102
+ const paths = Array.isArray(raw) ? raw.map((x) => String(x)) : [];
19103
+ if (paths.length === 0) throw new ApiError(400, "bad_request", "paths \u4E0D\u80FD\u4E3A\u7A7A");
19104
+ const maxBytes = typeof payload.maxBytes === "number" ? payload.maxBytes : void 0;
19105
+ return ok2(zipProjectPaths(t.workspaceRoot ?? null, paths, { maxBytes }));
19106
+ }
18436
19107
  case "list_tools": {
18437
19108
  const t = project();
18438
19109
  return ok2(buildToolCatalog(t.workspaceRoot ?? process.cwd()));
@@ -18785,6 +19456,22 @@ function toWireAck(ack) {
18785
19456
  data: ack.data === void 0 ? void 0 : utf8Encode(JSON.stringify(ack.data))
18786
19457
  };
18787
19458
  }
19459
+ var ACK_JSON_MAX_BYTES = MAX_FRAME_BYTES - 64 * 1024;
19460
+ function mb(bytes) {
19461
+ return (bytes / (1024 * 1024)).toFixed(1);
19462
+ }
19463
+ function guardAckSize(ack, json2) {
19464
+ if (!ack.ok || ack.data === void 0) return ack;
19465
+ const bytes = Buffer.byteLength(json2, "utf8");
19466
+ if (bytes <= ACK_JSON_MAX_BYTES) return ack;
19467
+ return {
19468
+ reqId: ack.reqId,
19469
+ type: ack.type,
19470
+ ok: false,
19471
+ // 带上实际大小:用户据此能判断「差一点」还是「差很多」,而不是只看到「失败」
19472
+ 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`
19473
+ };
19474
+ }
18788
19475
  function fromWireCommand(env3) {
18789
19476
  let payload = {};
18790
19477
  if (env3.payload && env3.payload.length > 0) {
@@ -19190,6 +19877,7 @@ var READONLY_CMD_TYPES = /* @__PURE__ */ new Set([
19190
19877
  "get_node_llm",
19191
19878
  "list_node_llm_models",
19192
19879
  "get_usage_summary",
19880
+ "get_llm_balance",
19193
19881
  "get_usage_records",
19194
19882
  "get_usage_pricing",
19195
19883
  "list_projects",
@@ -19614,7 +20302,7 @@ var FleetNodeBridge = class {
19614
20302
  label: this.opts.deps.store.loadNodeLabel() ?? void 0,
19615
20303
  platform: process.platform,
19616
20304
  mode: process.env.NODE_MODE ?? "standalone",
19617
- version: "0.2.16",
20305
+ version: "0.2.20",
19618
20306
  ip: primaryIp()
19619
20307
  };
19620
20308
  }
@@ -19712,7 +20400,7 @@ var FleetNodeBridge = class {
19712
20400
  label: this.opts.deps.store.loadNodeLabel() ?? void 0,
19713
20401
  platform: process.platform,
19714
20402
  mode: process.env.NODE_MODE ?? "standalone",
19715
- version: "0.2.16",
20403
+ version: "0.2.20",
19716
20404
  ip: primaryIp(),
19717
20405
  projects,
19718
20406
  totals,
@@ -19874,7 +20562,13 @@ var FleetNodeBridge = class {
19874
20562
  const stamped = { ...ack, reqId: env3.reqId, type: env3.type };
19875
20563
  const json2 = JSON.stringify(stamped);
19876
20564
  const __t1 = dbgNow();
19877
- this._send({ kind: "ack", clientId: env3.clientId, ack: stamped });
20565
+ const payload = guardAckSize(stamped, json2);
20566
+ if (payload !== stamped) {
20567
+ console.warn(
20568
+ `[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`
20569
+ );
20570
+ }
20571
+ this._send({ kind: "ack", clientId: env3.clientId, ack: payload });
19878
20572
  dbgMs(
19879
20573
  "publishAck",
19880
20574
  __t0,
@@ -21110,7 +21804,7 @@ import { createHash as createHash3 } from "crypto";
21110
21804
  import * as fs27 from "fs";
21111
21805
  import { Readable as Readable2 } from "stream";
21112
21806
  import { pipeline as pipeline2 } from "stream/promises";
21113
- import { spawnSync as spawnSync4 } from "child_process";
21807
+ import { spawnSync as spawnSync5 } from "child_process";
21114
21808
  import * as path36 from "path";
21115
21809
 
21116
21810
  // src/update/manifest.ts
@@ -21337,7 +22031,7 @@ async function downloadBinary(bin, destPath, log = () => {
21337
22031
  }
21338
22032
  function selfCheck(binPath, expectedVersion) {
21339
22033
  try {
21340
- const r = spawnSync4(binPath, ["--version"], {
22034
+ const r = spawnSync5(binPath, ["--version"], {
21341
22035
  timeout: 2e4,
21342
22036
  encoding: "utf-8",
21343
22037
  env: { ...process.env, ZQAGENT_AUTO_UPDATE: "0" }
@@ -21918,7 +22612,9 @@ function seedDemoTenant(store) {
21918
22612
  // 未显式指定时随机生成:固定默认密钥一旦随 HOST=0.0.0.0 暴露到公网,等于公开凭据。
21919
22613
  // 只在首次创建时生成并落盘,后续启动沿用磁盘上的值(启动日志会打印完整密钥)。
21920
22614
  apiKey: env2("TENANT_API_KEY") ?? generateApiKey(),
21921
- model: env2("MODEL") ?? env2("OPENAI_MODEL") ?? "deepseek-v4-flash",
22615
+ // 不再硬编码具体厂商型号:志前 API 只认 deepseek-flash,写死 deepseek-v4-flash
22616
+ // 会让新装节点的第一次请求 404。默认值统一走 defaultTenantModel(见 nodeLlm.ts)。
22617
+ model: defaultTenantModel(store),
21922
22618
  provider: {
21923
22619
  apiKey: env2("OPENAI_API_KEY"),
21924
22620
  baseURL: env2("OPENAI_BASE_URL")
@@ -22058,6 +22754,8 @@ function startServer(opts = {}) {
22058
22754
  const store = new Store(dataDir);
22059
22755
  const demo = seedDemoTenant(store);
22060
22756
  syncTenantModels(store);
22757
+ const nodeModel = nodeDefaultProvider(store)?.model;
22758
+ if (nodeModel) adoptBuiltinModelForTenants(store, nodeModel);
22061
22759
  const usageLedger = new UsageLedger(dataDir);
22062
22760
  usageLedger.init();
22063
22761
  const manager = new SessionManager(store, void 0, { usage: usageLedger });
@@ -22201,8 +22899,12 @@ function startServer(opts = {}) {
22201
22899
  if (initialCred) {
22202
22900
  connectFleet(initialCred);
22203
22901
  } else if (opts.autoEnroll) {
22204
- 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}`));
22902
+ void fleetController.enroll(opts.autoEnroll).then((st) => {
22903
+ console.log(`[fleet] \u5DF2\u81EA\u52A8\u63A5\u5165\uFF0C\u8282\u70B9 ${st.nodeId}\uFF08${st.gatewayUrl}\uFF09`);
22904
+ scheduleBuiltinProvider(store, (m) => console.log(m));
22905
+ }).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}`));
22205
22906
  }
22907
+ if (initialCred) scheduleBuiltinProvider(store, (m) => console.log(m));
22206
22908
  const pairingCodes = new PairingCodeStore();
22207
22909
  const qq = new QqManager({
22208
22910
  store,
@@ -23256,7 +23958,10 @@ async function connectWizard() {
23256
23958
  break;
23257
23959
  }
23258
23960
  cliLog(`\u274C \u81EA\u68C0\u5931\u8D25: ${probe.error}${probe.code ? ` | ${probe.code}` : ""}`);
23259
- for (const hint of paramErrorHints(probe.error ?? "", probe.code ?? "")) {
23961
+ for (const hint of [
23962
+ ...authErrorHints(probe.error ?? "", probe.code ?? ""),
23963
+ ...paramErrorHints(probe.error ?? "", probe.code ?? "")
23964
+ ]) {
23260
23965
  cliLog(` \u{1F4A1} ${hint}`);
23261
23966
  }
23262
23967
  const keep = (await question("\u91CD\u65B0\u9009\u62E9\u6A21\u578B (\u56DE\u8F66\u91CD\u9009) / \u8F93\u5165 y \u4ECD\u4F7F\u7528\u8BE5\u6A21\u578B: ")).trim().toLowerCase();
@@ -23490,7 +24195,7 @@ async function offerTerminalSetup(engine, canUseAgent) {
23490
24195
  }
23491
24196
  var KNOWN_SUBCOMMANDS = /* @__PURE__ */ new Set(["link", "serve", "update", "rename"]);
23492
24197
  function cliVersion() {
23493
- const env3 = "0.2.16"?.trim();
24198
+ const env3 = "0.2.20"?.trim();
23494
24199
  if (env3) return env3;
23495
24200
  try {
23496
24201
  const req2 = createRequire4(import.meta.url);
@@ -24003,6 +24708,12 @@ async function main() {
24003
24708
  cliLog(`\u274C \u672A\u627E\u5230\u4F9B\u5E94\u5546: ${arg}`);
24004
24709
  break;
24005
24710
  }
24711
+ if (target.id === BUILTIN_PROVIDER_ID) {
24712
+ cliLog(
24713
+ "\u274C \u5E73\u53F0\u5185\u7F6E\u6A21\u578B\u4E0D\u53EF\u5220\u9664\uFF08\u63A5\u5165 Hub \u540E\u81EA\u52A8\u914D\u7F6E\uFF0CKey \u7531\u5E73\u53F0\u6258\u7BA1\uFF09\u3002\n \u5982\u9700\u6539\u7528\u5176\u5B83\u6A21\u578B\uFF1A/connect \u9009\u62E9\u6216\u6DFB\u52A0\u4F9B\u5E94\u5546\u540E\u8BBE\u4E3A\u9ED8\u8BA4\u3002"
24714
+ );
24715
+ break;
24716
+ }
24006
24717
  if (removeConnection(target.id)) {
24007
24718
  currentConnection = loadConnection();
24008
24719
  engine = buildEngine(SESSION_ID);