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/CHANGELOG.md +140 -0
- package/README.en.md +6 -1
- package/README.md +6 -1
- package/lib/bin.js +15 -2
- package/lib/client.js +416 -66
- package/lib/{host-heartbeat--iKDuwr5.js → host-heartbeat-DPbL9oi2.js} +539 -31
- package/lib/index.d.ts +438 -10
- package/lib/index.js +262 -27
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
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
|
-
/**
|
|
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:
|
|
745
|
+
accounts: webAccounts,
|
|
579
746
|
message: safeMessage(error),
|
|
580
747
|
selectionExplicit,
|
|
581
|
-
|
|
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:
|
|
586
|
-
accountName: credential.nickname ??
|
|
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)
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
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.
|
|
5
|
+
"version": "2.0.10",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|