dsh-all-usage 1.1.15 → 1.1.17
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 +17 -0
- package/README.md +15 -17
- package/lib/aggregation.js +14 -1
- package/lib/client.js +1 -1
- package/lib/http.js +26 -11
- package/lib/plugin.js +21 -2
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `dsh-all-usage` are documented here.
|
|
4
4
|
|
|
5
|
+
## [1.1.17] - 2026-10-01
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **Writes work in the DSH desktop client again.** The write routes (price save, immediate sync, holiday fetch, alias) required an `Origin` header equal to the loopback host. The desktop shell serves the UI on its own scheme and forwards page requests through Electron, which strips `Origin` along with `Host`, `Cookie` and `Sec-Fetch-*`, so every desktop write was rejected with 403: saving prices reported "no permission", the models.dev catalog could never be synced, and the auto-sync checkbox rolled back. Writes are now authorized by the process capability alone — an unguessable secret that a cross-origin page can neither read (the read routes send no CORS headers) nor attach to a form POST — while a present `Origin` must still be loopback or the shell's own scheme, and `Host` must still be loopback so a rebound DNS name cannot reach the API.
|
|
10
|
+
- **A missing `usageBacked` no longer reads as a verdict.** A host that predates the merged price table sends no `usageBacked`, and the panel rendered that as "no ledger usage" even for rows with recorded calls; it now degrades to "usage unknown" (distinct flag, row style and footer count), and says so when the payload carries no model list at all.
|
|
11
|
+
- **A rotated write capability heals itself.** The status poll now carries a derived `capabilityId` (never the capability itself), the page treats a rotation as a full refresh, and a write rejected with 403 re-reads the capability from the full snapshot and retries once before reporting anything; the message now names a stale capability instead of claiming a permission problem.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `GET /api/all-usage/status` reports the running plugin version and the derived capability id, so a page served by a newer package than the running host can say "host plugin vX / this page vY differ — restart DSH" instead of leaving that mismatch invisible.
|
|
16
|
+
|
|
17
|
+
## [1.1.16] - 2026-09-29
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **DSH 0.2.0-rc.2 is a supported runtime.** The real Cordis smoke passes on it — every route, the session hooks, the ledger flush, the revision fast path and disposal — so `dsh.compatibility.runtime` gains `>=0.2.0-rc.2 <0.2.1-0`, `0.2.0-rc.2` joins the verified list (now six runtimes) and the CI smoke matrix (Node 22/24, twelve jobs). The line keeps the 0.1.7 Cordis pairing (cordis 4.0.4 / loader 1.0.5 / timer 1.1.6) and the abstract settings seam instead of the file-backed provider, which is exactly what the smoke runs against. README (both languages, including the badge and the verified matrix) follows the new declaration.
|
|
5
22
|
## [1.1.15] - 2026-09-28
|
|
6
23
|
|
|
7
24
|
### Added
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](https://github.com/ParticleLight/dsh-all-usage/stargazers)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
[](https://github.com/ParticleLight/dsh-all-usage/actions/workflows/ci.yml)
|
|
9
|
-
[](#兼容性与已知限制)
|
|
10
10
|
[](https://github.com/ParticleLight/dsh-all-usage/releases)
|
|
11
11
|
|
|
12
12
|
[中文](#中文) · [English](#english)
|
|
@@ -38,12 +38,13 @@ DeepSeek Harness 全量用量看板:按模型、供应商、工作区和时间
|
|
|
38
38
|
### 兼容性与已知限制
|
|
39
39
|
|
|
40
40
|
- **运行环境**:需要 Node.js `>=22 <25`;CI 会在 Node 22 和 Node 24 上运行测试、语法检查和 npm 包内容检查。
|
|
41
|
-
- **DSH 兼容**:`package.json` 声明 DSH runtime `>=0.1.1-rc.1 <0.1.5-0 || >=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.2 <0.1.8-0`,已使用 `0.1.7-rc.2`、`0.1.5-rc.2`、`0.1.5-rc.1`、`0.1.1-rc.2` 和 `0.1.1-rc.1` 的真实 Cordis 服务链验证,且全部纳入 CI smoke 矩阵(Node 22/24 双档);`0.1.2-rc.1` 已通过实际使用验证兼容,但未纳入 CI smoke
|
|
41
|
+
- **DSH 兼容**:`package.json` 声明 DSH runtime `>=0.1.1-rc.1 <0.1.5-0 || >=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.2 <0.1.8-0 || >=0.2.0-rc.2 <0.2.1-0`,已使用 `0.2.0-rc.2`、`0.1.7-rc.2`、`0.1.5-rc.2`、`0.1.5-rc.1`、`0.1.1-rc.2` 和 `0.1.1-rc.1` 的真实 Cordis 服务链验证,且全部纳入 CI smoke 矩阵(Node 22/24 双档);`0.1.2-rc.1` 已通过实际使用验证兼容,但未纳入 CI smoke 矩阵。声明按元组拆成四段而非写成单一区间,是因为 node-semver 只有在范围里存在与目标版本同 `major.minor.patch` 且自身带预发布标签的比较符时,才会放行该预发布版本 —— 例如必须写成 `>=0.2.0-rc.2` 才能让 `0.2.0-rc.2` 进入范围。
|
|
42
42
|
- **桌面客户端(Electron,Windows)**:本插件在 DSH 桌面客户端上实测运行正常(实测 `@deepseek-ai/dsh-desktop` `0.1.7-rc.2`)。桌面客户端会在窗口顶部为自身的最小化 / 最大化 / 关闭按钮保留一条 40px 顶栏,并在文档上标记 `data-windows-titlebar` 与 `--dsh-windows-titlebar-height`;面板据此从该条**下方**开始绘制,所以桌面端自己的关闭按钮永远不会被面板盖住。浏览器端没有该标记,不留白。顶栏高度优先取宿主声明,其次取 Window Controls Overlay 矩形,最后退回 UA;需要微调时可在 DevTools 执行 `localStorage.setItem('dsh-all-usage:topInset', '48')` 后刷新(`0` 表示不留白),无需重新构建。桌面客户端自带运行时,其运行时 `0.1.7-rc.2` 已纳入声明区间与 CI smoke 矩阵(见下表)。
|
|
43
|
-
- **Web 服务依赖**:Host 将 `webServer` 声明为必需依赖,确保服务晚挂载时由 DSH 等待后再执行插件;该包面向 DSH Web profile,不提供无 WebServer 的 headless 路由。HTTP 守卫还会检查真实 socket peer,反向代理只有在连接本身来自 loopback
|
|
43
|
+
- **Web 服务依赖**:Host 将 `webServer` 声明为必需依赖,确保服务晚挂载时由 DSH 等待后再执行插件;该包面向 DSH Web profile,不提供无 WebServer 的 headless 路由。HTTP 守卫还会检查真实 socket peer,反向代理只有在连接本身来自 loopback 时才会被接受。写接口(价格保存、立即同步、节假日抓取、别名)以**进程令牌**为授权凭据,不强制要求 `Origin`:DSH 桌面端把 UI 跑在自定义协议上、请求由 Electron 主进程转发,转发时会删掉 `Origin`(连同 `Host`/`Cookie`/`Sec-Fetch-*`),强制 Origin 会让桌面端所有写入 403;而 `Origin` 若存在仍必须是 loopback 或桌面端自身 scheme,`Host` 仍必须是 loopback(防 DNS rebinding)。
|
|
44
44
|
|
|
45
45
|
| DSH runtime | Node.js 支持 | 真实 Cordis smoke | 结论 |
|
|
46
46
|
| --- | --- | --- | --- |
|
|
47
|
+
| `0.2.0-rc.2` | `>=22 <25`,CI 覆盖 22/24 | 通过(真实 Cordis 服务链,CI 覆盖 Node 22/24) | 已声明、已验证(DSH 官方 `next` 线;本机 web profile 即运行于此版本) |
|
|
47
48
|
| `0.1.7-rc.2` | `>=22 <25`,CI 覆盖 22/24 | 通过(真实 Cordis 服务链,CI 覆盖 Node 22/24) | 已声明、已验证(DSH 桌面客户端即运行于此版本) |
|
|
48
49
|
| `0.1.5-rc.2` | `>=22 <25`,CI 覆盖 22/24 | 通过(真实 Cordis 服务链,CI 覆盖 Node 22/24) | 已声明、已验证 |
|
|
49
50
|
| `0.1.5-rc.1` | `>=22 <25`,CI 覆盖 22/24 | 通过(真实 Cordis 服务链,CI 覆盖 Node 22/24) | 已声明、已验证 |
|
|
@@ -115,12 +116,10 @@ node scripts/replay-fixture.mjs fixtures/usage-events.json
|
|
|
115
116
|
|
|
116
117
|
### 最近更新
|
|
117
118
|
|
|
118
|
-
**v1.1.
|
|
119
|
+
**v1.1.17**
|
|
119
120
|
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
- **面板体验与性能**:合并表按行 memo(500 行时改一个价格只重渲染该行)、行查找改为索引后极限配置由 282 ms/次 降到 3.6 ms;表头不再被输入框盖住、表格不再横向滚动;修复「点官方模型输入框会清空当前模型」(改为保留并全选)以及快照丢弃 `providerId`/`temporalPricing` 导致「映射 + 手工价」保存后失效的问题。
|
|
123
|
-
- **历史记录自动修正**:内置峰谷表政策哈希变化后,官方直连/已映射的 DeepSeek 记录会按各自用量发生时刻一次性重新对账(实测 9/25–9/27 中秋假日的 237 条峰值记录全部改为「节假日谷时」,费率 0.44 → 0.15)。
|
|
121
|
+
- **修复:DSH 桌面端无法保存成本设置。** 写接口(保存价格、立即同步、节假日抓取、别名)原先要求请求带 `Origin` 且与 loopback Host 同源,而桌面端把 UI 跑在自定义协议 `dsh-app://app` 上、请求由 Electron 主进程转发时会删掉 `Origin`(连同 `Host`/`Cookie`/`Sec-Fetch-*`),于是桌面端的所有写入必然 403——保存价格报「没有权限」、models.dev 目录永远同步不下来、自动同步勾选后回滚。现在写入以**进程令牌**为授权凭据(跨域页面既读不到也塞不进表单 POST),`Origin` 存在时仍必须是 loopback 或桌面端自身 scheme,`Host` 仍必须是 loopback(防 DNS rebinding)。
|
|
122
|
+
- **健壮性修复**:宿主未下发 `usageBacked` 时不再断言「账本无用量」,改为「用量未知」三态(独立标记、行样式与页脚计数);`/api/all-usage/status` 新增派生的 `capabilityId`(令牌轮换时变化,令牌本身只在完整快照里下发)与 `pluginVersion`,页面据此察觉宿主被重新加载、写入遇 403 时自动重取令牌并重试一次,宿主与页面版本不一致时直接提示「请重启 DSH」。
|
|
124
123
|
|
|
125
124
|
完整版本记录见 [CHANGELOG.md](CHANGELOG.md)。
|
|
126
125
|
|
|
@@ -166,7 +165,7 @@ dsh plugin --profile web add github:ParticleLight/dsh-all-usage
|
|
|
166
165
|
|
|
167
166
|
- **Host 端**(入口 `lib/index.js`,组装 `lib/plugin.js`):按职责拆分为 `aggregation.js`(聚合与查询)、`ledger.js`(持久账本)、`session-sync.js`(历史/实时同步)、`pricing-runtime.js`(运行时定价)、`balance.js`(余额)、`http.js`(安全路由);扫描 `turn/end`、`assistant/chunk` usage 和最终 `assistant/message.usage`,监听 `session/event` 实时折叠,并通过 `webServer` 服务注册数据路由:
|
|
168
167
|
- `GET /api/all-usage` — 兼容统计快照
|
|
169
|
-
- `GET /api/all-usage/status` — 轻量 revision 与同步健康状态,并附带最近一次客户端环境报告(`clientEnv
|
|
168
|
+
- `GET /api/all-usage/status` — 轻量 revision 与同步健康状态,并附带最近一次客户端环境报告(`clientEnv`)、运行中插件的版本号(`pluginVersion`)与写入令牌的派生 ID(`capabilityId`,令牌轮换时变化,用于让页面察觉宿主被重新加载;令牌本身只在完整快照里下发)
|
|
170
169
|
- `GET /api/all-usage/query` — 按 scope 返回聚合、daily/hourly 趋势和 heatmap 数据;单日 scope 填充 `hourly`,跨日 scope 的 `hourly` 为空
|
|
171
170
|
- `GET /api/all-usage/records` — 按 scope 分页返回脱敏 canonical usage rows
|
|
172
171
|
- `GET /api/all-usage/balance?force=1` — 账户余额(复用 `llm-deepseek` 的 API Key 配置)
|
|
@@ -233,12 +232,13 @@ A full usage dashboard for DeepSeek Harness. Analyze tokens, cache behavior, est
|
|
|
233
232
|
### Compatibility and Known Limitations
|
|
234
233
|
|
|
235
234
|
- **Runtime**: Node.js `>=22 <25` is required. CI runs the test suite, syntax checks, and package-content checks on Node 22 and Node 24.
|
|
236
|
-
- **DSH compatibility**: `package.json` declares DSH runtime `>=0.1.1-rc.1 <0.1.5-0 || >=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.2 <0.1.8-0`; the real Cordis service chain is verified on `0.1.7-rc.2`, `0.1.5-rc.2`, `0.1.5-rc.1`, `0.1.1-rc.2` and `0.1.1-rc.1`, all of them covered by the CI smoke matrix on Node 22 and 24. `0.1.2-rc.1` has also been verified compatible through real-world use, but is not covered by the CI smoke matrix. The declaration is split per tuple rather than written as one interval because node-semver only admits a prerelease version when some comparator shares its exact `major.minor.patch` tuple and itself carries a prerelease tag — `0.
|
|
235
|
+
- **DSH compatibility**: `package.json` declares DSH runtime `>=0.1.1-rc.1 <0.1.5-0 || >=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.2 <0.1.8-0 || >=0.2.0-rc.2 <0.2.1-0`; the real Cordis service chain is verified on `0.2.0-rc.2`, `0.1.7-rc.2`, `0.1.5-rc.2`, `0.1.5-rc.1`, `0.1.1-rc.2` and `0.1.1-rc.1`, all of them covered by the CI smoke matrix on Node 22 and 24. `0.1.2-rc.1` has also been verified compatible through real-world use, but is not covered by the CI smoke matrix. The declaration is split per tuple rather than written as one interval because node-semver only admits a prerelease version when some comparator shares its exact `major.minor.patch` tuple and itself carries a prerelease tag — `0.2.0-rc.2` is admitted only because the range says `>=0.2.0-rc.2`.
|
|
237
236
|
- **Desktop client (Electron, Windows)**: the plugin is verified running in the DSH desktop client (`@deepseek-ai/dsh-desktop` `0.1.7-rc.2` as measured). The desktop client keeps a 40px caption strip across the top of its window for its own minimise / maximise / close buttons and marks the document with `data-windows-titlebar` and `--dsh-windows-titlebar-height`; the panel draws from **below** that strip, so the client's own close button is never covered. Browsers carry no such marker and reserve nothing. The strip height comes from the host's declaration first, then the Window Controls Overlay rectangle, then the user agent; to tune it, run `localStorage.setItem('dsh-all-usage:topInset', '48')` in DevTools and reload (`0` reserves nothing) — no rebuild required. The desktop client ships its own runtime, and that runtime — `0.1.7-rc.2` — is now part of the declared range and of the CI smoke matrix below.
|
|
238
237
|
- **Web service dependency**: the Host declares `webServer` as a required dependency, so DSH waits for a late-mounted service before applying the plugin; this package targets the DSH Web profile and does not expose routes without WebServer. The HTTP guard also checks the actual socket peer, so a reverse proxy is accepted only when the connection itself is loopback.
|
|
239
238
|
|
|
240
239
|
| DSH runtime | Node.js support | Real Cordis smoke | Conclusion |
|
|
241
240
|
| --- | --- | --- | --- |
|
|
241
|
+
| `0.2.0-rc.2` | `>=22 <25`, CI covers 22/24 | Passed (real Cordis service chain, CI covers Node 22/24) | Declared and verified (the current DSH `next` line; this machine's web profile runs it) |
|
|
242
242
|
| `0.1.7-rc.2` | `>=22 <25`, CI covers 22/24 | Passed (real Cordis service chain, CI covers Node 22/24) | Declared and verified (the DSH desktop client runs this version) |
|
|
243
243
|
| `0.1.5-rc.2` | `>=22 <25`, CI covers 22/24 | Passed (real Cordis service chain, CI covers Node 22/24) | Declared and verified |
|
|
244
244
|
| `0.1.5-rc.1` | `>=22 <25`, CI covers 22/24 | Passed (real Cordis service chain, CI covers Node 22/24) | Declared and verified |
|
|
@@ -310,12 +310,10 @@ The command loads the real plugin Host, calls its compatible APIs, checks the do
|
|
|
310
310
|
|
|
311
311
|
### Latest Update
|
|
312
312
|
|
|
313
|
-
**v1.1.
|
|
313
|
+
**v1.1.17**
|
|
314
314
|
|
|
315
|
-
- **
|
|
316
|
-
- **
|
|
317
|
-
- **Panel and performance work**: rows are memoized (editing one price re-renders that row only), indexed lookups cut a worst-case configuration from 282 ms to 3.6 ms per keystroke, the sticky header no longer hides behind row inputs and the table no longer scrolls sideways. Fixes: focusing the official-model field no longer clears the current model, and the pricing snapshot no longer drops `providerId`/`temporalPricing` (which made a mapped manual price silently fall back to the catalog).
|
|
318
|
-
- **Existing history is corrected automatically**: when the built-in plan's policy hash changes, first-party and mapped DeepSeek records are reconciled once against their own usage instant (in practice the 237 peak records of the 9/25-9/27 Mid-Autumn holidays moved to "Holiday off-peak", 0.44 to 0.15).
|
|
315
|
+
- **Fixed: the DSH desktop client could not save cost settings.** The write routes (price save, immediate sync, holiday fetch, alias) required an `Origin` header matching the loopback host, but the desktop shell serves the UI on its own `dsh-app://app` scheme and forwards page requests through Electron, which strips `Origin` (along with `Host`, `Cookie` and `Sec-Fetch-*`) — so every desktop write was rejected with 403: saving prices reported "no permission", the models.dev catalog could never be synced, and the auto-sync checkbox rolled back. Writes are now authorized by the process capability (which a cross-origin page can neither read nor attach to a form POST), a present `Origin` must still be loopback or the shell's own scheme, and `Host` must still be loopback against DNS rebinding.
|
|
316
|
+
- **Robustness fixes**: a host that omits `usageBacked` no longer reads as "no ledger usage" (the panel now has a distinct "usage unknown" state, flag, row style and footer count); `GET /api/all-usage/status` reports a derived `capabilityId` (changes when the capability rotates, while the capability itself is only ever sent in the full snapshot) and the running `pluginVersion`, so the page notices a plugin reload, re-reads the capability and retries once when a write is rejected with 403, and says "restart DSH" when the host and the page disagree on the version.
|
|
319
317
|
|
|
320
318
|
See [CHANGELOG.md](CHANGELOG.md) for the complete version history.
|
|
321
319
|
|
|
@@ -353,7 +351,7 @@ The profile patch layer hot-reloads; save the file and refresh the page.
|
|
|
353
351
|
|
|
354
352
|
- **Host** (entry `lib/index.js`, assembled by `lib/plugin.js`): split by responsibility across `aggregation.js` (aggregation/query), `ledger.js` (durable ledger), `session-sync.js` (history/live sync), `pricing-runtime.js` (runtime pricing), `balance.js` (balance), and `http.js` (protected routes); aggregates `turn/end`, `assistant/chunk` usage, and final `assistant/message.usage`, folds live `session/event` updates, and exposes data routes through `webServer`:
|
|
355
353
|
- `GET /api/all-usage` — compatible usage snapshot
|
|
356
|
-
- `GET /api/all-usage/status` — lightweight revision and sync health, plus the last client environment report (`clientEnv`)
|
|
354
|
+
- `GET /api/all-usage/status` — lightweight revision and sync health, plus the last client environment report (`clientEnv`), the running plugin version (`pluginVersion`) and a derived id of the write capability (`capabilityId`, changes when the capability rotates so the page can notice a plugin reload; the capability itself is only ever sent in the full snapshot)
|
|
357
355
|
- `GET /api/all-usage/query` — scoped aggregate, daily/hourly trend, and heatmap data; single-day scopes populate `hourly`, while cross-day scopes return an empty `hourly` array
|
|
358
356
|
- `GET /api/all-usage/records` — paginated privacy-safe canonical usage rows
|
|
359
357
|
- `GET /api/all-usage/balance?force=1` — account balance using the configured `llm-deepseek` API key
|
|
@@ -364,7 +362,7 @@ The profile patch layer hot-reloads; save the file and refresh the page.
|
|
|
364
362
|
- `POST /api/all-usage/pricing/sync` — sync models.dev and backfill unpriced calls
|
|
365
363
|
- `POST /api/all-usage/pricing/holidays` — fetch one year of the Chinese holiday arrangement (the holiday-cn dataset, jsDelivr first with a GitHub raw fallback, cached host-side for 24 hours) and return only the off-day list plus its source; **nothing is written to the configuration** — the client freezes the dates into a peak plan
|
|
366
364
|
- `GET /api/all-usage/client-env` — client environment report (user agent, window-controls overlay height, the reserved strip **and its source**, viewport, panel rectangles); the client reports it at load and whenever the sheet opens, kept in memory only
|
|
367
|
-
- **Client**: readable source lives in `src/client.js`; `npm run build:client` uses the pinned Terser version to generate the `window.__ModuleLoader__` bundle at `lib/client.js`, which registers the “Usage statistics” sidebar entry through the `sidebar.footer.action` slot. All API routes accept loopback requests and reject an explicit cross-origin Origin; balance reads and alias writes also require a process-scoped token generated when the plugin starts (the balance GET tolerates browsers omitting Origin).
|
|
365
|
+
- **Client**: readable source lives in `src/client.js`; `npm run build:client` uses the pinned Terser version to generate the `window.__ModuleLoader__` bundle at `lib/client.js`, which registers the “Usage statistics” sidebar entry through the `sidebar.footer.action` slot. All API routes accept loopback requests and reject an explicit cross-origin Origin; balance reads and alias writes also require a process-scoped token generated when the plugin starts (the balance GET tolerates browsers omitting Origin). **Writes are authorized by that token, not by an Origin**: the DSH desktop shell runs the UI on its own scheme and forwards requests through Electron, which strips `Origin` (with `Host`/`Cookie`/`Sec-Fetch-*`), so requiring one made every desktop save fail with 403. A present `Origin` must still be loopback or the shell's own scheme, and `Host` must still be loopback (DNS-rebinding guard).
|
|
368
366
|
|
|
369
367
|
### Data semantics
|
|
370
368
|
|
package/lib/aggregation.js
CHANGED
|
@@ -601,7 +601,20 @@ export function createAggregation(host) {
|
|
|
601
601
|
}
|
|
602
602
|
function statusSnapshot() {
|
|
603
603
|
commitPendingStats()
|
|
604
|
-
return {
|
|
604
|
+
return {
|
|
605
|
+
instanceId: state.instanceId,
|
|
606
|
+
// The write capability is minted per plugin apply, so a reload rotates it
|
|
607
|
+
// without moving the process instance id or any revision. The status poll
|
|
608
|
+
// carries only this derived id (never the capability itself), which is enough
|
|
609
|
+
// for the page to notice the rotation and re-fetch the full snapshot.
|
|
610
|
+
capabilityId: state.capabilityId,
|
|
611
|
+
pluginVersion: state.pluginVersion,
|
|
612
|
+
...revisionSnapshot(),
|
|
613
|
+
updatedAt: state.statsUpdatedAt,
|
|
614
|
+
scan: scanSnapshot(),
|
|
615
|
+
sync: syncSnapshot(),
|
|
616
|
+
clientEnv: state.clientEnv,
|
|
617
|
+
}
|
|
605
618
|
}
|
|
606
619
|
function serializeIdentity(identity) {
|
|
607
620
|
const value = coerceIdentity(identity)
|