dsh-multi-router 0.4.0 → 0.5.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.
@@ -1,20 +1,20 @@
1
1
  import {
2
2
  jsonCredential,
3
3
  qoderPluginDataDir
4
- } from "./chunk-UFS7ZJKY.js";
4
+ } from "./chunk-YUP77Q3A.js";
5
5
  import {
6
6
  discoverLocalAccounts,
7
7
  importAccount,
8
8
  localAccountKey,
9
9
  readPortableCredential
10
- } from "./chunk-3VGKH7MF.js";
10
+ } from "./chunk-OMG4FZWV.js";
11
11
  import {
12
12
  hostIsLoopback
13
13
  } from "./chunk-DNQPZMPF.js";
14
14
  import {
15
15
  Telemetry,
16
16
  matchModel
17
- } from "./chunk-GGRCX2NR.js";
17
+ } from "./chunk-XV7O2LRF.js";
18
18
  import {
19
19
  PROVIDERS,
20
20
  PROVIDER_NAMES,
@@ -25,22 +25,22 @@ import {
25
25
  routeReasoning,
26
26
  validateConfig,
27
27
  writePrivateJson
28
- } from "./chunk-XMXFIV5G.js";
28
+ } from "./chunk-GAQ77CH4.js";
29
29
  import {
30
30
  modelWithCurrentPromotion
31
31
  } from "./chunk-TEZ2FMEO.js";
32
32
 
33
33
  // src/manager.mjs
34
- import { createHash, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
34
+ import { createHash as createHash2, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
35
35
 
36
36
  // package.json
37
- var version = "0.4.0";
37
+ var version = "0.5.0";
38
38
 
39
39
  // src/manager.mjs
40
- import { readFile as readFile2, mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm2 } from "node:fs/promises";
40
+ import { readFile as readFile3, mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm3 } from "node:fs/promises";
41
41
  import { watch } from "node:fs";
42
42
  import { dirname as dirname3, join as join4, relative as relative2, resolve } from "node:path";
43
- import { withFileLock } from "@deepseek-ai/dsh-atomic-write";
43
+ import { withFileLock as withFileLock3 } from "@deepseek-ai/dsh-atomic-write";
44
44
 
45
45
  // upstream/qoder/src/status-paths.ts
46
46
  var QODER_STATUS_PATH = "/plugins/dsh-qoder-connect/status";
@@ -101,7 +101,7 @@ function settings(config) {
101
101
  return {
102
102
  ...pick(config, options),
103
103
  models: Object.fromEntries(Object.entries(config.models ?? {}).map(([p, rows]) => [p, Object.fromEntries(Object.entries(rows).map(([id, pref]) => [id, pick(pref, ["name", "visible"])]))])),
104
- accounts: config.accounts.map((a) => pick(a, ["id", "provider", "name", "enabled"])),
104
+ accounts: config.accounts.map((a) => pick(a, ["id", "provider", "name", "enabled", "autoCheckin"])),
105
105
  routes: Object.fromEntries(Object.entries(config.routes).map(([id, r]) => [id, {
106
106
  ...pick(r, ["name", "visible", "strategy", "contextWindow", "maxTokens", "preferExpiringCredits", "expiringWithinDays", "freeOnly", "freeModels"]),
107
107
  targets: r.targets.map((t) => pick(t, ["provider", "account", "model", "models", "weight", "tokenBudget", "creditBudget"]))
@@ -234,12 +234,13 @@ async function prepareMigration(manager, raw, portable, mode) {
234
234
  }
235
235
 
236
236
  // src/plugin-updates.mjs
237
- import { readFile, stat } from "node:fs/promises";
237
+ import { readFile, stat, rm as rm2 } from "node:fs/promises";
238
238
  import { join as join3 } from "node:path";
239
239
  import { randomUUID } from "node:crypto";
240
240
  import { createRequire } from "node:module";
241
241
  import { pathToFileURL } from "node:url";
242
242
  import { parse as parseYaml } from "yaml";
243
+ import { withFileLock, writeFileAtomic as writeFileAtomic2 } from "@deepseek-ai/dsh-atomic-write";
243
244
 
244
245
  // upstream/workbuddy/src/update.ts
245
246
  var WORKBUDDY_REPOSITORY_URL = "https://github.com/corrinehu/dsh-workbuddy-connect";
@@ -360,14 +361,63 @@ async function registryVersion(service, profile, spec, signal) {
360
361
  }
361
362
  throw Error("npm \u67E5\u8BE2\u5931\u8D25\uFF1B\u8BF7\u68C0\u67E5 DSH \u7F51\u7EDC\u3001registry \u8BBE\u7F6E\u6216\u5305\u7684\u8BBF\u95EE\u6743\u9650");
362
363
  }
364
+ async function refreshGitPlugin(service, profile, row, options2, signal) {
365
+ const require2 = createRequire(profile.installAnchor);
366
+ const { runProfilePnpm } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/operations")).href);
367
+ const { registryPlan, attributeFailure } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/registry")).href);
368
+ const { classifyInstallFailure } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager")).href);
369
+ const plan = registryPlan(options2.registry, await service.registries());
370
+ return withFileLock(join3(profile.dir, "package.json"), async () => {
371
+ const files = await Promise.all(["package.json", "pnpm-lock.yaml"].map(async (name) => {
372
+ const path = join3(profile.dir, name);
373
+ try {
374
+ return { path, content: await readFile(path, "utf8") };
375
+ } catch (e) {
376
+ if (e.code !== "ENOENT") throw e;
377
+ return { path };
378
+ }
379
+ }));
380
+ const restore = () => Promise.all(files.map((f) => f.content === void 0 ? rm2(f.path, { force: true }) : writeFileAtomic2(f.path, f.content, { mode: 384 })));
381
+ if (JSON.parse(files[0].content).dependencies?.[row.name] !== row.spec) throw Error("\u5B89\u88C5\u6765\u6E90\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5");
382
+ let result;
383
+ try {
384
+ for (const registry of plan) {
385
+ signal.throwIfAborted();
386
+ const run = await runProfilePnpm({ ...profile, profile: profile.name }, ["update", row.name, ...registry ? [`--registry=${registry}`] : []], {
387
+ execution: "service",
388
+ ...profile.packageManager,
389
+ signal,
390
+ outputBytes: 16384,
391
+ idleTimeoutMs: 6e5,
392
+ lookupTimeoutMs: 2e4,
393
+ activateNewBundles: false
394
+ });
395
+ if (run.exitCode === 0 && !run.timedOut && !signal.aborted) return { packageResult: run, application: "restart-required" };
396
+ const kind = classifyInstallFailure({ log: run.output, timedOut: run.timedOut });
397
+ result = {
398
+ packageResult: { ...run, kind },
399
+ application: signal.aborted ? "cancelled" : "failed",
400
+ ...run.incompatible?.length ? { error: { code: "incompatible-version" } } : {}
401
+ };
402
+ await restore();
403
+ if (signal.aborted || attributeFailure(kind, run.output, { kind: "git", host: "github.com" }) !== "registry") break;
404
+ }
405
+ return result;
406
+ } catch (e) {
407
+ await restore();
408
+ throw e;
409
+ }
410
+ }, { waitMs: 12e4 });
411
+ }
363
412
  var PluginUpdates = class {
364
- constructor({ service = () => void 0, profile = () => void 0, fetchImpl = (...args) => fetch(...args), lookup = registryVersion } = {}) {
413
+ constructor({ service = () => void 0, profile = () => void 0, fetchImpl = (...args) => fetch(...args), lookup = registryVersion, refreshGit = refreshGitPlugin } = {}) {
365
414
  this.service = service;
366
415
  this.profile = profile;
367
416
  this.fetchImpl = fetchImpl;
368
417
  this.rows = [];
369
418
  this.abort = new AbortController();
370
419
  this.lookup = lookup;
420
+ this.refreshGit = refreshGit;
371
421
  }
372
422
  state() {
373
423
  return {
@@ -475,11 +525,16 @@ var PluginUpdates = class {
475
525
  if (original?.dependencies?.[row.name] !== row.spec) throw Error("\u5B89\u88C5\u6765\u6E90\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5");
476
526
  const current = (await this.service().listBundles()).find((b) => b.name === row.name);
477
527
  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");
478
- const result = await this.service().installBundle(row.target, {
479
- enabled: row.enabled,
480
- requestId: this.job.id,
481
- ...registry ? { registry } : {}
482
- });
528
+ const options2 = { enabled: row.enabled, requestId: this.job.id, ...registry ? { registry } : {} };
529
+ let result;
530
+ if (row.source.kind === "github") {
531
+ this.gitAbort = new AbortController();
532
+ try {
533
+ result = await this.refreshGit(this.service(), this.profile(), row, options2, AbortSignal.any([this.abort.signal, this.gitAbort.signal]));
534
+ } finally {
535
+ this.gitAbort = void 0;
536
+ }
537
+ } else result = await this.service().installBundle(row.target, options2);
483
538
  if (result.packageResult && result.packageResult.exitCode !== 0 || ["failed", "cancelled"].includes(result.application) || result.error) {
484
539
  return {
485
540
  name: row.name,
@@ -496,6 +551,16 @@ var PluginUpdates = class {
496
551
  status: "failed",
497
552
  message: `\u5B9E\u9645\u5B89\u88C5\u7248\u672C ${installed?.version ?? "\u672A\u77E5"} \u4E0E\u68C0\u67E5\u7248\u672C ${row.latestVersion} \u4E0D\u540C\uFF1B\u8BF7\u7A0D\u540E\u91CD\u65B0\u68C0\u67E5\uFF0C\u53EF\u80FD\u662F\u955C\u50CF\u5C1A\u672A\u540C\u6B65\u6216\u53D1\u884C\u65F6\u95F4\u9650\u5236`
498
553
  };
554
+ if (row.source.kind === "github") {
555
+ const lock = await locks(this.profile().dir);
556
+ const pnpm = lock.pnpm?.importers?.["."]?.dependencies?.[row.name]?.version, npm = lock.npm?.packages?.[`node_modules/${row.name}`]?.resolved;
557
+ if (githubCommit(lock.preferNpm ? npm ?? pnpm : pnpm ?? npm) !== row.latestCommit) return {
558
+ name: row.name,
559
+ status: "failed",
560
+ restartRequired: true,
561
+ message: "\u5B9E\u9645 Git \u63D0\u4EA4\u4E0E\u68C0\u67E5\u7ED3\u679C\u4E0D\u4E00\u81F4\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5\u540E\u91CD\u8BD5\uFF1B\u4E0D\u628A\u4ECD\u505C\u7559\u5728\u65E7\u9501\u6587\u4EF6\u7684\u5B89\u88C5\u62A5\u544A\u4E3A\u6210\u529F"
562
+ };
563
+ }
499
564
  return { name: row.name, status: "installed", application: result.application, message: result.application === "restart-required" ? "\u5DF2\u5B89\u88C5\uFF0C\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u65B0\u6253\u5F00 DSH \u540E\u751F\u6548" : "\u5B89\u88C5\u5B8C\u6210", restartRequired: result.application === "restart-required" };
500
565
  }
501
566
  update(names, registry) {
@@ -555,15 +620,179 @@ var PluginUpdates = class {
555
620
  else if (input.action === "plugins.cancel") {
556
621
  if (this.job?.state === "running") {
557
622
  this.job.cancelRequested = true;
558
- if (this.job.kind !== "check") await this.service().cancelInstall?.(this.job.id);
623
+ if (this.gitAbort) this.gitAbort.abort();
624
+ else if (this.job.kind !== "check") await this.service().cancelInstall?.(this.job.id);
559
625
  }
560
626
  } else throw Error("\u672A\u77E5\u63D2\u4EF6\u64CD\u4F5C");
561
627
  }
562
628
  };
563
629
 
630
+ // src/checkins.mjs
631
+ import { createHash } from "node:crypto";
632
+ import { readFile as readFile2 } from "node:fs/promises";
633
+ import { setTimeout as delay } from "node:timers/promises";
634
+ import { withFileLock as withFileLock2 } from "@deepseek-ai/dsh-atomic-write";
635
+ var supportsCheckin = (provider) => ["trae", "qoder", "qoder-global"].includes(provider);
636
+ var hash = (value) => createHash("sha256").update(value).digest("hex");
637
+ var nextDay = (now) => Date.parse(`${beijingDay(now)}T00:00:00+08:00`) + 864e5;
638
+ var checkinMessages = {
639
+ claimed: "\u7B7E\u5230\u6210\u529F",
640
+ "already-claimed": "\u4ECA\u65E5\u5DF2\u9886\u53D6",
641
+ "device-used": "\u6B64\u8BBE\u5907\u4ECA\u65E5\u5DF2\u53C2\u4E0E\u7B7E\u5230\uFF0C\u4E0D\u518D\u5207\u6362\u5176\u4ED6\u8D26\u53F7\u9886\u53D6",
642
+ "no-campaign": "\u5E73\u53F0\u672A\u5F00\u653E\u53EF\u9886\u53D6\u6D3B\u52A8",
643
+ "before-window": "\u7B7E\u5230\u7A97\u53E3\u5C1A\u672A\u5F00\u542F",
644
+ paused: "\u5E73\u53F0\u9650\u5236\u6216\u9700\u8981\u9A8C\u8BC1\uFF0C\u5DF2\u6682\u505C\u6B64\u5E73\u53F0\u7B7E\u5230\uFF0C\u8BF7\u5728\u5B98\u65B9\u5BA2\u6237\u7AEF\u5904\u7406",
645
+ uncertain: "\u8BF7\u6C42\u7ED3\u679C\u5F85\u786E\u8BA4\uFF0C\u4ECA\u65E5\u4E0D\u518D\u81EA\u52A8\u63D0\u4EA4\uFF0C\u8BF7\u5728\u5B98\u65B9\u5BA2\u6237\u7AEF\u6838\u5BF9",
646
+ error: "\u7B7E\u5230\u672A\u5B8C\u6210\uFF0C\u4ECA\u65E5\u4E0D\u518D\u91CD\u590D\u5C1D\u8BD5\uFF0C\u8BF7\u5728\u5B98\u65B9\u5BA2\u6237\u7AEF\u67E5\u770B",
647
+ unsupported: "\u6682\u672A\u63A5\u5165\u53EF\u6838\u9A8C\u7684\u7B7E\u5230\u63A5\u53E3\uFF0C\u8BF7\u4F7F\u7528\u5B98\u65B9\u5BA2\u6237\u7AEF"
648
+ };
649
+ var Checkins = class {
650
+ constructor(file, manager, { now = Date.now, spacingMs = 1e4 } = {}) {
651
+ this.file = file;
652
+ this.manager = manager;
653
+ this.now = now;
654
+ this.spacingMs = spacingMs;
655
+ this.data = { records: {}, devices: {}, pauses: {} };
656
+ this.abort = new AbortController();
657
+ }
658
+ async read() {
659
+ try {
660
+ const raw = JSON.parse(await readFile2(this.file, "utf8"));
661
+ const object = (value) => value && typeof value === "object" && !Array.isArray(value);
662
+ if (!object(raw) || !["records", "devices", "pauses"].every((k) => object(raw[k])) || Object.values(raw.records).some((r) => !object(r) || !Array.isArray(r.ids) || !Number.isFinite(r.at) || typeof r.day !== "string")) throw Error("Invalid check-in journal");
663
+ this.data = raw;
664
+ } catch (e) {
665
+ if (e.code !== "ENOENT") throw e;
666
+ }
667
+ }
668
+ async load() {
669
+ try {
670
+ await this.read();
671
+ } catch {
672
+ this.failed = true;
673
+ }
674
+ }
675
+ state() {
676
+ return {
677
+ running: !!this.flight,
678
+ current: this.current,
679
+ error: this.failed ? "\u7B7E\u5230\u8BB0\u5F55\u65E0\u6CD5\u8BFB\u53D6\uFF0C\u5DF2\u505C\u6B62\u7B7E\u5230\uFF1B\u8BF7\u4FEE\u590D\u8BB0\u5F55\u6587\u4EF6\u540E\u91CD\u542F\u63D2\u4EF6\u3002" : void 0,
680
+ schedule: "\u5317\u4EAC\u65F6\u95F4\u6BCF\u5929 10:05 \u540E\uFF0CDSH \u8FD0\u884C\u65F6\u6267\u884C\uFF1B\u9519\u8FC7\u540E\u4E0B\u6B21\u542F\u52A8\u8865\u67E5",
681
+ accounts: this.manager.config.accounts.map((a) => {
682
+ const rows = Object.values(this.data.records).filter((r) => r.ids?.includes(a.id));
683
+ const last = rows.sort((a2, b) => b.at - a2.at)[0];
684
+ const pause = this.data.pauses[a.provider];
685
+ return {
686
+ id: a.id,
687
+ supported: supportsCheckin(a.provider),
688
+ auto: a.autoCheckin === true,
689
+ paused: !!pause,
690
+ last: last ? {
691
+ day: last.day,
692
+ at: last.at,
693
+ status: last.status,
694
+ amount: last.amount,
695
+ message: checkinMessages[last.status] || checkinMessages.error
696
+ } : void 0
697
+ };
698
+ })
699
+ };
700
+ }
701
+ start(ids, automatic = false) {
702
+ if (this.failed || this.abort.signal.aborted || this.flight) return;
703
+ if (!automatic && (!Array.isArray(ids) || !ids.length || ids.length > 200 || ids.some((id) => !this.manager.config.accounts.some((a) => a.id === id)))) throw Error("\u8BF7\u9009\u62E9\u6709\u6548\u7684\u7B7E\u5230\u8D26\u53F7");
704
+ const selected = this.manager.config.accounts.filter((a) => supportsCheckin(a.provider) && a.enabled !== false && (automatic ? a.autoCheckin === true : ids.includes(a.id))).map((a) => a.id);
705
+ if (!selected.length) return;
706
+ this.flight = (async () => {
707
+ let requested = false;
708
+ for (const id of selected) {
709
+ if (this.abort.signal.aborted) break;
710
+ const account = this.manager.config.accounts.find((a) => a.id === id && a.enabled !== false && (!automatic || a.autoCheckin === true));
711
+ if (!account) continue;
712
+ if (requested) {
713
+ await delay(this.spacingMs, void 0, { signal: this.abort.signal });
714
+ requested = false;
715
+ }
716
+ this.current = id;
717
+ requested = await this.run(account, automatic);
718
+ }
719
+ })().catch(() => {
720
+ if (!this.abort.signal.aborted) this.failed = true;
721
+ }).finally(() => {
722
+ this.flight = void 0;
723
+ this.current = void 0;
724
+ });
725
+ }
726
+ async resume(provider) {
727
+ if (!supportsCheckin(provider) || this.failed || this.flight) throw Error("\u8BF7\u7B49\u5F85\u7B7E\u5230\u5B8C\u6210\uFF0C\u6216\u5148\u4FEE\u590D\u7B7E\u5230\u8BB0\u5F55");
728
+ await withFileLock2(this.file, async () => {
729
+ await this.read();
730
+ delete this.data.pauses[provider];
731
+ await writePrivateJson(this.file, this.data);
732
+ });
733
+ }
734
+ async run(account, automatic = false) {
735
+ const now = this.now(), day = beijingDay(now);
736
+ if (now < Date.parse(`${day}T10:05:00+08:00`)) return false;
737
+ return withFileLock2(this.file, async () => {
738
+ await this.read();
739
+ if (this.abort.signal.aborted || this.data.pauses[account.provider]) return false;
740
+ if (Object.values(this.data.records).some((r) => r.day === day && r.ids?.includes(account.id))) return false;
741
+ if (this.data.lastRequestAt && now - this.data.lastRequestAt < this.spacingMs) return false;
742
+ this.data.lastRequestAt = now;
743
+ await writePrivateJson(this.file, this.data);
744
+ const client = await this.manager.router.client(account);
745
+ if (!client.checkin) return false;
746
+ let identity;
747
+ try {
748
+ identity = await client.checkin.identity(AbortSignal.any([this.abort.signal, AbortSignal.timeout(3e4)]));
749
+ } catch {
750
+ this.data.pauses[account.provider] = { at: now };
751
+ await writePrivateJson(this.file, this.data);
752
+ return true;
753
+ }
754
+ if (!identity?.account || account.provider === "trae" && !identity.device) throw Error("Missing check-in identity");
755
+ if (this.abort.signal.aborted || !this.manager.config.accounts.some((a) => a.id === account.id && a.enabled !== false && a.credentialFile === account.credentialFile && (!automatic || a.autoCheckin === true))) return true;
756
+ const key = hash(`${account.provider}:${identity.account}`), device = identity.device ? hash(`${account.provider}:${identity.device}`) : void 0;
757
+ const old = this.data.records[key];
758
+ if (old?.day === day) {
759
+ if (!old.ids.includes(account.id)) {
760
+ old.ids.push(account.id);
761
+ await writePrivateJson(this.file, this.data);
762
+ }
763
+ return true;
764
+ }
765
+ const record = { day, at: now, status: "uncertain", ids: [account.id] };
766
+ this.data.records[key] = record;
767
+ for (const [k, row] of Object.entries(this.data.records)) if (row.at < now - 30 * 864e5) delete this.data.records[k];
768
+ for (const [k, row] of Object.entries(this.data.devices)) if (row.until <= now) delete this.data.devices[k];
769
+ if (device && this.data.devices[device]?.until > now) {
770
+ record.status = "device-used";
771
+ await writePrivateJson(this.file, this.data);
772
+ return true;
773
+ }
774
+ if (device) this.data.devices[device] = { until: nextDay(now) };
775
+ await writePrivateJson(this.file, this.data);
776
+ let result;
777
+ try {
778
+ result = await client.checkin.claim(AbortSignal.any([this.abort.signal, AbortSignal.timeout(3e4)]));
779
+ } catch {
780
+ result = { status: "uncertain" };
781
+ }
782
+ record.status = Object.hasOwn(checkinMessages, result?.status) ? result.status : "uncertain";
783
+ if (device && ["already-claimed", "no-campaign"].includes(record.status)) delete this.data.devices[device];
784
+ if (["claimed", "already-claimed"].includes(record.status) && Number.isFinite(result.amount) && result.amount >= 0) record.amount = result.amount;
785
+ if (record.status === "paused") this.data.pauses[account.provider] = { at: now };
786
+ await writePrivateJson(this.file, this.data);
787
+ if (record.status === "claimed") await this.manager.monitorAccount(account);
788
+ return true;
789
+ }, { waitMs: 1e3 });
790
+ }
791
+ };
792
+
564
793
  // src/manager.mjs
565
794
  var MANAGEMENT_PATH = "/.well-known/dsh-multi-router";
566
- var revisionOf = (raw) => createHash("sha256").update(JSON.stringify(raw)).digest("hex");
795
+ var revisionOf = (raw) => createHash2("sha256").update(JSON.stringify(raw)).digest("hex");
567
796
  var ManagementError = class extends Error {
568
797
  constructor(status, message) {
569
798
  super(message);
@@ -613,6 +842,7 @@ var Manager = class {
613
842
  this.loginInfo = /* @__PURE__ */ new Map();
614
843
  this.telemetry = new Telemetry(join4(dirname3(file), "usage.json"));
615
844
  this.plugins = new PluginUpdates(pluginAccess);
845
+ this.checkins = new Checkins(join4(dirname3(file), "checkins.json"), this);
616
846
  this.setupFile = join4(dirname3(file), "initial-setup.json");
617
847
  router.telemetry = this.telemetry;
618
848
  router.monitor = (a) => this.monitorAccount(a);
@@ -620,15 +850,16 @@ var Manager = class {
620
850
  router.freeModels = (account) => this.freeModels(account);
621
851
  }
622
852
  async load() {
623
- this.raw = JSON.parse(await readFile2(this.file, "utf8"));
853
+ this.raw = JSON.parse(await readFile3(this.file, "utf8"));
624
854
  try {
625
- this.setup = JSON.parse(await readFile2(this.setupFile, "utf8"));
855
+ this.setup = JSON.parse(await readFile3(this.setupFile, "utf8"));
626
856
  } catch {
627
857
  this.setup = void 0;
628
858
  }
629
859
  this.config = validateConfig(this.raw, this.file);
860
+ await this.checkins.load();
630
861
  try {
631
- const saved = JSON.parse(await readFile2(this.catalogFile, "utf8"));
862
+ const saved = JSON.parse(await readFile3(this.catalogFile, "utf8"));
632
863
  for (const account of this.config.accounts) {
633
864
  const item = saved[account.id];
634
865
  if (item?.credentialFile === account.credentialFile && Array.isArray(item.models)) {
@@ -668,9 +899,9 @@ var Manager = class {
668
899
  ...discovered.status.map((s) => ({ ...s, checkedAt: Date.now() }))
669
900
  ];
670
901
  if (this.closed || !discovered.accounts.length) return;
671
- await withFileLock(this.file, async () => {
902
+ await withFileLock3(this.file, async () => {
672
903
  if (this.closed) return;
673
- let raw = JSON.parse(await readFile2(this.file, "utf8"));
904
+ let raw = JSON.parse(await readFile3(this.file, "utf8"));
674
905
  let changed = false;
675
906
  const saved = /* @__PURE__ */ new Map();
676
907
  for (const item of discovered.accounts) {
@@ -689,7 +920,7 @@ var Manager = class {
689
920
  } catch {
690
921
  }
691
922
  }
692
- const id = existing?.id ?? `local_${createHash("sha256").update(key).digest("hex").slice(0, 20)}`;
923
+ const id = existing?.id ?? `local_${createHash2("sha256").update(key).digest("hex").slice(0, 20)}`;
693
924
  const recordSaved = () => {
694
925
  if (!saved.has(item.provider)) saved.set(item.provider, /* @__PURE__ */ new Set());
695
926
  saved.get(item.provider).add(id);
@@ -735,7 +966,7 @@ var Manager = class {
735
966
  this.catalogs.delete(id);
736
967
  this.catalogErrors.delete(id);
737
968
  } catch (e) {
738
- await rm2(staging, { recursive: true, force: true });
969
+ await rm3(staging, { recursive: true, force: true });
739
970
  this.localDiscovery = this.localDiscovery.map((s) => s.provider === item.provider ? { ...s, state: "unreadable", code: "import_failed" } : s);
740
971
  }
741
972
  }
@@ -754,9 +985,9 @@ var Manager = class {
754
985
  }
755
986
  async finishInitialSetup() {
756
987
  if (this.setup?.completedAt) return;
757
- await withFileLock(this.setupFile, async () => {
988
+ await withFileLock3(this.setupFile, async () => {
758
989
  try {
759
- this.setup = JSON.parse(await readFile2(this.setupFile, "utf8"));
990
+ this.setup = JSON.parse(await readFile3(this.setupFile, "utf8"));
760
991
  } catch {
761
992
  }
762
993
  if (this.setup?.completedAt) return;
@@ -823,7 +1054,7 @@ var Manager = class {
823
1054
  for (const account of this.config.accounts.filter((a) => a.enabled !== false)) {
824
1055
  for (const model of this.freeModels(account)) {
825
1056
  if (!row.targets.some((t) => t.provider === account.provider && (!t.account || t.account === account.id) && (!t.model || t.model === model.id) && (!t.models || t.models.includes(model.id)))) continue;
826
- const id = `free_${createHash("sha256").update(JSON.stringify([parentId, account.provider, model.id])).digest("hex").slice(0, 24)}`;
1057
+ const id = `free_${createHash2("sha256").update(JSON.stringify([parentId, account.provider, model.id])).digest("hex").slice(0, 24)}`;
827
1058
  if (!groups.has(id)) groups.set(id, { id, models: [], targets: [], accountNames: [], modelName: model.name, provider: account.provider, modelId: model.id });
828
1059
  const group = groups.get(id);
829
1060
  group.models.push({ model, provider: account.provider });
@@ -938,7 +1169,7 @@ var Manager = class {
938
1169
  if (this.closed || !this.config.accounts.some((a) => a.id === account.id && a.credentialFile === account.credentialFile)) return;
939
1170
  this.catalogs.set(account.id, { credentialFile: account.credentialFile, fetchedAt: Date.now(), pricingVersion: 1, models });
940
1171
  this.catalogErrors.delete(account.id);
941
- await withFileLock(this.catalogFile, () => writePrivateJson(this.catalogFile, Object.fromEntries(this.catalogs)));
1172
+ await withFileLock3(this.catalogFile, () => writePrivateJson(this.catalogFile, Object.fromEntries(this.catalogs)));
942
1173
  } catch (error) {
943
1174
  if (this.closed || !this.config.accounts.some((a) => a.id === account.id)) return;
944
1175
  this.catalogErrors.set(account.id, { status: Number(error.status ?? error.failure?.status) || 502, code: "catalog_fetch_failed", at: Date.now() });
@@ -956,6 +1187,7 @@ var Manager = class {
956
1187
  localDiscovery: this.localDiscovery ?? [],
957
1188
  setup: this.setup,
958
1189
  pluginUpdates: this.plugins.state(),
1190
+ checkins: this.checkins.state(),
959
1191
  telemetry: this.telemetry.state(),
960
1192
  accounts: this.config.accounts.map((a) => ({
961
1193
  id: a.id,
@@ -990,6 +1222,22 @@ var Manager = class {
990
1222
  }
991
1223
  async action(input) {
992
1224
  if (!input || typeof input !== "object" || Array.isArray(input)) bad("\u8BF7\u6C42\u683C\u5F0F\u65E0\u6548");
1225
+ if (input.action === "checkins.run") {
1226
+ try {
1227
+ this.checkins.start(input.ids);
1228
+ } catch (e) {
1229
+ bad(e.message);
1230
+ }
1231
+ return this.state();
1232
+ }
1233
+ if (input.action === "checkins.resume") {
1234
+ try {
1235
+ await this.checkins.resume(input.provider);
1236
+ } catch (e) {
1237
+ bad(e.message);
1238
+ }
1239
+ return this.state();
1240
+ }
993
1241
  if (typeof input.action === "string" && input.action.startsWith("plugins.")) {
994
1242
  try {
995
1243
  await this.plugins.action(input);
@@ -999,14 +1247,14 @@ var Manager = class {
999
1247
  return this.state();
1000
1248
  }
1001
1249
  if (input.action === "setup.dismiss") {
1002
- await withFileLock(this.setupFile, async () => {
1250
+ await withFileLock3(this.setupFile, async () => {
1003
1251
  this.setup = { ...this.setup, completedAt: this.setup?.completedAt ?? Date.now(), dismissed: true };
1004
1252
  await writePrivateJson(this.setupFile, this.setup);
1005
1253
  });
1006
1254
  return this.state();
1007
1255
  }
1008
1256
  if (input.action === "account.export") {
1009
- if (input.revision !== revisionOf(JSON.parse(await readFile2(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u63D0\u53D6\u51ED\u8BC1");
1257
+ 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");
1010
1258
  const account = this.config.accounts.find((a) => a.id === input.id);
1011
1259
  if (!account) bad("\u8D26\u53F7\u4E0D\u5B58\u5728");
1012
1260
  let credential;
@@ -1028,7 +1276,7 @@ var Manager = class {
1028
1276
  } };
1029
1277
  }
1030
1278
  if (input.action === "config.export") {
1031
- if (input.revision !== revisionOf(JSON.parse(await readFile2(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u5BFC\u51FA");
1279
+ 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");
1032
1280
  try {
1033
1281
  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) } };
1034
1282
  } catch (e) {
@@ -1075,8 +1323,8 @@ var Manager = class {
1075
1323
  await this.checkFreeModels(true);
1076
1324
  return this.state();
1077
1325
  }
1078
- return withFileLock(this.file, async () => {
1079
- let raw = JSON.parse(await readFile2(this.file, "utf8"));
1326
+ return withFileLock3(this.file, async () => {
1327
+ let raw = JSON.parse(await readFile3(this.file, "utf8"));
1080
1328
  if (input.revision !== revisionOf(raw)) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u518D\u4FDD\u5B58");
1081
1329
  raw.routes ??= structuredClone(this.config.routes);
1082
1330
  let staging;
@@ -1169,6 +1417,10 @@ var Manager = class {
1169
1417
  if (!input.name && !replacing) added.name = imported.credential?.nickname || imported.credential?.accountName || input.id;
1170
1418
  } else if (input.action === "account.update") {
1171
1419
  if (!account) bad("\u8D26\u53F7\u4E0D\u5B58\u5728");
1420
+ if (input.autoCheckin !== void 0) {
1421
+ if (typeof input.autoCheckin !== "boolean" || !supportsCheckin(account.provider)) bad("\u6B64\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u81EA\u52A8\u7B7E\u5230");
1422
+ account.autoCheckin = input.autoCheckin;
1423
+ }
1172
1424
  if (input.name !== void 0) account.name = input.name;
1173
1425
  if (input.enabled !== void 0) account.enabled = input.enabled;
1174
1426
  } else if (input.action === "account.move") {
@@ -1262,7 +1514,7 @@ var Manager = class {
1262
1514
  this.publish();
1263
1515
  await this.telemetry.persist();
1264
1516
  } finally {
1265
- if (staging) await rm2(staging, { recursive: true, force: true });
1517
+ if (staging) await rm3(staging, { recursive: true, force: true });
1266
1518
  }
1267
1519
  return this.state();
1268
1520
  }, { waitMs: 3e4 }).then(async (state) => {
@@ -1290,8 +1542,8 @@ var Manager = class {
1290
1542
  if (filename && String(filename) !== this.file.split(/[\\/]/).pop()) return;
1291
1543
  clearTimeout(timer);
1292
1544
  timer = setTimeout(() => {
1293
- void withFileLock(this.file, async () => {
1294
- const raw = JSON.parse(await readFile2(this.file, "utf8"));
1545
+ void withFileLock3(this.file, async () => {
1546
+ const raw = JSON.parse(await readFile3(this.file, "utf8"));
1295
1547
  if (this.closed || revisionOf(raw) === this.revision) return;
1296
1548
  const config = validateConfig(raw, this.file);
1297
1549
  this.raw = raw;
@@ -1358,4 +1610,4 @@ export {
1358
1610
  Manager,
1359
1611
  managementHandler
1360
1612
  };
1361
- //# sourceMappingURL=chunk-UQS7DNOQ.js.map
1613
+ //# sourceMappingURL=chunk-W5WP4ATM.js.map