dsh-xdpool 2.5.0 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -52
- package/README.en.md +11 -2
- package/README.md +12 -3
- package/assets/community-qr.png +0 -0
- package/cordis.patch.yml +17 -41
- package/lib/bin.js +194 -30
- package/lib/client.js +285 -774
- package/lib/index.d.ts +54 -82
- package/lib/index.js +276 -409
- package/package.json +1 -4
- package/vendor/channel-pack/pack.js +8 -124
package/lib/bin.js
CHANGED
|
@@ -993,7 +993,8 @@ function parseTask(value) {
|
|
|
993
993
|
status: typeof raw["status"] === "string" ? raw["status"] : "",
|
|
994
994
|
claimable: !claimed && target > 0 && current >= target,
|
|
995
995
|
claimed,
|
|
996
|
-
locked: raw["locked"] === true
|
|
996
|
+
locked: raw["locked"] === true,
|
|
997
|
+
jumpUrl: typeof raw["jump_url"] === "string" ? raw["jump_url"] : ""
|
|
997
998
|
};
|
|
998
999
|
}
|
|
999
1000
|
var WorkBuddyUpstreamClient = class {
|
|
@@ -1354,17 +1355,14 @@ var WorkBuddyUpstreamClient = class {
|
|
|
1354
1355
|
if (typeof raw !== "object" || raw === null) continue;
|
|
1355
1356
|
const account = raw;
|
|
1356
1357
|
const num = (key) => typeof account[key] === "number" ? account[key] : 0;
|
|
1357
|
-
const
|
|
1358
|
-
const
|
|
1359
|
-
const
|
|
1360
|
-
const size = periodic ? num("CycleCapacitySize") : num("CapacitySize");
|
|
1361
|
-
const remain = periodic ? num("CycleCapacityRemain") : num("CapacityRemain");
|
|
1358
|
+
const monthly = num("CapacityType") === 4;
|
|
1359
|
+
const size = monthly ? num("CycleCapacitySize") : num("CapacitySize");
|
|
1360
|
+
const remain = monthly ? num("CycleCapacityRemain") : num("CapacityRemain");
|
|
1362
1361
|
const capped = remain < 0 ? 0 : remain;
|
|
1363
1362
|
const cycleEndMs = parseDate(account["CycleEndTime"]);
|
|
1364
|
-
const expiresAtMs = monthly ? void 0 :
|
|
1363
|
+
const expiresAtMs = monthly ? void 0 : parseDate(account["ExpiredTime"]) ?? cycleEndMs;
|
|
1365
1364
|
const refreshAtMs = monthly ? cycleEndMs === void 0 ? void 0 : cycleEndMs + 1e3 : void 0;
|
|
1366
|
-
if (expiresAtMs !== void 0 && expiresAtMs <= Date.now()) continue;
|
|
1367
|
-
if (!periodic && capped <= 0) continue;
|
|
1365
|
+
if (!monthly && (capped <= 0 || expiresAtMs !== void 0 && expiresAtMs <= Date.now())) continue;
|
|
1368
1366
|
total += capped;
|
|
1369
1367
|
if (expiresAtMs !== void 0) {
|
|
1370
1368
|
if (nearestExpiryMs === void 0 || expiresAtMs < nearestExpiryMs) nearestExpiryMs = expiresAtMs;
|
|
@@ -3696,11 +3694,11 @@ var WorkBuddyAccountPool = class {
|
|
|
3696
3694
|
* model id the legacy account-wide check applies (callers that cannot name a
|
|
3697
3695
|
* model, e.g. CLI diagnostics).
|
|
3698
3696
|
*/
|
|
3699
|
-
available(now, modelId, region) {
|
|
3697
|
+
available(now, modelId, region, reserveExempt) {
|
|
3700
3698
|
return this.accounts.filter((account) => {
|
|
3701
3699
|
if (this.disabledIds.has(account.id)) return false;
|
|
3702
3700
|
const reserve = this.creditReserves.get(account.id);
|
|
3703
|
-
if (reserve !== void 0 && reserve > 0) {
|
|
3701
|
+
if (!reserveExempt?.has(account.id) && reserve !== void 0 && reserve > 0) {
|
|
3704
3702
|
const balance = this.creditBalances.get(account.id);
|
|
3705
3703
|
if (balance !== void 0 && balance <= reserve) return false;
|
|
3706
3704
|
}
|
|
@@ -3724,7 +3722,7 @@ var WorkBuddyAccountPool = class {
|
|
|
3724
3722
|
* Counts are over the region's accounts, since a provider only ever sees its
|
|
3725
3723
|
* own gateway.
|
|
3726
3724
|
*/
|
|
3727
|
-
unavailableReason(modelId, region) {
|
|
3725
|
+
unavailableReason(modelId, region, reserveExempt) {
|
|
3728
3726
|
const now = Date.now();
|
|
3729
3727
|
const inRegion = this.accounts.filter((account) => region === void 0 || regionOf(account.credential.domain) === region);
|
|
3730
3728
|
if (inRegion.length === 0) return {
|
|
@@ -3737,6 +3735,29 @@ var WorkBuddyAccountPool = class {
|
|
|
3737
3735
|
let cooling = 0;
|
|
3738
3736
|
let disabled = 0;
|
|
3739
3737
|
let dead = 0;
|
|
3738
|
+
/**
|
|
3739
|
+
* Accounts that survived every OTHER filter and are held back only by their
|
|
3740
|
+
* credit floor.
|
|
3741
|
+
*
|
|
3742
|
+
* Counted rather than returned on the first hit. The old `return` fired the
|
|
3743
|
+
* moment it met one reserved account, so a single account parked under its
|
|
3744
|
+
* floor reported `reserve` for the whole gateway — even while four other
|
|
3745
|
+
* accounts were healthy and had just been tried. The caller turns
|
|
3746
|
+
* `reason === 'reserve'` into a 402 telling the user to lower the reserve,
|
|
3747
|
+
* which pointed at a setting that was not the problem (#98: a five-account
|
|
3748
|
+
* pool reported `reserve_floor` because of one account that had sat below
|
|
3749
|
+
* its floor for weeks, while the actual state was "the rest are cooling").
|
|
3750
|
+
*/
|
|
3751
|
+
let reserved = 0;
|
|
3752
|
+
/**
|
|
3753
|
+
* Accounts that reached the end of the walk unblocked.
|
|
3754
|
+
*
|
|
3755
|
+
* Non-zero means the pool still has somewhere to send a request, so a
|
|
3756
|
+
* reserve is NOT what this answer is about: editing a number cannot fix a
|
|
3757
|
+
* request that some other account is already able to serve, and saying
|
|
3758
|
+
* otherwise sends the reader to the wrong setting.
|
|
3759
|
+
*/
|
|
3760
|
+
let usable = 0;
|
|
3740
3761
|
for (const account of inRegion) {
|
|
3741
3762
|
if (this.disabledIds.has(account.id)) {
|
|
3742
3763
|
disabled += 1;
|
|
@@ -3752,16 +3773,14 @@ var WorkBuddyAccountPool = class {
|
|
|
3752
3773
|
continue;
|
|
3753
3774
|
}
|
|
3754
3775
|
const reserve = this.creditReserves.get(account.id);
|
|
3755
|
-
if (reserve !== void 0 && reserve > 0) {
|
|
3776
|
+
if (!reserveExempt?.has(account.id) && reserve !== void 0 && reserve > 0) {
|
|
3756
3777
|
const balance = this.creditBalances.get(account.id);
|
|
3757
|
-
if (balance !== void 0 && balance <= reserve)
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
dead,
|
|
3762
|
-
reason: "reserve"
|
|
3763
|
-
};
|
|
3778
|
+
if (balance !== void 0 && balance <= reserve) {
|
|
3779
|
+
reserved += 1;
|
|
3780
|
+
continue;
|
|
3781
|
+
}
|
|
3764
3782
|
}
|
|
3783
|
+
usable += 1;
|
|
3765
3784
|
}
|
|
3766
3785
|
if (dead > 0) return {
|
|
3767
3786
|
total: inRegion.length,
|
|
@@ -3784,6 +3803,13 @@ var WorkBuddyAccountPool = class {
|
|
|
3784
3803
|
dead,
|
|
3785
3804
|
reason: "disabled"
|
|
3786
3805
|
};
|
|
3806
|
+
if (usable === 0 && reserved > 0) return {
|
|
3807
|
+
total: inRegion.length,
|
|
3808
|
+
cooling,
|
|
3809
|
+
disabled,
|
|
3810
|
+
dead,
|
|
3811
|
+
reason: "reserve"
|
|
3812
|
+
};
|
|
3787
3813
|
return {
|
|
3788
3814
|
total: inRegion.length,
|
|
3789
3815
|
cooling,
|
|
@@ -3960,16 +3986,18 @@ var WorkBuddyAccountPool = class {
|
|
|
3960
3986
|
* Scans on first use, and rescans when every known account is cooling down: a
|
|
3961
3987
|
* fresh desktop login is the usual way out of an exhausted pool.
|
|
3962
3988
|
*
|
|
3989
|
+
* `reserveExempt` is host-only: account ids whose own credentials freshly
|
|
3990
|
+
* confirmed a zero-credit model. It never bypasses disabled, dead credentials, cooldowns or region.
|
|
3963
3991
|
* `conversationKey` is only consulted under `sticky`; other modes ignore it.
|
|
3964
3992
|
*/
|
|
3965
|
-
async acquire(modelId, region, conversationKey) {
|
|
3993
|
+
async acquire(modelId, region, conversationKey, reserveExempt) {
|
|
3966
3994
|
if (this.accounts.length === 0) await this.scan();
|
|
3967
3995
|
const maxTries = this.accounts.length + 1;
|
|
3968
3996
|
for (let attempt = 0; attempt < maxTries; attempt += 1) {
|
|
3969
|
-
let pool = this.available(Date.now(), modelId, region);
|
|
3997
|
+
let pool = this.available(Date.now(), modelId, region, reserveExempt);
|
|
3970
3998
|
if (pool.length === 0 && attempt === 0) {
|
|
3971
3999
|
await this.scan();
|
|
3972
|
-
pool = this.available(Date.now(), modelId, region);
|
|
4000
|
+
pool = this.available(Date.now(), modelId, region, reserveExempt);
|
|
3973
4001
|
}
|
|
3974
4002
|
if (pool.length === 0) return void 0;
|
|
3975
4003
|
if (this.preferredAccountId !== void 0) {
|
|
@@ -9967,6 +9995,71 @@ function appearanceChain(themeKey = APPEARANCE_THEME_KEY) {
|
|
|
9967
9995
|
};
|
|
9968
9996
|
}
|
|
9969
9997
|
/**
|
|
9998
|
+
* Pull the `expertId` out of a task's `jump_url`.
|
|
9999
|
+
*
|
|
10000
|
+
* The "体验XX专家" tasks (#98) name an expert that is NOT in the marketplace
|
|
10001
|
+
* listing — the reporter翻遍了 marketExpertList in both `agent` and `team`
|
|
10002
|
+
* types and got zero hits — so the only place the id appears is the deeplink
|
|
10003
|
+
* embedded in the task itself:
|
|
10004
|
+
*
|
|
10005
|
+
* https://www.workbuddy.cn/work/launch/?deeplink=workbuddy%3A%2F%2Fexperts%3FexpertId%3Dex_GDl31WahyIH0&buddy_type=workbuddy
|
|
10006
|
+
*
|
|
10007
|
+
* The deeplink is percent-encoded, and it has to be: the `?` and `&` inside it
|
|
10008
|
+
* would otherwise end the outer query. So the parse is two steps — decode the
|
|
10009
|
+
* `deeplink` parameter, then read `expertId` off the scheme it decodes to — and
|
|
10010
|
+
* NOT a regex over the whole URL, which would also match the outer `expertId`
|
|
10011
|
+
* spelling in a future URL shape and silently pick the wrong one.
|
|
10012
|
+
*
|
|
10013
|
+
* Returns undefined for the tasks whose deeplink carries no id at all
|
|
10014
|
+
* (`WorkBuddy_Valorant` points at `workbuddy://expert` with no query), which is
|
|
10015
|
+
* the honest answer: those need a different source and are not guessed here.
|
|
10016
|
+
*/
|
|
10017
|
+
function expertIdFromJumpUrl(jumpUrl) {
|
|
10018
|
+
if (jumpUrl === "") return void 0;
|
|
10019
|
+
/** Read `expertId` off any URL-ish string, decoded or not. */
|
|
10020
|
+
const readId = (value) => {
|
|
10021
|
+
let query = value;
|
|
10022
|
+
try {
|
|
10023
|
+
const parsed = new URL(value);
|
|
10024
|
+
query = parsed.search === "" ? value : parsed.search;
|
|
10025
|
+
} catch {
|
|
10026
|
+
query = value;
|
|
10027
|
+
}
|
|
10028
|
+
const marker = query.indexOf("expertId=");
|
|
10029
|
+
if (marker === -1) return void 0;
|
|
10030
|
+
const rest = query.slice(marker + 9);
|
|
10031
|
+
const end = rest.search(/[&#]/u);
|
|
10032
|
+
const id = (end === -1 ? rest : rest.slice(0, end)).trim();
|
|
10033
|
+
return id === "" ? void 0 : id;
|
|
10034
|
+
};
|
|
10035
|
+
let source = jumpUrl;
|
|
10036
|
+
try {
|
|
10037
|
+
const raw = new URL(jumpUrl).searchParams.get("deeplink");
|
|
10038
|
+
if (raw !== null && raw !== "") source = raw;
|
|
10039
|
+
} catch {
|
|
10040
|
+
source = jumpUrl;
|
|
10041
|
+
}
|
|
10042
|
+
return readId(source);
|
|
10043
|
+
}
|
|
10044
|
+
/**
|
|
10045
|
+
* Build a summon record for an expert the marketplace does not list.
|
|
10046
|
+
*
|
|
10047
|
+
* Everything the chains need beyond the id is cosmetic — the scorer keys on
|
|
10048
|
+
* `expertId` — so the title from the task is reused as the display name rather
|
|
10049
|
+
* than inventing one. `version` is left empty, which every consumer already
|
|
10050
|
+
* defaults to `1.0.0`.
|
|
10051
|
+
*/
|
|
10052
|
+
function expertFromTask(expertId, title) {
|
|
10053
|
+
return {
|
|
10054
|
+
expertId,
|
|
10055
|
+
expertType: "agent",
|
|
10056
|
+
displayName: title,
|
|
10057
|
+
profession: title,
|
|
10058
|
+
version: "",
|
|
10059
|
+
categories: []
|
|
10060
|
+
};
|
|
10061
|
+
}
|
|
10062
|
+
/**
|
|
9970
10063
|
* The three "summon an expert" events (`expert_summon_click` and friends).
|
|
9971
10064
|
*
|
|
9972
10065
|
* Paid before the conversation, in the order the app emits them.
|
|
@@ -10080,7 +10173,12 @@ const EVENT_CHAIN_BUILDERS = {
|
|
|
10080
10173
|
skill_1: true,
|
|
10081
10174
|
expert_5: true,
|
|
10082
10175
|
Expert_team_use_3: true,
|
|
10083
|
-
Expert_lighthouse: true
|
|
10176
|
+
Expert_lighthouse: true,
|
|
10177
|
+
expert_use_maidehao: true,
|
|
10178
|
+
expert_use_douyinyin: true,
|
|
10179
|
+
expert_use_shangjia_xuanchuan: true,
|
|
10180
|
+
expert_use_bowangwang: true,
|
|
10181
|
+
WorkBuddy_Valorant: true
|
|
10084
10182
|
};
|
|
10085
10183
|
const AUTOMATION_TICK_MS = 6e4;
|
|
10086
10184
|
const JOB_KINDS = [
|
|
@@ -10269,6 +10367,16 @@ var WorkBuddyScheduler = class {
|
|
|
10269
10367
|
/** Host hooks that persist the ledger across restarts. */
|
|
10270
10368
|
loadEarnings;
|
|
10271
10369
|
saveEarningsFn;
|
|
10370
|
+
/**
|
|
10371
|
+
* The most recent ledger write that may still be in flight.
|
|
10372
|
+
*
|
|
10373
|
+
* `persistEarnings` is synchronous from its callers' point of view, but the
|
|
10374
|
+
* host's settings write is not: it is an async document update. Keeping the
|
|
10375
|
+
* promise here is what lets {@link flushEarnings} wait for the write that
|
|
10376
|
+
* matters — the last one before a restart — without making every counter
|
|
10377
|
+
* update await disk.
|
|
10378
|
+
*/
|
|
10379
|
+
pendingEarningsWrite = Promise.resolve();
|
|
10272
10380
|
constructor(pool, client, options = {}) {
|
|
10273
10381
|
this.pool = pool;
|
|
10274
10382
|
this.client = client;
|
|
@@ -10305,6 +10413,18 @@ var WorkBuddyScheduler = class {
|
|
|
10305
10413
|
this.saveEarningsFn = save;
|
|
10306
10414
|
}
|
|
10307
10415
|
/**
|
|
10416
|
+
* Wait for the ledger write that is (or was last) in flight.
|
|
10417
|
+
*
|
|
10418
|
+
* Called at the END of a task pass, right after the last claim, so the rewards
|
|
10419
|
+
* that were just collected are on disk before a restart can lose them. It is
|
|
10420
|
+
* deliberately NOT awaited on every counter bump: the settings document write
|
|
10421
|
+
* is the slow part, and paying it per account would slow the pass for no
|
|
10422
|
+
* benefit while the process is still alive.
|
|
10423
|
+
*/
|
|
10424
|
+
async flushEarnings() {
|
|
10425
|
+
await this.pendingEarningsWrite;
|
|
10426
|
+
}
|
|
10427
|
+
/**
|
|
10308
10428
|
* Fold a previously persisted ledger back in, when it belongs to today.
|
|
10309
10429
|
*
|
|
10310
10430
|
* Used after the settings document becomes readable, which happens after
|
|
@@ -10574,7 +10694,7 @@ var WorkBuddyScheduler = class {
|
|
|
10574
10694
|
compensationCredit: (existing?.compensationCredit ?? 0) + compensationCredit,
|
|
10575
10695
|
date: today
|
|
10576
10696
|
});
|
|
10577
|
-
this.persistEarnings();
|
|
10697
|
+
this.pendingEarningsWrite = this.persistEarnings();
|
|
10578
10698
|
}
|
|
10579
10699
|
/** Clear the per-account counters when the local day changes. */
|
|
10580
10700
|
rollEarnings(today) {
|
|
@@ -10590,9 +10710,18 @@ var WorkBuddyScheduler = class {
|
|
|
10590
10710
|
* Best effort on purpose: a failed save must never abort a run that has
|
|
10591
10711
|
* already collected rewards, and the in-memory ledger keeps serving the card
|
|
10592
10712
|
* for the rest of the session either way.
|
|
10713
|
+
*
|
|
10714
|
+
* The returned promise settles when the write has landed (or failed). Callers
|
|
10715
|
+
* that are about to END a run await it: reported in #98, a task pass that
|
|
10716
|
+
* claimed 21 tasks had its process restarted before the volatile settings
|
|
10717
|
+
* write reached disk, so `automationEarnings` still read `credit: 0` while
|
|
10718
|
+
* the upstream had really paid out. Fire-and-forget is fine for a counter the
|
|
10719
|
+
* card will re-read in this process; it is not fine for the last write before
|
|
10720
|
+
* exit.
|
|
10593
10721
|
*/
|
|
10594
10722
|
persistEarnings() {
|
|
10595
|
-
|
|
10723
|
+
const save = this.saveEarningsFn;
|
|
10724
|
+
if (save === void 0) return Promise.resolve();
|
|
10596
10725
|
const accounts = {};
|
|
10597
10726
|
for (const [id, entry] of this.earnings) accounts[id] = entry;
|
|
10598
10727
|
const ledger = {
|
|
@@ -10600,12 +10729,14 @@ var WorkBuddyScheduler = class {
|
|
|
10600
10729
|
accounts
|
|
10601
10730
|
};
|
|
10602
10731
|
try {
|
|
10603
|
-
const saved =
|
|
10604
|
-
if (saved !== void 0 && typeof saved.then === "function") saved.then(void 0, (error) => {
|
|
10732
|
+
const saved = save(ledger);
|
|
10733
|
+
if (saved !== void 0 && typeof saved.then === "function") return Promise.resolve(saved).then(void 0, (error) => {
|
|
10605
10734
|
this.logger.warn?.("dsh-xdpool: could not persist automation earnings:", error);
|
|
10606
10735
|
});
|
|
10736
|
+
return Promise.resolve();
|
|
10607
10737
|
} catch (error) {
|
|
10608
10738
|
this.logger.warn?.("dsh-xdpool: could not persist automation earnings:", error);
|
|
10739
|
+
return Promise.resolve();
|
|
10609
10740
|
}
|
|
10610
10741
|
}
|
|
10611
10742
|
/**
|
|
@@ -10714,6 +10845,7 @@ var WorkBuddyScheduler = class {
|
|
|
10714
10845
|
state.detail = detail;
|
|
10715
10846
|
state.progress = progressNote;
|
|
10716
10847
|
this.logger.info?.(`automation ${accountWord}: ${state.message}`);
|
|
10848
|
+
if (kind === "tasks" || kind === "checkin" || kind === "streak" || kind === "travel" || kind === "compensation") await this.flushEarnings();
|
|
10717
10849
|
}
|
|
10718
10850
|
/** Compose the one-line summary shown on the card. */
|
|
10719
10851
|
summarise(kind, ok, failed, claimed, credit, energy) {
|
|
@@ -10836,10 +10968,43 @@ var WorkBuddyScheduler = class {
|
|
|
10836
10968
|
case "expert_5": return this.expertChains(credential, "agent", task);
|
|
10837
10969
|
case "Expert_team_use_3": return this.expertChains(credential, "team", task);
|
|
10838
10970
|
case "Expert_lighthouse": return this.lighthouseChains(credential);
|
|
10971
|
+
case "expert_use_maidehao":
|
|
10972
|
+
case "expert_use_douyinyin":
|
|
10973
|
+
case "expert_use_shangjia_xuanchuan":
|
|
10974
|
+
case "expert_use_bowangwang":
|
|
10975
|
+
case "WorkBuddy_Valorant": return this.taskExpertChains(credential, task);
|
|
10839
10976
|
default: return [];
|
|
10840
10977
|
}
|
|
10841
10978
|
}
|
|
10842
10979
|
/**
|
|
10980
|
+
* Summon-and-use one expert NAMED BY THE TASK.
|
|
10981
|
+
*
|
|
10982
|
+
* Structurally `expertChains` minus the marketplace: the expert id is read
|
|
10983
|
+
* from the task's deeplink instead of by browsing `marketExpertList`, because
|
|
10984
|
+
* this family's experts are not listed there at all (#98 measured 500 entries
|
|
10985
|
+
* across both `agent` and `team`, zero hits).
|
|
10986
|
+
*
|
|
10987
|
+
* Returns `[]` — not a throw — when the task carries no usable id: the caller
|
|
10988
|
+
* logs "could not be built" only for exceptions, and a task shape this code
|
|
10989
|
+
* does not understand is information, not a fault.
|
|
10990
|
+
*/
|
|
10991
|
+
async taskExpertChains(credential, task) {
|
|
10992
|
+
const expertId = expertIdFromJumpUrl(task.jumpUrl);
|
|
10993
|
+
if (expertId === void 0) {
|
|
10994
|
+
this.logger.warn?.(`automation events ${task.taskCode}: no expertId in jump_url, skipping`);
|
|
10995
|
+
return [];
|
|
10996
|
+
}
|
|
10997
|
+
const expert = expertFromTask(expertId, task.title);
|
|
10998
|
+
await this.client.reportDesktopEvents(credential, expertSummonEvents(expert));
|
|
10999
|
+
if (this.delayMs > 0 && !this.stopped) await sleep(this.delayMs);
|
|
11000
|
+
const chat = await this.client.openConversation(credential, expert.expertId);
|
|
11001
|
+
if (chat === void 0) throw new Error(`${task.taskCode}: no server conversation`);
|
|
11002
|
+
return [{
|
|
11003
|
+
transport: "desktop",
|
|
11004
|
+
events: [...expertChatEvents(expert, chat.conversationId, chat.requestId), expertActualUseEvent(expert, chat.conversationId, chat.requestId)]
|
|
11005
|
+
}];
|
|
11006
|
+
}
|
|
11007
|
+
/**
|
|
10843
11008
|
* The summon-and-use chains for the expert tasks.
|
|
10844
11009
|
*
|
|
10845
11010
|
* Two steps per expert, and both are load-bearing: the summon events alone are
|
|
@@ -11306,8 +11471,7 @@ z.object({
|
|
|
11306
11471
|
modelSelectionCn: asVolatile(modelSelectionSchema.description("Model selection for the domestic gateway")),
|
|
11307
11472
|
modelSelectionGlobal: asVolatile(modelSelectionSchema.description("Model selection for the international gateway")),
|
|
11308
11473
|
automation: asVolatile(automationSchema.description("Daily points automation (activity report, task claiming, check-in)")),
|
|
11309
|
-
automationEarnings: asVolatile(z.any().description("Automation earnings ledger (written by the scheduler)"))
|
|
11310
|
-
sidebarRow: asVolatile(z.boolean().default(true).description("Show the XD Pool entry in the left sidebar (the Settings page keeps working either way)"))
|
|
11474
|
+
automationEarnings: asVolatile(z.any().description("Automation earnings ledger (written by the scheduler)"))
|
|
11311
11475
|
});
|
|
11312
11476
|
/** Assemble the runtime objects without registering anything. */
|
|
11313
11477
|
/**
|