dsh-account-pool 0.2.1 → 0.2.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/README.md CHANGED
@@ -117,6 +117,19 @@ dsh plugin --profile web add dsh-account-pool
117
117
 
118
118
  全完成显示绿色 `3/3 已完成`,部分完成显示黄色 `2/3`,没做显示灰色 `0/3`。
119
119
 
120
+ **账号明细**表里还有一列「今日获得」,按账号显示本次到手的积分:
121
+
122
+ | 情况 | 显示 |
123
+ |---|---|
124
+ | 还没执行过 | `未执行` |
125
+ | 算出收益 | `+150`(有收益时绿色) |
126
+ | 收益为 0 | `+0`——「确实没加」与「不知道」是两回事 |
127
+ | 算不出 | `—`,不编造数字 |
128
+
129
+ 数值来自**执行前后的余额差**:签到接口只回成功/失败、不回奖励数量,
130
+ Trae 的任务流也没有计费字段,余额差是唯一可靠的真值。只把**正差值**
131
+ 算作获得(负差值可能是期间用了额度)。
132
+
120
133
  ### 只读与执行分离
121
134
 
122
135
  **打开页面只做只读查询,不会替你签到或派出。** 点「全部执行」才真正执行。
@@ -176,10 +189,15 @@ dsh plugin --profile web add dsh-account-pool
176
189
  |---|---|
177
190
  | 请求数 / 失败数 | **失败的尝试也计入请求数**,这样「重试放大」才看得出来 |
178
191
  | 输入 / 输出 / 总 tokens | 取自上游 SSE 末尾的 `usage` 帧 |
179
- | 积分消耗 | 上游 `usage.credit` |
192
+ | 积分消耗 | 上游 `usage.credit`。**只有逐次上报消耗的上游才有这一格**(WorkBuddy);Trae 的对话流没有计费字段,该指标整块隐藏,而不是显示恒为 0 |
180
193
  | 平均延迟 | 按样本数加权,不是对每桶均值再取平均 |
181
194
  | 按模型明细 | 每个模型的请求、失败、token、积分 |
182
195
 
196
+ > Trae 为什么没有积分消耗:它的对话流只有 6 种事件
197
+ > (`metadata` / `timing_cost` / `output` / `extra_info` / `token_usage` / `done`),
198
+ > 没有任何计费字段。积分接口虽然能给「账号累计已消耗」,但那是账号总量、
199
+ > 不是本页消耗,混着展示会误导,所以不提供。
200
+
183
201
  保留策略:按小时分桶;当桶数超过上限(20 万)时,把**超过 90 天**的小时桶
184
202
  折叠为日桶(日桶长期保留)。折叠由**桶数上限触发**,不是按时间定时执行——
185
203
  所以在流量不大的情况下,历史小时桶会原样保留,不会自动精简。
package/lib/client.js CHANGED
@@ -453,26 +453,6 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
453
453
  h("td", { style: Object.assign({}, TD_STYLE, { fontSize: "12px", opacity: 0.7 }) }, note || ""),
454
454
  );
455
455
 
456
- /**
457
- * 「今日获得积分」统计行。
458
- *
459
- * 数据来源是执行前后两次余额的差值(签到接口不回奖励数量,
460
- * Trae 的任务流也没有计费字段)。所以:
461
- * - 没执行过 → 显示「未执行」,不猜
462
- * - 执行过但算式缺数据 → 显示「—」
463
- * - 有数字 → 显示合计,部分账号缺数据时加注
464
- */
465
- const earnedRow = () => h("tr", { key: "earned" },
466
- h("td", { style: Object.assign({}, TD_STYLE, { fontWeight: 600 }) }, "今日获得积分"),
467
- h("td", { style: Object.assign({}, TD_STYLE, NUM_STYLE, { color: "var(--dsw-alias-state-success-primary)" }) },
468
- typeof lastRun?.totalEarned === "number"
469
- ? "+" + formatNumber(lastRun.totalEarned)
470
- : h("span", { style: { opacity: 0.5 } }, lastRun ? "—" : "未执行")),
471
- h("td", { style: Object.assign({}, TD_STYLE, { fontSize: "12px", opacity: 0.7 }) },
472
- typeof lastRun?.totalEarned === "number"
473
- ? (lastRun.earnedPartial ? "部分账号余额未取到,合计可能偏低" : "执行前后余额之差")
474
- : "点「全部执行」后统计"),
475
- );
476
456
 
477
457
  return h("div", null,
478
458
  h("div", { style: CARD_STYLE },
@@ -501,7 +481,7 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
501
481
  const map = {};
502
482
  for (const r of out.results || []) map[r.accountId] = r;
503
483
  // 把「本次获得的总积分」也带进 lastRun,供统计行显示
504
- onRunDone(map, { totalEarned: out.totalEarned, earnedPartial: out.earnedPartial });
484
+ onRunDone(map);
505
485
  // 提示里带上实际到手的积分,比干巴巴一句"已完成"有用
506
486
  setNotice(region, "ok", grantedText(out.totalEarned, "每日任务"));
507
487
  // 重新拉只读状态,让旅行/抽奖/连登的完成数同步更新
@@ -535,7 +515,6 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
535
515
  taskRow("猫猫旅行", travelDone, "到站领奖 · 空闲派出", "今日已派或在途都算完成"),
536
516
  taskRow("连登兑换", redeemDone, "满 7/14/28 天可兑换", redeemPending > 0 ? redeemPending + " 个账号有可兑换档位" : "没有待兑换的档位"),
537
517
  taskRow("抽奖", lotteryDone, "次数来自连登兑换", lotteryPending > 0 ? lotteryPending + " 个账号有抽奖次数" : "暂无抽奖次数"),
538
- earnedRow(),
539
518
  ),
540
519
  ),
541
520
 
@@ -555,6 +534,7 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
555
534
  h("th", { style: TH_STYLE }, "猫"),
556
535
  h("th", { style: TH_STYLE }, "旅行"),
557
536
  h("th", { style: TH_STYLE }, "抽奖次数"),
537
+ h("th", { style: TH_STYLE }, "今日获得"),
558
538
  )),
559
539
  h("tbody", null, accounts.map(a => h("tr", { key: a.accountId },
560
540
  h("td", { style: TD_STYLE }, a.nickname || a.accountId?.slice(0, 8) || "—"),
@@ -580,6 +560,7 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
580
560
  : h("span", { style: { opacity: 0.5 } }, "—")),
581
561
  h("td", { style: TD_STYLE },
582
562
  a.lottery !== undefined ? String(a.lottery.chances) : h("span", { style: { opacity: 0.5 } }, "—")),
563
+ h("td", { style: TD_STYLE }, earnedCell(a, lastRun)),
583
564
  ))),
584
565
  ),
585
566
  ),
@@ -633,6 +614,33 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
633
614
  return what + "完成,获得 " + formatNumber(totalEarned) + " 积分";
634
615
  }
635
616
 
617
+ /**
618
+ * 账号明细里的「今日获得」单元格。
619
+ *
620
+ * 取值优先级:
621
+ * 1. account.earnedCredits —— 后端台账里的**今日累计**,打开页面就有
622
+ * 2. lastRun[id].earnedCredits —— 刚执行完的最新值(台账已含它,
623
+ * 这里是防状态还没刷新的兜底)
624
+ *
625
+ * 为什么不是"本次差值":同一天重复点执行时签到幂等、余额不变,
626
+ * 差值会是 0,但今天确实拿到过积分。台账按天累加才是用户要的口径。
627
+ *
628
+ * 三态刻意区分开:
629
+ * 有数字 → +N(有收益为绿);0 也显示 +0,因为「确实没加」与
630
+ * 「不知道」不同
631
+ * 没数字 → —;从没执行过也走这里(后端台账为空)
632
+ */
633
+ function earnedCell(account, lastRun) {
634
+ const run = lastRun?.[account.accountId];
635
+ const earned = typeof account.earnedCredits === "number"
636
+ ? account.earnedCredits
637
+ : run?.earnedCredits;
638
+ if (typeof earned !== "number") return h("span", { style: { opacity: 0.5 } }, "—");
639
+ return h("span", {
640
+ style: { color: earned > 0 ? "var(--dsw-alias-state-success-primary)" : "inherit" },
641
+ }, "+" + formatNumber(earned));
642
+ }
643
+
636
644
  function checkinCell(account) {
637
645
  if (account.checkinError) {
638
646
  return h("span", { style: { color: "var(--dsw-alias-state-error-primary)", fontSize: "12px" } }, "查询失败");
@@ -665,23 +673,6 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
665
673
 
666
674
  const sumCredits = accounts.reduce((sum, a) => sum + (typeof a.credits === "number" ? a.credits : 0), 0);
667
675
 
668
- /**
669
- * 「今日获得积分」统计行。
670
- *
671
- * 与 WorkBuddy 同源:执行前后两次余额的差值。Trae 的签到接口
672
- * 只回成功/失败,任务流也没有计费字段,差值是真值。
673
- */
674
- const earnedRow = () => h("tr", { key: "earned" },
675
- h("td", { style: Object.assign({}, TD_STYLE, { fontWeight: 600 }) }, "今日获得积分"),
676
- h("td", { style: Object.assign({}, TD_STYLE, NUM_STYLE, { color: "var(--dsw-alias-state-success-primary)" }) },
677
- typeof lastRun?.totalEarned === "number"
678
- ? "+" + formatNumber(lastRun.totalEarned)
679
- : h("span", { style: { opacity: 0.5 } }, lastRun ? "—" : "未执行")),
680
- h("td", { style: Object.assign({}, TD_STYLE, { fontSize: "12px", opacity: 0.7 }) },
681
- typeof lastRun?.totalEarned === "number"
682
- ? (lastRun.earnedPartial ? "部分账号余额未取到,合计可能偏低" : "执行前后余额之差")
683
- : "点「全部执行」后统计"),
684
- );
685
676
 
686
677
  const taskRow = (name, done, note) => h("tr", { key: name },
687
678
  h("td", { style: TD_STYLE }, name),
@@ -715,7 +706,7 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
715
706
  if (!out.ok) throw new Error(out.error || "执行失败");
716
707
  const map = {};
717
708
  for (const r of out.results || []) map[r.accountId] = r;
718
- onRunDone(map, { totalEarned: out.totalEarned, earnedPartial: out.earnedPartial });
709
+ onRunDone(map);
719
710
  setNotice(region, "ok", grantedText(out.totalEarned, "签到"));
720
711
  await reload();
721
712
  } catch (error) {
@@ -745,7 +736,6 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
745
736
  h("tbody", null,
746
737
  taskRow("每日签到", checkedIn, "每天一次,先查状态再领取,不会重复领"),
747
738
  taskRow("积分查询", withCredits, total > 0 ? "当前合计 " + formatNumber(sumCredits) + " 积分" : "只读查询,不消耗额度"),
748
- earnedRow(),
749
739
  ),
750
740
  ),
751
741
  ),
@@ -759,6 +749,7 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
759
749
  h("th", { style: TH_STYLE }, "账号"),
760
750
  h("th", { style: TH_STYLE }, "今日签到"),
761
751
  h("th", { style: TH_STYLE }, "积分"),
752
+ h("th", { style: TH_STYLE }, "今日获得"),
762
753
  )),
763
754
  h("tbody", null, accounts.map(a => h("tr", { key: a.accountId },
764
755
  h("td", { style: TD_STYLE }, a.nickname || String(a.accountId).slice(0, 8)),
@@ -767,6 +758,7 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
767
758
  a.creditsError
768
759
  ? "—"
769
760
  : typeof a.credits === "number" ? formatNumber(a.credits) : "—"),
761
+ h("td", { style: TD_STYLE }, earnedCell(a, lastRun)),
770
762
  ))),
771
763
  ),
772
764
  )
@@ -1750,24 +1742,10 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
1750
1742
  const timer = setInterval(() => setTick(t => t + 1), 1000);
1751
1743
  return () => clearInterval(timer);
1752
1744
  }, []);
1753
- // 上次每日任务的执行结果:签到没有只读接口,靠它显示完成情况。
1754
- // 除了每账号结果(key 是 accountId),还带两个汇总结算字段:
1755
- // totalEarned 本次获得的总积分(执行前后余额之差)
1756
- // earnedPartial 是否有账号算不出差值(合计可能偏低)
1757
- const [lastRun, setLastRunState] = useState(null);
1758
- /**
1759
- * 保存执行结果。
1760
- *
1761
- * 按 accountId 建立索引,同时把汇总字段挂在同一个对象上——
1762
- * 这样「按账号取结果」和「读合计」共用一份状态,不用两个 state
1763
- * 引起额外的重渲染。
1764
- */
1765
- const setLastRun = useCallback((results, summary) => {
1766
- setLastRunState(Object.assign({}, results, {
1767
- totalEarned: summary?.totalEarned,
1768
- earnedPartial: summary?.earnedPartial === true,
1769
- }));
1770
- }, []);
1745
+ // 上次每日任务的执行结果,按 accountId 索引。
1746
+ // 清单里的「完成数」和明细里的「今日获得」都从它取
1747
+ //(签到没有只读接口,这些只能靠执行结果)。
1748
+ const [lastRun, setLastRun] = useState(null);
1771
1749
  const pollTimer = useRef(null);
1772
1750
 
1773
1751
  /** 给某个区域设一条提示信息。 */
@@ -0,0 +1,146 @@
1
+ /**
2
+ * 每日积分收益记录器。
3
+ *
4
+ * ## 为什么需要它
5
+ *
6
+ * 「今日获得」不能直接用「执行前后余额差」表示——那个差值只反映
7
+ * **本次执行**的变化:
8
+ *
9
+ * ```text
10
+ * 第 1 次点执行 未签到 → 领取 → 余额 +150 显示 +150 ✓
11
+ * 第 2 次点执行 已签到 → 无变化 → 余额 +0 显示 0 ✗
12
+ * ```
13
+ *
14
+ * 同一天重复点执行(幂等),第二次就开始显示 0,看起来像"没获得积分",
15
+ * 但今天确实拿到过 150。用户真正想知道的是**今天累计获得了多少**。
16
+ *
17
+ * 所以这里把每次算出的**正收益累加**并按天保存;跨天自动归零,
18
+ * 不需要清理任务。
19
+ *
20
+ * ## 为什么只累加正差值
21
+ *
22
+ * 差值为负说明期间有别的消耗(对话),不是"获得了负数积分"。
23
+ * 只有变多才算收益。另外重复减去同一笔收益不可能发生——每次执行
24
+ * 的差值本身就是增量。
25
+ */
26
+
27
+ import { readFile, writeFile, rename, mkdir } from 'node:fs/promises'
28
+ import { dirname, join } from 'node:path'
29
+
30
+ /** 文件格式版本,字段不兼容时便于识别旧数据。 */
31
+ const FILE_VERSION = 1
32
+
33
+ /** 把时间戳转成本地日期键(YYYY-MM-DD),与用户看到的"今天"一致。 */
34
+ export function dayKeyOf(timestamp = Date.now()) {
35
+ const d = new Date(timestamp)
36
+ const pad = (n) => String(n).padStart(2, '0')
37
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
38
+ }
39
+
40
+ /**
41
+ * 按天的积分收益台账。
42
+ *
43
+ * 结构:`{ version, days: { '2026-09-21': { [accountId]: 150 } } }`
44
+ * 只保留最近若干天,避免文件无限增长。
45
+ */
46
+ export class DailyEarningsRecorder {
47
+ /**
48
+ * @param {object} options
49
+ * @param {string} options.file 落盘路径
50
+ * @param {number} [options.keepDays] 保留多少天(默认 30)
51
+ */
52
+ constructor({ file, keepDays = 30 }) {
53
+ this.file = file
54
+ this.keepDays = keepDays
55
+ /** @type {Record<string, Record<string, number>>} */
56
+ this.days = {}
57
+ this.loaded = false
58
+ /** 写操作串行:read→改→write 不是原子的,并发会互相覆盖。 */
59
+ this.writeQueue = Promise.resolve()
60
+ /** 待写入标记:多次累加合并成一次写盘。 */
61
+ this.dirty = false
62
+ }
63
+
64
+ /** 读盘(幂等)。文件不存在或损坏时按空台账处理,不抛错。 */
65
+ async load() {
66
+ if (this.loaded) return
67
+ this.loaded = true
68
+ try {
69
+ const doc = JSON.parse(await readFile(this.file, 'utf8'))
70
+ if (doc?.version === FILE_VERSION && typeof doc.days === 'object' && doc.days !== null) {
71
+ this.days = doc.days
72
+ }
73
+ } catch {
74
+ // 首次运行或文件损坏:保持空台账
75
+ }
76
+ }
77
+
78
+ /**
79
+ * 累加一个账号今天的收益,返回它今天的累计值。
80
+ *
81
+ * @param {string} accountId 账号 id
82
+ * @param {number} delta 本次新增的收益(<=0 时直接返回当前累计,不记负数)
83
+ * @param {number} [now] 当前时间戳(便于测试)
84
+ * @returns {Promise<number>} 该账号今日累计收益
85
+ */
86
+ async add(accountId, delta, now = Date.now()) {
87
+ await this.load()
88
+ const key = dayKeyOf(now)
89
+ const today = this.days[key] ?? (this.days[key] = {})
90
+ const current = typeof today[accountId] === 'number' ? today[accountId] : 0
91
+ if (typeof delta !== 'number' || !Number.isFinite(delta) || delta <= 0) {
92
+ return current
93
+ }
94
+ today[accountId] = Math.round((current + delta) * 100) / 100
95
+ this.dirty = true
96
+ await this.flush()
97
+ return today[accountId]
98
+ }
99
+
100
+ /**
101
+ * 读某天各账号的累计收益。
102
+ *
103
+ * @param {number} [now] 当前时间戳(便于测试)
104
+ * @returns {Promise<Record<string, number>>}
105
+ */
106
+ async today(now = Date.now()) {
107
+ await this.load()
108
+ return this.days[dayKeyOf(now)] ?? {}
109
+ }
110
+
111
+ /** 落盘(先串行化,再清理过期天数)。 */
112
+ async flush() {
113
+ if (!this.dirty) return
114
+ this.dirty = false
115
+ this.prune()
116
+ const payload = JSON.stringify({ version: FILE_VERSION, days: this.days }, null, 0)
117
+ this.writeQueue = this.writeQueue.then(async () => {
118
+ try {
119
+ await mkdir(dirname(this.file), { recursive: true })
120
+ // 先写临时文件再改名:避免写一半崩溃留下损坏的 JSON
121
+ const tmp = `${this.file}.${process.pid}.tmp`
122
+ await writeFile(tmp, payload, { encoding: 'utf8', mode: 0o600 })
123
+ await rename(tmp, this.file)
124
+ } catch {
125
+ // 落盘失败不影响本次收益的正常返回;下次累加会再试
126
+ }
127
+ })
128
+ await this.writeQueue
129
+ }
130
+
131
+ /** 只保留最近 keepDays 天,防止文件无限增长。 */
132
+ prune() {
133
+ const keys = Object.keys(this.days).sort()
134
+ if (keys.length <= this.keepDays) return
135
+ for (const key of keys.slice(0, keys.length - this.keepDays)) {
136
+ delete this.days[key]
137
+ }
138
+ }
139
+ }
140
+
141
+ /** 按区域打开台账(每个区域一份,与凭证/用量文件命名一致)。 */
142
+ export function openDailyEarnings(dshHome, region) {
143
+ return new DailyEarningsRecorder({
144
+ file: join(dshHome, `.account-pool.earnings.${region}.json`),
145
+ })
146
+ }
package/lib/index.js CHANGED
@@ -35,6 +35,7 @@ import { fetchModels, fetchCredits } from './upstream.js'
35
35
  import { AccountPicker } from './selector.js'
36
36
  import { createShim } from './shim.js'
37
37
  import { UsageRecorder } from './usage.js'
38
+ import { openDailyEarnings } from './earnings.js'
38
39
  import { runDailyTasks, fetchStreak, fetchBuddy, fetchLottery, travelStatus as fetchTravelStatus } from './tasks.js'
39
40
  import { openTraeStore, accountIdOf as accountIdOfTrae } from './trae-accounts.js'
40
41
  import { parseTraeStorage } from './trae-storage.js'
@@ -220,6 +221,9 @@ async function createRegionStack({ region, dshHome, config, logger, ctx }) {
220
221
  ],
221
222
  })
222
223
  await usage.load()
224
+ // 每日积分收益台账:同一天多次执行要能累计,不能只看本次差值。
225
+ const earnings = openDailyEarnings(dshHome, region)
226
+ await earnings.load()
223
227
  const picker = new AccountPicker({
224
228
  softCooldownMs: (config.softCooldownSeconds ?? 600) * 1000,
225
229
  breakerThreshold: config.breakerThreshold ?? 3,
@@ -373,6 +377,7 @@ async function createRegionStack({ region, dshHome, config, logger, ctx }) {
373
377
  shim,
374
378
  adapter,
375
379
  usage,
380
+ earnings,
376
381
  refreshCatalog,
377
382
  loadCredits,
378
383
  getCatalog: () => catalog,
@@ -981,24 +986,38 @@ export function apply(ctx, config = {}) {
981
986
  const after = await stack.loadCredits(true).catch(() => ({}))
982
987
 
983
988
  /**
984
- * 算出每个账号这次获得的积分。
989
+ * 算出每个账号本次的增量,并**累加进今天的台账**。
985
990
  *
986
- * 只把**正差值**当作「获得」:
987
- * - 差值为正 → 签到/活动奖励到账
988
- * - 差值为负 → 期间有别的消耗(对话),不算获得,记 0
989
- * - 两边都取不到 → 标 unknown,界面显示 —,不编造数字
991
+ * 为什么要台账:差值只反映"本次执行"的变化。同一天再点一次
992
+ * 执行(签到幂等,余额不变)差值就是 0——但今天确实拿到过积分。
993
+ * 界面要显示的是"今天累计获得多少",所以按天累加并持久化,
994
+ * 跨天自动归零(按本地日期分键)。
995
+ *
996
+ * 只把**正差值**算作获得:差值为负说明期间有别的消耗(对话),
997
+ * 不是"获得负数"。
990
998
  */
991
999
  const earnedOf = (accountId) => {
992
1000
  const b = before[accountId]?.total
993
1001
  const a = after[accountId]?.total
994
1002
  if (typeof b !== 'number' || typeof a !== 'number') return undefined
995
1003
  const delta = a - b
996
- // 负差值可能是「期间用了额度」,不算获得;只取正收益
997
1004
  return delta > 0 ? Math.round(delta * 100) / 100 : 0
998
1005
  }
999
1006
  for (const r of results) {
1000
- const earned = earnedOf(r.accountId)
1001
- if (earned !== undefined) r.earnedCredits = earned
1007
+ const delta = earnedOf(r.accountId)
1008
+ if (delta === undefined) continue
1009
+ // add() 返回该账号**今日累计**值;delta<=0 时原样返回已有累计,
1010
+ // 所以重复执行不会把数字冲掉
1011
+ r.earnedCredits = await stack.earnings.add(r.accountId, delta)
1012
+ }
1013
+
1014
+ // 补上今天已经累计、但本次没参与执行(或算不出差值)的账号,
1015
+ // 否则它们的「今日获得」会显示成"未执行"或"—"。
1016
+ const todayEarnings = await stack.earnings.today()
1017
+ for (const r of results) {
1018
+ if (typeof r.earnedCredits === 'number') continue
1019
+ const accumulated = todayEarnings[r.accountId]
1020
+ if (typeof accumulated === 'number') r.earnedCredits = accumulated
1002
1021
  }
1003
1022
 
1004
1023
  const totalEarned = results.reduce(
@@ -1007,8 +1026,6 @@ export function apply(ctx, config = {}) {
1007
1026
  ok: true,
1008
1027
  results,
1009
1028
  totalEarned: Math.round(totalEarned * 100) / 100,
1010
- // 有账号算不出差值时,界面要对「合计」加注说明,避免误以为是全部
1011
- earnedPartial: results.some(r => typeof r.earnedCredits !== 'number'),
1012
1029
  })
1013
1030
  } catch (error) {
1014
1031
  json(res, 500, { ok: false, error: safeMessage(error) })
@@ -1031,6 +1048,10 @@ export function apply(ctx, config = {}) {
1031
1048
  const stack = list.find(s => s.region === region)
1032
1049
  if (stack === undefined) { json(res, 503, { ok: false, error: '该区域尚未初始化' }); return }
1033
1050
 
1051
+ // 今日已累计的收益:让「今日获得」在打开页面时就有值,
1052
+ // 不必等到点过执行(台账里存的是按天累计值)。
1053
+ const todayEarnings = await stack.earnings.today()
1054
+
1034
1055
  const out = []
1035
1056
  for (const account of await stack.store.list()) {
1036
1057
  try {
@@ -1048,6 +1069,7 @@ export function apply(ctx, config = {}) {
1048
1069
  creditsSize: status.creditsSize,
1049
1070
  ...(status.checkinError === undefined ? {} : { checkinError: status.checkinError }),
1050
1071
  ...(status.creditsError === undefined ? {} : { creditsError: status.creditsError }),
1072
+ earnedCredits: todayEarnings[account.id],
1051
1073
  })
1052
1074
  continue
1053
1075
  }
@@ -1067,6 +1089,7 @@ export function apply(ctx, config = {}) {
1067
1089
  buddy,
1068
1090
  travel: travelNow,
1069
1091
  lottery,
1092
+ earnedCredits: todayEarnings[account.id],
1070
1093
  ...(credential.expiresAtMs === undefined ? {} : { expiresAtMs: credential.expiresAtMs }),
1071
1094
  })
1072
1095
  } catch (error) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-account-pool",
3
3
  "displayName": "Account Pool",
4
- "version": "0.2.1",
4
+ "version": "0.2.3",
5
5
  "description": "把多个 WorkBuddy / Trae 账号汇成账号池接入 DeepSeek Harness:自动切换、限流熔断退避、凭证安全落盘",
6
6
  "type": "module",
7
7
  "license": "MIT",