dsh-xdpool 2.2.1 → 2.3.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 +19 -0
- package/lib/bin.js +160 -8
- package/lib/client.js +7395 -231
- package/lib/index.d.ts +157 -6
- package/lib/index.js +3425 -2633
- package/package.json +1 -1
- package/vendor/channel-pack/pack.js +489 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
本项目的所有重要变更都会记录在此文件。
|
|
3
3
|
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
4
|
+
## 2.3.0 — 扫码渠道接入账号池,免密车道修复,数据看板有了真数
|
|
5
|
+
> 这一版收掉三件大事:扫码登录的账号真正归入账号池并按渠道展示;免费模型车道跟上上游的新校验、不再动辄 403;数据看板不再是一排零。
|
|
6
|
+
### 账号池:扫码渠道按渠道入池、逐模型可控
|
|
7
|
+
**账号池页新增了扫码渠道卡**。此前扫码登录页加的账号游离在账号池之外——账号池只认 WorkBuddy 桌面端登录的账号,扫码加进来的十几个渠道账号在池里没有一席之地,模型开不开、哪个开,全靠猜。现在每家扫码渠道在账号池页有一张自己的卡:账号列表(昵称、启停、到期、余额)、模型列表(逐个勾选启用、免费/失效标签、上下文大小选择)、每日签到与积分领取。改动即时生效,不必点保存。
|
|
8
|
+
配套的 RPC 通道一并补齐:前端与扫码渠道后端之间的账号、模型、积分、启停等请求全部走同一封装(`channel-rpc.ts`),新增 `model.setDisabledMany`、`model.setContextBudget` 等方法,保证卡片上看到的和后端真实状态一致。
|
|
9
|
+
**重复账号自动清理**:同一渠道反复扫码不再攒出一排半成品账号——按「可用性」保留(长期凭据优先于临期凭据),其余清掉,日志里写明留了谁删了谁。
|
|
10
|
+
### 停机补偿:自动领取
|
|
11
|
+
WorkBuddy 侧偶发的模型服务异常会按账号补偿积分(在网页「用量」页顶部领,过期作废)。现在自动任务多了一项「停机补偿」,默认每天下午检查一次,有就领、领完记账,账单里能看到「补偿 +N 积分」。
|
|
12
|
+
### 免费模型车道:跟上上游的客户端校验
|
|
13
|
+
免费模型上游在 10 月中旬收紧了两道校验,旧版几乎每次调用都撞 403:
|
|
14
|
+
- **会话 id 编码**:上游要求会话 id 按规范的降序时间戳编码(低 12 位落在固定区间),旧实现用哈希摘要当前缀,合规概率只有约 1.5%。现在改为确定性派生规范 id——同一会话稳定映射到同一上游会话,配额不会被反复消耗。
|
|
15
|
+
- **客户端指纹**:请求头、路径、工具声明的形状全部对齐当前 CLI 客户端(v2 形状),并用真实网关验证过:免费模型能正常返回内容,超限时明确报限流而不是 403 拒绝。
|
|
16
|
+
### 数据看板:不再是一排零
|
|
17
|
+
免密通道的调用此前从未接入用量记录器,看板上免费模型的调用数恒为 0、重启即清空。现在每次调用(无论成败)都会记录请求数、轮次、首字延迟、生成速度;Token 总量取决于上游是否回传用量帧,不回传时如实显示「未上报」而不是编一个数。
|
|
18
|
+
### 其他
|
|
19
|
+
- 聚合渠道页的模型按「免费优先、上下文窗口、识图能力」排序;能力未知的模型如实留空,不编造窗口数字。
|
|
20
|
+
- 渠道卡模型行在模型较多时提供搜索框;按钮配色统一为中性色,hover 有扫光动效。
|
|
21
|
+
- 重复的登录入口合并;「已添加 N 个账号」的计数修正为去重后的真实数量。
|
|
22
|
+
- 新增 5 个测试文件(免密车道闸门、扫码账号接入、RPC 方法清单、模型选择、客户端 bundle 纯净度),全套 68 文件 895 用例。
|
|
4
23
|
## 2.2.1 — README 已知限制描述更正
|
|
5
24
|
纯文档修正:README「已知限制」里写着池「没法替你发起登录或扫码」——这是改名前的旧描述。现在的 XD Pool 有扫码登录页,可以为自己十三家渠道发起扫码登录;WorkBuddy 本体的 token 仍由桌面 App 的腾讯 SSO 铸造并绑定设备,这条限制保留但改成了准确的说法。代码无变化,发布这一版只为让 npm 页面的 README 跟上。
|
|
6
25
|
## 2.2.0 — 账号池重启不再失忆,CLI 修好,快照真正回读
|
package/lib/bin.js
CHANGED
|
@@ -161,6 +161,31 @@ const SESSION_DEAD_MARKERS = [
|
|
|
161
161
|
*/
|
|
162
162
|
const ALREADY_CHECKIN_MARKERS = ["已签到", "already"];
|
|
163
163
|
/**
|
|
164
|
+
* Markers for "there is no compensation offer to collect".
|
|
165
|
+
*
|
|
166
|
+
* The offer is seasonal — an outage announcement opens one, and it closes on a
|
|
167
|
+
* deadline — so for most of the year every account answers a claim with a
|
|
168
|
+
* refusal. That is a successful no-op, not a failure, for the same reason
|
|
169
|
+
* `isAlreadyCheckin` exists.
|
|
170
|
+
*/
|
|
171
|
+
const NO_COMPENSATION_MARKERS = [
|
|
172
|
+
"没有可领取",
|
|
173
|
+
"暂无可领取",
|
|
174
|
+
"无可领取",
|
|
175
|
+
"不可领取",
|
|
176
|
+
"未开启",
|
|
177
|
+
"没有活动",
|
|
178
|
+
"已领取",
|
|
179
|
+
"已过期",
|
|
180
|
+
"活动已结束",
|
|
181
|
+
"已结束",
|
|
182
|
+
"not eligible",
|
|
183
|
+
"no compensation",
|
|
184
|
+
"already claimed",
|
|
185
|
+
"not found",
|
|
186
|
+
"expired"
|
|
187
|
+
];
|
|
188
|
+
/**
|
|
164
189
|
* Hosts the international product answers on, once each has been stripped of a
|
|
165
190
|
* leading label. The WorkBuddy AI desktop app signs in at `workbuddy.ai` (and
|
|
166
191
|
* the desktop client itself lists `workbuddy.cc` alongside it); the CodeBuddy
|
|
@@ -456,6 +481,16 @@ function isAlreadyCheckin(error) {
|
|
|
456
481
|
const message = error instanceof Error ? error.message : String(error);
|
|
457
482
|
return ALREADY_CHECKIN_MARKERS.some((marker) => message.includes(marker));
|
|
458
483
|
}
|
|
484
|
+
/**
|
|
485
|
+
* Whether an error means "no compensation offer is running for this account".
|
|
486
|
+
*
|
|
487
|
+
* Callers treat this as a successful no-op: the job did its job and correctly
|
|
488
|
+
* found nothing to collect.
|
|
489
|
+
*/
|
|
490
|
+
function isNoCompensation(error) {
|
|
491
|
+
const lower = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
492
|
+
return NO_COMPENSATION_MARKERS.some((marker) => lower.includes(marker.toLowerCase()));
|
|
493
|
+
}
|
|
459
494
|
/** Parse the upstream's `credits` string into a multiplier. */
|
|
460
495
|
function parseCreditMultiplier(value) {
|
|
461
496
|
if (typeof value !== "string") return void 0;
|
|
@@ -1112,6 +1147,52 @@ var WorkBuddyUpstreamClient = class {
|
|
|
1112
1147
|
};
|
|
1113
1148
|
}
|
|
1114
1149
|
/**
|
|
1150
|
+
* Read the service-outage compensation offer, if any.
|
|
1151
|
+
*
|
|
1152
|
+
* NOTE the missing `/v2`: unlike the check-in routes, this one lives at
|
|
1153
|
+
* `/billing/meter/compensation-status` and the `/v2` variant answers a plain
|
|
1154
|
+
* 404 (verified on both billing hosts). The web client calls it that way too.
|
|
1155
|
+
*/
|
|
1156
|
+
async fetchCompensationStatus(credential) {
|
|
1157
|
+
const response = await this.fetchImpl(`${billingBase(credential)}/billing/meter/compensation-status`, {
|
|
1158
|
+
method: "POST",
|
|
1159
|
+
headers: billingHeaders(credential),
|
|
1160
|
+
body: "{}",
|
|
1161
|
+
signal: AbortSignal.timeout(JSON_TIMEOUT_MS)
|
|
1162
|
+
});
|
|
1163
|
+
const envelope = await readEnvelope(response);
|
|
1164
|
+
if (!response.ok || envelope.code !== 0) throw envelopeError(response.status, envelope);
|
|
1165
|
+
const data = typeof envelope.data === "object" && envelope.data !== null ? envelope.data : {};
|
|
1166
|
+
return {
|
|
1167
|
+
active: data["active"] === true,
|
|
1168
|
+
claimed: data["claimed"] === true,
|
|
1169
|
+
creditNum: typeof data["credit_num"] === "number" ? data["credit_num"] : 0,
|
|
1170
|
+
...typeof data["end_time"] === "string" && data["end_time"] !== "" ? { endTime: data["end_time"] } : {}
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Collect the running compensation offer.
|
|
1175
|
+
*
|
|
1176
|
+
* Idempotent upstream: a repeat claim answers `code: 0` with
|
|
1177
|
+
* `claimed: true` rather than an error, so a second pass in the same day
|
|
1178
|
+
* records as a no-op instead of a failure.
|
|
1179
|
+
*/
|
|
1180
|
+
async claimCompensation(credential) {
|
|
1181
|
+
const response = await this.fetchImpl(`${billingBase(credential)}/billing/meter/claim-compensation`, {
|
|
1182
|
+
method: "POST",
|
|
1183
|
+
headers: billingHeaders(credential),
|
|
1184
|
+
body: "{}",
|
|
1185
|
+
signal: AbortSignal.timeout(JSON_TIMEOUT_MS)
|
|
1186
|
+
});
|
|
1187
|
+
const envelope = await readEnvelope(response);
|
|
1188
|
+
if (!response.ok || envelope.code !== 0) throw envelopeError(response.status, envelope);
|
|
1189
|
+
const data = typeof envelope.data === "object" && envelope.data !== null ? envelope.data : {};
|
|
1190
|
+
return {
|
|
1191
|
+
claimed: data["claimed"] !== false,
|
|
1192
|
+
creditNum: typeof data["credit_num"] === "number" ? data["credit_num"] : 0
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1115
1196
|
* Public fingerprint fields every desktop event carries.
|
|
1116
1197
|
*
|
|
1117
1198
|
* The upstream scores a desktop-fingerprinted task only when the event looks
|
|
@@ -3120,6 +3201,15 @@ var WorkBuddyAccountPool = class {
|
|
|
3120
3201
|
persistent;
|
|
3121
3202
|
/** Path of the durable store; computed once so tests can pin it. */
|
|
3122
3203
|
storePath;
|
|
3204
|
+
/**
|
|
3205
|
+
* Optional second source of credentials: the channel pack's account pool,
|
|
3206
|
+
* which holds the accounts the user signed in through the scan-login page.
|
|
3207
|
+
*
|
|
3208
|
+
* A function rather than a list because that pool is mounted asynchronously
|
|
3209
|
+
* and can be re-read on demand; `undefined` means "no such source", which is
|
|
3210
|
+
* the state every test and every pre-mount startup is in.
|
|
3211
|
+
*/
|
|
3212
|
+
externalSource;
|
|
3123
3213
|
constructor(options = {}) {
|
|
3124
3214
|
this.logger = options.logger;
|
|
3125
3215
|
this.authDirs = options.authDirs ?? candidateAuthDirs();
|
|
@@ -3146,6 +3236,14 @@ var WorkBuddyAccountPool = class {
|
|
|
3146
3236
|
if (options.creditReserves !== void 0) this.setCreditReserves(options.creditReserves);
|
|
3147
3237
|
}
|
|
3148
3238
|
/**
|
|
3239
|
+
* Register (or clear, with `undefined`) the extra credential source that
|
|
3240
|
+
* `scan()` merges alongside the auth directories. Rescanning is the caller's
|
|
3241
|
+
* job: this only changes where the next scan looks.
|
|
3242
|
+
*/
|
|
3243
|
+
applyExternalSource(source) {
|
|
3244
|
+
this.externalSource = source;
|
|
3245
|
+
}
|
|
3246
|
+
/**
|
|
3149
3247
|
* Replace the permanent ignore list.
|
|
3150
3248
|
*
|
|
3151
3249
|
* Also drops any already-discovered account that is now ignored, so the change
|
|
@@ -3202,6 +3300,15 @@ var WorkBuddyAccountPool = class {
|
|
|
3202
3300
|
}
|
|
3203
3301
|
this.skippedFiles = skipped;
|
|
3204
3302
|
if (skipped.length > 0) this.logger?.warn?.(`dsh-xdpool: ${skipped.length} credential file(s) could not be read; ${found.length} account(s) still loaded. Reasons: ` + [...new Set(skipped.map((s) => s.reason))].join("; "));
|
|
3303
|
+
if (this.externalSource !== void 0) try {
|
|
3304
|
+
const external = await this.externalSource();
|
|
3305
|
+
for (const credential of external) {
|
|
3306
|
+
if (this.ignoredIds.size > 0 && this.ignoredIds.has(workbuddyAccountId(credential))) continue;
|
|
3307
|
+
found.push(credential);
|
|
3308
|
+
}
|
|
3309
|
+
} catch (error) {
|
|
3310
|
+
this.logger?.warn?.("dsh-xdpool: could not read channel-pack accounts; desktop accounts only", error);
|
|
3311
|
+
}
|
|
3205
3312
|
const byId = /* @__PURE__ */ new Map();
|
|
3206
3313
|
for (const account of this.accounts) byId.set(account.id, account);
|
|
3207
3314
|
if (this.persistent && this.accounts.length === 0) {
|
|
@@ -4198,7 +4305,8 @@ const DEFAULT_AUTOMATION_HOURS = {
|
|
|
4198
4305
|
report: [10],
|
|
4199
4306
|
tasks: [11],
|
|
4200
4307
|
streak: [12],
|
|
4201
|
-
travel: [9, 21]
|
|
4308
|
+
travel: [9, 21],
|
|
4309
|
+
compensation: [13]
|
|
4202
4310
|
};
|
|
4203
4311
|
//#endregion
|
|
4204
4312
|
//#region src/channels.ts
|
|
@@ -9156,7 +9264,7 @@ Object.freeze({
|
|
|
9156
9264
|
"openai-responses": "/responses",
|
|
9157
9265
|
"anthropic-messages": "/messages"
|
|
9158
9266
|
});
|
|
9159
|
-
process.env.XDPOOL_FREE_BASE;
|
|
9267
|
+
typeof process === "undefined" || process.env.XDPOOL_FREE_BASE;
|
|
9160
9268
|
Object.freeze([
|
|
9161
9269
|
"muse-spark-1.3-contributor-free",
|
|
9162
9270
|
"muse-spark-1.2-contributor-free",
|
|
@@ -9615,7 +9723,8 @@ const JOB_KINDS = [
|
|
|
9615
9723
|
"report",
|
|
9616
9724
|
"tasks",
|
|
9617
9725
|
"streak",
|
|
9618
|
-
"travel"
|
|
9726
|
+
"travel",
|
|
9727
|
+
"compensation"
|
|
9619
9728
|
];
|
|
9620
9729
|
/**
|
|
9621
9730
|
* Pick the hour list to run on: the configured one, or the default.
|
|
@@ -9662,9 +9771,10 @@ function diffEarnings(before, after) {
|
|
|
9662
9771
|
claimed: entry.claimed - (was?.claimed ?? 0),
|
|
9663
9772
|
checkinCredit: entry.checkinCredit - (was?.checkinCredit ?? 0),
|
|
9664
9773
|
bonusCredit: entry.bonusCredit - (was?.bonusCredit ?? 0),
|
|
9665
|
-
travelCredit: entry.travelCredit - (was?.travelCredit ?? 0)
|
|
9774
|
+
travelCredit: entry.travelCredit - (was?.travelCredit ?? 0),
|
|
9775
|
+
compensationCredit: entry.compensationCredit - (was?.compensationCredit ?? 0)
|
|
9666
9776
|
};
|
|
9667
|
-
if (gain.credit === 0 && gain.energy === 0 && gain.claimed === 0 && gain.checkinCredit === 0 && gain.bonusCredit === 0 && gain.travelCredit === 0) continue;
|
|
9777
|
+
if (gain.credit === 0 && gain.energy === 0 && gain.claimed === 0 && gain.checkinCredit === 0 && gain.bonusCredit === 0 && gain.travelCredit === 0 && gain.compensationCredit === 0) continue;
|
|
9668
9778
|
accounts[id] = gain;
|
|
9669
9779
|
credit += gain.credit;
|
|
9670
9780
|
energy += gain.energy;
|
|
@@ -9758,6 +9868,7 @@ var WorkBuddyScheduler = class {
|
|
|
9758
9868
|
reportHours;
|
|
9759
9869
|
streakHours;
|
|
9760
9870
|
travelHours;
|
|
9871
|
+
compensationHours;
|
|
9761
9872
|
timer;
|
|
9762
9873
|
running = false;
|
|
9763
9874
|
/** Guards against a slow run overlapping the next tick. */
|
|
@@ -9777,7 +9888,8 @@ var WorkBuddyScheduler = class {
|
|
|
9777
9888
|
report: { ...EMPTY_JOB_STATE },
|
|
9778
9889
|
tasks: { ...EMPTY_JOB_STATE },
|
|
9779
9890
|
streak: { ...EMPTY_JOB_STATE },
|
|
9780
|
-
travel: { ...EMPTY_JOB_STATE }
|
|
9891
|
+
travel: { ...EMPTY_JOB_STATE },
|
|
9892
|
+
compensation: { ...EMPTY_JOB_STATE }
|
|
9781
9893
|
};
|
|
9782
9894
|
claimableSeen = 0;
|
|
9783
9895
|
/**
|
|
@@ -9814,6 +9926,7 @@ var WorkBuddyScheduler = class {
|
|
|
9814
9926
|
this.taskHours = hoursOrDefault(options.taskHours, DEFAULT_AUTOMATION_HOURS.tasks);
|
|
9815
9927
|
this.streakHours = hoursOrDefault(options.streakHours, DEFAULT_AUTOMATION_HOURS.streak);
|
|
9816
9928
|
this.travelHours = hoursOrDefault(options.travelHours, DEFAULT_AUTOMATION_HOURS.travel);
|
|
9929
|
+
this.compensationHours = hoursOrDefault(options.compensationHours, DEFAULT_AUTOMATION_HOURS.compensation);
|
|
9817
9930
|
}
|
|
9818
9931
|
/** Apply a new configuration; safe to call while running. */
|
|
9819
9932
|
/**
|
|
@@ -9846,6 +9959,7 @@ var WorkBuddyScheduler = class {
|
|
|
9846
9959
|
if (options.taskHours !== void 0) this.taskHours = hoursOrDefault(options.taskHours, DEFAULT_AUTOMATION_HOURS.tasks);
|
|
9847
9960
|
if (options.streakHours !== void 0) this.streakHours = hoursOrDefault(options.streakHours, DEFAULT_AUTOMATION_HOURS.streak);
|
|
9848
9961
|
if (options.travelHours !== void 0) this.travelHours = hoursOrDefault(options.travelHours, DEFAULT_AUTOMATION_HOURS.travel);
|
|
9962
|
+
if (options.compensationHours !== void 0) this.compensationHours = hoursOrDefault(options.compensationHours, DEFAULT_AUTOMATION_HOURS.compensation);
|
|
9849
9963
|
}
|
|
9850
9964
|
/** Hours for one job, used by the loop and the status document. */
|
|
9851
9965
|
hoursOf(kind) {
|
|
@@ -9855,6 +9969,7 @@ var WorkBuddyScheduler = class {
|
|
|
9855
9969
|
case "tasks": return this.taskHours;
|
|
9856
9970
|
case "streak": return this.streakHours;
|
|
9857
9971
|
case "travel": return this.travelHours;
|
|
9972
|
+
case "compensation": return this.compensationHours;
|
|
9858
9973
|
}
|
|
9859
9974
|
}
|
|
9860
9975
|
/** Start the loop. Idempotent. */
|
|
@@ -9968,6 +10083,7 @@ var WorkBuddyScheduler = class {
|
|
|
9968
10083
|
reportHours: this.reportHours,
|
|
9969
10084
|
streakHours: this.streakHours,
|
|
9970
10085
|
travelHours: this.travelHours,
|
|
10086
|
+
compensationHours: this.compensationHours,
|
|
9971
10087
|
jobs,
|
|
9972
10088
|
claimableSeen: this.claimableSeen,
|
|
9973
10089
|
earningsToday: this.earningsSnapshot(),
|
|
@@ -10080,7 +10196,8 @@ var WorkBuddyScheduler = class {
|
|
|
10080
10196
|
const checkinCredit = delta.checkinCredit ?? 0;
|
|
10081
10197
|
const bonusCredit = delta.bonusCredit ?? 0;
|
|
10082
10198
|
const travelCredit = delta.travelCredit ?? 0;
|
|
10083
|
-
|
|
10199
|
+
const compensationCredit = delta.compensationCredit ?? 0;
|
|
10200
|
+
if (credit === 0 && energy === 0 && claimed === 0 && checkinCredit === 0 && bonusCredit === 0 && travelCredit === 0 && compensationCredit === 0) return;
|
|
10084
10201
|
const existing = this.earnings.get(accountId);
|
|
10085
10202
|
this.earnings.set(accountId, {
|
|
10086
10203
|
credit: (existing?.credit ?? 0) + credit,
|
|
@@ -10089,6 +10206,7 @@ var WorkBuddyScheduler = class {
|
|
|
10089
10206
|
checkinCredit: (existing?.checkinCredit ?? 0) + checkinCredit,
|
|
10090
10207
|
bonusCredit: (existing?.bonusCredit ?? 0) + bonusCredit,
|
|
10091
10208
|
travelCredit: (existing?.travelCredit ?? 0) + travelCredit,
|
|
10209
|
+
compensationCredit: (existing?.compensationCredit ?? 0) + compensationCredit,
|
|
10092
10210
|
date: today
|
|
10093
10211
|
});
|
|
10094
10212
|
this.persistEarnings();
|
|
@@ -10196,7 +10314,19 @@ var WorkBuddyScheduler = class {
|
|
|
10196
10314
|
if (progress !== void 0) progressNote = progress;
|
|
10197
10315
|
break;
|
|
10198
10316
|
}
|
|
10199
|
-
case "travel":
|
|
10317
|
+
case "travel":
|
|
10318
|
+
await this.runTravel(account);
|
|
10319
|
+
break;
|
|
10320
|
+
case "compensation": try {
|
|
10321
|
+
const outcome = await this.runCompensation(account);
|
|
10322
|
+
if (outcome.credit > 0) {
|
|
10323
|
+
detail.push(`补偿 +${outcome.credit}`);
|
|
10324
|
+
this.logger.info?.(`automation compensation ${account.label}: +${outcome.credit} credit`);
|
|
10325
|
+
}
|
|
10326
|
+
} catch (error) {
|
|
10327
|
+
if (!isNoCompensation(error)) throw error;
|
|
10328
|
+
this.logger.info?.(`automation compensation ${account.label}: no offer to collect`);
|
|
10329
|
+
}
|
|
10200
10330
|
}
|
|
10201
10331
|
ok++;
|
|
10202
10332
|
} catch (error) {
|
|
@@ -10525,6 +10655,27 @@ var WorkBuddyScheduler = class {
|
|
|
10525
10655
|
}
|
|
10526
10656
|
this.logger.info?.(`automation travel ${account.label}: nothing to do (state=${travel.state})`);
|
|
10527
10657
|
}
|
|
10658
|
+
/**
|
|
10659
|
+
* Collect the running service-outage compensation offer, if any.
|
|
10660
|
+
*
|
|
10661
|
+
* Read-then-claim, rather than claim-blind: a claim costs one mutation the
|
|
10662
|
+
* upstream may reject, and the status call is a free read that also tells us
|
|
10663
|
+
* the size on offer so the ledger records the real number instead of
|
|
10664
|
+
* assuming the usual 500.
|
|
10665
|
+
*
|
|
10666
|
+
* Returns the credits collected — 0 when there was nothing to collect, which
|
|
10667
|
+
* is the normal case for most of the year.
|
|
10668
|
+
*/
|
|
10669
|
+
async runCompensation(account) {
|
|
10670
|
+
const credential = account.credential;
|
|
10671
|
+
const status = await this.client.fetchCompensationStatus(credential);
|
|
10672
|
+
if (!status.active || status.claimed || status.creditNum <= 0) return { credit: 0 };
|
|
10673
|
+
const claim = await this.client.claimCompensation(credential);
|
|
10674
|
+
if (!claim.claimed) return { credit: 0 };
|
|
10675
|
+
const credit = claim.creditNum > 0 ? claim.creditNum : status.creditNum;
|
|
10676
|
+
this.recordEarnings(account.id, dayKey(this.now()), { compensationCredit: credit });
|
|
10677
|
+
return { credit };
|
|
10678
|
+
}
|
|
10528
10679
|
};
|
|
10529
10680
|
Object.freeze({
|
|
10530
10681
|
"agnes": "推理内容以 think 标签返回",
|
|
@@ -10769,6 +10920,7 @@ const automationSchema = z.object({
|
|
|
10769
10920
|
taskHours: z.array(z.number().step(1).min(0).max(23)).description("Local hours for task enrolment and claiming"),
|
|
10770
10921
|
streakHours: z.array(z.number().step(1).min(0).max(23)).description("Local hours for streak redemption"),
|
|
10771
10922
|
travelHours: z.array(z.number().step(1).min(0).max(23)).description("Local hours for the buddy travel loop"),
|
|
10923
|
+
compensationHours: z.array(z.number().step(1).min(0).max(23)).description("Local hours for the outage-compensation claim"),
|
|
10772
10924
|
exhaustCooldownMs: z.number().step(1).min(1e3).description("How long a spent account rests, in milliseconds")
|
|
10773
10925
|
});
|
|
10774
10926
|
z.object({
|