dsh-workbuddy-xdpool 1.7.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +145 -0
- package/lib/bin.js +392 -31
- package/lib/client.js +167 -9
- package/lib/index.d.ts +200 -3
- package/lib/index.js +433 -57
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -27,10 +27,39 @@ window.__ModuleLoader__.load({
|
|
|
27
27
|
const POOL_RESET_COOLDOWN_PATH = "/plugins/dsh-workbuddy-xdpool/cooldowns/reset";
|
|
28
28
|
/** Plugin-owned daily check-in action endpoint (claim today's reward). */
|
|
29
29
|
const POOL_CHECKIN_PATH = "/plugins/dsh-workbuddy-xdpool/checkin";
|
|
30
|
+
/**
|
|
31
|
+
* Throw one account out of the pool for good, or take it back.
|
|
32
|
+
*
|
|
33
|
+
* Separate from the disable route because the semantics differ: disabling is a
|
|
34
|
+
* rotation preference the account survives, ignoring survives the account.
|
|
35
|
+
*/
|
|
36
|
+
const POOL_ACCOUNT_IGNORE_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/ignored";
|
|
30
37
|
/** Run one automation job immediately, so the card can verify it on demand. */
|
|
31
38
|
const POOL_AUTOMATION_RUN_PATH = "/plugins/dsh-workbuddy-xdpool/automation/run";
|
|
32
39
|
/** Set or clear one account's reserved-credit floor. */
|
|
33
40
|
const POOL_CREDIT_RESERVE_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/credit-reserve";
|
|
41
|
+
/**
|
|
42
|
+
* The schedule every automation job falls back to.
|
|
43
|
+
*
|
|
44
|
+
* Shared by both halves on purpose. The host uses it when a configured hour
|
|
45
|
+
* list arrives empty (the settings schema materializes "never configured" into
|
|
46
|
+
* `[]`), and the card uses it when it writes the `automation` block back, so a
|
|
47
|
+
* document that already holds an empty list is healed instead of being saved
|
|
48
|
+
* back as an unrunnable schedule.
|
|
49
|
+
*
|
|
50
|
+
* This lives here rather than in `scheduler.ts` because the browser half cannot
|
|
51
|
+
* import the host module: `scheduler.ts` pulls in `node:crypto` and the whole
|
|
52
|
+
* upstream client, none of which exists in the browser bundle. Two hand-written
|
|
53
|
+
* copies would drift, and the drift is invisible — the card would write a
|
|
54
|
+
* schedule the scheduler does not run.
|
|
55
|
+
*/
|
|
56
|
+
const DEFAULT_AUTOMATION_HOURS = {
|
|
57
|
+
checkin: [9],
|
|
58
|
+
report: [10],
|
|
59
|
+
tasks: [11],
|
|
60
|
+
streak: [12],
|
|
61
|
+
travel: [9, 21]
|
|
62
|
+
};
|
|
34
63
|
//#endregion
|
|
35
64
|
//#region src/client/icon.ts
|
|
36
65
|
/**
|
|
@@ -168,6 +197,23 @@ window.__ModuleLoader__.load({
|
|
|
168
197
|
.dsm-workbuddy-xdpool-account-toggle:disabled{cursor:default;opacity:.6}
|
|
169
198
|
.dsm-workbuddy-xdpool-account-toggle-on{background:color-mix(in oklab, var(--dsw-alias-state-success-primary,#22a06b) 14%, transparent);border-color:color-mix(in oklab, var(--dsw-alias-state-success-primary,#22a06b) 55%, transparent);color:var(--dsw-alias-state-success-primary,#22a06b);font-weight:600}
|
|
170
199
|
.dsm-workbuddy-xdpool-account-toggle-dot{width:6px;height:6px;border-radius:50%;flex:none;background:currentColor;opacity:.9}
|
|
200
|
+
/* "Remove" button: destructive and reversible, so it is quiet until hovered. */
|
|
201
|
+
.dsm-workbuddy-xdpool-account-ignore{appearance:none;font:inherit;flex:none;cursor:pointer;border:1px solid transparent;border-radius:999px;padding:3px 10px;font-size:11px;line-height:17px;background:transparent;color:var(--dsw-alias-label-tertiary,#9aa0a8);transition:color .16s,border-color .16s,background .16s}
|
|
202
|
+
.dsm-workbuddy-xdpool-account-ignore:hover:not(:disabled){color:var(--dsw-alias-state-error-primary,#ef4444);border-color:color-mix(in oklab, var(--dsw-alias-state-error-primary,#ef4444) 45%, transparent);background:color-mix(in oklab, var(--dsw-alias-state-error-primary,#ef4444) 10%, transparent)}
|
|
203
|
+
.dsm-workbuddy-xdpool-account-ignore:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
204
|
+
.dsm-workbuddy-xdpool-account-ignore:disabled{cursor:default;opacity:.6}
|
|
205
|
+
/* Ignored-account list: visible proof that "remove" can be undone. */
|
|
206
|
+
.dsm-workbuddy-xdpool-ignored-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid color-mix(in oklab, var(--dsw-alias-border-l2,#3a3d45) 45%, transparent)}
|
|
207
|
+
.dsm-workbuddy-xdpool-ignored-title{margin:0;color:var(--dsw-alias-label-primary,#e6e6e6);font-size:13px;font-weight:600;line-height:19px}
|
|
208
|
+
.dsm-workbuddy-xdpool-ignored-summary{margin:0;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:11px;line-height:17px}
|
|
209
|
+
.dsm-workbuddy-xdpool-ignored-list{display:flex;flex-direction:column}
|
|
210
|
+
.dsm-workbuddy-xdpool-ignored-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 16px}
|
|
211
|
+
.dsm-workbuddy-xdpool-ignored-row+.dsm-workbuddy-xdpool-ignored-row{border-top:1px solid color-mix(in oklab, var(--dsw-alias-border-l2,#3a3d45) 30%, transparent)}
|
|
212
|
+
.dsm-workbuddy-xdpool-ignored-label{color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;line-height:18px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
213
|
+
.dsm-workbuddy-xdpool-ignored-restore{appearance:none;font:inherit;flex:none;cursor:pointer;border:1px solid color-mix(in oklab, var(--dsw-alias-border-l2,#3a3d45) 70%, transparent);border-radius:999px;padding:3px 10px;font-size:11px;line-height:17px;background:transparent;color:var(--dsw-alias-label-secondary,#c6c9d0);transition:color .16s,border-color .16s}
|
|
214
|
+
.dsm-workbuddy-xdpool-ignored-restore:hover:not(:disabled){color:var(--dsw-alias-label-primary,#e6e6e6);border-color:var(--dsw-alias-label-dimmed,#777)}
|
|
215
|
+
.dsm-workbuddy-xdpool-ignored-restore:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
216
|
+
.dsm-workbuddy-xdpool-ignored-restore:disabled{cursor:default;opacity:.6}
|
|
171
217
|
.dsm-workbuddy-xdpool-account-tags{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
|
172
218
|
.dsm-workbuddy-xdpool-account-tag{padding:1px 8px;border-radius:999px;font-size:11px;line-height:18px;background:var(--dsw-alias-state-success-subtle,rgba(34,160,107,.12));color:var(--dsw-alias-state-success-primary,#22a06b)}
|
|
173
219
|
.dsm-workbuddy-xdpool-account-tag-cooling{background:var(--dsw-alias-state-warning-subtle,rgba(217,119,6,.15));color:var(--dsw-alias-state-warning-primary,#d97706)}
|
|
@@ -391,6 +437,17 @@ window.__ModuleLoader__.load({
|
|
|
391
437
|
"streak",
|
|
392
438
|
"travel"
|
|
393
439
|
];
|
|
440
|
+
/**
|
|
441
|
+
* The hour list to save for one job: what the card holds, or the default.
|
|
442
|
+
*
|
|
443
|
+
* Mirrors the host's own fallback (`hoursOrDefault` in `scheduler.ts`). An
|
|
444
|
+
* empty list must resolve to the default on BOTH sides, or the card would save
|
|
445
|
+
* a schedule the scheduler then refuses to run — the exact silent standstill
|
|
446
|
+
* this pair of fixes exists to remove.
|
|
447
|
+
*/
|
|
448
|
+
function hoursOrDefault(configured, fallback) {
|
|
449
|
+
return configured !== void 0 && configured.length > 0 ? [...configured] : [...fallback];
|
|
450
|
+
}
|
|
394
451
|
/** Read one job's configured hours off the status document. */
|
|
395
452
|
function automationHours(status, kind) {
|
|
396
453
|
const automation = status.automation;
|
|
@@ -473,9 +530,21 @@ window.__ModuleLoader__.load({
|
|
|
473
530
|
const days = daysUntil(pack.expiresAtMs);
|
|
474
531
|
return days !== void 0 && days <= 3;
|
|
475
532
|
}
|
|
533
|
+
/**
|
|
534
|
+
* The promo badge for one model, or undefined when it has none.
|
|
535
|
+
*
|
|
536
|
+
* `free` is read off the CREDIT MULTIPLIER, not off a tag. Neither gateway ever
|
|
537
|
+
* sends a literal `free` tag: the global roster marks its zero-cost models
|
|
538
|
+
* (`hy3`, `hy4-preview-f`, `deepseek-v4.1-flash`) as `credits: "x0.00"`, so
|
|
539
|
+
* waiting for a tag meant the badge NEVER appeared — including on models that
|
|
540
|
+
* genuinely cost nothing.
|
|
541
|
+
*
|
|
542
|
+
* An explicit free-ish tag still wins when present, so a gateway that starts
|
|
543
|
+
* tagging them keeps working without another change.
|
|
544
|
+
*/
|
|
476
545
|
function tagFor(model) {
|
|
477
546
|
const tags = model.tags ?? [];
|
|
478
|
-
if (tags.includes("free")) return "free";
|
|
547
|
+
if (tags.includes("free") || model.multiplier === 0) return "free";
|
|
479
548
|
if (tags.includes("limited-free")) return "limited";
|
|
480
549
|
if (tags.includes("night-discount")) return "night";
|
|
481
550
|
}
|
|
@@ -687,6 +756,37 @@ window.__ModuleLoader__.load({
|
|
|
687
756
|
if (mounted.current) setAccountBusyId(void 0);
|
|
688
757
|
}
|
|
689
758
|
};
|
|
759
|
+
/**
|
|
760
|
+
* Throw one account out of the pool for good, or take it back.
|
|
761
|
+
*
|
|
762
|
+
* The host owns the ignore file, so this is a plain route call: no settings
|
|
763
|
+
* scope is involved, which is also why it keeps working on a profile whose
|
|
764
|
+
* settings section is read-only.
|
|
765
|
+
*/
|
|
766
|
+
const setAccountIgnored = async (accountId, ignored) => {
|
|
767
|
+
setAccountBusyId(accountId);
|
|
768
|
+
setFlash(void 0);
|
|
769
|
+
try {
|
|
770
|
+
const response = await fetch(POOL_ACCOUNT_IGNORE_PATH, {
|
|
771
|
+
method: "POST",
|
|
772
|
+
headers: { "Content-Type": "application/json" },
|
|
773
|
+
body: JSON.stringify({
|
|
774
|
+
accountId,
|
|
775
|
+
ignored
|
|
776
|
+
})
|
|
777
|
+
});
|
|
778
|
+
if (!response.ok) {
|
|
779
|
+
const detail = await response.json().catch(() => ({}));
|
|
780
|
+
throw new Error(detail.error ?? `HTTP ${response.status}`);
|
|
781
|
+
}
|
|
782
|
+
await refresh(activeRegion);
|
|
783
|
+
} catch (cause) {
|
|
784
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
785
|
+
if (mounted.current) setError(t?.("row.accountIgnoreError", { message }) ?? "Could not change the ignore list: " + message);
|
|
786
|
+
} finally {
|
|
787
|
+
if (mounted.current) setAccountBusyId(void 0);
|
|
788
|
+
}
|
|
789
|
+
};
|
|
690
790
|
/** Keep the draft in step with the server copy while nothing is dirty. */
|
|
691
791
|
const modelDraft = draft ?? (status === void 0 ? {} : draftFromStatus(status));
|
|
692
792
|
/** Model edits need a writable settings scope; otherwise the rows are read-only. */
|
|
@@ -790,12 +890,11 @@ window.__ModuleLoader__.load({
|
|
|
790
890
|
setFlash(void 0);
|
|
791
891
|
try {
|
|
792
892
|
await write.call(settingsScope, "automation", {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
},
|
|
893
|
+
checkinHours: hoursOrDefault(existing?.checkinHours, DEFAULT_AUTOMATION_HOURS.checkin),
|
|
894
|
+
reportHours: hoursOrDefault(existing?.reportHours, DEFAULT_AUTOMATION_HOURS.report),
|
|
895
|
+
taskHours: hoursOrDefault(existing?.taskHours, DEFAULT_AUTOMATION_HOURS.tasks),
|
|
896
|
+
streakHours: hoursOrDefault(existing?.streakHours, DEFAULT_AUTOMATION_HOURS.streak),
|
|
897
|
+
travelHours: hoursOrDefault(existing?.travelHours, DEFAULT_AUTOMATION_HOURS.travel),
|
|
799
898
|
enabled
|
|
800
899
|
});
|
|
801
900
|
await refresh(activeRegion);
|
|
@@ -1272,11 +1371,46 @@ window.__ModuleLoader__.load({
|
|
|
1272
1371
|
onToggleDisabled: (accountId, disabled) => {
|
|
1273
1372
|
toggleAccountDisabled(accountId, disabled);
|
|
1274
1373
|
},
|
|
1374
|
+
onIgnoreAccount: (accountId) => {
|
|
1375
|
+
setAccountIgnored(accountId, true);
|
|
1376
|
+
},
|
|
1275
1377
|
...accountBusyId === void 0 ? {} : { accountBusyId },
|
|
1276
1378
|
t
|
|
1277
1379
|
}, account.id))
|
|
1278
1380
|
]
|
|
1279
1381
|
}) : null,
|
|
1382
|
+
(status?.ignored.length ?? 0) > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1383
|
+
className: "dsm-workbuddy-xdpool-card dsm-workbuddy-xdpool-ignored",
|
|
1384
|
+
"aria-label": t?.("row.ignoredTitle") ?? "Removed accounts",
|
|
1385
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1386
|
+
className: "dsm-workbuddy-xdpool-ignored-head",
|
|
1387
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1388
|
+
className: "dsm-workbuddy-xdpool-ignored-title",
|
|
1389
|
+
children: t?.("row.ignoredTitle") ?? "Removed accounts"
|
|
1390
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1391
|
+
className: "dsm-workbuddy-xdpool-ignored-summary",
|
|
1392
|
+
children: t?.("row.ignoredSummary", { count: status?.ignored.length ?? 0 }) ?? `${status?.ignored.length ?? 0} account(s) no longer in the pool`
|
|
1393
|
+
})]
|
|
1394
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1395
|
+
className: "dsm-workbuddy-xdpool-ignored-list",
|
|
1396
|
+
children: (status?.ignored ?? []).map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1397
|
+
className: "dsm-workbuddy-xdpool-ignored-row",
|
|
1398
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1399
|
+
className: "dsm-workbuddy-xdpool-ignored-label",
|
|
1400
|
+
children: entry.label
|
|
1401
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1402
|
+
type: "button",
|
|
1403
|
+
className: "dsm-workbuddy-xdpool-ignored-restore",
|
|
1404
|
+
title: t?.("row.ignoredRestoreHint") ?? "Put this account back into the pool",
|
|
1405
|
+
disabled: accountBusyId === entry.id,
|
|
1406
|
+
onClick: () => {
|
|
1407
|
+
setAccountIgnored(entry.id, false);
|
|
1408
|
+
},
|
|
1409
|
+
children: t?.("row.ignoredRestore") ?? "Restore"
|
|
1410
|
+
})]
|
|
1411
|
+
}, entry.id))
|
|
1412
|
+
})]
|
|
1413
|
+
}) : null,
|
|
1280
1414
|
(status?.models.length ?? 0) > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1281
1415
|
className: "dsm-workbuddy-xdpool-card dsm-workbuddy-xdpool-models",
|
|
1282
1416
|
"aria-label": t?.("row.modelsTitle") ?? "Models",
|
|
@@ -1332,7 +1466,7 @@ window.__ModuleLoader__.load({
|
|
|
1332
1466
|
});
|
|
1333
1467
|
}
|
|
1334
1468
|
/** One account block: label + status tag + meta + optional credit panels. */
|
|
1335
|
-
function AccountBlock({ account, t, checkinBusyId, onClaimCheckin, accountBusyId, onToggleDisabled, onSaveCreditReserve, reserveBusyId }) {
|
|
1469
|
+
function AccountBlock({ account, t, checkinBusyId, onClaimCheckin, accountBusyId, onToggleDisabled, onIgnoreAccount, onSaveCreditReserve, reserveBusyId }) {
|
|
1336
1470
|
const isDisabled = account.disabled === true;
|
|
1337
1471
|
const isCooling = account.cooling === true;
|
|
1338
1472
|
const cooldownUntil = account.cooldownUntil !== void 0 ? Date.parse(account.cooldownUntil) : void 0;
|
|
@@ -1365,6 +1499,16 @@ window.__ModuleLoader__.load({
|
|
|
1365
1499
|
onToggleDisabled(account.id, !isDisabled);
|
|
1366
1500
|
},
|
|
1367
1501
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dsm-workbuddy-xdpool-account-toggle-dot" }), isDisabled ? t?.("row.accountOff") ?? "Disabled" : t?.("row.accountInRotation") ?? "Enabled"]
|
|
1502
|
+
}),
|
|
1503
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1504
|
+
type: "button",
|
|
1505
|
+
className: "dsm-workbuddy-xdpool-account-ignore",
|
|
1506
|
+
title: t?.("row.accountIgnoreHint") ?? "Remove this account from the pool for good",
|
|
1507
|
+
disabled: accountBusyId === account.id,
|
|
1508
|
+
onClick: () => {
|
|
1509
|
+
onIgnoreAccount(account.id);
|
|
1510
|
+
},
|
|
1511
|
+
children: t?.("row.accountIgnore") ?? "Remove"
|
|
1368
1512
|
})
|
|
1369
1513
|
]
|
|
1370
1514
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1682,7 +1826,7 @@ window.__ModuleLoader__.load({
|
|
|
1682
1826
|
className: "dsm-workbuddy-xdpool-model-copy",
|
|
1683
1827
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1684
1828
|
className: "dsm-workbuddy-xdpool-model-name",
|
|
1685
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: model.name }), model.multiplier === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1829
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: model.name }), model.multiplier === void 0 || model.multiplier === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1686
1830
|
className: "dsm-workbuddy-xdpool-model-name-rate",
|
|
1687
1831
|
children: t?.("row.rate", { rate: model.multiplier.toFixed(2) }) ?? `${model.multiplier.toFixed(2)}x`
|
|
1688
1832
|
})]
|
|
@@ -1829,6 +1973,13 @@ window.__ModuleLoader__.load({
|
|
|
1829
1973
|
"row.accountOff": "Disabled",
|
|
1830
1974
|
"row.accountToggleHint": "Enable this account (uncheck to keep it out of the pool)",
|
|
1831
1975
|
"row.accountToggleError": "Could not switch the account: {message}",
|
|
1976
|
+
"row.accountIgnore": "Remove",
|
|
1977
|
+
"row.accountIgnoreHint": "Remove this account from the pool for good — its credential stops being read and it will not come back",
|
|
1978
|
+
"row.accountIgnoreError": "Could not change the ignore list: {message}",
|
|
1979
|
+
"row.ignoredTitle": "Removed accounts",
|
|
1980
|
+
"row.ignoredSummary": "{count} account(s) no longer in the pool",
|
|
1981
|
+
"row.ignoredRestore": "Restore",
|
|
1982
|
+
"row.ignoredRestoreHint": "Put this account back into the pool",
|
|
1832
1983
|
"row.currentAccount": "In use now",
|
|
1833
1984
|
"row.currentAccountDisabled": "switched off — the next request moves on",
|
|
1834
1985
|
"row.cooling": "Cooling (rate-limited)",
|
|
@@ -1961,6 +2112,13 @@ window.__ModuleLoader__.load({
|
|
|
1961
2112
|
"row.accountOff": "已停用",
|
|
1962
2113
|
"row.accountToggleHint": "启用该账号(取消勾选则不参与池子)",
|
|
1963
2114
|
"row.accountToggleError": "切换账号失败:{message}",
|
|
2115
|
+
"row.accountIgnore": "移出池子",
|
|
2116
|
+
"row.accountIgnoreHint": "把这个账号永久移出池子 —— 不再读取它的凭据,即使重新登录也不会回来",
|
|
2117
|
+
"row.accountIgnoreError": "修改忽略列表失败:{message}",
|
|
2118
|
+
"row.ignoredTitle": "已移出的账号",
|
|
2119
|
+
"row.ignoredSummary": "{count} 个账号已不在池中",
|
|
2120
|
+
"row.ignoredRestore": "恢复",
|
|
2121
|
+
"row.ignoredRestoreHint": "把这个账号放回池子",
|
|
1964
2122
|
"row.currentAccount": "当前使用",
|
|
1965
2123
|
"row.currentAccountDisabled": "已停用 —— 下次请求会换号",
|
|
1966
2124
|
"row.cooling": "冷却中(被限流)",
|
package/lib/index.d.ts
CHANGED
|
@@ -221,6 +221,21 @@ interface WorkBuddyUpstreamModel {
|
|
|
221
221
|
descriptionZh?: string;
|
|
222
222
|
descriptionEn?: string;
|
|
223
223
|
supportsToolCall?: boolean;
|
|
224
|
+
/**
|
|
225
|
+
* Promo tags the upstream attaches to a model.
|
|
226
|
+
*
|
|
227
|
+
* Both gateways send a `tags` array, but the vocabularies are NOT the same
|
|
228
|
+
* and neither uses the words this plugin used to expect:
|
|
229
|
+
*
|
|
230
|
+
* - Global sends `["craft"]`, `["text-to-image"]`, `["text-to-video"]`, `[]`
|
|
231
|
+
* — capability/grouping labels, never `free`.
|
|
232
|
+
* - Zero cost is expressed as `credits: "x0.00"` instead.
|
|
233
|
+
*
|
|
234
|
+
* So this field is a passthrough of what the upstream really said, and
|
|
235
|
+
* `free` is DERIVED from the multiplier rather than awaited as a tag (see
|
|
236
|
+
* {@link isFreeModel}).
|
|
237
|
+
*/
|
|
238
|
+
tags?: readonly string[];
|
|
224
239
|
}
|
|
225
240
|
/** One billing package, already normalised. */
|
|
226
241
|
interface WorkBuddyCreditPackage {
|
|
@@ -830,6 +845,21 @@ export declare class WorkBuddyAccountPool {
|
|
|
830
845
|
* lives on the pool and is re-applied from settings after each scan.
|
|
831
846
|
*/
|
|
832
847
|
private disabledIds;
|
|
848
|
+
/**
|
|
849
|
+
* Account ids the user threw out of the pool for good.
|
|
850
|
+
*
|
|
851
|
+
* Enforced BEFORE the credential is parsed: `scan()` skips a file whose
|
|
852
|
+
* identity is already ignored, so an ignored account costs no at-rest key
|
|
853
|
+
* lookup (which spawns the desktop app on 5.6.0+) and cannot re-enter the pool
|
|
854
|
+
* when the app writes a fresh sign-in for it. That is the difference from
|
|
855
|
+
* {@link disabledIds}, which only filters at pick time and leaves the account
|
|
856
|
+
* listed, readable and re-discoverable.
|
|
857
|
+
*
|
|
858
|
+
* The set is supplied by the host from the plugin's own ignore file, and is
|
|
859
|
+
* replaced wholesale on every {@link applyIgnored} so removing an entry takes
|
|
860
|
+
* effect on the next scan without a restart.
|
|
861
|
+
*/
|
|
862
|
+
private ignoredIds;
|
|
833
863
|
/**
|
|
834
864
|
* Per-account credit floor, keyed by account id. 0 (or absent) means "spend
|
|
835
865
|
* it all".
|
|
@@ -875,6 +905,19 @@ export declare class WorkBuddyAccountPool {
|
|
|
875
905
|
/** Per-account credit floor, keyed by account id. Absent keeps the current map. */
|
|
876
906
|
creditReserves?: Readonly<Record<string, number>>;
|
|
877
907
|
}): void;
|
|
908
|
+
/**
|
|
909
|
+
* Replace the permanent ignore list.
|
|
910
|
+
*
|
|
911
|
+
* Also drops any already-discovered account that is now ignored, so the change
|
|
912
|
+
* is visible without waiting for the next scan: the card refreshes its status
|
|
913
|
+
* document right after the write, and an account still sitting in `accounts`
|
|
914
|
+
* would keep showing up there.
|
|
915
|
+
*/
|
|
916
|
+
applyIgnored(ids: Iterable<string>): void;
|
|
917
|
+
/** Whether this account has been thrown out of the pool for good. */
|
|
918
|
+
isIgnored(accountId: string): boolean;
|
|
919
|
+
/** Every ignored id currently in force, in insertion order. */
|
|
920
|
+
ignoredIdsInOrder(): string[];
|
|
878
921
|
/** Rescan the auth directories and merge newly discovered accounts. */
|
|
879
922
|
scan(): Promise<WorkBuddyAccount[]>;
|
|
880
923
|
/** All accounts, cooldown state included. */
|
|
@@ -1036,7 +1079,20 @@ interface WorkBuddyModelInfo {
|
|
|
1036
1079
|
/** Upstream tags: free / limited-free / night-discount. */
|
|
1037
1080
|
tags?: readonly string[];
|
|
1038
1081
|
}
|
|
1039
|
-
/**
|
|
1082
|
+
/**
|
|
1083
|
+
* Static fallback used before the first live catalog fetch, and whenever the
|
|
1084
|
+
* upstream cannot be reached.
|
|
1085
|
+
*
|
|
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.
|
|
1092
|
+
*
|
|
1093
|
+
* `multiplier: 0` is the gateways' own spelling of "free" (`credits: "x0.00"`),
|
|
1094
|
+
* which is what turns on the free badge.
|
|
1095
|
+
*/
|
|
1040
1096
|
export declare const FALLBACK_WORKBUDDY_MODELS: readonly WorkBuddyModelInfo[];
|
|
1041
1097
|
/** Live catalog with a static fallback behind it. */
|
|
1042
1098
|
export declare class WorkBuddyCatalog {
|
|
@@ -1692,11 +1748,18 @@ export declare const POOL_RESET_COOLDOWN_PATH = "/plugins/dsh-workbuddy-xdpool/c
|
|
|
1692
1748
|
export declare const POOL_CHECKIN_PATH = "/plugins/dsh-workbuddy-xdpool/checkin";
|
|
1693
1749
|
/** Plugin-owned model-selection save endpoint (writes the settings section). */
|
|
1694
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";
|
|
1695
1758
|
/** Run one automation job immediately, so the card can verify it on demand. */
|
|
1696
1759
|
export declare const POOL_AUTOMATION_RUN_PATH = "/plugins/dsh-workbuddy-xdpool/automation/run";
|
|
1697
1760
|
/** Set or clear one account's reserved-credit floor. */
|
|
1698
1761
|
export declare const POOL_CREDIT_RESERVE_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/credit-reserve";
|
|
1699
|
-
/** One
|
|
1762
|
+
/** One account's row, token-free. */
|
|
1700
1763
|
interface PoolWebAccount {
|
|
1701
1764
|
id: string;
|
|
1702
1765
|
label: string;
|
|
@@ -1826,6 +1889,34 @@ interface PoolWebModel {
|
|
|
1826
1889
|
/** Whether this model is currently enabled in the picker. */
|
|
1827
1890
|
enabled: boolean;
|
|
1828
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
|
+
}
|
|
1829
1920
|
interface PoolWebModelSelection {
|
|
1830
1921
|
/** Absent = every model is enabled. */
|
|
1831
1922
|
enabledModelIds?: readonly string[];
|
|
@@ -1861,6 +1952,14 @@ interface PoolWebStatus {
|
|
|
1861
1952
|
automation: PoolWebAutomation;
|
|
1862
1953
|
/** Per-account credit floors currently in force, keyed by account id. */
|
|
1863
1954
|
creditReserves: Readonly<Record<string, number>>;
|
|
1955
|
+
/**
|
|
1956
|
+
* Accounts thrown out of the pool, in the order they were ignored.
|
|
1957
|
+
*
|
|
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.
|
|
1961
|
+
*/
|
|
1962
|
+
ignored: readonly PoolWebIgnoredAccount[];
|
|
1864
1963
|
}
|
|
1865
1964
|
/** One automation job's last run, as shown on the card. */
|
|
1866
1965
|
interface PoolWebAutomationJob {
|
|
@@ -1952,6 +2051,89 @@ interface PoolWebAutomationEarnings {
|
|
|
1952
2051
|
type PoolRegion = 'cn' | 'global';
|
|
1953
2052
|
/** How the pool spreads requests across its accounts. */
|
|
1954
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
|
+
};
|
|
2076
|
+
//#endregion
|
|
2077
|
+
//#region src/ignored.d.ts
|
|
2078
|
+
/** Directory holding this plugin's own state (imported snapshots, ignore list). */
|
|
2079
|
+
export declare const PLUGIN_DATA_DIR_NAME = ".workbuddy-xdpool";
|
|
2080
|
+
/** File holding the permanent ignore list, inside {@link pluginDataDir}. */
|
|
2081
|
+
export declare const IGNORED_FILE_NAME = "ignored.json";
|
|
2082
|
+
/** One ignored account, as stored on disk and shown on the card. */
|
|
2083
|
+
type IgnoredAccount = PoolWebIgnoredAccount;
|
|
2084
|
+
/**
|
|
2085
|
+
* The DSH home directory, honouring the same override the host uses.
|
|
2086
|
+
*
|
|
2087
|
+
* Shared by the CLI and the host so both halves resolve the same file: an
|
|
2088
|
+
* `ignore` written from the terminal has to be visible to the running plugin,
|
|
2089
|
+
* which is only true if they agree on where "home" is.
|
|
2090
|
+
*/
|
|
2091
|
+
export declare function dshHome(env?: NodeJS.ProcessEnv): string;
|
|
2092
|
+
/** This plugin's own state directory. */
|
|
2093
|
+
export declare function pluginDataDir(env?: NodeJS.ProcessEnv): string;
|
|
2094
|
+
/** Absolute path of the ignore list. */
|
|
2095
|
+
export declare function ignoredIdsPath(env?: NodeJS.ProcessEnv): string;
|
|
2096
|
+
/**
|
|
2097
|
+
* Read the ignore list, tolerating every "no list yet" shape.
|
|
2098
|
+
*
|
|
2099
|
+
* A missing file, unreadable file, or invalid JSON all mean the same thing to
|
|
2100
|
+
* the caller — nothing is ignored — so none of them throws. The pool must be
|
|
2101
|
+
* able to start on a machine that has never ignored anything.
|
|
2102
|
+
*/
|
|
2103
|
+
export declare function readIgnoredAccounts(path?: string): Promise<IgnoredAccount[]>;
|
|
2104
|
+
/**
|
|
2105
|
+
* Synchronous read, for startup.
|
|
2106
|
+
*
|
|
2107
|
+
* The host applies the ignore list from inside `apply()`, which is synchronous,
|
|
2108
|
+
* and doing it there removes a startup race: an async load could resolve AFTER
|
|
2109
|
+
* the first account scan, which would let an ignored account slip into the pool
|
|
2110
|
+
* once per boot. The file is a few hundred bytes, so a blocking read at startup
|
|
2111
|
+
* costs nothing measurable.
|
|
2112
|
+
*/
|
|
2113
|
+
export declare function readIgnoredAccountsSync(path?: string): IgnoredAccount[];
|
|
2114
|
+
/**
|
|
2115
|
+
* Replace the ignore list, atomically.
|
|
2116
|
+
*
|
|
2117
|
+
* Written to a sibling temp file and renamed over the target so a crash (or a
|
|
2118
|
+
* concurrent reader) can never observe a half-written document — the ignore
|
|
2119
|
+
* list is the only thing standing between a dead account and the rotation, and
|
|
2120
|
+
* a truncated file reads as "nothing is ignored", which would quietly put every
|
|
2121
|
+
* discarded account back in the pool.
|
|
2122
|
+
*/
|
|
2123
|
+
export declare function writeIgnoredAccounts(accounts: readonly IgnoredAccount[], path?: string): Promise<void>;
|
|
2124
|
+
/**
|
|
2125
|
+
* Add one account to the ignore list, preserving the rest.
|
|
2126
|
+
*
|
|
2127
|
+
* A read-modify-write rather than a wholesale replace: the card and the CLI can
|
|
2128
|
+
* both be open, and each request names exactly one account, so re-writing the
|
|
2129
|
+
* whole list from a stale view would drop the other side's edits.
|
|
2130
|
+
*/
|
|
2131
|
+
export declare function ignoreAccount(account: {
|
|
2132
|
+
id: string;
|
|
2133
|
+
label?: string;
|
|
2134
|
+
}, path?: string): Promise<IgnoredAccount[]>;
|
|
2135
|
+
/** Drop one account from the ignore list. Returns the resulting list. */
|
|
2136
|
+
export declare function unignoreAccount(accountId: string, path?: string): Promise<IgnoredAccount[]>;
|
|
1955
2137
|
//#endregion
|
|
1956
2138
|
//#region src/web-status.d.ts
|
|
1957
2139
|
/** Constructor dependencies — a narrow slice of the pool runtime. */
|
|
@@ -2005,6 +2187,21 @@ interface PoolStatusRouteOptions {
|
|
|
2005
2187
|
* Absent without a settings service: the route then answers 503.
|
|
2006
2188
|
*/
|
|
2007
2189
|
setAccountDisabled?: (accountId: string, disabled: boolean) => Promise<void> | void;
|
|
2190
|
+
/**
|
|
2191
|
+
* Throw one account out of the pool for good, or take it back.
|
|
2192
|
+
*
|
|
2193
|
+
* Backed by the plugin's own ignore file rather than the settings document,
|
|
2194
|
+
* because the CLI writes the same list and has no settings service. Absent
|
|
2195
|
+
* when the host did not wire it: the route then answers 503.
|
|
2196
|
+
*/
|
|
2197
|
+
setAccountIgnored?: (accountId: string, ignored: boolean) => Promise<void> | void;
|
|
2198
|
+
/**
|
|
2199
|
+
* The accounts currently ignored, for the card's "ignored" list.
|
|
2200
|
+
*
|
|
2201
|
+
* A thunk rather than a snapshot so the document always reflects the file on
|
|
2202
|
+
* disk, including edits made by the CLI while the card is open.
|
|
2203
|
+
*/
|
|
2204
|
+
ignoredAccounts?: () => readonly PoolWebIgnoredAccount[];
|
|
2008
2205
|
}
|
|
2009
2206
|
/**
|
|
2010
2207
|
* Assemble the card's status document. Per-account credits and check-in state
|
|
@@ -2214,4 +2411,4 @@ export declare function createCore(logger?: {
|
|
|
2214
2411
|
*/
|
|
2215
2412
|
export declare function apply(ctx: Context, config?: Config): void;
|
|
2216
2413
|
//#endregion
|
|
2217
|
-
export type { AccountStatus, AutomationLedger, AutomationRunSummary, AutomationStatus, Context, ExpertUseMode, MarketExpert, ModelSelection, PoolStatusRouteOptions, PoolWebCheckin, PoolWebCheckinClaim, PoolWebModel, PoolWebModelSelection, PoolWebStatus, SchedulerLogger, TaskEventChain, TaskEventTransport, UpstreamErrorKind, WorkBuddyAccount, WorkBuddyAdapter, WorkBuddyCredential, WorkBuddyModelInfo, WorkBuddyShim, WorkBuddyStatus };
|
|
2414
|
+
export type { AccountStatus, AutomationLedger, AutomationRunSummary, AutomationStatus, Context, ExpertUseMode, IgnoredAccount, MarketExpert, ModelSelection, PoolStatusRouteOptions, PoolWebAccountIgnore, PoolWebCheckin, PoolWebCheckinClaim, PoolWebIgnoredAccount, PoolWebModel, PoolWebModelSelection, PoolWebStatus, SchedulerLogger, TaskEventChain, TaskEventTransport, UpstreamErrorKind, WorkBuddyAccount, WorkBuddyAdapter, WorkBuddyCredential, WorkBuddyModelInfo, WorkBuddyShim, WorkBuddyStatus };
|