dsh-multi-router 0.5.4 → 0.5.6

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.
@@ -2,8 +2,11 @@ import {
2
2
  Checkins,
3
3
  jsonCredential,
4
4
  qoderPluginDataDir,
5
- supportsCheckin
6
- } from "./chunk-LADXRFL7.js";
5
+ readLimitedText,
6
+ refreshWorkBuddyCheckinCredential,
7
+ supportsCheckin,
8
+ workbuddyToken
9
+ } from "./chunk-U2MU2M5C.js";
7
10
  import {
8
11
  discoverLocalAccounts,
9
12
  importAccount,
@@ -16,7 +19,7 @@ import {
16
19
  import {
17
20
  Telemetry,
18
21
  matchModel
19
- } from "./chunk-6ZXEVC3R.js";
22
+ } from "./chunk-J3XO7RTF.js";
20
23
  import {
21
24
  PROVIDERS,
22
25
  PROVIDER_NAMES,
@@ -33,16 +36,16 @@ import {
33
36
  } from "./chunk-TEZ2FMEO.js";
34
37
 
35
38
  // src/manager.mjs
36
- import { createHash, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
39
+ import { createHash, randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
37
40
 
38
41
  // package.json
39
- var version = "0.5.4";
42
+ var version = "0.5.6";
40
43
 
41
44
  // src/manager.mjs
42
- import { readFile as readFile2, mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm3 } from "node:fs/promises";
45
+ import { readFile as readFile3, mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm3 } from "node:fs/promises";
43
46
  import { watch } from "node:fs";
44
47
  import { dirname as dirname3, join as join4, relative as relative2, resolve } from "node:path";
45
- import { withFileLock as withFileLock2 } from "@deepseek-ai/dsh-atomic-write";
48
+ import { withFileLock as withFileLock3 } from "@deepseek-ai/dsh-atomic-write";
46
49
 
47
50
  // upstream/qoder/src/status-paths.ts
48
51
  var QODER_STATUS_PATH = "/plugins/dsh-qoder-connect/status";
@@ -414,7 +417,12 @@ async function refreshGitPlugin(service, profile, row, options2, signal) {
414
417
  }, { waitMs: 12e4 });
415
418
  }
416
419
  var PluginUpdates = class {
417
- constructor({ service = () => void 0, profile = () => void 0, fetchImpl = (...args) => fetch(...args), lookup = registryVersion, refreshGit = refreshGitPlugin } = {}) {
420
+ constructor({ service = () => void 0, profile = () => void 0, fetchImpl = (...args) => fetch(...args), lookup = registryVersion, refreshGit = refreshGitPlugin, stateFile, now = Date.now } = {}) {
421
+ this.stateFile = stateFile;
422
+ this.now = now;
423
+ this.dismissed = {};
424
+ this.lastAttempt = 0;
425
+ this.saveQueue = Promise.resolve();
418
426
  this.service = service;
419
427
  this.profile = profile;
420
428
  this.fetchImpl = fetchImpl;
@@ -423,10 +431,47 @@ var PluginUpdates = class {
423
431
  this.lookup = lookup;
424
432
  this.refreshGit = refreshGit;
425
433
  }
434
+ async load() {
435
+ const saved = this.stateFile && await jsonFile(this.stateFile);
436
+ if (saved?.version === 1) {
437
+ this.lastAttempt = Number.isFinite(saved.lastAttempt) ? saved.lastAttempt : 0;
438
+ this.checkedAt = saved.checkedAt;
439
+ this.dismissed = saved.dismissed && typeof saved.dismissed === "object" && !Array.isArray(saved.dismissed) ? saved.dismissed : {};
440
+ this.rows = Array.isArray(saved.rows) ? saved.rows.filter((r) => ["github", "npm"].includes(r.source?.kind)) : [];
441
+ }
442
+ }
443
+ persist() {
444
+ if (!this.stateFile) return Promise.resolve();
445
+ const saved = structuredClone({ version: 1, lastAttempt: this.lastAttempt, checkedAt: this.checkedAt, dismissed: this.dismissed, rows: this.rows.filter((r) => ["github", "npm"].includes(r.source.kind)) });
446
+ this.saveQueue = this.saveQueue.catch(() => {
447
+ }).then(() => writePrivateJson(this.stateFile, saved));
448
+ return this.saveQueue;
449
+ }
450
+ reminderKey(row) {
451
+ return JSON.stringify([row.name, row.source, row.version, row.latestVersion ?? row.latestCommit]);
452
+ }
453
+ reminders() {
454
+ return this.rows.filter((r) => r.status === "available" && (!Object.hasOwn(this.dismissed, r.name) || this.dismissed[r.name] !== this.reminderKey(r))).map((r) => ({ key: this.reminderKey(r), name: r.name, version: r.version, latestVersion: r.latestVersion, latestCommit: r.latestCommit }));
455
+ }
456
+ async dismiss(keys) {
457
+ if (!Array.isArray(keys) || keys.length > 200 || keys.some((k) => typeof k !== "string")) throw Error("\u65E0\u6548\u7684\u63D0\u9192\u5217\u8868");
458
+ for (const row of this.reminders()) if (keys.includes(row.key)) Object.defineProperty(this.dismissed, row.name, { value: row.key, enumerable: true, configurable: true, writable: true });
459
+ await this.persist();
460
+ }
461
+ async tick() {
462
+ if (this.flight || !this.state().available) return;
463
+ await this.list();
464
+ if (this.flight || this.now() >= this.lastAttempt && this.now() - this.lastAttempt < 864e5) return;
465
+ this.lastAttempt = this.now();
466
+ await this.persist();
467
+ if (!this.flight && !this.abort.signal.aborted) this.check();
468
+ }
426
469
  state() {
427
470
  return {
428
471
  available: !!this.service()?.listBundles && !!this.profile()?.dir,
429
472
  checkedAt: this.checkedAt,
473
+ reminders: this.reminders(),
474
+ autoCheckHours: 24,
430
475
  rows: this.rows.map(({ spec, ...row }) => ({ ...row, source: row.source.kind === "unsupported" ? { kind: "unsupported" } : row.source })),
431
476
  job: this.job,
432
477
  error: this.error
@@ -502,13 +547,20 @@ var PluginUpdates = class {
502
547
  }, (e) => {
503
548
  this.job.state = this.job.cancelRequested ? "cancelled" : "failed";
504
549
  this.job.message = e.message;
505
- }).finally(() => {
550
+ }).finally(async () => {
506
551
  this.job.finishedAt = Date.now();
552
+ try {
553
+ await this.persist();
554
+ } catch {
555
+ this.error = "\u66F4\u65B0\u68C0\u67E5\u8BB0\u5F55\u672A\u80FD\u4FDD\u5B58\uFF0C\u8BF7\u68C0\u67E5\u672C\u5730\u76EE\u5F55\u6743\u9650";
556
+ }
507
557
  this.flight = void 0;
508
558
  });
509
559
  }
510
560
  check() {
511
561
  this.start("check", async (job) => {
562
+ this.lastAttempt = this.now();
563
+ await this.persist();
512
564
  await this.list();
513
565
  const rows = this.rows.filter((r) => ["npm", "github"].includes(r.source.kind));
514
566
  job.total = rows.length;
@@ -617,7 +669,8 @@ var PluginUpdates = class {
617
669
  if (input.registry && !["https://registry.npmjs.org/", "https://registry.npmmirror.com/"].includes(input.registry)) throw Error("\u65E0\u6548\u7684 npm \u6765\u6E90");
618
670
  if (input.action === "plugins.list") {
619
671
  if (!this.flight) await this.list();
620
- } else if (input.action === "plugins.check") this.check();
672
+ } else if (input.action === "plugins.dismiss") await this.dismiss(input.keys);
673
+ else if (input.action === "plugins.check") this.check();
621
674
  else if (input.action === "plugins.update") this.update(input.names, input.registry);
622
675
  else if (input.action === "plugins.install") this.install(input.spec, input.registry);
623
676
  else if (input.action === "plugins.cancel") {
@@ -630,6 +683,141 @@ var PluginUpdates = class {
630
683
  }
631
684
  };
632
685
 
686
+ // src/workbuddy-login.mjs
687
+ import { randomBytes as randomBytes2 } from "node:crypto";
688
+ import { readFile as readFile2 } from "node:fs/promises";
689
+ import { withFileLock as withFileLock2 } from "@deepseek-ai/dsh-atomic-write";
690
+ var WorkBuddyLogin = class {
691
+ constructor(file, { fetchImpl = (...args) => fetch(...args), now = Date.now } = {}) {
692
+ this.file = file;
693
+ this.fetchImpl = fetchImpl;
694
+ this.now = now;
695
+ this.abort = new AbortController();
696
+ this.abort.signal.addEventListener("abort", () => this.finish(), { once: true });
697
+ }
698
+ async reserve(field, ms) {
699
+ await withFileLock2(this.file, async () => {
700
+ let state = {};
701
+ try {
702
+ state = JSON.parse(await readFile2(this.file, "utf8"));
703
+ } catch (e) {
704
+ if (e.code !== "ENOENT") throw Error("\u767B\u5F55\u51B7\u5374\u8BB0\u5F55\u4E0D\u53EF\u8BFB\uFF0C\u8BF7\u68C0\u67E5\u672C\u673A\u6587\u4EF6");
705
+ }
706
+ if (state.pausedUntil > this.now() || state[field] > this.now()) throw Error("\u8BF7\u6C42\u51B7\u5374\u4E2D\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\uFF1B\u5E73\u53F0\u9650\u5236\u8BF7\u5728\u5B98\u65B9\u5BA2\u6237\u7AEF\u5904\u7406");
707
+ state[field] = this.now() + ms;
708
+ await writePrivateJson(this.file, state);
709
+ });
710
+ }
711
+ async pause() {
712
+ this.pending = void 0;
713
+ await withFileLock2(this.file, async () => {
714
+ const state = JSON.parse(await readFile2(this.file, "utf8"));
715
+ state.pausedUntil = this.now() + 15 * 6e4;
716
+ await writePrivateJson(this.file, state);
717
+ });
718
+ }
719
+ async post(path, body) {
720
+ let response;
721
+ try {
722
+ response = await this.fetchImpl(`https://www.workbuddy.cn/v2/plugin/login/${path}`, {
723
+ method: "POST",
724
+ headers: { "Content-Type": "application/json", Accept: "application/json", Origin: "https://www.workbuddy.cn", Referer: "https://www.workbuddy.cn/" },
725
+ body: JSON.stringify(body),
726
+ signal: AbortSignal.any([this.abort.signal, AbortSignal.timeout(3e4)]),
727
+ redirect: "error"
728
+ });
729
+ } catch {
730
+ throw Error("\u767B\u5F55\u8BF7\u6C42\u672A\u5B8C\u6210\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8\u91CD\u8BD5\uFF1B\u4E0D\u4F1A\u81EA\u52A8\u91CD\u53D1\u77ED\u4FE1");
731
+ }
732
+ if ([403, 429].includes(response.status)) {
733
+ await this.pause();
734
+ throw Error("\u5E73\u53F0\u9650\u5236\u6216\u9700\u8981\u9A8C\u8BC1\uFF0C\u5DF2\u6682\u505C\u767B\u5F55\u8BF7\u6C42\uFF0C\u8BF7\u4F7F\u7528\u5B98\u65B9\u5BA2\u6237\u7AEF");
735
+ }
736
+ if (!response.ok) throw Error("\u5B98\u65B9\u767B\u5F55\u670D\u52A1\u6682\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
737
+ let result;
738
+ try {
739
+ result = JSON.parse(await readLimitedText(response, 256 * 1024, "Login"));
740
+ } catch {
741
+ throw Error("\u767B\u5F55\u54CD\u5E94\u65E0\u6CD5\u8BC6\u522B\uFF0C\u8BF7\u4F7F\u7528\u5B98\u65B9\u5BA2\u6237\u7AEF");
742
+ }
743
+ if (result?.code !== 0) {
744
+ if ([403, 429].includes(result?.code) || /频繁|图形验证码|滑块|安全验证|captcha|risk|too many|frequent|验证失败次数/i.test(result?.msg || "")) {
745
+ await this.pause();
746
+ throw Error("\u5E73\u53F0\u9650\u5236\u6216\u9700\u8981\u9A8C\u8BC1\uFF0C\u5DF2\u6682\u505C\u767B\u5F55\u8BF7\u6C42\uFF0C\u8BF7\u4F7F\u7528\u5B98\u65B9\u5BA2\u6237\u7AEF");
747
+ }
748
+ throw Error(path === "token" ? "\u767B\u5F55\u672A\u6210\u529F\uFF0C\u8BF7\u68C0\u67E5\u9A8C\u8BC1\u7801\u662F\u5426\u6B63\u786E\u3001\u8FC7\u671F\uFF1B\u4EC5\u652F\u6301\u5DF2\u6709\u8D26\u53F7" : "\u77ED\u4FE1\u672A\u53D1\u9001\u6210\u529F\uFF0C\u8BF7\u786E\u8BA4\u5DF2\u6709\u8D26\u53F7\u53CA\u624B\u673A\u53F7\uFF0C\u6216\u4F7F\u7528\u5B98\u65B9\u5BA2\u6237\u7AEF");
749
+ }
750
+ return result.data ?? {};
751
+ }
752
+ async send(phone) {
753
+ if (typeof phone !== "string" || !/^1[3-9]\d{9}$/.test(phone)) throw Error("\u8BF7\u8F93\u5165 11 \u4F4D\u4E2D\u56FD\u5927\u9646\u624B\u673A\u53F7");
754
+ if (this.flight) throw Error("\u767B\u5F55\u8BF7\u6C42\u6B63\u5728\u5904\u7406\u4E2D");
755
+ this.flight = (async () => {
756
+ await this.reserve("smsAt", 6e4);
757
+ this.pending = void 0;
758
+ const data = await this.post("send-sms", { phone });
759
+ const expires = Math.min(300, Math.max(1, Number(data.expires_in) || 300)) * 1e3;
760
+ this.pending = { phone, challenge: randomBytes2(24).toString("hex"), expiresAt: this.now() + expires, attempts: 0 };
761
+ clearTimeout(this.expiryTimer);
762
+ this.expiryTimer = setTimeout(() => this.finish(), expires);
763
+ this.expiryTimer.unref();
764
+ return { challenge: this.pending.challenge, expiresAt: this.pending.expiresAt, resendAt: this.now() + 6e4 };
765
+ })();
766
+ try {
767
+ return await this.flight;
768
+ } finally {
769
+ this.flight = void 0;
770
+ }
771
+ }
772
+ async complete(challenge, code, verify = true) {
773
+ if (typeof challenge !== "string" || !this.pending || challenge !== this.pending.challenge || this.pending.expiresAt <= this.now()) throw Error("\u672C\u6B21\u767B\u5F55\u5DF2\u8FC7\u671F\u6216\u63D2\u4EF6\u5DF2\u91CD\u542F\uFF0C\u8BF7\u91CD\u65B0\u83B7\u53D6\u9A8C\u8BC1\u7801");
774
+ if (this.flight) throw Error("\u767B\u5F55\u8BF7\u6C42\u6B63\u5728\u5904\u7406\u4E2D");
775
+ if (this.pending.document) return this.pending;
776
+ if (typeof code !== "string" || !/^\d{4,8}$/.test(code) || typeof verify !== "boolean") throw Error("\u8BF7\u8F93\u5165\u6536\u5230\u7684\u77ED\u4FE1\u9A8C\u8BC1\u7801");
777
+ if (this.pending.attempts >= 5) throw Error("\u672C\u6B21\u9A8C\u8BC1\u7801\u5C1D\u8BD5\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u8BF7\u7A0D\u540E\u91CD\u65B0\u83B7\u53D6");
778
+ this.flight = (async () => {
779
+ await this.reserve("loginAt", 5e3);
780
+ const pending = this.pending;
781
+ pending.attempts++;
782
+ const data = await this.post("token", { login_method: "phone", phone: pending.phone, sms_code: code });
783
+ const accessToken = data.accessToken || data.access_token, refreshToken = data.refreshToken || data.refresh_token;
784
+ let token;
785
+ try {
786
+ token = workbuddyToken(accessToken);
787
+ } catch {
788
+ throw Error("\u767B\u5F55\u8FD4\u56DE\u7684\u4EE4\u724C\u4E0D\u5C5E\u4E8E WorkBuddy \u4E2D\u56FD\u7248\uFF0C\u8BF7\u4F7F\u7528\u5B98\u65B9\u5BA2\u6237\u7AEF");
789
+ }
790
+ if (typeof refreshToken !== "string" || !refreshToken || !Number.isFinite(token.exp) || token.exp * 1e3 <= this.now()) throw Error("\u767B\u5F55\u54CD\u5E94\u7F3A\u5C11\u6709\u6548\u51ED\u8BC1\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55");
791
+ let credential = { accessToken, refreshToken, uid: token.sub, expiresAtMs: token.exp * 1e3, domain: "www.workbuddy.cn", source: "dsh" };
792
+ if (verify) {
793
+ try {
794
+ const rotated = await refreshWorkBuddyCheckinCredential(credential, { fetchImpl: this.fetchImpl, signal: AbortSignal.any([this.abort.signal, AbortSignal.timeout(3e4)]) });
795
+ credential = {
796
+ ...credential,
797
+ accessToken: rotated.accessToken,
798
+ refreshToken: rotated.refreshToken || credential.refreshToken,
799
+ expiresAtMs: Date.now() + rotated.expiresInSec * 1e3
800
+ };
801
+ } catch {
802
+ pending.warning = "\u767B\u5F55\u51ED\u8BC1\u5DF2\u4FDD\u5B58\uFF0C\u4F46\u5237\u65B0\u4EE4\u724C\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF1B\u8BF7\u7A0D\u540E\u5728\u8D26\u53F7\u8BE6\u60C5\u68C0\u67E5\u767B\u5F55\u6709\u6548\u671F\u3002";
803
+ }
804
+ }
805
+ delete pending.phone;
806
+ pending.document = { version: 1, credential };
807
+ return pending;
808
+ })();
809
+ try {
810
+ return await this.flight;
811
+ } finally {
812
+ this.flight = void 0;
813
+ }
814
+ }
815
+ finish() {
816
+ clearTimeout(this.expiryTimer);
817
+ this.pending = void 0;
818
+ }
819
+ };
820
+
633
821
  // src/manager.mjs
634
822
  var MANAGEMENT_PATH = "/.well-known/dsh-multi-router";
635
823
  var revisionOf = (raw) => createHash("sha256").update(JSON.stringify(raw)).digest("hex");
@@ -681,8 +869,9 @@ var Manager = class {
681
869
  this.monitorFlights = /* @__PURE__ */ new Map();
682
870
  this.loginInfo = /* @__PURE__ */ new Map();
683
871
  this.telemetry = new Telemetry(join4(dirname3(file), "usage.json"));
684
- this.plugins = new PluginUpdates(pluginAccess);
872
+ this.plugins = new PluginUpdates({ ...pluginAccess, stateFile: join4(dirname3(file), "plugin-updates.json") });
685
873
  this.checkins = new Checkins(join4(dirname3(file), "checkins.json"), this);
874
+ this.workbuddyLogin = new WorkBuddyLogin(join4(dirname3(file), "workbuddy-login.json"), { fetchImpl: pluginAccess.fetchImpl });
686
875
  this.setupFile = join4(dirname3(file), "initial-setup.json");
687
876
  router.telemetry = this.telemetry;
688
877
  router.monitor = (a) => this.monitorAccount(a);
@@ -690,16 +879,20 @@ var Manager = class {
690
879
  router.freeModels = (account) => this.freeModels(account);
691
880
  }
692
881
  async load() {
693
- this.raw = JSON.parse(await readFile2(this.file, "utf8"));
882
+ this.raw = JSON.parse(await readFile3(this.file, "utf8"));
694
883
  try {
695
- this.setup = JSON.parse(await readFile2(this.setupFile, "utf8"));
884
+ this.setup = JSON.parse(await readFile3(this.setupFile, "utf8"));
696
885
  } catch {
697
886
  this.setup = void 0;
698
887
  }
699
888
  this.config = validateConfig(this.raw, this.file);
700
889
  await this.checkins.load();
890
+ await this.plugins.load();
891
+ if (this.plugins.state().available) await this.plugins.list().catch(() => {
892
+ this.plugins.rows = [];
893
+ });
701
894
  try {
702
- const saved = JSON.parse(await readFile2(this.catalogFile, "utf8"));
895
+ const saved = JSON.parse(await readFile3(this.catalogFile, "utf8"));
703
896
  for (const account of this.config.accounts) {
704
897
  const item = saved[account.id];
705
898
  if (item?.credentialFile === account.credentialFile && Array.isArray(item.models)) {
@@ -739,9 +932,9 @@ var Manager = class {
739
932
  ...discovered.status.map((s) => ({ ...s, checkedAt: Date.now() }))
740
933
  ];
741
934
  if (this.closed || !discovered.accounts.length) return;
742
- await withFileLock2(this.file, async () => {
935
+ await withFileLock3(this.file, async () => {
743
936
  if (this.closed) return;
744
- let raw = JSON.parse(await readFile2(this.file, "utf8"));
937
+ let raw = JSON.parse(await readFile3(this.file, "utf8"));
745
938
  let changed = false;
746
939
  const saved = /* @__PURE__ */ new Map();
747
940
  for (const item of discovered.accounts) {
@@ -825,9 +1018,9 @@ var Manager = class {
825
1018
  }
826
1019
  async finishInitialSetup() {
827
1020
  if (this.setup?.completedAt) return;
828
- await withFileLock2(this.setupFile, async () => {
1021
+ await withFileLock3(this.setupFile, async () => {
829
1022
  try {
830
- this.setup = JSON.parse(await readFile2(this.setupFile, "utf8"));
1023
+ this.setup = JSON.parse(await readFile3(this.setupFile, "utf8"));
831
1024
  } catch {
832
1025
  }
833
1026
  if (this.setup?.completedAt) return;
@@ -1009,7 +1202,7 @@ var Manager = class {
1009
1202
  if (this.closed || !this.config.accounts.some((a) => a.id === account.id && a.credentialFile === account.credentialFile)) return;
1010
1203
  this.catalogs.set(account.id, { credentialFile: account.credentialFile, fetchedAt: Date.now(), pricingVersion: 1, models });
1011
1204
  this.catalogErrors.delete(account.id);
1012
- await withFileLock2(this.catalogFile, () => writePrivateJson(this.catalogFile, Object.fromEntries(this.catalogs)));
1205
+ await withFileLock3(this.catalogFile, () => writePrivateJson(this.catalogFile, Object.fromEntries(this.catalogs)));
1013
1206
  } catch (error) {
1014
1207
  if (this.closed || !this.config.accounts.some((a) => a.id === account.id)) return;
1015
1208
  this.catalogErrors.set(account.id, { status: Number(error.status ?? error.failure?.status) || 502, code: "catalog_fetch_failed", at: Date.now() });
@@ -1062,6 +1255,63 @@ var Manager = class {
1062
1255
  }
1063
1256
  async action(input) {
1064
1257
  if (!input || typeof input !== "object" || Array.isArray(input)) bad("\u8BF7\u6C42\u683C\u5F0F\u65E0\u6548");
1258
+ if (input.action === "workbuddy.sms") {
1259
+ try {
1260
+ const workbuddyLogin = await this.workbuddyLogin.send(input.phone);
1261
+ return { ...this.state(), workbuddyLogin };
1262
+ } catch (e) {
1263
+ bad(e.message);
1264
+ }
1265
+ }
1266
+ if (input.action === "workbuddy.login") {
1267
+ 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");
1268
+ let login;
1269
+ try {
1270
+ login = await this.workbuddyLogin.complete(input.challenge, input.code, input.verify ?? true);
1271
+ } catch (e) {
1272
+ bad(e.message);
1273
+ }
1274
+ let existing;
1275
+ for (const a of this.config.accounts.filter((a2) => a2.provider === "workbuddy")) {
1276
+ try {
1277
+ if ((await jsonCredential(a.credentialFile)).credential.uid === login.document.credential.uid) {
1278
+ existing = a;
1279
+ break;
1280
+ }
1281
+ } catch {
1282
+ }
1283
+ }
1284
+ if (input.id && input.id !== existing?.id) bad("\u767B\u5F55\u8EAB\u4EFD\u4E0E\u6B63\u5728\u7F16\u8F91\u7684\u8D26\u53F7\u4E0D\u540C\uFF0C\u8BF7\u4ECE\u6DFB\u52A0\u8D26\u53F7\u5165\u53E3\u4FDD\u5B58");
1285
+ const state = await this.action({
1286
+ action: existing ? "account.credentials" : "account.add",
1287
+ revision: input.revision,
1288
+ id: existing?.id ?? `workbuddy_${randomBytes3(8).toString("hex")}`,
1289
+ provider: "workbuddy",
1290
+ name: input.name,
1291
+ document: login.document
1292
+ });
1293
+ this.workbuddyLogin.finish();
1294
+ return { ...state, loginResult: { warning: login.warning, saved: true } };
1295
+ }
1296
+ if (input.action === "checkins.verify") {
1297
+ const account = this.config.accounts.find((a) => a.id === input.id && a.provider === "workbuddy");
1298
+ if (!account) bad("\u8BF7\u9009\u62E9 WorkBuddy \u4E2D\u56FD\u7248\u8D26\u53F7");
1299
+ if (this.checkins.flight || this.checkins.failed) bad("\u8BF7\u7B49\u5F85\u7B7E\u5230\u5B8C\u6210\uFF0C\u6216\u5148\u4FEE\u590D\u7B7E\u5230\u8BB0\u5F55");
1300
+ try {
1301
+ await this.workbuddyLogin.reserve("verifyAt", 3e4);
1302
+ } catch (e) {
1303
+ bad(e.message);
1304
+ }
1305
+ try {
1306
+ const client = await this.router.client(account);
1307
+ await client.checkin.validate(AbortSignal.timeout(3e4));
1308
+ await this.checkins.credentialsUpdated(account.id);
1309
+ await this.readLogin(account);
1310
+ } catch {
1311
+ bad("\u767B\u5F55\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF1A\u8BF7\u5728\u8D26\u53F7\u4E0E\u767B\u5F55\u4E2D\u66F4\u65B0\u51ED\u8BC1\uFF1B\u4E0D\u4F1A\u81EA\u52A8\u91CD\u8BD5\u6216\u9886\u53D6");
1312
+ }
1313
+ return this.state();
1314
+ }
1065
1315
  if (input.action === "checkins.run") {
1066
1316
  try {
1067
1317
  this.checkins.start(input.ids);
@@ -1087,14 +1337,14 @@ var Manager = class {
1087
1337
  return this.state();
1088
1338
  }
1089
1339
  if (input.action === "setup.dismiss") {
1090
- await withFileLock2(this.setupFile, async () => {
1340
+ await withFileLock3(this.setupFile, async () => {
1091
1341
  this.setup = { ...this.setup, completedAt: this.setup?.completedAt ?? Date.now(), dismissed: true };
1092
1342
  await writePrivateJson(this.setupFile, this.setup);
1093
1343
  });
1094
1344
  return this.state();
1095
1345
  }
1096
1346
  if (input.action === "account.export") {
1097
- 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");
1347
+ 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");
1098
1348
  const account = this.config.accounts.find((a) => a.id === input.id);
1099
1349
  if (!account) bad("\u8D26\u53F7\u4E0D\u5B58\u5728");
1100
1350
  let credential;
@@ -1116,7 +1366,7 @@ var Manager = class {
1116
1366
  } };
1117
1367
  }
1118
1368
  if (input.action === "config.export") {
1119
- 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");
1369
+ 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");
1120
1370
  try {
1121
1371
  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) } };
1122
1372
  } catch (e) {
@@ -1152,7 +1402,7 @@ var Manager = class {
1152
1402
  }
1153
1403
  if (input.action === "account.capture") {
1154
1404
  if (!PROVIDERS.includes(input.provider)) bad("\u672A\u77E5\u4F9B\u5E94\u5546");
1155
- input = { ...input, action: "account.add", id: `${input.provider.replace("-", "_")}_${randomBytes2(4).toString("hex")}`, capture: true };
1405
+ input = { ...input, action: "account.add", id: `${input.provider.replace("-", "_")}_${randomBytes3(4).toString("hex")}`, capture: true };
1156
1406
  }
1157
1407
  if (input.action === "refresh") {
1158
1408
  if (input.provider && !PROVIDERS.includes(input.provider)) bad("\u672A\u77E5\u4F9B\u5E94\u5546");
@@ -1163,8 +1413,8 @@ var Manager = class {
1163
1413
  await this.checkFreeModels(true);
1164
1414
  return this.state();
1165
1415
  }
1166
- return withFileLock2(this.file, async () => {
1167
- let raw = JSON.parse(await readFile2(this.file, "utf8"));
1416
+ return withFileLock3(this.file, async () => {
1417
+ let raw = JSON.parse(await readFile3(this.file, "utf8"));
1168
1418
  if (input.revision !== revisionOf(raw)) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u518D\u4FDD\u5B58");
1169
1419
  raw.routes ??= structuredClone(this.config.routes);
1170
1420
  let staging;
@@ -1174,7 +1424,7 @@ var Manager = class {
1174
1424
  try {
1175
1425
  const prepared = await prepareMigration(this, raw, await decodeConfiguration(input), input.mode);
1176
1426
  staging = prepared.staging;
1177
- await writePrivateJson(`${this.file}.before-import-${Date.now()}-${randomBytes2(3).toString("hex")}.bak`, raw);
1427
+ await writePrivateJson(`${this.file}.before-import-${Date.now()}-${randomBytes3(3).toString("hex")}.bak`, raw);
1178
1428
  raw = prepared.raw;
1179
1429
  } catch (e) {
1180
1430
  bad(e.message);
@@ -1264,8 +1514,8 @@ var Manager = class {
1264
1514
  if (input.name !== void 0) account.name = input.name;
1265
1515
  if (input.enabled !== void 0) account.enabled = input.enabled;
1266
1516
  } else if (input.action === "accounts.enabled") {
1267
- if (!PROVIDERS.includes(input.provider) || typeof input.enabled !== "boolean") bad("\u4F9B\u5E94\u5546\u6216\u8DEF\u7531\u53C2\u4E0E\u5F00\u5173\u65E0\u6548");
1268
- for (const a of raw.accounts.filter((a2) => a2.provider === input.provider)) a.enabled = input.enabled;
1517
+ if (input.provider !== void 0 && !PROVIDERS.includes(input.provider) || typeof input.enabled !== "boolean") bad("\u4F9B\u5E94\u5546\u6216\u8DEF\u7531\u53C2\u4E0E\u5F00\u5173\u65E0\u6548");
1518
+ for (const a of raw.accounts.filter((a2) => !input.provider || a2.provider === input.provider)) a.enabled = input.enabled;
1269
1519
  } else if (input.action === "account.move") {
1270
1520
  if (!account || ![-1, 1].includes(input.direction)) bad("\u8D26\u53F7\u6216\u987A\u5E8F\u65E0\u6548");
1271
1521
  const group = raw.accounts.filter((a) => a.provider === account.provider), position = group.indexOf(account) + input.direction;
@@ -1313,7 +1563,7 @@ var Manager = class {
1313
1563
  } };
1314
1564
  } else if (input.action === "route.save") {
1315
1565
  if (input.id === void 0 || input.id === "") {
1316
- input.id = input.previousId || `route_${randomBytes2(8).toString("hex")}`;
1566
+ input.id = input.previousId || `route_${randomBytes3(8).toString("hex")}`;
1317
1567
  }
1318
1568
  if (!/^[a-zA-Z0-9_.-]{1,128}$/.test(input.id ?? "")) bad("\u8DEF\u7531 ID \u4EC5\u652F\u6301\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u70B9\u3001\u4E0B\u5212\u7EBF\u548C\u77ED\u6A2A\u7EBF");
1319
1569
  if (input.previousId && !Object.hasOwn(raw.routes, input.previousId)) bad("\u539F\u8DEF\u7531\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5237\u65B0\u9875\u9762");
@@ -1390,8 +1640,8 @@ var Manager = class {
1390
1640
  if (filename && String(filename) !== this.file.split(/[\\/]/).pop()) return;
1391
1641
  clearTimeout(timer);
1392
1642
  timer = setTimeout(() => {
1393
- void withFileLock2(this.file, async () => {
1394
- const raw = JSON.parse(await readFile2(this.file, "utf8"));
1643
+ void withFileLock3(this.file, async () => {
1644
+ const raw = JSON.parse(await readFile3(this.file, "utf8"));
1395
1645
  if (this.closed || revisionOf(raw) === this.revision) return;
1396
1646
  const config = validateConfig(raw, this.file);
1397
1647
  this.raw = raw;
@@ -1409,7 +1659,7 @@ var Manager = class {
1409
1659
  }
1410
1660
  };
1411
1661
  function managementHandler(manager) {
1412
- const key = randomBytes2(32).toString("base64url");
1662
+ const key = randomBytes3(32).toString("base64url");
1413
1663
  const json = (res, status, value) => {
1414
1664
  res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });
1415
1665
  res.end(JSON.stringify(value));
@@ -1458,4 +1708,4 @@ export {
1458
1708
  Manager,
1459
1709
  managementHandler
1460
1710
  };
1461
- //# sourceMappingURL=chunk-KCAJ4OAN.js.map
1711
+ //# sourceMappingURL=chunk-XKDXPB5T.js.map