dsh-xdpool 2.6.2 → 2.6.3
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 +27 -0
- package/lib/client.js +1 -1
- package/lib/index.d.ts +22 -0
- package/lib/index.js +146 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
本项目的所有重要变更都会记录在此文件。
|
|
3
3
|
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
4
|
+
## 2.6.3 — 修掉 2.6.2 装上后插件起不来的问题
|
|
5
|
+
|
|
6
|
+
> 2.6.2 从 npm 安装后,插件导入失败(`Cannot find package '@earendil-works/pi-ai'`),
|
|
7
|
+
> 于是它的所有 web 路由都没注册 —— 积分接口 404,用量面板空白,并显示
|
|
8
|
+
> 「网关尚未上报 token 数」。这句话把问题指向了网关,实际是插件自己没加载起来。
|
|
9
|
+
|
|
10
|
+
### 缺 7 个 peer 依赖
|
|
11
|
+
|
|
12
|
+
2.6.2 有 7 个 peerDependency(`cordis`、`dsh-llm`、`dsh-llm-pi-ai`、`dsh-credentials`、
|
|
13
|
+
`dsh-settings`、`schemastery`、`pi-ai`),这些由宿主提供。手动复制目录时它们从宿主的
|
|
14
|
+
`node_modules` 可见;改用包管理器安装后,解析链断了。
|
|
15
|
+
|
|
16
|
+
现在 `postinstall` 会把这些包从宿主链接进 profile,版本与宿主完全一致,不额外占用空间。
|
|
17
|
+
装完自带一次导入自检,失败会直接把原因写进日志,而不是等用户看到空白面板才发现。
|
|
18
|
+
|
|
19
|
+
### 今日用量的三个缺陷
|
|
20
|
+
|
|
21
|
+
**日期用了 UTC**:`toISOString()` 取的是 UTC 日期,在 UTC+8 意味着凌晨 0–8 点算成前一天。
|
|
22
|
+
现在按本地日历取值。
|
|
23
|
+
|
|
24
|
+
**`days: 1` 实际覆盖两天**:窗口下界原先减了整个 `days`,于是「今日」包含了昨天。
|
|
25
|
+
改成 `days - 1`,标签与口径一致。
|
|
26
|
+
|
|
27
|
+
**重启后归零**:`UsageRecorder` 只存内存,重启即空。现在启动时从渠道包的持久账本
|
|
28
|
+
(保留 90 天)回填当天用量 —— 那些数据一直在磁盘上,只是从没被读回来。
|
|
29
|
+
回填是尽力而为的:读不到只损失重启前的数字,不影响插件。
|
|
30
|
+
|
|
4
31
|
## 2.6.2 — 积分统计、账号备份、输入框积分显示
|
|
5
32
|
|
|
6
33
|
> 这一版把「用了多少积分」「还剩多少」「账号怎么搬走」三件事做完了,并修掉了几个把正确数据说错的地方。
|
package/lib/client.js
CHANGED
|
@@ -13460,7 +13460,7 @@ window.__ModuleLoader__.load({
|
|
|
13460
13460
|
*
|
|
13461
13461
|
* @module dsh-xdpool/version
|
|
13462
13462
|
*/
|
|
13463
|
-
const POOL_VERSION = "2.6.
|
|
13463
|
+
const POOL_VERSION = "2.6.3";
|
|
13464
13464
|
//#endregion
|
|
13465
13465
|
//#region src/client/PoolCard.tsx
|
|
13466
13466
|
/**
|
package/lib/index.d.ts
CHANGED
|
@@ -2987,6 +2987,28 @@ declare class UsageRecorder {
|
|
|
2987
2987
|
size(): number;
|
|
2988
2988
|
/** Forget every retained call. */
|
|
2989
2989
|
reset(): void;
|
|
2990
|
+
/**
|
|
2991
|
+
* Refill today's samples from the pack's own ledger.
|
|
2992
|
+
*
|
|
2993
|
+
* WHY THIS EXISTS
|
|
2994
|
+
*
|
|
2995
|
+
* `samples` is process memory, so every restart emptied the usage panel and the pool
|
|
2996
|
+
* card — "网关尚未上报 token 数" after a restart was not the gateway declining to
|
|
2997
|
+
* answer, it was this recorder having forgotten. The pack, meanwhile, persists a 90-day
|
|
2998
|
+
* day-aggregated ledger on disk, so the same period was already recorded and simply not
|
|
2999
|
+
* read.
|
|
3000
|
+
*
|
|
3001
|
+
* Called ONCE at startup, before the first request. It REPLACES rather than appends:
|
|
3002
|
+
* whatever is in memory at that moment is either empty (a fresh process) or belongs to
|
|
3003
|
+
* the same ledger entries, and adding both would double every figure.
|
|
3004
|
+
*
|
|
3005
|
+
* The rows are synthesised at DAY granularity — one `UsageCall` per (channel, model) with
|
|
3006
|
+
* the day's totals — because that is the resolution the ledger keeps. Per-call timestamps
|
|
3007
|
+
* are not recoverable, so the panel's "recent calls" list stays empty after a restart
|
|
3008
|
+
* while its totals are right; inventing timestamps to fill the list would be worse than
|
|
3009
|
+
* an empty list.
|
|
3010
|
+
*/
|
|
3011
|
+
backfill(calls: readonly UsageCall[]): void;
|
|
2990
3012
|
/** Aggregate the retained window. */
|
|
2991
3013
|
summary(): UsageSummary;
|
|
2992
3014
|
/**
|
package/lib/index.js
CHANGED
|
@@ -11862,6 +11862,31 @@ var UsageRecorder = class {
|
|
|
11862
11862
|
reset() {
|
|
11863
11863
|
this.samples = [];
|
|
11864
11864
|
}
|
|
11865
|
+
/**
|
|
11866
|
+
* Refill today's samples from the pack's own ledger.
|
|
11867
|
+
*
|
|
11868
|
+
* WHY THIS EXISTS
|
|
11869
|
+
*
|
|
11870
|
+
* `samples` is process memory, so every restart emptied the usage panel and the pool
|
|
11871
|
+
* card — "网关尚未上报 token 数" after a restart was not the gateway declining to
|
|
11872
|
+
* answer, it was this recorder having forgotten. The pack, meanwhile, persists a 90-day
|
|
11873
|
+
* day-aggregated ledger on disk, so the same period was already recorded and simply not
|
|
11874
|
+
* read.
|
|
11875
|
+
*
|
|
11876
|
+
* Called ONCE at startup, before the first request. It REPLACES rather than appends:
|
|
11877
|
+
* whatever is in memory at that moment is either empty (a fresh process) or belongs to
|
|
11878
|
+
* the same ledger entries, and adding both would double every figure.
|
|
11879
|
+
*
|
|
11880
|
+
* The rows are synthesised at DAY granularity — one `UsageCall` per (channel, model) with
|
|
11881
|
+
* the day's totals — because that is the resolution the ledger keeps. Per-call timestamps
|
|
11882
|
+
* are not recoverable, so the panel's "recent calls" list stays empty after a restart
|
|
11883
|
+
* while its totals are right; inventing timestamps to fill the list would be worse than
|
|
11884
|
+
* an empty list.
|
|
11885
|
+
*/
|
|
11886
|
+
backfill(calls) {
|
|
11887
|
+
this.samples = [];
|
|
11888
|
+
for (const call of calls) this.record(call);
|
|
11889
|
+
}
|
|
11865
11890
|
/** Aggregate the retained window. */
|
|
11866
11891
|
summary() {
|
|
11867
11892
|
const rows = /* @__PURE__ */ new Map();
|
|
@@ -21172,10 +21197,25 @@ function numberOf(value) {
|
|
|
21172
21197
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
21173
21198
|
}
|
|
21174
21199
|
/** `2026-11-07` minus N days, as the same string shape. */
|
|
21200
|
+
/**
|
|
21201
|
+
* A `YYYY-MM-DD` key for `days` ago, in the LOCAL calendar.
|
|
21202
|
+
*
|
|
21203
|
+
* Not `toISOString()`: that is UTC, and the ledger's day keys are whatever the machine's
|
|
21204
|
+
* clock says when the pack writes them — i.e. local. Mixing the two shifts the window by
|
|
21205
|
+
* the UTC offset, so a user in UTC+8 reading "today" between midnight and 08:00 was shown
|
|
21206
|
+
* the previous day's totals. The bug is invisible for most of the day and wrong for
|
|
21207
|
+
* exactly the hours someone checks whether a long run has spent much.
|
|
21208
|
+
*
|
|
21209
|
+
* Built from local parts rather than by subtracting milliseconds, because a DST boundary
|
|
21210
|
+
* would otherwise land on the wrong day.
|
|
21211
|
+
*/
|
|
21175
21212
|
function daysAgoKey(days, now = /* @__PURE__ */ new Date()) {
|
|
21176
21213
|
const copy = new Date(now.getTime());
|
|
21177
|
-
copy.
|
|
21178
|
-
|
|
21214
|
+
copy.setDate(copy.getDate() - days);
|
|
21215
|
+
const year = copy.getFullYear();
|
|
21216
|
+
const month = String(copy.getMonth() + 1).padStart(2, "0");
|
|
21217
|
+
const day = String(copy.getDate()).padStart(2, "0");
|
|
21218
|
+
return `${String(year)}-${month}-${day}`;
|
|
21179
21219
|
}
|
|
21180
21220
|
/** Split `mode|channel|account|model`, refusing anything that is not four parts. */
|
|
21181
21221
|
function parseLedgerKey(key) {
|
|
@@ -21224,7 +21264,15 @@ async function readCreditSpend(input) {
|
|
|
21224
21264
|
}
|
|
21225
21265
|
const daysNode = parsed.days;
|
|
21226
21266
|
if (daysNode === null || typeof daysNode !== "object") return empty;
|
|
21227
|
-
|
|
21267
|
+
/**
|
|
21268
|
+
* `days: 1` means TODAY ALONE, not "today plus yesterday".
|
|
21269
|
+
*
|
|
21270
|
+
* The old arithmetic subtracted the window size before taking the bound
|
|
21271
|
+
* (`daysAgoKey(days)`), so a one-day window covered today AND yesterday: a strip labelled
|
|
21272
|
+
* "today" reported a figure that included yesterday evening's work. `days - 1` makes the
|
|
21273
|
+
* label true — one day back from today is the start of today.
|
|
21274
|
+
*/
|
|
21275
|
+
const earliest = input.days === void 0 ? void 0 : daysAgoKey(Math.max(0, input.days - 1), input.now);
|
|
21228
21276
|
const acc = /* @__PURE__ */ new Map();
|
|
21229
21277
|
let unreadable = 0;
|
|
21230
21278
|
let gatewayRequests = 0;
|
|
@@ -21292,6 +21340,86 @@ async function readCreditSpend(input) {
|
|
|
21292
21340
|
empty: !sawAnyDay
|
|
21293
21341
|
};
|
|
21294
21342
|
}
|
|
21343
|
+
/**
|
|
21344
|
+
* Refill a `UsageRecorder` with TODAY's totals from the pack ledger.
|
|
21345
|
+
*
|
|
21346
|
+
* The recorder keeps process memory only, so every restart emptied the usage panel and the
|
|
21347
|
+
* pool card printed "the gateway reported no token counts" — an accusation aimed at the
|
|
21348
|
+
* gateway for something this plugin had forgotten. The pack already persists a 90-day
|
|
21349
|
+
* ledger, so the same period was on disk the whole time.
|
|
21350
|
+
*
|
|
21351
|
+
* Day granularity on purpose: the ledger aggregates per (channel, account, model) per day
|
|
21352
|
+
* and keeps no per-call timestamps, so one `UsageCall` is synthesised per row carrying the
|
|
21353
|
+
* day's totals. That makes the TOTALS correct after a restart while the panel's
|
|
21354
|
+
* recent-calls list stays empty — inventing timestamps to fill a list would be worse than
|
|
21355
|
+
* an empty list.
|
|
21356
|
+
*
|
|
21357
|
+
* Returns how many rows were restored, so a startup note can say whether it worked.
|
|
21358
|
+
*/
|
|
21359
|
+
async function backfillUsageFromLedger(home, recorder, logger) {
|
|
21360
|
+
let text;
|
|
21361
|
+
try {
|
|
21362
|
+
text = await readFile(join(home, LEDGER_FILE), "utf8");
|
|
21363
|
+
} catch {
|
|
21364
|
+
return 0;
|
|
21365
|
+
}
|
|
21366
|
+
let parsed;
|
|
21367
|
+
try {
|
|
21368
|
+
parsed = JSON.parse(text);
|
|
21369
|
+
} catch {
|
|
21370
|
+
logger?.warn?.("dsh-xdpool: token ledger is not readable JSON; usage starts empty");
|
|
21371
|
+
return 0;
|
|
21372
|
+
}
|
|
21373
|
+
const daysNode = parsed.days;
|
|
21374
|
+
if (daysNode === null || typeof daysNode !== "object") return 0;
|
|
21375
|
+
const today = daysKey(/* @__PURE__ */ new Date());
|
|
21376
|
+
const day = daysNode[today];
|
|
21377
|
+
if (day === null || day === void 0 || typeof day !== "object") return 0;
|
|
21378
|
+
const calls = [];
|
|
21379
|
+
for (const [key, raw] of Object.entries(day)) {
|
|
21380
|
+
const named = parseLedgerKey(key);
|
|
21381
|
+
if (named === void 0 || named.mode !== POOL_MODE) continue;
|
|
21382
|
+
if (raw === null || typeof raw !== "object") continue;
|
|
21383
|
+
const entry = raw;
|
|
21384
|
+
const requests = numberOf(entry.requests);
|
|
21385
|
+
const input = numberOf(entry.inputTokens);
|
|
21386
|
+
const cache = numberOf(entry.cacheReadTokens);
|
|
21387
|
+
const output = numberOf(entry.outputTokens);
|
|
21388
|
+
calls.push({
|
|
21389
|
+
channel: named.channel,
|
|
21390
|
+
region: named.channel === "workbuddy" ? "global" : named.channel === "buddy" ? "cn" : "",
|
|
21391
|
+
model: named.model,
|
|
21392
|
+
account: "",
|
|
21393
|
+
turnId: `backfill:${today}:${named.channel}:${named.model}`,
|
|
21394
|
+
at: startOfLocalDay(/* @__PURE__ */ new Date()).getTime(),
|
|
21395
|
+
ok: requests > numberOf(entry.errors),
|
|
21396
|
+
usage: {
|
|
21397
|
+
inputTokens: input,
|
|
21398
|
+
outputTokens: output,
|
|
21399
|
+
cachedTokens: cache,
|
|
21400
|
+
reasoningTokens: numberOf(entry.reasoningTokens)
|
|
21401
|
+
}
|
|
21402
|
+
});
|
|
21403
|
+
}
|
|
21404
|
+
if (calls.length === 0) return 0;
|
|
21405
|
+
recorder.reset();
|
|
21406
|
+
for (const call of calls) recorder.record(call);
|
|
21407
|
+
logger?.info?.(`dsh-xdpool: restored ${String(calls.length)} usage row(s) for ${today} from the ledger`);
|
|
21408
|
+
return calls.length;
|
|
21409
|
+
}
|
|
21410
|
+
/** Local `YYYY-MM-DD`, matching how the pack names its day buckets. */
|
|
21411
|
+
function daysKey(now) {
|
|
21412
|
+
const year = now.getFullYear();
|
|
21413
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
21414
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
21415
|
+
return `${String(year)}-${month}-${day}`;
|
|
21416
|
+
}
|
|
21417
|
+
/** Midnight at the start of `now`'s local day. */
|
|
21418
|
+
function startOfLocalDay(now) {
|
|
21419
|
+
const copy = new Date(now.getTime());
|
|
21420
|
+
copy.setHours(0, 0, 0, 0);
|
|
21421
|
+
return copy;
|
|
21422
|
+
}
|
|
21295
21423
|
//#endregion
|
|
21296
21424
|
//#region src/relay.ts
|
|
21297
21425
|
/**
|
|
@@ -23301,6 +23429,21 @@ function apply(ctx, config = {}) {
|
|
|
23301
23429
|
recordStartupNote(`registration FAILED: ${describeError(error)}`);
|
|
23302
23430
|
return;
|
|
23303
23431
|
}
|
|
23432
|
+
/**
|
|
23433
|
+
* Refill today's usage from the pack's ledger before anything is served.
|
|
23434
|
+
*
|
|
23435
|
+
* `UsageRecorder` holds process memory, so a restart used to empty the usage panel
|
|
23436
|
+
* and the pool card — the card then printed "网关尚未上报 token 数", which was never
|
|
23437
|
+
* true: nothing had asked yet. The pack keeps a 90-day ledger on disk, so the same
|
|
23438
|
+
* day was already recorded and simply not read back.
|
|
23439
|
+
*
|
|
23440
|
+
* Best-effort and fire-and-forget: a ledger that cannot be read costs the panel its
|
|
23441
|
+
* pre-restart totals, never the plugin. It is deliberately not awaited — the first
|
|
23442
|
+
* request must not wait on a file read.
|
|
23443
|
+
*/
|
|
23444
|
+
backfillUsageFromLedger(dshHome(), usageRecorder, ctx.logger).catch((error) => {
|
|
23445
|
+
ctx.logger.warn?.("dsh-xdpool: usage backfill failed", error);
|
|
23446
|
+
});
|
|
23304
23447
|
if (stopped) return;
|
|
23305
23448
|
core.pool.scan().then((accounts) => {
|
|
23306
23449
|
ctx.logger.info?.(`dsh-xdpool: ${accounts.length} WorkBuddy account(s) in rotation`);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-xdpool",
|
|
3
3
|
"displayName": "DSH XD Pool",
|
|
4
4
|
"description": "One DSH model pool for every free quota you have: merge all locally signed-in WorkBuddy accounts with auto-failover, sign in to 13 vendors by QR/web, aggregate 76 vendors' free tiers, plus a usage dashboard, model routing, a local OpenAI-compatible gateway and a points-automation suite.",
|
|
5
|
-
"version": "2.6.
|
|
5
|
+
"version": "2.6.3",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "XDTrees",
|
|
8
8
|
"homepage": "https://github.com/XDTrees/dsh-xdpool#readme",
|