dsh-connect-workbuddy 2.0.6 → 2.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as prepareChatBody, C as workbuddyAccountId, D as parseCreditMultiplier, E as classifyUpstreamError, O as parseReasoning, S as parseWorkBuddyAuth, T as WorkBuddyUpstreamClient, _ as authFileName, a as readHostHeartbeat, b as defaultDesktopAuthPath, c as WORKBUDDY_CONNECT_VERSION, d as WorkBuddyCatalog, f as deriveCatalog, g as WorkBuddyCredentialStore, h as WORKBUDDY_AUTH_FILE_ENV, i as processStartTimeMs, j as regionOf, k as parseUpstreamModel, l as FALLBACK_WORKBUDDY_MODELS, m as WORKBUDDY_AUTH_FILENAME, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as fallbackModelsFor, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as FALLBACK_WORKBUDDY_MODELS_GLOBAL, v as defaultDesktopAuthCandidates, w as workbuddyOwnAuthPath, x as legacyWorkbuddyOwnAuthPath, y as defaultDesktopAuthDirs } from "./host-heartbeat--iKDuwr5.js";
1
+ import { A as isCredentialRejectedError, B as fetchAtRestKeyPayload, C as parseWorkBuddyAuth, D as WorkBuddyCredentialRejectedError, E as CREDENTIAL_REJECTED_CODE, F as regionOf, G as workbuddyAppExecutableCandidates, H as isEncryptedFieldWrapper, I as WORKBUDDY_APP_EXECUTABLE_ENV, L as clearAtRestKeyCache, M as parseReasoning, N as parseUpstreamModel, O as WorkBuddyUpstreamClient, P as prepareChatBody, R as deriveAtRestKey, S as legacyWorkbuddyOwnAuthPath, T as workbuddyOwnAuthPath, U as openEncryptedField, V as findWorkbuddyAppExecutable, W as readAtRestKey, _ as authFileName, a as readHostHeartbeat, b as defaultDesktopAuthPath, c as WORKBUDDY_CONNECT_VERSION, d as WorkBuddyCatalog, f as deriveCatalog, g as WorkBuddyCredentialStore, h as WORKBUDDY_AUTH_FILE_ENV, i as processStartTimeMs, j as parseCreditMultiplier, k as classifyUpstreamError, l as FALLBACK_WORKBUDDY_MODELS, m as WORKBUDDY_AUTH_FILENAME, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as fallbackModelsFor, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as FALLBACK_WORKBUDDY_MODELS_GLOBAL, v as defaultDesktopAuthCandidates, w as workbuddyAccountId, x as hasEncryptedCredentialFields, y as defaultDesktopAuthDirs, z as deriveAtRestKeyId } from "./host-heartbeat-DPbL9oi2.js";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { randomBytes, timingSafeEqual } from "node:crypto";
4
4
  import { createProvider } from "@earendil-works/pi-ai";
@@ -7,6 +7,76 @@ import { resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
7
7
  import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
8
8
  import { createServer } from "node:http";
9
9
  import { Readable } from "node:stream";
10
+ //#region src/credential-recovery.ts
11
+ /** How long a verdict stays fresh by default. */
12
+ const DEFAULT_PROBE_TTL_MS = 6e4;
13
+ /**
14
+ * Cached "is this account still accepted upstream?" probe.
15
+ *
16
+ * The cache key includes the credential's own `lastRefreshAtMs`, so a fresh
17
+ * sign-in (which rewrites the file's issuance time) invalidates a stale verdict
18
+ * automatically — a rejection recorded before the user signed in again cannot
19
+ * keep labelling the account unusable afterwards, and no explicit invalidation
20
+ * is needed.
21
+ */
22
+ function createAccountUsabilityProbe(options) {
23
+ const ttlMs = options.ttlMs ?? DEFAULT_PROBE_TTL_MS;
24
+ const now = options.now ?? Date.now;
25
+ const verdicts = /* @__PURE__ */ new Map();
26
+ return async (region, account) => {
27
+ let credential;
28
+ try {
29
+ credential = await options.store(region).credentialFor(account.id);
30
+ } catch {
31
+ return false;
32
+ }
33
+ if (credential === void 0) return false;
34
+ const key = `${region}\0${account.id}\0${credential.lastRefreshAtMs ?? credential.expiresAtMs}`;
35
+ const cached = verdicts.get(key);
36
+ if (cached !== void 0 && now() - cached.at < ttlMs) return cached.usable;
37
+ let usable = false;
38
+ try {
39
+ await options.client.fetchCheckinStatus(credential);
40
+ usable = true;
41
+ } catch {
42
+ usable = false;
43
+ }
44
+ verdicts.set(key, {
45
+ at: now(),
46
+ usable
47
+ });
48
+ return usable;
49
+ };
50
+ }
51
+ /**
52
+ * Decide what to tell the user after the upstream refused the selected
53
+ * credential: switch to a verified-usable account, or sign in again.
54
+ *
55
+ * The two answers are mutually exclusive by construction. `reloginRequired` is
56
+ * set ONLY when there is no other local account to switch to — never as a
57
+ * fallback for "the probe did not verify anything", because that would repeat
58
+ * the misdirection this module exists to remove. When other accounts exist but
59
+ * none was verified, both fields stay empty and the card says so honestly.
60
+ */
61
+ async function resolveCredentialRecovery(options) {
62
+ let accounts;
63
+ try {
64
+ accounts = await options.store.accounts();
65
+ } catch {
66
+ return { reloginRequired: false };
67
+ }
68
+ const others = accounts.filter((account) => account.id !== options.rejectedAccountId);
69
+ if (others.length === 0) return { reloginRequired: true };
70
+ for (const account of others) if (await options.probe(options.region, account)) return {
71
+ usableAccount: {
72
+ accountId: account.id,
73
+ accountName: account.accountName
74
+ },
75
+ reloginRequired: false
76
+ };
77
+ return { reloginRequired: false };
78
+ }
79
+ //#endregion
10
80
  //#region src/adapter.ts
11
81
  /**
12
82
  * The WorkBuddy pi-ai providers: loopback-backed adapters registered
@@ -467,6 +537,69 @@ function regionOfStatusUrl(url) {
467
537
  return WORKBUDDY_REGIONS.includes(value) ? value : void 0;
468
538
  }
469
539
  /**
540
+ * Narrow a settings value to the `regions` map. Accepts EITHER the whole
541
+ * settings section (the Host's resolved `Config`) OR the `regions` map itself,
542
+ * and unwraps the former. This tolerance is deliberate: passing the whole
543
+ * section where the map was expected was a real shipped bug in the sibling
544
+ * project — the lookup then read `section['cn']` (absent), so the card's
545
+ * checkbox reported `true` forever and clicking it appeared to do nothing even
546
+ * though the write succeeded.
547
+ */
548
+ function regionsMapOf(value) {
549
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return {};
550
+ const record = value;
551
+ const nested = record["regions"];
552
+ if (typeof nested === "object" && nested !== null && !Array.isArray(nested)) return nested;
553
+ return record;
554
+ }
555
+ /** One region's stored slot as a plain object; any other shape reads as empty. */
556
+ function regionSlotOf(value, region) {
557
+ const slot = regionsMapOf(value)[region];
558
+ return typeof slot === "object" && slot !== null && !Array.isArray(slot) ? slot : {};
559
+ }
560
+ /**
561
+ * Whether one region's provider is switched on. Opt-out semantics: only an
562
+ * explicit `false` disables it, so a config written before this switch existed
563
+ * (and the pre-region-split flat fields, which never carry `enabled`) keep both
564
+ * providers running exactly as before. The Host reads the same rule through
565
+ * `regionStateOf`, so card and Host can never disagree about a region's state.
566
+ *
567
+ * `value` may be the whole settings section or the `regions` map (see
568
+ * {@link regionsMapOf}).
569
+ */
570
+ function regionEnabledOf(value, region) {
571
+ return regionSlotOf(value, region)["enabled"] !== false;
572
+ }
573
+ /** Build the next `regions` settings value for a signed-in tab's save. */
574
+ function nextRegionSlots(regions, region, slot) {
575
+ return {
576
+ ...typeof regions === "object" && regions !== null && !Array.isArray(regions) ? regions : {},
577
+ [region]: slot
578
+ };
579
+ }
580
+ /**
581
+ * Build the next `regions` settings value for a provider on/off toggle. ONLY
582
+ * the target region's `enabled` flag changes: every other field of that slot
583
+ * (its directory, selection, image opt-ins, context budgets) and every other
584
+ * region's slot are carried over verbatim, so switching a provider off never
585
+ * discards the user's model picks and switching it back on restores them.
586
+ *
587
+ * This is deliberately separate from {@link nextRegionSlots}: that helper
588
+ * writes a whole slot from a signed-in tab's draft, while this one must work
589
+ * for a region that is signed OUT — which is precisely the region a user wants
590
+ * to switch off (no international install, no international account).
591
+ *
592
+ * `value` may be the whole settings section or the `regions` map; the RETURN
593
+ * value is always the `regions` map, i.e. exactly what `settingsScope.set(
594
+ * 'regions', ...)` needs.
595
+ */
596
+ function nextRegionEnabled(value, region, enabled) {
597
+ return nextRegionSlots(regionsMapOf(value), region, {
598
+ ...regionSlotOf(value, region),
599
+ enabled
600
+ });
601
+ }
602
+ /**
470
603
  * Project one card row into its persisted `lastCatalog` shape: the native
471
604
  * context window becomes the stored `contextWindow`, and the card-only
472
605
  * presentation fields (`nativeContextWindow`, `multimodal`) are removed BY
@@ -539,12 +672,18 @@ function toWebModel(model, budgets) {
539
672
  } }
540
673
  };
541
674
  }
542
- /** Project a store account into the card's token-free account row. */
675
+ /**
676
+ * Project a store account into the card's token-free account row.
677
+ *
678
+ * `uin` is deliberately NOT forwarded: the card has never rendered it, so
679
+ * sending it was pure exposure of an account identifier for no feature. The
680
+ * name carries whatever the desktop app recorded, and `''` means the card
681
+ * should show its own placeholder rather than an identifier.
682
+ */
543
683
  function toWebAccount(account) {
544
684
  return {
545
685
  id: account.id,
546
686
  accountName: account.accountName,
547
- ...account.uin === void 0 ? {} : { uin: account.uin },
548
687
  domain: account.domain,
549
688
  source: account.source,
550
689
  tokenExpiresAtMs: account.tokenExpiresAtMs,
@@ -552,6 +691,31 @@ function toWebAccount(account) {
552
691
  };
553
692
  }
554
693
  /**
694
+ * The probed-path list for a signed-out region, or nothing when the store
695
+ * cannot produce one.
696
+ *
697
+ * Diagnostics must never turn a page into an error: `diagnose()` re-reads the
698
+ * filesystem, and a store built without it (or one whose probe throws on an
699
+ * exotic filesystem) degrades to the plain "not signed in" hint the card showed
700
+ * before this existed. Failure reasons are `safeMessage`d because a raw
701
+ * filesystem error can embed an absolute path or a fragment of file content.
702
+ */
703
+ async function searchedPaths(store) {
704
+ if (typeof store.diagnose !== "function") return {};
705
+ try {
706
+ const { failures } = await store.diagnose();
707
+ if (failures.length === 0) return {};
708
+ return { searched: failures.map((failure) => ({
709
+ path: failure.path,
710
+ source: failure.source,
711
+ reason: failure.reason,
712
+ ...failure.message === void 0 ? {} : { message: safeMessage(failure.message) }
713
+ })) };
714
+ } catch {
715
+ return {};
716
+ }
717
+ }
718
+ /**
555
719
  * Assemble one region's card document. `region` is the tab the card is on;
556
720
  * the region-scoped store already answers with only that region's accounts,
557
721
  * so the document's model slots and account list are that region's by
@@ -562,45 +726,60 @@ async function workBuddyWebStatus(deps, region) {
562
726
  const store = deps.store(region);
563
727
  const accounts = await store.accounts();
564
728
  deps.regionUsable?.(region, accounts.length > 0);
729
+ const enabled = deps.regionEnabled(region);
565
730
  const authStatus = await store.status();
566
731
  const selectionExplicit = store.hasExplicitSelection();
567
732
  if (authStatus.state !== "signed-out" && accounts.length === 0) return {
568
733
  status: "signed-out",
569
734
  accounts: [],
570
- selectionExplicit
735
+ selectionExplicit,
736
+ enabled
571
737
  };
572
738
  let credential;
573
739
  try {
574
740
  credential = await store.resolve();
575
741
  } catch (error) {
742
+ const webAccounts = accounts.map(toWebAccount);
576
743
  return {
577
744
  status: "signed-out",
578
- accounts: accounts.map(toWebAccount),
745
+ accounts: webAccounts,
579
746
  message: safeMessage(error),
580
747
  selectionExplicit,
581
- ...await store.selectionLost() ? { selectionLost: true } : {}
748
+ enabled,
749
+ ...await store.selectionLost() ? { selectionLost: true } : {},
750
+ ...webAccounts.length === 0 ? await searchedPaths(store) : {}
582
751
  };
583
752
  }
753
+ const selected = accounts.find((account) => account.selected);
584
754
  const account = {
585
- accountId: accounts.find((account) => account.selected)?.id ?? "",
586
- accountName: credential.nickname ?? credential.uin ?? credential.uid,
587
- ...credential.uin === void 0 ? {} : { uin: credential.uin },
755
+ accountId: selected?.id ?? "",
756
+ accountName: credential.nickname ?? "",
588
757
  ...credential.domain === "" ? {} : { domain: credential.domain },
589
758
  region,
590
759
  source: credential.source,
591
760
  tokenExpiresAtMs: credential.expiresAtMs,
592
761
  selectionExplicit,
762
+ enabled,
593
763
  accounts: accounts.map(toWebAccount),
594
764
  models: deps.displayModels(region).map((model) => toWebModel(model, deps.contextBudgets(region))),
595
765
  enabledModelIds: [...deps.enabledModelIds(region)],
596
766
  imageModelIds: [...deps.imageModelIds(region)]
597
767
  };
598
768
  const [creditsResult, checkinResult] = await Promise.allSettled([deps.client.fetchCredits(credential), deps.client.fetchCheckinStatus(credential)]);
769
+ const rejected = [creditsResult, checkinResult].some((result) => result.status === "rejected" && isCredentialRejectedError(result.reason));
770
+ const recovery = !rejected ? void 0 : await resolveCredentialRecovery({
771
+ region,
772
+ store: deps.store(region),
773
+ ...selected === void 0 ? {} : { rejectedAccountId: selected.id },
774
+ probe: deps.accountUsable ?? (async () => false)
775
+ });
599
776
  return {
600
777
  status: "signed-in",
601
778
  ...account,
602
779
  ...creditsResult.status === "fulfilled" ? { credits: toCredits(creditsResult.value) } : { creditsError: safeMessage(creditsResult.reason) },
603
- ...checkinResult.status === "fulfilled" ? { checkin: checkinResult.value } : { checkinError: safeMessage(checkinResult.reason) }
780
+ ...checkinResult.status === "fulfilled" ? { checkin: checkinResult.value } : { checkinError: safeMessage(checkinResult.reason) },
781
+ ...!rejected ? {} : { credentialRejected: true },
782
+ ...recovery === void 0 ? {} : { recovery }
604
783
  };
605
784
  }
606
785
  /**
@@ -725,6 +904,7 @@ const modelConfig = z.object({
725
904
  maxTokens: z.number().step(1).min(1)
726
905
  });
727
906
  const regionStateConfig = z.object({
907
+ enabled: z.boolean().default(true).description("Whether this region's provider is offered to DSH (opt-out; false withdraws it entirely)"),
728
908
  lastCatalog: z.array(modelConfig).default([]),
729
909
  enabledModelIds: z.array(z.string()).default([]),
730
910
  imageModelIds: z.array(z.string()).default([]),
@@ -764,6 +944,17 @@ function regionStateOf(config, region) {
764
944
  ...config.contextBudgets === void 0 ? {} : { contextBudgets: config.contextBudgets }
765
945
  };
766
946
  }
947
+ /**
948
+ * Whether one region's provider is switched on. Opt-out semantics: only an
949
+ * explicit `false` disables it, so every config written before this switch
950
+ * existed — including the pre-region-split flat fields, which never carry
951
+ * `enabled` — keeps both providers running exactly as before. The card reads the
952
+ * same rule through `regionEnabledOf`, so the two halves can never disagree
953
+ * about a region's state.
954
+ */
955
+ function regionEnabled(config, region) {
956
+ return regionStateOf(config, region).enabled !== false;
957
+ }
767
958
  /** Every region, in card tab order. */
768
959
  const REGION_KEYS = ["cn", "global"];
769
960
  /**
@@ -847,6 +1038,10 @@ function apply(ctx, config) {
847
1038
  })
848
1039
  };
849
1040
  }
1041
+ const accountUsabilityProbe = createAccountUsabilityProbe({
1042
+ store: (region) => stacks[region].store,
1043
+ client
1044
+ });
850
1045
  const withImageSelection = (models, images) => models.map((model) => ({
851
1046
  ...model,
852
1047
  ...images.has(model.id) ? { multimodal: true } : { multimodal: false }
@@ -882,6 +1077,7 @@ function apply(ctx, config) {
882
1077
  stacks[region].catalog.set(configuredModels(value, region));
883
1078
  }
884
1079
  invalidateCatalog();
1080
+ syncRegionRegistration(value);
885
1081
  refreshRegionUsability();
886
1082
  };
887
1083
  /**
@@ -911,14 +1107,48 @@ function apply(ctx, config) {
911
1107
  if (stacks[region].catalog.setRegionUsable(accounts.length > 0)) invalidateCatalog();
912
1108
  }
913
1109
  };
1110
+ /**
1111
+ * Live registration handles per region, filled once the shim is listening.
1112
+ * A disabled region holds ZERO routes while staying registered: DSH allows
1113
+ * `replace([])` for exactly this case ("a settings section that emptied holds
1114
+ * zero routes while staying registered"), which is what makes the on/off
1115
+ * switch reversible without a restart. Withdrawing the adapter route is what
1116
+ * actually removes the region's models from DSH's model picker — hiding the
1117
+ * card tab alone would leave every model selectable.
1118
+ */
1119
+ const registration = {
1120
+ cn: {},
1121
+ global: {}
1122
+ };
1123
+ /**
1124
+ * Publish each region's on/off state to the harness (issue #11-style region
1125
+ * switch). Both swaps are single synchronous sections, so no request can
1126
+ * observe a half-applied state, and `replace` announces itself through
1127
+ * `llm/adapters-updated`, which is what makes third-party consumers drop the
1128
+ * region too. No-op until the shim has registered; `applySelection` runs again
1129
+ * on every card write (and once more after registration completes), so a
1130
+ * toggle lands immediately.
1131
+ */
1132
+ const syncRegionRegistration = (value) => {
1133
+ for (const region of REGION_KEYS) registration[region].adapter?.replace(regionEnabled(value, region) ? [WORKBUDDY_PROVIDERS[region]] : []);
1134
+ registration.cn.directory?.replace(REGION_KEYS.filter((region) => regionEnabled(value, region)).map((region) => ({
1135
+ provider: WORKBUDDY_PROVIDERS[region],
1136
+ displayName: WORKBUDDY_PROVIDER_DISPLAY_NAMES[region],
1137
+ settingsNs: WORKBUDDY_SETTINGS_NS,
1138
+ settingsPath: [],
1139
+ declared: false
1140
+ })));
1141
+ };
914
1142
  ctx.inject(["webServer"], (webCtx) => registerWorkBuddyStatusRoute(webCtx, {
915
1143
  store: (region) => stacks[region].store,
916
1144
  client,
1145
+ accountUsable: accountUsabilityProbe,
917
1146
  displayModels: (region) => displayModels(current(), region),
918
1147
  enabledModelIds: (region) => regionStateOf(current(), region).enabledModelIds ?? [],
919
1148
  imageModelIds: (region) => regionStateOf(current(), region).imageModelIds ?? [],
920
1149
  contextBudgets: (region) => regionStateOf(current(), region).contextBudgets ?? {},
921
1150
  discoverModels,
1151
+ regionEnabled: (region) => regionEnabled(current(), region),
922
1152
  regionUsable(region, usable) {
923
1153
  if (stacks[region].catalog.setRegionUsable(usable)) invalidateCatalog();
924
1154
  }
@@ -965,9 +1195,9 @@ function apply(ctx, config) {
965
1195
  let releaseAdapterGlobal;
966
1196
  let releaseDirectory;
967
1197
  try {
968
- releaseAdapterCn = ctx.llm.registerAdapter([WORKBUDDY_PROVIDER], adapters.cn.adapter);
969
- releaseAdapterGlobal = ctx.llm.registerAdapter([WORKBUDDY_GLOBAL_PROVIDER], adapters.global.adapter);
970
- releaseDirectory = ctx.llm.registerConfigurableProviders([{
1198
+ releaseAdapterCn = registration.cn.adapter = ctx.llm.registerAdapter([WORKBUDDY_PROVIDER], adapters.cn.adapter);
1199
+ releaseAdapterGlobal = registration.global.adapter = ctx.llm.registerAdapter([WORKBUDDY_GLOBAL_PROVIDER], adapters.global.adapter);
1200
+ releaseDirectory = registration.cn.directory = ctx.llm.registerConfigurableProviders([{
971
1201
  provider: WORKBUDDY_PROVIDER,
972
1202
  displayName: WORKBUDDY_PROVIDER_DISPLAY_NAMES.cn,
973
1203
  settingsNs: WORKBUDDY_SETTINGS_NS,
@@ -998,9 +1228,11 @@ function apply(ctx, config) {
998
1228
  releaseAdapterGlobal?.();
999
1229
  releaseDirectory?.();
1000
1230
  }
1231
+ syncRegionRegistration(current());
1001
1232
  ctx.llm.registerModelDiscovery(WORKBUDDY_SETTINGS_NS, async (request, signal) => {
1002
1233
  const region = regionOfProvider(request.provider ?? "");
1003
1234
  if (region === void 0) return [];
1235
+ if (!regionEnabled(current(), region)) return [];
1004
1236
  const discovered = await discoverModels(region, signal);
1005
1237
  const state = regionStateOf(current(), region);
1006
1238
  return withImageSelection(deriveCatalog(discovered, new Set(state.enabledModelIds ?? []), state.contextBudgets ?? {}), new Set(state.imageModelIds ?? [])).map((model) => ({
@@ -1017,22 +1249,25 @@ function apply(ctx, config) {
1017
1249
  return;
1018
1250
  }
1019
1251
  if (stopped) return;
1020
- for (const region of REGION_KEYS) (async () => {
1021
- try {
1022
- const credential = await stacks[region].store.resolve();
1023
- if (stopped) return;
1024
- const models = await client.fetchModels(credential);
1025
- if (stopped) return;
1026
- const state = regionStateOf(current(), region);
1027
- stacks[region].catalog.set(withImageSelection(deriveCatalog(models, new Set(state.enabledModelIds ?? []), state.contextBudgets ?? {}), new Set(state.imageModelIds ?? [])));
1028
- adapters[region].invalidate();
1029
- } catch (error) {
1030
- ctx.logger.warn(`dsh-connect-workbuddy: dynamic ${region} model catalog unavailable; serving the static fallback list`, error);
1031
- }
1032
- })();
1252
+ for (const region of REGION_KEYS) {
1253
+ if (!regionEnabled(current(), region)) continue;
1254
+ (async () => {
1255
+ try {
1256
+ const credential = await stacks[region].store.resolve();
1257
+ if (stopped) return;
1258
+ const models = await client.fetchModels(credential);
1259
+ if (stopped) return;
1260
+ const state = regionStateOf(current(), region);
1261
+ stacks[region].catalog.set(withImageSelection(deriveCatalog(models, new Set(state.enabledModelIds ?? []), state.contextBudgets ?? {}), new Set(state.imageModelIds ?? [])));
1262
+ adapters[region].invalidate();
1263
+ } catch (error) {
1264
+ ctx.logger.warn(`dsh-connect-workbuddy: dynamic ${region} model catalog unavailable; serving the static fallback list`, error);
1265
+ }
1266
+ })();
1267
+ }
1033
1268
  }).catch((error) => {
1034
1269
  ctx.logger.error("dsh-connect-workbuddy: loopback endpoint failed to start; providers not registered", error);
1035
1270
  });
1036
1271
  }
1037
1272
  //#endregion
1038
- export { Config, FALLBACK_WORKBUDDY_MODELS, FALLBACK_WORKBUDDY_MODELS_GLOBAL, WORKBUDDY_ACCOUNTS_REFRESH_PATH, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CHECKIN_PATH, WORKBUDDY_CONNECT_VERSION, WORKBUDDY_GLOBAL_PROVIDER, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_MODELS_REFRESH_PATH, WORKBUDDY_PROVIDER, WORKBUDDY_PROVIDERS, WORKBUDDY_PROVIDER_DISPLAY_NAMES, WORKBUDDY_REGIONS, WORKBUDDY_REGION_PARAM, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_USAGE_PATH, WorkBuddyCatalog, WorkBuddyCredentialStore, WorkBuddyUpstreamClient, apply, authFileName, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, deriveCatalog, fallbackModelsFor, inject, isHeartbeatProcessAlive, legacyAttributionRegion, legacyWorkbuddyOwnAuthPath, name, parseCreditMultiplier, parseReasoning, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, processStartTimeMs, readHostHeartbeat, regionCleared, regionOf, regionOfProvider, regionOfStatusUrl, regionStateOf, registerWorkBuddyStatusRoute, selectAccountFor, toPersistedWorkBuddyModel, withWorkBuddyRegion, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
1273
+ export { CREDENTIAL_REJECTED_CODE, Config, FALLBACK_WORKBUDDY_MODELS, FALLBACK_WORKBUDDY_MODELS_GLOBAL, WORKBUDDY_ACCOUNTS_REFRESH_PATH, WORKBUDDY_APP_EXECUTABLE_ENV, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CHECKIN_PATH, WORKBUDDY_CONNECT_VERSION, WORKBUDDY_GLOBAL_PROVIDER, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_MODELS_REFRESH_PATH, WORKBUDDY_PROVIDER, WORKBUDDY_PROVIDERS, WORKBUDDY_PROVIDER_DISPLAY_NAMES, WORKBUDDY_REGIONS, WORKBUDDY_REGION_PARAM, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_USAGE_PATH, WorkBuddyCatalog, WorkBuddyCredentialRejectedError, WorkBuddyCredentialStore, WorkBuddyUpstreamClient, apply, authFileName, classifyUpstreamError, clearAtRestKeyCache, clearHostHeartbeat, createAccountUsabilityProbe, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, deriveAtRestKey, deriveAtRestKeyId, deriveCatalog, fallbackModelsFor, fetchAtRestKeyPayload, findWorkbuddyAppExecutable, hasEncryptedCredentialFields, inject, isCredentialRejectedError, isEncryptedFieldWrapper, isHeartbeatProcessAlive, legacyAttributionRegion, legacyWorkbuddyOwnAuthPath, name, nextRegionEnabled, nextRegionSlots, openEncryptedField, parseCreditMultiplier, parseReasoning, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, processStartTimeMs, readAtRestKey, readHostHeartbeat, regionCleared, regionEnabled, regionEnabledOf, regionOf, regionOfProvider, regionOfStatusUrl, regionStateOf, registerWorkBuddyStatusRoute, resolveCredentialRecovery, selectAccountFor, toPersistedWorkBuddyModel, withWorkBuddyRegion, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyAppExecutableCandidates, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-connect-workbuddy",
3
3
  "displayName": "DSH Connect WorkBuddy",
4
4
  "description": "Connect locally signed-in WorkBuddy models to DeepSeek Harness with a read-only credits overview and model management.",
5
- "version": "2.0.6",
5
+ "version": "2.0.10",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",