Skip to content

feat: reference pricing, peak billing, fold cache, native typert channel and dashboard (0.5.0) - #5

Merged
xinspark merged 6 commits into
bennett-dsh:mainfrom
xinspark:release/0.5.0
Oct 30, 2026
Merged

xinspark merged 6 commits into
bennett-dsh:mainfrom
xinspark:release/0.5.0

Conversation

@xinspark

@xinspark xinspark commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

这个 PR 做了什么

给 dsh-plugin-usage 增加参考定价体系、峰谷计价、折叠缓存与原生 typert 通道,并把设置页的用量面板补齐到可用状态。分支已 rebase 到最新 main(0.4.0,输入区成本 pill),该 pill 原样保留。净改动 51 文件 / +9431 −417,按层分成 6 个提交(feat / test / docs / fix / docs / build)。

与 0.4.0 的关系

  • 冲突集中在 src/client/index.js(dock 的 CSS 与本次的定价弹窗 CSS)、package.json(版本 + 新增
    dsh-client-ui-conversation 注入)、CHANGELOG.md。解析时两边都保留:dock 的 CostPill、注册与
    tests/client-dock.test.js 随基线留下,定价弹窗与新的 apply 逻辑共存。
  • 上游 0.4.0 的 CHANGELOG 段保留为已发布历史;本地 0.4.x 迭代(含本地标为 0.4.0 的定价改造)并入
    0.5.0 说明与文末附录,不再出现两个 0.4.0。

主要能力

  • 参考价目与逐级匹配:内置 opencode Zen + DeepSeek 价目(scripts/gen-prices.mjs 生成),
    查询只走 resolvePrice 一个阶梯:用户自定义价 → 用户确认的匹配 → 字面命中 → 变体
    (-free / -fast / -1017)→ 模糊。四态(精确/模糊/未匹配/自定义)由宿主给出,
    客户端不再二次解释——此前两处词表各自维护,一处写错就让所有精确命中显示成「未匹配」。
  • 峰谷计价:北京时间 09:00–12:00、14:00–18:00 为峰时,排除中国大陆法定节假日;
    节假表随用量数据年份顺延,未公布年份显式标注而不是静默丢表。
  • 折叠结果落盘缓存:热启动 0.08 s(此前每次重启都要重读 404 MB / 解压后 1.35 GB,
    仅读取+解压就 12.5 s)。签名 = 折叠规则版本 + 节假表版本,损坏一律降级全量扫;
    不做 prune(部分列表时 prune 曾删掉 257/258 条有效缓存)。
  • 原生 typert Remote 通道:注册一份原生贡献 + 接收方服务,connection.fetch 的
    /api/usage/* 常驻兜底;实际通道随响应返回(channel 字段),便于核对。
  • 仪表盘:按模型明细(可排序)、定价匹配弹窗(四态可改可撤)、年度热力图、币种切换。
  • 成本出现在每一处"某个时间段"的汇总里:趋势图(柱状与折线)与年度热力图的悬浮卡都给出该时段的
    参考成本,三处共用同一个 TipRows;只有"图例里真有这一项"的行(输入/输出/总 Token)带色块,
    参考成本与调用次数保留无色占位以维持对齐。读屏的 barAria / hmTitle 带同一份 {cost}。
  • 演示图与市场截图:README 的两张图换成 0.5.0 仪表盘截图,新增 screenshots.json,插件市场
    按仓库里的声明取图(不再从 README 猜)。

两个需要说明的设计决定

  1. 全量统计改为「第一次查询」才启动(startBackfillOnDemand)。此前在 apply 里开扫,
    而缓存加载只需 0.78 s(冷缓存 ~12 s)——窗口永远发生在用户打开面板之前,代码里有加载态、
    屏幕上一次都看不见。代价是"读请求触发写",权衡与边界写在 docs/native-transport.md §3。
  2. 加载态只由宿主的 backfill 状态驱动:统计未完成期间整段骨架 + 确定进度条
    (宽度 = done/total),不先给半份会往上爬的数字;尊重 prefers-reduced-motion。

测试

  • npm test 224 项(宿主行为 + 客户端槽位/加载态/定价接线门 + dock 契约)
  • tests/dialog.test.cjs 75 项(在构建产物上跑真实 bundle + 假宿主)
  • npm run verify:typert(官方 loader 校验 4 个端点)/ npm run verify:remote(7/7)

其中几条门是"接线门"而非行为门,专门抓 两半各自都对 的回归:模型表必须渲染宿主的
unpriced 判定(不得自行重算)、骨架卡片数必须与统计卡槽位同源、按需扫描必须仍接在查询
处理器上。变异验证过:删掉调用点/改回旧写法都会立即变红。

另外修掉一处会随包发布的本机路径:节假日历生成物的 coverage.basis 不再写入维护者的
C:\Users\…\sessions,只保留 session store / calendar default,并加门防止回归。

兼容性

engines.dsh 为 >=0.2.1-rc.3 <0.3.0,dsh.verifiedAgainst 为 0.2.1-rc.4(两个契约门都在
rc.4 上重跑过)。输入区 pill 需要 0.2.1 的 conversation.composer.dock;0.2.0 缺该 slot 时
注入失败只告警,不影响设置页。证书与取证对象写在 docs/native-transport.md。

ruanbw and others added 6 commits October 31, 2026 03:14
…dashboard

Adds the reference price table (generated from opencode Zen + DeepSeek by
scripts/gen-prices.mjs) and one resolution ladder in resolvePrice: user custom
price, confirmed match, literal id, variant, fuzzy. Peak/off-peak billing follows
the official DeepSeek rule (Beijing 09-12 and 14-18, Mon-Fri, excluding Chinese
public holidays) against a generated holiday table.

Persists the per-session fold result so a restart hydrates instead of re-reading
the whole history: 0.08 s warm, against 12.5 s of pure inflate for 404 MB. It
never prunes, because a partial listing once deleted 257 of 258 valid entries.

Replaces the dynamic-plugin source endpoint with a native typert Remote face and
keeps connection.fetch as the always-on fallback.

Backfill starts on the first query rather than at activation. The cache-loading
window is the only moment a loading state can be seen, and starting it from apply
put that window before anyone opens the panel; the read-triggers-write trade-off
is documented in docs/native-transport.md.

The dashboard shows reference cost wherever a period is summarised, including the
hover cards of the trend chart (bar and line) and the yearly heatmap: one TipRows
serves all three, and only the rows that exist in the legend carry a colour
swatch — 参考成本 and 调用次数 keep a colourless spacer so the labels still line up.
219 tests plus a 75-check build-artifact gate. Several pin the connections a
refactor can quietly break, because that failure mode leaves both halves green on
their own:

- the model table renders the host's `unpriced` verdict instead of re-deriving it
  from `tokens > 0 && cost == 0` (how a model the user priced at 0 read 未定价);
- the loading skeleton takes its card count from the same slot table as the
  dashboard (it was hard-coded to five while the row had six);
- the on-demand scan stays wired to the query handler, and activation must not
  call runBackfill again;
- the three chart hover cards share one TipRows whose cost comes from the host
  bucket, and only legend-backed rows carry a colour swatch.

fold-parity compares our event fold against a recording of the official tokenUsage
projection taken from the shipped asar. Each gate was mutation-checked: breaking
the thing it guards turns it red.
docs/native-transport.md records the two L2 gaps with their counter-proofs (the
settings nav icon and the fetch fallback), the fold-cache bypass with the three
facts that justify keeping it, and the read-triggers-write trade-off the on-demand
scan introduced.

CHANGELOG carries one 0.5.0 entry. The local 0.4.1-0.4.8 iterations and the
0.5.1/0.5.2 bumps are folded into it: they were never releases, only local
iterations and a workaround for replacing a same-version file: dependency.

README's module map lists the modules that exist, and its install section records
what was measured: a same-version file: dependency cannot be replaced in this
profile (pnpm resolves from the store by the lockfile integrity), and replacing
files under a running host is not an option — at 02:00 on 2026-10-31 a hand copy
into node_modules was followed 0.8 s later by a host exit with 0xC0000005.
The holiday generator recorded the directory it derived the year span from
(`session store (C:\Users\...\sessions)`) into `coverage.basis`. That string
ships inside the package, so it leaked the maintainer's home directory — and
the weekly CI run, which has no session store, would rewrite it to `calendar
default` and back on every regeneration.

The span still comes from the session store; only the recorded label is now
environment-neutral (`session store` / `calendar default`). A gate in
tests/holiday-years.test.js rejects an absolute path in the generated file.
- Replace image.png / image-1.png with the 0.5.0 dashboard (overview, then the
  tooltip / heatmap / model table).
- Add screenshots.json so the plugin market reads these two images straight
  from this repository instead of extracting them from the README.
- README: state the verified engines range (>=0.2.1-rc.3, verified on rc.4) and
  the composer-dock requirement; CHANGELOG: fold in the 0.4.0 merge (dock pill
  kept), the current test count (224) and the generated-path fix.
The `lib/` build-output rule matches every directory named lib, so
scripts/lib/holiday-years.mjs was silently ignored and never committed. Local
runs passed because the file sits on disk; on a clean CI checkout
tests/holiday-years.test.js failed with ERR_MODULE_NOT_FOUND, taking its eight
cases down with it.

Negate the rule for scripts/lib and add the helper.
@xinspark
xinspark merged commit fc4a34d into bennett-dsh:main Oct 30, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants