@chenmiao8563/dsh-token-ledger 0.1.0 → 0.2.0
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 +60 -1
- package/README.md +38 -1
- package/README.zh.md +31 -1
- package/docs/VERIFICATION.md +152 -7
- package/lib/cli.js +12 -9
- package/lib/client.js +678 -0
- package/lib/index.js +42 -6
- package/lib/ledger.js +63 -12
- package/lib/overview.js +228 -0
- package/lib/route.js +141 -0
- package/package.json +16 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,63 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
8
8
|
|
|
9
9
|
Nothing yet.
|
|
10
10
|
|
|
11
|
+
## [0.2.0] - 2026-09-10
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **A settings page.** The browser half registers a `settings.section`, so the
|
|
16
|
+
ledger appears in the settings sidebar beside every other section. It shows:
|
|
17
|
+
- range totals for **this month**, **this year** and the **last 7 days**, each
|
|
18
|
+
with total tokens, cache hit rate and call count, plus the bucket breakdown;
|
|
19
|
+
- **today**, live, which moves as steps complete;
|
|
20
|
+
- a **usage calendar** switchable between year, month and week, where the week
|
|
21
|
+
view is a bar chart rather than a heatmap;
|
|
22
|
+
- the per-model breakdown with its own cache hit rates.
|
|
23
|
+
- `lib/overview.js`, a pure function from a ledger snapshot to the payload the
|
|
24
|
+
page renders, and `lib/route.js`, the read-only
|
|
25
|
+
`GET /api/token-ledger/summary` route that serves it.
|
|
26
|
+
- The browser half is hand-written against the client's module loader and
|
|
27
|
+
requires nothing but `react`, so the package still installs with no build step.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- The overview is served over an HTTP route rather than a settings namespace.
|
|
32
|
+
A namespace would need a schema — a real dependency — and would rewrite
|
|
33
|
+
`settings.yaml` on every debounce with data that is derived and reproducible.
|
|
34
|
+
The ledger file remains the only store of record.
|
|
35
|
+
|
|
36
|
+
## [0.1.1] - 2026-09-10
|
|
37
|
+
|
|
38
|
+
Both fixes come from mounting the plugin in a real DSH host, which is the only
|
|
39
|
+
place either defect was observable. Neither was reachable from the test suite,
|
|
40
|
+
so both now have regression tests.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- **`/tokens` never registered.** The command definition passed `input` as a
|
|
45
|
+
bare string, and the command registry requires an object carrying a non-empty
|
|
46
|
+
`hint`. The resulting `TypeError` was thrown during mount, which also aborted
|
|
47
|
+
the rest of `apply()` — so the flush-on-dispose effect was never installed
|
|
48
|
+
either.
|
|
49
|
+
- **Backfill under-counted forked sessions.** The inherited boundary was applied
|
|
50
|
+
as a raw index, but a stored session can arrive in the compact row form where
|
|
51
|
+
several logical events share one record and the declared count overshoots the
|
|
52
|
+
array length by an order of magnitude. Applying it skipped those sessions
|
|
53
|
+
entirely, which under-counted a real 139-session home by hundreds of millions
|
|
54
|
+
of tokens. The boundary is now located by the `session/end-seed` marker, which
|
|
55
|
+
is present and equivalent in both coordinate spaces, with the declared count
|
|
56
|
+
as a fallback that is refused when it cannot index the array it came with. A
|
|
57
|
+
fork with no usable boundary is folded whole and warned about, so the error
|
|
58
|
+
direction is over-counting rather than silent loss.
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
|
|
62
|
+
- The on-disk ledger format is now version 2. A ledger written by 0.1.0 is
|
|
63
|
+
ignored on start rather than trusted, because its cursors would otherwise
|
|
64
|
+
mark every session as consumed and preserve the mis-counted totals forever.
|
|
65
|
+
The ledger rebuilds itself from the session logs, so the only effect is that
|
|
66
|
+
the first start after upgrading does the backfill again.
|
|
67
|
+
|
|
11
68
|
## [0.1.0] - 2026-09-10
|
|
12
69
|
|
|
13
70
|
### Added
|
|
@@ -31,5 +88,7 @@ Nothing yet.
|
|
|
31
88
|
- Zero runtime dependencies, zero peer dependencies and no install scripts, so
|
|
32
89
|
the package installs without a build step.
|
|
33
90
|
|
|
34
|
-
[Unreleased]: https://github.com/chenmiao8563/dsh-token-ledger/compare/v0.
|
|
91
|
+
[Unreleased]: https://github.com/chenmiao8563/dsh-token-ledger/compare/v0.2.0...HEAD
|
|
92
|
+
[0.2.0]: https://github.com/chenmiao8563/dsh-token-ledger/compare/v0.1.1...v0.2.0
|
|
93
|
+
[0.1.1]: https://github.com/chenmiao8563/dsh-token-ledger/compare/v0.1.0...v0.1.1
|
|
35
94
|
[0.1.0]: https://github.com/chenmiao8563/dsh-token-ledger/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -144,6 +144,33 @@ audit useless. The ledger's own `updatedAt` settles it:
|
|
|
144
144
|
(1 calls, 5100 tokens not yet flushed)
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
+
## Settings page
|
|
148
|
+
|
|
149
|
+
The browser half adds a **Token ledger** section to the settings sidebar:
|
|
150
|
+
|
|
151
|
+
- **Range totals** for this month, this year and the last 7 days — total tokens,
|
|
152
|
+
cache hit rate, call count, and the bucket breakdown behind them. The cache hit
|
|
153
|
+
rate is `cacheRead / (cacheRead + uncachedInput)`: the share of input the prompt
|
|
154
|
+
cache absorbed, so a route with no caching reads 0% rather than undefined.
|
|
155
|
+
- **Today, live** — today's tokens, hit rate and calls, which move as steps
|
|
156
|
+
complete.
|
|
157
|
+
- **A usage calendar** switchable between year, month and week. Year and month are
|
|
158
|
+
heatmaps; the week view is a bar chart. Heat levels are relative to the busiest
|
|
159
|
+
day in the window and square-rooted, so one huge day cannot flatten the rest.
|
|
160
|
+
- **Per model** totals, each with its own hit rate.
|
|
161
|
+
|
|
162
|
+
The page reads one read-only route, `GET /api/token-ledger/summary`, and polls it
|
|
163
|
+
every 15 seconds. The route refuses a non-loopback peer, so it stays private even
|
|
164
|
+
if the web server is bound to `0.0.0.0`.
|
|
165
|
+
|
|
166
|
+
It needs a profile with a web server (the `web` or `desktop` profile). Without one,
|
|
167
|
+
`/tokens` and the CLI still work, and the section says so instead of failing.
|
|
168
|
+
|
|
169
|
+
The overview is deliberately **not** published through a settings namespace. That
|
|
170
|
+
would need a schema — a real dependency, and this package has none — and would
|
|
171
|
+
rewrite `settings.yaml` on every debounce with data that is derived and
|
|
172
|
+
reproducible. The ledger file stays the only store of record.
|
|
173
|
+
|
|
147
174
|
## Configuration
|
|
148
175
|
|
|
149
176
|
Override the composition entry by its `id`:
|
|
@@ -186,7 +213,8 @@ The ledger file is left alone on purpose — delete
|
|
|
186
213
|
## Development
|
|
187
214
|
|
|
188
215
|
```bash
|
|
189
|
-
npm
|
|
216
|
+
npm install # devDependencies only: react, react-dom, and the slot registry DSH runs
|
|
217
|
+
npm test # 106 tests
|
|
190
218
|
npm run verify # packaging invariants (dependency-free, no install scripts, no bare imports)
|
|
191
219
|
```
|
|
192
220
|
|
|
@@ -194,6 +222,15 @@ npm run verify # packaging invariants (dependency-free, no install scripts,
|
|
|
194
222
|
spawning a child process per test file is blocked, use
|
|
195
223
|
`npm run test:single-process`.
|
|
196
224
|
|
|
225
|
+
The devDependencies are **test-only**. They are never installed for a consumer:
|
|
226
|
+
the package ships with no dependencies, no peer dependencies and no install
|
|
227
|
+
scripts, and `pnpm` does not install a dependency's devDependencies. They exist
|
|
228
|
+
so the browser half can be checked against the real thing — React renders it, so
|
|
229
|
+
a hook-order or DOM-prop mistake fails instead of passing silently, and
|
|
230
|
+
`@deepseek-ai/dsh-client-ui-slots` validates the registration against the very
|
|
231
|
+
registry that DSH runs. Without them installed the affected tests skip with a
|
|
232
|
+
stated reason, so `npm test` still works from a fresh clone with no network.
|
|
233
|
+
|
|
197
234
|
See [docs/VERIFICATION.md](docs/VERIFICATION.md) for what was actually verified
|
|
198
235
|
and how, including the evidence behind the fork rule.
|
|
199
236
|
|
package/README.zh.md
CHANGED
|
@@ -136,6 +136,29 @@ dsh-token-ledger export [选项] 导出 CSV 与 JSON
|
|
|
136
136
|
(1 calls, 5100 tokens not yet flushed)
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
+
## 设置页
|
|
140
|
+
|
|
141
|
+
浏览器端会在设置侧边栏注册一个 **用量账本** 分区:
|
|
142
|
+
|
|
143
|
+
- **区间总计**:本月 / 本年 / 近 7 天三档可切换,每档显示 Token 总计、缓存命中率、
|
|
144
|
+
调用次数,以及背后的四桶明细。缓存命中率定义为
|
|
145
|
+
`缓存读入 / (缓存读入 + 未命中输入)`——即输入中被缓存吸收的比例,因此完全不缓存的
|
|
146
|
+
路由读数是 0%,而不是空白。
|
|
147
|
+
- **今日实时**:今天的 Token、命中率与调用次数,随每一步完成而变动。
|
|
148
|
+
- **用量热力图**:年 / 月 / 一周三档可切换;年和月是热力图,切到一周改为柱状图。
|
|
149
|
+
热力档位相对窗口内最忙的一天取平方根,避免某一天特别大把其余全部压成最淡档。
|
|
150
|
+
- **按模型**:各模型的用量与各自命中率。
|
|
151
|
+
|
|
152
|
+
页面只读一个路由 `GET /api/token-ledger/summary`,每 15 秒轮询一次。
|
|
153
|
+
该路由会拒绝非回环来源,因此即使 web 服务器绑定到 `0.0.0.0` 也不会外泄。
|
|
154
|
+
|
|
155
|
+
它需要带 web 服务器的 profile(`web` 或 `desktop`)。没有的话 `/tokens` 与 CLI
|
|
156
|
+
照常可用,分区会明确说明而不是直接失败。
|
|
157
|
+
|
|
158
|
+
概览**刻意不**通过设置命名空间下发:那需要 schema(一个真实依赖,而本包零依赖),
|
|
159
|
+
而且每次防抖都会用可推导、可重放的数据重写一遍 `settings.yaml`。
|
|
160
|
+
账本文件始终是唯一的记录来源。
|
|
161
|
+
|
|
139
162
|
## 配置
|
|
140
163
|
|
|
141
164
|
按 `id` 覆盖组合条目:
|
|
@@ -172,13 +195,20 @@ dsh plugin --profile web remove @chenmiao8563/dsh-token-ledger
|
|
|
172
195
|
## 开发
|
|
173
196
|
|
|
174
197
|
```bash
|
|
175
|
-
npm
|
|
198
|
+
npm install # 两个 devDependency:react 与 react-dom,供渲染测试使用
|
|
199
|
+
npm test # 96 个测试
|
|
176
200
|
npm run verify # 打包不变式(零依赖、无安装脚本、无裸模块说明符)
|
|
177
201
|
```
|
|
178
202
|
|
|
179
203
|
`npm test` 使用 Node 内置测试运行器。在禁止逐文件 spawn 子进程的受限环境里,
|
|
180
204
|
改用 `npm run test:single-process`。
|
|
181
205
|
|
|
206
|
+
React **只是 devDependency**,消费者永远不会安装它:本包不带任何依赖、任何 peer
|
|
207
|
+
依赖、任何安装脚本,而 pnpm 不会为依赖安装其 devDependencies。它在这里的作用是让
|
|
208
|
+
浏览器端能用**真库**渲染并断言——这能抓到替身抓不到的东西:hook 顺序违规与非法
|
|
209
|
+
DOM 属性,React 会报出来,而手写的 `createElement` 会默默接受。没装它时这些渲染
|
|
210
|
+
测试会带明确原因**跳过而不是失败**,所以全新克隆、无网络也能跑 `npm test`。
|
|
211
|
+
|
|
182
212
|
实际验证了什么、怎么验证的(包括 fork 规则背后的证据)见
|
|
183
213
|
[docs/VERIFICATION.md](docs/VERIFICATION.md)。
|
|
184
214
|
|
package/docs/VERIFICATION.md
CHANGED
|
@@ -139,6 +139,49 @@ this similarity gate, which runs at publish time. The scoped name was
|
|
|
139
139
|
re-verified end to end as shown above; the CLI command and Cordis entry id are
|
|
140
140
|
unchanged.
|
|
141
141
|
|
|
142
|
+
### Install from the npm registry, isolated
|
|
143
|
+
|
|
144
|
+
The published artifact was installed the way a stranger would install it, into a
|
|
145
|
+
disposable `DSH_HOME`:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
$ DSH_HOME=<tmp>/home <dsh-desktop-cli> plugin --profile web add @chenmiao8563/dsh-token-ledger
|
|
149
|
+
dsh: initialized profile web at <tmp>/home/profiles/web
|
|
150
|
+
Progress: resolved 1, reused 0, downloaded 0, added 0
|
|
151
|
+
dependencies:
|
|
152
|
+
+ @chenmiao8563/dsh-token-ledger ^0.1.0
|
|
153
|
+
Packages: +1
|
|
154
|
+
Progress: resolved 1, reused 0, downloaded 1, added 1, done
|
|
155
|
+
Done in 2.7s using pnpm v11.8.0
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The lockfile records a real registry resolution with its integrity hash and the
|
|
159
|
+
declared engine range:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
'@chenmiao8563/dsh-token-ledger@0.1.0':
|
|
163
|
+
resolution: {integrity: sha512-yApGruS1Hcyq0oIOq8t6Y8gAJFefAoiHLzDsvPsecND6+DIj878x8YtQ5Ggz+PVYL9yTvaPAgIVcOhyBNvPGnw==}
|
|
164
|
+
engines: {node: '>=22.15.0'}
|
|
165
|
+
hasBin: true
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The reconciler added it to the bundle stack, and `pnpm-workspace.yaml` was left
|
|
169
|
+
untouched — **no `allowBuilds` entry was needed**, which is the installability
|
|
170
|
+
claim this document exists to check:
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
"dsh": { "profile": { "bundles": [
|
|
174
|
+
"@deepseek-ai/dsh-base",
|
|
175
|
+
"@deepseek-ai/dsh-web-app",
|
|
176
|
+
"@chenmiao8563/dsh-token-ledger"
|
|
177
|
+
] } }
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The packument itself took roughly two minutes to become readable after the first
|
|
181
|
+
publish in the new scope — during that window `dist-tags` and the tarball were
|
|
182
|
+
already live while `GET /@chenmiao8563%2Fdsh-token-ledger` still returned 404.
|
|
183
|
+
That is registry propagation, not a failed publish.
|
|
184
|
+
|
|
142
185
|
### Note for whoever verifies this next
|
|
143
186
|
|
|
144
187
|
On this machine the `dsh` shell shim hardcodes `DSH_HOME`, so exporting
|
|
@@ -151,18 +194,120 @@ $env:DSH_HOME = '<tmp>\home'
|
|
|
151
194
|
& '<install>\DSH Desktop.exe' --expose-internals '<install>\resources\app.asar\lib\desktop-cli.js' plugin --profile web add <path>
|
|
152
195
|
```
|
|
153
196
|
|
|
197
|
+
## Live host mount
|
|
198
|
+
|
|
199
|
+
Version 0.1.0 was installed into a real desktop profile, the host was restarted,
|
|
200
|
+
and the host log recorded the mount:
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
20:24:52 [I] [token-ledger] started a new ledger at <DSH_HOME>\token-ledger\ledger.json
|
|
204
|
+
20:24:52 [E] [token-ledger] TypeError: command "tokens" input hint must be a string
|
|
205
|
+
20:25:00 [I] [token-ledger] backfilled 139 stored session(s), 0 unreadable; total 790455336 tokens over 4518 calls
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
This step was worth doing precisely because it failed. Both defects it exposed
|
|
209
|
+
were unreachable from the 49-test suite, and both are fixed in 0.1.1 with
|
|
210
|
+
regression tests:
|
|
211
|
+
|
|
212
|
+
| Defect | Evidence | Cause |
|
|
213
|
+
| --- | --- | --- |
|
|
214
|
+
| `/tokens` never registered | the `TypeError` above, and no `command/*` event when `/tokens` was typed | `input` was passed as a bare string; the registry requires `{ hint: string }`. The throw also aborted the rest of `apply()`, so the flush-on-dispose effect was never installed |
|
|
215
|
+
| backfill under-counted forks | 4,518 calls / 790,455,336 tokens against the CLI's 6,901 / 1,205,685,663 on the same logs | the inherited boundary was applied as a raw index, but the row-form log packs several logical events per record, so the declared count overshot the array and skipped those sessions entirely |
|
|
216
|
+
|
|
217
|
+
The diagnosis came from the ledger's own artifacts rather than from guesswork:
|
|
218
|
+
the host log line above, and a cross-tabulation of the ledger's 65 recorded
|
|
219
|
+
sessions against the 72 logs carrying a `session/end-seed` marker and the 29
|
|
220
|
+
declaring a parent. The 61 sessions outside both sets turned out to be
|
|
221
|
+
four-record logs with no usage at all, which is why they correctly have no entry.
|
|
222
|
+
|
|
223
|
+
## Browser half (0.2.0)
|
|
224
|
+
|
|
225
|
+
What is checked here and what is not, because the difference is large.
|
|
226
|
+
|
|
227
|
+
**Checked by test.** `lib/overview.js` and `lib/route.js` are pure or near-pure
|
|
228
|
+
and are tested directly: range boundaries, cache-hit-rate arithmetic, gap-filled
|
|
229
|
+
series, the loopback and origin guard, method handling, and the 500-on-fault
|
|
230
|
+
path. The browser half is loaded through a stand-in module loader with a
|
|
231
|
+
stand-in React, which exercises the module wrapper, the registration contract,
|
|
232
|
+
and the whole render tree — formatting, heat levels, week-bar slicing, view
|
|
233
|
+
switching, and the empty, malformed and stale-data paths.
|
|
234
|
+
`scripts/verify-package.mjs` additionally fails the build if the browser half
|
|
235
|
+
requires anything the loader does not provide, if its module id stops matching
|
|
236
|
+
the package name, if it stops compiling as a classic script, or if a declared
|
|
237
|
+
client dependency is outside the client-half namespace.
|
|
238
|
+
|
|
239
|
+
**Checked by reading DSH's own source, not by inference.** The contracts the
|
|
240
|
+
browser half depends on were read out of the shipped bundles rather than copied
|
|
241
|
+
from a third-party plugin:
|
|
242
|
+
|
|
243
|
+
| Contract | Where it was read | Result |
|
|
244
|
+
| --- | --- | --- |
|
|
245
|
+
| The `settings.section` registration shape | the sections DSH ships — `dsh-client-ui-settings-models` (order 10), `-settings-plugins` (15) and `-ui-agent-preset` (20) — plus the shell in `-settings-general` that renders them via `renderSlot("settings.section", …, { only: active })` | identical field set; `locale` is optional, proven by `-settings-models` omitting it; the sidebar rows come from `id`, `order` and `label`, all of which this plugin supplies |
|
|
246
|
+
| The client module format | every `@deepseek-ai/dsh-client-*/lib/client.js` | byte-identical wrapper: `window.__ModuleLoader__.load({ id, factory })` with `var module = { exports: {} }` and `exports.apply`/`exports.inject` |
|
|
247
|
+
| When the factory runs | `dsh-client-modules` module docs | only registration happens at script execution; body side effects, styles included, run at materialization — which is why the stylesheet is installed inside `apply()` |
|
|
248
|
+
| The `dsh.client` declaration | `parseDshClient` in `dsh-client-modules/lib/index.js` | `platform` must be a string and only `web` is loaded; `inject` and `external` are optional string arrays; `external` — not `inject` — is what orders the module graph |
|
|
249
|
+
| What `dsh.client.inject` **contains** | the five `dsh.client` declarations DSH ships, two of them non-empty: `dsh-client-locale` and `dsh-client-ui-settings-general` | **package/entry ids, not service names.** A module's own exported `inject` is the separate service list. This plugin lists the two packages whose services it uses, which is the same convention, and an empty list is also legal (`dsh-client-connection` uses one) |
|
|
250
|
+
| What `external` contains | the module-graph walk in `dsh-client-modules/lib/client.js`, which runs every entry through `stripClientSuffix` before resolving it | module specifiers for *other rows*. This plugin requires only `react`, which the loader provides and no row owns, so it declares none |
|
|
251
|
+
| How `exports["./client"]` is located | `clientExportOf` in `dsh-client-modules/lib/index.js` | a plain string or an object with a string `default`; anything else throws. A plain string is used here, and `verify-package.mjs` pins it |
|
|
252
|
+
| Whether a **scoped** package id survives | `stripClientSuffix` in `dsh-client-modules/lib/client.js` | it removes only a trailing `/client`, so `@chenmiao8563/dsh-token-ledger` passes through intact. DSH's own client packages are scoped too, so the transport already carries ids containing `/` |
|
|
253
|
+
|
|
254
|
+
That reading corrected two real mistakes in the first draft of this half, both of
|
|
255
|
+
which would have been a silent failure rather than an error:
|
|
256
|
+
|
|
257
|
+
- `@deepseek-ai/dsh-client-ui-slots` was listed as a client dependency. It is a
|
|
258
|
+
pure core library (its manifest exports only `.`, and it declares no
|
|
259
|
+
`dsh.client`), so it is never a client module row.
|
|
260
|
+
- The settings shell package was listed as `-ui-settings`, which holds the
|
|
261
|
+
namespace-scope service; the section slot is declared and rendered by
|
|
262
|
+
`-ui-settings-general`.
|
|
263
|
+
|
|
264
|
+
**Checked under the real React.** `test/client-render.test.mjs` renders the
|
|
265
|
+
presentation component with React 18.3.1 — the same major version DSH bundles —
|
|
266
|
+
through `react-dom/server`, and asserts the actual markup: the range tabs and
|
|
267
|
+
their active state, the totals, hit rate and call count for each range, today's
|
|
268
|
+
block surviving a year-range selection, the year heat grid (one cell per day plus
|
|
269
|
+
five legend swatches), the month grid (every day of March), the week bars (seven,
|
|
270
|
+
with inline heights), bounded heat levels drawn from a five-entry palette,
|
|
271
|
+
proportional model bars, and the loading, error, stale and empty states. It also
|
|
272
|
+
captures `console.error` and `console.warn` and **fails if React complained at
|
|
273
|
+
all**, which is what catches an invalid DOM prop or a hook-order violation — the
|
|
274
|
+
class of defect a hand-rolled `createElement` accepts silently.
|
|
275
|
+
|
|
276
|
+
React is a devDependency, so this runs in CI and under `prepublishOnly`. Without
|
|
277
|
+
it installed the file skips with a stated reason rather than failing, which keeps
|
|
278
|
+
`npm test` working from a fresh clone with no network.
|
|
279
|
+
|
|
280
|
+
**Checked against the real slot registry.** `test/slot-registration.test.mjs`
|
|
281
|
+
feeds the registration `apply` actually builds into
|
|
282
|
+
`@deepseek-ai/dsh-client-ui-slots` — the registry DSH itself runs, pinned as a
|
|
283
|
+
devDependency to `0.1.2-rc.1`, the exact version this DSH bundles (`latest` on
|
|
284
|
+
npm is an older `0.0.1-rc.1`, so the version is pinned rather than ranged). It
|
|
285
|
+
asserts that the registry accepts the entry, that `resolveSlotLabel` resolves the
|
|
286
|
+
label thunk to a non-empty string — a label it could not resolve would render as
|
|
287
|
+
a blank sidebar row — that the entry id does not collide with the three sections
|
|
288
|
+
DSH ships, that a duplicate id is refused, that disposing retires the entry, and
|
|
289
|
+
that registering into an undeclared slot is refused, which is why the plugin uses
|
|
290
|
+
`slots.inject` rather than registering blind.
|
|
291
|
+
|
|
292
|
+
**Not checked.** Whether the client module loader accepts the file, whether the
|
|
293
|
+
shell renders the section, and how any of it looks. Nothing here executes
|
|
294
|
+
`window.__ModuleLoader__` or mounts the component into a DOM: the module's shape
|
|
295
|
+
is asserted to match every bundle DSH ships, and the registration is validated by
|
|
296
|
+
the real registry, but no single process loads the plugin the way the browser
|
|
297
|
+
does. The remaining risk is integration and visual — a first-run surprise, not a
|
|
298
|
+
contractual one.
|
|
299
|
+
|
|
154
300
|
## Not verified
|
|
155
301
|
|
|
156
302
|
Stated plainly, because a verification file that only lists successes is not
|
|
157
303
|
useful:
|
|
158
304
|
|
|
159
|
-
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
contract at mount time, not to the counting logic.
|
|
305
|
+
- **`/tokens` end to end through the GUI.** The command now satisfies the
|
|
306
|
+
registry contract and is covered by a regression test, but the corrected build
|
|
307
|
+
has not yet been observed answering `/tokens` in a live conversation.
|
|
308
|
+
- **Backfill parity with the CLI on a real host.** Both paths now share one
|
|
309
|
+
boundary function, and each is tested, but the backfill's numbers have not
|
|
310
|
+
been compared against a CLI rebuild on the same host after the 0.1.1 fix.
|
|
166
311
|
- **Any DSH release other than `0.1.2-rc.1`.** The API surface used is stable
|
|
167
312
|
across the `0.1.2` line by inspection, not by test.
|
|
168
313
|
- **Non-Windows platforms.** The logic is platform-independent and CI runs
|
package/lib/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import { readdirSync, readFileSync, statSync } from 'node:fs'
|
|
|
13
13
|
import { join } from 'node:path'
|
|
14
14
|
import { fileURLToPath } from 'node:url'
|
|
15
15
|
|
|
16
|
-
import { UsageLedger, inheritedCut } from './ledger.js'
|
|
16
|
+
import { UsageLedger, inheritedCut, isForkSession } from './ledger.js'
|
|
17
17
|
import { readSessionLog } from './session-log.js'
|
|
18
18
|
import { ledgerPaths, loadLedger, saveLedger, writeFileAtomic } from './store.js'
|
|
19
19
|
|
|
@@ -195,14 +195,17 @@ export function rebuildFromLogs(sessionsDir, { onWarning = () => {} } = {}) {
|
|
|
195
195
|
const header = records[0]?.type === 'session' ? records[0] : undefined
|
|
196
196
|
const fallbackId = file.replace(/\\/g, '/').split('/').slice(-2)[0]
|
|
197
197
|
const sessionId = String(header?.id ?? fallbackId)
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
198
|
+
const isFork = isForkSession(header)
|
|
199
|
+
if (isFork) forks += 1
|
|
200
|
+
|
|
201
|
+
// This file is the compact row form, so a stored `seq` is not an array
|
|
202
|
+
// index; the boundary is located structurally, by the marker. See
|
|
203
|
+
// inheritedCut for why the declared count cannot be trusted here.
|
|
204
|
+
const cut = inheritedCut({ header, events: records, inheritedEventCount: header?.seedLength })
|
|
205
|
+
if (isFork && cut === 0) {
|
|
206
|
+
onWarning(
|
|
207
|
+
`forked session ${sessionId} has no usable inheritance boundary; folding it whole, so its totals may include the parent prefix`,
|
|
208
|
+
)
|
|
206
209
|
}
|
|
207
210
|
|
|
208
211
|
// The newest event time lets the audit tell "the ledger is merely behind a
|