dsh-connect-workbuddy 3.1.0 → 3.4.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.
- package/CHANGELOG.md +186 -0
- package/README.en.md +49 -20
- package/README.md +56 -26
- package/THIRD_PARTY_NOTICES.md +4 -2
- package/docs/WINDOWS.md +1 -0
- package/docs/assets/account-pool-demo.html +207 -262
- package/lib/bin.js +1 -1
- package/lib/client.js +457 -725
- package/lib/{host-heartbeat-DZnPggVT.js → host-heartbeat-C2_Zmuqq.js} +96 -3
- package/lib/index.d.ts +126 -32
- package/lib/index.js +1441 -278
- package/package.json +1 -1
|
@@ -416,6 +416,19 @@ const CLIENT_UA = "CLI/2.63.2 CodeBuddy/2.63.2";
|
|
|
416
416
|
const DESKTOP_UA = "WorkBuddy/5.5.2";
|
|
417
417
|
const JSON_TIMEOUT_MS = 3e4;
|
|
418
418
|
const ERROR_BODY_LIMIT = 4096;
|
|
419
|
+
/**
|
|
420
|
+
* Ceiling for one real-volume probe to get a RESPONSE, which is not a metadata
|
|
421
|
+
* call.
|
|
422
|
+
*
|
|
423
|
+
* Much larger than {@link JSON_TIMEOUT_MS} on purpose: a probe posts ~25k input
|
|
424
|
+
* tokens, so prompt processing legitimately takes a while before the first byte
|
|
425
|
+
* comes back. It exists only to stop a connection that will NEVER answer — the
|
|
426
|
+
* pool's batch is serial, so one hung member would otherwise block all the rest
|
|
427
|
+
* with nothing in the UI to say which one it was. Ten seconds is comfortably
|
|
428
|
+
* above a working gateway's first byte and far below a user's patience; the
|
|
429
|
+
* budget covers the response only, never the one-line body that follows.
|
|
430
|
+
*/
|
|
431
|
+
const PROBE_TIMEOUT_MS = 1e4;
|
|
419
432
|
/** Insufficient-credit markers, ASCII lowercase plus the original Chinese. */
|
|
420
433
|
const HARD_CREDIT_MARKERS = [
|
|
421
434
|
"insufficient credit",
|
|
@@ -637,6 +650,67 @@ function normalizeToolChoice(obj) {
|
|
|
637
650
|
delete obj["tool_choice"];
|
|
638
651
|
}
|
|
639
652
|
/**
|
|
653
|
+
* What a PREPARED request body declares, as the input to DSML recovery.
|
|
654
|
+
*
|
|
655
|
+
* This exists so the recovery path can answer the one question its gates turn
|
|
656
|
+
* on — "was this tool name offered in THIS request?" — without a second parse
|
|
657
|
+
* of the body and without a second source of truth. `prepareChatBody` has
|
|
658
|
+
* already parsed it; this reads the same prepared JSON.
|
|
659
|
+
*
|
|
660
|
+
* Two facts make it cheap and exact:
|
|
661
|
+
*
|
|
662
|
+
* - `tool_choice: "none"` deletes `tools` outright in
|
|
663
|
+
* {@link normalizeToolChoice}, so "no tools declared" is directly
|
|
664
|
+
* observable here rather than a separate condition to remember;
|
|
665
|
+
* - a pinned `tool_choice` arrives as the bare function name (the object form
|
|
666
|
+
* was flattened), so anything that is not `auto`/`required`/`none` is a pin.
|
|
667
|
+
*
|
|
668
|
+
* Returns `undefined` when the body declares nothing usable — malformed JSON,
|
|
669
|
+
* no `tools` array, or an empty one. Callers must read that as "recovery is
|
|
670
|
+
* off" (gate 3), not as "no information, so guess".
|
|
671
|
+
*/
|
|
672
|
+
function declaredTools(bodyJson) {
|
|
673
|
+
let body;
|
|
674
|
+
try {
|
|
675
|
+
body = JSON.parse(bodyJson);
|
|
676
|
+
} catch {
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) return void 0;
|
|
680
|
+
const obj = body;
|
|
681
|
+
const tools = obj["tools"];
|
|
682
|
+
if (!Array.isArray(tools)) return void 0;
|
|
683
|
+
const names = /* @__PURE__ */ new Set();
|
|
684
|
+
const requiredParameters = /* @__PURE__ */ new Map();
|
|
685
|
+
for (const value of tools) {
|
|
686
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) continue;
|
|
687
|
+
const fn = value["function"];
|
|
688
|
+
if (typeof fn !== "object" || fn === null || Array.isArray(fn)) continue;
|
|
689
|
+
const definition = fn;
|
|
690
|
+
const name = typeof definition["name"] === "string" ? definition["name"] : "";
|
|
691
|
+
if (name === "") continue;
|
|
692
|
+
names.add(name);
|
|
693
|
+
const parameters = definition["parameters"];
|
|
694
|
+
if (typeof parameters !== "object" || parameters === null || Array.isArray(parameters)) continue;
|
|
695
|
+
const required = parameters["required"];
|
|
696
|
+
if (!Array.isArray(required)) continue;
|
|
697
|
+
const list = required.filter((entry) => typeof entry === "string" && entry !== "");
|
|
698
|
+
if (list.length > 0) requiredParameters.set(name, list);
|
|
699
|
+
}
|
|
700
|
+
if (names.size === 0) return void 0;
|
|
701
|
+
const pinned = typeof obj["tool_choice"] === "string" ? obj["tool_choice"].trim() : "";
|
|
702
|
+
const pinnedToolName = pinned !== "" && ![
|
|
703
|
+
"auto",
|
|
704
|
+
"required",
|
|
705
|
+
"none"
|
|
706
|
+
].includes(pinned.toLowerCase()) ? pinned : void 0;
|
|
707
|
+
return {
|
|
708
|
+
names,
|
|
709
|
+
requiredParameters,
|
|
710
|
+
...pinnedToolName === void 0 ? {} : { pinnedToolName }
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
640
714
|
* Gateway (openresty/APISIX) rejection of a token it no longer accepts.
|
|
641
715
|
*
|
|
642
716
|
* The business APIs answer JSON; an edge rejection answers an HTML error page
|
|
@@ -885,9 +959,26 @@ var WorkBuddyUpstreamClient = class {
|
|
|
885
959
|
*
|
|
886
960
|
* The caller MUST drain {@link WorkBuddyProbeAnswer.response}; an unread body
|
|
887
961
|
* holds the connection open.
|
|
962
|
+
*
|
|
963
|
+
* ALWAYS bounded in time. The pool's batch runner is serial and passes no
|
|
964
|
+
* signal, so without a ceiling here one stalled connection blocks every account
|
|
965
|
+
* behind it — the batch looks hung with no way to tell which member did it.
|
|
966
|
+
* A supplied signal is COMBINED with the timeout rather than replacing it, so
|
|
967
|
+
* the single-model route keeps its cancellation behaviour and still cannot
|
|
968
|
+
* hang forever.
|
|
969
|
+
*
|
|
970
|
+
* The ceiling covers ONLY the wait for a response — it is cleared the moment
|
|
971
|
+
* `fetch` resolves. Holding a timer over the body would put a healthy but slow
|
|
972
|
+
* model on the same clock as a dead endpoint, and the whole point of the
|
|
973
|
+
* period is to answer "did the upstream answer at all". A probe asks for a
|
|
974
|
+
* single token, so the body that follows is one SSE line.
|
|
888
975
|
*/
|
|
889
976
|
async probeChat(credential, bodyJson, signal) {
|
|
890
977
|
let response;
|
|
978
|
+
const ceiling = new AbortController();
|
|
979
|
+
const timer = setTimeout(() => {
|
|
980
|
+
ceiling.abort(/* @__PURE__ */ new Error(`no response within ${PROBE_TIMEOUT_MS}ms`));
|
|
981
|
+
}, PROBE_TIMEOUT_MS);
|
|
891
982
|
try {
|
|
892
983
|
response = await fetch(`${chatBase(credential)}/v2/chat/completions`, {
|
|
893
984
|
method: "POST",
|
|
@@ -896,7 +987,7 @@ var WorkBuddyUpstreamClient = class {
|
|
|
896
987
|
"Authorization": `Bearer ${credential.accessToken}`
|
|
897
988
|
},
|
|
898
989
|
body: bodyJson,
|
|
899
|
-
|
|
990
|
+
signal: signal === void 0 ? ceiling.signal : AbortSignal.any([signal, ceiling.signal])
|
|
900
991
|
});
|
|
901
992
|
} catch (error) {
|
|
902
993
|
return {
|
|
@@ -905,6 +996,8 @@ var WorkBuddyUpstreamClient = class {
|
|
|
905
996
|
retryAfter: null,
|
|
906
997
|
body: `transport error: ${String(error)}`
|
|
907
998
|
};
|
|
999
|
+
} finally {
|
|
1000
|
+
clearTimeout(timer);
|
|
908
1001
|
}
|
|
909
1002
|
const retryAfter = response.headers.get("retry-after");
|
|
910
1003
|
if (response.ok) return {
|
|
@@ -2459,7 +2552,7 @@ function generationOf(version) {
|
|
|
2459
2552
|
//#endregion
|
|
2460
2553
|
//#region src/version.ts
|
|
2461
2554
|
/** The npm package version this build was produced from. */
|
|
2462
|
-
const WORKBUDDY_CONNECT_VERSION = "3.
|
|
2555
|
+
const WORKBUDDY_CONNECT_VERSION = "3.4.0";
|
|
2463
2556
|
//#endregion
|
|
2464
2557
|
//#region src/host-heartbeat.ts
|
|
2465
2558
|
/**
|
|
@@ -2617,4 +2710,4 @@ async function clearHostHeartbeat() {
|
|
|
2617
2710
|
await rm(workbuddyHostHeartbeatPath(), { force: true });
|
|
2618
2711
|
}
|
|
2619
2712
|
//#endregion
|
|
2620
|
-
export {
|
|
2713
|
+
export { readAtRestKey as $, CREDENTIAL_REJECTED_CODE as A, prepareChatBody as B, defaultDesktopAuthPath as C, parseWorkBuddyAuth as D, legacyWorkbuddyOwnAuthPath as E, declaredTools as F, deriveAtRestKeyId as G, WORKBUDDY_APP_EXECUTABLE_ENV as H, isCredentialRejectedError as I, isEncryptedFieldWrapper as J, fetchAtRestKeyPayload as K, parseCreditMultiplier as L, WorkBuddyCredentialRejectedError as M, WorkBuddyUpstreamClient as N, workbuddyAccountId as O, classifyUpstreamError as P, openEncryptedField as Q, parseReasoning as R, defaultDesktopAuthDirs as S, isEncryptedCredentialError as T, clearAtRestKeyCache as U, regionOf as V, deriveAtRestKey as W, macosBundleExecutable as X, isWorkbuddyBundle as Y, macosNestedAppBundles as Z, WORKBUDDY_AUTH_FILE_ENV as _, readHostHeartbeat as a, authFileName as b, WORKBUDDY_CONNECT_VERSION as c, FALLBACK_WORKBUDDY_MODELS_GLOBAL as d, workbuddyAppExecutableCandidates as et, WorkBuddyCatalog as f, WORKBUDDY_AUTH_FILENAME as g, ENCRYPTED_CREDENTIAL_CODE as h, processStartTimeMs as i, WORKBUDDY_FALLBACK_SYSTEM_PROMPT as j, workbuddyOwnAuthPath as k, piAiRuntimeInfo as l, fallbackModelsFor as m, clearHostHeartbeat as n, workbuddyHostHeartbeatPath as o, deriveCatalog as p, findWorkbuddyAppExecutable as q, isHeartbeatProcessAlive as r, writeHostHeartbeat as s, WORKBUDDY_HOST_HEARTBEAT_FILENAME as t, FALLBACK_WORKBUDDY_MODELS as u, WorkBuddyCredentialStore as v, hasEncryptedCredentialFields as w, defaultDesktopAuthCandidates as x, WorkBuddyEncryptedCredentialError as y, parseUpstreamModel as z };
|
package/lib/index.d.ts
CHANGED
|
@@ -631,6 +631,19 @@ declare class WorkBuddyUpstreamClient {
|
|
|
631
631
|
*
|
|
632
632
|
* The caller MUST drain {@link WorkBuddyProbeAnswer.response}; an unread body
|
|
633
633
|
* holds the connection open.
|
|
634
|
+
*
|
|
635
|
+
* ALWAYS bounded in time. The pool's batch runner is serial and passes no
|
|
636
|
+
* signal, so without a ceiling here one stalled connection blocks every account
|
|
637
|
+
* behind it — the batch looks hung with no way to tell which member did it.
|
|
638
|
+
* A supplied signal is COMBINED with the timeout rather than replacing it, so
|
|
639
|
+
* the single-model route keeps its cancellation behaviour and still cannot
|
|
640
|
+
* hang forever.
|
|
641
|
+
*
|
|
642
|
+
* The ceiling covers ONLY the wait for a response — it is cleared the moment
|
|
643
|
+
* `fetch` resolves. Holding a timer over the body would put a healthy but slow
|
|
644
|
+
* model on the same clock as a dead endpoint, and the whole point of the
|
|
645
|
+
* period is to answer "did the upstream answer at all". A probe asks for a
|
|
646
|
+
* single token, so the body that follows is one SSE line.
|
|
634
647
|
*/
|
|
635
648
|
probeChat(credential: WorkBuddyCredential, bodyJson: string, signal?: AbortSignal): Promise<WorkBuddyProbeAnswer>;
|
|
636
649
|
/** POST the token-refresh endpoint; the caller merges the outcome. */
|
|
@@ -793,12 +806,62 @@ interface WorkBuddyShim {
|
|
|
793
806
|
/** Stop serving and destroy open connections. */
|
|
794
807
|
close(): Promise<void>;
|
|
795
808
|
}
|
|
809
|
+
/**
|
|
810
|
+
* Supplies the credential to retry a failed chat request with.
|
|
811
|
+
*
|
|
812
|
+
* Called with every account id already tried for THIS request, the one that just
|
|
813
|
+
* failed LAST, and resolves to the next candidate — or `undefined` when the pool
|
|
814
|
+
* is off, empty, or exhausted, in which case the original failure is reported
|
|
815
|
+
* unchanged.
|
|
816
|
+
*
|
|
817
|
+
* The plugin owns the policy (which pool members count as usable, in what
|
|
818
|
+
* order); the shim owns only the retry loop and the wire-level rule that a
|
|
819
|
+
* retry may happen before anything has been written to the client.
|
|
820
|
+
*/
|
|
821
|
+
type WorkBuddyFailoverAccount = (triedAccountIds: readonly string[]) => Promise<WorkBuddyCredential | undefined>;
|
|
796
822
|
/** Constructor dependencies. */
|
|
797
823
|
interface WorkBuddyShimOptions {
|
|
798
824
|
store: WorkBuddyCredentialStore;
|
|
799
825
|
client: Pick<WorkBuddyUpstreamClient, 'chatStream'>;
|
|
800
826
|
catalog: WorkBuddyCatalog;
|
|
801
827
|
logger?: ShimLogger;
|
|
828
|
+
/**
|
|
829
|
+
* Pool failover for chat requests. Absent means no failover at all — the
|
|
830
|
+
* pre-pool behaviour, where the selected account serves and its failure is
|
|
831
|
+
* reported as-is.
|
|
832
|
+
*/
|
|
833
|
+
failoverAccount?: WorkBuddyFailoverAccount;
|
|
834
|
+
/**
|
|
835
|
+
* Called before the FIRST attempt of every chat request, so the plugin can
|
|
836
|
+
* point the store at the account the pool's ranking says should serve.
|
|
837
|
+
*
|
|
838
|
+
* It exists because "who serves" and "who to retry with" are the same decision
|
|
839
|
+
* made at two moments: the ranking picks the first account, and after a failure
|
|
840
|
+
* picks the next one. The shim resolves the credential itself right after this
|
|
841
|
+
* returns, so the ranking never has to produce a credential — which keeps
|
|
842
|
+
* token REFRESH in one place (`store.resolve()`) instead of duplicating it on
|
|
843
|
+
* a path that would silently send expired tokens.
|
|
844
|
+
*
|
|
845
|
+
* Absent means the store's own selection always serves.
|
|
846
|
+
*/
|
|
847
|
+
prepareAccount?: () => Promise<void>;
|
|
848
|
+
/**
|
|
849
|
+
* Called for every attempt that failed upstream, before the next one is tried.
|
|
850
|
+
*
|
|
851
|
+
* Exists so the plugin can RECORD the failure as a measurement. The ranking
|
|
852
|
+
* that picks the serving account reads the pool's STORED measurements, so
|
|
853
|
+
* without this an account that just answered 429 still reads as untested — and
|
|
854
|
+
* the next request picks it again and pays the same failed round trip before
|
|
855
|
+
* failing over.
|
|
856
|
+
*
|
|
857
|
+
* Fired without awaiting: a slow write must never delay the retry the user is
|
|
858
|
+
* waiting on, and the measurement only matters for LATER requests.
|
|
859
|
+
*/
|
|
860
|
+
onAccountFailure?: (accountId: string, failure: {
|
|
861
|
+
status: number;
|
|
862
|
+
kind: UpstreamErrorKind;
|
|
863
|
+
message: string;
|
|
864
|
+
}) => void;
|
|
802
865
|
}
|
|
803
866
|
/**
|
|
804
867
|
* Start the loopback endpoint. Requests must carry the shim's shared secret;
|
|
@@ -1468,6 +1531,20 @@ interface WorkBuddyPoolProbe {
|
|
|
1468
1531
|
* must never be turned into a locally invented countdown.
|
|
1469
1532
|
*/
|
|
1470
1533
|
retryAtMs?: number;
|
|
1534
|
+
/**
|
|
1535
|
+
* The upstream's own words on the failure, already redacted.
|
|
1536
|
+
*
|
|
1537
|
+
* Kept so the card can say WHY an account left the pool instead of repeating
|
|
1538
|
+
* one generic sentence for a DNS failure, a gateway 502 and a request the user
|
|
1539
|
+
* cancelled alike. Those three used to render as the SAME line, which left the
|
|
1540
|
+
* user with a message they could not act on and no way to tell a broken
|
|
1541
|
+
* account from a broken network.
|
|
1542
|
+
*
|
|
1543
|
+
* ABSENT for a success, and for any measurement written before this field
|
|
1544
|
+
* existed: an `outcome` without a `message` is a complete, valid record, so
|
|
1545
|
+
* old probe files stay readable rather than being discarded.
|
|
1546
|
+
*/
|
|
1547
|
+
message?: string;
|
|
1471
1548
|
}
|
|
1472
1549
|
/** One account's live credit situation, as the pool ranks it. */
|
|
1473
1550
|
interface WorkBuddyPoolCredits {
|
|
@@ -1874,6 +1951,15 @@ interface WorkBuddyWebPoolAccount {
|
|
|
1874
1951
|
atMs: number;
|
|
1875
1952
|
/** Only when the upstream named a time; never invented locally. */
|
|
1876
1953
|
retryAtMs?: number;
|
|
1954
|
+
/**
|
|
1955
|
+
* Why it failed, in the upstream's own redacted words.
|
|
1956
|
+
*
|
|
1957
|
+
* Carried so the pool table can distinguish a DNS failure from a gateway
|
|
1958
|
+
* 502 from a request the user cancelled, instead of repeating one generic
|
|
1959
|
+
* sentence for all three. ABSENT on success and on measurements written
|
|
1960
|
+
* before this field existed, so the card must render the bare outcome then.
|
|
1961
|
+
*/
|
|
1962
|
+
message?: string;
|
|
1877
1963
|
};
|
|
1878
1964
|
/** Set when this account cannot serve right now. */
|
|
1879
1965
|
excludedBy?: WorkBuddyWebPoolExclusion;
|
|
@@ -1907,8 +1993,13 @@ interface WorkBuddyWebPoolAccount {
|
|
|
1907
1993
|
*/
|
|
1908
1994
|
interface WorkBuddyWebPool {
|
|
1909
1995
|
enabled: boolean;
|
|
1910
|
-
|
|
1911
|
-
|
|
1996
|
+
/**
|
|
1997
|
+
* Whether this region offers the daily check-in at all.
|
|
1998
|
+
*
|
|
1999
|
+
* The card hides the action entirely when false, rather than rendering a
|
|
2000
|
+
* button whose only outcome is a refusal.
|
|
2001
|
+
*/
|
|
2002
|
+
checkinSupported: boolean;
|
|
1912
2003
|
/** The model a test would use right now; absent when none can be resolved. */
|
|
1913
2004
|
targetModelId?: string;
|
|
1914
2005
|
/**
|
|
@@ -1936,15 +2027,6 @@ interface WorkBuddyWebPool {
|
|
|
1936
2027
|
* ran on ZERO accounts and reported success.
|
|
1937
2028
|
*/
|
|
1938
2029
|
effectiveMemberAccountIds?: readonly string[];
|
|
1939
|
-
/**
|
|
1940
|
-
* The account rotation most recently moved billing TO, when it moved.
|
|
1941
|
-
*
|
|
1942
|
-
* Reported so the card can record a switch it did not perform: rotation
|
|
1943
|
-
* happens on the Host (including on a timer), and the card is otherwise
|
|
1944
|
-
* unable to tell a switch from an unchanged state — which would make
|
|
1945
|
-
* "every switch is recorded" a promise the UI could not keep.
|
|
1946
|
-
*/
|
|
1947
|
-
rotatedToAccountId?: string;
|
|
1948
2030
|
accounts: readonly WorkBuddyWebPoolAccount[];
|
|
1949
2031
|
/**
|
|
1950
2032
|
* The models a manual target can be chosen from.
|
|
@@ -2093,8 +2175,6 @@ interface WorkBuddyPoolDeps {
|
|
|
2093
2175
|
/** The region's stored preferences, resolved from the Host config. */
|
|
2094
2176
|
preferences(region: WorkBuddyRegion): {
|
|
2095
2177
|
enabled: boolean;
|
|
2096
|
-
rotateByCredits: boolean;
|
|
2097
|
-
autoTestIntervalMinutes: number;
|
|
2098
2178
|
targetModelId: string;
|
|
2099
2179
|
memberAccountIds: readonly string[];
|
|
2100
2180
|
};
|
|
@@ -2110,14 +2190,6 @@ interface WorkBuddyPoolDeps {
|
|
|
2110
2190
|
* checked) instead of passing the guard and running a batch over nothing.
|
|
2111
2191
|
*/
|
|
2112
2192
|
effectiveMemberAccountIds?(region: WorkBuddyRegion): Promise<readonly string[]>;
|
|
2113
|
-
/**
|
|
2114
|
-
* The account rotation last redirected billing to, when it did.
|
|
2115
|
-
*
|
|
2116
|
-
* Reported so the card can log a switch that happened on the Host (possibly
|
|
2117
|
-
* on a timer) — without it, "every switch leaves a record" is a promise the
|
|
2118
|
-
* browser half has no way to keep.
|
|
2119
|
-
*/
|
|
2120
|
-
rotatedToAccountId?(region: WorkBuddyRegion): Promise<string | undefined>;
|
|
2121
2193
|
/**
|
|
2122
2194
|
* Today's check-in state per account id.
|
|
2123
2195
|
*
|
|
@@ -2125,6 +2197,8 @@ interface WorkBuddyPoolDeps {
|
|
|
2125
2197
|
* entry) means "not read", which the card renders as unknown rather than as a
|
|
2126
2198
|
* definite "not checked in" — a claim that would contradict the check-in the
|
|
2127
2199
|
* user just performed.
|
|
2200
|
+
*
|
|
2201
|
+
* Only the CN region has a check-in to report; see {@link checkinSupported}.
|
|
2128
2202
|
*/
|
|
2129
2203
|
checkedInToday?(region: WorkBuddyRegion): Promise<Readonly<Record<string, boolean>>>;
|
|
2130
2204
|
/**
|
|
@@ -2137,6 +2211,16 @@ interface WorkBuddyPoolDeps {
|
|
|
2137
2211
|
}[]>;
|
|
2138
2212
|
/** The account currently billing traffic for the region, when known. */
|
|
2139
2213
|
currentAccountId?(region: WorkBuddyRegion): Promise<string | undefined>;
|
|
2214
|
+
/**
|
|
2215
|
+
* Whether this region offers the daily check-in action at all.
|
|
2216
|
+
*
|
|
2217
|
+
* The CN app rewards a daily check-in; the international region is not offered
|
|
2218
|
+
* one here, so its card must not show the button and its route must refuse the
|
|
2219
|
+
* action. A capability rather than a region name compared in the browser: the
|
|
2220
|
+
* policy belongs in the Host, and the card should not have to know which
|
|
2221
|
+
* regions happen to have the feature.
|
|
2222
|
+
*/
|
|
2223
|
+
checkinSupported?(region: WorkBuddyRegion): boolean;
|
|
2140
2224
|
/**
|
|
2141
2225
|
* Claim the daily reward for every account of one region.
|
|
2142
2226
|
*
|
|
@@ -2145,8 +2229,15 @@ interface WorkBuddyPoolDeps {
|
|
|
2145
2229
|
* reports "unavailable" instead of appearing to succeed.
|
|
2146
2230
|
*/
|
|
2147
2231
|
checkin?(region: WorkBuddyRegion): Promise<readonly WorkBuddyPoolCheckinRow[]>;
|
|
2148
|
-
/**
|
|
2149
|
-
|
|
2232
|
+
/**
|
|
2233
|
+
* Test every account of one region against the resolved target model.
|
|
2234
|
+
*
|
|
2235
|
+
* `onRow` is optional and exists so the route can STREAM each account as it
|
|
2236
|
+
* finishes. An older Host ignores it and answers with the whole batch at once;
|
|
2237
|
+
* the route handles both, because a missing reporter must degrade to the
|
|
2238
|
+
* previous behaviour rather than to no rows at all.
|
|
2239
|
+
*/
|
|
2240
|
+
test?(region: WorkBuddyRegion, modelId: string, onRow?: (row: WorkBuddyPoolTestRow) => void): Promise<readonly WorkBuddyPoolTestRow[]>;
|
|
2150
2241
|
/** The region's live catalog, used to resolve the free target model. */
|
|
2151
2242
|
catalog?(region: WorkBuddyRegion): readonly WorkBuddyModelInfo[];
|
|
2152
2243
|
}
|
|
@@ -2224,18 +2315,21 @@ interface WorkBuddyRegionState {
|
|
|
2224
2315
|
* measurement living in the same slot would be rolled back by the next save.
|
|
2225
2316
|
*/
|
|
2226
2317
|
interface WorkBuddyPoolPreferences {
|
|
2227
|
-
/** Whether the pool is active for this region. Opt-in: default false. */
|
|
2228
|
-
enabled?: boolean;
|
|
2229
2318
|
/**
|
|
2230
|
-
* Whether the
|
|
2319
|
+
* Whether the pool is active for this region — which now means the
|
|
2320
|
+
* FAILOVER switch, not a credit ranking.
|
|
2231
2321
|
*
|
|
2232
|
-
* Opt-in
|
|
2233
|
-
*
|
|
2234
|
-
*
|
|
2322
|
+
* Opt-in: default false. While on, a chat request that fails upstream is
|
|
2323
|
+
* retried against the other usable members of this region's pool, in order,
|
|
2324
|
+
* until one serves it or none is left (see `src/shim.ts`). While off the
|
|
2325
|
+
* plugin behaves exactly as it did before the pool existed: the account the
|
|
2326
|
+
* user selected serves every request, and a failure is reported as-is.
|
|
2327
|
+
*
|
|
2328
|
+
* It deliberately does NOT change which account serves NEXT: failover is
|
|
2329
|
+
* per-request borrowing, so the user's selection stays in effect and the
|
|
2330
|
+
* card can keep reporting who really served.
|
|
2235
2331
|
*/
|
|
2236
|
-
|
|
2237
|
-
/** Minutes between automatic tests; clamped to 5..1440. */
|
|
2238
|
-
autoTestIntervalMinutes?: number;
|
|
2332
|
+
enabled?: boolean;
|
|
2239
2333
|
/** Model id to test; `''` means auto-pick a free model from this region. */
|
|
2240
2334
|
targetModelId?: string;
|
|
2241
2335
|
/**
|