dsh-all-usage 1.1.5 → 1.1.6

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/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  All notable changes to `dsh-all-usage` are documented here.
4
4
 
5
+ ## [1.1.6] - 2026-09-11
6
+
7
+ ### Fixed
8
+
9
+ - DSH 0.1.5 compatibility: that runtime replaced the Session `events` getter with `snapshotEvents()`, so the live flush path read an empty event log and folded every flushed session into an empty ledger record (`lastSeq: -1`, no turns, no usage). The dashboard stayed correct because live totals come from the `session/event` hook, but the durable "usage survives session deletion" guarantee silently degraded and restart lost the zero-rescan fast path. Event access now prefers `session.events` and falls back to `session.snapshotEvents()`, leaving 0.1.1 behaviour unchanged.
10
+ - The runtime smoke test now asserts the persisted ledger keeps the flushed session's sequence and usage, and covers 0.1.5-rc.1 alongside the 0.1.1 line, so this regression cannot return unnoticed.
11
+
12
+ ### Changed
13
+
14
+ - Declared DSH runtime compatibility widened to `>=0.1.1-rc.1 <0.1.6`, with `0.1.5-rc.1` added to the verified list; the runtime smoke test pins the Cordis/loader/timer lines per runtime version.
15
+
5
16
  ## [1.1.5] - 2026-09-07
6
17
 
7
18
  ### Added
package/README.md CHANGED
@@ -21,6 +21,7 @@ DeepSeek Harness 全量用量看板:按模型、供应商、工作区和时间
21
21
  - **完整历史与增量重建**:基线扫描全部可读历史会话;独立用量账本同时作为每会话游标——未变化的会话直接复用账本,新增事件只增量回填,长历史重启不再全量重建
22
22
  - **重启免读**:用持久化日志的 revision 作为每会话的变更信号(只读头部行 + stat,不读全量)——日志未变的会话重启时连事件都不读,直接从账本复用;仅日志变化(新增/修改)的会话才做增量读取
23
23
  - **数据健康与按需刷新**:扫描完成后浏览器只检查轻量状态版本,只有用量、别名或同步状态变化时才拉完整历史;显示本次数据更新时间、历史扫描健康、revision 免读、实际读取、账本恢复和失败,网络异常保留上次成功数据并可重试
24
+ - **工作区注册同步**:监听 DSH 的 `domain/changed` 事件自动跟随工作区注册表——注册表一有改动(创建/删除/重命名/重排/归档/成员变化)就重读 `workspaceRegistry.list()`;只对新增/删除的工作区做增量处理,未变化的已有工作区直接复用已计算账本(零重扫)。统计严格限定已注册工作区:未注册 cwd(含存在但未登记目录)一律忽略
24
25
  - **性能优化**:Host 在 ingest 时维护 local/UTC 的日期、工作区、模型身份日级 cube 与单日小时桶;scope 查询按 bucket 合并,成本使用精确 BigInt 小数累加,53 周热力图只生成实际需要的字段,并继续使用 revision-scoped snapshot/records 缓存和可回收的实时事件队列;Client 将热力图、tooltip、趋势、环形图、请求日志和定价对话框隔离为 memoized 边界,指针坐标通过 ref + requestAnimationFrame 更新,不再触发整页重渲染;浏览器入口在打包前确定性压缩
25
26
  - **趋势折线图**:按当前范围、时区、工作区、供应商和模型显示输入、缓存读写、输出、推理及总处理量;单日范围按小时聚合并显示小时轴,跨日范围按日聚合;使用平滑单调曲线与入场动画,悬停查看精确值,图例可切换曲线,点击点位进入当日明细
26
27
  - **统一筛选与审计**:工作区、供应商、模型和日期筛选贯穿摘要、热力图、趋势、表格与 CSV;工作区、供应商、模型三个筛选维度可独立自由组合,工作区、供应商和模型选项只展示当前日期范围内实际使用过的值;切换范围后失效筛选会自动清除;请求日志以紧凑分页表常驻显示,选择单条后查看分组 Token 详情
@@ -103,6 +104,11 @@ node scripts/replay-fixture.mjs fixtures/usage-events.json
103
104
 
104
105
  ### 最近更新
105
106
 
107
+ **v1.1.5**
108
+
109
+ - **工作区注册探针**:自动跟随 DSH 工作区注册表(`domain/changed` 事件);只对新增/删除的工作区增量重扫,未变化工作区直接复用账本,零全量重扫。移除了手动“刷新工作区”按钮与其 `POST /api/all-usage/workspaces/refresh` 路由。
110
+ - **严格注册边界**:统计只包含 cwd 能映射到已注册工作区的会话;未登记目录(即使存在)与历史 `unregistered:` 账本行不再进入统计,已删除目录的旧账本行在恢复时跳过(`sourceCwd` 现在随每条账本持久化以校验归属)。
111
+
106
112
  **v1.1.4**
107
113
 
108
114
  - **模型品牌图标**:请求日志、选中调用详情、模型统计表、成本设置匹配表、环形图图例与模型筛选下拉现在显示真实厂商品牌 SVG(DeepSeek、OpenAI、Claude、Gemini、Meta、Zhipu、xAI、Qwen、Doubao、Kimi、MiniMax);模型命名空间优先于 DSH 供应商名,未知或混牌行保持中性。图标在构建期以 data URI 内嵌并由 scripts/svg-guard.mjs 校验(实体解码、CSS 转义/CDO/命名空间、url()/image-set() 均覆盖,注释与文本不误杀),运行时零网络请求。
@@ -213,6 +219,7 @@ A full usage dashboard for DeepSeek Harness. Analyze tokens, cache behavior, est
213
219
  - **Interface language**: switch between Chinese and English from the dashboard header; your choice persists locally in the browser
214
220
  - **Full history & incremental rebuild**: the baseline scans every readable historical session; the durable usage ledger doubles as a per-session cursor, so unchanged sessions are reused straight from the ledger and only newly appended events are folded — long histories restart without a full rebuild
215
221
  - **Restart with no re-read**: the persisted log revision (a header-line + stat via `sessionPersistence.listSnapshots()`) acts as a per-session change signal — sessions whose log is unchanged are applied from the ledger on restart without reading their events at all; only changed/new sessions are read incrementally
222
+ - **Workspace registry sync**: follows DSH's `domain/changed` event so the workspace registry stays fresh automatically — any durable registry write (create/delete/rename/reorder/archive/membership) triggers a reread of `workspaceRegistry.list()`, with only added/removed workspaces reprocessed incrementally while unchanged workspaces reuse their computed aggregates and ledger (zero rescan). Usage is strictly limited to registered workspaces: unregistered cwds, including existing directories absent from the registry, are ignored.
216
223
  - **Data health and on-demand refresh**: after a scan completes, the browser polls only a lightweight status revision and fetches full history only after usage, alias, or sync state changes; it shows the latest full-data update, historical scan health, revision skips, rereads, ledger recovery, and failures while preserving last-good data on network errors
217
224
  - **Performance**: Host maintains ingest-time local/UTC day, workspace, model-identity cubes and single-day hour buckets; scope queries merge buckets, exact costs use BigInt decimal accumulators, and the 53-week heatmap emits only the fields it consumes, while revision-scoped snapshot/records caches and recyclable live-event queues remain in place. Client isolates the heatmap, tooltip, trend, donuts, request records, and pricing dialog behind memoized boundaries; pointer coordinates update through refs plus requestAnimationFrame instead of rerendering the page, and the browser entry is deterministically minified before packing
218
225
  - **Trend line chart**: show input, cache read/write, output, reasoning, and total processed tokens for the active range, timezone, workspace, provider, and model scope; use hourly buckets for a single-day scope and daily buckets for cross-day scopes, with smooth monotone curves, staged entrance animation, hover for exact values, and click a point to inspect that day
@@ -296,6 +303,11 @@ The command loads the real plugin Host, calls its compatible APIs, checks the do
296
303
 
297
304
  ### Latest Update
298
305
 
306
+ **v1.1.5**
307
+
308
+ - **Workspace registry probe**: all-usage follows DSH's workspace registry automatically through the `domain/changed` event; only added/removed workspaces are reprocessed incrementally while unchanged workspaces reuse their ledger with zero rescan. The manual Refresh workspaces control and its `POST /api/all-usage/workspaces/refresh` route were removed.
309
+ - **Strict registration boundary**: only sessions whose cwd maps to a registered workspace are counted; unregistered directories (even existing ones) and legacy `unregistered:` ledger rows never re-enter statistics, and ledger rows for deleted directories are skipped on recovery (`sourceCwd` is now persisted with every ledger record so ownership can be validated after a restart).
310
+
299
311
  **v1.1.4**
300
312
 
301
313
  - **Vendor brand icons**: request logs, selected-call details, the model summary table, the cost-settings match table, donut legends, and the model filter dropdown now render real vendor brand SVGs (DeepSeek, OpenAI, Claude, Gemini, Meta, Zhipu, xAI, Qwen, Doubao, Kimi, MiniMax); the model namespace wins over the DSH provider name, and unknown or mixed-brand rows stay neutral. Icons are embedded as data URIs at build time and validated by scripts/svg-guard.mjs (entity decoding, CSS escapes/CDO/namespaced styles, url()/image-set() all covered, comments and text stay inert), with zero runtime network access.
package/lib/ledger.js CHANGED
@@ -9,6 +9,24 @@ const LEDGER_UNIT_NAME = 'all_usage_ledger'
9
9
  const LEDGER_META_KEY = '__all_usage_ledger_meta__'
10
10
  const LEDGER_WRITE_DEBOUNCE_MS = 25
11
11
 
12
+ // DSH 0.1.5 replaced the Session `events` getter with `snapshotEvents()` while
13
+ // 0.1.1 exposed `events`; both return the log in seq order. Read whichever the
14
+ // running runtime provides, because falling back to an empty log would fold a
15
+ // flushed session into an empty ledger record.
16
+ function sessionEvents(session) {
17
+ if (session === null || session === undefined) return []
18
+ if (Array.isArray(session.events)) return session.events
19
+ if (typeof session.snapshotEvents === 'function') {
20
+ try {
21
+ const snapshot = session.snapshotEvents()
22
+ if (Array.isArray(snapshot)) return snapshot
23
+ } catch (error) {
24
+ return []
25
+ }
26
+ }
27
+ return []
28
+ }
29
+
12
30
  // Ledger sequences follow the DSH contract: -1 marks a missing/unavailable
13
31
  // sequence, otherwise a non-negative safe integer is required.
14
32
  function validLedgerSeq(value) {
@@ -173,7 +191,7 @@ export function createLedger(host) {
173
191
  }
174
192
  function buildLedgerRecord(session, workspaceId, source = 'scan', revision, previousRecord, sequenceClean = true) {
175
193
  const sid = session && typeof session.id === 'string' ? session.id : ''
176
- const events = session && Array.isArray(session.events) ? session.events : []
194
+ const events = sessionEvents(session)
177
195
  if (sid === '' || workspaceId === undefined) return null
178
196
  // DSH sequences every persisted event with seq === log index, so the last
179
197
  // event's seq is the record's lastSeq in O(1); fall back to a full scan only
@@ -476,6 +494,7 @@ export function createLedger(host) {
476
494
  return {
477
495
  nextLedgerRevision,
478
496
  ledgerEventKey,
497
+ sessionEvents,
479
498
  buildLedgerRecord,
480
499
  normalizeLedgerRecord,
481
500
  prepareLedgerRecord,
@@ -18,6 +18,7 @@ export function createSessionSync(host) {
18
18
  } = host.aggregation
19
19
  const { beginSync, noteSyncError, markStatsChanged } = host
20
20
  const {
21
+ sessionEvents,
21
22
  buildLedgerRecord,
22
23
  applyLedgerRecord,
23
24
  storeLedgerRecord,
@@ -710,7 +711,7 @@ export function createSessionSync(host) {
710
711
  if (state.disposed || session === null || typeof session !== 'object' || typeof session.id !== 'string') return
711
712
  const sid = session.id
712
713
  const previous = state.ledgerRecords.get(sid)
713
- const eventList = Array.isArray(session.events) ? session.events : []
714
+ const eventList = sessionEvents(session)
714
715
  // DSH sequences events with seq === log index: the tail event gives the
715
716
  // latest sequence in O(1) without scanning a 500K-event session twice.
716
717
  const tailEvent = eventList.length > 0 ? eventList[eventList.length - 1] : null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-all-usage",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "description": "DeepSeek Harness usage dashboard with model, provider, workspace, cache, balance, and CSV insights",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,8 +31,8 @@
31
31
  },
32
32
  "dsh": {
33
33
  "compatibility": {
34
- "runtime": ">=0.1.1-rc.1 <0.1.2",
35
- "verified": ["0.1.1-rc.2", "0.1.1-rc.1"]
34
+ "runtime": ">=0.1.1-rc.1 <0.1.6",
35
+ "verified": ["0.1.5-rc.1", "0.1.1-rc.2", "0.1.1-rc.1"]
36
36
  },
37
37
  "bundle": {
38
38
  "patch": "./cordis.patch.yml"