dsh-multi-router 0.5.8 → 0.6.0

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +49 -8
  3. package/cordis.patch.yml +2 -2
  4. package/lib/accounts.js +6 -4
  5. package/lib/api-service.js +11 -0
  6. package/lib/api-service.js.map +7 -0
  7. package/lib/chunk-4V52V5GO.js +105 -0
  8. package/lib/chunk-4V52V5GO.js.map +7 -0
  9. package/lib/{chunk-OFFVP6YN.js → chunk-JUOESKAK.js} +22 -5
  10. package/lib/chunk-JUOESKAK.js.map +7 -0
  11. package/lib/{chunk-P43AWJ4Q.js → chunk-LLDJVBTQ.js} +3 -3
  12. package/lib/{chunk-U2MU2M5C.js → chunk-NSKCFVHP.js} +13 -111
  13. package/lib/chunk-NSKCFVHP.js.map +7 -0
  14. package/lib/{chunk-ILRW3ELD.js → chunk-OCMOMSB4.js} +293 -77
  15. package/lib/chunk-OCMOMSB4.js.map +7 -0
  16. package/lib/{chunk-FTM7RHQV.js → chunk-OK2T2UAZ.js} +4 -4
  17. package/lib/{chunk-J3XO7RTF.js → chunk-ROZINFTV.js} +3 -2
  18. package/lib/chunk-ROZINFTV.js.map +7 -0
  19. package/lib/chunk-SNEZMYNU.js +332 -0
  20. package/lib/chunk-SNEZMYNU.js.map +7 -0
  21. package/lib/{chunk-MCHV2R25.js → chunk-WVNY4LSL.js} +3 -1
  22. package/lib/{chunk-MCHV2R25.js.map → chunk-WVNY4LSL.js.map} +2 -2
  23. package/lib/cli.js +7 -6
  24. package/lib/cli.js.map +1 -1
  25. package/lib/client.js +743 -315
  26. package/lib/client.js.map +2 -2
  27. package/lib/clients.js +6 -5
  28. package/lib/{host-v0.5.8.js → host-v0.6.0.js} +22 -123
  29. package/lib/host.js +22 -123
  30. package/lib/index.js +22 -123
  31. package/lib/index.js.map +4 -4
  32. package/lib/manager.js +11 -7
  33. package/lib/router.js +2 -2
  34. package/lib/shim.js +3 -3
  35. package/package.json +1 -1
  36. package/lib/chunk-ILRW3ELD.js.map +0 -7
  37. package/lib/chunk-J3XO7RTF.js.map +0 -7
  38. package/lib/chunk-OFFVP6YN.js.map +0 -7
  39. package/lib/chunk-U2MU2M5C.js.map +0 -7
  40. /package/lib/{chunk-P43AWJ4Q.js.map → chunk-LLDJVBTQ.js.map} +0 -0
  41. /package/lib/{chunk-FTM7RHQV.js.map → chunk-OK2T2UAZ.js.map} +0 -0
@@ -6,20 +6,27 @@ import {
6
6
  refreshWorkBuddyCheckinCredential,
7
7
  supportsCheckin,
8
8
  workbuddyToken
9
- } from "./chunk-U2MU2M5C.js";
9
+ } from "./chunk-NSKCFVHP.js";
10
10
  import {
11
11
  discoverLocalAccounts,
12
12
  importAccount,
13
13
  localAccountKey,
14
+ normalizeLocalAccountKey,
14
15
  readPortableCredential
15
- } from "./chunk-OFFVP6YN.js";
16
+ } from "./chunk-JUOESKAK.js";
17
+ import {
18
+ modelWithCurrentPromotion
19
+ } from "./chunk-TEZ2FMEO.js";
20
+ import {
21
+ ApiService
22
+ } from "./chunk-SNEZMYNU.js";
16
23
  import {
17
24
  hostIsLoopback
18
25
  } from "./chunk-DNQPZMPF.js";
19
26
  import {
20
27
  Telemetry,
21
28
  matchModel
22
- } from "./chunk-J3XO7RTF.js";
29
+ } from "./chunk-ROZINFTV.js";
23
30
  import {
24
31
  PROVIDERS,
25
32
  PROVIDER_NAMES,
@@ -30,21 +37,18 @@ import {
30
37
  routeReasoning,
31
38
  validateConfig,
32
39
  writePrivateJson
33
- } from "./chunk-MCHV2R25.js";
34
- import {
35
- modelWithCurrentPromotion
36
- } from "./chunk-TEZ2FMEO.js";
40
+ } from "./chunk-WVNY4LSL.js";
37
41
 
38
42
  // src/manager.mjs
39
43
  import { createHash, randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
40
44
 
41
45
  // package.json
42
- var version = "0.5.8";
46
+ var version = "0.6.0";
43
47
 
44
48
  // src/manager.mjs
45
- import { readFile as readFile3, mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm3 } from "node:fs/promises";
46
- import { watch } from "node:fs";
47
- import { dirname as dirname3, join as join4, relative as relative2, resolve } from "node:path";
49
+ import { readFile as readFile4, mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm3 } from "node:fs/promises";
50
+ import { watch as watch2 } from "node:fs";
51
+ import { dirname as dirname3, join as join5, relative as relative2, resolve } from "node:path";
48
52
  import { withFileLock as withFileLock3 } from "@deepseek-ai/dsh-atomic-write";
49
53
 
50
54
  // upstream/qoder/src/status-paths.ts
@@ -101,7 +105,7 @@ var derive = promisify(scrypt);
101
105
  var FORMAT = "dsh-multi-router";
102
106
  var MAX_BYTES = 512 * 1024;
103
107
  var pick = (value, keys) => Object.fromEntries(keys.filter((k) => Object.hasOwn(value ?? {}, k)).map((k) => [k, value[k]]));
104
- var options = ["cooldownMs", "requestTimeoutMs", "catalogTtlMs", "retryServerErrors", "modelVisibility", "usageRetentionDays", "checkinSchedule"];
108
+ var options = ["cooldownMs", "requestTimeoutMs", "catalogTtlMs", "retryServerErrors", "modelVisibility", "usageRetentionDays", "checkinSchedule", "sidebarUsage"];
105
109
  function settings(config) {
106
110
  return {
107
111
  ...pick(config, options),
@@ -239,14 +243,140 @@ async function prepareMigration(manager, raw, portable, mode) {
239
243
  }
240
244
 
241
245
  // src/plugin-updates.mjs
242
- import { readFile, stat, rm as rm2 } from "node:fs/promises";
243
- import { join as join3 } from "node:path";
246
+ import { readFile as readFile2, stat, rm as rm2 } from "node:fs/promises";
247
+ import { join as join4 } from "node:path";
244
248
  import { randomUUID } from "node:crypto";
245
249
  import { createRequire } from "node:module";
246
250
  import { pathToFileURL } from "node:url";
247
251
  import { parse as parseYaml } from "yaml";
248
252
  import { withFileLock, writeFileAtomic as writeFileAtomic2 } from "@deepseek-ai/dsh-atomic-write";
249
253
 
254
+ // src/plugin-reload.mjs
255
+ import { readFile } from "node:fs/promises";
256
+ import { watch } from "node:fs";
257
+ import { join as join3 } from "node:path";
258
+ import { parse } from "yaml";
259
+ function outsideActivation(hmr, operation) {
260
+ const scope = hmr?.executing;
261
+ return typeof scope?.exit === "function" ? scope.exit(operation) : operation();
262
+ }
263
+ var PluginReload = class {
264
+ constructor({ service, profile, canReload, hmr = () => void 0, busy = () => false, now = Date.now, runningVersion }) {
265
+ Object.assign(this, { service, profile, canReload, hmr, busy, now, runningVersion });
266
+ this.phase = "current";
267
+ this.installedVersion = runningVersion;
268
+ this.retryAt = 0;
269
+ }
270
+ state() {
271
+ return { runningVersion: this.runningVersion, installedVersion: this.installedVersion, phase: this.phase, canReload: !!this.canReload(), message: this.message };
272
+ }
273
+ start() {
274
+ const dir = this.profile()?.dir;
275
+ this.watchers = [];
276
+ if (dir) for (const target of [dir, join3(dir, "node_modules")]) try {
277
+ const watcher = watch(target, () => this.schedule());
278
+ watcher.unref();
279
+ this.watchers.push(watcher);
280
+ } catch {
281
+ }
282
+ this.schedule();
283
+ }
284
+ schedule(delay = 500) {
285
+ if (this.closed) return;
286
+ clearTimeout(this.timer);
287
+ this.timer = setTimeout(() => {
288
+ void this.check().catch(() => {
289
+ });
290
+ }, delay);
291
+ this.timer.unref();
292
+ }
293
+ stop() {
294
+ this.closed = true;
295
+ clearTimeout(this.timer);
296
+ for (const watcher of this.watchers ?? []) watcher.close();
297
+ }
298
+ check() {
299
+ if (this.closed) return Promise.resolve();
300
+ if (this.flight) return this.flight;
301
+ this.flight = this.inspect().finally(() => {
302
+ this.flight = void 0;
303
+ });
304
+ return this.flight;
305
+ }
306
+ async inspect() {
307
+ const dir = this.profile()?.dir;
308
+ if (!dir) return;
309
+ let manifest, installed;
310
+ try {
311
+ manifest = JSON.parse(await readFile(join3(dir, "package.json"), "utf8"));
312
+ installed = JSON.parse(await readFile(join3(dir, "node_modules/dsh-multi-router/package.json"), "utf8"));
313
+ } catch {
314
+ return;
315
+ }
316
+ if (this.closed || !manifest.dsh?.profile?.bundles?.includes("dsh-multi-router")) return;
317
+ if (installed.name !== "dsh-multi-router" || !/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(installed.version)) return;
318
+ this.installedVersion = installed.version;
319
+ if (installed.version === this.runningVersion) {
320
+ this.phase = "current";
321
+ this.message = void 0;
322
+ return;
323
+ }
324
+ const declared = manifest.dependencies?.["dsh-multi-router"];
325
+ const exact = typeof declared === "string" && /^(?:npm:dsh-multi-router@)?(\d+\.\d+\.\d+(?:-[\w.-]+)?)$/.exec(declared)?.[1];
326
+ let installing = false;
327
+ const pid = Number(await readFile(join3(dir, "package.json.lock"), "utf8").catch(() => ""));
328
+ if (Number.isInteger(pid) && pid > 0) try {
329
+ process.kill(pid, 0);
330
+ installing = true;
331
+ } catch (e) {
332
+ installing = e.code !== "ESRCH";
333
+ }
334
+ if (installing || exact && exact !== installed.version) {
335
+ this.phase = "waiting";
336
+ this.message = "\u5B89\u88C5\u4E8B\u52A1\u5C1A\u672A\u5B8C\u6210\uFF0C\u6216\u5B89\u88C5\u5931\u8D25\u5DF2\u56DE\u6EDA\uFF1B\u7B49\u5F85\u914D\u7F6E\u4E0E\u6587\u4EF6\u7248\u672C\u4E00\u81F4\u540E\u518D\u52A0\u8F7D\u3002";
337
+ if (installing) this.schedule(1e3);
338
+ return;
339
+ }
340
+ if (!this.canReload() || !this.service()?.setBundleEnabled) {
341
+ this.phase = "restart-required";
342
+ this.message = "\u5F53\u524D DSH \u672A\u63D0\u4F9B\u8FD0\u884C\u4E2D\u91CD\u8F7D\uFF0C\u8BF7\u5347\u7EA7 DSH \u6216\u91CD\u542F\u5E94\u7528\u3002";
343
+ return;
344
+ }
345
+ if (this.busy()) {
346
+ this.phase = "waiting";
347
+ this.message = "\u65B0\u7248\u672C\u5DF2\u5B89\u88C5\uFF0C\u7B49\u5F85\u5F53\u524D\u8BF7\u6C42\u548C\u8D26\u53F7\u64CD\u4F5C\u7ED3\u675F\u540E\u81EA\u52A8\u751F\u6548\u3002";
348
+ this.schedule(1e3);
349
+ return;
350
+ }
351
+ const root = join3(dir, "node_modules/dsh-multi-router");
352
+ try {
353
+ const patch = parse(await readFile(join3(root, "cordis.patch.yml"), "utf8"));
354
+ const row = Array.isArray(patch) && patch.flatMap((p) => p?.insert ?? []).find((e) => e?.id === `llm-multi-router-v2-${installed.version}`);
355
+ if (row?.name !== `./lib/host-v${installed.version}.js` || row.disabled) throw Error("legacy entry");
356
+ await readFile(join3(root, `lib/host-v${installed.version}.js`));
357
+ } catch {
358
+ this.phase = "restart-required";
359
+ this.message = "\u5B89\u88C5\u7248\u672C\u7F3A\u5C11\u7248\u672C\u5316\u5165\u53E3\uFF0C\u91CD\u65B0\u5B89\u88C5\u6700\u65B0\u7248\u540E\u518D\u8BD5\u3002";
360
+ return;
361
+ }
362
+ if (this.now() < this.retryAt) return;
363
+ this.phase = "reloading";
364
+ this.message = "\u6B63\u5728\u91CD\u65B0\u52A0\u8F7D\u63D2\u4EF6\u4E0E\u914D\u7F6E\u9762\u677F\u2026";
365
+ this.retryAt = this.now() + 6e4;
366
+ try {
367
+ const result = await outsideActivation(this.hmr(), () => this.service().setBundleEnabled("dsh-multi-router", true));
368
+ if (this.closed) return;
369
+ this.phase = "restart-required";
370
+ this.message = result.application === "applied" ? "DSH \u5C1A\u672A\u5207\u6362\u5230\u65B0\u7248\u672C\uFF0C\u8BF7\u91CD\u65B0\u52A0\u8F7D\u63D2\u4EF6\u6216\u67E5\u770B\u7EC4\u4EF6\u9519\u8BEF\u3002" : "DSH \u672A\u80FD\u91CD\u8F7D\u63D2\u4EF6\uFF0C\u8BF7\u5347\u7EA7 DSH \u6216\u91CD\u542F\u5E94\u7528\u3002";
371
+ } catch {
372
+ if (!this.closed) {
373
+ this.phase = "failed";
374
+ this.message = "\u63D2\u4EF6\u91CD\u8F7D\u672A\u5B8C\u6210\uFF0C\u53EF\u7A0D\u540E\u91CD\u8BD5\uFF1B\u5DF2\u4FDD\u5B58\u7684\u914D\u7F6E\u4FDD\u6301\u4E0D\u53D8\u3002";
375
+ }
376
+ }
377
+ }
378
+ };
379
+
250
380
  // upstream/workbuddy/src/update.ts
251
381
  var WORKBUDDY_REPOSITORY_URL = "https://github.com/corrinehu/dsh-workbuddy-connect";
252
382
  var WORKBUDDY_UPDATE_PACKAGE_NAME = "dsh-workbuddy-connect";
@@ -322,7 +452,7 @@ function githubCommit(value) {
322
452
  }
323
453
  async function jsonFile(file) {
324
454
  try {
325
- return JSON.parse(await readFile(file, "utf8"));
455
+ return JSON.parse(await readFile2(file, "utf8"));
326
456
  } catch {
327
457
  return void 0;
328
458
  }
@@ -330,11 +460,11 @@ async function jsonFile(file) {
330
460
  async function locks(dir) {
331
461
  let pnpm;
332
462
  try {
333
- pnpm = parseYaml(await readFile(join3(dir, "pnpm-lock.yaml"), "utf8"), { maxAliasCount: 20 });
463
+ pnpm = parseYaml(await readFile2(join4(dir, "pnpm-lock.yaml"), "utf8"), { maxAliasCount: 20 });
334
464
  } catch {
335
465
  }
336
- const npm = await jsonFile(join3(dir, "package-lock.json"));
337
- const times = await Promise.all(["pnpm-lock.yaml", "package-lock.json"].map((name) => stat(join3(dir, name)).then((s) => s.mtimeMs).catch(() => 0)));
466
+ const npm = await jsonFile(join4(dir, "package-lock.json"));
467
+ const times = await Promise.all(["pnpm-lock.yaml", "package-lock.json"].map((name) => stat(join4(dir, name)).then((s) => s.mtimeMs).catch(() => 0)));
338
468
  return { pnpm, npm, preferNpm: times[1] > times[0] };
339
469
  }
340
470
  async function publicJson(url, fetchImpl, signal) {
@@ -355,11 +485,46 @@ async function publicJson(url, fetchImpl, signal) {
355
485
  return /^[a-f0-9]{40}$/i.test(text) ? { sha: text } : JSON.parse(text);
356
486
  }
357
487
  var short = (sha) => sha?.slice(0, 8);
358
- async function registryVersion(service, profile, spec, signal) {
488
+ var installFailed = (r) => !!(r.error || ["failed", "cancelled"].includes(r.application) || r.packageResult && r.packageResult.exitCode !== 0);
489
+ function installFailure(result) {
490
+ const text = [result.error?.diagnostic, result.packageResult?.output, result.packageResult?.kind].filter((v) => typeof v === "string").join("\n");
491
+ let reason = "unknown", message = "\u5B89\u88C5\u672A\u5B8C\u6210\uFF1B\u8BF7\u5728 DSH \u539F\u751F\u63D2\u4EF6\u9875\u67E5\u770B\u5B89\u88C5\u65E5\u5FD7\uFF0C\u6216\u590D\u5236\u4E0B\u65B9\u8BCA\u65AD\u4FE1\u606F\u3002";
492
+ if (result.application === "cancelled") {
493
+ reason = "cancelled";
494
+ message = "\u5B89\u88C5\u5DF2\u53D6\u6D88\uFF0C\u53EF\u91CD\u65B0\u68C0\u67E5\u540E\u91CD\u8BD5\u3002";
495
+ } else if (/ERR_PNPM_(?:MINIMUM_RELEASE_AGE_VIOLATION|NO_MATURE_MATCHING_VERSION)|minimumReleaseAge/i.test(text)) {
496
+ reason = "release-age";
497
+ message = "\u65B0\u7248\u672C\u5C1A\u5728\u5B89\u88C5\u5668\u7684\u65B0\u5305\u7B49\u5F85\u671F\u5185\u3002\u8F93\u5165\u5305\u540D\u53EF\u80FD\u9009\u4E2D\u65E7\u7248\uFF0C\u6307\u5B9A\u65B0\u7248\u672C\u53EF\u80FD\u88AB\u62D2\u7EDD\uFF1B\u8BF7\u7B49\u5F85\u7B56\u7565\u89C4\u5B9A\u7684\u65F6\u95F4\u540E\u91CD\u8BD5\uFF0C\u6216\u7531\u7528\u6237\u660E\u786E\u4E3A\u8BE5\u7248\u672C\u8BBE\u7F6E\u4F8B\u5916\u3002\u6362\u955C\u50CF\u6216\u91CD\u542F\u4E0D\u4F1A\u89E3\u9664\u6B64\u9650\u5236\u3002";
498
+ } else if (result.pendingBuilds?.length) {
499
+ reason = "build";
500
+ message = "\u4F9D\u8D56\u6784\u5EFA\u811A\u672C\u9700\u8981\u6388\u6743\uFF0C\u8BF7\u5230 DSH \u63D2\u4EF6\u5B89\u88C5\u9875\u5141\u8BB8\u8FD9\u4E9B\u811A\u672C\u540E\u91CD\u8BD5\u3002";
501
+ } else if (result.error?.code === "incompatible-version") {
502
+ reason = "compatibility";
503
+ message = "\u63D2\u4EF6\u4E0E\u5F53\u524D DSH \u4E0D\u517C\u5BB9\uFF0C\u8BF7\u5148\u5347\u7EA7 DSH \u6216\u9009\u62E9\u517C\u5BB9\u7248\u672C\u3002";
504
+ } else if (/\b(?:EPERM|EBUSY|EACCES)\b|resource busy|file.*(?:locked|in use)/i.test(text)) {
505
+ reason = "files";
506
+ message = "\u63D2\u4EF6\u6587\u4EF6\u88AB\u5360\u7528\u6216\u76EE\u5F55\u6743\u9650\u4E0D\u8DB3\uFF0C\u5B89\u88C5\u672A\u5B8C\u6210\u3002\u8BF7\u5148\u5728 DSH \u505C\u7528\u8BE5\u63D2\u4EF6\u540E\u91CD\u8BD5\uFF0C\u5E76\u68C0\u67E5\u5B89\u88C5\u76EE\u5F55\u6743\u9650\uFF1B\u4ECD\u88AB\u5360\u7528\u65F6\u9700\u9000\u51FA\u6258\u76D8\u5E94\u7528\u3002";
507
+ } else if (/HMR transactions cannot be nested|activation|did not activate|entry.*activate/i.test(text)) {
508
+ reason = "activation";
509
+ message = "\u63D2\u4EF6\u52A0\u8F7D\u5931\u8D25\u3002\u8BF7\u68C0\u67E5 DSH \u63D2\u4EF6\u9875\u7684\u7EC4\u4EF6\u9519\u8BEF\uFF0C\u518D\u5173\u95ED\u5E76\u91CD\u65B0\u542F\u7528\uFF1B\u5B89\u88C5\u4E0E\u52A0\u8F7D\u662F\u4E24\u4E2A\u72EC\u7ACB\u6B65\u9AA4\u3002";
510
+ } else if (result.packageResult?.timedOut || /\b(?:ETIMEDOUT|ECONNRESET|ENOTFOUND|EAI_AGAIN|ECONNREFUSED)\b|network|fetch|download/i.test(text)) {
511
+ reason = "network";
512
+ message = "\u63D2\u4EF6\u4E0B\u8F7D\u6216\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25\uFF0C\u5B89\u88C5\u672A\u5B8C\u6210\u3002npm \u6765\u6E90\u53EF\u5207\u6362\u5230\u300Cnpm \u5B98\u65B9\u300D\u540E\u91CD\u8BD5\uFF1BGitHub \u6765\u6E90\u8BF7\u68C0\u67E5 GitHub \u7F51\u7EDC\u8FDE\u63A5\u3002";
513
+ }
514
+ const code = /\b(?:ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION|ERR_PNPM_NO_MATURE_MATCHING_VERSION|EPERM|EBUSY|EACCES|ETIMEDOUT|ECONNRESET|ENOTFOUND|EAI_AGAIN|ECONNREFUSED)\b/.exec(text)?.[0] ?? (["operation-error", "incompatible-version"].includes(result.error?.code) ? result.error.code : "unknown");
515
+ return { status: "failed", application: result.application, message, diagnostic: {
516
+ reason,
517
+ code,
518
+ platform: process.platform,
519
+ stage: reason === "activation" ? "activation" : "install",
520
+ ...Number.isInteger(result.packageResult?.exitCode) ? { exitCode: result.packageResult.exitCode } : {}
521
+ } };
522
+ }
523
+ async function registryVersion(service, profile, spec, signal, requestedRegistry) {
359
524
  const require2 = createRequire(profile.installAnchor);
360
525
  const { viewProfilePackage } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/operations")).href);
361
526
  const { registryPlan } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/registry")).href);
362
- for (const registry of registryPlan(void 0, await service.registries())) {
527
+ for (const registry of registryPlan(requestedRegistry, await service.registries())) {
363
528
  const answer = await viewProfilePackage(profile.dir, spec, { ...profile.packageManager, registry, timeoutMs: 12e3, signal });
364
529
  if (signal.aborted) throw Error("\u68C0\u67E5\u5DF2\u53D6\u6D88");
365
530
  if (answer.exitCode !== 0) continue;
@@ -374,11 +539,11 @@ async function refreshGitPlugin(service, profile, row, options2, signal) {
374
539
  const { registryPlan, attributeFailure } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/registry")).href);
375
540
  const { classifyInstallFailure } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager")).href);
376
541
  const plan = registryPlan(options2.registry, await service.registries());
377
- return withFileLock(join3(profile.dir, "package.json"), async () => {
542
+ return withFileLock(join4(profile.dir, "package.json"), async () => {
378
543
  const files = await Promise.all(["package.json", "pnpm-lock.yaml"].map(async (name) => {
379
- const path = join3(profile.dir, name);
544
+ const path = join4(profile.dir, name);
380
545
  try {
381
- return { path, content: await readFile(path, "utf8") };
546
+ return { path, content: await readFile2(path, "utf8") };
382
547
  } catch (e) {
383
548
  if (e.code !== "ENOENT") throw e;
384
549
  return { path };
@@ -417,7 +582,7 @@ async function refreshGitPlugin(service, profile, row, options2, signal) {
417
582
  }, { waitMs: 12e4 });
418
583
  }
419
584
  var PluginUpdates = class {
420
- constructor({ service = () => void 0, profile = () => void 0, fetchImpl = (...args) => fetch(...args), lookup = registryVersion, refreshGit = refreshGitPlugin, onInstalled, stateFile, now = Date.now } = {}) {
585
+ constructor({ service = () => void 0, profile = () => void 0, hmr = () => void 0, fetchImpl = (...args) => fetch(...args), lookup = registryVersion, refreshGit = refreshGitPlugin, onInstalled, stateFile, now = Date.now } = {}) {
421
586
  this.stateFile = stateFile;
422
587
  this.now = now;
423
588
  this.dismissed = {};
@@ -425,6 +590,7 @@ var PluginUpdates = class {
425
590
  this.saveQueue = Promise.resolve();
426
591
  this.service = service;
427
592
  this.profile = profile;
593
+ this.hmr = hmr;
428
594
  this.fetchImpl = fetchImpl;
429
595
  this.rows = [];
430
596
  this.abort = new AbortController();
@@ -485,7 +651,7 @@ var PluginUpdates = class {
485
651
  this.rows = [];
486
652
  return;
487
653
  }
488
- const [manifest, bundles, lock] = await Promise.all([jsonFile(join3(profile.dir, "package.json")), service.listBundles(), locks(profile.dir)]);
654
+ const [manifest, bundles, lock] = await Promise.all([jsonFile(join4(profile.dir, "package.json")), service.listBundles(), locks(profile.dir)]);
489
655
  if (!manifest) throw Error("\u5F53\u524D DSH \u914D\u7F6E\u7684\u63D2\u4EF6\u6E05\u5355\u65E0\u6CD5\u8BFB\u53D6");
490
656
  this.rows = bundles.filter((b) => (b.installed || b.version) && manifest.dependencies?.[b.name] && b.removable !== false).map((b) => {
491
657
  const spec = manifest.dependencies?.[b.name];
@@ -578,7 +744,7 @@ var PluginUpdates = class {
578
744
  });
579
745
  }
580
746
  async installRow(row, registry) {
581
- const original = await jsonFile(join3(this.profile().dir, "package.json"));
747
+ const original = await jsonFile(join4(this.profile().dir, "package.json"));
582
748
  if (original?.dependencies?.[row.name] !== row.spec) throw Error("\u5B89\u88C5\u6765\u6E90\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5");
583
749
  const current = (await this.service().listBundles()).find((b) => b.name === row.name);
584
750
  if (!current || current.enabled !== row.checkedEnabled || current.version !== row.version) throw Error("\u63D2\u4EF6\u7248\u672C\u6216\u542F\u7528\u72B6\u6001\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5");
@@ -591,17 +757,8 @@ var PluginUpdates = class {
591
757
  } finally {
592
758
  this.gitAbort = void 0;
593
759
  }
594
- } else result = await this.service().installBundle(row.target, options2);
595
- if (result.packageResult && result.packageResult.exitCode !== 0 || ["failed", "cancelled"].includes(result.application) || result.error) {
596
- return {
597
- name: row.name,
598
- status: "failed",
599
- application: result.application,
600
- message: result.pendingBuilds?.length ? "\u4F9D\u8D56\u6784\u5EFA\u811A\u672C\u9700\u8981\u6388\u6743\uFF0C\u8BF7\u5230 DSH \u63D2\u4EF6\u5B89\u88C5\u9875\u5141\u8BB8\u8FD9\u4E9B\u811A\u672C\u540E\u91CD\u8BD5" : result.error?.code === "incompatible-version" ? "\u63D2\u4EF6\u4E0E\u5F53\u524D DSH \u4E0D\u517C\u5BB9\uFF0C\u8BF7\u5148\u5347\u7EA7 DSH \u6216\u9009\u62E9\u517C\u5BB9\u7248\u672C" : `\u5B89\u88C5\u672A\u5B8C\u6210\uFF08${result.error?.code ?? result.packageResult?.kind ?? "unknown"}\uFF09\uFF1B\u8BF7\u5230 DSH \u63D2\u4EF6\u9875\u67E5\u770B\u5B89\u88C5\u65E5\u5FD7`,
601
- pendingBuilds: result.pendingBuilds ?? [],
602
- logPath: result.packageResult?.logPath
603
- };
604
- }
760
+ } else result = await outsideActivation(this.hmr(), () => this.service().installBundle(row.target, options2));
761
+ if (installFailed(result)) return { name: row.name, ...installFailure(result), expectedVersion: row.latestVersion };
605
762
  const installed = (await this.service().listBundles()).find((b) => b.name === row.name);
606
763
  if (row.source.kind === "npm" && installed?.version !== row.latestVersion) return {
607
764
  name: row.name,
@@ -654,7 +811,32 @@ var PluginUpdates = class {
654
811
  this.start("install", async (job) => {
655
812
  job.total = 1;
656
813
  job.current = spec;
657
- const result = await this.service().installBundle(spec, { requestId: job.id, ...registry ? { registry } : {} });
814
+ let expected;
815
+ if (npm?.kind === "npm" && github.kind !== "github") {
816
+ const found = await this.lookup(this.service(), this.profile(), `${npm.package}@${named[2] || "latest"}`, this.abort.signal, registry);
817
+ if (!parseWorkBuddyVersion(found.version)) throw Error("npm \u672A\u8FD4\u56DE\u6709\u6548\u7248\u672C\uFF0C\u8BF7\u68C0\u67E5\u5B89\u88C5\u6E90\u540E\u91CD\u8BD5");
818
+ expected = found.version;
819
+ }
820
+ const result = await outsideActivation(this.hmr(), () => this.service().installBundle(spec, { requestId: job.id, ...registry ? { registry } : {} }));
821
+ if (installFailed(result)) {
822
+ job.results.push({ name: result.bundle ?? spec, ...installFailure(result), expectedVersion: expected });
823
+ delete job.current;
824
+ await this.list();
825
+ return;
826
+ }
827
+ const installed = (await this.service().listBundles()).find((b) => b.name === (result.bundle ?? named?.[1]));
828
+ if (expected && installed?.version !== expected) {
829
+ job.results.push({
830
+ name: result.bundle ?? named[1],
831
+ status: "failed",
832
+ expectedVersion: expected,
833
+ installedVersion: installed?.version,
834
+ message: `\u5B9E\u9645\u5B89\u88C5\u7248\u672C ${installed?.version ?? "\u672A\u77E5"} \u4E0E\u68C0\u67E5\u7248\u672C ${expected} \u4E0D\u540C\uFF1B\u8BF7\u5207\u6362\u300Cnpm \u5B98\u65B9\u300D\u540E\u91CD\u65B0\u68C0\u67E5\u5E76\u5B89\u88C5\u6307\u5B9A\u7248\u672C\u3002`
835
+ });
836
+ delete job.current;
837
+ await this.list();
838
+ return;
839
+ }
658
840
  const reloadPending = !result.error && result.packageResult?.exitCode === 0 && result.application === "restart-required" && this.onInstalled?.(result.bundle) === true;
659
841
  job.results.push({
660
842
  name: result.bundle ?? spec,
@@ -689,7 +871,7 @@ var PluginUpdates = class {
689
871
 
690
872
  // src/workbuddy-login.mjs
691
873
  import { randomBytes as randomBytes2 } from "node:crypto";
692
- import { readFile as readFile2 } from "node:fs/promises";
874
+ import { readFile as readFile3 } from "node:fs/promises";
693
875
  import { withFileLock as withFileLock2 } from "@deepseek-ai/dsh-atomic-write";
694
876
  var WorkBuddyLogin = class {
695
877
  constructor(file, { fetchImpl = (...args) => fetch(...args), now = Date.now } = {}) {
@@ -703,7 +885,7 @@ var WorkBuddyLogin = class {
703
885
  await withFileLock2(this.file, async () => {
704
886
  let state = {};
705
887
  try {
706
- state = JSON.parse(await readFile2(this.file, "utf8"));
888
+ state = JSON.parse(await readFile3(this.file, "utf8"));
707
889
  } catch (e) {
708
890
  if (e.code !== "ENOENT") throw Error("\u767B\u5F55\u51B7\u5374\u8BB0\u5F55\u4E0D\u53EF\u8BFB\uFF0C\u8BF7\u68C0\u67E5\u672C\u673A\u6587\u4EF6");
709
891
  }
@@ -715,7 +897,7 @@ var WorkBuddyLogin = class {
715
897
  async pause() {
716
898
  this.pending = void 0;
717
899
  await withFileLock2(this.file, async () => {
718
- const state = JSON.parse(await readFile2(this.file, "utf8"));
900
+ const state = JSON.parse(await readFile3(this.file, "utf8"));
719
901
  state.pausedUntil = this.now() + 15 * 6e4;
720
902
  await writePrivateJson(this.file, state);
721
903
  });
@@ -824,6 +1006,7 @@ var WorkBuddyLogin = class {
824
1006
 
825
1007
  // src/manager.mjs
826
1008
  var MANAGEMENT_PATH = "/.well-known/dsh-multi-router";
1009
+ var SUMMARY_PATH = `${MANAGEMENT_PATH}/summary`;
827
1010
  var revisionOf = (raw) => createHash("sha256").update(JSON.stringify(raw)).digest("hex");
828
1011
  var ManagementError = class extends Error {
829
1012
  constructor(status, message) {
@@ -865,31 +1048,32 @@ var Manager = class {
865
1048
  this.file = file;
866
1049
  this.router = router;
867
1050
  this.onChange = onChange;
868
- this.catalogFile = join4(dirname3(file), "catalogs.json");
1051
+ this.catalogFile = join5(dirname3(file), "catalogs.json");
869
1052
  this.catalogs = /* @__PURE__ */ new Map();
870
1053
  this.catalogErrors = /* @__PURE__ */ new Map();
871
1054
  this.refreshes = /* @__PURE__ */ new Map();
872
1055
  this.closed = false;
873
1056
  this.monitorFlights = /* @__PURE__ */ new Map();
874
1057
  this.loginInfo = /* @__PURE__ */ new Map();
875
- this.telemetry = new Telemetry(join4(dirname3(file), "usage.json"));
876
- this.plugins = new PluginUpdates({ ...pluginAccess, stateFile: join4(dirname3(file), "plugin-updates.json"), onInstalled: (name) => {
1058
+ this.telemetry = new Telemetry(join5(dirname3(file), "usage.json"));
1059
+ this.plugins = new PluginUpdates({ ...pluginAccess, stateFile: join5(dirname3(file), "plugin-updates.json"), onInstalled: (name) => {
877
1060
  if (name !== "dsh-multi-router" || !this.runtime?.canReload()) return false;
878
1061
  this.runtime.schedule();
879
1062
  return true;
880
1063
  } });
881
- this.checkins = new Checkins(join4(dirname3(file), "checkins.json"), this);
882
- this.workbuddyLogin = new WorkBuddyLogin(join4(dirname3(file), "workbuddy-login.json"), { fetchImpl: pluginAccess.fetchImpl });
883
- this.setupFile = join4(dirname3(file), "initial-setup.json");
1064
+ this.checkins = new Checkins(join5(dirname3(file), "checkins.json"), this);
1065
+ this.workbuddyLogin = new WorkBuddyLogin(join5(dirname3(file), "workbuddy-login.json"), { fetchImpl: pluginAccess.fetchImpl });
1066
+ this.setupFile = join5(dirname3(file), "initial-setup.json");
1067
+ this.api = new ApiService(join5(dirname3(file), "api-service.json"), this);
884
1068
  router.telemetry = this.telemetry;
885
- router.monitor = (a) => this.monitorAccount(a);
1069
+ router.monitor = (a) => this.monitorAccount(a, { maxAgeMs: 6e4 });
886
1070
  router.prepareFree = () => this.checkFreeModels(true);
887
1071
  router.freeModels = (account) => this.freeModels(account);
888
1072
  }
889
1073
  async load() {
890
- this.raw = JSON.parse(await readFile3(this.file, "utf8"));
1074
+ this.raw = JSON.parse(await readFile4(this.file, "utf8"));
891
1075
  try {
892
- this.setup = JSON.parse(await readFile3(this.setupFile, "utf8"));
1076
+ this.setup = JSON.parse(await readFile4(this.setupFile, "utf8"));
893
1077
  } catch {
894
1078
  this.setup = void 0;
895
1079
  }
@@ -897,7 +1081,7 @@ var Manager = class {
897
1081
  await this.checkins.load();
898
1082
  await this.plugins.load();
899
1083
  try {
900
- const saved = JSON.parse(await readFile3(this.catalogFile, "utf8"));
1084
+ const saved = JSON.parse(await readFile4(this.catalogFile, "utf8"));
901
1085
  for (const account of this.config.accounts) {
902
1086
  const item = saved[account.id];
903
1087
  if (item?.credentialFile === account.credentialFile && Array.isArray(item.models)) {
@@ -908,6 +1092,7 @@ var Manager = class {
908
1092
  }
909
1093
  this.telemetry.configure(this.config.usageRetentionDays);
910
1094
  await this.telemetry.load();
1095
+ await this.api.load();
911
1096
  await Promise.all(this.config.accounts.map((a) => this.readLogin(a)));
912
1097
  this.publish();
913
1098
  }
@@ -939,12 +1124,12 @@ var Manager = class {
939
1124
  if (this.closed || !discovered.accounts.length) return;
940
1125
  await withFileLock3(this.file, async () => {
941
1126
  if (this.closed) return;
942
- let raw = JSON.parse(await readFile3(this.file, "utf8"));
1127
+ let raw = JSON.parse(await readFile4(this.file, "utf8"));
943
1128
  let changed = false;
944
1129
  const saved = /* @__PURE__ */ new Map();
945
1130
  for (const item of discovered.accounts) {
946
1131
  const key = localAccountKey(item.provider, item.document);
947
- if (!force && raw.ignoredLocalAccounts?.includes(key)) continue;
1132
+ if (!force && raw.ignoredLocalAccounts?.some((k) => normalizeLocalAccountKey(k) === key)) continue;
948
1133
  const config = validateConfig(raw, this.file);
949
1134
  let existing, current;
950
1135
  for (const a of config.accounts.filter((a2) => a2.provider === item.provider)) {
@@ -976,11 +1161,11 @@ var Manager = class {
976
1161
  recordSaved();
977
1162
  continue;
978
1163
  }
979
- const base = join4(dirname3(this.file), "accounts");
1164
+ const base = join5(dirname3(this.file), "accounts");
980
1165
  await mkdir2(base, { recursive: true, mode: 448 });
981
- const staging = await mkdtemp2(join4(base, `${id}-`));
1166
+ const staging = await mkdtemp2(join5(base, `${id}-`));
982
1167
  try {
983
- const credentialFile = join4(staging, "credential.json"), identityFile = join4(staging, "identity.json");
1168
+ const credentialFile = join5(staging, "credential.json"), identityFile = join5(staging, "identity.json");
984
1169
  await importAccount({ ...item, credentialFile, identityFile });
985
1170
  const paths = {
986
1171
  credentialFile: relative2(dirname3(this.file), credentialFile),
@@ -994,7 +1179,7 @@ var Manager = class {
994
1179
  name: item.document.credential.nickname || item.document.credential.accountName || item.document.credential.uid || item.document.credential.userId,
995
1180
  ...paths
996
1181
  });
997
- next.ignoredLocalAccounts = (raw.ignoredLocalAccounts ?? []).filter((k) => k !== key);
1182
+ next.ignoredLocalAccounts = (raw.ignoredLocalAccounts ?? []).filter((k) => normalizeLocalAccountKey(k) !== key);
998
1183
  validateConfig(next, this.file);
999
1184
  await writePrivateJson(this.file, next);
1000
1185
  raw = next;
@@ -1025,7 +1210,7 @@ var Manager = class {
1025
1210
  if (this.setup?.completedAt) return;
1026
1211
  await withFileLock3(this.setupFile, async () => {
1027
1212
  try {
1028
- this.setup = JSON.parse(await readFile3(this.setupFile, "utf8"));
1213
+ this.setup = JSON.parse(await readFile4(this.setupFile, "utf8"));
1029
1214
  } catch {
1030
1215
  }
1031
1216
  if (this.setup?.completedAt) return;
@@ -1033,8 +1218,12 @@ var Manager = class {
1033
1218
  await writePrivateJson(this.setupFile, this.setup);
1034
1219
  });
1035
1220
  }
1036
- async monitorAccount(account) {
1221
+ async monitorAccount(account, { force = false, maxAgeMs = 3e5 } = {}) {
1037
1222
  if (this.monitorFlights.has(account.id)) return this.monitorFlights.get(account.id);
1223
+ const cached = this.telemetry.row("accounts", account.id).balance;
1224
+ const last = cached?.error ? cached.attemptedAt : cached?.fetchedAt;
1225
+ const ttl = cached?.error ? 3e5 : maxAgeMs;
1226
+ if (!force && Number.isFinite(last) && Date.now() >= last && Date.now() - last < ttl) return;
1038
1227
  const promise = (async () => {
1039
1228
  try {
1040
1229
  const client = await this.router.client(account);
@@ -1053,8 +1242,8 @@ var Manager = class {
1053
1242
  this.monitorFlights.set(account.id, promise);
1054
1243
  return promise;
1055
1244
  }
1056
- async monitorAll(provider) {
1057
- await Promise.all(this.config.accounts.filter((a) => !provider || a.provider === provider).map((a) => this.monitorAccount(a)));
1245
+ async monitorAll(provider, options2) {
1246
+ await Promise.all(this.config.accounts.filter((a) => (!provider || a.provider === provider) && (options2?.force || a.enabled !== false)).map((a) => this.monitorAccount(a, options2)));
1058
1247
  }
1059
1248
  freeAccounts() {
1060
1249
  const targets = Object.values(this.config.routes).filter((r) => r.freeOnly).flatMap((r) => r.targets);
@@ -1217,6 +1406,19 @@ var Manager = class {
1217
1406
  this.refreshes.set(account.id, promise);
1218
1407
  return promise;
1219
1408
  }
1409
+ summaryState() {
1410
+ const today = new Date(this.telemetry.now()).toLocaleDateString("en-CA", { timeZone: "Asia/Shanghai" });
1411
+ const daily = this.telemetry.data.days[today] ?? {};
1412
+ return {
1413
+ pluginVersion: version,
1414
+ enabled: this.config.sidebarUsage,
1415
+ today,
1416
+ tokens: daily.tokens ?? 0,
1417
+ requests: daily.requests ?? 0,
1418
+ unmetered: daily.unmetered ?? 0,
1419
+ totalTokens: Object.values(this.telemetry.data.accounts).reduce((sum, row) => sum + (row.tokens ?? 0), 0)
1420
+ };
1421
+ }
1220
1422
  state() {
1221
1423
  const status = this.router.status();
1222
1424
  return {
@@ -1229,6 +1431,7 @@ var Manager = class {
1229
1431
  pluginUpdates: this.plugins.state(),
1230
1432
  checkins: this.checkins.state(),
1231
1433
  telemetry: this.telemetry.state(),
1434
+ apiService: this.api.state(),
1232
1435
  accounts: this.config.accounts.map((a) => ({
1233
1436
  id: a.id,
1234
1437
  provider: a.provider,
@@ -1256,12 +1459,22 @@ var Manager = class {
1256
1459
  cooldownMs: this.config.cooldownMs,
1257
1460
  requestTimeoutMs: this.config.requestTimeoutMs,
1258
1461
  retryServerErrors: this.config.retryServerErrors,
1259
- usageRetentionDays: this.config.usageRetentionDays
1462
+ usageRetentionDays: this.config.usageRetentionDays,
1463
+ sidebarUsage: this.config.sidebarUsage
1260
1464
  }
1261
1465
  };
1262
1466
  }
1263
1467
  async action(input) {
1264
1468
  if (!input || typeof input !== "object" || Array.isArray(input)) bad("\u8BF7\u6C42\u683C\u5F0F\u65E0\u6548");
1469
+ if (input.action?.startsWith("api.")) {
1470
+ let result;
1471
+ try {
1472
+ result = await this.api.action(input);
1473
+ } catch (e) {
1474
+ bad(e.message);
1475
+ }
1476
+ return { ...this.state(), ...result };
1477
+ }
1265
1478
  if (input.action === "plugin.reload") {
1266
1479
  if (!this.runtime) bad("\u5F53\u524D\u540E\u53F0\u4E0D\u652F\u6301\u91CD\u65B0\u52A0\u8F7D");
1267
1480
  this.runtime.retryAt = 0;
@@ -1277,7 +1490,7 @@ var Manager = class {
1277
1490
  }
1278
1491
  }
1279
1492
  if (input.action === "workbuddy.login") {
1280
- if (input.revision !== revisionOf(JSON.parse(await readFile3(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u65B0\u63D0\u4EA4\u767B\u5F55");
1493
+ if (input.revision !== revisionOf(JSON.parse(await readFile4(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u65B0\u63D0\u4EA4\u767B\u5F55");
1281
1494
  let login;
1282
1495
  try {
1283
1496
  login = await this.workbuddyLogin.complete(input.challenge, input.code, input.verify ?? true);
@@ -1357,7 +1570,7 @@ var Manager = class {
1357
1570
  return this.state();
1358
1571
  }
1359
1572
  if (input.action === "account.export") {
1360
- if (input.revision !== revisionOf(JSON.parse(await readFile3(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u63D0\u53D6\u51ED\u8BC1");
1573
+ if (input.revision !== revisionOf(JSON.parse(await readFile4(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u63D0\u53D6\u51ED\u8BC1");
1361
1574
  const account = this.config.accounts.find((a) => a.id === input.id);
1362
1575
  if (!account) bad("\u8D26\u53F7\u4E0D\u5B58\u5728");
1363
1576
  let credential;
@@ -1379,7 +1592,7 @@ var Manager = class {
1379
1592
  } };
1380
1593
  }
1381
1594
  if (input.action === "config.export") {
1382
- if (input.revision !== revisionOf(JSON.parse(await readFile3(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u5BFC\u51FA");
1595
+ if (input.revision !== revisionOf(JSON.parse(await readFile4(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u5BFC\u51FA");
1383
1596
  try {
1384
1597
  return { ...this.state(), exported: { filename: `dsh-multi-router-${input.includeCredentials ? "encrypted" : "settings"}-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.json`, document: await exportConfiguration(this, input) } };
1385
1598
  } catch (e) {
@@ -1400,10 +1613,10 @@ var Manager = class {
1400
1613
  const account = this.config.accounts.find((a) => a.id === input.id);
1401
1614
  if (!account) bad("\u8D26\u53F7\u4E0D\u5B58\u5728");
1402
1615
  this.router.clients.delete(account.id);
1403
- await this.monitorAccount(account);
1616
+ await this.monitorAccount(account, { force: true });
1404
1617
  } else {
1405
1618
  await this.syncLocalAccounts(input.provider);
1406
- await this.monitorAll(input.provider);
1619
+ await this.monitorAll(input.provider, { force: true });
1407
1620
  }
1408
1621
  return this.state();
1409
1622
  }
@@ -1427,7 +1640,7 @@ var Manager = class {
1427
1640
  return this.state();
1428
1641
  }
1429
1642
  return withFileLock3(this.file, async () => {
1430
- let raw = JSON.parse(await readFile3(this.file, "utf8"));
1643
+ let raw = JSON.parse(await readFile4(this.file, "utf8"));
1431
1644
  if (input.revision !== revisionOf(raw)) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u518D\u4FDD\u5B58");
1432
1645
  raw.routes ??= structuredClone(this.config.routes);
1433
1646
  let staging;
@@ -1450,10 +1663,10 @@ var Manager = class {
1450
1663
  }
1451
1664
  if (!/^[a-zA-Z0-9_-]{1,64}$/.test(input.id ?? "") || !PROVIDERS.includes(input.provider)) bad("\u8D26\u53F7 ID \u6216\u4F9B\u5E94\u5546\u65E0\u6548");
1452
1665
  if (account && !replacing) bad("\u8D26\u53F7 ID \u5DF2\u5B58\u5728\uFF0C\u8BF7\u4F7F\u7528\u4E0D\u540C ID");
1453
- const base = join4(dirname3(this.file), "accounts");
1666
+ const base = join5(dirname3(this.file), "accounts");
1454
1667
  await mkdir2(base, { recursive: true, mode: 448 });
1455
- staging = await mkdtemp2(join4(base, `${input.id}-`));
1456
- const credentialFile = join4(staging, "credential.json"), identityFile = join4(staging, "identity.json");
1668
+ staging = await mkdtemp2(join5(base, `${input.id}-`));
1669
+ const credentialFile = join5(staging, "credential.json"), identityFile = join5(staging, "identity.json");
1457
1670
  const added = {
1458
1671
  id: input.id,
1459
1672
  name: input.name || account?.name || input.id,
@@ -1604,7 +1817,7 @@ var Manager = class {
1604
1817
  if (!input.schedule) bad("\u8BF7\u586B\u5199\u7B7E\u5230\u8BA1\u5212");
1605
1818
  raw.checkinSchedule = input.schedule;
1606
1819
  } else if (input.action === "options.save") {
1607
- for (const key of ["cooldownMs", "requestTimeoutMs", "retryServerErrors", "usageRetentionDays"]) if (input[key] !== void 0) raw[key] = input[key];
1820
+ for (const key of ["cooldownMs", "requestTimeoutMs", "retryServerErrors", "usageRetentionDays", "sidebarUsage"]) if (input[key] !== void 0) raw[key] = input[key];
1608
1821
  } else bad("\u672A\u77E5\u64CD\u4F5C");
1609
1822
  let config;
1610
1823
  try {
@@ -1649,12 +1862,12 @@ var Manager = class {
1649
1862
  }
1650
1863
  watch(onError) {
1651
1864
  let timer;
1652
- const watcher = watch(dirname3(this.file), (_, filename) => {
1865
+ const watcher = watch2(dirname3(this.file), (_, filename) => {
1653
1866
  if (filename && String(filename) !== this.file.split(/[\\/]/).pop()) return;
1654
1867
  clearTimeout(timer);
1655
1868
  timer = setTimeout(() => {
1656
1869
  void withFileLock3(this.file, async () => {
1657
- const raw = JSON.parse(await readFile3(this.file, "utf8"));
1870
+ const raw = JSON.parse(await readFile4(this.file, "utf8"));
1658
1871
  if (this.closed || revisionOf(raw) === this.revision) return;
1659
1872
  const config = validateConfig(raw, this.file);
1660
1873
  this.raw = raw;
@@ -1671,7 +1884,7 @@ var Manager = class {
1671
1884
  };
1672
1885
  }
1673
1886
  };
1674
- function managementHandler(manager) {
1887
+ function managementHandler(manager, { summaryOnly = false } = {}) {
1675
1888
  const key = randomBytes3(32).toString("base64url");
1676
1889
  const json = (res, status, value) => {
1677
1890
  res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });
@@ -1685,9 +1898,10 @@ function managementHandler(manager) {
1685
1898
  if (req.headers.origin && req.headers.origin !== `http://${req.headers.host}` && req.headers.origin !== `https://${req.headers.host}`) throw new ManagementError(403, "\u6765\u6E90\u65E0\u6548");
1686
1899
  if (req.headers["sec-fetch-site"] === "cross-site") throw new ManagementError(403, "\u6765\u6E90\u65E0\u6548");
1687
1900
  if (req.method === "GET") {
1688
- json(res, 200, { ...manager.state(), key });
1901
+ json(res, 200, summaryOnly ? manager.summaryState() : { ...manager.state(), key });
1689
1902
  return;
1690
1903
  }
1904
+ if (summaryOnly) throw new ManagementError(405, "\u65B9\u6CD5\u65E0\u6548");
1691
1905
  if (req.method !== "POST") throw new ManagementError(405, "\u65B9\u6CD5\u65E0\u6548");
1692
1906
  const actual = Buffer.from(req.headers["x-multi-router-key"] ?? ""), expected = Buffer.from(key);
1693
1907
  if (actual.length !== expected.length || !timingSafeEqual(actual, expected)) throw new ManagementError(401, "\u8BF7\u5237\u65B0\u914D\u7F6E\u9875\u9762");
@@ -1719,10 +1933,12 @@ function managementHandler(manager) {
1719
1933
 
1720
1934
  export {
1721
1935
  version,
1936
+ PluginReload,
1722
1937
  MANAGEMENT_PATH,
1938
+ SUMMARY_PATH,
1723
1939
  ManagementError,
1724
1940
  modelInfo,
1725
1941
  Manager,
1726
1942
  managementHandler
1727
1943
  };
1728
- //# sourceMappingURL=chunk-ILRW3ELD.js.map
1944
+ //# sourceMappingURL=chunk-OCMOMSB4.js.map