dsh-account-pool 0.2.0 → 0.2.2

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,14 @@ dsh plugin --profile web add dsh-account-pool
117
117
 
118
118
  全完成显示绿色 `3/3 已完成`,部分完成显示黄色 `2/3`,没做显示灰色 `0/3`。
119
119
 
120
+ 清单底部还有一行**今日获得积分**:点「全部执行」后,用**执行前后的余额差**
121
+ 算出这次实际到手的积分。
122
+
123
+ 为什么用差值:签到接口只回成功/失败、不回奖励数量,Trae 的任务流也没有
124
+ 计费字段——余额差是唯一可靠的真值。只把**正差值**算作获得(负差值可能是
125
+ 期间用了额度);算不出时显示 `—` 而不是编造数字;部分账号缺数据时会注明
126
+ 「合计可能偏低」。
127
+
120
128
  ### 只读与执行分离
121
129
 
122
130
  **打开页面只做只读查询,不会替你签到或派出。** 点「全部执行」才真正执行。
@@ -176,10 +184,15 @@ dsh plugin --profile web add dsh-account-pool
176
184
  |---|---|
177
185
  | 请求数 / 失败数 | **失败的尝试也计入请求数**,这样「重试放大」才看得出来 |
178
186
  | 输入 / 输出 / 总 tokens | 取自上游 SSE 末尾的 `usage` 帧 |
179
- | 积分消耗 | 上游 `usage.credit` |
187
+ | 积分消耗 | 上游 `usage.credit`。**只有逐次上报消耗的上游才有这一格**(WorkBuddy);Trae 的对话流没有计费字段,该指标整块隐藏,而不是显示恒为 0 |
180
188
  | 平均延迟 | 按样本数加权,不是对每桶均值再取平均 |
181
189
  | 按模型明细 | 每个模型的请求、失败、token、积分 |
182
190
 
191
+ > Trae 为什么没有积分消耗:它的对话流只有 6 种事件
192
+ > (`metadata` / `timing_cost` / `output` / `extra_info` / `token_usage` / `done`),
193
+ > 没有任何计费字段。积分接口虽然能给「账号累计已消耗」,但那是账号总量、
194
+ > 不是本页消耗,混着展示会误导,所以不提供。
195
+
183
196
  保留策略:按小时分桶;当桶数超过上限(20 万)时,把**超过 90 天**的小时桶
184
197
  折叠为日桶(日桶长期保留)。折叠由**桶数上限触发**,不是按时间定时执行——
185
198
  所以在流量不大的情况下,历史小时桶会原样保留,不会自动精简。
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,27 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
633
614
  return what + "完成,获得 " + formatNumber(totalEarned) + " 积分";
634
615
  }
635
616
 
617
+ /**
618
+ * 账号明细里的「今日获得」单元格。
619
+ *
620
+ * 数值来自本次执行的**余额前后差**(后端在 tasks/daily 里算好,
621
+ * 挂在每账号结果的 earnedCredits 上)。
622
+ *
623
+ * 三态刻意区分开:
624
+ * 没执行过 → 未执行(灰)
625
+ * 算出来了 → +N(绿);0 也显示 +0,因为「确实没加」与「不知道」不同
626
+ * 算不出 → —(灰),不编造
627
+ */
628
+ function earnedCell(account, lastRun) {
629
+ const run = lastRun?.[account.accountId];
630
+ if (!run) return h("span", { style: { opacity: 0.5 } }, "未执行");
631
+ const earned = run.earnedCredits;
632
+ if (typeof earned !== "number") return h("span", { style: { opacity: 0.5 } }, "—");
633
+ return h("span", {
634
+ style: { color: earned > 0 ? "var(--dsw-alias-state-success-primary)" : "inherit" },
635
+ }, "+" + formatNumber(earned));
636
+ }
637
+
636
638
  function checkinCell(account) {
637
639
  if (account.checkinError) {
638
640
  return h("span", { style: { color: "var(--dsw-alias-state-error-primary)", fontSize: "12px" } }, "查询失败");
@@ -665,23 +667,6 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
665
667
 
666
668
  const sumCredits = accounts.reduce((sum, a) => sum + (typeof a.credits === "number" ? a.credits : 0), 0);
667
669
 
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
670
 
686
671
  const taskRow = (name, done, note) => h("tr", { key: name },
687
672
  h("td", { style: TD_STYLE }, name),
@@ -715,7 +700,7 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
715
700
  if (!out.ok) throw new Error(out.error || "执行失败");
716
701
  const map = {};
717
702
  for (const r of out.results || []) map[r.accountId] = r;
718
- onRunDone(map, { totalEarned: out.totalEarned, earnedPartial: out.earnedPartial });
703
+ onRunDone(map);
719
704
  setNotice(region, "ok", grantedText(out.totalEarned, "签到"));
720
705
  await reload();
721
706
  } catch (error) {
@@ -745,7 +730,6 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
745
730
  h("tbody", null,
746
731
  taskRow("每日签到", checkedIn, "每天一次,先查状态再领取,不会重复领"),
747
732
  taskRow("积分查询", withCredits, total > 0 ? "当前合计 " + formatNumber(sumCredits) + " 积分" : "只读查询,不消耗额度"),
748
- earnedRow(),
749
733
  ),
750
734
  ),
751
735
  ),
@@ -759,6 +743,7 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
759
743
  h("th", { style: TH_STYLE }, "账号"),
760
744
  h("th", { style: TH_STYLE }, "今日签到"),
761
745
  h("th", { style: TH_STYLE }, "积分"),
746
+ h("th", { style: TH_STYLE }, "今日获得"),
762
747
  )),
763
748
  h("tbody", null, accounts.map(a => h("tr", { key: a.accountId },
764
749
  h("td", { style: TD_STYLE }, a.nickname || String(a.accountId).slice(0, 8)),
@@ -767,6 +752,7 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
767
752
  a.creditsError
768
753
  ? "—"
769
754
  : typeof a.credits === "number" ? formatNumber(a.credits) : "—"),
755
+ h("td", { style: TD_STYLE }, earnedCell(a, lastRun)),
770
756
  ))),
771
757
  ),
772
758
  )
@@ -953,30 +939,11 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
953
939
  const t = usage.totals || {};
954
940
  const models = usage.byModel || [];
955
941
  const totalCalls = t.requests || 0;
942
+ // 上游是否逐次上报积分消耗。默认按「支持」走,避免老数据缺字段时
943
+ // 把指标整块藏掉;只有后端明确说 false 才隐藏。
944
+ const reportsCredits = usage.reportsPerRequestCredits !== false;
956
945
  const failRate = totalCalls > 0 ? (t.errors || 0) / totalCalls : 0;
957
946
 
958
- /**
959
- * 积分消耗指标。
960
- *
961
- * 优先用「按请求累加」的值;它为 0 而上游给了累计消耗时,改用
962
- * 累计值并注明口径不同——这两个数字含义不一样,不能混着看:
963
- * 按请求累加:本页时间窗口内消耗了多少
964
- * 累计已消耗:账号从开通到现在的总量(不受时间窗口影响)
965
- */
966
- const creditsKpi = (totals, whole) => {
967
- const perRequest = typeof totals.credits === "number" ? totals.credits : 0;
968
- const upstream = whole.upstreamConsumed;
969
- if (perRequest > 0) {
970
- return kpi("积分消耗", formatNumber(perRequest), { full: formatNumber(perRequest) + " 积分" });
971
- }
972
- if (typeof upstream === "number" && upstream > 0) {
973
- return kpi("累计消耗", formatNumber(upstream), {
974
- full: "上游未上报单次消耗,这里显示账号累计已消耗 " + formatNumber(upstream) + " 积分",
975
- });
976
- }
977
- return kpi("积分消耗", formatNumber(perRequest), { full: formatNumber(perRequest) + " 积分" });
978
- };
979
-
980
947
  /** 核心指标块:大号数字 + 小标签。 */
981
948
  const kpi = (label, value, opts = {}) => h("div", {
982
949
  style: {
@@ -1073,7 +1040,8 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
1073
1040
  // 用 grid 而不是 flex-wrap:wrap 在窄面板里会把第 4 块挤到下一行,
1074
1041
  // grid 的 1fr 会按可用宽度均分,永远保持一行。
1075
1042
  h("div", {
1076
- className: "wb-kpi-grid-4",
1043
+ // 少一个指标时用 3 列网格,避免留下一个空位
1044
+ className: reportsCredits ? "wb-kpi-grid-4" : "wb-kpi-grid-3",
1077
1045
  style: {
1078
1046
  display: "grid",
1079
1047
  gridTemplateColumns: "repeat(4, minmax(0, 1fr))",
@@ -1091,12 +1059,14 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
1091
1059
  // 积分精确到个位、不缩写:它是能对账的数字,
1092
1060
  // 显示成 8.4K 会让人不知道到底扣了多少。
1093
1061
  //
1094
- // 两种口径:
1095
- // 按请求累加(t.credits)—— 上游逐次上报时可用(WorkBuddy)
1096
- // 累计已消耗(upstreamConsumed)—— 上游不报单次时用(Trae)
1097
- // Trae 的对话流没有计费字段,所以逐次累加恒为 0;那种情况下
1098
- // 显示上游累计值,并在悬停里说明口径,避免误读成"没消耗"。
1099
- creditsKpi(t, usage),
1062
+ // 上游不逐次上报消耗时整块隐藏(Trae 的对话流没有计费
1063
+ // 字段,累加恒为 0)。显示一个恒为 0 的指标比不显示更糟:
1064
+ // 用户会以为「没消耗」或「统计坏了」,而真相是上游不给。
1065
+ // 也不拿积分接口的累计值来凑数——那是账号总量,不是本页
1066
+ // 消耗,混着展示会直接误导。
1067
+ reportsCredits
1068
+ ? kpi("积分消耗", formatNumber(t.credits), { full: formatNumber(t.credits) + " 积分" })
1069
+ : null,
1100
1070
  // 延迟只统计成功请求。没有成功请求时显示 —,
1101
1071
  // 而不是误导性的 0(或把失败请求的快速返回当成"很快")。
1102
1072
  h("div", {
@@ -1766,24 +1736,10 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
1766
1736
  const timer = setInterval(() => setTick(t => t + 1), 1000);
1767
1737
  return () => clearInterval(timer);
1768
1738
  }, []);
1769
- // 上次每日任务的执行结果:签到没有只读接口,靠它显示完成情况。
1770
- // 除了每账号结果(key 是 accountId),还带两个汇总结算字段:
1771
- // totalEarned 本次获得的总积分(执行前后余额之差)
1772
- // earnedPartial 是否有账号算不出差值(合计可能偏低)
1773
- const [lastRun, setLastRunState] = useState(null);
1774
- /**
1775
- * 保存执行结果。
1776
- *
1777
- * 按 accountId 建立索引,同时把汇总字段挂在同一个对象上——
1778
- * 这样「按账号取结果」和「读合计」共用一份状态,不用两个 state
1779
- * 引起额外的重渲染。
1780
- */
1781
- const setLastRun = useCallback((results, summary) => {
1782
- setLastRunState(Object.assign({}, results, {
1783
- totalEarned: summary?.totalEarned,
1784
- earnedPartial: summary?.earnedPartial === true,
1785
- }));
1786
- }, []);
1739
+ // 上次每日任务的执行结果,按 accountId 索引。
1740
+ // 清单里的「完成数」和明细里的「今日获得」都从它取
1741
+ //(签到没有只读接口,这些只能靠执行结果)。
1742
+ const [lastRun, setLastRun] = useState(null);
1787
1743
  const pollTimer = useRef(null);
1788
1744
 
1789
1745
  /** 给某个区域设一条提示信息。 */
package/lib/index.js CHANGED
@@ -355,6 +355,16 @@ async function createRegionStack({ region, dshHome, config, logger, ctx }) {
355
355
  region,
356
356
  providerId,
357
357
  isTrae,
358
+ /**
359
+ * 上游是否**逐次上报**积分消耗。
360
+ *
361
+ * WorkBuddy 的响应里带 credit 字段,能按请求累加;
362
+ * Trae 的对话流只有六种事件(metadata/timing_cost/output/
363
+ * extra_info/token_usage/done),没有任何计费字段——它只在
364
+ * 积分接口给一个累计值。那个累计值不能当「本页消耗」展示
365
+ * (含义不同,会误导),所以这种情况下界面直接不显示该指标。
366
+ */
367
+ reportsPerRequestCredits: !isTrae,
358
368
  runTasks,
359
369
  // 只读状态:Trae 用 readStatus,WorkBuddy 用现有的那组查询
360
370
  readStatus: isTrae ? traeReadStatus : undefined,
@@ -1082,31 +1092,12 @@ export function apply(ctx, config = {}) {
1082
1092
  const list = await ready
1083
1093
  const out = {}
1084
1094
  for (const stack of list) {
1085
- const snapshot = stack.usage.snapshot(hours)
1086
- /**
1087
- * 上游不报单次消耗时,用积分接口的**累计已消耗**做真值。
1088
- *
1089
- * Trae 的对话流没有任何计费字段(实测只有 metadata /
1090
- * timing_cost / output / extra_info / token_usage / done
1091
- * 六种事件),所以按请求累加的 credits 恒为 0。它的积分
1092
- * 接口能给出累计已消耗,虽然分不到「哪次请求」,但至少
1093
- * 是真实数字,比显示 0 有意义。
1094
- *
1095
- * 用缓存(不强制刷新)以免打开用量页就打一轮上游。
1096
- */
1097
- const credits = await stack.loadCredits().catch(() => ({}))
1098
- let consumedTotal = 0
1099
- let hasConsumed = false
1100
- for (const account of Object.values(credits)) {
1101
- if (typeof account?.consumed === 'number') {
1102
- consumedTotal += account.consumed
1103
- hasConsumed = true
1104
- }
1105
- }
1095
+ // 只给「本页时间窗口内按请求累加」这一种口径。
1096
+ // 上游不逐次上报时(Trae),该指标没有真实数据可展示——
1097
+ // 界面据此隐藏它,而不是显示恒为 0 的数字。
1106
1098
  out[stack.region] = {
1107
- ...snapshot,
1108
- // 上游累计口径(目前只有 Trae 有);undefined 表示这家不支持
1109
- upstreamConsumed: hasConsumed ? Math.round(consumedTotal * 100) / 100 : undefined,
1099
+ ...stack.usage.snapshot(hours),
1100
+ reportsPerRequestCredits: stack.reportsPerRequestCredits,
1110
1101
  }
1111
1102
  }
1112
1103
  json(res, 200, { ok: true, regions: out })
@@ -421,15 +421,14 @@ export async function fetchCredits(credential) {
421
421
  const consumed = Number(summary?.consumed_amount)
422
422
  if (Number.isFinite(totalAmount) && Number.isFinite(consumed)) {
423
423
  // 保留两位小数:桌面显示也是两位(1621.73)。
424
- // size = 总量,供界面悬停展示「总量 / 已用 / 剩余」。
425
- // consumed = 累计已消耗。Trae 的对话流**不报告单次消耗**
426
- // (实测上游只有 metadata/timing_cost/output/extra_info/
427
- // token_usage/done 六种事件,没有任何计费字段),
428
- // 所以用量统计只能靠这个累计值算差值。
424
+ // size = 总量,供界面悬停展示「总量 / 已用 / 剩余」。
425
+ //
426
+ // 不返回 consumed:Trae 的对话流没有计费字段,用量统计拿不到
427
+ // 单次消耗;累计值含义不同(账号总量 ≠ 本页消耗),给了只会
428
+ // 诱导误用,所以不提供。
429
429
  return {
430
430
  total: Math.round((totalAmount - consumed) * 100) / 100,
431
431
  size: totalAmount,
432
- consumed: Math.round(consumed * 100) / 100,
433
432
  }
434
433
  }
435
434
  return { total: 0 }
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.0",
4
+ "version": "0.2.2",
5
5
  "description": "把多个 WorkBuddy / Trae 账号汇成账号池接入 DeepSeek Harness:自动切换、限流熔断退避、凭证安全落盘",
6
6
  "type": "module",
7
7
  "license": "MIT",