dsh-workbuddy-xdpool 1.7.1 → 1.7.3

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.d.ts CHANGED
@@ -288,6 +288,13 @@ interface UpstreamClientOptions {
288
288
  fetchImpl?: typeof fetch;
289
289
  /** Client version string sent to the upstream. */
290
290
  clientVersion?: string;
291
+ /**
292
+ * Override the catalog-fetch backoff, in milliseconds.
293
+ *
294
+ * Tests set this to `[]` (or tiny values) so a retry case does not spend real
295
+ * seconds sleeping. Production uses {@link CATALOG_RETRY_BACKOFF_MS}.
296
+ */
297
+ catalogRetryBackoffMs?: readonly number[];
291
298
  }
292
299
  /** Region for a login domain; an empty domain means CN (matching upstream tooling). */
293
300
  /** The two gateways WorkBuddy serves: the domestic one and the international one. */
@@ -421,6 +428,19 @@ export declare function buddyAppEvents(buddyId: string, buddyName: string): Reco
421
428
  export declare class WorkBuddyUpstreamClient {
422
429
  private readonly fetchImpl;
423
430
  private readonly clientVersion;
431
+ /** Backoff ladder for `fetchModels`; empty means a single attempt. */
432
+ private readonly catalogRetryBackoffMs;
433
+ /**
434
+ * Optional logger for retry notices.
435
+ *
436
+ * Set by the host so a retry is visible in the log with its attempt count —
437
+ * without it, a retry that eventually succeeds is invisible, and an operator
438
+ * debugging "why was the catalog slow" has nothing to look at.
439
+ */
440
+ logger: {
441
+ warn?(...args: unknown[]): void;
442
+ info?(...args: unknown[]): void;
443
+ } | undefined;
424
444
  constructor(options?: UpstreamClientOptions);
425
445
  /**
426
446
  * Normalize an OpenAI chat-completions body for the WorkBuddy upstream:
@@ -467,6 +487,16 @@ export declare class WorkBuddyUpstreamClient {
467
487
  * below is common to the two branches.
468
488
  */
469
489
  fetchModels(credential: WorkBuddyCredential, signal?: AbortSignal): Promise<readonly WorkBuddyUpstreamModel[]>;
490
+ /**
491
+ * One catalog attempt, without retries.
492
+ *
493
+ * Split out so {@link fetchModels} can retry it: the failure this guards
494
+ * against is a startup network hiccup, where several independent components
495
+ * see `fetch failed` inside the same second and the very next request
496
+ * succeeds — exactly the case a single attempt turns into "the user's model
497
+ * list is missing half its entries for the rest of the session".
498
+ */
499
+ private fetchModelsOnce;
470
500
  /** Read-only credits query, aggregated by package. Does not consume credits. */
471
501
  fetchCredits(credential: WorkBuddyCredential): Promise<WorkBuddyCredits>;
472
502
  /** Query today's check-in status without changing account state. */
@@ -1063,92 +1093,520 @@ export declare class WorkBuddyAccountPool {
1063
1093
  };
1064
1094
  }
1065
1095
  //#endregion
1066
- //#region src/catalog.d.ts
1067
- /** One model the provider exposes. */
1068
- interface WorkBuddyModelInfo {
1096
+ //#region src/status-paths.d.ts
1097
+ /**
1098
+ * Node-free constants and types shared by the Host and browser halves of the
1099
+ * WorkBuddy XD Pool settings card.
1100
+ *
1101
+ * Pool's runtime state already lives in `src/status.ts` (`buildStatus` /
1102
+ * `WorkBuddyStatus`); this module only carves the cross-domain (Host→browser)
1103
+ * JSON document into a shape that stays token-free and matches what the
1104
+ * browser card renders. Route paths are plugin-owned and mounted on the Host's
1105
+ * same-origin web server (see `src/web-status.ts`).
1106
+ *
1107
+ * @module dsh-workbuddy-xdpool/status-paths
1108
+ */
1109
+ /** Plugin-owned read-only pool status endpoint (account rows + models + shim). */
1110
+ export declare const POOL_STATUS_PATH = "/plugins/dsh-workbuddy-xdpool/status";
1111
+ /** Plugin-owned local account rescan endpoint (re-read desktop snapshots). */
1112
+ export declare const POOL_RESCAN_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/rescan";
1113
+ /** Plugin-owned cooldown reset endpoint (clear all 429 cooldowns). */
1114
+ export declare const POOL_RESET_COOLDOWN_PATH = "/plugins/dsh-workbuddy-xdpool/cooldowns/reset";
1115
+ /** Plugin-owned daily check-in action endpoint (claim today's reward). */
1116
+ export declare const POOL_CHECKIN_PATH = "/plugins/dsh-workbuddy-xdpool/checkin";
1117
+ /** Plugin-owned model-selection save endpoint (writes the settings section). */
1118
+ export declare const POOL_MODELS_SAVE_PATH = "/plugins/dsh-workbuddy-xdpool/models/save";
1119
+ /**
1120
+ * Throw one account out of the pool for good, or take it back.
1121
+ *
1122
+ * Separate from the disable route because the semantics differ: disabling is a
1123
+ * rotation preference the account survives, ignoring survives the account.
1124
+ */
1125
+ export declare const POOL_ACCOUNT_IGNORE_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/ignored";
1126
+ /** Run one automation job immediately, so the card can verify it on demand. */
1127
+ export declare const POOL_AUTOMATION_RUN_PATH = "/plugins/dsh-workbuddy-xdpool/automation/run";
1128
+ /** Set or clear one account's reserved-credit floor. */
1129
+ export declare const POOL_CREDIT_RESERVE_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/credit-reserve";
1130
+ /** One account's row, token-free. */
1131
+ interface PoolWebAccount {
1132
+ id: string;
1133
+ label: string;
1134
+ nickname?: string;
1135
+ domain: string;
1136
+ /** ISO timestamp; absent when the credential carries no expiry. */
1137
+ expiresAt?: string;
1138
+ /** Account-wide cooldown (every model blocked); only after a no-model penalize. */
1139
+ cooling: boolean;
1140
+ /** ISO timestamp when the account-wide 429 cooldown lifts; only while cooling. */
1141
+ cooldownUntil?: string;
1142
+ /**
1143
+ * Per-model cooldowns currently active. The account is NOT `cooling` while a
1144
+ * model is limited — its other models still serve — but each entry tells the
1145
+ * card which model is out until when (e.g. `hy4-preview` cooling to 10:14,
1146
+ * `hy3` normal).
1147
+ */
1148
+ modelCooldowns?: ReadonlyArray<{
1149
+ modelId: string;
1150
+ until: string;
1151
+ }>;
1152
+ /**
1153
+ * Whether the user switched this account off. A disabled account never
1154
+ * serves a request, but it stays listed so the card can switch it back on.
1155
+ */
1156
+ disabled: boolean;
1157
+ rateLimitHits: number;
1158
+ /**
1159
+ * Credits the user asked to keep for this account. The pool stops picking the
1160
+ * account once its balance reaches the reserve, so this many credits survive.
1161
+ * 0 means the account may be spent down as before.
1162
+ */
1163
+ creditReserve: number;
1164
+ /**
1165
+ * Whether the account is held back purely by its reserve right now. Kept
1166
+ * distinct from `cooling`: a reserved account is healthy and simply
1167
+ * protected, which is a different thing to tell the user than rate-limited.
1168
+ */
1169
+ reserved: boolean;
1170
+ /**
1171
+ * What the automation earned for this account today. Absent when it earned
1172
+ * nothing (or the automation never ran for it), so the card can stay quiet
1173
+ * instead of printing a row of zeroes.
1174
+ */
1175
+ automationToday?: PoolWebAutomationEarnings;
1176
+ /** ISO timestamp of the last successful use (best-effort pool bookkeeping). */
1177
+ lastUsedAt?: string;
1178
+ /** Aggregated credit summary for the account, read-only. */
1179
+ credits?: PoolWebCredits;
1180
+ creditsError?: string;
1181
+ /**
1182
+ * Today's check-in state for this account, read-only. Present only when the
1183
+ * per-account check-in probe succeeded and the program is active. The card
1184
+ * renders one claim button per account, so a multi-account pool can collect
1185
+ * every account's daily reward without switching accounts by hand.
1186
+ */
1187
+ checkin?: PoolWebCheckin;
1188
+ checkinError?: string;
1189
+ }
1190
+ /** One credit package (as surfaced by the pool's upstream client), node-free. */
1191
+ interface PoolWebCreditPackage {
1192
+ packageName: string;
1193
+ remain?: number;
1194
+ size?: number;
1195
+ /** CapacityType 4 — refreshed each cycle and never expires. */
1196
+ monthly?: boolean;
1197
+ /** Next cycle refresh point, ms. */
1198
+ cycleRefreshMs?: number;
1199
+ /** One-off expiry, ms. */
1200
+ expiresAtMs?: number;
1201
+ }
1202
+ /** Aggregated credit answer the card renders under one account. */
1203
+ interface PoolWebCredits {
1204
+ total?: number;
1205
+ packages: readonly PoolWebCreditPackage[];
1206
+ /** Credits expiring within 3 days. */
1207
+ expiringSoon?: number;
1208
+ /** When the nearest package expires, ms. */
1209
+ nearestExpiryMs?: number;
1210
+ }
1211
+ /**
1212
+ * Daily check-in state the card renders under one account's credits. Mirrors
1213
+ * the upstream activity endpoint, minus anything the browser does not need.
1214
+ */
1215
+ interface PoolWebCheckin {
1216
+ /** The activity is running; a claim button is offered only while true. */
1217
+ active: boolean;
1218
+ /** Already collected today — the button renders as a done state. */
1219
+ todayCheckedIn: boolean;
1220
+ /** Consecutive days checked in. */
1221
+ streakDays: number;
1222
+ /** Credits a single day grants. */
1223
+ dailyCredit: number;
1224
+ /** Credits collected today (0 before claiming). */
1225
+ todayCredit: number;
1226
+ /** Today is a streak milestone day. */
1227
+ isStreakDay: boolean;
1228
+ /** The day count the next milestone lands on. */
1229
+ nextStreakDay: number;
1230
+ /** Bonus credits granted on a milestone day. */
1231
+ streakBonusCredit: number;
1232
+ }
1233
+ /** Result of one claim, so the card can confirm what was collected. */
1234
+ interface PoolWebCheckinClaim {
1235
+ credit: number;
1236
+ streakDays: number;
1237
+ isStreakDay: boolean;
1238
+ }
1239
+ /** One model the pool exposes to DSH, with cost / free tags. */
1240
+ interface PoolWebModel {
1069
1241
  id: string;
1070
- /** Display name; the multiplier is appended for the picker. */
1071
1242
  name: string;
1072
- contextWindow: number;
1073
- maxOutputTokens: number;
1074
- /** Relative credit cost, e.g. 0.79 for `x0.79`. */
1243
+ /** Relative credit cost, e.g. 0.79 for x0.79. */
1075
1244
  multiplier?: number;
1076
- /** Upstream-declared thinking levels. */
1077
- supportedEfforts?: readonly string[];
1078
- supportsImages: boolean;
1079
1245
  /** Upstream tags: free / limited-free / night-discount. */
1080
1246
  tags?: readonly string[];
1247
+ /** Effective image support after the user's per-model toggle. */
1248
+ supportsImages: boolean;
1249
+ /** Effective context window after the user's budget cap. */
1250
+ contextWindow: number;
1251
+ /** The window the upstream advertises, before any cap. */
1252
+ nativeContextWindow: number;
1253
+ /** Upstream output ceiling, so the card can show both limits. */
1254
+ maxOutputTokens: number;
1255
+ /** Thinking levels the upstream declares, when it declares any. */
1256
+ supportedEfforts?: readonly string[];
1257
+ /** Whether this model is currently enabled in the picker. */
1258
+ enabled: boolean;
1081
1259
  }
1082
1260
  /**
1083
- * Static fallback used before the first live catalog fetch, and whenever the
1084
- * upstream cannot be reached.
1261
+ * Body of the account ignore/unignore route: exactly one account per request.
1085
1262
  *
1086
- * The multipliers are carried on purpose. Without them the provider's model
1087
- * picker silently loses every rate and every free badge the moment the live
1088
- * fetch fails — which reads to the user as "the plugin broke my model list"
1089
- * rather than "the upstream is unreachable". The values are the ones the two
1090
- * gateways actually advertise for these ids (`credits: "x0.79 credits"` and so
1091
- * on), so a fallback row looks the same as a live one.
1263
+ * `ignored: true` throws the account out of the pool for good (its credential is
1264
+ * not even read on the next scan, and a fresh desktop sign-in will not bring it
1265
+ * back). `false` restores it, at which point the next scan discovers it again.
1266
+ */
1267
+ interface PoolWebAccountIgnore {
1268
+ /** Pool account id, as reported in `PoolWebAccount.id`. */
1269
+ accountId: string;
1270
+ /** `true` ignores the account permanently; `false` takes it back. */
1271
+ ignored: boolean;
1272
+ }
1273
+ /**
1274
+ * One account the user has thrown out of the pool.
1092
1275
  *
1093
- * `multiplier: 0` is the gateways' own spelling of "free" (`credits: "x0.00"`),
1094
- * which is what turns on the free badge.
1276
+ * Kept on the status document so the card can list what was ignored and offer a
1277
+ * way back: without that, "ignored" is a one-way door the user cannot inspect or
1278
+ * undo from the UI, which is how a hidden list becomes a support burden.
1095
1279
  */
1096
- export declare const FALLBACK_WORKBUDDY_MODELS: readonly WorkBuddyModelInfo[];
1097
- /** Live catalog with a static fallback behind it. */
1098
- export declare class WorkBuddyCatalog {
1099
- private models;
1100
- private listeners;
1101
- /** User's model selection. Empty object = follow the catalog unfiltered. */
1102
- private selection;
1103
- current(): readonly WorkBuddyModelInfo[];
1104
- /**
1105
- * The models DSH should actually offer, after applying the user's selection:
1106
- * disabled models are dropped, an explicit image list overrides the upstream
1107
- * capability flag, and a per-model budget caps the advertised window.
1108
- *
1109
- * An absent `enabledModelIds` means "everything" — a fresh install with no
1110
- * saved selection must not present an empty picker.
1111
- */
1112
- visible(): readonly WorkBuddyModelInfo[];
1113
- /** Replace the catalog and notify the adapter to rebuild its model list. */
1114
- update(models: readonly WorkBuddyModelInfo[]): void;
1115
- /** Restore the static fallback, e.g. when the upstream stops answering. */
1116
- reset(): void;
1117
- /** Replace the user's selection; the adapter rebuilds from `visible()`. */
1118
- applySelection(selection: ModelSelection): void;
1119
- /** The selection currently in force, for the card's save round-trip. */
1120
- currentSelection(): ModelSelection;
1121
- onChange(listener: () => void): () => void;
1122
- find(id: string): WorkBuddyModelInfo | undefined;
1123
- /** Replace the catalog from the live upstream list; keeps the fallback if empty. */
1124
- updateFromUpstream(models: readonly WorkBuddyUpstreamModel[]): void;
1125
- private notify;
1280
+ interface PoolWebIgnoredAccount {
1281
+ /** Pool account id, the same key `PoolWebAccount.id` uses. */
1282
+ id: string;
1283
+ /** Human label captured at ignore time, so the row reads without a rescan. */
1284
+ label: string;
1285
+ /** ISO timestamp of when it was ignored. */
1286
+ ignoredAt: string;
1126
1287
  }
1127
- /** The user's model selection, as stored in the settings section. */
1128
- interface ModelSelection {
1129
- /** Absent = every model in the catalog is offered. */
1288
+ interface PoolWebModelSelection {
1289
+ /** Absent = every model is enabled. */
1130
1290
  enabledModelIds?: readonly string[];
1131
1291
  /** Absent = each model follows its upstream image capability. */
1132
1292
  imageModelIds?: readonly string[];
1133
1293
  /** Per-model context-window cap, keyed by model id. */
1134
1294
  contextBudgets?: Readonly<Record<string, number | undefined>>;
1135
1295
  }
1136
- //#endregion
1137
- //#region src/shim.d.ts
1138
- interface ShimLogger {
1139
- info?(...args: unknown[]): void;
1140
- warn(...args: unknown[]): void;
1141
- error(...args: unknown[]): void;
1142
- }
1143
- interface WorkBuddyShim {
1144
- ready: Promise<void>;
1145
- baseUrl(): string;
1146
- token(): string;
1147
- close(): Promise<void>;
1148
- }
1149
- interface WorkBuddyShimOptions {
1150
- pool: WorkBuddyAccountPool;
1151
- client: WorkBuddyUpstreamClient;
1296
+ /** The JSON document the pool card renders. */
1297
+ interface PoolWebStatus {
1298
+ ok: boolean;
1299
+ accounts: readonly PoolWebAccount[];
1300
+ /** The next account the pool would use (rotation cursor). */
1301
+ activeAccountId?: string;
1302
+ cooling: number;
1303
+ models: readonly PoolWebModel[];
1304
+ /** The saved selection the card diffs its draft against. */
1305
+ selection: PoolWebModelSelection;
1306
+ /**
1307
+ * How the pool spreads requests: `priority` drains one account before
1308
+ * moving on, `round-robin` splits the spend evenly.
1309
+ */
1310
+ distribution: PoolDistribution;
1311
+ /** Which region this document describes. */
1312
+ region: PoolRegion;
1313
+ /** Every region holding at least one account, in display order. */
1314
+ regions: readonly PoolRegion[];
1315
+ shim: {
1316
+ running: boolean;
1317
+ baseUrl?: string;
1318
+ };
1319
+ /** Daily-points automation state, so the card can show what ran and when. */
1320
+ automation: PoolWebAutomation;
1321
+ /** Per-account credit floors currently in force, keyed by account id. */
1322
+ creditReserves: Readonly<Record<string, number>>;
1323
+ /**
1324
+ * Accounts thrown out of the pool, in the order they were ignored.
1325
+ *
1326
+ * Reported so the card can show the list and offer a way back. These accounts
1327
+ * are NOT in `accounts`: they are filtered out before their credentials are
1328
+ * read, which is the whole point of the feature.
1329
+ */
1330
+ ignored: readonly PoolWebIgnoredAccount[];
1331
+ /**
1332
+ * Where THIS region's model list came from.
1333
+ *
1334
+ * `live` = fetched from the gateway (or a cached fetch survived a restart).
1335
+ * `fallback` = the built-in static table, which means the startup fetch
1336
+ * failed and the user is looking at a SHORTER, possibly stale roster — models
1337
+ * they had enabled can be missing from it entirely.
1338
+ *
1339
+ * Surfaced because the failure used to be log-only: the picker quietly lost
1340
+ * half its entries and nothing on screen said why, so it read as "the plugin
1341
+ * deleted my models".
1342
+ */
1343
+ catalogSource?: PoolWebCatalogSource;
1344
+ /** When the live catalog was last successfully fetched, ISO. */
1345
+ catalogUpdatedAt?: string;
1346
+ /** Why the last fetch failed, when it did. Redacted and length-capped. */
1347
+ catalogError?: string;
1348
+ }
1349
+ /** How a region's model list was obtained. */
1350
+ type PoolWebCatalogSource = 'live' | 'fallback';
1351
+ /** One automation job's last run, as shown on the card. */
1352
+ interface PoolWebAutomationJob {
1353
+ /** `YYYY-MM-DD` of the last run in this process, if it has run. */
1354
+ lastRunDate?: string;
1355
+ /**
1356
+ * Epoch ms of the last run, so the card can show the TIME.
1357
+ *
1358
+ * Carried because a date-only stamp cannot tell one run from eight: every
1359
+ * repeat inside the same day rendered as the identical `2026-09-28 · 2`,
1360
+ * which is what kept a "re-runs every hour" defect invisible on the card.
1361
+ */
1362
+ lastRunAtMs?: number;
1363
+ /**
1364
+ * Configured slots consumed today, as `YYYY-MM-DDTHH`.
1365
+ *
1366
+ * Shown so "which of today's hours already ran" is answerable at a glance
1367
+ * rather than inferred from a counter.
1368
+ */
1369
+ firedSlots?: readonly string[];
1370
+ /** Accounts that finished without error on the last run. */
1371
+ ok: number;
1372
+ /** Accounts that failed on the last run (each one skipped, the run continued). */
1373
+ failed: number;
1374
+ /** Credits claimed by the task job on the last run. */
1375
+ credit: number;
1376
+ /** Energy claimed by the task job on the last run. */
1377
+ energy: number;
1378
+ /** Tasks claimed by the task job on the last run. */
1379
+ claimed: number;
1380
+ /** One-line summary of the last run. */
1381
+ message?: string;
1382
+ /**
1383
+ * What the last run actually did, in the words of the task board.
1384
+ *
1385
+ * `message` is a count; this is the list a person can check off, which is
1386
+ * what turns a row from "it ran" into "it did the things I care about".
1387
+ */
1388
+ detail?: readonly string[];
1389
+ /** A pending milestone worth naming, e.g. the next streak tier countdown. */
1390
+ progress?: string;
1391
+ }
1392
+ /**
1393
+ * Automation block on the status document.
1394
+ *
1395
+ * Carries the schedule and each job's last outcome so the card can answer
1396
+ * "is it on, when does it run, and what did it last do" without reaching into
1397
+ * the scheduler itself.
1398
+ */
1399
+ interface PoolWebAutomation {
1400
+ /** Master switch, mirrored from the saved config. */
1401
+ enabled: boolean;
1402
+ /** Whether the loop is currently running. */
1403
+ running: boolean;
1404
+ /** Configured hours per job, so the card can show the schedule. */
1405
+ checkinHours: readonly number[];
1406
+ reportHours: readonly number[];
1407
+ taskHours: readonly number[];
1408
+ streakHours: readonly number[];
1409
+ travelHours: readonly number[];
1410
+ jobs: {
1411
+ checkin: PoolWebAutomationJob;
1412
+ report: PoolWebAutomationJob;
1413
+ tasks: PoolWebAutomationJob;
1414
+ streak: PoolWebAutomationJob;
1415
+ travel: PoolWebAutomationJob;
1416
+ };
1417
+ /** Claimable tasks seen on the most recent task pass, across accounts. */
1418
+ claimableSeen: number;
1419
+ /**
1420
+ * Whether a manual run is in flight. The card polls this to know when to
1421
+ * stop showing progress and report the result.
1422
+ */
1423
+ runInProgress: boolean;
1424
+ /**
1425
+ * Per-account credits/energy/tasks the automation earned TODAY, keyed by
1426
+ * account id. An account that earned nothing is simply absent, so the card
1427
+ * can say "nothing yet" instead of showing a bare zero.
1428
+ */
1429
+ earningsToday: Readonly<Record<string, PoolWebAutomationEarnings>>;
1430
+ }
1431
+ /** Today's automation take for one account. */
1432
+ interface PoolWebAutomationEarnings {
1433
+ /** Credits claimed from the task centre today. */
1434
+ credit: number;
1435
+ /** Energy claimed from the task centre today. */
1436
+ energy: number;
1437
+ /** Tasks claimed today. */
1438
+ claimed: number;
1439
+ /** Credits collected from check-in today. */
1440
+ checkinCredit: number;
1441
+ /** Credits from streak redemption and the lottery today. */
1442
+ bonusCredit: number;
1443
+ /** Credits from buddy adoption and the travel loop today. */
1444
+ travelCredit: number;
1445
+ /** Local date the counters belong to (YYYY-MM-DD). */
1446
+ date: string;
1447
+ }
1448
+ /**
1449
+ * The two gateways, matching the provider ids the host registers. `cn` is the
1450
+ * domestic gateway (`copilot.tencent.com` / `codebuddy.cn`); `global` is the
1451
+ * international one (`workbuddy.ai`).
1452
+ */
1453
+ type PoolRegion = 'cn' | 'global';
1454
+ /** How the pool spreads requests across its accounts. */
1455
+ type PoolDistribution = 'priority' | 'round-robin' | 'balanced';
1456
+ /**
1457
+ * The schedule every automation job falls back to.
1458
+ *
1459
+ * Shared by both halves on purpose. The host uses it when a configured hour
1460
+ * list arrives empty (the settings schema materializes "never configured" into
1461
+ * `[]`), and the card uses it when it writes the `automation` block back, so a
1462
+ * document that already holds an empty list is healed instead of being saved
1463
+ * back as an unrunnable schedule.
1464
+ *
1465
+ * This lives here rather than in `scheduler.ts` because the browser half cannot
1466
+ * import the host module: `scheduler.ts` pulls in `node:crypto` and the whole
1467
+ * upstream client, none of which exists in the browser bundle. Two hand-written
1468
+ * copies would drift, and the drift is invisible — the card would write a
1469
+ * schedule the scheduler does not run.
1470
+ */
1471
+ export declare const DEFAULT_AUTOMATION_HOURS: {
1472
+ readonly checkin: readonly [9];
1473
+ readonly report: readonly [10];
1474
+ readonly tasks: readonly [11];
1475
+ readonly streak: readonly [12];
1476
+ readonly travel: readonly [9, 21];
1477
+ };
1478
+ //#endregion
1479
+ //#region src/catalog.d.ts
1480
+ /** One model the provider exposes. */
1481
+ interface WorkBuddyModelInfo {
1482
+ id: string;
1483
+ /** Display name; the multiplier is appended for the picker. */
1484
+ name: string;
1485
+ contextWindow: number;
1486
+ maxOutputTokens: number;
1487
+ /** Relative credit cost, e.g. 0.79 for `x0.79`. */
1488
+ multiplier?: number;
1489
+ /** Upstream-declared thinking levels. */
1490
+ supportedEfforts?: readonly string[];
1491
+ supportsImages: boolean;
1492
+ /** Upstream tags: free / limited-free / night-discount. */
1493
+ tags?: readonly string[];
1494
+ }
1495
+ /**
1496
+ * Static fallback used before the first live catalog fetch, and whenever the
1497
+ * upstream cannot be reached.
1498
+ *
1499
+ * This table is the DOMESTIC (CN) roster, and it is kept in step with what
1500
+ * `copilot.tencent.com` actually advertises. It matters more than a "just in
1501
+ * case" list usually would: a failed startup fetch falls back to it, and the
1502
+ * user sees that as "half my models were deleted" — `deepseek-v4.1-flash`
1503
+ * and friends simply vanishing from the picker, with no visible explanation.
1504
+ *
1505
+ * Every row below was captured from the live endpoint, INCLUDING the window
1506
+ * sizes: the previous version carried a 32K window for `hy3` when the gateway
1507
+ * says 192K, and listed `kimi-k3` under an id the gateway no longer uses
1508
+ * (`kimi-k3-1`). A stale fallback is worse than a short one — it looks
1509
+ * authoritative while being wrong.
1510
+ *
1511
+ * `multiplier: 0` is the gateways' own spelling of "free" (`credits: "x0.00"`),
1512
+ * which is what turns on the free badge.
1513
+ */
1514
+ export declare const FALLBACK_WORKBUDDY_MODELS: readonly WorkBuddyModelInfo[];
1515
+ /** Live catalog with a static fallback behind it. */
1516
+ export declare class WorkBuddyCatalog {
1517
+ private models;
1518
+ private listeners;
1519
+ /** User's model selection. Empty object = follow the catalog unfiltered. */
1520
+ private selection;
1521
+ /**
1522
+ * Whether `models` came from the gateway or from the static table.
1523
+ *
1524
+ * Tracked so the card can SAY which it is showing. A failed fetch used to be
1525
+ * invisible on screen: the picker simply held fewer models than before, which
1526
+ * the user reasonably read as "the plugin deleted my models" rather than "the
1527
+ * network hiccuped at startup".
1528
+ */
1529
+ private source;
1530
+ /** When the live list last landed. */
1531
+ private lastUpdatedAt;
1532
+ /** Why the last fetch failed, for display. */
1533
+ private lastError;
1534
+ current(): readonly WorkBuddyModelInfo[];
1535
+ /**
1536
+ * The models DSH should actually offer, after applying the user's selection:
1537
+ * disabled models are dropped, an explicit image list overrides the upstream
1538
+ * capability flag, and a per-model budget caps the advertised window.
1539
+ *
1540
+ * An absent `enabledModelIds` means "everything" — a fresh install with no
1541
+ * saved selection must not present an empty picker.
1542
+ */
1543
+ visible(): readonly WorkBuddyModelInfo[];
1544
+ /** Replace the catalog and notify the adapter to rebuild its model list. */
1545
+ update(models: readonly WorkBuddyModelInfo[]): void;
1546
+ /** Restore the static fallback, e.g. when the upstream stops answering. */
1547
+ reset(): void;
1548
+ /** Replace the user's selection; the adapter rebuilds from `visible()`. */
1549
+ applySelection(selection: ModelSelection): void;
1550
+ /** The selection currently in force, for the card's save round-trip. */
1551
+ currentSelection(): ModelSelection;
1552
+ /**
1553
+ * Whether this catalog is serving live data or the built-in table.
1554
+ *
1555
+ * `fallback` is not an error state, but it IS a degraded one: the user is
1556
+ * looking at a shorter roster than the gateway offers, so the card says so
1557
+ * and offers a retry instead of letting them wonder where the models went.
1558
+ */
1559
+ currentSource(): PoolWebCatalogSource;
1560
+ /**
1561
+ * When the live list last landed, if it ever did.
1562
+ *
1563
+ * Named `catalogUpdatedAt()` rather than `updatedAt()` because the class
1564
+ * already had an `updatedAt` member; two members of the same name is a
1565
+ * compile error, and the awkwardness is a useful signal that the concept is
1566
+ * "when THIS catalog was refreshed", not a generic timestamp.
1567
+ */
1568
+ catalogUpdatedAt(): string | undefined;
1569
+ /** Why the last fetch failed, if it did. */
1570
+ lastFetchError(): string | undefined;
1571
+ onChange(listener: () => void): () => void;
1572
+ find(id: string): WorkBuddyModelInfo | undefined;
1573
+ /** Replace the catalog from the live upstream list; keeps the fallback if empty. */
1574
+ updateFromUpstream(models: readonly WorkBuddyUpstreamModel[]): void;
1575
+ /**
1576
+ * Record that a fetch attempt failed, leaving the current list in place.
1577
+ *
1578
+ * The list is deliberately NOT reset here: a refresh that fails should keep
1579
+ * whatever working catalog is already loaded, rather than demoting a healthy
1580
+ * session to the static table because one retry ran out.
1581
+ */
1582
+ noteFetchFailure(message: string): void;
1583
+ private notify;
1584
+ }
1585
+ /** The user's model selection, as stored in the settings section. */
1586
+ interface ModelSelection {
1587
+ /** Absent = every model in the catalog is offered. */
1588
+ enabledModelIds?: readonly string[];
1589
+ /** Absent = each model follows its upstream image capability. */
1590
+ imageModelIds?: readonly string[];
1591
+ /** Per-model context-window cap, keyed by model id. */
1592
+ contextBudgets?: Readonly<Record<string, number | undefined>>;
1593
+ }
1594
+ //#endregion
1595
+ //#region src/shim.d.ts
1596
+ interface ShimLogger {
1597
+ info?(...args: unknown[]): void;
1598
+ warn(...args: unknown[]): void;
1599
+ error(...args: unknown[]): void;
1600
+ }
1601
+ interface WorkBuddyShim {
1602
+ ready: Promise<void>;
1603
+ baseUrl(): string;
1604
+ token(): string;
1605
+ close(): Promise<void>;
1606
+ }
1607
+ interface WorkBuddyShimOptions {
1608
+ pool: WorkBuddyAccountPool;
1609
+ client: WorkBuddyUpstreamClient;
1152
1610
  catalog: WorkBuddyCatalog;
1153
1611
  logger?: ShimLogger;
1154
1612
  /**
@@ -1265,9 +1723,37 @@ interface AutomationJobState {
1265
1723
  * repeat tick inside the same hour is still refused.
1266
1724
  */
1267
1725
  lastRunSlot?: string;
1268
- /** Epoch ms of the last completed run. */
1269
- lastRunAtMs?: number;
1270
- /** Accounts that completed without throwing. */
1726
+ /**
1727
+ * Every configured slot consumed TODAY, as `YYYY-MM-DDTHH`.
1728
+ *
1729
+ * This is what actually gates a re-run, and it is a SET rather than a single
1730
+ * slot because one field could not express the contract: a job configured for
1731
+ * `[9, 21]` runs twice, and a single "last slot" value can only remember one
1732
+ * of them — the second run erased the first, so the 9 o'clock candidate looked
1733
+ * unconsumed again and the job re-fired every hour after 21:00.
1734
+ *
1735
+ * The values are the CONFIGURED hours that were spent, never the clock time
1736
+ * the run happened to finish at. Storing the finish time was the original
1737
+ * defect: the reader compared it against a configured candidate, so the two
1738
+ * almost never matched and the gate stayed open all day.
1739
+ *
1740
+ * In-memory like the rest of `states`: this is a per-process record, and the
1741
+ * catch-up design deliberately re-runs an hour that passed while DSH was
1742
+ * closed. See `automationEarnings` for the ledger that DOES persist.
1743
+ */
1744
+ firedSlots?: readonly string[];
1745
+ /**
1746
+ * The clock slot the last run STARTED in (`YYYY-MM-DDTHH`).
1747
+ *
1748
+ * Separate from {@link firedSlots} on purpose: this is a throttle ("do not
1749
+ * start twice inside the same hour"), while `firedSlots` is the schedule
1750
+ * ledger. Conflating the two is what let a catch-up run at 13:00 erase the
1751
+ * record of the 9 o'clock slot.
1752
+ */
1753
+ lastFiredHour?: string;
1754
+ /** Epoch ms of the last completed run. */
1755
+ lastRunAtMs?: number;
1756
+ /** Accounts that completed without throwing. */
1271
1757
  ok: number;
1272
1758
  /** Accounts that threw (each one skipped, the run continued). */
1273
1759
  failed: number;
@@ -1535,7 +2021,27 @@ export declare class WorkBuddyScheduler {
1535
2021
  * two hours still runs twice a day — but a job whose hour passed while DSH was
1536
2022
  * closed runs immediately on the next tick instead of waiting for tomorrow.
1537
2023
  */
1538
- private isDue;
2024
+ /**
2025
+ * The configured slot `kind` should consume at `now`, or undefined when none.
2026
+ *
2027
+ * Returns the SLOT STRING (not a boolean) because the caller must record the
2028
+ * same value it acted on. Returning a boolean was the original bug's enabler:
2029
+ * the tick asked "is it due", then `runJob` independently wrote "what time is
2030
+ * it now" — and those two answers were almost never equal.
2031
+ *
2032
+ * CATCH-UP semantics are deliberate: a candidate fires once its hour has
2033
+ * PASSED and its slot is still unconsumed, so a laptop that slept through
2034
+ * 10:00 still runs the job when it wakes, on the same day.
2035
+ *
2036
+ * The EARLIEST unconsumed candidate wins, which is what keeps a two-hour job
2037
+ * (`travelHours: [9, 21]`) whole: consuming the earliest due slot leaves the
2038
+ * later one for its own hour.
2039
+ */
2040
+ private dueSlot;
2041
+ /** Today's consumed slots for one job, as a set. */
2042
+ private firedSlotsOf;
2043
+ /** Record one consumed slot on a job's state. */
2044
+ private consumeSlot;
1539
2045
  private tick;
1540
2046
  /** Run one job against every eligible account and record the outcome. */
1541
2047
  /**
@@ -1568,6 +2074,12 @@ export declare class WorkBuddyScheduler {
1568
2074
  /**
1569
2075
  * Run one job against every eligible account and record the outcome.
1570
2076
  *
2077
+ * `consumedSlot` is the configured slot this run spends (`dueSlot`'s answer).
2078
+ * It is passed IN rather than recomputed here so the value recorded is exactly
2079
+ * the value the schedule decided on — the defect this replaces wrote
2080
+ * `slotKey(now)` instead, i.e. the clock time the run finished at, which
2081
+ * almost never equals the configured candidate the gate had compared against.
2082
+ *
1571
2083
  * The task job runs in TWO passes. The first sends the event chains that light
1572
2084
  * up client-scored tasks; the second collects rewards. They are separate
1573
2085
  * because scoring lands asynchronously — a chain sent and claimed within the
@@ -1582,497 +2094,148 @@ export declare class WorkBuddyScheduler {
1582
2094
  * Accounts to run against, in pool order.
1583
2095
  *
1584
2096
  * Disabled accounts are excluded here rather than filtered by the caller so a
1585
- * card switch takes effect on the next pass without any event plumbing.
1586
- */
1587
- private accountsInOrder;
1588
- /**
1589
- * Send one activity report, then verify it landed.
1590
- *
1591
- * The upstream answers 200 even when it drops the event, so the streak is
1592
- * read back as the oracle: `days > 0` means it counted. A failed read-back is
1593
- * logged and treated as a suspicious result, never as a retry — the report is
1594
- * idempotent per day, and hammering it is exactly what the one-a-day quota
1595
- * exists to avoid.
1596
- */
1597
- private reportOne;
1598
- private sendEventChains;
1599
- /**
1600
- * Send one chain on the channel it was built for.
1601
- *
1602
- * The transport is not a detail of the sender: the scorer keys different
1603
- * tasks to different fingerprint families, so a web-scored event posted as a
1604
- * desktop event is accepted and then ignored.
1605
- */
1606
- private sendChain;
1607
- /**
1608
- * Build every chain that scores one task.
1609
- *
1610
- * Most tasks need a single chain; `template_5` needs five, because the scorer
1611
- * counts distinct `template_used` events rather than a boolean. The two tasks
1612
- * that join a conversation (skill, expert) open a real one first, which is why
1613
- * this is async.
1614
- */
1615
- private chainsFor;
1616
- /**
1617
- * The summon-and-use chains for the expert tasks.
1618
- *
1619
- * Two steps per expert, and both are load-bearing: the summon events alone are
1620
- * impressions, and a use event on its own scores nothing because the scorer
1621
- * looks the conversation up. Only a real chat with `X-Expert-Id` produces an
1622
- * id it will accept.
1623
- */
1624
- private expertChains;
1625
- /**
1626
- * The 腾讯轻量云 expert chain.
1627
- *
1628
- * Structurally the same as the expert task, with two differences the scorer
1629
- * checks: `agent_task_created` has to name the expert, and the use event has
1630
- * to report `mode: 'LOCAL'` with an empty type and zero cost — that is what
1631
- * the lighthouse criterion looks for.
1632
- */
1633
- private lighthouseChains;
1634
- /**
1635
- * The task-centre pass for one account.
1636
- *
1637
- * Order matters: enrich first (enrol in everything open), then claim. Both
1638
- * halves are idempotent — accepting an already-accepted task succeeds, and a
1639
- * repeat claim answers `already_claimed` — so a pass that dies halfway is
1640
- * safe to replay on the next tick.
1641
- */
1642
- private runTasks;
1643
- /**
1644
- * Streak redemption plus the lottery it unlocks.
1645
- *
1646
- * Tiers unlock on consecutive active days (7/14/28). Redeeming one pays
1647
- * credits, energy, a makeup card and — the part nothing else grants — lottery
1648
- * draws, so the draw runs straight after and only for the chances in hand.
1649
- *
1650
- * Everything here is idempotent: a tier already claimed is skipped by its
1651
- * status, and a draw consumes one chance, so a replay cannot double-spend.
1652
- */
1653
- private redeemStreak;
1654
- /**
1655
- * One trip through the buddy travel loop for an account.
1656
- *
1657
- * A single pass advances the state machine by at most one step: a trip
1658
- * that has arrived is collected, and an idle buddy is sent out. A buddy
1659
- * already travelling is left alone — there is nothing to do until it lands.
1660
- *
1661
- * Measured against the live upstream: the departed trip reports
1662
- * `dailyLimitReached` immediately, so the once-a-day limit needs no local
1663
- * bookkeeping.
1664
- */
1665
- private runTravel;
1666
- }
1667
- //#endregion
1668
- //#region src/status.d.ts
1669
- /** One account's status row. */
1670
- interface AccountStatus {
1671
- id: string;
1672
- label: string;
1673
- nickname?: string;
1674
- domain: string;
1675
- /** ISO timestamp when the access token expires. */
1676
- expiresAt?: string;
1677
- /** Account-wide cooldown (every model blocked). */
1678
- cooling: boolean;
1679
- cooldownUntil?: string;
1680
- /** Per-model cooldowns active right now (modelId → ISO until); the account
1681
- * itself is not `cooling` while only some models are limited. */
1682
- modelCooldowns?: readonly {
1683
- modelId: string;
1684
- until: string;
1685
- }[];
1686
- rateLimitHits: number;
1687
- /** Read-only aggregated credit summary for the account. */
1688
- credits?: WorkBuddyCredits;
1689
- creditsError?: string;
1690
- sourcePath: string;
1691
- }
1692
- /** Whole-plugin status document. */
1693
- interface WorkBuddyStatus {
1694
- ok: boolean;
1695
- accounts: AccountStatus[];
1696
- activeAccountId?: string;
1697
- cooling: number;
1698
- models: {
1699
- id: string;
1700
- name: string;
1701
- multiplier?: number;
1702
- tags?: readonly string[];
1703
- }[];
1704
- shim: {
1705
- running: boolean;
1706
- baseUrl?: string;
1707
- };
1708
- }
1709
- interface StatusOptions {
1710
- pool: WorkBuddyAccountPool;
1711
- /** The catalog to report. Regional callers pass their own region's. */
1712
- catalog: WorkBuddyCatalog;
1713
- client: WorkBuddyUpstreamClient;
1714
- shim?: {
1715
- running: boolean;
1716
- baseUrl?: string;
1717
- };
1718
- /** Query credits per account. Off for cheap diagnostics runs. */
1719
- includeCredits?: boolean;
1720
- }
1721
- /** Build the status document. Never throws. */
1722
- export declare function buildStatus(options: StatusOptions): Promise<WorkBuddyStatus>;
1723
- /** Format the status document for a terminal. */
1724
- export declare function formatStatus(status: WorkBuddyStatus): string;
1725
- /** Format the per-model credit multipliers. */
1726
- export declare function formatRates(status: WorkBuddyStatus): string;
1727
- //#endregion
1728
- //#region src/status-paths.d.ts
1729
- /**
1730
- * Node-free constants and types shared by the Host and browser halves of the
1731
- * WorkBuddy XD Pool settings card.
1732
- *
1733
- * Pool's runtime state already lives in `src/status.ts` (`buildStatus` /
1734
- * `WorkBuddyStatus`); this module only carves the cross-domain (Host→browser)
1735
- * JSON document into a shape that stays token-free and matches what the
1736
- * browser card renders. Route paths are plugin-owned and mounted on the Host's
1737
- * same-origin web server (see `src/web-status.ts`).
1738
- *
1739
- * @module dsh-workbuddy-xdpool/status-paths
1740
- */
1741
- /** Plugin-owned read-only pool status endpoint (account rows + models + shim). */
1742
- export declare const POOL_STATUS_PATH = "/plugins/dsh-workbuddy-xdpool/status";
1743
- /** Plugin-owned local account rescan endpoint (re-read desktop snapshots). */
1744
- export declare const POOL_RESCAN_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/rescan";
1745
- /** Plugin-owned cooldown reset endpoint (clear all 429 cooldowns). */
1746
- export declare const POOL_RESET_COOLDOWN_PATH = "/plugins/dsh-workbuddy-xdpool/cooldowns/reset";
1747
- /** Plugin-owned daily check-in action endpoint (claim today's reward). */
1748
- export declare const POOL_CHECKIN_PATH = "/plugins/dsh-workbuddy-xdpool/checkin";
1749
- /** Plugin-owned model-selection save endpoint (writes the settings section). */
1750
- export declare const POOL_MODELS_SAVE_PATH = "/plugins/dsh-workbuddy-xdpool/models/save";
1751
- /**
1752
- * Throw one account out of the pool for good, or take it back.
1753
- *
1754
- * Separate from the disable route because the semantics differ: disabling is a
1755
- * rotation preference the account survives, ignoring survives the account.
1756
- */
1757
- export declare const POOL_ACCOUNT_IGNORE_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/ignored";
1758
- /** Run one automation job immediately, so the card can verify it on demand. */
1759
- export declare const POOL_AUTOMATION_RUN_PATH = "/plugins/dsh-workbuddy-xdpool/automation/run";
1760
- /** Set or clear one account's reserved-credit floor. */
1761
- export declare const POOL_CREDIT_RESERVE_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/credit-reserve";
1762
- /** One account's row, token-free. */
1763
- interface PoolWebAccount {
1764
- id: string;
1765
- label: string;
1766
- nickname?: string;
1767
- domain: string;
1768
- /** ISO timestamp; absent when the credential carries no expiry. */
1769
- expiresAt?: string;
1770
- /** Account-wide cooldown (every model blocked); only after a no-model penalize. */
1771
- cooling: boolean;
1772
- /** ISO timestamp when the account-wide 429 cooldown lifts; only while cooling. */
1773
- cooldownUntil?: string;
1774
- /**
1775
- * Per-model cooldowns currently active. The account is NOT `cooling` while a
1776
- * model is limited — its other models still serve — but each entry tells the
1777
- * card which model is out until when (e.g. `hy4-preview` cooling to 10:14,
1778
- * `hy3` normal).
1779
- */
1780
- modelCooldowns?: ReadonlyArray<{
1781
- modelId: string;
1782
- until: string;
1783
- }>;
1784
- /**
1785
- * Whether the user switched this account off. A disabled account never
1786
- * serves a request, but it stays listed so the card can switch it back on.
1787
- */
1788
- disabled: boolean;
1789
- rateLimitHits: number;
1790
- /**
1791
- * Credits the user asked to keep for this account. The pool stops picking the
1792
- * account once its balance reaches the reserve, so this many credits survive.
1793
- * 0 means the account may be spent down as before.
1794
- */
1795
- creditReserve: number;
1796
- /**
1797
- * Whether the account is held back purely by its reserve right now. Kept
1798
- * distinct from `cooling`: a reserved account is healthy and simply
1799
- * protected, which is a different thing to tell the user than rate-limited.
1800
- */
1801
- reserved: boolean;
1802
- /**
1803
- * What the automation earned for this account today. Absent when it earned
1804
- * nothing (or the automation never ran for it), so the card can stay quiet
1805
- * instead of printing a row of zeroes.
1806
- */
1807
- automationToday?: PoolWebAutomationEarnings;
1808
- /** ISO timestamp of the last successful use (best-effort pool bookkeeping). */
1809
- lastUsedAt?: string;
1810
- /** Aggregated credit summary for the account, read-only. */
1811
- credits?: PoolWebCredits;
1812
- creditsError?: string;
1813
- /**
1814
- * Today's check-in state for this account, read-only. Present only when the
1815
- * per-account check-in probe succeeded and the program is active. The card
1816
- * renders one claim button per account, so a multi-account pool can collect
1817
- * every account's daily reward without switching accounts by hand.
1818
- */
1819
- checkin?: PoolWebCheckin;
1820
- checkinError?: string;
1821
- }
1822
- /** One credit package (as surfaced by the pool's upstream client), node-free. */
1823
- interface PoolWebCreditPackage {
1824
- packageName: string;
1825
- remain?: number;
1826
- size?: number;
1827
- /** CapacityType 4 — refreshed each cycle and never expires. */
1828
- monthly?: boolean;
1829
- /** Next cycle refresh point, ms. */
1830
- cycleRefreshMs?: number;
1831
- /** One-off expiry, ms. */
1832
- expiresAtMs?: number;
1833
- }
1834
- /** Aggregated credit answer the card renders under one account. */
1835
- interface PoolWebCredits {
1836
- total?: number;
1837
- packages: readonly PoolWebCreditPackage[];
1838
- /** Credits expiring within 3 days. */
1839
- expiringSoon?: number;
1840
- /** When the nearest package expires, ms. */
1841
- nearestExpiryMs?: number;
1842
- }
1843
- /**
1844
- * Daily check-in state the card renders under one account's credits. Mirrors
1845
- * the upstream activity endpoint, minus anything the browser does not need.
1846
- */
1847
- interface PoolWebCheckin {
1848
- /** The activity is running; a claim button is offered only while true. */
1849
- active: boolean;
1850
- /** Already collected today — the button renders as a done state. */
1851
- todayCheckedIn: boolean;
1852
- /** Consecutive days checked in. */
1853
- streakDays: number;
1854
- /** Credits a single day grants. */
1855
- dailyCredit: number;
1856
- /** Credits collected today (0 before claiming). */
1857
- todayCredit: number;
1858
- /** Today is a streak milestone day. */
1859
- isStreakDay: boolean;
1860
- /** The day count the next milestone lands on. */
1861
- nextStreakDay: number;
1862
- /** Bonus credits granted on a milestone day. */
1863
- streakBonusCredit: number;
1864
- }
1865
- /** Result of one claim, so the card can confirm what was collected. */
1866
- interface PoolWebCheckinClaim {
1867
- credit: number;
1868
- streakDays: number;
1869
- isStreakDay: boolean;
1870
- }
1871
- /** One model the pool exposes to DSH, with cost / free tags. */
1872
- interface PoolWebModel {
1873
- id: string;
1874
- name: string;
1875
- /** Relative credit cost, e.g. 0.79 for x0.79. */
1876
- multiplier?: number;
1877
- /** Upstream tags: free / limited-free / night-discount. */
1878
- tags?: readonly string[];
1879
- /** Effective image support after the user's per-model toggle. */
1880
- supportsImages: boolean;
1881
- /** Effective context window after the user's budget cap. */
1882
- contextWindow: number;
1883
- /** The window the upstream advertises, before any cap. */
1884
- nativeContextWindow: number;
1885
- /** Upstream output ceiling, so the card can show both limits. */
1886
- maxOutputTokens: number;
1887
- /** Thinking levels the upstream declares, when it declares any. */
1888
- supportedEfforts?: readonly string[];
1889
- /** Whether this model is currently enabled in the picker. */
1890
- enabled: boolean;
1891
- }
1892
- /**
1893
- * Body of the account ignore/unignore route: exactly one account per request.
1894
- *
1895
- * `ignored: true` throws the account out of the pool for good (its credential is
1896
- * not even read on the next scan, and a fresh desktop sign-in will not bring it
1897
- * back). `false` restores it, at which point the next scan discovers it again.
1898
- */
1899
- interface PoolWebAccountIgnore {
1900
- /** Pool account id, as reported in `PoolWebAccount.id`. */
1901
- accountId: string;
1902
- /** `true` ignores the account permanently; `false` takes it back. */
1903
- ignored: boolean;
1904
- }
1905
- /**
1906
- * One account the user has thrown out of the pool.
1907
- *
1908
- * Kept on the status document so the card can list what was ignored and offer a
1909
- * way back: without that, "ignored" is a one-way door the user cannot inspect or
1910
- * undo from the UI, which is how a hidden list becomes a support burden.
1911
- */
1912
- interface PoolWebIgnoredAccount {
1913
- /** Pool account id, the same key `PoolWebAccount.id` uses. */
1914
- id: string;
1915
- /** Human label captured at ignore time, so the row reads without a rescan. */
1916
- label: string;
1917
- /** ISO timestamp of when it was ignored. */
1918
- ignoredAt: string;
1919
- }
1920
- interface PoolWebModelSelection {
1921
- /** Absent = every model is enabled. */
1922
- enabledModelIds?: readonly string[];
1923
- /** Absent = each model follows its upstream image capability. */
1924
- imageModelIds?: readonly string[];
1925
- /** Per-model context-window cap, keyed by model id. */
1926
- contextBudgets?: Readonly<Record<string, number | undefined>>;
1927
- }
1928
- /** The JSON document the pool card renders. */
1929
- interface PoolWebStatus {
1930
- ok: boolean;
1931
- accounts: readonly PoolWebAccount[];
1932
- /** The next account the pool would use (rotation cursor). */
1933
- activeAccountId?: string;
1934
- cooling: number;
1935
- models: readonly PoolWebModel[];
1936
- /** The saved selection the card diffs its draft against. */
1937
- selection: PoolWebModelSelection;
2097
+ * card switch takes effect on the next pass without any event plumbing.
2098
+ */
2099
+ private accountsInOrder;
1938
2100
  /**
1939
- * How the pool spreads requests: `priority` drains one account before
1940
- * moving on, `round-robin` splits the spend evenly.
2101
+ * Send one activity report, then verify it landed.
2102
+ *
2103
+ * The upstream answers 200 even when it drops the event, so the streak is
2104
+ * read back as the oracle: `days > 0` means it counted. A failed read-back is
2105
+ * logged and treated as a suspicious result, never as a retry — the report is
2106
+ * idempotent per day, and hammering it is exactly what the one-a-day quota
2107
+ * exists to avoid.
1941
2108
  */
1942
- distribution: PoolDistribution;
1943
- /** Which region this document describes. */
1944
- region: PoolRegion;
1945
- /** Every region holding at least one account, in display order. */
1946
- regions: readonly PoolRegion[];
1947
- shim: {
1948
- running: boolean;
1949
- baseUrl?: string;
1950
- };
1951
- /** Daily-points automation state, so the card can show what ran and when. */
1952
- automation: PoolWebAutomation;
1953
- /** Per-account credit floors currently in force, keyed by account id. */
1954
- creditReserves: Readonly<Record<string, number>>;
2109
+ private reportOne;
2110
+ private sendEventChains;
1955
2111
  /**
1956
- * Accounts thrown out of the pool, in the order they were ignored.
2112
+ * Send one chain on the channel it was built for.
1957
2113
  *
1958
- * Reported so the card can show the list and offer a way back. These accounts
1959
- * are NOT in `accounts`: they are filtered out before their credentials are
1960
- * read, which is the whole point of the feature.
2114
+ * The transport is not a detail of the sender: the scorer keys different
2115
+ * tasks to different fingerprint families, so a web-scored event posted as a
2116
+ * desktop event is accepted and then ignored.
1961
2117
  */
1962
- ignored: readonly PoolWebIgnoredAccount[];
1963
- }
1964
- /** One automation job's last run, as shown on the card. */
1965
- interface PoolWebAutomationJob {
1966
- /** `YYYY-MM-DD` of the last run in this process, if it has run. */
1967
- lastRunDate?: string;
1968
- /** Accounts that finished without error on the last run. */
1969
- ok: number;
1970
- /** Accounts that failed on the last run (each one skipped, the run continued). */
1971
- failed: number;
1972
- /** Credits claimed by the task job on the last run. */
1973
- credit: number;
1974
- /** Energy claimed by the task job on the last run. */
1975
- energy: number;
1976
- /** Tasks claimed by the task job on the last run. */
1977
- claimed: number;
1978
- /** One-line summary of the last run. */
1979
- message?: string;
2118
+ private sendChain;
1980
2119
  /**
1981
- * What the last run actually did, in the words of the task board.
2120
+ * Build every chain that scores one task.
1982
2121
  *
1983
- * `message` is a count; this is the list a person can check off, which is
1984
- * what turns a row from "it ran" into "it did the things I care about".
2122
+ * Most tasks need a single chain; `template_5` needs five, because the scorer
2123
+ * counts distinct `template_used` events rather than a boolean. The two tasks
2124
+ * that join a conversation (skill, expert) open a real one first, which is why
2125
+ * this is async.
1985
2126
  */
1986
- detail?: readonly string[];
1987
- /** A pending milestone worth naming, e.g. the next streak tier countdown. */
1988
- progress?: string;
1989
- }
1990
- /**
1991
- * Automation block on the status document.
1992
- *
1993
- * Carries the schedule and each job's last outcome so the card can answer
1994
- * "is it on, when does it run, and what did it last do" without reaching into
1995
- * the scheduler itself.
1996
- */
1997
- interface PoolWebAutomation {
1998
- /** Master switch, mirrored from the saved config. */
1999
- enabled: boolean;
2000
- /** Whether the loop is currently running. */
2001
- running: boolean;
2002
- /** Configured hours per job, so the card can show the schedule. */
2003
- checkinHours: readonly number[];
2004
- reportHours: readonly number[];
2005
- taskHours: readonly number[];
2006
- streakHours: readonly number[];
2007
- travelHours: readonly number[];
2008
- jobs: {
2009
- checkin: PoolWebAutomationJob;
2010
- report: PoolWebAutomationJob;
2011
- tasks: PoolWebAutomationJob;
2012
- streak: PoolWebAutomationJob;
2013
- travel: PoolWebAutomationJob;
2014
- };
2015
- /** Claimable tasks seen on the most recent task pass, across accounts. */
2016
- claimableSeen: number;
2127
+ private chainsFor;
2017
2128
  /**
2018
- * Whether a manual run is in flight. The card polls this to know when to
2019
- * stop showing progress and report the result.
2129
+ * The summon-and-use chains for the expert tasks.
2130
+ *
2131
+ * Two steps per expert, and both are load-bearing: the summon events alone are
2132
+ * impressions, and a use event on its own scores nothing because the scorer
2133
+ * looks the conversation up. Only a real chat with `X-Expert-Id` produces an
2134
+ * id it will accept.
2020
2135
  */
2021
- runInProgress: boolean;
2136
+ private expertChains;
2022
2137
  /**
2023
- * Per-account credits/energy/tasks the automation earned TODAY, keyed by
2024
- * account id. An account that earned nothing is simply absent, so the card
2025
- * can say "nothing yet" instead of showing a bare zero.
2138
+ * The 腾讯轻量云 expert chain.
2139
+ *
2140
+ * Structurally the same as the expert task, with two differences the scorer
2141
+ * checks: `agent_task_created` has to name the expert, and the use event has
2142
+ * to report `mode: 'LOCAL'` with an empty type and zero cost — that is what
2143
+ * the lighthouse criterion looks for.
2026
2144
  */
2027
- earningsToday: Readonly<Record<string, PoolWebAutomationEarnings>>;
2145
+ private lighthouseChains;
2146
+ /**
2147
+ * The task-centre pass for one account.
2148
+ *
2149
+ * Order matters: enrich first (enrol in everything open), then claim. Both
2150
+ * halves are idempotent — accepting an already-accepted task succeeds, and a
2151
+ * repeat claim answers `already_claimed` — so a pass that dies halfway is
2152
+ * safe to replay on the next tick.
2153
+ */
2154
+ private runTasks;
2155
+ /**
2156
+ * Streak redemption plus the lottery it unlocks.
2157
+ *
2158
+ * Tiers unlock on consecutive active days (7/14/28). Redeeming one pays
2159
+ * credits, energy, a makeup card and — the part nothing else grants — lottery
2160
+ * draws, so the draw runs straight after and only for the chances in hand.
2161
+ *
2162
+ * Everything here is idempotent: a tier already claimed is skipped by its
2163
+ * status, and a draw consumes one chance, so a replay cannot double-spend.
2164
+ */
2165
+ private redeemStreak;
2166
+ /**
2167
+ * One trip through the buddy travel loop for an account.
2168
+ *
2169
+ * A single pass advances the state machine by at most one step: a trip
2170
+ * that has arrived is collected, and an idle buddy is sent out. A buddy
2171
+ * already travelling is left alone — there is nothing to do until it lands.
2172
+ *
2173
+ * Measured against the live upstream: the departed trip reports
2174
+ * `dailyLimitReached` immediately, so the once-a-day limit needs no local
2175
+ * bookkeeping.
2176
+ */
2177
+ private runTravel;
2028
2178
  }
2029
- /** Today's automation take for one account. */
2030
- interface PoolWebAutomationEarnings {
2031
- /** Credits claimed from the task centre today. */
2032
- credit: number;
2033
- /** Energy claimed from the task centre today. */
2034
- energy: number;
2035
- /** Tasks claimed today. */
2036
- claimed: number;
2037
- /** Credits collected from check-in today. */
2038
- checkinCredit: number;
2039
- /** Credits from streak redemption and the lottery today. */
2040
- bonusCredit: number;
2041
- /** Credits from buddy adoption and the travel loop today. */
2042
- travelCredit: number;
2043
- /** Local date the counters belong to (YYYY-MM-DD). */
2044
- date: string;
2179
+ //#endregion
2180
+ //#region src/status.d.ts
2181
+ /** One account's status row. */
2182
+ interface AccountStatus {
2183
+ id: string;
2184
+ label: string;
2185
+ nickname?: string;
2186
+ domain: string;
2187
+ /** ISO timestamp when the access token expires. */
2188
+ expiresAt?: string;
2189
+ /** Account-wide cooldown (every model blocked). */
2190
+ cooling: boolean;
2191
+ cooldownUntil?: string;
2192
+ /** Per-model cooldowns active right now (modelId → ISO until); the account
2193
+ * itself is not `cooling` while only some models are limited. */
2194
+ modelCooldowns?: readonly {
2195
+ modelId: string;
2196
+ until: string;
2197
+ }[];
2198
+ rateLimitHits: number;
2199
+ /** Read-only aggregated credit summary for the account. */
2200
+ credits?: WorkBuddyCredits;
2201
+ creditsError?: string;
2202
+ sourcePath: string;
2045
2203
  }
2046
- /**
2047
- * The two gateways, matching the provider ids the host registers. `cn` is the
2048
- * domestic gateway (`copilot.tencent.com` / `codebuddy.cn`); `global` is the
2049
- * international one (`workbuddy.ai`).
2050
- */
2051
- type PoolRegion = 'cn' | 'global';
2052
- /** How the pool spreads requests across its accounts. */
2053
- type PoolDistribution = 'priority' | 'round-robin' | 'balanced';
2054
- /**
2055
- * The schedule every automation job falls back to.
2056
- *
2057
- * Shared by both halves on purpose. The host uses it when a configured hour
2058
- * list arrives empty (the settings schema materializes "never configured" into
2059
- * `[]`), and the card uses it when it writes the `automation` block back, so a
2060
- * document that already holds an empty list is healed instead of being saved
2061
- * back as an unrunnable schedule.
2062
- *
2063
- * This lives here rather than in `scheduler.ts` because the browser half cannot
2064
- * import the host module: `scheduler.ts` pulls in `node:crypto` and the whole
2065
- * upstream client, none of which exists in the browser bundle. Two hand-written
2066
- * copies would drift, and the drift is invisible — the card would write a
2067
- * schedule the scheduler does not run.
2068
- */
2069
- export declare const DEFAULT_AUTOMATION_HOURS: {
2070
- readonly checkin: readonly [9];
2071
- readonly report: readonly [10];
2072
- readonly tasks: readonly [11];
2073
- readonly streak: readonly [12];
2074
- readonly travel: readonly [9, 21];
2075
- };
2204
+ /** Whole-plugin status document. */
2205
+ interface WorkBuddyStatus {
2206
+ ok: boolean;
2207
+ accounts: AccountStatus[];
2208
+ activeAccountId?: string;
2209
+ cooling: number;
2210
+ models: {
2211
+ id: string;
2212
+ name: string;
2213
+ multiplier?: number;
2214
+ tags?: readonly string[];
2215
+ }[];
2216
+ shim: {
2217
+ running: boolean;
2218
+ baseUrl?: string;
2219
+ };
2220
+ }
2221
+ interface StatusOptions {
2222
+ pool: WorkBuddyAccountPool;
2223
+ /** The catalog to report. Regional callers pass their own region's. */
2224
+ catalog: WorkBuddyCatalog;
2225
+ client: WorkBuddyUpstreamClient;
2226
+ shim?: {
2227
+ running: boolean;
2228
+ baseUrl?: string;
2229
+ };
2230
+ /** Query credits per account. Off for cheap diagnostics runs. */
2231
+ includeCredits?: boolean;
2232
+ }
2233
+ /** Build the status document. Never throws. */
2234
+ export declare function buildStatus(options: StatusOptions): Promise<WorkBuddyStatus>;
2235
+ /** Format the status document for a terminal. */
2236
+ export declare function formatStatus(status: WorkBuddyStatus): string;
2237
+ /** Format the per-model credit multipliers. */
2238
+ export declare function formatRates(status: WorkBuddyStatus): string;
2076
2239
  //#endregion
2077
2240
  //#region src/ignored.d.ts
2078
2241
  /** Directory holding this plugin's own state (imported snapshots, ignore list). */
@@ -2202,6 +2365,19 @@ interface PoolStatusRouteOptions {
2202
2365
  * disk, including edits made by the CLI while the card is open.
2203
2366
  */
2204
2367
  ignoredAccounts?: () => readonly PoolWebIgnoredAccount[];
2368
+ /**
2369
+ * Re-fetch the model catalog for BOTH regions and report what landed.
2370
+ *
2371
+ * Absent when the host did not wire it: the route then answers 503 rather
2372
+ * than pretending the refresh happened.
2373
+ */
2374
+ refreshCatalog?: () => Promise<{
2375
+ regions: Readonly<Record<PoolRegion, {
2376
+ source: PoolWebCatalogSource;
2377
+ models: number;
2378
+ error?: string;
2379
+ }>>;
2380
+ }>;
2205
2381
  }
2206
2382
  /**
2207
2383
  * Assemble the card's status document. Per-account credits and check-in state