dsh-connect-workbuddy 2.3.0 → 3.0.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.
@@ -1494,6 +1494,16 @@ var WorkBuddyCredentialStore = class {
1494
1494
  desktopPathOverride;
1495
1495
  accountId;
1496
1496
  inflight;
1497
+ /**
1498
+ * A RUNTIME account override from the pool's rotation, or undefined.
1499
+ *
1500
+ * Deliberately separate from {@link accountId}, which is the user's persisted
1501
+ * choice. Rotation decides who serves *now*; it must never rewrite what the
1502
+ * user picked, or "turn rotation off" could not restore their selection and
1503
+ * the plugin would be unable to tell the two apart afterwards. Nothing here
1504
+ * is ever written to settings.
1505
+ */
1506
+ rotatedAccountId;
1497
1507
  constructor(options) {
1498
1508
  this.refresh = options.refresh;
1499
1509
  this.refreshMarginMs = options.refreshMarginMs ?? 3e5;
@@ -1715,10 +1725,28 @@ var WorkBuddyCredentialStore = class {
1715
1725
  /** The freshest stored credential for the current selection, no refresh. */
1716
1726
  async current() {
1717
1727
  const credentials = await this.readAll();
1728
+ if (this.rotatedAccountId !== void 0) {
1729
+ const rotated = credentials.find((credential) => workbuddyAccountId(credential) === this.rotatedAccountId);
1730
+ if (rotated !== void 0) return rotated;
1731
+ }
1718
1732
  if (this.accountId === void 0) return this.preferred(credentials);
1719
1733
  return credentials.find((credential) => workbuddyAccountId(credential) === this.accountId);
1720
1734
  }
1721
1735
  /**
1736
+ * Set (or clear) the pool's RUNTIME account override.
1737
+ *
1738
+ * Never persists and never touches {@link accountId}: the user's saved choice
1739
+ * is what `clear` restores, which is the whole reason this lives in its own
1740
+ * field. Rotation is off when this is called with `undefined`.
1741
+ */
1742
+ setRotatedAccount(accountId) {
1743
+ this.rotatedAccountId = accountId;
1744
+ }
1745
+ /** The account the pool is currently rotating to, if any. */
1746
+ rotatedAccount() {
1747
+ return this.rotatedAccountId;
1748
+ }
1749
+ /**
1722
1750
  * One account's stored credential by id, WITHOUT consulting or changing the
1723
1751
  * current selection.
1724
1752
  *
@@ -2258,7 +2286,7 @@ var WorkBuddyCatalog = class {
2258
2286
  //#endregion
2259
2287
  //#region src/version.ts
2260
2288
  /** The npm package version this build was produced from. */
2261
- const WORKBUDDY_CONNECT_VERSION = "2.3.0";
2289
+ const WORKBUDDY_CONNECT_VERSION = "3.0.0";
2262
2290
  //#endregion
2263
2291
  //#region src/host-heartbeat.ts
2264
2292
  /**
package/lib/index.d.ts CHANGED
@@ -237,6 +237,16 @@ declare class WorkBuddyCredentialStore {
237
237
  private desktopPathOverride;
238
238
  private accountId;
239
239
  private inflight;
240
+ /**
241
+ * A RUNTIME account override from the pool's rotation, or undefined.
242
+ *
243
+ * Deliberately separate from {@link accountId}, which is the user's persisted
244
+ * choice. Rotation decides who serves *now*; it must never rewrite what the
245
+ * user picked, or "turn rotation off" could not restore their selection and
246
+ * the plugin would be unable to tell the two apart afterwards. Nothing here
247
+ * is ever written to settings.
248
+ */
249
+ private rotatedAccountId;
240
250
  constructor(options: WorkBuddyStoreOptions);
241
251
  /** Whether a credential's login domain belongs to this store's region. */
242
252
  private matchesRegion;
@@ -346,6 +356,16 @@ declare class WorkBuddyCredentialStore {
346
356
  selectionLost(): Promise<boolean>;
347
357
  /** The freshest stored credential for the current selection, no refresh. */
348
358
  current(): Promise<WorkBuddyCredential | undefined>;
359
+ /**
360
+ * Set (or clear) the pool's RUNTIME account override.
361
+ *
362
+ * Never persists and never touches {@link accountId}: the user's saved choice
363
+ * is what `clear` restores, which is the whole reason this lives in its own
364
+ * field. Rotation is off when this is called with `undefined`.
365
+ */
366
+ setRotatedAccount(accountId: string | undefined): void;
367
+ /** The account the pool is currently rotating to, if any. */
368
+ rotatedAccount(): string | undefined;
349
369
  /**
350
370
  * One account's stored credential by id, WITHOUT consulting or changing the
351
371
  * current selection.
@@ -1195,23 +1215,48 @@ declare const WORKBUDDY_CONNECT_VERSION: string;
1195
1215
  * would report every global model as broken.
1196
1216
  */
1197
1217
  declare const PROBE_SYSTEM_PROMPT = "You are a connectivity check. Reply with a single word.";
1198
- /** The one-word user turn; the smallest thing a chat endpoint accepts. */
1199
- declare const PROBE_USER_PROMPT = "ping";
1200
1218
  /**
1201
- * Output cap for a probe. One token is enough to prove the model answers, and
1202
- * keeps the cost negligible: live measurements of `max_tokens: 1` probes
1203
- * reported `credit` between 0 and 0.01, and one such probe left the credit
1204
- * balance unchanged (933 → 933). For scale, a model on that roster bills 0.79+
1205
- * per real reply. The point of the feature is to learn whether a model works
1206
- * while spending as close to nothing as the service allows.
1219
+ * Output cap for a probe. One token is enough to prove the model answers.
1220
+ *
1221
+ * The probe's cost comes from its INPUT, not its output (see
1222
+ * {@link PROBE_INPUT_TOKENS}), so paying for a long completion would add money
1223
+ * without adding signal.
1207
1224
  */
1208
1225
  declare const PROBE_MAX_TOKENS = 1;
1226
+ /**
1227
+ * How much input a probe sends, in tokens.
1228
+ *
1229
+ * This is the whole point of the probe, and it is NOT a free choice. The
1230
+ * upstream's rate limit (business code 6004) fires on request SIZE, measured on
1231
+ * 2026-09-29 by varying only the input against one account and one model:
1232
+ *
1233
+ * | input | prompt_tokens the upstream counted | result |
1234
+ * | --- | --- | --- |
1235
+ * | ~10 | 38 | ok |
1236
+ * | ~1,000 | 1,138 | ok |
1237
+ * | ~10,000 | 11,138 | ok |
1238
+ * | ~18,000 | 20,018 | ok |
1239
+ * | ~25,000 | 25,023 | ok |
1240
+ * | ~30,000 | ~32k | **429 / 6004** |
1241
+ *
1242
+ * So the threshold sits between 20k and 30k, a SINGLE large request is enough to
1243
+ * trip it (it is not a running total), and each account reports its own reset
1244
+ * time (two accounts at the same moment: `22:20:33` and `00:32:51`).
1245
+ *
1246
+ * A probe that sent a handful of tokens answered "usable" while every real
1247
+ * request in a long conversation was refused — the probe was asking too small a
1248
+ * question. 25k is sized to be a realistic long-conversation payload.
1249
+ */
1250
+ declare const PROBE_INPUT_TOKENS = 25000;
1209
1251
  /**
1210
1252
  * Build the request body for one probe.
1211
1253
  *
1212
1254
  * `stream` is forced because the upstream rejects non-streaming chat requests;
1213
1255
  * `prepareChatBody` enforces that too, but sending it explicitly keeps this
1214
1256
  * function's output valid on its own.
1257
+ *
1258
+ * Measured on the built artifact: 112,713 bytes of body, and the upstream
1259
+ * counted `prompt_tokens: 25023` at a cost of `credit: 0.72`.
1215
1260
  */
1216
1261
  declare function probeRequestBody(modelId: string): string;
1217
1262
  /**
@@ -1242,7 +1287,7 @@ interface WorkBuddyProbeResult {
1242
1287
  /** A time the upstream named, in ms. Absent when it named none. */
1243
1288
  retryAtMs?: number;
1244
1289
  /** Where a supplied `retryAtMs` came from, so the card can say so. */
1245
- retrySource?: 'retry-after' | 'quota-refresh';
1290
+ retrySource?: 'retry-after' | 'upstream-message' | 'quota-refresh';
1246
1291
  }
1247
1292
  /**
1248
1293
  * Parse a `Retry-After` header value into an absolute time.
@@ -1258,24 +1303,29 @@ declare function parseRetryAfter(value: string | null, nowMs: number): number |
1258
1303
  /**
1259
1304
  * The cooldown the upstream stated, if it stated one.
1260
1305
  *
1261
- * Two sources, in order of authority:
1306
+ * Three sources, in order of authority:
1262
1307
  *
1263
1308
  * 1. `Retry-After` on the failure response. This is the upstream naming a time
1264
1309
  * for THIS request, so it wins.
1265
- * 2. The region's monthly quota refresh point, but ONLY for an out-of-credit
1310
+ * 2. The reset time the upstream writes into its own failure body (see
1311
+ * {@link parseUpstreamResetAt}). This is the case that actually fires on
1312
+ * this service: a 429 from the Chinese gateway carries code 6004 and a
1313
+ * `将在 … 重置` sentence, with no `Retry-After` header at all.
1314
+ * 3. The region's monthly quota refresh point, but ONLY for an out-of-credit
1266
1315
  * outcome. A quota that resets at a known time is the one case where "when
1267
1316
  * can I use this again" has a real answer even without a header — and it is
1268
1317
  * the answer for the most common real limit on this service.
1269
1318
  *
1270
- * A `rate-limited` outcome with neither source returns `{}`, which the card
1271
- * renders as "the upstream did not say when". That is the honest answer: the
1272
- * live probes found no rate-limit metadata in any response at all, so inventing
1273
- * a number here would be pure fiction.
1319
+ * A limited outcome with none of the three returns `{}`, which the card renders
1320
+ * as "the upstream did not say when". That remains the honest answer for a
1321
+ * genuinely timeless failure; inventing a number here would be pure fiction.
1274
1322
  */
1275
1323
  declare function cooldownOf(input: {
1276
1324
  outcome: WorkBuddyProbeOutcome;
1277
1325
  retryAfter: string | null;
1278
1326
  nowMs: number;
1327
+ /** The upstream's own failure text, where a reset sentence may live. */
1328
+ body?: string;
1279
1329
  /** The region's next monthly quota refresh, when the upstream declares one. */
1280
1330
  quotaRefreshAtMs?: number;
1281
1331
  }): Pick<WorkBuddyProbeResult, 'retryAtMs' | 'retrySource'>;
@@ -1314,9 +1364,9 @@ interface WorkBuddyProbeClient {
1314
1364
  * Run one probe for one model and reduce it to the card's row.
1315
1365
  *
1316
1366
  * The successful path drains the SSE stream purely to read the `credit` figure
1317
- * the upstream reports, then discards the rest: the probe's question is "did
1318
- * this model answer", and `max_tokens: 1` means there is nothing else in the
1319
- * stream worth keeping.
1367
+ * the upstream reports, then discards the rest: the question is whether this
1368
+ * model answers a real-sized request, and `max_tokens: 1` means there is nothing
1369
+ * else in the stream worth keeping.
1320
1370
  *
1321
1371
  * Never throws. A probe is a diagnostic the user asked for, so every failure is
1322
1372
  * a RESULT — one dead model must not abort the batch and hide the other rows.
@@ -1331,6 +1381,77 @@ declare function probeModel(input: {
1331
1381
  signal?: AbortSignal;
1332
1382
  }): Promise<WorkBuddyProbeResult>;
1333
1383
  //#endregion
1384
+ //#region src/account-pool.d.ts
1385
+ /** One account's pool entry: what the picker needs, plus its last measurement. */
1386
+ interface WorkBuddyPoolAccount {
1387
+ /** Stable account id (the same one `WorkBuddyAccountChoice` carries). */
1388
+ id: string;
1389
+ /** Human name, or `''` when the desktop app recorded none. */
1390
+ accountName: string;
1391
+ }
1392
+ /**
1393
+ * What the pool knows about ONE account's last test of the target model.
1394
+ *
1395
+ * `undefined` means "never tested", which is deliberately distinct from every
1396
+ * measured outcome: an untested account is a candidate (the user may want to
1397
+ * use it now) whereas a measured failure is not. Collapsing the two would make
1398
+ * a fresh pool look empty until the first timer tick.
1399
+ */
1400
+ interface WorkBuddyPoolProbe {
1401
+ outcome: WorkBuddyProbeOutcome;
1402
+ /** When this measurement was taken, in epoch ms. */
1403
+ atMs: number;
1404
+ /**
1405
+ * When this account may be usable again, when the upstream stated a time.
1406
+ *
1407
+ * Present for a `rate-limited` outcome whose failure body carried a reset
1408
+ * sentence, and for an `out-of-credit` outcome with a known quota refresh.
1409
+ * ABSENT when the upstream named no time — that absence is meaningful and
1410
+ * must never be turned into a locally invented countdown.
1411
+ */
1412
+ retryAtMs?: number;
1413
+ }
1414
+ /** One account's live credit situation, as the pool ranks it. */
1415
+ interface WorkBuddyPoolCredits {
1416
+ total: number;
1417
+ /** Credits expiring within the card's "soon" window (3 days). */
1418
+ expiringSoon: number;
1419
+ /** When the nearest package expires, if any does. */
1420
+ nearestExpiryMs?: number;
1421
+ }
1422
+ /**
1423
+ * Everything the ranking rule reads about one account. Assembled by the Host
1424
+ * from the credential store, the credits route, and the runtime probe store —
1425
+ * this module only consumes it.
1426
+ */
1427
+ interface WorkBuddyPoolMember {
1428
+ account: WorkBuddyPoolAccount;
1429
+ credits?: WorkBuddyPoolCredits;
1430
+ probe?: WorkBuddyPoolProbe;
1431
+ /** When the stored credential expires, used as the final tie-break. */
1432
+ tokenExpiresAtMs?: number;
1433
+ }
1434
+ //#endregion
1435
+ //#region src/account-pool-run.d.ts
1436
+ /** One account's check-in outcome. */
1437
+ interface WorkBuddyPoolCheckinRow {
1438
+ accountId: string;
1439
+ accountName: string;
1440
+ status: 'claimed' | 'already' | 'failed';
1441
+ /** Credits gained, when the upstream stated a figure. */
1442
+ credit?: number;
1443
+ /** Streak length after the claim, when the upstream stated one. */
1444
+ streakDays?: number;
1445
+ /** The upstream's own words on failure, redacted by the caller. */
1446
+ message?: string;
1447
+ }
1448
+ /** One account's test outcome. */
1449
+ interface WorkBuddyPoolTestRow {
1450
+ accountId: string;
1451
+ accountName: string;
1452
+ result: WorkBuddyProbeResult;
1453
+ }
1454
+ //#endregion
1334
1455
  //#region src/status-paths.d.ts
1335
1456
  /**
1336
1457
  * Node-free constants and types shared by the Host and browser halves.
@@ -1390,22 +1511,6 @@ declare function regionOfStatusUrl(url: string): WorkBuddyWebRegion | undefined;
1390
1511
  declare function regionEnabledOf(value: unknown, region: WorkBuddyWebRegion): boolean;
1391
1512
  /** Build the next `regions` settings value for a signed-in tab's save. */
1392
1513
  declare function nextRegionSlots<Slot extends object>(regions: unknown, region: WorkBuddyWebRegion, slot: Slot): Record<string, unknown>;
1393
- /**
1394
- * Build the next `regions` settings value for a provider on/off toggle. ONLY
1395
- * the target region's `enabled` flag changes: every other field of that slot
1396
- * (its directory, selection, image opt-ins, context budgets) and every other
1397
- * region's slot are carried over verbatim, so switching a provider off never
1398
- * discards the user's model picks and switching it back on restores them.
1399
- *
1400
- * This is deliberately separate from {@link nextRegionSlots}: that helper
1401
- * writes a whole slot from a signed-in tab's draft, while this one must work
1402
- * for a region that is signed OUT — which is precisely the region a user wants
1403
- * to switch off (no international install, no international account).
1404
- *
1405
- * `value` may be the whole settings section or the `regions` map; the RETURN
1406
- * value is always the `regions` map, i.e. exactly what `settingsScope.set(
1407
- * 'regions', ...)` needs.
1408
- */
1409
1514
  declare function nextRegionEnabled(value: unknown, region: WorkBuddyWebRegion, enabled: boolean): Record<string, unknown>;
1410
1515
  /** One credit package as the upstream returns it, node-free. */
1411
1516
  interface WorkBuddyWebCreditPackage {
@@ -1600,6 +1705,23 @@ type WorkBuddyWebUsage =
1600
1705
  imageModelIds: readonly string[];
1601
1706
  credits?: WorkBuddyWebCredits;
1602
1707
  creditsError?: string;
1708
+ /**
1709
+ * The LAST refresh failed, but the snapshot below is intact.
1710
+ *
1711
+ * A failed usage re-read must not blank the panel. The account picker,
1712
+ * credits, model directory and account pool are all gated on this region
1713
+ * being 'signed-in', so collapsing the whole snapshot into the union's
1714
+ * `{ status: 'error' }` variant (which carries no data at all) hides every
1715
+ * one of them — including a pool the user is midway through editing. It
1716
+ * reads as "the plugin lost my configuration". Carrying the failure as an
1717
+ * overlay keeps the last good directory on screen with a banner above it,
1718
+ * which is also what `creditsError`/`checkinError` already do per section.
1719
+ *
1720
+ * Only ever set on a snapshot that WAS healthy; a region that never loaded
1721
+ * still reports the plain `{ status: 'error' }` so it can show the
1722
+ * sign-in guidance instead of an empty panel.
1723
+ */
1724
+ refreshError?: string;
1603
1725
  checkin?: WorkBuddyWebCheckin;
1604
1726
  checkinError?: string;
1605
1727
  /**
@@ -1610,6 +1732,13 @@ type WorkBuddyWebUsage =
1610
1732
  credentialRejected?: boolean;
1611
1733
  /** Present only alongside {@link credentialRejected}. */
1612
1734
  recovery?: WorkBuddyWebRecovery;
1735
+ /**
1736
+ * This region's account-pool state, when the Host supports it.
1737
+ *
1738
+ * Absent on a Host without pool support, so the card renders no pool
1739
+ * section instead of an empty one.
1740
+ */
1741
+ pool?: WorkBuddyWebPool;
1613
1742
  /**
1614
1743
  * Diagnostic only: whether this Host build's Config carries the volatile
1615
1744
  * markers dsh-settings requires before it will persist any field. Exposed
@@ -1653,15 +1782,129 @@ interface WorkBuddyWebProbeResult {
1653
1782
  * A time the upstream named for using this model again, in ms.
1654
1783
  *
1655
1784
  * ABSENT means the upstream named none, and the card must say exactly that.
1656
- * The live probes that shaped this feature found no rate-limit metadata in
1657
- * any response — no `Retry-After`, no `X-RateLimit-*` — so a client-invented
1658
- * countdown here would be fiction. Only two sources ever fill this:
1659
- * a real `Retry-After`, or (for an exhausted quota) the region's own quota
1660
- * refresh point, which the upstream does declare.
1785
+ * A client-invented countdown here would be fiction. Three sources fill it:
1786
+ * a real `Retry-After` header; the reset sentence the upstream writes into
1787
+ * its own failure BODY (`将在 … UTC+8 重置`, which is where this service
1788
+ * actually puts it — its 429s carry no rate-limit header at all); or, for an
1789
+ * exhausted quota, the region's own quota refresh point.
1661
1790
  */
1662
1791
  retryAtMs?: number;
1663
- /** Which of those two sources supplied {@link retryAtMs}. */
1664
- retrySource?: 'retry-after' | 'quota-refresh';
1792
+ /** Which of those three sources supplied {@link retryAtMs}. */
1793
+ retrySource?: 'retry-after' | 'upstream-message' | 'quota-refresh';
1794
+ }
1795
+ /** Why one account is currently out of the pool. */
1796
+ type WorkBuddyWebPoolExclusion = 'unusable' | 'rate-limited' | 'out-of-credit' | 'credential-rejected' | 'not-found' | 'unavailable' | 'failed';
1797
+ /**
1798
+ * One account's row in the pool, as the card renders it.
1799
+ *
1800
+ * Ties together the three facts the card shows per account — credits, the last
1801
+ * test, and whether it is currently eligible — so the ranking the card displays
1802
+ * comes from ONE Host answer rather than being re-derived in the browser.
1803
+ */
1804
+ interface WorkBuddyWebPoolAccount {
1805
+ accountId: string;
1806
+ accountName: string;
1807
+ /** Total remaining credits, absent when the credits route failed. */
1808
+ credits?: number;
1809
+ /** Credits expiring within 3 days. */
1810
+ expiringSoon?: number;
1811
+ /** When the nearest package expires, in ms. */
1812
+ nearestExpiryMs?: number;
1813
+ /** The last test of the target model, absent when never tested. */
1814
+ probe?: {
1815
+ outcome: WorkBuddyWebProbeOutcome;
1816
+ atMs: number;
1817
+ /** Only when the upstream named a time; never invented locally. */
1818
+ retryAtMs?: number;
1819
+ };
1820
+ /** Set when this account cannot serve right now. */
1821
+ excludedBy?: WorkBuddyWebPoolExclusion;
1822
+ /** Whether this is the account currently billing traffic for the region. */
1823
+ current: boolean;
1824
+ /**
1825
+ * Whether the user checked this account into the pool.
1826
+ *
1827
+ * Listed rows include UNCHECKED accounts so the card can offer them to check:
1828
+ * membership is an explicit opt-in, and a user can only opt in to something
1829
+ * they can see. Only checked rows can bill or be batched.
1830
+ */
1831
+ member: boolean;
1832
+ /**
1833
+ * Today's check-in state for this account.
1834
+ *
1835
+ * ABSENT means it was not read — the card must then say so rather than
1836
+ * rendering a definite "not checked in", which would contradict the check-in
1837
+ * result the user just watched succeed.
1838
+ */
1839
+ checkedInToday?: boolean;
1840
+ }
1841
+ /**
1842
+ * The pool's state for one region.
1843
+ *
1844
+ * `targetModelId`/`targetModelSource` exist so the card can state WHICH model a
1845
+ * test would use and WHERE that choice came from. `none` is a real, common
1846
+ * answer (the CN catalog carries no free model until its first refresh) and the
1847
+ * card must render it as "no free model" rather than silently substituting a
1848
+ * paid one.
1849
+ */
1850
+ interface WorkBuddyWebPool {
1851
+ enabled: boolean;
1852
+ rotateByCredits: boolean;
1853
+ autoTestIntervalMinutes: number;
1854
+ /** The model a test would use right now; absent when none can be resolved. */
1855
+ targetModelId?: string;
1856
+ /**
1857
+ * The saved target id that is no longer offered by this region's catalog.
1858
+ *
1859
+ * Present only when {@link targetModelSource} is `'stale'`. The card says so
1860
+ * and offers a way back to automatic, instead of enabling a test button that
1861
+ * would run against a model the upstream does not offer.
1862
+ */
1863
+ staleTargetModelId?: string;
1864
+ /**
1865
+ * Where the target came from. `'stale'` means the SAVED id is no longer in
1866
+ * the catalog — a distinct state from `'none'` (no free model exists),
1867
+ * because the two need opposite advice.
1868
+ */
1869
+ targetModelSource: 'preferred' | 'free' | 'none' | 'stale';
1870
+ /** The account ids the user has checked into this region's pool. */
1871
+ memberAccountIds?: readonly string[];
1872
+ /**
1873
+ * The subset of {@link memberAccountIds} that resolves to a real local
1874
+ * sign-in — i.e. what a batch actually runs on.
1875
+ *
1876
+ * Sent so the card never re-derives it: counting the raw saved list is what
1877
+ * let the UI claim "1 of 1 selected" and enable both buttons while the Host
1878
+ * ran on ZERO accounts and reported success.
1879
+ */
1880
+ effectiveMemberAccountIds?: readonly string[];
1881
+ /**
1882
+ * The account rotation most recently moved billing TO, when it moved.
1883
+ *
1884
+ * Reported so the card can record a switch it did not perform: rotation
1885
+ * happens on the Host (including on a timer), and the card is otherwise
1886
+ * unable to tell a switch from an unchanged state — which would make
1887
+ * "every switch is recorded" a promise the UI could not keep.
1888
+ */
1889
+ rotatedToAccountId?: string;
1890
+ accounts: readonly WorkBuddyWebPoolAccount[];
1891
+ /**
1892
+ * The models a manual target can be chosen from.
1893
+ *
1894
+ * TRIMMED to what the picker renders (id, name, multiplier) rather than the
1895
+ * full `WorkBuddyWebModel` roster: the model table above already ships that
1896
+ * roster in the same document, and this block is re-read on every 60-second
1897
+ * poll, so carrying the full records here duplicated the whole catalog twice
1898
+ * per response for three fields.
1899
+ */
1900
+ catalog?: readonly WorkBuddyWebPoolModel[];
1901
+ }
1902
+ /** One model as the pool's target picker needs it. */
1903
+ interface WorkBuddyWebPoolModel {
1904
+ id: string;
1905
+ name: string;
1906
+ /** Absent when the upstream stated no multiplier (NOT the same as free). */
1907
+ creditMultiplier?: number;
1665
1908
  }
1666
1909
  /**
1667
1910
  * Deep copy of a settings value with every `{get(): T}` live reference replaced
@@ -1731,14 +1974,19 @@ interface WorkBuddyStatusRouteOptions {
1731
1974
  */
1732
1975
  regionEnabled(region: WorkBuddyRegion): boolean;
1733
1976
  /**
1734
- * Send one minimal request to each named model of a region and report what
1977
+ * Send one real-volume request to each named model of a region and report what
1735
1978
  * came back.
1736
1979
  *
1737
1980
  * Optional: it is the only dependency the card's test button needs, so a Host
1738
1981
  * built without it (or a route mounted for a status-only consumer) simply
1739
1982
  * answers 503 and the button reports that instead of failing the card.
1983
+ *
1984
+ * Every request costs real credits (measured `credit: 0.72` for one probe), so
1985
+ * the card drives this ONE model per click and never as a batch.
1740
1986
  */
1741
- probeModels?(region: WorkBuddyRegion, modelIds: readonly string[], signal?: AbortSignal): Promise<readonly WorkBuddyWebProbeResult[]>;
1987
+ probeModels?(region: WorkBuddyRegion, modelIds: readonly string[], options?: {
1988
+ signal?: AbortSignal;
1989
+ }): Promise<readonly WorkBuddyWebProbeResult[]>;
1742
1990
  /**
1743
1991
  * The region's next monthly quota refresh, when the upstream declares one.
1744
1992
  *
@@ -1767,6 +2015,82 @@ interface WorkBuddyStatusRouteOptions {
1767
2015
  * probe.
1768
2016
  */
1769
2017
  accountUsable?(region: WorkBuddyRegion, account: WorkBuddyRecoveryCandidate): Promise<boolean>;
2018
+ /**
2019
+ * The region's account pool, assembled on the Host.
2020
+ *
2021
+ * Optional as a whole: a Host built without pool support simply omits it, and
2022
+ * the card then renders no pool section rather than an empty, confusing one.
2023
+ * Inside it, `test` is in turn optional because a batch test is the one part
2024
+ * that needs a working probe dependency.
2025
+ */
2026
+ pool?: WorkBuddyPoolDeps;
2027
+ }
2028
+ /**
2029
+ * What the pool route needs from the Host.
2030
+ *
2031
+ * Everything is region-scoped because the two regions are parallel stacks: one
2032
+ * region's pool must never see, rank, or bill the other region's accounts.
2033
+ */
2034
+ interface WorkBuddyPoolDeps {
2035
+ /** The region's stored preferences, resolved from the Host config. */
2036
+ preferences(region: WorkBuddyRegion): {
2037
+ enabled: boolean;
2038
+ rotateByCredits: boolean;
2039
+ autoTestIntervalMinutes: number;
2040
+ targetModelId: string;
2041
+ memberAccountIds: readonly string[];
2042
+ };
2043
+ /** One region's CHECKED accounts, plus their credits and last measurements. */
2044
+ members(region: WorkBuddyRegion): Promise<readonly WorkBuddyPoolMember[]>;
2045
+ /**
2046
+ * The checked accounts that actually RESOLVE to a local sign-in.
2047
+ *
2048
+ * Cheap by contract (a local scan, no upstream calls), unlike {@link members}
2049
+ * which fetches credits per account. The route's empty-pool guard uses this
2050
+ * so a ghost id — a saved member whose sign-in is gone — is refused with
2051
+ * `no-live-members` (distinct from `no-members`, which means nothing was ever
2052
+ * checked) instead of passing the guard and running a batch over nothing.
2053
+ */
2054
+ effectiveMemberAccountIds?(region: WorkBuddyRegion): Promise<readonly string[]>;
2055
+ /**
2056
+ * The account rotation last redirected billing to, when it did.
2057
+ *
2058
+ * Reported so the card can log a switch that happened on the Host (possibly
2059
+ * on a timer) — without it, "every switch leaves a record" is a promise the
2060
+ * browser half has no way to keep.
2061
+ */
2062
+ rotatedToAccountId?(region: WorkBuddyRegion): Promise<string | undefined>;
2063
+ /**
2064
+ * Today's check-in state per account id.
2065
+ *
2066
+ * Read for the pool's table so the card can state it. Absent (or a missing
2067
+ * entry) means "not read", which the card renders as unknown rather than as a
2068
+ * definite "not checked in" — a claim that would contradict the check-in the
2069
+ * user just performed.
2070
+ */
2071
+ checkedInToday?(region: WorkBuddyRegion): Promise<Readonly<Record<string, boolean>>>;
2072
+ /**
2073
+ * Every local sign-in the pool does NOT cover, so the card can offer them to
2074
+ * check in. Absent means the card lists only pool members.
2075
+ */
2076
+ otherAccounts?(region: WorkBuddyRegion): Promise<readonly {
2077
+ id: string;
2078
+ accountName: string;
2079
+ }[]>;
2080
+ /** The account currently billing traffic for the region, when known. */
2081
+ currentAccountId?(region: WorkBuddyRegion): Promise<string | undefined>;
2082
+ /**
2083
+ * Claim the daily reward for every account of one region.
2084
+ *
2085
+ * Absent means the action answers 503 — the same contract as {@link
2086
+ * WorkBuddyStatusRouteOptions.probeModels}, so a Host without the dependency
2087
+ * reports "unavailable" instead of appearing to succeed.
2088
+ */
2089
+ checkin?(region: WorkBuddyRegion): Promise<readonly WorkBuddyPoolCheckinRow[]>;
2090
+ /** Test every account of one region against the resolved target model. */
2091
+ test?(region: WorkBuddyRegion, modelId: string): Promise<readonly WorkBuddyPoolTestRow[]>;
2092
+ /** The region's live catalog, used to resolve the free target model. */
2093
+ catalog?(region: WorkBuddyRegion): readonly WorkBuddyModelInfo[];
1770
2094
  }
1771
2095
  /**
1772
2096
  * Assemble one region's card document. `region` is the tab the card is on;
@@ -1831,6 +2155,40 @@ interface WorkBuddyRegionState {
1831
2155
  imageModelIds?: string[];
1832
2156
  /** Local DSH context budget per model in this region. */
1833
2157
  contextBudgets?: Record<string, WorkBuddyContextBudget>;
2158
+ /** This region's account-pool preferences (opt-in; see {@link WorkBuddyPoolPreferences}). */
2159
+ pool?: WorkBuddyPoolPreferences;
2160
+ }
2161
+ /**
2162
+ * One region's account-pool preferences, as stored.
2163
+ *
2164
+ * Every field is a USER CHOICE, which is why the pool's measured facts are not
2165
+ * here: preferences are saved through the card's draft-and-discard path, and a
2166
+ * measurement living in the same slot would be rolled back by the next save.
2167
+ */
2168
+ interface WorkBuddyPoolPreferences {
2169
+ /** Whether the pool is active for this region. Opt-in: default false. */
2170
+ enabled?: boolean;
2171
+ /**
2172
+ * Whether the plugin may choose the billed account by credits.
2173
+ *
2174
+ * Opt-in, and mutually exclusive with manual selection: while it is on, the
2175
+ * card disables the account dropdown, because two writers deciding the same
2176
+ * slot is exactly how "the dropdown says A but B is billed" happens.
2177
+ */
2178
+ rotateByCredits?: boolean;
2179
+ /** Minutes between automatic tests; clamped to 5..1440. */
2180
+ autoTestIntervalMinutes?: number;
2181
+ /** Model id to test; `''` means auto-pick a free model from this region. */
2182
+ targetModelId?: string;
2183
+ /**
2184
+ * The account ids checked into this region's pool.
2185
+ *
2186
+ * An EXPLICIT opt-in, and empty by default: the batch actions claim rewards
2187
+ * and spend credits on real accounts, so membership is the user's decision
2188
+ * rather than a consequence of which sign-ins happen to exist. Empty means
2189
+ * the pool covers nothing and both buttons stay disabled.
2190
+ */
2191
+ memberAccountIds?: string[];
1834
2192
  }
1835
2193
  /** Plugin configuration. */
1836
2194
  interface Config {
@@ -1976,4 +2334,4 @@ declare function legacyAttributionRegion(value: Config, accountsFor: (region: Wo
1976
2334
  */
1977
2335
  declare function apply(ctx: Context, config: Config): void;
1978
2336
  //#endregion
1979
- export { CREDENTIAL_REJECTED_CODE, Config, ENCRYPTED_CREDENTIAL_CODE, FALLBACK_WORKBUDDY_MODELS, FALLBACK_WORKBUDDY_MODELS_GLOBAL, NATIVE_MODALITY_BY_MODEL_ID, PROBE_MAX_TOKENS, PROBE_SYSTEM_PROMPT, PROBE_USER_PROMPT, type UpstreamErrorKind, 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_PROBE_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, type WorkBuddyAccountChoice, type WorkBuddyAdapter, type WorkBuddyAuthStatus, type WorkBuddyCandidateFailure, WorkBuddyCatalog, type WorkBuddyChatResult, type WorkBuddyCredential, type WorkBuddyCredentialRecovery, WorkBuddyCredentialRejectedError, WorkBuddyCredentialStore, type WorkBuddyCreditPackage, type WorkBuddyCredits, WorkBuddyEncryptedCredentialError, type WorkBuddyEncryptedField, type WorkBuddyHostHeartbeat, type WorkBuddyModelInfo, type WorkBuddyNativeModality, type WorkBuddyProbeClient, type WorkBuddyProbeOutcome, type WorkBuddyProbeResult, type WorkBuddyProbeStore, type WorkBuddyReasoning, type WorkBuddyRecoveryCandidate, type WorkBuddyRefreshOutcome, WorkBuddyRegionState, type WorkBuddyShim, type WorkBuddyStatusRouteOptions, type WorkBuddyStoreOptions, WorkBuddyUpstreamClient, type WorkBuddyUpstreamModel, type WorkBuddyUsabilityProbeOptions, apply, asVolatile, authFileName, classifyUpstreamError, clearAtRestKeyCache, clearHostHeartbeat, cooldownOf, createAccountUsabilityProbe, createWorkBuddyAdapter, createWorkBuddyShim, creditOfStream, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, deriveAtRestKey, deriveAtRestKeyId, deriveCatalog, fallbackModelsFor, fetchAtRestKeyPayload, findWorkbuddyAppExecutable, hasEncryptedCredentialFields, imageDefaultFor, inject, isCredentialRejectedError, isEncryptedCredentialError, isEncryptedFieldWrapper, isHeartbeatProcessAlive, isWorkbuddyBundle, legacyAttributionRegion, legacyWorkbuddyOwnAuthPath, macosBundleExecutable, macosNestedAppBundles, name, nativeModalityOf, nextRegionEnabled, nextRegionSlots, openEncryptedField, outcomeOfFailure, parseCreditMultiplier, parseReasoning, parseRetryAfter, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, probeModel, probeRequestBody, probeSucceeded, processStartTimeMs, readAtRestKey, readHostHeartbeat, regionCleared, regionEnabled, regionEnabledOf, regionOf, regionOfProvider, regionOfStatusUrl, regionStateOf, registerWorkBuddyStatusRoute, resolveCredentialRecovery, selectAccountFor, settingsNamespaceOf, toPersistedWorkBuddyModel, unwrapVolatileDeep, withWorkBuddyRegion, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyAppExecutableCandidates, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };
2337
+ export { CREDENTIAL_REJECTED_CODE, Config, ENCRYPTED_CREDENTIAL_CODE, FALLBACK_WORKBUDDY_MODELS, FALLBACK_WORKBUDDY_MODELS_GLOBAL, NATIVE_MODALITY_BY_MODEL_ID, PROBE_INPUT_TOKENS, PROBE_MAX_TOKENS, PROBE_SYSTEM_PROMPT, type UpstreamErrorKind, 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_PROBE_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, type WorkBuddyAccountChoice, type WorkBuddyAdapter, type WorkBuddyAuthStatus, type WorkBuddyCandidateFailure, WorkBuddyCatalog, type WorkBuddyChatResult, type WorkBuddyCredential, type WorkBuddyCredentialRecovery, WorkBuddyCredentialRejectedError, WorkBuddyCredentialStore, type WorkBuddyCreditPackage, type WorkBuddyCredits, WorkBuddyEncryptedCredentialError, type WorkBuddyEncryptedField, type WorkBuddyHostHeartbeat, type WorkBuddyModelInfo, type WorkBuddyNativeModality, WorkBuddyPoolPreferences, type WorkBuddyProbeClient, type WorkBuddyProbeOutcome, type WorkBuddyProbeResult, type WorkBuddyProbeStore, type WorkBuddyReasoning, type WorkBuddyRecoveryCandidate, type WorkBuddyRefreshOutcome, WorkBuddyRegionState, type WorkBuddyShim, type WorkBuddyStatusRouteOptions, type WorkBuddyStoreOptions, WorkBuddyUpstreamClient, type WorkBuddyUpstreamModel, type WorkBuddyUsabilityProbeOptions, apply, asVolatile, authFileName, classifyUpstreamError, clearAtRestKeyCache, clearHostHeartbeat, cooldownOf, createAccountUsabilityProbe, createWorkBuddyAdapter, createWorkBuddyShim, creditOfStream, defaultDesktopAuthCandidates, defaultDesktopAuthDirs, defaultDesktopAuthPath, deriveAtRestKey, deriveAtRestKeyId, deriveCatalog, fallbackModelsFor, fetchAtRestKeyPayload, findWorkbuddyAppExecutable, hasEncryptedCredentialFields, imageDefaultFor, inject, isCredentialRejectedError, isEncryptedCredentialError, isEncryptedFieldWrapper, isHeartbeatProcessAlive, isWorkbuddyBundle, legacyAttributionRegion, legacyWorkbuddyOwnAuthPath, macosBundleExecutable, macosNestedAppBundles, name, nativeModalityOf, nextRegionEnabled, nextRegionSlots, openEncryptedField, outcomeOfFailure, parseCreditMultiplier, parseReasoning, parseRetryAfter, parseUpstreamModel, parseWorkBuddyAuth, prepareChatBody, probeModel, probeRequestBody, probeSucceeded, processStartTimeMs, readAtRestKey, readHostHeartbeat, regionCleared, regionEnabled, regionEnabledOf, regionOf, regionOfProvider, regionOfStatusUrl, regionStateOf, registerWorkBuddyStatusRoute, resolveCredentialRecovery, selectAccountFor, settingsNamespaceOf, toPersistedWorkBuddyModel, unwrapVolatileDeep, withWorkBuddyRegion, workBuddyDisplayName, workBuddyModelInput, workBuddyThinkingLevelMap, workBuddyWebStatus, workbuddyAccountId, workbuddyAppExecutableCandidates, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, writeHostHeartbeat };