🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

余额胶囊 jiangpengtkd-alt/dsh-balance-chip

DeepSeek Harness
jiangpengtkd-alt3 天前有提交已提供中文文档扫描:含敏感能力

在输入框下实时显示余额、会话花费与本轮消耗

30.8/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/29 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add jiangpengtkd-alt/dsh-balance-chip
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · model
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L5 · 运行时冒烟等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 3 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件发起外部网络请求
查看扫描证据(共 7 处,展示前 7 条)
  • 发起网络请求
    lib/index.js:183
    const response = await fetch(`${baseURL}/user/balance`, {
  • 读取本地文件
    lib/index.js:491
    if (existsSync(fixed)) return fixed;
  • 读取本地文件
    lib/index.js:493
    const found = readdirSync(dir)
  • 读取本地文件
    lib/index.js:556
    workspaces = readdirSync(cacheRoot);
  • 读取本地文件
    lib/index.js:567
    ids = readdirSync(join(cacheRoot, ws));
  • 读取本地文件
    lib/index.js:586
    if (statSync(file).mtimeMs < dayStart) continue;
  • 读取本地文件
    lib/index.js:593
    buf = readFileSync(file);
扫描于 2026/10/31(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

README

由 DeepSeek 最新模型翻译生成
DSH 余额与消费显示器 给 DeepSeek Harness 装的插件。 官方只告诉你用了多少 token,不告诉你花了多少钱。装上它,输入框下面就会显示余额、这个会话花了多少、你这一句话花了多少;点开还能看今天总共花了多少、现在是高峰还是空闲。 胶囊:右边是本插件,左边三个是原厂胶囊,同一排、同一字号、同一基线 为什么要装 - 官方那三个胶囊显示的是轮次、token 数量、上下文占用,一个金额都没有 - 想知道今天花了多少、这句话花了多少,只能自己拿 token 数去乘单价 - 官方计费分高峰和空闲,单价差一倍,但界面上看不出现在处在哪个时段 安装 1. 去 Releases 下载最新的 dsh-balance-chip-<版本>.tgz 2. 执行 dsh plugin --profile desktop add dsh-balance-chip-<版本>.tgz 3. 重启 DSH 装的时候不用联网,也不用账号。装完点开长这样: 浮层:总余额与构成、本日消耗、每轮平均、缓存命中率、动态单价、当前时段 胶囊上的三个数 从左到右是余额、这个会话的累计、这一轮的消耗。换得一个比一个勤,所以最右边跳得最明显。 三段始终占着位置,算不出来就显示 —。某段要是直接消失,右边的数字就会左右横移,看着像坏了。
展开完整文档(README 剩余 1 节)
DSH 余额与消费显示器

给 DeepSeek Harness 装的插件。

官方只告诉你用了多少 token,不告诉你花了多少钱。装上它,输入框下面就会显示余额、这个会话花了多少、你这一句话花了多少;点开还能看今天总共花了多少、现在是高峰还是空闲。

胶囊:右边是本插件,左边三个是原厂胶囊,同一排、同一字号、同一基线

为什么要装

- 官方那三个胶囊显示的是轮次、token 数量、上下文占用,一个金额都没有
- 想知道今天花了多少、这句话花了多少,只能自己拿 token 数去乘单价
- 官方计费分高峰和空闲,单价差一倍,但界面上看不出现在处在哪个时段

安装

1. 去 Releases 下载最新的 dsh-balance-chip-<版本>.tgz
2. 执行 dsh plugin --profile desktop add dsh-balance-chip-<版本>.tgz
3. 重启 DSH

装的时候不用联网,也不用账号。装完点开长这样:

浮层:总余额与构成、本日消耗、每轮平均、缓存命中率、动态单价、当前时段

胶囊上的三个数

从左到右是余额、这个会话的累计、这一轮的消耗。换得一个比一个勤,所以最右边跳得最明显。

三段始终占着位置,算不出来就显示 —。某段要是直接消失,右边的数字就会左右横移,看着像坏了。

| 数 | 是什么 | 从哪来 |
| 总余额 | 账户可用总额 = 充值余额 + 赠金余额 | 快照值。浮层标题也是它,正文前两行是构成 |
| 会话累计 | 这个会话从建立到现在一共花了多少 | 官方 tokenUsage.totals |
| 本轮消耗 | 你这一句话花了多少,从发出到此刻 | 当前 totals − 本轮起点的 totals,实时累加,答完就停 |

最右边还有个时段:空闲 / 高峰 / 假日。它决定单价是 1 倍还是 2 倍,看到「高峰」就知道这轮为什么贵。法定节假日按官方规则全天算空闲,但仍然单独显示「假日」,免得你以为插件没认出节假日、按工作日算错了。

拿不到时段时同样显示 —,不会整段消失。四种状态都是两个字,宽度一样,切换时右边不会动。

外观和交互是照着官方那三个胶囊做的:透明胶囊、悬停出底色、点击弹浮层、点外面或按 Esc 关闭。

几个细节

- 够快:余额和会话累计 15 秒刷一次,「本轮消耗」2 秒一次,回答过程中能看到数字在涨
- 浮层里还有别的:本日消耗、每轮平均、缓存命中率、当前单价。其中本日消耗是扫当天全部会话日志算的,不是只看当前会话
- 零依赖:纯 JavaScript,没有原生模块,也没有 install / postinstall 脚本;运行时对磁盘只读
- 默认不联网:余额走本机账号服务。只有它读不到时才回退到 api.deepseek.com,详见下面的「安全边界」

desktop 是 profile 名,换成你自己的。完整安装说明和排查清单见 INSTALL.md。

本插件没有发布到 npm,安装一律走 tgz,不依赖任何账号。

想先看效果、不用装:把仓库 clone 下来跑 node tools/gen-preview.mjs,再打开 docs/preview.html —— 里面把原厂胶囊和本插件并排渲染,可以直接比对。

安全边界与下载来源

只从本仓库的 Releases 下载安装包。

本插件会读取本机 ~/.dsh/.credentials.yaml,用于账号服务不可用时直连 api.deepseek.com/user/balance 的兜底查询。默认配置下凭据只发往 api.deepseek.com:不写入日志、不返回给浏览器、不发往任何第三方。

⚠️ 两个例外,必须说清:

1. baseURL 是可配置的(见「配置」一节)。若你把它改成别的地址,兜底路径就会把 API Key 发往那个地址 —— 上面那条保证只对默认值成立。
2. 被改过的副本(fork、二次打包、第三方网盘)不受任何保证 —— 这也是要核对 SHA256 的原因。

另外,HTTP 接口只校验回环地址与 Host,不做 DSH 登录校验:同一台机器上的其他进程也能读取余额。它只能读 —— 插件对磁盘、对配置都没有写操作。

每个 Release 附带 SHA256SUMS.txt,下载后可自行核对:

Windows
Get-FileHash .\dsh-balance-chip-<版本>.tgz -Algorithm SHA256

macOS / Linux
sha256sum dsh-balance-chip-<版本>.tgz

与 Release 页面上的值一致即可。

其他边界:零依赖、无 install / postinstall 脚本;运行时对磁盘只读 (不写任何文件);HTTP 路由只接受回环地址(127.0.0.1)的请求。

测试

五套测试全是纯 Node、零依赖、不联网,也不用启动 DSH。

运行环境要求 Node ≥ 22.15.0 —— host 端用 node:zlib 的 zstdDecompressSync 解压会话日志,该 API 自 Node 22.15.0 起提供。(DSH 桌面版内置的是 Node 24,满足。)

node tools/selftest.mjs # 68 项 host 端到端(余额、计费、回环校验、换轮基准)
node tools/render-test.mjs . # 110 项 渲染(金额精度、标签配对、胶囊可见性、面板结构)
node tools/heartbeat-test.mjs . # 6 项 心跳独立于组件、节奏参数
node tools/refresh-test.mjs . # 13 项 「立即刷新」的排队与补发
node tools/gen-preview.mjs # 28 项 预览页自检

退出码 0 即全部通过。CI 在 Node 22 / 24 上跑(.github/workflows/tests.yml)。

⚠️ 测试里刻意放了几条只有实现正确时才成立的等式断言(例如"第二次开新轮时本轮消耗 ≈ 0 且小于会话累计")。
它们不是装饰:0.10.0 修掉的那个高危 bug(换轮不重置基准 →「本轮消耗」等于「会话累计」)就是靠一条这样的断言才被抓住的,并用变异测试验证过"把缺陷注回去测试确实会失败"。
详见 CHANGELOG 的 0.10.0 与 0.10.2。

独立审计记录

这个项目经历过两轮外部独立审计(在 macOS 上做:浅克隆 + 字节级比对 + ditto 实测解压 + 向断言注入缺陷验证其有效性),共报出 16 条问题,绝大多数自测发现不了。

完整记录见 docs/audits/ —— 包含每条问题的证据、根因、修法,以及我为什么会漏掉它们。

其中最值得看的一条:某次修复和对应的断言犯了同一个盲区(都只覆盖平台表格、漏掉散文段落),于是同一份发布包里两份文档的平台结论互相矛盾 —— 而测试全绿。

那条矛盾的原文是:README 写"macOS 装不了"、INSTALL 写"macOS 实测可用"。
放在引用块里是因为它是错的,只是被引来作说明; tools/pack-release.mjs 的文档断言以"是否在引用块内"区分主张与更正说明。

项目结构

lib/
client.js 浏览器端:胶囊 + 浮层(手写 createElement,无构建步骤) index.js host 端:余额读取、token 计费、本日跨会话扫描 holidays.js 法定节假日表(唯一的年度维护点) tools/ 五套测试 + 打包脚本 pack-release.mjs + 预览生成器 docs/preview.html 样式预览(由 gen-preview.mjs 生成,需与源码同步) docs/audits/ 两轮独立审计的完整记录(问题、证据、根因、修法) releases/ 发布包(.tgz 安装包,每个版本一个 tag) QUICKSTART.md 中文上手(含每个数字的定义、联网边界、年度维护) CHANGELOG.md 完整版本历史(含每个 bug 的根因与修法) INSTALL.md 安装与排查

安装位置

插件目录可以放在任意位置,下例用 ~/.dsh/plugins/dsh-balance-chip:

~/.dsh/plugins/dsh-balance-chip ← 插件源码(位置可自选) ├─ lib/index.js host 半身 ├─ lib/client.js client 半身(浏览器) ├─ lib/holidays.js 法定节假日数据(唯一的年度维护点) ├─ cordis.patch.yml bundle 激活层 ├─ docs/preview.html 样式预览(浏览器打开) └─ tools/gen-preview.mjs 预览生成器(从 client.js 求值,见「预览」一节)

profile 侧用 link 依赖指向它,不需要把代码拷进 profile 目录:

~/.dsh/profiles/<profile>/
├─ package.json dependencies: dsh-balance-chip → link:<插件目录> ├─ pnpm-lock.yaml 同上(specifier / version) └─ node_modules/dsh-balance-chip → 软链到插件目录

装完(改完代码后同理)都要重启 DSH 才生效:host 半身必须重启,client 半身随 host 一起重新生成。

平台支持

插件代码本身是跨平台的:

- 纯 JavaScript,无原生模块(无 .node)、无 install/postinstall 脚本、无运行时依赖
- 无 win32 / process.platform / process.arch 判断,无盘符或反斜杠硬编码
- 唯一的文件路径访问走 path.join(os.homedir(), ".dsh"),在 macOS / Linux 上自动解析为 /Users/<你>/.dsh、/home/<你>/.dsh
- cordis.patch.yml 用的是 POSIX 相对路径 ./cordis.patch.yml
- client bundle 运行在浏览器里,与操作系统无关
- 节假日表是内置静态数据,运行时不访问任何外部接口

平台支持:Windows 与 macOS 均已实机验证。

| 平台 | 状态 | 已验证的组合 |
| Windows x64 | ✅ 实机验证 | win32-x64 + DSH 0.2.0-rc.2 / 0.2.1-rc.3(Node 24.18.1) |
| macOS arm64 | ✅ 实机验证 | darwin-arm64 + DSH 0.2.0-rc.2(Node 24.18.1) |
| macOS x64 / Linux | 预期可用 | 同一份代码,未单独验证 |

判断你自己的 DSH 是哪个平台,读安装目录里的 resources/app.asar → dsh/desktop-runtime.json: json { "platform": "darwin", "arch": "arm64", "release": { "version": "0.2.0-rc.2" } }

platform 在 Windows 上是 win32、macOS 上是 darwin、Linux 上是 linux。

⚠️ 这一节曾经写错过两次,都记在这里以免重犯:

1. 引用过一个更新源地址并声称"实测 200 / 其余 404" —— 那些 URL 是我猜的,实测全部 404,没有依据。
2. 更严重的是据此断言"官方只有 Windows x64 一个构建、macOS 装不了"。
错在把本机安装实例的构建元数据(platform: win32、更新地址 feeds/win-x64/)当成了官方平台列表。macOS 版确实存在,且已在 darwin-arm64 上实测装通、接口正常返回余额。

教训:一个描述「这台机器」的字段,不能拿来推断「所有平台」。

安装命令(各平台相同,只是路径写法不同):

macOS / Linux
dsh plugin --profile desktop add ~/Downloads/dsh-balance-chip-<版本>.tgz

Windows (PowerShell)
dsh plugin --profile desktop add "C:\路径\dsh-balance-chip-<版本>.tgz"

把版本号换成你实际下载到的那个文件名 —— 包名里的版本会随发布变化。
运行环境要求 Node ≥ 22.15.0(DSH 桌面版内置的 Node 已满足),详见 INSTALL.md。

完整的平台说明、三种安装方式、以及"装完没反应"的排查步骤,见 INSTALL.md。

数据来源(都是官方接口,不自己造数据)

| 显示项 | 来源 | 说明 |
| 总余额 | 下面两者之和(host 计算) | 胶囊与浮层标题都显示它。账号侧返回的是 normal_wallets 与 bonus_wallets 两个并列钱包,total 由插件相加得出;赠金缺省时就等于充值余额 |
| 充值余额 | ctx.deepseekAccount.getBalance(client) 的 normal_wallets | DSH 账号服务;已处理凭据读取、Platform 的五个 x-client- 头、余额超时与 401 失效。兜底可走 GET api.deepseek.com/user/balance(API Key 账号,那里 total_balance 本身就是总额)。浮层正文第一排显示的就是这个数 |
| 赠金余额 | 同上返回值的 bonusWallets | 账号的第二个钱包(送的那部分,会过期)。官方账号设置页并列展示「充值余额 / 赠金余额」;本插件放在浮层正文第二排,没有时显示 —。官方扣费规则:两种余额同时存在时优先扣减赠送余额(文档),所以行标签写作「赠金余额(优先消耗)」 |
| 会话累计 | ctx.sessionProjections.stateOf(session, "tokenUsage") 的 totals | 官方 @deepseek-ai/dsh-token-meter 注册的会话投影 |
| 本轮消耗 | 当前 totals − 本轮起点 totals | 本插件补出来的口径。官方只有 totals(整会话)与 last(最近一步),没有"一轮合计"。做法:在 turn/start 事件记下当时的 totals 作基准(按会话存 WeakMap),每次请求做差 |
| 每轮平均消耗 | 会话累计 ÷ sessionStats.turns | 官方 session-stats 投影提供轮数。这是现有数据下唯一能回答"一句话大约多少钱"的数 |
| 缓存占比 | totals.cacheReadTokens ÷ 四项之和 | 解释成本为何低:缓存读取单价 0.02,未缓存输入 1,差 50 倍 |

为什么要自己补「本轮」:官方 tokenUsage 的 wire.view 是
view: state => state.totals —— 只把累计值发给客户端,last(最近一步)
与 turnBoundary 都留在 host 侧,官方 UI 因此从不展示"单次/单轮花了多少"。
本插件用 stateOf() 读 host 原始状态才拿到 last.buckets;而"本轮合计" 官方根本没算,是用 turn/start 基准做差补出来的。

三个数的含义务必分清,混淆会得出完全错误的结论:余额(账户快照)· 本轮(这一句话从发问到此刻)· 会话(本会话建立至今)。

计价按 DeepSeek 官方价目表(元/百万 tokens,deepseek-flash):

| | 空闲时段 | 高峰时段 |
| 输入(缓存命中) | 0.02 | 0.04 |
| 输入(缓存未命中) | 1 | 2 |
| 输出 | 4 | 8 |

高峰 / 空闲是怎么判断的

依据 DeepSeek 官方文档(模型 & 价格 注 2)原文:

空闲时段价格为高峰时段价格的一半。北京时间周一至周五(不含中国法定节假日) 9:00 - 12:00、14:00 - 18:00 为高峰时段;其余时段,包括周末及中国法定节假日全天均为空闲时段。

译成判定式(lib/index.js 的 computeCost):

// 1. 先把本机时间换算成北京时间(getTimezoneOffset() 是"UTC 减本地"的分钟数) const beijing = new Date(now.getTime() + (now.getTimezoneOffset() + 480) 60000);
const weekday = day >= 1 && day <= 5; // 周一~周五
const inPeak = (hour >= 9 && hour < 12) || (hour >= 14 && hour < 18); // 9-12、14-18
const holiday = isStatutoryHoliday(年, 月, 日); // 见 lib/holidays.js
const peak = weekday && inPeak && !holiday;

四条规则,缺一不可:

| 情况 | 判定 |
| 周末(含调休补班的周六日) | 空闲 |
| 法定节假日(即使落在周一至周五) | 空闲 |
| 周一至周五、非节假日、9–12 或 14–18 点 | 高峰 |
| 其余一切 | 空闲 |

为什么不需要「调休补班日」数据:官方原文是

北京时间周一至周五(不含中国法定节假日)9:00 - 12:00、14:00 - 18:00 为高峰时段;其余时段,包括周末及中国法定节假日全天均为空闲时段。
—— 模型 & 价格

判据是「星期几」,不是「这天是否上班」。所以调休补班的周六日仍然是周末 → 空闲,不会产生新的高峰时段。于是只需要「法定节假日」这一张表,不需要再维护一张补班表。

节假日数据与维护

lib/holidays.js 内置「放假日期表」,2025 与 2026 两年已收录。数据来自国务院办公厅每年发布的《关于XXXX年部分节假日安排的通知》,2026 年已与官方原文逐条核对一致。

每年 11 月前后国务院会公布次年安排。届时重新生成即可(把年份换成需要的):

node -e "fetch('https://timor.tech/api/holiday/year/2027').then(r=>r.json()).then(b=>console.log(Object.entries(b.holiday).filter(([,v])=>v.holiday===true).map(([k])=>k).sort().join(' ')))"

把输出填进 STATUTORY_HOLIDAYS 对应年份即可。

未收录的年份会自动回退:isStatutoryHoliday() 返回 false,退化为「仅按工作日 + 时段」
判断,不会报错,只是那年的法定节假日会偏高估约一倍。浮层底部「当前时段」只显示结论(空闲 绿 / 高峰 橙),不再带括号里的判断依据 —— 依据看下面那行警示即可。

数据缺失的那一年,浮层里会额外多出一行「节假日数据」(本年未收录 · 节假日期间费用可能偏高)—— 只在缺数据时出现,数据齐全时不显示:这一行的意义是警示,不是汇报「一切正常」。

另一层近似(无法消除):tokenUsage 投影给的是累计总数,把不同时段的用量加总在一起了。所以跨时段的长会话,累计金额是按「当前时刻的费率」折算的近似值。
要精确需从会话日志逐步读每次请求的时间戳与用量、分段计价 —— 成本过高,未实现。

配置

可选,在 profile 的 cordis.patch.yml 里按 id 覆盖:

- id: balance-mini
name: dsh-balance-chip
config:
refreshMs: 30000 # 余额刷新间隔(毫秒,默认 60000) timeoutMs: 15000 # 余额查询超时(毫秒,默认 15000) baseURL: https://api.deepseek.com # 仅 API Key 兜底路径使用

实现要点(踩过的坑,改代码前请先看)

1. client bundle 必须是经典脚本,不是 ES module。 宿主用 window.__ModuleLoader__.load({ id, factory }) 加载,factory 里用 module.exports 导出。
写成顶层 export 会直接 SyntaxError → web 启动失败,宿主为了自保会把整个 bundle 从 dsh.profile.bundles 摘掉(历史 crash 日志里是 web boot: 1 entry did not activate)。
插件加载失败时,宿主提供「禁用第三方插件」恢复入口,那会备份并重置 profile patch。

2. 不要用 ctx.connection.rpc.handle() 注册通道。 它内部走 owner.webServer.register(),而 owner 取的是 connection 实例自己的 ctx,读不到 webServer(与本插件加载时机无关,重试 15 次错误一字不变),恒抛 cannot get property "webServer" without inject。
改用本插件自己的 ctx:ctx.webServer.register({ kind:"prefix", path, handler })。

3. 客户端 RPC 是带信封的协议,不是普通 POST。 请求 { type:"client-request", rpcId, method, payload } → 响应必须是 { type:"server-response", rpcId, result:{ ok:true, value } };失败时 result.error 的 code/message/details 三者都必需,details 还必须是对象,否则客户端解析时抛 TypeError。rpcId 必须原样回传(否则 rpcId mismatch)。

4. 绕过了 Connection 的 BrowserAuth,所以自己把门。
路由只接受 Host 为 127.0.0.1 / localhost / ::1 的请求,否则 403 —— 别把这个检查删掉。

5. 会话刚建立的头几秒取不到费用是正常的。
ctx.sessions.get() 只认「活着」的会话;此时 computeCost 静默返回 undefined,
由客户端 3 秒快速重试补上(取到后回到 60 秒节奏)。这是设计,不是 bug。

6. 不要给 design token 的 var() 加兜底值。 原厂组件一律写 var(--token),不加 fallback。加兜底看似更健壮,实际会让该属性解析出与原厂不同的值,产生难以察觉的视觉偏差 —— 本插件在这上面栽过两次:

| token | 加兜底的后果 |
| --dsh-content-font-size-secondary | 胶囊字号与另外三个胶囊不一致(0.1.1 修) |
| --dsw-alias-separator-primary | 分隔点亮度只有原厂的一半,看着更小更虚(0.2.2 修) |

只有 token 确实可能缺失时才加兜底。DSH 的主题层保证这些 token 存在。

7. 投影形状是扁平的。
useProjection("tokenUsage") 返回的对象直接带 uncachedInputTokens / outputTokens /
cacheReadTokens / cacheWriteTokens,没有 totals 包一层(host 侧 stateOf 返回的才有 { totals, last })。两处读取形状不同,别混。

卸载

dsh plugin --profile desktop remove dsh-balance-chip

然后删掉本目录即可。也可以手动把 dsh-balance-chip 从 profile 的 package.json 的 dsh.profile.bundles 数组里移除、并删除 node_modules\dsh-balance-chip 链接。

刷新节奏

| 常量 | 值 | 含义 |
| BEAT_MS | 1000 | 心跳检查频率(醒来判断是否需要取数,不代表每次都发请求)。必须 ≤ TURN_MS,否则检查粒度成为瓶颈 |
| TURN_MS | 2000 | 「本轮消耗」距上次取数超过此值就重取 —— 它是活跃字段,一个回答里会跳好几次 |
| refreshMs | 15000 | 其余字段(余额、会话累计)的常规周期,可用插件 config 覆盖。活跃周期取 min(refreshMs, TURN_MS),所以配得比 2000 小才有意义 |
| DAY_SCAN_TTL_MS | 60000 | host 端「本日消耗」的缓存时长。扫描要 50–190ms(看日志量),所以请求路径只读缓存;过期后由 setImmediate 异步补扫,不在请求的关键路径上 |

失败退避:连续取数失败时重试门槛按 refreshMs × 2^(n-1) 递增,上限 60 秒,成功后清零。
没有它的话"取不到数"会退化成 1 秒一次的永久轮询(未配置 key 的用户挂机一天约 8 万次请求)。

⚠️ 这张表曾长期与源码不符(写着 2000 / 8000 / 60000,实际是 1000 / 2000 / 15000)。
现在 tools/selftest.mjs 有一条断言直接比对文档与源码,改常量不改文档会被测出来。

心跳在模块级(beatStart() 在 apply 里调用一次),组件卸载不会终止它。
这是刻意设计:插槽会被宿主反复重挂,轮询若挂在组件 effect 上就会被 cleanup 清掉(实测曾导致每次启动只请求 1 次)。

node tools/heartbeat-test.mjs

覆盖:心跳只创建一个、节奏参数与源码一致、数据缺失时持续重试、数据新鲜时不重复取数、组件卸载后心跳仍存活。

两个已知权衡(审查时确认过,不是 bug)

1. 切会话瞬间可能显示上一个会话的数据:心跳只有一个全局 sessionId 槽位,切换会话时新值在 render 阶段写入,但已在途的请求返回的仍是旧会话的结果,会先渲染一次。下一次心跳(≤ TURN_MS)就被新数据覆盖。
刻意不为此加防护 —— 账户余额本来就与会话无关,而"会话累计"在切换瞬间两个值都是合理的,"先显示旧值再刷新"比"先清空再填"更不刺眼。

2. 连点「立即刷新」可能被吞掉一次:beatFetchNow() 有 inflight 去重,若手动刷新恰好撞上进行中的请求就直接返回。2 秒后心跳自然会补上。

自测

node tools/selftest.mjs

不需要启动 DSH。 它加载真实的 lib/index.js、用桩 ctx 调 apply()、从注册的路由里取出 handler 直接喂 HTTP 形状的假请求,然后断言响应内容(22 项)。

覆盖关键路径:新会话(turnBoundary.openTurnStartSeq 为 null,这里曾导致 cost 整块消失)→ 本轮建立 → 同轮累加 → 新轮重置 → 无 sessionId → 会话不存在 → 非回环请求被拒(403) → GET 可用 → 未知 RPC 方法。

金额断言用同一套价目表独立算一遍再比对,不是"看着像对"。

为什么值得维护这个脚本:host 半身的问题过去只能靠"改代码 → 让用户重启 → 肉眼看界面"发现,一轮好几分钟,而且我为此误判过两次 ——一次把"进程还跑着旧代码"当成逻辑 bug,一次因 mock 里 turnSeq 一直有值而漏掉 null 分支。这类错误在真机上要用户配合才能暴露。

预览

docs/preview.html 用浏览器直接打开。该文件的 CSS 由生成器从 lib/client.js 求值得到,不是手抄 —— 手抄必然会与实现脱节:

⚠️ 本项目吃过这个亏:预览里曾写着手抄的 margin:0 1px(分隔点间距)、 gap:6px、缺少 hover 变亮,与实机全都不符,而 README 却声称"与实机一致"。
现在预览页的样式不可能脱节,因为它是从同一份定义生成的。

预览页包含 5 节:

1. 实机排布(上下文圆圈 + 余额胶囊)
2. 与原厂胶囊并排 —— 直接比对分隔点大小/亮度/间距、字号
3. 点击展开的浮层(底部一行是「↻ 立即刷新」+「当前时段:空闲/高峰」)
4. 余额分档与异常状态
5. 字号跟随设置 —— 演示 13 / 14 / 15px 下与原厂胶囊同步变化

改动样式后重新生成

node tools/gen-preview.mjs # 写回 docs/preview.html
node tools/gen-preview.mjs <路径> # 或写到指定位置

生成器会自检 5 条关键规则(gap:0、sep 的 margin:0 6px、状态点间距、 hover 变亮、颜色 token 无兜底);任一条不通过就以非零码退出,提示你可能改了 client.js 的 CSS 数组结构。

需要位图时用浏览器截图(不随包分发,因为它无法自动保持最新):

msedge --headless=new --force-device-scale-factor=2 --window-size=780,900 \
--screenshot=preview.png file:///<绝对路径>/docs/preview.html

两条硬约束

字号必须用同一个 calc 表达式,别写死 px:

font-size: calc(var(--dsh-content-font-size-secondary,13px) - 1px);
line-height: calc(20px + var(--dsh-content-font-delta-secondary,0px));

写死 px(曾用过 font-size:12px)会与另外三个原厂胶囊不一致 —— 用户能一眼看出来。

不要给颜色类 design token 的 var() 加兜底值。
原厂一律写 var(--token)。加兜底(如 var(--dsw-alias-separator-primary, rgba(128,128,128,.5)))可能让该属性解析出与原厂不同的值 —— 分隔点变暗后会显得更小。
详见下面「实现要点」第 6 条。

许可与致谢

本插件以 MIT 发布(见 LICENSE)。

胶囊与浮层的视觉(字号、字重、行高、内边距、分割线、状态色)参考并对齐 DeepSeek Harness 的官方组件。
该项目同为 MIT 许可,版权归 DeepSeek 及其贡献者所有 —— 此处保留该声明,以符合 MIT 对再分发的要求。若有不周之处,欢迎指出。
预览分享卡片长什么样 ↓jiangpengtkd-alt/dsh-balance-chip
分享 · DeepSeek Harness Hub
jiangpengtkd-alt/dsh-balance-chip

在输入框下实时显示余额、会话花费与本轮消耗

回到顶部