dsh-account-pool 0.1.2 → 0.2.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/lib/client.js CHANGED
@@ -453,6 +453,27 @@ 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
  return h("div", null,
457
478
  h("div", { style: CARD_STYLE },
458
479
  h("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "10px" } },
@@ -479,8 +500,10 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
479
500
  // 直接调用会 ReferenceError(跨作用域)。
480
501
  const map = {};
481
502
  for (const r of out.results || []) map[r.accountId] = r;
482
- onRunDone(map);
483
- setNotice(region, "ok", "已完成 " + (out.results || []).length + " 个账号的每日任务");
503
+ // 把「本次获得的总积分」也带进 lastRun,供统计行显示
504
+ onRunDone(map, { totalEarned: out.totalEarned, earnedPartial: out.earnedPartial });
505
+ // 提示里带上实际到手的积分,比干巴巴一句"已完成"有用
506
+ setNotice(region, "ok", grantedText(out.totalEarned, "每日任务"));
484
507
  // 重新拉只读状态,让旅行/抽奖/连登的完成数同步更新
485
508
  await reload();
486
509
  } catch (error) {
@@ -512,6 +535,7 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
512
535
  taskRow("猫猫旅行", travelDone, "到站领奖 · 空闲派出", "今日已派或在途都算完成"),
513
536
  taskRow("连登兑换", redeemDone, "满 7/14/28 天可兑换", redeemPending > 0 ? redeemPending + " 个账号有可兑换档位" : "没有待兑换的档位"),
514
537
  taskRow("抽奖", lotteryDone, "次数来自连登兑换", lotteryPending > 0 ? lotteryPending + " 个账号有抽奖次数" : "暂无抽奖次数"),
538
+ earnedRow(),
515
539
  ),
516
540
  ),
517
541
 
@@ -597,6 +621,18 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
597
621
  * 设备已签到 本账号没领,但同设备今天签过(did_checked_in=true)
598
622
  * 未签到 都没签,可以执行
599
623
  */
624
+ /**
625
+ * 执行完成的提示文案,带上本次获得的积分。
626
+ *
627
+ * 拿不到数字时不编造——只说完成了几个账号。
628
+ * 部分账号算不出差值时加「部分」字样,避免让人以为这是全部。
629
+ */
630
+ function grantedText(totalEarned, what) {
631
+ if (typeof totalEarned !== "number") return "已完成" + what;
632
+ if (totalEarned === 0) return "已完成" + what + "(本次没有新增积分)";
633
+ return what + "完成,获得 " + formatNumber(totalEarned) + " 积分";
634
+ }
635
+
600
636
  function checkinCell(account) {
601
637
  if (account.checkinError) {
602
638
  return h("span", { style: { color: "var(--dsw-alias-state-error-primary)", fontSize: "12px" } }, "查询失败");
@@ -615,7 +651,7 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
615
651
  return h("span", { style: { opacity: 0.7 } }, "未签到");
616
652
  }
617
653
 
618
- function TraeTasksView({ region, data, busy, setBusy, setNotice, notice, reload, onRunDone }) {
654
+ function TraeTasksView({ region, data, busy, setBusy, setNotice, notice, lastRun, reload, onRunDone }) {
619
655
  const accounts = data.accounts || [];
620
656
  const total = accounts.length;
621
657
 
@@ -629,6 +665,24 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
629
665
 
630
666
  const sumCredits = accounts.reduce((sum, a) => sum + (typeof a.credits === "number" ? a.credits : 0), 0);
631
667
 
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
+
632
686
  const taskRow = (name, done, note) => h("tr", { key: name },
633
687
  h("td", { style: TD_STYLE }, name),
634
688
  h("td", { style: TD_STYLE },
@@ -661,8 +715,8 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
661
715
  if (!out.ok) throw new Error(out.error || "执行失败");
662
716
  const map = {};
663
717
  for (const r of out.results || []) map[r.accountId] = r;
664
- onRunDone(map);
665
- setNotice(region, "ok", "已完成 " + (out.results || []).length + " 个账号的签到");
718
+ onRunDone(map, { totalEarned: out.totalEarned, earnedPartial: out.earnedPartial });
719
+ setNotice(region, "ok", grantedText(out.totalEarned, "签到"));
666
720
  await reload();
667
721
  } catch (error) {
668
722
  setNotice(region, "error", error.message);
@@ -691,6 +745,7 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
691
745
  h("tbody", null,
692
746
  taskRow("每日签到", checkedIn, "每天一次,先查状态再领取,不会重复领"),
693
747
  taskRow("积分查询", withCredits, total > 0 ? "当前合计 " + formatNumber(sumCredits) + " 积分" : "只读查询,不消耗额度"),
748
+ earnedRow(),
694
749
  ),
695
750
  ),
696
751
  ),
@@ -898,6 +953,9 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
898
953
  const t = usage.totals || {};
899
954
  const models = usage.byModel || [];
900
955
  const totalCalls = t.requests || 0;
956
+ // 上游是否逐次上报积分消耗。默认按「支持」走,避免老数据缺字段时
957
+ // 把指标整块藏掉;只有后端明确说 false 才隐藏。
958
+ const reportsCredits = usage.reportsPerRequestCredits !== false;
901
959
  const failRate = totalCalls > 0 ? (t.errors || 0) / totalCalls : 0;
902
960
 
903
961
  /** 核心指标块:大号数字 + 小标签。 */
@@ -996,7 +1054,8 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
996
1054
  // 用 grid 而不是 flex-wrap:wrap 在窄面板里会把第 4 块挤到下一行,
997
1055
  // grid 的 1fr 会按可用宽度均分,永远保持一行。
998
1056
  h("div", {
999
- className: "wb-kpi-grid-4",
1057
+ // 少一个指标时用 3 列网格,避免留下一个空位
1058
+ className: reportsCredits ? "wb-kpi-grid-4" : "wb-kpi-grid-3",
1000
1059
  style: {
1001
1060
  display: "grid",
1002
1061
  gridTemplateColumns: "repeat(4, minmax(0, 1fr))",
@@ -1013,7 +1072,15 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
1013
1072
  kpi("总 tokens", formatCompact(t.totalTokens), { full: formatNumber(t.totalTokens) }),
1014
1073
  // 积分精确到个位、不缩写:它是能对账的数字,
1015
1074
  // 显示成 8.4K 会让人不知道到底扣了多少。
1016
- kpi("积分消耗", formatNumber(t.credits), { full: formatNumber(t.credits) + " 积分" }),
1075
+ //
1076
+ // 上游不逐次上报消耗时整块隐藏(Trae 的对话流没有计费
1077
+ // 字段,累加恒为 0)。显示一个恒为 0 的指标比不显示更糟:
1078
+ // 用户会以为「没消耗」或「统计坏了」,而真相是上游不给。
1079
+ // 也不拿积分接口的累计值来凑数——那是账号总量,不是本页
1080
+ // 消耗,混着展示会直接误导。
1081
+ reportsCredits
1082
+ ? kpi("积分消耗", formatNumber(t.credits), { full: formatNumber(t.credits) + " 积分" })
1083
+ : null,
1017
1084
  // 延迟只统计成功请求。没有成功请求时显示 —,
1018
1085
  // 而不是误导性的 0(或把失败请求的快速返回当成"很快")。
1019
1086
  h("div", {
@@ -1684,7 +1751,23 @@ window.__ModuleLoader__.load({ id: "dsh-account-pool", factory: (require) => {
1684
1751
  return () => clearInterval(timer);
1685
1752
  }, []);
1686
1753
  // 上次每日任务的执行结果:签到没有只读接口,靠它显示完成情况。
1687
- const [lastRun, setLastRun] = useState(null);
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
+ }, []);
1688
1771
  const pollTimer = useRef(null);
1689
1772
 
1690
1773
  /** 给某个区域设一条提示信息。 */
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,
@@ -944,6 +954,13 @@ export function apply(ctx, config = {}) {
944
954
  const accounts = await stack.store.list()
945
955
  if (accounts.length === 0) { json(res, 200, { ok: true, results: [] }); return }
946
956
 
957
+ // 跑任务前先取一次积分:跑完再取一次,差值就是这次**实际获得的积分**。
958
+ //
959
+ // 为什么不直接读接口返回值:签到接口只回成功/失败,不回奖励数量;
960
+ // Trae 的对话/任务流也没有任何计费字段(实测只有六种事件)。
961
+ // 唯一可靠的真值就是「积分余额的前后差」。
962
+ const before = await stack.loadCredits(true).catch(() => ({}))
963
+
947
964
  // 账号间串行:上游对短时间内的密集请求有风控,串行更稳。
948
965
  const results = []
949
966
  for (const account of accounts) {
@@ -960,9 +977,39 @@ export function apply(ctx, config = {}) {
960
977
  }
961
978
  }
962
979
 
963
- // 任务跑完刷新积分与目录
964
- await stack.loadCredits(true).catch(() => {})
965
- json(res, 200, { ok: true, results })
980
+ // 任务跑完刷新积分:这里的结果既是缓存更新,也是「跑完后」的快照
981
+ const after = await stack.loadCredits(true).catch(() => ({}))
982
+
983
+ /**
984
+ * 算出每个账号这次获得的积分。
985
+ *
986
+ * 只把**正差值**当作「获得」:
987
+ * - 差值为正 → 签到/活动奖励到账
988
+ * - 差值为负 → 期间有别的消耗(对话),不算获得,记 0
989
+ * - 两边都取不到 → 标 unknown,界面显示 —,不编造数字
990
+ */
991
+ const earnedOf = (accountId) => {
992
+ const b = before[accountId]?.total
993
+ const a = after[accountId]?.total
994
+ if (typeof b !== 'number' || typeof a !== 'number') return undefined
995
+ const delta = a - b
996
+ // 负差值可能是「期间用了额度」,不算获得;只取正收益
997
+ return delta > 0 ? Math.round(delta * 100) / 100 : 0
998
+ }
999
+ for (const r of results) {
1000
+ const earned = earnedOf(r.accountId)
1001
+ if (earned !== undefined) r.earnedCredits = earned
1002
+ }
1003
+
1004
+ const totalEarned = results.reduce(
1005
+ (sum, r) => sum + (typeof r.earnedCredits === 'number' ? r.earnedCredits : 0), 0)
1006
+ json(res, 200, {
1007
+ ok: true,
1008
+ results,
1009
+ totalEarned: Math.round(totalEarned * 100) / 100,
1010
+ // 有账号算不出差值时,界面要对「合计」加注说明,避免误以为是全部
1011
+ earnedPartial: results.some(r => typeof r.earnedCredits !== 'number'),
1012
+ })
966
1013
  } catch (error) {
967
1014
  json(res, 500, { ok: false, error: safeMessage(error) })
968
1015
  }
@@ -1045,7 +1092,13 @@ export function apply(ctx, config = {}) {
1045
1092
  const list = await ready
1046
1093
  const out = {}
1047
1094
  for (const stack of list) {
1048
- out[stack.region] = stack.usage.snapshot(hours)
1095
+ // 只给「本页时间窗口内按请求累加」这一种口径。
1096
+ // 上游不逐次上报时(Trae),该指标没有真实数据可展示——
1097
+ // 界面据此隐藏它,而不是显示恒为 0 的数字。
1098
+ out[stack.region] = {
1099
+ ...stack.usage.snapshot(hours),
1100
+ reportsPerRequestCredits: stack.reportsPerRequestCredits,
1101
+ }
1049
1102
  }
1050
1103
  json(res, 200, { ok: true, regions: out })
1051
1104
  } catch (error) {
@@ -422,6 +422,10 @@ export async function fetchCredits(credential) {
422
422
  if (Number.isFinite(totalAmount) && Number.isFinite(consumed)) {
423
423
  // 保留两位小数:桌面显示也是两位(1621.73)。
424
424
  // size = 总量,供界面悬停展示「总量 / 已用 / 剩余」。
425
+ //
426
+ // 不返回 consumed:Trae 的对话流没有计费字段,用量统计拿不到
427
+ // 单次消耗;累计值含义不同(账号总量 ≠ 本页消耗),给了只会
428
+ // 诱导误用,所以不提供。
425
429
  return {
426
430
  total: Math.round((totalAmount - consumed) * 100) / 100,
427
431
  size: totalAmount,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-account-pool",
3
3
  "displayName": "Account Pool",
4
- "version": "0.1.2",
4
+ "version": "0.2.1",
5
5
  "description": "把多个 WorkBuddy / Trae 账号汇成账号池接入 DeepSeek Harness:自动切换、限流熔断退避、凭证安全落盘",
6
6
  "type": "module",
7
7
  "license": "MIT",