dsh-workbuddy-xdpool 1.1.0 → 1.2.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 +36 -0
- package/lib/bin.js +1668 -19
- package/lib/client.js +415 -80
- package/lib/index.d.ts +891 -17
- package/lib/index.js +1943 -42
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -3,6 +3,167 @@ import { Api, Model } from "@earendil-works/pi-ai";
|
|
|
3
3
|
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
4
4
|
import { Context, Context as Context$1 } from "@deepseek-ai/cordis";
|
|
5
5
|
import { SettingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
6
|
+
//#region src/task-events.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* The event chains that light up the client-scored daily tasks.
|
|
9
|
+
*
|
|
10
|
+
* The task board advertises things like "open an app", "use five templates" or
|
|
11
|
+
* "summon a platform expert". Those read as actions only a human at the desktop
|
|
12
|
+
* app can take, but the scorer does not check the app — it checks the event
|
|
13
|
+
* stream. Replaying the same events, with the same desktop fingerprint, scores
|
|
14
|
+
* the task. Every chain here was measured against the live upstream.
|
|
15
|
+
*
|
|
16
|
+
* Kept apart from the scheduler because a chain is pure data: the scheduler
|
|
17
|
+
* decides *when* to send one, this module decides *what* one is. A few chains
|
|
18
|
+
* need a real server-side id first (the expert family), which is why the builder
|
|
19
|
+
* is allowed to be async and to call the upstream.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* How a chain reaches the upstream.
|
|
23
|
+
*
|
|
24
|
+
* There are three fingerprint families and the scorer keys different tasks to
|
|
25
|
+
* different ones, so the transport is part of the chain rather than a detail of
|
|
26
|
+
* the sender: `Buddy_App` needs the desktop fingerprint, while `Library_read` is
|
|
27
|
+
* only scored when it arrives with the WEB fingerprint.
|
|
28
|
+
*/
|
|
29
|
+
type TaskEventTransport = 'desktop' | 'web';
|
|
30
|
+
/** A ready-to-send chain, plus the channel it must go out on. */
|
|
31
|
+
interface TaskEventChain {
|
|
32
|
+
transport: TaskEventTransport;
|
|
33
|
+
/** Desktop chains: the event array. */
|
|
34
|
+
events?: readonly Record<string, unknown>[];
|
|
35
|
+
/** Web chains: the single page event to report. */
|
|
36
|
+
web?: {
|
|
37
|
+
eventCode: string;
|
|
38
|
+
pageUrl: string;
|
|
39
|
+
elementId: string;
|
|
40
|
+
elementName: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The app the buddy chain enters.
|
|
45
|
+
*
|
|
46
|
+
* One chain lights up two tasks: `Buddy_App` (open any app) and `Buddy_App_QQ`
|
|
47
|
+
* (the QQ-specific one), because this is a QQ-hosted app. Measured 0/1 → 1/1 on
|
|
48
|
+
* both from a single chain.
|
|
49
|
+
*/
|
|
50
|
+
export declare const BUDDY_APP_ID = "cb_y5Dy46tPQGGWtueMxXbe";
|
|
51
|
+
export declare const BUDDY_APP_NAME = "企鹅教师助手";
|
|
52
|
+
/**
|
|
53
|
+
* The buddy-app chain (two tasks).
|
|
54
|
+
*
|
|
55
|
+
* Five clicks in the order a user would make them: discover the app, see it,
|
|
56
|
+
* enter it, confirm the account link, skip the second binding step.
|
|
57
|
+
*/
|
|
58
|
+
export declare function buddyAppChain(): TaskEventChain;
|
|
59
|
+
/**
|
|
60
|
+
* The design-canvas chain (`create_canvas`, +300 — the joint largest reward).
|
|
61
|
+
*
|
|
62
|
+
* The two canvas events ride the same metrics channel as everything else, so no
|
|
63
|
+
* real canvas is ever created; the chat chain in front supplies the ids they
|
|
64
|
+
* reference. Measured 1/1 on three accounts.
|
|
65
|
+
*/
|
|
66
|
+
export declare function canvasChain(): TaskEventChain;
|
|
67
|
+
/**
|
|
68
|
+
* The scheduled-task event (`automation_1`).
|
|
69
|
+
*
|
|
70
|
+
* One event is the whole chain — measured 1/1 on two accounts. The name is only
|
|
71
|
+
* for the server's own records, so a generated one is fine.
|
|
72
|
+
*/
|
|
73
|
+
export declare function automationChain(): TaskEventChain;
|
|
74
|
+
/**
|
|
75
|
+
* The plain chat chain (`RichMeow_Chat`, and the base of the template chain).
|
|
76
|
+
*
|
|
77
|
+
* Measured: this chain alone lights `RichMeow_Chat`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function chatChain(): TaskEventChain;
|
|
80
|
+
/**
|
|
81
|
+
* The "same as this case" chain (`playbook_prompt`).
|
|
82
|
+
*
|
|
83
|
+
* The scorer watches `playbook_prompt_send` — sending the prompt that the
|
|
84
|
+
* inspiration case pre-fills — not the card impression or the button click, so
|
|
85
|
+
* the whole click path is replayed for realism but the send is what counts.
|
|
86
|
+
*/
|
|
87
|
+
export declare function playbookChain(caseId?: string, caseName?: string): TaskEventChain;
|
|
88
|
+
/** The inspiration case the reference panel sends a prompt for. */
|
|
89
|
+
export declare const PLAYBOOK_CASE_ID = "pm-gtm-launch-plan";
|
|
90
|
+
export declare const PLAYBOOK_CASE_NAME = "新产品上市 GTM 发布计划一页纸";
|
|
91
|
+
/**
|
|
92
|
+
* The five templates the reference panel cycles through, as `[id, name]`.
|
|
93
|
+
*
|
|
94
|
+
* The upstream does not check that these templates exist — only that five
|
|
95
|
+
* distinct `template_used` events arrive — so they are the reference set.
|
|
96
|
+
*/
|
|
97
|
+
export declare const TEMPLATE_PRESETS: readonly (readonly [string, string])[];
|
|
98
|
+
/**
|
|
99
|
+
* One "created a task from a template" chain (`template_5`, +100 for five).
|
|
100
|
+
*
|
|
101
|
+
* Each group is a chat chain (which supplies the ids the template events join
|
|
102
|
+
* on) plus `agent_task_created_with_template` and `template_used`. Measured:
|
|
103
|
+
* five groups in one report scored 5/5.
|
|
104
|
+
*/
|
|
105
|
+
export declare function templateChain(templateId: string, templateName: string): TaskEventChain;
|
|
106
|
+
/** Every template group, ready to send in order. */
|
|
107
|
+
export declare function templateChains(): readonly TaskEventChain[];
|
|
108
|
+
/**
|
|
109
|
+
* The library-introduction click (`Library_read`).
|
|
110
|
+
*
|
|
111
|
+
* Scored on the WEB fingerprint — the same event posted with the desktop
|
|
112
|
+
* fingerprint scores nothing — so this chain returns a web transport and the
|
|
113
|
+
* scheduler routes it through `reportWebEvent`.
|
|
114
|
+
*/
|
|
115
|
+
export declare function libraryReadChain(): TaskEventChain;
|
|
116
|
+
/** The document the library click is reported against. */
|
|
117
|
+
export declare const LIBRARY_DOC_URL = "https://www.workbuddy.cn/space/d/o0KWYeynteVv06UnAZqIFm";
|
|
118
|
+
/** The theme key `Hp_Appearance` is scored on (和平精英激战金秋). */
|
|
119
|
+
export declare const APPEARANCE_THEME_KEY = "theme-tkmw7j";
|
|
120
|
+
/** The skill `skill_1` is scored on. */
|
|
121
|
+
export declare const SKILL_ID = "skill_2097350077599879168";
|
|
122
|
+
export declare const SKILL_NAME = "润泽小馆·日报撰写";
|
|
123
|
+
/** The 腾讯轻量云 expert `Expert_lighthouse` is scored on. */
|
|
124
|
+
export declare const LIGHTHOUSE_EXPERT_ID = "ex_2cvvUZQhDyeJ";
|
|
125
|
+
/**
|
|
126
|
+
* Build the `skill_1` chain from a REAL conversation.
|
|
127
|
+
*
|
|
128
|
+
* Unlike the template and canvas chains, this one is verified against the
|
|
129
|
+
* conversation it names, so the caller must first open a real chat and hand the
|
|
130
|
+
* server-side ids in.
|
|
131
|
+
*/
|
|
132
|
+
export declare function skillChain(conversationId: string, requestId: string): TaskEventChain;
|
|
133
|
+
/** The theme-apply event `Hp_Appearance` is scored on. */
|
|
134
|
+
export declare function appearanceChain(themeKey?: string): TaskEventChain;
|
|
135
|
+
/** One expert from the platform's marketplace, as the summon chains need it. */
|
|
136
|
+
interface MarketExpert {
|
|
137
|
+
expertId: string;
|
|
138
|
+
expertType: string;
|
|
139
|
+
displayName: string;
|
|
140
|
+
profession: string;
|
|
141
|
+
version: string;
|
|
142
|
+
categories: readonly string[];
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The three "summon an expert" events (`expert_summon_click` and friends).
|
|
146
|
+
*
|
|
147
|
+
* Paid before the conversation, in the order the app emits them.
|
|
148
|
+
*/
|
|
149
|
+
export declare function expertSummonEvents(expert: MarketExpert): Record<string, unknown>[];
|
|
150
|
+
/** `mode` for the `expert_actual_use` payload; the scorer checks it. */
|
|
151
|
+
type ExpertUseMode = 'craft' | 'LOCAL';
|
|
152
|
+
/**
|
|
153
|
+
* The "an expert really answered" event, which is what the expert tasks count.
|
|
154
|
+
*
|
|
155
|
+
* The `requestId` must be the SERVER's id for a real chat: a made-up one scores
|
|
156
|
+
* nothing, because the scorer looks the conversation up.
|
|
157
|
+
*/
|
|
158
|
+
export declare function expertActualUseEvent(expert: MarketExpert, conversationId: string, requestId: string, mode?: ExpertUseMode): Record<string, unknown>;
|
|
159
|
+
/**
|
|
160
|
+
* The chat chain for an expert conversation.
|
|
161
|
+
*
|
|
162
|
+
* `agent_task_created` carries the expert fields the scorer reads to attribute
|
|
163
|
+
* the conversation to that expert.
|
|
164
|
+
*/
|
|
165
|
+
export declare function expertChatEvents(expert: MarketExpert, conversationId: string, requestId: string): Record<string, unknown>[];
|
|
166
|
+
//#endregion
|
|
6
167
|
//#region src/upstream.d.ts
|
|
7
168
|
/** Upstream failure classes the shim maps onto distinct HTTP answers. */
|
|
8
169
|
type UpstreamErrorKind = 'hard_credit' | 'soft_rate' | 'session_dead' | 'not_found' | 'server' | 'client';
|
|
@@ -103,6 +264,45 @@ export declare function classifyUpstreamError(status: number, body: string): Ups
|
|
|
103
264
|
*/
|
|
104
265
|
export declare function parseRateLimitReset(body: string): number | undefined;
|
|
105
266
|
/** One growth-centre task, flattened from the upstream's loosely-shaped entry. */
|
|
267
|
+
/** One streak tier and what redeeming it pays. */
|
|
268
|
+
interface WorkBuddyStreakTier {
|
|
269
|
+
/** Tier key, e.g. `7d`. */
|
|
270
|
+
tier: string;
|
|
271
|
+
/** Login days the tier needs. */
|
|
272
|
+
days: number;
|
|
273
|
+
credit: number;
|
|
274
|
+
energy: number;
|
|
275
|
+
cards: number;
|
|
276
|
+
/** Lottery draws the tier grants. */
|
|
277
|
+
chances: number;
|
|
278
|
+
/** `locked` / `unlocked` / `claimed`. */
|
|
279
|
+
status: string;
|
|
280
|
+
}
|
|
281
|
+
/** The growth streak as a whole: progress, tiers, makeup cards. */
|
|
282
|
+
interface WorkBuddyStreakStatus {
|
|
283
|
+
/** Current consecutive active days. */
|
|
284
|
+
days: number;
|
|
285
|
+
/** Days active this month. */
|
|
286
|
+
monthTotalDays: number;
|
|
287
|
+
/** Next tier key, e.g. `7d`. */
|
|
288
|
+
nextTier: string;
|
|
289
|
+
/** Days still needed for `nextTier`. */
|
|
290
|
+
nextTierRemaining: number;
|
|
291
|
+
/** Makeup cards in hand. */
|
|
292
|
+
makeupCards: number;
|
|
293
|
+
tiers: readonly WorkBuddyStreakTier[];
|
|
294
|
+
}
|
|
295
|
+
/** One buddy trip state. */
|
|
296
|
+
interface WorkBuddyTravelState {
|
|
297
|
+
/** `idle` (can depart) / `traveling` / `arrived` (can claim). */
|
|
298
|
+
state: string;
|
|
299
|
+
/** Trip id, required to claim an arrived trip. */
|
|
300
|
+
recordId: number;
|
|
301
|
+
/** The once-a-day depart limit has been used. */
|
|
302
|
+
dailyLimitReached: boolean;
|
|
303
|
+
/** Credits an arrived trip pays. */
|
|
304
|
+
rewardCredit: number;
|
|
305
|
+
}
|
|
106
306
|
interface WorkBuddyTask {
|
|
107
307
|
/** Upstream task code; the claim path is built from it. */
|
|
108
308
|
taskCode: string;
|
|
@@ -128,6 +328,55 @@ interface WorkBuddyTask {
|
|
|
128
328
|
/** Upstream marked the task locked (not yet reachable). */
|
|
129
329
|
locked: boolean;
|
|
130
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* Flatten one upstream task entry.
|
|
333
|
+
*
|
|
334
|
+
* Progress is reported two ways depending on the task: flat `current`/`target`
|
|
335
|
+
* fields, or a nested `progress: {current, target}`. The nested form wins when
|
|
336
|
+
* it carries anything, because a task that reports both puts the live counter
|
|
337
|
+
* there. Entries without a usable `task_code` are dropped — without one the
|
|
338
|
+
* claim path cannot be built.
|
|
339
|
+
*/
|
|
340
|
+
/**
|
|
341
|
+
* The event chain that scores the two Buddy-app tasks.
|
|
342
|
+
*
|
|
343
|
+
* Both tasks accept the same chain, and the chain is a pure value: building it
|
|
344
|
+
* needs no client, so callers (and tests) can hold one without a live upstream.
|
|
345
|
+
*
|
|
346
|
+
* The task text says "upgrade to the desktop client and open it from the app
|
|
347
|
+
* launcher". The scorer does not watch the UI — it watches this event sequence
|
|
348
|
+
* with the desktop fingerprint, which is why the sequence is what gets sent.
|
|
349
|
+
*
|
|
350
|
+
* Measured against the live upstream: progress 0/1 → 1/1 claimable in ~8s.
|
|
351
|
+
*/
|
|
352
|
+
/**
|
|
353
|
+
* A complete "desktop client ran a request successfully" event chain.
|
|
354
|
+
*
|
|
355
|
+
* Six events in the order the real client emits them: task created, message
|
|
356
|
+
* send, request send, message response, message status, request response.
|
|
357
|
+
* Several tasks are scored off this chain (or one that embeds it), because what
|
|
358
|
+
* they measure is "a real request completed", which the client only proves
|
|
359
|
+
* through this exact sequence.
|
|
360
|
+
*
|
|
361
|
+
* Measured: this chain alone lights up `RichMeow_Chat`.
|
|
362
|
+
*/
|
|
363
|
+
export declare function desktopChatEvents(conversationId: string, requestId: string, messageId: string, modelId?: string, modelName?: string): Record<string, unknown>[];
|
|
364
|
+
/**
|
|
365
|
+
* A chat chain plus the two canvas events that score `create_canvas`.
|
|
366
|
+
*
|
|
367
|
+
* Worth +300, the joint largest task on the board. The canvas events ride
|
|
368
|
+
* the same metrics channel as everything else, so no real canvas is needed.
|
|
369
|
+
*
|
|
370
|
+
* Measured: three accounts scored 1/1 from this sequence.
|
|
371
|
+
*/
|
|
372
|
+
export declare function desktopCanvasEvents(conversationId: string, requestId: string): Record<string, unknown>[];
|
|
373
|
+
/**
|
|
374
|
+
* The single event that scores `automation_1` (a scheduled task was created).
|
|
375
|
+
*
|
|
376
|
+
* Measured: two accounts lit it with this event alone.
|
|
377
|
+
*/
|
|
378
|
+
export declare function desktopAutomationCreatedEvent(name: string): Record<string, unknown>;
|
|
379
|
+
export declare function buddyAppEvents(buddyId: string, buddyName: string): Record<string, unknown>[];
|
|
131
380
|
export declare class WorkBuddyUpstreamClient {
|
|
132
381
|
private readonly fetchImpl;
|
|
133
382
|
private readonly clientVersion;
|
|
@@ -165,17 +414,97 @@ export declare class WorkBuddyUpstreamClient {
|
|
|
165
414
|
fetchCheckinStatus(credential: WorkBuddyCredential): Promise<WorkBuddyCheckinStatus>;
|
|
166
415
|
/** Claim today's check-in reward. The browser route guards this mutation. */
|
|
167
416
|
claimDailyCheckin(credential: WorkBuddyCredential): Promise<WorkBuddyCheckinClaim>;
|
|
417
|
+
/**
|
|
418
|
+
* Public fingerprint fields every desktop event carries.
|
|
419
|
+
*
|
|
420
|
+
* The upstream scores a desktop-fingerprinted task only when the event looks
|
|
421
|
+
* like it came from the desktop client: the same field set, the same stable
|
|
422
|
+
* device ids, the same build. A partial map is accepted with 200 and scores
|
|
423
|
+
* nothing, so these are copied wholesale rather than trimmed.
|
|
424
|
+
*
|
|
425
|
+
* `machineId`/`sessionId` are DERIVED from the account uid, never random: a
|
|
426
|
+
* new device id on every call is itself a signal that the traffic is not a
|
|
427
|
+
* real client.
|
|
428
|
+
*/
|
|
429
|
+
desktopFingerprint(credential: WorkBuddyCredential): Record<string, unknown>;
|
|
430
|
+
/**
|
|
431
|
+
* Send desktop-fingerprinted events to the growth system.
|
|
432
|
+
*
|
|
433
|
+
* The body is an ARRAY of events, and every event carries the full desktop
|
|
434
|
+
* fingerprint plus its own business fields. Different tasks recognise
|
|
435
|
+
* different fingerprint families (CLI / desktop / web), which is why this is
|
|
436
|
+
* separate from {@link reportActivity}: they are not interchangeable.
|
|
437
|
+
*
|
|
438
|
+
* Business fields win over the fingerprint, so a caller can override a device
|
|
439
|
+
* id to align with a real install.
|
|
440
|
+
*/
|
|
441
|
+
reportDesktopEvents(credential: WorkBuddyCredential, events: readonly Record<string, unknown>[]): Promise<void>;
|
|
442
|
+
/**
|
|
443
|
+
* Report one chat-activity event to the growth system.
|
|
444
|
+
*
|
|
445
|
+
* The body is an ARRAY holding a single `chat_request_send` event, and every
|
|
446
|
+
* field is filled in: a three-field minimal event is accepted with 200 and
|
|
447
|
+
* then silently dropped, so the full shape is load-bearing rather than
|
|
448
|
+
* cosmetic. `userId` is the one field the server actually keys on — without
|
|
449
|
+
* it the request still answers 200 and scores nothing.
|
|
450
|
+
*
|
|
451
|
+
* One report per account per day is the quota the reference panel settled on;
|
|
452
|
+
* a single report lights the growth streak and unlocks the `first_buddy`
|
|
453
|
+
* family, which is why this runs before the task-centre pass.
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Send a WEB-fingerprinted event.
|
|
457
|
+
*
|
|
458
|
+
* A third fingerprint family, alongside CLI and desktop: a browser shape
|
|
459
|
+
* posted to the web origin with x-client-platform: web. Page-behaviour
|
|
460
|
+
* tasks such as `Library_read` are scored on it. Measured:
|
|
461
|
+
* `library_doc_intro_click` scored about four seconds after landing.
|
|
462
|
+
*/
|
|
463
|
+
reportWebEvent(credential: WorkBuddyCredential, eventCode: string, pageUrl: string, elementId: string, elementName: string): Promise<void>;
|
|
464
|
+
/**
|
|
465
|
+
* Apply an appearance theme on the account.
|
|
466
|
+
*
|
|
467
|
+
* The theme task is scored on the `appearance_skin_apply` event, not on this
|
|
468
|
+
* call — but the event alone is not enough either. The pair is what a real
|
|
469
|
+
* client produces: it PATCHes the account's selected skin, then reports the
|
|
470
|
+
* event as the settings page closes. Measured on the reference panel after
|
|
471
|
+
* the earlier "the API alone does not score" reading was corrected.
|
|
472
|
+
*/
|
|
473
|
+
setAppearanceTheme(credential: WorkBuddyCredential, resourceKey: string): Promise<void>;
|
|
474
|
+
/**
|
|
475
|
+
* The platform's expert marketplace.
|
|
476
|
+
*
|
|
477
|
+
* Needed before any expert can be summoned: the scorer verifies that the
|
|
478
|
+
* expert id exists on the platform, so a made-up id scores nothing. The
|
|
479
|
+
* response carries the display fields the summon events replay.
|
|
480
|
+
*/
|
|
481
|
+
marketExpertList(credential: WorkBuddyCredential, expertType?: 'agent' | 'team' | ''): Promise<readonly MarketExpert[]>;
|
|
482
|
+
/**
|
|
483
|
+
* Open a REAL desktop conversation and return the ids the server assigned.
|
|
484
|
+
*
|
|
485
|
+
* The expert and skill tasks join their events to a conversation the server
|
|
486
|
+
* has actually seen, so a locally invented `requestId` scores nothing. This
|
|
487
|
+
* starts a chat, reads the server's id out of the SSE stream, then drops the
|
|
488
|
+
* rest of the stream — the answer itself is irrelevant, only its identity is.
|
|
489
|
+
*
|
|
490
|
+
* Returns `undefined` instead of throwing when the conversation cannot be
|
|
491
|
+
* opened or carries no recognisable id, because every caller is a best-effort
|
|
492
|
+
* task chain.
|
|
493
|
+
*/
|
|
494
|
+
openConversation(credential: WorkBuddyCredential, expertId?: string, signal?: AbortSignal): Promise<{
|
|
495
|
+
conversationId: string;
|
|
496
|
+
requestId: string;
|
|
497
|
+
} | undefined>;
|
|
168
498
|
/**
|
|
169
499
|
* Report one chat-activity event to the growth system.
|
|
170
500
|
*
|
|
171
|
-
* The body is an ARRAY holding a single
|
|
501
|
+
* The body is an ARRAY holding a single chat_request_send event, and every
|
|
172
502
|
* field is filled in: a three-field minimal event is accepted with 200 and
|
|
173
503
|
* then silently dropped, so the full shape is load-bearing rather than
|
|
174
|
-
* cosmetic.
|
|
175
|
-
* it the request still answers 200 and scores nothing.
|
|
504
|
+
* cosmetic. userId is the one field the server actually keys on.
|
|
176
505
|
*
|
|
177
|
-
* One report per account per day is the quota the reference panel settled
|
|
178
|
-
* a single report lights the growth streak and unlocks the
|
|
506
|
+
* One report per account per day is the quota the reference panel settled
|
|
507
|
+
* on; a single report lights the growth streak and unlocks the first_buddy
|
|
179
508
|
* family, which is why this runs before the task-centre pass.
|
|
180
509
|
*/
|
|
181
510
|
reportActivity(credential: WorkBuddyCredential, conversationId?: string): Promise<void>;
|
|
@@ -199,6 +528,81 @@ export declare class WorkBuddyUpstreamClient {
|
|
|
199
528
|
* Returns 0 only when the field is genuinely absent.
|
|
200
529
|
*/
|
|
201
530
|
growthStreakDays(credential: WorkBuddyCredential): Promise<number>;
|
|
531
|
+
/**
|
|
532
|
+
* The full streak picture: days, tier unlock state, and what each tier pays.
|
|
533
|
+
*
|
|
534
|
+
* Read before redeeming, because the tier state is the only honest answer to
|
|
535
|
+
* "is there anything to claim": the redeem endpoint answers 403 for a locked
|
|
536
|
+
* tier, which is indistinguishable from a real failure once the response is
|
|
537
|
+
* just an error.
|
|
538
|
+
*/
|
|
539
|
+
growthStreakFull(credential: WorkBuddyCredential): Promise<WorkBuddyStreakStatus>;
|
|
540
|
+
/**
|
|
541
|
+
* Redeem one unlocked streak tier.
|
|
542
|
+
*
|
|
543
|
+
* A locked tier answers 403 ("连续登录天数不足"); callers check the status from
|
|
544
|
+
* {@link growthStreakFull} first, so this only throws for genuine failures.
|
|
545
|
+
* The client token is the upstream's idempotency key — a fresh one per attempt
|
|
546
|
+
* keeps a retry from being read as a duplicate of the last one.
|
|
547
|
+
*/
|
|
548
|
+
redeemStreakTier(credential: WorkBuddyCredential, tier: string): Promise<void>;
|
|
549
|
+
/** How many lottery draws are available right now. */
|
|
550
|
+
lotteryChances(credential: WorkBuddyCredential): Promise<number>;
|
|
551
|
+
/**
|
|
552
|
+
* Draw the lottery once.
|
|
553
|
+
*
|
|
554
|
+
* Returns the raw prize payload: its shape is set by the running campaign, so
|
|
555
|
+
* it is passed through rather than modelled.
|
|
556
|
+
*/
|
|
557
|
+
lotteryDraw(credential: WorkBuddyCredential): Promise<unknown>;
|
|
558
|
+
/**
|
|
559
|
+
* The buddy profile, or undefined when the account has no buddy yet.
|
|
560
|
+
*
|
|
561
|
+
* `data.buddy` is null / absent / an empty object depending on how far the
|
|
562
|
+
* account got, and all three mean the same thing to a caller: adopt first.
|
|
563
|
+
*/
|
|
564
|
+
buddyInfo(credential: WorkBuddyCredential): Promise<{
|
|
565
|
+
instanceId: number;
|
|
566
|
+
name: string;
|
|
567
|
+
} | undefined>;
|
|
568
|
+
/** Agree to the buddy terms. Idempotent upstream. */
|
|
569
|
+
buddyAgree(credential: WorkBuddyCredential): Promise<void>;
|
|
570
|
+
/**
|
|
571
|
+
* Adopt the first buddy.
|
|
572
|
+
*
|
|
573
|
+
* Gated upstream on having reported activity that day: without it the answer
|
|
574
|
+
* is 400 "first_buddy task not completed yet". Callers treat that as "not yet"
|
|
575
|
+
* rather than an error, which is why it is thrown as-is for them to classify.
|
|
576
|
+
*/
|
|
577
|
+
buddyAdoptFirst(credential: WorkBuddyCredential): Promise<void>;
|
|
578
|
+
/** Current travel state for the account's buddy. */
|
|
579
|
+
buddyTravelStatus(credential: WorkBuddyCredential): Promise<WorkBuddyTravelState>;
|
|
580
|
+
/**
|
|
581
|
+
* Send the buddy travelling.
|
|
582
|
+
*
|
|
583
|
+
* The location is always 4 (古镇客栈): the four locations have identical
|
|
584
|
+
* reward and duration ranges, so there is nothing to optimise.
|
|
585
|
+
*/
|
|
586
|
+
buddyTravelDepart(credential: WorkBuddyCredential, locationId?: number): Promise<void>;
|
|
587
|
+
/**
|
|
588
|
+
* Collect an arrived trip's reward.
|
|
589
|
+
*
|
|
590
|
+
* `recordId` is required and comes from the status read; the upstream rejects
|
|
591
|
+
* a claim without it.
|
|
592
|
+
*/
|
|
593
|
+
buddyTravelClaim(credential: WorkBuddyCredential, recordId: number): Promise<number>;
|
|
594
|
+
/** Whether yesterday is a gap in the activity heatmap. */
|
|
595
|
+
heatmapYesterdayMissed(credential: WorkBuddyCredential): Promise<boolean>;
|
|
596
|
+
/** Spend one makeup card on a date. Idempotent for an already-filled date. */
|
|
597
|
+
useMakeupCard(credential: WorkBuddyCredential, date: string): Promise<void>;
|
|
598
|
+
/**
|
|
599
|
+
* Call a growth-domain endpoint and return its unwrapped `data`.
|
|
600
|
+
*
|
|
601
|
+
* These endpoints live on the chat host with the billing header set, and
|
|
602
|
+
* carry the same envelope as everything else. Centralised here because every
|
|
603
|
+
* growth call needs the identical envelope check.
|
|
604
|
+
*/
|
|
605
|
+
private growthJson;
|
|
202
606
|
/** Legacy thin wrapper kept for `status`/`doctor`: returns raw envelope data. */
|
|
203
607
|
credits(credential: WorkBuddyCredential): Promise<{
|
|
204
608
|
ok: true;
|
|
@@ -378,6 +782,25 @@ export declare class WorkBuddyAccountPool {
|
|
|
378
782
|
* lives on the pool and is re-applied from settings after each scan.
|
|
379
783
|
*/
|
|
380
784
|
private disabledIds;
|
|
785
|
+
/**
|
|
786
|
+
* Per-account credit floor, keyed by account id. 0 (or absent) means "spend
|
|
787
|
+
* it all".
|
|
788
|
+
*
|
|
789
|
+
* A reserved balance is protection, not a hard limit the upstream knows
|
|
790
|
+
* about: the pool simply stops picking that account once its last known
|
|
791
|
+
* balance is at or below the floor, so the user keeps a cushion instead of
|
|
792
|
+
* draining every account to zero.
|
|
793
|
+
*/
|
|
794
|
+
private creditReserves;
|
|
795
|
+
/**
|
|
796
|
+
* Last known credit balance per account, epoch ms aside.
|
|
797
|
+
*
|
|
798
|
+
* Refreshed in the background after a successful request, so a pick can
|
|
799
|
+
* consult it. An account with no reading is treated as usable: refusing to
|
|
800
|
+
* pick an account just because its balance has not been checked yet would
|
|
801
|
+
* strand a healthy pool, and the first 402 still cools it as before.
|
|
802
|
+
*/
|
|
803
|
+
private creditBalances;
|
|
381
804
|
/**
|
|
382
805
|
* Last time each account served a request, epoch ms. Drives the idle term
|
|
383
806
|
* of the priority-mode weighting below: an account that just served loses to
|
|
@@ -401,6 +824,8 @@ export declare class WorkBuddyAccountPool {
|
|
|
401
824
|
exhaustCooldownMs?: number;
|
|
402
825
|
distribution?: AccountDistribution;
|
|
403
826
|
disabledAccountIds?: readonly string[];
|
|
827
|
+
/** Per-account credit floor, keyed by account id. Absent keeps the current map. */
|
|
828
|
+
creditReserves?: Readonly<Record<string, number>>;
|
|
404
829
|
}): void;
|
|
405
830
|
/** Rescan the auth directories and merge newly discovered accounts. */
|
|
406
831
|
scan(): Promise<WorkBuddyAccount[]>;
|
|
@@ -470,6 +895,33 @@ export declare class WorkBuddyAccountPool {
|
|
|
470
895
|
* not count as used for the account that was merely tried.
|
|
471
896
|
*/
|
|
472
897
|
noteServed(accountId: string): void;
|
|
898
|
+
/**
|
|
899
|
+
* Record an account latest known credit balance.
|
|
900
|
+
*
|
|
901
|
+
* Called after a request and by the card balance refresh, so the reserve
|
|
902
|
+
* check has something to compare against. A reading for an unknown account is
|
|
903
|
+
* dropped: `scan()` rebuilds the account list and a stale id would otherwise
|
|
904
|
+
* accumulate forever.
|
|
905
|
+
*/
|
|
906
|
+
noteCredits(accountId: string, balance: number): void;
|
|
907
|
+
/** Last known balance for one account, or undefined when never read. */
|
|
908
|
+
creditsOf(accountId: string): number | undefined;
|
|
909
|
+
/** The credit floor the user set for one account; 0 when unset. */
|
|
910
|
+
creditReserveOf(accountId: string): number;
|
|
911
|
+
/**
|
|
912
|
+
* Replace every reserve. Called from settings on each apply, so the map
|
|
913
|
+
* mirrors the saved document exactly instead of accumulating old keys.
|
|
914
|
+
*/
|
|
915
|
+
setCreditReserves(reserves: Readonly<Record<string, number>>): void;
|
|
916
|
+
/** Every reserve currently in force, keyed by account id. */
|
|
917
|
+
creditReservesInOrder(): Record<string, number>;
|
|
918
|
+
/**
|
|
919
|
+
* Whether an account is held back only by its reserve.
|
|
920
|
+
*
|
|
921
|
+
* Separates "resting to protect credits" from every other reason an account
|
|
922
|
+
* is out of rotation, which is what the card shows the user.
|
|
923
|
+
*/
|
|
924
|
+
isReserved(accountId: string): boolean;
|
|
473
925
|
/**
|
|
474
926
|
* The account that served the most recent request, if any.
|
|
475
927
|
*
|
|
@@ -636,7 +1088,27 @@ interface WorkBuddyAdapter {
|
|
|
636
1088
|
export declare function createWorkBuddyAdapter(options: WorkBuddyAdapterOptions): WorkBuddyAdapter;
|
|
637
1089
|
//#endregion
|
|
638
1090
|
//#region src/scheduler.d.ts
|
|
1091
|
+
/** How often the loop wakes to look for a due job. */
|
|
1092
|
+
/**
|
|
1093
|
+
* How long to wait for event scoring to land before re-reading the task list.
|
|
1094
|
+
*
|
|
1095
|
+
* Scoring is asynchronous on the upstream side, so an immediate re-read still
|
|
1096
|
+
* shows the old progress and the claim pass would skip a task that is in fact
|
|
1097
|
+
* now claimable. Measured: the chain is reflected by about eight seconds.
|
|
1098
|
+
*/
|
|
1099
|
+
export declare const EVENT_SCORE_WAIT_MS = 9000;
|
|
1100
|
+
export declare const AUTOMATION_TICK_MS = 60000;
|
|
639
1101
|
/** Which jobs the automation runs, and when. */
|
|
1102
|
+
/**
|
|
1103
|
+
* The persisted daily earnings ledger.
|
|
1104
|
+
*
|
|
1105
|
+
* `date` is the local day the counters belong to: a ledger from a previous day
|
|
1106
|
+
* is discarded on load rather than carried forward as "today".
|
|
1107
|
+
*/
|
|
1108
|
+
interface AutomationLedger {
|
|
1109
|
+
date: string;
|
|
1110
|
+
accounts: Record<string, AutomationAccountEarnings>;
|
|
1111
|
+
}
|
|
640
1112
|
interface AutomationOptions {
|
|
641
1113
|
/** Master switch; false stops every job. */
|
|
642
1114
|
enabled?: boolean;
|
|
@@ -648,12 +1120,27 @@ interface AutomationOptions {
|
|
|
648
1120
|
reportHours?: readonly number[];
|
|
649
1121
|
/** Hour list for the streak-redemption job. */
|
|
650
1122
|
streakHours?: readonly number[];
|
|
1123
|
+
/** Hour list for the buddy travel job. */
|
|
1124
|
+
travelHours?: readonly number[];
|
|
651
1125
|
/** Per-account serial delay, in milliseconds. */
|
|
652
1126
|
accountDelayMs?: number;
|
|
1127
|
+
/** Override the gap between expert chains, in milliseconds (tests use 0). */
|
|
1128
|
+
expertGapMs?: number;
|
|
1129
|
+
/** Override the event-scoring wait, in milliseconds (tests use 0). */
|
|
1130
|
+
eventScoreWaitMs?: number;
|
|
653
1131
|
/** Override the clock, for tests. */
|
|
654
1132
|
now?: () => Date;
|
|
655
1133
|
/** Logger; defaults to a no-op so tests stay quiet. */
|
|
656
1134
|
logger?: SchedulerLogger;
|
|
1135
|
+
/**
|
|
1136
|
+
* Persist the daily earnings ledger, and restore it on construction.
|
|
1137
|
+
*
|
|
1138
|
+
* The ledger cannot live only in memory: a host restart mid-day would wipe
|
|
1139
|
+
* what the automation already earned, and the card would show nothing for
|
|
1140
|
+
* rewards that really were collected.
|
|
1141
|
+
*/
|
|
1142
|
+
loadEarnings?: () => AutomationLedger | undefined;
|
|
1143
|
+
saveEarnings?: (ledger: AutomationLedger) => void;
|
|
657
1144
|
}
|
|
658
1145
|
/** Logger surface, kept structural so any host logger fits. */
|
|
659
1146
|
interface SchedulerLogger {
|
|
@@ -679,6 +1166,61 @@ interface AutomationJobState {
|
|
|
679
1166
|
/** Human-readable summary of the last run. */
|
|
680
1167
|
message?: string;
|
|
681
1168
|
}
|
|
1169
|
+
/**
|
|
1170
|
+
* What the automation earned for ONE account today.
|
|
1171
|
+
*
|
|
1172
|
+
* Reset at the local day boundary alongside the per-job "already ran today"
|
|
1173
|
+
* guard, so the card shows today rather than a running total that never
|
|
1174
|
+
* answers "did it do anything for this account recently".
|
|
1175
|
+
*/
|
|
1176
|
+
interface AutomationAccountEarnings {
|
|
1177
|
+
/** Credits the automation claimed from the task centre today. */
|
|
1178
|
+
credit: number;
|
|
1179
|
+
/** Energy claimed from the task centre today. */
|
|
1180
|
+
energy: number;
|
|
1181
|
+
/** Tasks claimed today. */
|
|
1182
|
+
claimed: number;
|
|
1183
|
+
/**
|
|
1184
|
+
* Credits the automation collected from check-in today.
|
|
1185
|
+
*
|
|
1186
|
+
* Kept separate from `credit` because they are different achievements and the
|
|
1187
|
+
* card shows them on their own lines: "the automation claimed 3 tasks" and
|
|
1188
|
+
* "the automation checked in" are not the same claim to the user.
|
|
1189
|
+
*/
|
|
1190
|
+
checkinCredit: number;
|
|
1191
|
+
/** Credits from streak redemption + lottery today. */
|
|
1192
|
+
bonusCredit: number;
|
|
1193
|
+
/** Credits from the buddy adoption / travel loop today. */
|
|
1194
|
+
travelCredit: number;
|
|
1195
|
+
/** Local date the counters belong to. */
|
|
1196
|
+
date: string;
|
|
1197
|
+
}
|
|
1198
|
+
/** What ONE account gained during a single run. */
|
|
1199
|
+
interface AutomationAccountGain {
|
|
1200
|
+
credit: number;
|
|
1201
|
+
energy: number;
|
|
1202
|
+
claimed: number;
|
|
1203
|
+
checkinCredit: number;
|
|
1204
|
+
bonusCredit: number;
|
|
1205
|
+
travelCredit: number;
|
|
1206
|
+
}
|
|
1207
|
+
/** Totals from running the whole ordered pass at once. */
|
|
1208
|
+
interface AutomationRunSummary {
|
|
1209
|
+
/** How many jobs actually ran (a job with no hour is skipped). */
|
|
1210
|
+
jobsRun: number;
|
|
1211
|
+
/** Accounts that finished without error, summed across jobs. */
|
|
1212
|
+
okCount: number;
|
|
1213
|
+
/** Accounts that failed, summed across jobs. */
|
|
1214
|
+
failed: number;
|
|
1215
|
+
credit: number;
|
|
1216
|
+
energy: number;
|
|
1217
|
+
claimed: number;
|
|
1218
|
+
/**
|
|
1219
|
+
* What each account gained during THIS run, keyed by account id. Only
|
|
1220
|
+
* accounts that gained something appear.
|
|
1221
|
+
*/
|
|
1222
|
+
accounts: Readonly<Record<string, AutomationAccountGain>>;
|
|
1223
|
+
}
|
|
682
1224
|
/** Automation snapshot for the status document and the card. */
|
|
683
1225
|
interface AutomationStatus {
|
|
684
1226
|
enabled: boolean;
|
|
@@ -688,15 +1230,48 @@ interface AutomationStatus {
|
|
|
688
1230
|
taskHours: readonly number[];
|
|
689
1231
|
reportHours: readonly number[];
|
|
690
1232
|
streakHours: readonly number[];
|
|
1233
|
+
travelHours: readonly number[];
|
|
691
1234
|
jobs: {
|
|
692
1235
|
checkin: AutomationJobState;
|
|
693
1236
|
report: AutomationJobState;
|
|
694
1237
|
tasks: AutomationJobState;
|
|
695
1238
|
streak: AutomationJobState;
|
|
1239
|
+
travel: AutomationJobState;
|
|
696
1240
|
};
|
|
697
1241
|
/** Claimable tasks seen on the most recent task pass, across accounts. */
|
|
698
1242
|
claimableSeen: number;
|
|
1243
|
+
/**
|
|
1244
|
+
* Per-account totals for today, keyed by account id. Only accounts that
|
|
1245
|
+
* actually earned something appear, so the card can render "no earnings"
|
|
1246
|
+
* as an absence rather than a zero it has to explain.
|
|
1247
|
+
*/
|
|
1248
|
+
earningsToday: Readonly<Record<string, AutomationAccountEarnings>>;
|
|
1249
|
+
/**
|
|
1250
|
+
* Whether a manual run is in flight right now.
|
|
1251
|
+
*
|
|
1252
|
+
* A run takes tens of seconds (one upstream call per account per job, plus
|
|
1253
|
+
* the scoring wait), which is far too long for the card to hold a request
|
|
1254
|
+
* open. The button starts a run and the panel polls this flag instead.
|
|
1255
|
+
*/
|
|
1256
|
+
runInProgress: boolean;
|
|
699
1257
|
}
|
|
1258
|
+
/** The three plus one job kinds, in a stable order. */
|
|
1259
|
+
type AutomationJobKind = 'checkin' | 'tasks' | 'report' | 'streak' | 'travel';
|
|
1260
|
+
/** The four jobs in the order a tick runs them: report before tasks, always. */
|
|
1261
|
+
export declare const AUTOMATION_JOB_KINDS: readonly AutomationJobKind[];
|
|
1262
|
+
/** Reject anything that is not a job kind, so a route cannot name an unknown job. */
|
|
1263
|
+
export declare function isAutomationJobKind(value: unknown): value is AutomationJobKind;
|
|
1264
|
+
export declare function dayKey(date: Date): string;
|
|
1265
|
+
/**
|
|
1266
|
+
* Whether `now`'s local hour is one of `hours`.
|
|
1267
|
+
*
|
|
1268
|
+
* The reference panel computes a `nextFire` instant and sleeps until it; this
|
|
1269
|
+
* loop instead wakes every minute and asks "is any job due now". Both fire at
|
|
1270
|
+
* the top of a configured hour, but the polling form cannot miss a slot to a
|
|
1271
|
+
* suspended process — a laptop that slept through 10:00 still runs the job the
|
|
1272
|
+
* moment it wakes, on the same day.
|
|
1273
|
+
*/
|
|
1274
|
+
export declare function isFireHour(now: Date, hours: readonly number[]): boolean;
|
|
700
1275
|
/**
|
|
701
1276
|
* The points automation.
|
|
702
1277
|
*
|
|
@@ -704,21 +1279,34 @@ interface AutomationStatus {
|
|
|
704
1279
|
* {@link start}, and {@link stop} is idempotent so a plugin teardown that fires
|
|
705
1280
|
* twice is harmless.
|
|
706
1281
|
*/
|
|
707
|
-
declare class WorkBuddyScheduler {
|
|
1282
|
+
export declare class WorkBuddyScheduler {
|
|
708
1283
|
private readonly pool;
|
|
709
1284
|
private readonly client;
|
|
710
1285
|
private readonly logger;
|
|
711
1286
|
private readonly now;
|
|
712
1287
|
private readonly delayMs;
|
|
1288
|
+
/**
|
|
1289
|
+
* How long to wait for event scoring before re-reading the task list.
|
|
1290
|
+
* Tests set 0 so a pass does not spend nine real seconds per account.
|
|
1291
|
+
*/
|
|
1292
|
+
private readonly eventScoreWaitMs;
|
|
1293
|
+
/**
|
|
1294
|
+
* Gap between two expert summon chains.
|
|
1295
|
+
* Tests set 0 so a pass does not spend six real seconds per expert.
|
|
1296
|
+
*/
|
|
1297
|
+
private readonly expertGapMs;
|
|
713
1298
|
private enabled;
|
|
714
1299
|
private checkinHours;
|
|
715
1300
|
private taskHours;
|
|
716
1301
|
private reportHours;
|
|
717
1302
|
private streakHours;
|
|
1303
|
+
private travelHours;
|
|
718
1304
|
private timer;
|
|
719
1305
|
private running;
|
|
720
1306
|
/** Guards against a slow run overlapping the next tick. */
|
|
721
1307
|
private busy;
|
|
1308
|
+
/** True while a manual run is in flight, so the card can poll it. */
|
|
1309
|
+
private runInFlight;
|
|
722
1310
|
/**
|
|
723
1311
|
* Set once {@link stop} is called.
|
|
724
1312
|
*
|
|
@@ -729,8 +1317,36 @@ declare class WorkBuddyScheduler {
|
|
|
729
1317
|
private stopped;
|
|
730
1318
|
private readonly states;
|
|
731
1319
|
private claimableSeen;
|
|
1320
|
+
/**
|
|
1321
|
+
* Credits/energy/tasks earned per account TODAY, keyed by account id.
|
|
1322
|
+
*
|
|
1323
|
+
* Cleared whenever the day key rolls over, so the card always answers
|
|
1324
|
+
* "what did the automation get for THIS account today".
|
|
1325
|
+
*/
|
|
1326
|
+
private earnings;
|
|
1327
|
+
/** Day key the counters above belong to. */
|
|
1328
|
+
private earningsDate;
|
|
1329
|
+
/** Host hooks that persist the ledger across restarts. */
|
|
1330
|
+
private readonly loadEarnings;
|
|
1331
|
+
private saveEarningsFn;
|
|
732
1332
|
constructor(pool: WorkBuddyAccountPool, client: WorkBuddyUpstreamClient, options?: AutomationOptions);
|
|
733
1333
|
/** Apply a new configuration; safe to call while running. */
|
|
1334
|
+
/**
|
|
1335
|
+
* Install the persistence hook once the host settings service is available.
|
|
1336
|
+
*
|
|
1337
|
+
* Separate from the constructor because the scheduler is built with the pool,
|
|
1338
|
+
* long before the settings section exists; a ledger written before that point
|
|
1339
|
+
* would have nowhere to go.
|
|
1340
|
+
*/
|
|
1341
|
+
setEarningsPersistence(save: (ledger: AutomationLedger) => void): void;
|
|
1342
|
+
/**
|
|
1343
|
+
* Fold a previously persisted ledger back in, when it belongs to today.
|
|
1344
|
+
*
|
|
1345
|
+
* Used after the settings document becomes readable, which happens after
|
|
1346
|
+
* construction; a ledger from an earlier day is ignored so the counters never
|
|
1347
|
+
* claim yesterday as today.
|
|
1348
|
+
*/
|
|
1349
|
+
applyEarningsLedger(ledger: AutomationLedger): void;
|
|
734
1350
|
applyConfig(options: AutomationOptions): void;
|
|
735
1351
|
/** Hours for one job, used by the loop and the status document. */
|
|
736
1352
|
private hoursOf;
|
|
@@ -739,6 +1355,39 @@ declare class WorkBuddyScheduler {
|
|
|
739
1355
|
/** Stop the loop. Idempotent, and safe before `start`. */
|
|
740
1356
|
stop(): void;
|
|
741
1357
|
/** Snapshot for the status document. */
|
|
1358
|
+
/**
|
|
1359
|
+
* Run one job immediately, regardless of the clock.
|
|
1360
|
+
*
|
|
1361
|
+
* Exists so the automation can be verified from the card without waiting for
|
|
1362
|
+
* its hour. A manual run is recorded exactly like a scheduled one, so the
|
|
1363
|
+
* timer will not repeat it later the same day: every job is idempotent, but a
|
|
1364
|
+
* second pass would still be wasted upstream calls.
|
|
1365
|
+
*
|
|
1366
|
+
* `force` ignores the already-ran-today guard, which is what pressing the
|
|
1367
|
+
* button a second time means.
|
|
1368
|
+
*/
|
|
1369
|
+
runNow(kind: AutomationJobKind, force?: boolean): Promise<AutomationJobState>;
|
|
1370
|
+
/**
|
|
1371
|
+
* Run every job once, in the scheduled order.
|
|
1372
|
+
*
|
|
1373
|
+
* Order matters and is not configurable: the activity report has to land
|
|
1374
|
+
* before the task pass reads task progress, or the pass sees counters the
|
|
1375
|
+
* report would have moved. This is what the card's single button calls.
|
|
1376
|
+
*/
|
|
1377
|
+
/**
|
|
1378
|
+
* Start a full pass in the background and return immediately.
|
|
1379
|
+
*
|
|
1380
|
+
* A pass takes tens of seconds - one upstream round trip per account per job,
|
|
1381
|
+
* plus the scoring wait - which is far too long to hold the card request open:
|
|
1382
|
+
* the browser or the host web server would time out, and the user would see
|
|
1383
|
+
* a hung button for a run that is actually working.
|
|
1384
|
+
*
|
|
1385
|
+
* Returns whether a run started. A second call while one is in flight is
|
|
1386
|
+
* ignored rather than queued: pressing the button twice means hurry up, and
|
|
1387
|
+
* the run already under way covers it.
|
|
1388
|
+
*/
|
|
1389
|
+
startRunAll(): boolean;
|
|
1390
|
+
runAll(): Promise<AutomationRunSummary>;
|
|
742
1391
|
status(): AutomationStatus;
|
|
743
1392
|
/**
|
|
744
1393
|
* One poll: run every due job, serially.
|
|
@@ -749,6 +1398,43 @@ declare class WorkBuddyScheduler {
|
|
|
749
1398
|
*/
|
|
750
1399
|
private tick;
|
|
751
1400
|
/** Run one job against every eligible account and record the outcome. */
|
|
1401
|
+
/**
|
|
1402
|
+
* Add one account's take to today's counters, resetting first if the day
|
|
1403
|
+
* rolled over. Called from the task pass, which is the only job that earns.
|
|
1404
|
+
*/
|
|
1405
|
+
/**
|
|
1406
|
+
* Today's per-account earnings, as a plain object for the status document.
|
|
1407
|
+
*
|
|
1408
|
+
* Rolls the day first so a status read just after midnight does not report
|
|
1409
|
+
* yesterday's totals under today's date.
|
|
1410
|
+
*/
|
|
1411
|
+
private earningsSnapshot;
|
|
1412
|
+
/**
|
|
1413
|
+
* Add one account take to today counters, resetting first if the day rolled
|
|
1414
|
+
* over. Every source is tracked separately so the card can show what earned
|
|
1415
|
+
* what, rather than one opaque total.
|
|
1416
|
+
*/
|
|
1417
|
+
private recordEarnings;
|
|
1418
|
+
/** Clear the per-account counters when the local day changes. */
|
|
1419
|
+
private rollEarnings;
|
|
1420
|
+
/**
|
|
1421
|
+
* Write the ledger through the host hook, when one was supplied.
|
|
1422
|
+
*
|
|
1423
|
+
* Best effort on purpose: a failed save must never abort a run that has
|
|
1424
|
+
* already collected rewards, and the in-memory ledger keeps serving the card
|
|
1425
|
+
* for the rest of the session either way.
|
|
1426
|
+
*/
|
|
1427
|
+
private persistEarnings;
|
|
1428
|
+
/**
|
|
1429
|
+
* Run one job against every eligible account and record the outcome.
|
|
1430
|
+
*
|
|
1431
|
+
* The task job runs in TWO passes. The first sends the event chains that light
|
|
1432
|
+
* up client-scored tasks; the second collects rewards. They are separate
|
|
1433
|
+
* because scoring lands asynchronously — a chain sent and claimed within the
|
|
1434
|
+
* same breath finds the task still un-scored — and because sending is fast
|
|
1435
|
+
* while claiming wants the whole pool to have been lit up first. Splitting
|
|
1436
|
+
* them costs one shared wait instead of one wait per account.
|
|
1437
|
+
*/
|
|
752
1438
|
private runJob;
|
|
753
1439
|
/** Compose the one-line summary shown on the card. */
|
|
754
1440
|
private summarise;
|
|
@@ -769,6 +1455,42 @@ declare class WorkBuddyScheduler {
|
|
|
769
1455
|
* exists to avoid.
|
|
770
1456
|
*/
|
|
771
1457
|
private reportOne;
|
|
1458
|
+
private sendEventChains;
|
|
1459
|
+
/**
|
|
1460
|
+
* Send one chain on the channel it was built for.
|
|
1461
|
+
*
|
|
1462
|
+
* The transport is not a detail of the sender: the scorer keys different
|
|
1463
|
+
* tasks to different fingerprint families, so a web-scored event posted as a
|
|
1464
|
+
* desktop event is accepted and then ignored.
|
|
1465
|
+
*/
|
|
1466
|
+
private sendChain;
|
|
1467
|
+
/**
|
|
1468
|
+
* Build every chain that scores one task.
|
|
1469
|
+
*
|
|
1470
|
+
* Most tasks need a single chain; `template_5` needs five, because the scorer
|
|
1471
|
+
* counts distinct `template_used` events rather than a boolean. The two tasks
|
|
1472
|
+
* that join a conversation (skill, expert) open a real one first, which is why
|
|
1473
|
+
* this is async.
|
|
1474
|
+
*/
|
|
1475
|
+
private chainsFor;
|
|
1476
|
+
/**
|
|
1477
|
+
* The summon-and-use chains for the expert tasks.
|
|
1478
|
+
*
|
|
1479
|
+
* Two steps per expert, and both are load-bearing: the summon events alone are
|
|
1480
|
+
* impressions, and a use event on its own scores nothing because the scorer
|
|
1481
|
+
* looks the conversation up. Only a real chat with `X-Expert-Id` produces an
|
|
1482
|
+
* id it will accept.
|
|
1483
|
+
*/
|
|
1484
|
+
private expertChains;
|
|
1485
|
+
/**
|
|
1486
|
+
* The 腾讯轻量云 expert chain.
|
|
1487
|
+
*
|
|
1488
|
+
* Structurally the same as the expert task, with two differences the scorer
|
|
1489
|
+
* checks: `agent_task_created` has to name the expert, and the use event has
|
|
1490
|
+
* to report `mode: 'LOCAL'` with an empty type and zero cost — that is what
|
|
1491
|
+
* the lighthouse criterion looks for.
|
|
1492
|
+
*/
|
|
1493
|
+
private lighthouseChains;
|
|
772
1494
|
/**
|
|
773
1495
|
* The task-centre pass for one account.
|
|
774
1496
|
*
|
|
@@ -779,15 +1501,28 @@ declare class WorkBuddyScheduler {
|
|
|
779
1501
|
*/
|
|
780
1502
|
private runTasks;
|
|
781
1503
|
/**
|
|
782
|
-
* Streak redemption
|
|
1504
|
+
* Streak redemption plus the lottery it unlocks.
|
|
1505
|
+
*
|
|
1506
|
+
* Tiers unlock on consecutive active days (7/14/28). Redeeming one pays
|
|
1507
|
+
* credits, energy, a makeup card and — the part nothing else grants — lottery
|
|
1508
|
+
* draws, so the draw runs straight after and only for the chances in hand.
|
|
783
1509
|
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
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.
|
|
1510
|
+
* Everything here is idempotent: a tier already claimed is skipped by its
|
|
1511
|
+
* status, and a draw consumes one chance, so a replay cannot double-spend.
|
|
789
1512
|
*/
|
|
790
1513
|
private redeemStreak;
|
|
1514
|
+
/**
|
|
1515
|
+
* One trip through the buddy travel loop for an account.
|
|
1516
|
+
*
|
|
1517
|
+
* A single pass advances the state machine by at most one step: a trip
|
|
1518
|
+
* that has arrived is collected, and an idle buddy is sent out. A buddy
|
|
1519
|
+
* already travelling is left alone — there is nothing to do until it lands.
|
|
1520
|
+
*
|
|
1521
|
+
* Measured against the live upstream: the departed trip reports
|
|
1522
|
+
* `dailyLimitReached` immediately, so the once-a-day limit needs no local
|
|
1523
|
+
* bookkeeping.
|
|
1524
|
+
*/
|
|
1525
|
+
private runTravel;
|
|
791
1526
|
}
|
|
792
1527
|
//#endregion
|
|
793
1528
|
//#region src/status.d.ts
|
|
@@ -873,6 +1608,10 @@ export declare const POOL_RESET_COOLDOWN_PATH = "/plugins/dsh-workbuddy-xdpool/c
|
|
|
873
1608
|
export declare const POOL_CHECKIN_PATH = "/plugins/dsh-workbuddy-xdpool/checkin";
|
|
874
1609
|
/** Plugin-owned model-selection save endpoint (writes the settings section). */
|
|
875
1610
|
export declare const POOL_MODELS_SAVE_PATH = "/plugins/dsh-workbuddy-xdpool/models/save";
|
|
1611
|
+
/** Run one automation job immediately, so the card can verify it on demand. */
|
|
1612
|
+
export declare const POOL_AUTOMATION_RUN_PATH = "/plugins/dsh-workbuddy-xdpool/automation/run";
|
|
1613
|
+
/** Set or clear one account's reserved-credit floor. */
|
|
1614
|
+
export declare const POOL_CREDIT_RESERVE_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/credit-reserve";
|
|
876
1615
|
/** One pool account's row, token-free. */
|
|
877
1616
|
interface PoolWebAccount {
|
|
878
1617
|
id: string;
|
|
@@ -901,6 +1640,24 @@ interface PoolWebAccount {
|
|
|
901
1640
|
*/
|
|
902
1641
|
disabled: boolean;
|
|
903
1642
|
rateLimitHits: number;
|
|
1643
|
+
/**
|
|
1644
|
+
* Credits the user asked to keep for this account. The pool stops picking the
|
|
1645
|
+
* account once its balance reaches the reserve, so this many credits survive.
|
|
1646
|
+
* 0 means the account may be spent down as before.
|
|
1647
|
+
*/
|
|
1648
|
+
creditReserve: number;
|
|
1649
|
+
/**
|
|
1650
|
+
* Whether the account is held back purely by its reserve right now. Kept
|
|
1651
|
+
* distinct from `cooling`: a reserved account is healthy and simply
|
|
1652
|
+
* protected, which is a different thing to tell the user than rate-limited.
|
|
1653
|
+
*/
|
|
1654
|
+
reserved: boolean;
|
|
1655
|
+
/**
|
|
1656
|
+
* What the automation earned for this account today. Absent when it earned
|
|
1657
|
+
* nothing (or the automation never ran for it), so the card can stay quiet
|
|
1658
|
+
* instead of printing a row of zeroes.
|
|
1659
|
+
*/
|
|
1660
|
+
automationToday?: PoolWebAutomationEarnings;
|
|
904
1661
|
/** ISO timestamp of the last successful use (best-effort pool bookkeeping). */
|
|
905
1662
|
lastUsedAt?: string;
|
|
906
1663
|
/** Aggregated credit summary for the account, read-only. */
|
|
@@ -1018,6 +1775,8 @@ interface PoolWebStatus {
|
|
|
1018
1775
|
};
|
|
1019
1776
|
/** Daily-points automation state, so the card can show what ran and when. */
|
|
1020
1777
|
automation: PoolWebAutomation;
|
|
1778
|
+
/** Per-account credit floors currently in force, keyed by account id. */
|
|
1779
|
+
creditReserves: Readonly<Record<string, number>>;
|
|
1021
1780
|
}
|
|
1022
1781
|
/** One automation job's last run, as shown on the card. */
|
|
1023
1782
|
interface PoolWebAutomationJob {
|
|
@@ -1058,9 +1817,38 @@ interface PoolWebAutomation {
|
|
|
1058
1817
|
report: PoolWebAutomationJob;
|
|
1059
1818
|
tasks: PoolWebAutomationJob;
|
|
1060
1819
|
streak: PoolWebAutomationJob;
|
|
1820
|
+
travel: PoolWebAutomationJob;
|
|
1061
1821
|
};
|
|
1062
1822
|
/** Claimable tasks seen on the most recent task pass, across accounts. */
|
|
1063
1823
|
claimableSeen: number;
|
|
1824
|
+
/**
|
|
1825
|
+
* Whether a manual run is in flight. The card polls this to know when to
|
|
1826
|
+
* stop showing progress and report the result.
|
|
1827
|
+
*/
|
|
1828
|
+
runInProgress: boolean;
|
|
1829
|
+
/**
|
|
1830
|
+
* Per-account credits/energy/tasks the automation earned TODAY, keyed by
|
|
1831
|
+
* account id. An account that earned nothing is simply absent, so the card
|
|
1832
|
+
* can say "nothing yet" instead of showing a bare zero.
|
|
1833
|
+
*/
|
|
1834
|
+
earningsToday: Readonly<Record<string, PoolWebAutomationEarnings>>;
|
|
1835
|
+
}
|
|
1836
|
+
/** Today's automation take for one account. */
|
|
1837
|
+
interface PoolWebAutomationEarnings {
|
|
1838
|
+
/** Credits claimed from the task centre today. */
|
|
1839
|
+
credit: number;
|
|
1840
|
+
/** Energy claimed from the task centre today. */
|
|
1841
|
+
energy: number;
|
|
1842
|
+
/** Tasks claimed today. */
|
|
1843
|
+
claimed: number;
|
|
1844
|
+
/** Credits collected from check-in today. */
|
|
1845
|
+
checkinCredit: number;
|
|
1846
|
+
/** Credits from streak redemption and the lottery today. */
|
|
1847
|
+
bonusCredit: number;
|
|
1848
|
+
/** Credits from buddy adoption and the travel loop today. */
|
|
1849
|
+
travelCredit: number;
|
|
1850
|
+
/** Local date the counters belong to (YYYY-MM-DD). */
|
|
1851
|
+
date: string;
|
|
1064
1852
|
}
|
|
1065
1853
|
/**
|
|
1066
1854
|
* The two gateways, matching the provider ids the host registers. `cn` is the
|
|
@@ -1071,6 +1859,72 @@ type PoolRegion = 'cn' | 'global';
|
|
|
1071
1859
|
/** How the pool spreads requests across its accounts. */
|
|
1072
1860
|
type PoolDistribution = 'priority' | 'round-robin' | 'balanced';
|
|
1073
1861
|
//#endregion
|
|
1862
|
+
//#region src/web-status.d.ts
|
|
1863
|
+
/** Constructor dependencies — a narrow slice of the pool runtime. */
|
|
1864
|
+
interface PoolStatusRouteOptions {
|
|
1865
|
+
pool: WorkBuddyAccountPool;
|
|
1866
|
+
/** One catalog per region; the card reads the one for its active tab. */
|
|
1867
|
+
catalogs: Readonly<Record<PoolRegion, WorkBuddyCatalog>>;
|
|
1868
|
+
client: WorkBuddyUpstreamClient;
|
|
1869
|
+
/** Lazily resolve the running loopback shim, when it has bound a port. */
|
|
1870
|
+
shim?: () => {
|
|
1871
|
+
running: boolean;
|
|
1872
|
+
baseUrl?: string;
|
|
1873
|
+
};
|
|
1874
|
+
/**
|
|
1875
|
+
* The daily-points automation, when the host half has one.
|
|
1876
|
+
*
|
|
1877
|
+
* Optional so these routes still mount on a profile that assembled a pool
|
|
1878
|
+
* without a scheduler (the CLI and the tests do exactly that); the card then
|
|
1879
|
+
* reports the automation as off instead of showing a broken panel.
|
|
1880
|
+
*/
|
|
1881
|
+
scheduler?: () => AutomationStatus;
|
|
1882
|
+
/**
|
|
1883
|
+
/**
|
|
1884
|
+
* Start a manual pass, for the card's "run now" button.
|
|
1885
|
+
*
|
|
1886
|
+
* Returns whether a run STARTED, not its result: a pass takes tens of
|
|
1887
|
+
* seconds, so it runs in the background and the card polls the scheduler
|
|
1888
|
+
* status for progress and earnings.
|
|
1889
|
+
*/
|
|
1890
|
+
runAutomation?: (job: string, force: boolean) => boolean;
|
|
1891
|
+
/**
|
|
1892
|
+
* Persist one account's reserved-credit floor. Same settings document as every
|
|
1893
|
+
* other card write, so it survives a restart and is re-applied after a scan.
|
|
1894
|
+
* Absent without a settings service: the route then answers 503.
|
|
1895
|
+
*/
|
|
1896
|
+
setCreditReserve?: (accountId: string, reserve: number) => Promise<void> | void;
|
|
1897
|
+
/**
|
|
1898
|
+
* Persist the user's model selection. Provided by the host half, which owns
|
|
1899
|
+
* the settings section; absent when the plugin runs without a settings
|
|
1900
|
+
* service (the save route then reports 503 rather than pretending to work).
|
|
1901
|
+
*/
|
|
1902
|
+
/**
|
|
1903
|
+
* Persist one region's selection. The region travels with the payload: the two
|
|
1904
|
+
* gateways advertise different rosters, so the domestic tab and the
|
|
1905
|
+
* international tab each own their list and must never overwrite each other.
|
|
1906
|
+
*/
|
|
1907
|
+
saveSelection?: (region: PoolRegion, selection: PoolWebModelSelection) => Promise<void> | void;
|
|
1908
|
+
/**
|
|
1909
|
+
* Persist one account switch. Same settings document as every other card
|
|
1910
|
+
* write, so it survives a restart and is re-applied after each re-scan.
|
|
1911
|
+
* Absent without a settings service: the route then answers 503.
|
|
1912
|
+
*/
|
|
1913
|
+
setAccountDisabled?: (accountId: string, disabled: boolean) => Promise<void> | void;
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Assemble the card's status document. Per-account credits and check-in state
|
|
1917
|
+
* are queried live; a failing query degrades to `creditsError` / `checkinError`
|
|
1918
|
+
* rather than failing the whole document. Never throws.
|
|
1919
|
+
*/
|
|
1920
|
+
export declare function poolWebStatus(deps: PoolStatusRouteOptions, region?: PoolRegion): Promise<PoolWebStatus>;
|
|
1921
|
+
/**
|
|
1922
|
+
* Mount the read-only routes on a context where `webServer` is available. The
|
|
1923
|
+
* caller uses `ctx.inject(['webServer'], ...)` so Desktop startup order cannot
|
|
1924
|
+
* make this registration disappear.
|
|
1925
|
+
*/
|
|
1926
|
+
export declare function registerPoolStatusRoute(ctx: Context$1, deps: PoolStatusRouteOptions): void;
|
|
1927
|
+
//#endregion
|
|
1074
1928
|
//#region src/index.d.ts
|
|
1075
1929
|
/** Stable Cordis plugin name. */
|
|
1076
1930
|
export declare const name = "llm-workbuddy-xdpool";
|
|
@@ -1092,11 +1946,16 @@ export interface Config {
|
|
|
1092
1946
|
/**
|
|
1093
1947
|
* How the pool spreads requests across accounts.
|
|
1094
1948
|
*
|
|
1095
|
-
* `priority` (default) drains one account before moving to the next, which
|
|
1096
|
-
*
|
|
1097
|
-
*
|
|
1949
|
+
* - `priority` (default) drains one account before moving to the next, which
|
|
1950
|
+
* is what a pool of your own accounts is for.
|
|
1951
|
+
* - `round-robin` walks the pool in order, so the spend splits evenly.
|
|
1952
|
+
* - `balanced` draws at random, weighting whichever account has been idle
|
|
1953
|
+
* longest. Spend still spreads, but without a fixed order, so one unhealthy
|
|
1954
|
+
* account cannot pin the pool to itself.
|
|
1955
|
+
*
|
|
1956
|
+
* Absent reads as `priority`.
|
|
1098
1957
|
*/
|
|
1099
|
-
distribution?: 'priority' | 'round-robin';
|
|
1958
|
+
distribution?: 'priority' | 'round-robin' | 'balanced';
|
|
1100
1959
|
/**
|
|
1101
1960
|
* Account ids switched off on the card. A disabled account is never picked
|
|
1102
1961
|
* to serve a request, but it stays in the pool and on the card so it can be
|
|
@@ -1104,6 +1963,12 @@ export interface Config {
|
|
|
1104
1963
|
* survive re-scans (see WorkBuddyAccountPool.disabledIds).
|
|
1105
1964
|
*/
|
|
1106
1965
|
disabledAccountIds?: string[];
|
|
1966
|
+
/**
|
|
1967
|
+
* Per-account credit floor, keyed by account id. The pool stops picking an
|
|
1968
|
+
* account once its last known balance reaches this value, so the reserved
|
|
1969
|
+
* credits survive. Absent or 0 spends the account down as before.
|
|
1970
|
+
*/
|
|
1971
|
+
creditReserves?: Record<string, number>;
|
|
1107
1972
|
/**
|
|
1108
1973
|
* Model ids enabled in the picker. Absent means "every model the catalog
|
|
1109
1974
|
* advertises" — an unconfigured install should never present an empty model
|
|
@@ -1136,6 +2001,13 @@ export interface Config {
|
|
|
1136
2001
|
* fresh install with background traffic.
|
|
1137
2002
|
*/
|
|
1138
2003
|
automation?: AutomationConfig;
|
|
2004
|
+
/**
|
|
2005
|
+
* The automation's daily earnings ledger, written by the scheduler itself.
|
|
2006
|
+
*
|
|
2007
|
+
* It lives in settings rather than only in memory so a host restart mid-day
|
|
2008
|
+
* does not wipe what the automation already earned.
|
|
2009
|
+
*/
|
|
2010
|
+
automationEarnings?: AutomationLedger;
|
|
1139
2011
|
}
|
|
1140
2012
|
/** One region's saved model selection. */
|
|
1141
2013
|
export interface ModelSelectionConfig {
|
|
@@ -1166,6 +2038,8 @@ export interface AutomationConfig {
|
|
|
1166
2038
|
taskHours?: number[];
|
|
1167
2039
|
/** Hours at which streak redemption runs. */
|
|
1168
2040
|
streakHours?: number[];
|
|
2041
|
+
/** Hours at which the buddy travel loop runs. */
|
|
2042
|
+
travelHours?: number[];
|
|
1169
2043
|
/** How long an account rests after its credits run out, in milliseconds. */
|
|
1170
2044
|
exhaustCooldownMs?: number;
|
|
1171
2045
|
}
|
|
@@ -1238,4 +2112,4 @@ export declare function createCore(logger?: {
|
|
|
1238
2112
|
*/
|
|
1239
2113
|
export declare function apply(ctx: Context, config?: Config): void;
|
|
1240
2114
|
//#endregion
|
|
1241
|
-
export type { AccountStatus, Context, ModelSelection, PoolWebCheckin, PoolWebCheckinClaim, PoolWebModel, PoolWebModelSelection, PoolWebStatus, UpstreamErrorKind, WorkBuddyAccount, WorkBuddyAdapter, WorkBuddyCredential, WorkBuddyModelInfo, WorkBuddyShim, WorkBuddyStatus };
|
|
2115
|
+
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 };
|