dsh-workbuddy-xdpool 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/lib/bin.js +553 -6
- package/lib/client.js +156 -2
- package/lib/index.d.ts +338 -0
- package/lib/index.js +609 -10
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -243,6 +243,21 @@ window.__ModuleLoader__.load({
|
|
|
243
243
|
.dsm-workbuddy-xdpool-checkin-meta{align-items:flex-start}
|
|
244
244
|
.dsm-workbuddy-xdpool-checkin-bonus{text-align:left}
|
|
245
245
|
}
|
|
246
|
+
|
|
247
|
+
/* Automation panel: schedule and last-run summary, collapsed behind a switch. */
|
|
248
|
+
.dsm-workbuddy-xdpool-auto{margin-top:12px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#36373b);border-radius:10px;background:var(--dsw-alias-bg-module-platform,#202126)}
|
|
249
|
+
.dsm-workbuddy-xdpool-auto-head{display:flex;align-items:center;justify-content:space-between;gap:8px}
|
|
250
|
+
.dsm-workbuddy-xdpool-auto-title{color:var(--dsw-alias-label-primary,#e8e8ea);font-size:12px;font-weight:600}
|
|
251
|
+
.dsm-workbuddy-xdpool-auto-switch{padding:3px 12px;border:1px solid var(--dsw-alias-border-l2,#36373b);border-radius:999px;background:transparent;color:var(--dsw-alias-label-dimmed,#8a97b5);font-size:11px;cursor:pointer;transition:color .16s,border-color .16s,background .16s}
|
|
252
|
+
.dsm-workbuddy-xdpool-auto-switch:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed,#777);color:var(--dsw-alias-label-primary,#e8e8ea)}
|
|
253
|
+
.dsm-workbuddy-xdpool-auto-switch:disabled{opacity:.5;cursor:not-allowed}
|
|
254
|
+
.dsm-workbuddy-xdpool-auto-switch-on{border-color:#28c8b4;color:#28c8b4;background:rgba(40,200,180,.12)}
|
|
255
|
+
.dsm-workbuddy-xdpool-auto-hint{margin:6px 0 0;color:var(--dsw-alias-label-dimmed,#8a97b5);font-size:11px;line-height:1.5}
|
|
256
|
+
.dsm-workbuddy-xdpool-auto-jobs{margin-top:8px;display:flex;flex-direction:column;gap:4px}
|
|
257
|
+
.dsm-workbuddy-xdpool-auto-job{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--dsw-alias-label-dimmed,#8a97b5)}
|
|
258
|
+
.dsm-workbuddy-xdpool-auto-job-name{flex:0 0 72px;color:var(--dsw-alias-label-primary,#e8e8ea)}
|
|
259
|
+
.dsm-workbuddy-xdpool-auto-job-when{flex:1 1 auto;font-variant-numeric:tabular-nums}
|
|
260
|
+
.dsm-workbuddy-xdpool-auto-job-last{flex:0 0 auto;text-align:right;font-variant-numeric:tabular-nums}
|
|
246
261
|
`.trim();
|
|
247
262
|
//#endregion
|
|
248
263
|
//#region src/client/PoolCard.tsx
|
|
@@ -303,6 +318,35 @@ window.__ModuleLoader__.load({
|
|
|
303
318
|
minute: "2-digit"
|
|
304
319
|
}).format(new Date(ms));
|
|
305
320
|
}
|
|
321
|
+
/**
|
|
322
|
+
* The automation jobs, in the order they run.
|
|
323
|
+
*
|
|
324
|
+
* The order is the contract: the report has to land before the task pass, or
|
|
325
|
+
* the task pass reads progress the report would have lit. `hoursOf` reads the
|
|
326
|
+
* matching hour list off the status document so the panel stays in step with
|
|
327
|
+
* whatever schedule the scheduler is actually running on.
|
|
328
|
+
*/
|
|
329
|
+
const AUTOMATION_JOBS = [
|
|
330
|
+
"report",
|
|
331
|
+
"tasks",
|
|
332
|
+
"checkin",
|
|
333
|
+
"streak"
|
|
334
|
+
];
|
|
335
|
+
/** Read one job's configured hours off the status document. */
|
|
336
|
+
function automationHours(status, kind) {
|
|
337
|
+
const automation = status.automation;
|
|
338
|
+
if (automation === void 0) return [];
|
|
339
|
+
switch (kind) {
|
|
340
|
+
case "report": return automation.reportHours;
|
|
341
|
+
case "tasks": return automation.taskHours;
|
|
342
|
+
case "checkin": return automation.checkinHours;
|
|
343
|
+
case "streak": return automation.streakHours;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/** Read one job's last-run record off the status document. */
|
|
347
|
+
function automationJob(status, kind) {
|
|
348
|
+
return status.automation?.jobs[kind];
|
|
349
|
+
}
|
|
306
350
|
function dotColor(status) {
|
|
307
351
|
return status === "ok" ? "var(--dsw-alias-state-success-primary, #22a06b)" : status === "error" ? "var(--dsw-alias-state-error-primary, #ef4444)" : "var(--dsw-alias-label-dimmed, #9aa0a6)";
|
|
308
352
|
}
|
|
@@ -392,6 +436,7 @@ window.__ModuleLoader__.load({
|
|
|
392
436
|
const [error, setError] = (0, react.useState)(void 0);
|
|
393
437
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
394
438
|
const [cooldownBusy, setCooldownBusy] = (0, react.useState)(false);
|
|
439
|
+
const [automationBusy, setAutomationBusy] = (0, react.useState)(false);
|
|
395
440
|
const [flash, setFlash] = (0, react.useState)(void 0);
|
|
396
441
|
/** Account id whose daily claim is currently in flight. */
|
|
397
442
|
const [checkinBusyId, setCheckinBusyId] = (0, react.useState)(void 0);
|
|
@@ -646,6 +691,39 @@ window.__ModuleLoader__.load({
|
|
|
646
691
|
if (mounted.current) setError(String(cause));
|
|
647
692
|
}
|
|
648
693
|
};
|
|
694
|
+
/**
|
|
695
|
+
* Switch the daily-points automation on or off.
|
|
696
|
+
*
|
|
697
|
+
* The whole `automation` object is written as one key, because that is how the
|
|
698
|
+
* settings document stores it: the schedule fields must be carried along, or a
|
|
699
|
+
* save would drop the hour lists the scheduler is running on.
|
|
700
|
+
*/
|
|
701
|
+
const setAutomationEnabled = async (enabled) => {
|
|
702
|
+
const write = settingsScope?.set;
|
|
703
|
+
if (write === void 0) {
|
|
704
|
+
setError(t?.("row.modelsSaveError", { message: "settings scope is read-only" }) ?? "settings scope is read-only");
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
const existing = status?.automation;
|
|
708
|
+
setAutomationBusy(true);
|
|
709
|
+
setFlash(void 0);
|
|
710
|
+
try {
|
|
711
|
+
await write.call(settingsScope, "automation", {
|
|
712
|
+
...existing === void 0 ? {} : {
|
|
713
|
+
checkinHours: [...existing.checkinHours],
|
|
714
|
+
reportHours: [...existing.reportHours],
|
|
715
|
+
taskHours: [...existing.taskHours],
|
|
716
|
+
streakHours: [...existing.streakHours]
|
|
717
|
+
},
|
|
718
|
+
enabled
|
|
719
|
+
});
|
|
720
|
+
await refresh(activeRegion);
|
|
721
|
+
} catch (cause) {
|
|
722
|
+
if (mounted.current) setError(String(cause));
|
|
723
|
+
} finally {
|
|
724
|
+
if (mounted.current) setAutomationBusy(false);
|
|
725
|
+
}
|
|
726
|
+
};
|
|
649
727
|
const saveModels = async () => {
|
|
650
728
|
if (draft === void 0 || status === void 0) return;
|
|
651
729
|
if (enabledCount === 0) {
|
|
@@ -818,6 +896,58 @@ window.__ModuleLoader__.load({
|
|
|
818
896
|
}) : null]
|
|
819
897
|
})]
|
|
820
898
|
}),
|
|
899
|
+
status?.automation === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
900
|
+
className: "dsm-workbuddy-xdpool-auto",
|
|
901
|
+
"aria-label": t?.("row.autoTitle") ?? "Automation",
|
|
902
|
+
children: [
|
|
903
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
904
|
+
className: "dsm-workbuddy-xdpool-auto-head",
|
|
905
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
906
|
+
className: "dsm-workbuddy-xdpool-auto-title",
|
|
907
|
+
children: t?.("row.autoTitle") ?? "Automation"
|
|
908
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
909
|
+
type: "button",
|
|
910
|
+
role: "switch",
|
|
911
|
+
"aria-checked": status.automation.enabled,
|
|
912
|
+
disabled: !settingsWritable || automationBusy,
|
|
913
|
+
className: `dsm-workbuddy-xdpool-auto-switch${status.automation.enabled ? " dsm-workbuddy-xdpool-auto-switch-on" : ""}`,
|
|
914
|
+
onClick: () => {
|
|
915
|
+
setAutomationEnabled(!status.automation.enabled);
|
|
916
|
+
},
|
|
917
|
+
children: automationBusy ? t?.("row.autoBusy") ?? "Saving…" : status.automation.enabled ? t?.("row.autoOn") ?? "On" : t?.("row.autoOff") ?? "Off"
|
|
918
|
+
})]
|
|
919
|
+
}),
|
|
920
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
921
|
+
className: "dsm-workbuddy-xdpool-auto-hint",
|
|
922
|
+
children: status.automation.enabled ? t?.("row.autoHintOn") ?? "Reports activity, claims task rewards and checks in once a day." : t?.("row.autoHintOff") ?? "Off: no background requests are made for you."
|
|
923
|
+
}),
|
|
924
|
+
status.automation.enabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
925
|
+
className: "dsm-workbuddy-xdpool-auto-jobs",
|
|
926
|
+
children: AUTOMATION_JOBS.map((kind) => {
|
|
927
|
+
const job = automationJob(status, kind);
|
|
928
|
+
const hours = automationHours(status, kind);
|
|
929
|
+
const label = t?.(`row.autoJob_${kind}`) ?? kind;
|
|
930
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
931
|
+
className: "dsm-workbuddy-xdpool-auto-job",
|
|
932
|
+
children: [
|
|
933
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
934
|
+
className: "dsm-workbuddy-xdpool-auto-job-name",
|
|
935
|
+
children: label
|
|
936
|
+
}),
|
|
937
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
938
|
+
className: "dsm-workbuddy-xdpool-auto-job-when",
|
|
939
|
+
children: hours.length === 0 ? t?.("row.autoHourNone") ?? "not scheduled" : hours.map((hour) => `${String(hour).padStart(2, "0")}:00`).join(" · ")
|
|
940
|
+
}),
|
|
941
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
942
|
+
className: "dsm-workbuddy-xdpool-auto-job-last",
|
|
943
|
+
children: job?.lastRunDate === void 0 ? t?.("row.autoNever") ?? "not run yet" : `${job.lastRunDate} · ${job.ok}${job.failed > 0 ? `/${job.failed}` : ""}`
|
|
944
|
+
})
|
|
945
|
+
]
|
|
946
|
+
}, kind);
|
|
947
|
+
})
|
|
948
|
+
}) : null
|
|
949
|
+
]
|
|
950
|
+
}),
|
|
821
951
|
flash === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
822
952
|
className: "dsm-workbuddy-xdpool-note",
|
|
823
953
|
children: flash
|
|
@@ -1343,7 +1473,19 @@ window.__ModuleLoader__.load({
|
|
|
1343
1473
|
"row.resetCooldownsBusy": "Clearing…",
|
|
1344
1474
|
"row.resetCooldownsDone": "Cooldowns cleared",
|
|
1345
1475
|
"row.accountsRescanned": "Detected {count} account(s)",
|
|
1346
|
-
"row.error": "Pool status unavailable: {message}"
|
|
1476
|
+
"row.error": "Pool status unavailable: {message}",
|
|
1477
|
+
"row.autoTitle": "Automation",
|
|
1478
|
+
"row.autoOn": "On",
|
|
1479
|
+
"row.autoOff": "Off",
|
|
1480
|
+
"row.autoBusy": "Saving…",
|
|
1481
|
+
"row.autoHintOn": "Reports activity, claims task rewards and checks in once a day.",
|
|
1482
|
+
"row.autoHintOff": "Off: no background requests are made for you.",
|
|
1483
|
+
"row.autoJob_report": "Activity report",
|
|
1484
|
+
"row.autoJob_tasks": "Task rewards",
|
|
1485
|
+
"row.autoJob_checkin": "Daily check-in",
|
|
1486
|
+
"row.autoJob_streak": "Streak bonus",
|
|
1487
|
+
"row.autoHourNone": "not scheduled",
|
|
1488
|
+
"row.autoNever": "not run yet"
|
|
1347
1489
|
};
|
|
1348
1490
|
const zh = {
|
|
1349
1491
|
"row.title": "WorkBuddy 池(dsh-workbuddy-xdpool)",
|
|
@@ -1436,7 +1578,19 @@ window.__ModuleLoader__.load({
|
|
|
1436
1578
|
"row.resetCooldownsBusy": "正在清除…",
|
|
1437
1579
|
"row.resetCooldownsDone": "冷却已清除",
|
|
1438
1580
|
"row.accountsRescanned": "检测到 {count} 个账号",
|
|
1439
|
-
"row.error": "池状态不可用:{message}"
|
|
1581
|
+
"row.error": "池状态不可用:{message}",
|
|
1582
|
+
"row.autoTitle": "积分自动化",
|
|
1583
|
+
"row.autoOn": "已开启",
|
|
1584
|
+
"row.autoOff": "已关闭",
|
|
1585
|
+
"row.autoBusy": "保存中…",
|
|
1586
|
+
"row.autoHintOn": "每天自动活跃上报、领取任务奖励并签到。",
|
|
1587
|
+
"row.autoHintOff": "已关闭:不会替你发起任何后台请求。",
|
|
1588
|
+
"row.autoJob_report": "活跃上报",
|
|
1589
|
+
"row.autoJob_tasks": "任务奖励",
|
|
1590
|
+
"row.autoJob_checkin": "每日签到",
|
|
1591
|
+
"row.autoJob_streak": "连登奖励",
|
|
1592
|
+
"row.autoHourNone": "未排程",
|
|
1593
|
+
"row.autoNever": "尚未执行"
|
|
1440
1594
|
};
|
|
1441
1595
|
//#endregion
|
|
1442
1596
|
//#region src/client/index.tsx
|
package/lib/index.d.ts
CHANGED
|
@@ -102,6 +102,32 @@ export declare function classifyUpstreamError(status: number, body: string): Ups
|
|
|
102
102
|
* form, so the pool can resume exactly when the window reopens.
|
|
103
103
|
*/
|
|
104
104
|
export declare function parseRateLimitReset(body: string): number | undefined;
|
|
105
|
+
/** One growth-centre task, flattened from the upstream's loosely-shaped entry. */
|
|
106
|
+
interface WorkBuddyTask {
|
|
107
|
+
/** Upstream task code; the claim path is built from it. */
|
|
108
|
+
taskCode: string;
|
|
109
|
+
title: string;
|
|
110
|
+
/** Reward in credits, when the task declares one. */
|
|
111
|
+
credit: number;
|
|
112
|
+
/** Reward in energy, when the task declares one. */
|
|
113
|
+
energy: number;
|
|
114
|
+
/** Whether the task carries any reward at all. */
|
|
115
|
+
hasReward: boolean;
|
|
116
|
+
/** Progress target; 0 is a valid value (a task with no counter). */
|
|
117
|
+
target: number;
|
|
118
|
+
/** Current progress; 0 is a valid value. */
|
|
119
|
+
current: number;
|
|
120
|
+
/** Upstream enrolment state: not_accepted / accepted / claimed. */
|
|
121
|
+
acceptStatus: string;
|
|
122
|
+
/** Upstream task state, e.g. `complete`. */
|
|
123
|
+
status: string;
|
|
124
|
+
/** Progress reached its target and the reward is still outstanding. */
|
|
125
|
+
claimable: boolean;
|
|
126
|
+
/** Reward already collected. */
|
|
127
|
+
claimed: boolean;
|
|
128
|
+
/** Upstream marked the task locked (not yet reachable). */
|
|
129
|
+
locked: boolean;
|
|
130
|
+
}
|
|
105
131
|
export declare class WorkBuddyUpstreamClient {
|
|
106
132
|
private readonly fetchImpl;
|
|
107
133
|
private readonly clientVersion;
|
|
@@ -139,6 +165,40 @@ export declare class WorkBuddyUpstreamClient {
|
|
|
139
165
|
fetchCheckinStatus(credential: WorkBuddyCredential): Promise<WorkBuddyCheckinStatus>;
|
|
140
166
|
/** Claim today's check-in reward. The browser route guards this mutation. */
|
|
141
167
|
claimDailyCheckin(credential: WorkBuddyCredential): Promise<WorkBuddyCheckinClaim>;
|
|
168
|
+
/**
|
|
169
|
+
* Report one chat-activity event to the growth system.
|
|
170
|
+
*
|
|
171
|
+
* The body is an ARRAY holding a single `chat_request_send` event, and every
|
|
172
|
+
* field is filled in: a three-field minimal event is accepted with 200 and
|
|
173
|
+
* then silently dropped, so the full shape is load-bearing rather than
|
|
174
|
+
* cosmetic. `userId` is the one field the server actually keys on — without
|
|
175
|
+
* it the request still answers 200 and scores nothing.
|
|
176
|
+
*
|
|
177
|
+
* One report per account per day is the quota the reference panel settled on;
|
|
178
|
+
* a single report lights the growth streak and unlocks the `first_buddy`
|
|
179
|
+
* family, which is why this runs before the task-centre pass.
|
|
180
|
+
*/
|
|
181
|
+
reportActivity(credential: WorkBuddyCredential, conversationId?: string): Promise<void>;
|
|
182
|
+
/**
|
|
183
|
+
* Read back the growth streak in days.
|
|
184
|
+
*
|
|
185
|
+
* This is the read-only oracle for {@link reportActivity}: a report that
|
|
186
|
+
* returned 200 yet left the streak untouched was silently dropped (a missing
|
|
187
|
+
* `userId` is the usual cause), so callers verify instead of trusting the
|
|
188
|
+
* status code.
|
|
189
|
+
*
|
|
190
|
+
* Two shape traps, both measured against the live upstream:
|
|
191
|
+
*
|
|
192
|
+
* - The path carries NO `/v2` prefix, unlike its sibling task endpoints under
|
|
193
|
+
* `/v2/activity/growth/*`. Asking for the `/v2` form does not 404; it
|
|
194
|
+
* answers with a body that carries no `streak` object at all.
|
|
195
|
+
* - The counter is nested as `data.streak.days`, not `data.days`. Reading the
|
|
196
|
+
* flat field yields a constant 0, which would make every successful report
|
|
197
|
+
* look like a silent drop.
|
|
198
|
+
*
|
|
199
|
+
* Returns 0 only when the field is genuinely absent.
|
|
200
|
+
*/
|
|
201
|
+
growthStreakDays(credential: WorkBuddyCredential): Promise<number>;
|
|
142
202
|
/** Legacy thin wrapper kept for `status`/`doctor`: returns raw envelope data. */
|
|
143
203
|
credits(credential: WorkBuddyCredential): Promise<{
|
|
144
204
|
ok: true;
|
|
@@ -147,6 +207,40 @@ export declare class WorkBuddyUpstreamClient {
|
|
|
147
207
|
ok: false;
|
|
148
208
|
message: string;
|
|
149
209
|
}>;
|
|
210
|
+
/**
|
|
211
|
+
* Fetch the growth task list for one account.
|
|
212
|
+
*
|
|
213
|
+
* The upstream answers `data.tasks[]`, and `claimable` is derived locally —
|
|
214
|
+
* the upstream does not mark it. Only a task whose progress reached its
|
|
215
|
+
* target and that is not already claimed counts as eligible.
|
|
216
|
+
*/
|
|
217
|
+
listTasks(credential: WorkBuddyCredential): Promise<readonly WorkBuddyTask[]>;
|
|
218
|
+
/**
|
|
219
|
+
* Accept (enrol in) tasks by code.
|
|
220
|
+
*
|
|
221
|
+
* Accepting is the "sign up" half: it produces no progress by itself, and the
|
|
222
|
+
* upstream answers success for an already-accepted task, so replaying this is
|
|
223
|
+
* safe. Progress is lit by real activity (a chat, an activity report).
|
|
224
|
+
*/
|
|
225
|
+
acceptTasks(credential: WorkBuddyCredential, taskCodes: readonly string[]): Promise<void>;
|
|
226
|
+
/**
|
|
227
|
+
* Claim one task's reward.
|
|
228
|
+
*
|
|
229
|
+
* Two details differ from list/accept and are load-bearing:
|
|
230
|
+
*
|
|
231
|
+
* - The task code rides the PATH, not the body.
|
|
232
|
+
* - It is served by the web origin, not the chat host, and only when the
|
|
233
|
+
* request carries the growth-centre Origin/Referer plus
|
|
234
|
+
* `x-client-platform: web`. The chat host's `/reward/claim` path does not
|
|
235
|
+
* exist and answers 400 "task not completed".
|
|
236
|
+
*
|
|
237
|
+
* A repeat claim answers `already_claimed` with zero credit, which is treated
|
|
238
|
+
* as success so the caller can stay idempotent.
|
|
239
|
+
*/
|
|
240
|
+
claimTaskReward(credential: WorkBuddyCredential, taskCode: string): Promise<{
|
|
241
|
+
credit: number;
|
|
242
|
+
energy: number;
|
|
243
|
+
}>;
|
|
150
244
|
}
|
|
151
245
|
//#endregion
|
|
152
246
|
//#region src/accounts.d.ts
|
|
@@ -541,6 +635,161 @@ interface WorkBuddyAdapter {
|
|
|
541
635
|
*/
|
|
542
636
|
export declare function createWorkBuddyAdapter(options: WorkBuddyAdapterOptions): WorkBuddyAdapter;
|
|
543
637
|
//#endregion
|
|
638
|
+
//#region src/scheduler.d.ts
|
|
639
|
+
/** Which jobs the automation runs, and when. */
|
|
640
|
+
interface AutomationOptions {
|
|
641
|
+
/** Master switch; false stops every job. */
|
|
642
|
+
enabled?: boolean;
|
|
643
|
+
/** Hour list for the daily check-in job. */
|
|
644
|
+
checkinHours?: readonly number[];
|
|
645
|
+
/** Hour list for the task-centre job (accept + claim). */
|
|
646
|
+
taskHours?: readonly number[];
|
|
647
|
+
/** Hour list for the activity-report job. */
|
|
648
|
+
reportHours?: readonly number[];
|
|
649
|
+
/** Hour list for the streak-redemption job. */
|
|
650
|
+
streakHours?: readonly number[];
|
|
651
|
+
/** Per-account serial delay, in milliseconds. */
|
|
652
|
+
accountDelayMs?: number;
|
|
653
|
+
/** Override the clock, for tests. */
|
|
654
|
+
now?: () => Date;
|
|
655
|
+
/** Logger; defaults to a no-op so tests stay quiet. */
|
|
656
|
+
logger?: SchedulerLogger;
|
|
657
|
+
}
|
|
658
|
+
/** Logger surface, kept structural so any host logger fits. */
|
|
659
|
+
interface SchedulerLogger {
|
|
660
|
+
info?(...args: unknown[]): void;
|
|
661
|
+
warn?(...args: unknown[]): void;
|
|
662
|
+
}
|
|
663
|
+
/** One job's last-run record, surfaced on the status document. */
|
|
664
|
+
interface AutomationJobState {
|
|
665
|
+
/** `YYYY-MM-DD` of the last completed run, or undefined if it never ran. */
|
|
666
|
+
lastRunDate?: string;
|
|
667
|
+
/** Epoch ms of the last completed run. */
|
|
668
|
+
lastRunAtMs?: number;
|
|
669
|
+
/** Accounts that completed without throwing. */
|
|
670
|
+
ok: number;
|
|
671
|
+
/** Accounts that threw (each one skipped, the run continued). */
|
|
672
|
+
failed: number;
|
|
673
|
+
/** Credits claimed by the task job on the last run. */
|
|
674
|
+
credit: number;
|
|
675
|
+
/** Energy claimed by the task job on the last run. */
|
|
676
|
+
energy: number;
|
|
677
|
+
/** Tasks claimed by the task job on the last run. */
|
|
678
|
+
claimed: number;
|
|
679
|
+
/** Human-readable summary of the last run. */
|
|
680
|
+
message?: string;
|
|
681
|
+
}
|
|
682
|
+
/** Automation snapshot for the status document and the card. */
|
|
683
|
+
interface AutomationStatus {
|
|
684
|
+
enabled: boolean;
|
|
685
|
+
/** Whether the loop is running. */
|
|
686
|
+
running: boolean;
|
|
687
|
+
checkinHours: readonly number[];
|
|
688
|
+
taskHours: readonly number[];
|
|
689
|
+
reportHours: readonly number[];
|
|
690
|
+
streakHours: readonly number[];
|
|
691
|
+
jobs: {
|
|
692
|
+
checkin: AutomationJobState;
|
|
693
|
+
report: AutomationJobState;
|
|
694
|
+
tasks: AutomationJobState;
|
|
695
|
+
streak: AutomationJobState;
|
|
696
|
+
};
|
|
697
|
+
/** Claimable tasks seen on the most recent task pass, across accounts. */
|
|
698
|
+
claimableSeen: number;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* The points automation.
|
|
702
|
+
*
|
|
703
|
+
* Owns a single timer loop. Construction is inert — nothing runs until
|
|
704
|
+
* {@link start}, and {@link stop} is idempotent so a plugin teardown that fires
|
|
705
|
+
* twice is harmless.
|
|
706
|
+
*/
|
|
707
|
+
declare class WorkBuddyScheduler {
|
|
708
|
+
private readonly pool;
|
|
709
|
+
private readonly client;
|
|
710
|
+
private readonly logger;
|
|
711
|
+
private readonly now;
|
|
712
|
+
private readonly delayMs;
|
|
713
|
+
private enabled;
|
|
714
|
+
private checkinHours;
|
|
715
|
+
private taskHours;
|
|
716
|
+
private reportHours;
|
|
717
|
+
private streakHours;
|
|
718
|
+
private timer;
|
|
719
|
+
private running;
|
|
720
|
+
/** Guards against a slow run overlapping the next tick. */
|
|
721
|
+
private busy;
|
|
722
|
+
/**
|
|
723
|
+
* Set once {@link stop} is called.
|
|
724
|
+
*
|
|
725
|
+
* Deliberately false before `start`: the loop is not running yet, but a
|
|
726
|
+
* manual `tick` must still work. `stop` is what makes a run abandon the
|
|
727
|
+
* accounts it has not reached yet.
|
|
728
|
+
*/
|
|
729
|
+
private stopped;
|
|
730
|
+
private readonly states;
|
|
731
|
+
private claimableSeen;
|
|
732
|
+
constructor(pool: WorkBuddyAccountPool, client: WorkBuddyUpstreamClient, options?: AutomationOptions);
|
|
733
|
+
/** Apply a new configuration; safe to call while running. */
|
|
734
|
+
applyConfig(options: AutomationOptions): void;
|
|
735
|
+
/** Hours for one job, used by the loop and the status document. */
|
|
736
|
+
private hoursOf;
|
|
737
|
+
/** Start the loop. Idempotent. */
|
|
738
|
+
start(): void;
|
|
739
|
+
/** Stop the loop. Idempotent, and safe before `start`. */
|
|
740
|
+
stop(): void;
|
|
741
|
+
/** Snapshot for the status document. */
|
|
742
|
+
status(): AutomationStatus;
|
|
743
|
+
/**
|
|
744
|
+
* One poll: run every due job, serially.
|
|
745
|
+
*
|
|
746
|
+
* Serial by design — the jobs share the same accounts and the upstream
|
|
747
|
+
* rate-limits per account, so overlapping passes would only trip that limit.
|
|
748
|
+
* A job that throws is recorded and the loop continues.
|
|
749
|
+
*/
|
|
750
|
+
private tick;
|
|
751
|
+
/** Run one job against every eligible account and record the outcome. */
|
|
752
|
+
private runJob;
|
|
753
|
+
/** Compose the one-line summary shown on the card. */
|
|
754
|
+
private summarise;
|
|
755
|
+
/**
|
|
756
|
+
* Accounts to run against, in pool order.
|
|
757
|
+
*
|
|
758
|
+
* Disabled accounts are excluded here rather than filtered by the caller so a
|
|
759
|
+
* card switch takes effect on the next pass without any event plumbing.
|
|
760
|
+
*/
|
|
761
|
+
private accountsInOrder;
|
|
762
|
+
/**
|
|
763
|
+
* Send one activity report, then verify it landed.
|
|
764
|
+
*
|
|
765
|
+
* The upstream answers 200 even when it drops the event, so the streak is
|
|
766
|
+
* read back as the oracle: `days > 0` means it counted. A failed read-back is
|
|
767
|
+
* logged and treated as a suspicious result, never as a retry — the report is
|
|
768
|
+
* idempotent per day, and hammering it is exactly what the one-a-day quota
|
|
769
|
+
* exists to avoid.
|
|
770
|
+
*/
|
|
771
|
+
private reportOne;
|
|
772
|
+
/**
|
|
773
|
+
* The task-centre pass for one account.
|
|
774
|
+
*
|
|
775
|
+
* Order matters: enrich first (enrol in everything open), then claim. Both
|
|
776
|
+
* halves are idempotent — accepting an already-accepted task succeeds, and a
|
|
777
|
+
* repeat claim answers `already_claimed` — so a pass that dies halfway is
|
|
778
|
+
* safe to replay on the next tick.
|
|
779
|
+
*/
|
|
780
|
+
private runTasks;
|
|
781
|
+
/**
|
|
782
|
+
* Streak redemption and lottery.
|
|
783
|
+
*
|
|
784
|
+
* Left as a deliberate no-op placeholder: the tier/lottery endpoints need
|
|
785
|
+
* their own round of probing against the live upstream before they can be
|
|
786
|
+
* wired safely, and a wrong call here could burn a redemption. The job slot,
|
|
787
|
+
* scheduling and status plumbing already exist, so filling it in is a
|
|
788
|
+
* self-contained change.
|
|
789
|
+
*/
|
|
790
|
+
private redeemStreak;
|
|
791
|
+
}
|
|
792
|
+
//#endregion
|
|
544
793
|
//#region src/status.d.ts
|
|
545
794
|
/** One account's status row. */
|
|
546
795
|
interface AccountStatus {
|
|
@@ -767,6 +1016,51 @@ interface PoolWebStatus {
|
|
|
767
1016
|
running: boolean;
|
|
768
1017
|
baseUrl?: string;
|
|
769
1018
|
};
|
|
1019
|
+
/** Daily-points automation state, so the card can show what ran and when. */
|
|
1020
|
+
automation: PoolWebAutomation;
|
|
1021
|
+
}
|
|
1022
|
+
/** One automation job's last run, as shown on the card. */
|
|
1023
|
+
interface PoolWebAutomationJob {
|
|
1024
|
+
/** `YYYY-MM-DD` of the last run in this process, if it has run. */
|
|
1025
|
+
lastRunDate?: string;
|
|
1026
|
+
/** Accounts that finished without error on the last run. */
|
|
1027
|
+
ok: number;
|
|
1028
|
+
/** Accounts that failed on the last run (each one skipped, the run continued). */
|
|
1029
|
+
failed: number;
|
|
1030
|
+
/** Credits claimed by the task job on the last run. */
|
|
1031
|
+
credit: number;
|
|
1032
|
+
/** Energy claimed by the task job on the last run. */
|
|
1033
|
+
energy: number;
|
|
1034
|
+
/** Tasks claimed by the task job on the last run. */
|
|
1035
|
+
claimed: number;
|
|
1036
|
+
/** One-line summary of the last run. */
|
|
1037
|
+
message?: string;
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Automation block on the status document.
|
|
1041
|
+
*
|
|
1042
|
+
* Carries the schedule and each job's last outcome so the card can answer
|
|
1043
|
+
* "is it on, when does it run, and what did it last do" without reaching into
|
|
1044
|
+
* the scheduler itself.
|
|
1045
|
+
*/
|
|
1046
|
+
interface PoolWebAutomation {
|
|
1047
|
+
/** Master switch, mirrored from the saved config. */
|
|
1048
|
+
enabled: boolean;
|
|
1049
|
+
/** Whether the loop is currently running. */
|
|
1050
|
+
running: boolean;
|
|
1051
|
+
/** Configured hours per job, so the card can show the schedule. */
|
|
1052
|
+
checkinHours: readonly number[];
|
|
1053
|
+
reportHours: readonly number[];
|
|
1054
|
+
taskHours: readonly number[];
|
|
1055
|
+
streakHours: readonly number[];
|
|
1056
|
+
jobs: {
|
|
1057
|
+
checkin: PoolWebAutomationJob;
|
|
1058
|
+
report: PoolWebAutomationJob;
|
|
1059
|
+
tasks: PoolWebAutomationJob;
|
|
1060
|
+
streak: PoolWebAutomationJob;
|
|
1061
|
+
};
|
|
1062
|
+
/** Claimable tasks seen on the most recent task pass, across accounts. */
|
|
1063
|
+
claimableSeen: number;
|
|
770
1064
|
}
|
|
771
1065
|
/**
|
|
772
1066
|
* The two gateways, matching the provider ids the host registers. `cn` is the
|
|
@@ -836,6 +1130,12 @@ export interface Config {
|
|
|
836
1130
|
*/
|
|
837
1131
|
modelSelectionCn?: ModelSelectionConfig;
|
|
838
1132
|
modelSelectionGlobal?: ModelSelectionConfig;
|
|
1133
|
+
/**
|
|
1134
|
+
* Daily-points automation. Absent means off: the scheduler makes upstream
|
|
1135
|
+
* calls on the user behalf, so it stays opt-in rather than surprising a
|
|
1136
|
+
* fresh install with background traffic.
|
|
1137
|
+
*/
|
|
1138
|
+
automation?: AutomationConfig;
|
|
839
1139
|
}
|
|
840
1140
|
/** One region's saved model selection. */
|
|
841
1141
|
export interface ModelSelectionConfig {
|
|
@@ -843,8 +1143,42 @@ export interface ModelSelectionConfig {
|
|
|
843
1143
|
imageModelIds?: string[];
|
|
844
1144
|
contextBudgets?: Record<string, number>;
|
|
845
1145
|
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Daily-points automation.
|
|
1148
|
+
*
|
|
1149
|
+
* Absent means off: the scheduler makes upstream calls on the user behalf, so
|
|
1150
|
+
* it stays opt-in rather than surprising a fresh install with background
|
|
1151
|
+
* traffic. Each job carries its own hour list so the passes can be spread out
|
|
1152
|
+
* (or pushed off-peak) without disabling any of them.
|
|
1153
|
+
*
|
|
1154
|
+
* Ordering note: the report job must run before the task job. A report is what
|
|
1155
|
+
* lights the growth streak and unlocks the `first_buddy` family, so a task pass
|
|
1156
|
+
* that ran first would read counters before they could have moved.
|
|
1157
|
+
*/
|
|
1158
|
+
export interface AutomationConfig {
|
|
1159
|
+
/** Master switch for every automation job. Absent reads as false. */
|
|
1160
|
+
enabled?: boolean;
|
|
1161
|
+
/** Hours (local, 0-23) at which the daily check-in runs. */
|
|
1162
|
+
checkinHours?: number[];
|
|
1163
|
+
/** Hours at which the activity report runs. Keep ahead of `taskHours`. */
|
|
1164
|
+
reportHours?: number[];
|
|
1165
|
+
/** Hours at which tasks are enrolled in and claimed. */
|
|
1166
|
+
taskHours?: number[];
|
|
1167
|
+
/** Hours at which streak redemption runs. */
|
|
1168
|
+
streakHours?: number[];
|
|
1169
|
+
/** How long an account rests after its credits run out, in milliseconds. */
|
|
1170
|
+
exhaustCooldownMs?: number;
|
|
1171
|
+
}
|
|
846
1172
|
/** Upper bound the card offers as the "default" context window, in tokens. */
|
|
847
1173
|
export declare const DEFAULT_CONTEXT_BUDGET = 200000;
|
|
1174
|
+
/**
|
|
1175
|
+
* Fold a saved automation block into scheduler options.
|
|
1176
|
+
*
|
|
1177
|
+
* Absent means off, stated once here so every caller agrees: the card writes
|
|
1178
|
+
* `enabled` as a real boolean, and a config that never touched the section must
|
|
1179
|
+
* not accidentally arm background upstream traffic.
|
|
1180
|
+
*/
|
|
1181
|
+
export declare function automationOptions(automation: AutomationConfig | undefined): AutomationOptions;
|
|
848
1182
|
/** Settings key holding one region's saved selection. */
|
|
849
1183
|
export declare const modelSelectionKeyFor: (region: 'cn' | 'global') => string;
|
|
850
1184
|
/**
|
|
@@ -869,6 +1203,8 @@ export interface WorkBuddyPoolApi {
|
|
|
869
1203
|
rescan(): Promise<number>;
|
|
870
1204
|
status(includeCredits?: boolean): Promise<Awaited<ReturnType<typeof buildStatus>>>;
|
|
871
1205
|
resetCooldowns(): void;
|
|
1206
|
+
/** Daily-points automation; assembled with the core, inert until started. */
|
|
1207
|
+
scheduler: WorkBuddyScheduler;
|
|
872
1208
|
}
|
|
873
1209
|
/** The live API, or undefined when the plugin has not applied yet. */
|
|
874
1210
|
export declare function currentApi(): WorkBuddyPoolApi | undefined;
|
|
@@ -885,6 +1221,7 @@ export declare function setApi(next: WorkBuddyPoolApi | undefined): void;
|
|
|
885
1221
|
*/
|
|
886
1222
|
export declare function createCore(logger?: {
|
|
887
1223
|
warn(...args: unknown[]): void;
|
|
1224
|
+
info?(...args: unknown[]): void;
|
|
888
1225
|
}): {
|
|
889
1226
|
pool: WorkBuddyAccountPool;
|
|
890
1227
|
catalogs: {
|
|
@@ -892,6 +1229,7 @@ export declare function createCore(logger?: {
|
|
|
892
1229
|
readonly global: WorkBuddyCatalog;
|
|
893
1230
|
};
|
|
894
1231
|
client: WorkBuddyUpstreamClient;
|
|
1232
|
+
scheduler: WorkBuddyScheduler;
|
|
895
1233
|
};
|
|
896
1234
|
/**
|
|
897
1235
|
* Start the loopback endpoint, register the `workbuddy-xdpool` provider, and
|