@kidli1412/dsh-token-heatmap 0.1.3 → 0.1.4

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/README.md CHANGED
@@ -61,9 +61,10 @@ dsh plugin --profile web remove @kidli1412/dsh-token-heatmap
61
61
 
62
62
  ## 兼容性 / Compatibility
63
63
 
64
- - **DSH**:manifest 通过 `dsh.compatibility.dshReleases` 将官方最新三个版本 `0.1.2-alpha.4`、`0.1.2-alpha.5`、`0.1.2-rc.1` 逐项声明为 `compatible`(DSH STORE 的精确逐版本兼容证据;仅范围声明不会恢复上架)。插件使用的客户端注入(locale / runtime / ui-slots)与 Host 服务(`settings` namespace、`webServer` 精确路由)在这条版本线上保持稳定。
64
+ - **DSH**:manifest 通过 `dsh.compatibility.dshReleases` 将官方最新三个版本 `0.1.2-alpha.4`、`0.1.2-alpha.5`、`0.1.2-rc.1` 逐项声明为 `compatible`(DSH STORE 的精确逐版本兼容证据;仅范围声明不会恢复上架)。插件使用的客户端注入(`dsh-api-remotes` / `dsh-client-connection` / `dsh-client-locale` / `dsh-client-ui-conversation` / `dsh-client-ui-settings`)与 Host 服务(`settings` namespace、`webServer` 精确路由)在这条版本线上保持稳定。
65
65
  - **Node**:`^22.19.0 || >=24.0.0`(与 DSH 一致)。
66
66
  - **依赖**:`@deepseek-ai/dsh-settings` 自 0.1.3 起提升为 `^0.1.2-rc.1`、`@deepseek-ai/schemastery` 提升为 `^3.18.2`,与 DSH 0.1.2 版本线对齐。npm 的 prerelease 解析规则下 `^0.1.0-rc.7` 不会解析到 `0.1.2-rc.1`(只会装 `0.1.0-rc.8`),因此较低的范围会拉到与新版 DSH 不同 train 的 settings 副本。
67
+ - **0.1.4(DSH 0.1.2 适配)**:rc.1 起 live session 不再携带 `.events` 数组(改用 `session.seq` + `session.eventAt(seq)`,与官方 `dsh-token-meter` 相同),新会话判断从 `composerPhase === "blank"` 改为布尔 `session.blank`;`sessionPersistence` 在 rc.1 不再提供会话枚举(list/listSnapshots 已移除),持久化历史的增量刷新降级为保留已有缓存、只累计 live 会话。客户端注入模块列表同步为新架构模块(见上)。
67
68
 
68
69
  ## License
69
70
 
package/lib/client.js CHANGED
@@ -542,10 +542,13 @@ window.__ModuleLoader__.load({
542
542
  // config fetch — the loopback endpoint remains as a back-compat API.
543
543
  const config = react.useSyncExternalStore(configStore.subscribe, configStore.getSnapshot);
544
544
 
545
- // New-session hero only: a blank session whose composer is still in
546
- // the guidance phase. Conversed sessions hide the card.
545
+ // New-session hero only. DSH 0.1.2+ (rc.1) exposes the state as
546
+ // the boolean `session.blank` (the `composerPhase` string is
547
+ // gone); older harnesses keep `composerPhase === "blank"`.
548
+ // Conversed sessions hide the card.
547
549
  if (session === void 0 || session === null || input === void 0 || input === null) return null;
548
- if (session.composerPhase !== "blank") return null;
550
+ const heroBlank = typeof session.blank === "boolean" ? session.blank === true : session.composerPhase === "blank";
551
+ if (!heroBlank) return null;
549
552
  // Master switch from the settings card (设置 → 插件 → 插件配置).
550
553
  if (config.enabled === false) return null;
551
554
 
package/lib/index.js CHANGED
@@ -389,6 +389,31 @@ async function handleConfig(ctx, req, res) {
389
389
  }
390
390
  //#endregion
391
391
 
392
+ /**
393
+ * Enumerate a live session's events from an offset, across session shapes.
394
+ *
395
+ * Pre-0.1.2 sessions kept the whole log on the object as `.events`; DSH
396
+ * 0.1.2+ (rc.1) live sessions expose no `.events` array — the count is
397
+ * `session.seq` and each event is read via `session.eventAt(seq)` (0-based,
398
+ * the same reads @deepseek-ai/dsh-token-meter uses).
399
+ * @returns `{ count, events }` — `count` the session's total event count,
400
+ * `events` the events from `from` onward.
401
+ */
402
+ function liveSessionEvents(session, from) {
403
+ if (Array.isArray(session.events)) {
404
+ return { count: session.events.length, events: session.events.slice(from) };
405
+ }
406
+ if (typeof session.seq !== "number" || typeof session.eventAt !== "function") {
407
+ return { count: 0, events: [] };
408
+ }
409
+ const events = [];
410
+ for (let seq = from; seq < session.seq; seq += 1) {
411
+ const event = session.eventAt(seq);
412
+ if (event !== void 0) events.push(event);
413
+ }
414
+ return { count: session.seq, events };
415
+ }
416
+
392
417
  /**
393
418
  * Collect per-day usage across live and persisted sessions, incrementally.
394
419
  *
@@ -400,6 +425,9 @@ async function handleConfig(ctx, req, res) {
400
425
  * the log was truncated/rewritten, so the session is refolded from scratch.
401
426
  * Sessions that vanished are dropped, and a session switching between
402
427
  * live/persisted is refolded from scratch to stay exact.
428
+ * On DSH 0.1.2+ (rc.1) sessionPersistence no longer exposes a session
429
+ * enumeration (list/listSnapshots are gone), so persisted-only history is
430
+ * kept in the cache untouched instead of being refreshed or dropped.
403
431
  */
404
432
  export async function collectUsage(ctx) {
405
433
  return withLock(async () => {
@@ -417,9 +445,9 @@ export async function collectUsage(ctx) {
417
445
  state.currentModel = null;
418
446
  state.consumed = 0;
419
447
  }
420
- const count = session.events.length;
448
+ const { count, events } = liveSessionEvents(session, state.consumed ?? 0);
421
449
  if ((state.consumed ?? 0) < count) {
422
- applyUsageDelta(state, session.events.slice(state.consumed ?? 0));
450
+ applyUsageDelta(state, events);
423
451
  state.consumed = count;
424
452
  }
425
453
  state.kind = "live";
@@ -428,7 +456,10 @@ export async function collectUsage(ctx) {
428
456
  }
429
457
  const persistence = ctx.get("sessionPersistence");
430
458
  const persistedIds = new Set();
431
- if (persistence !== void 0) {
459
+ const canEnumeratePersisted = persistence !== void 0 && (
460
+ typeof persistence.list === "function" || typeof persistence.listSnapshots === "function"
461
+ );
462
+ if (canEnumeratePersisted) {
432
463
  // Prefer the backend's opaque per-log revisions (no file I/O in the
433
464
  // plugin, works for any backend that exposes listSnapshots).
434
465
  let snapshots = null;
@@ -482,9 +513,16 @@ export async function collectUsage(ctx) {
482
513
  }
483
514
  cache.sessions[meta.id] = state;
484
515
  }
516
+ } else if (persistence !== void 0) {
517
+ // DSH 0.1.2+ (rc.1): sessionPersistence exposes no session
518
+ // enumeration (list/listSnapshots are gone). Previously folded
519
+ // persisted days stay in the cache untouched — they are neither
520
+ // refreshed (no enumeration to walk) nor dropped (they still
521
+ // describe real past days).
522
+ ctx.logger.warn("token-heatmap: sessionPersistence exposes no list()/listSnapshots() enumeration on this DSH; persisted-only history will not refresh");
485
523
  }
486
524
  for (const id of Object.keys(cache.sessions)) {
487
- if (!attached.has(id) && !persistedIds.has(id)) delete cache.sessions[id];
525
+ if (!attached.has(id) && !persistedIds.has(id) && canEnumeratePersisted) delete cache.sessions[id];
488
526
  }
489
527
  const byDay = new Map();
490
528
  for (const state of Object.values(cache.sessions)) mergeInto(byDay, state.days);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kidli1412/dsh-token-heatmap",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "DSH web plugin: GitHub-style daily token-usage heatmap on the new-session screen with a selectable calendar-year view, green/blue color schemes and a display switch (设置 → 插件 → 插件配置), plus today / this-month / all-time totals.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -27,9 +27,11 @@
27
27
  "client": {
28
28
  "platform": "web",
29
29
  "inject": [
30
+ "@deepseek-ai/dsh-api-remotes",
31
+ "@deepseek-ai/dsh-client-connection",
30
32
  "@deepseek-ai/dsh-client-locale",
31
- "@deepseek-ai/dsh-client-runtime",
32
- "@deepseek-ai/dsh-client-ui-slots"
33
+ "@deepseek-ai/dsh-client-ui-conversation",
34
+ "@deepseek-ai/dsh-client-ui-settings"
33
35
  ]
34
36
  },
35
37
  "compatibility": {
@@ -42,7 +44,7 @@
42
44
  },
43
45
  "scripts": {
44
46
  "check": "node --check lib/usage.js && node --check lib/config.js && node --check lib/index.js && node --check lib/client.js",
45
- "test": "node scripts/smoke.mjs && node scripts/settings-smoke.mjs",
47
+ "test": "node scripts/smoke.mjs && node scripts/settings-smoke.mjs && node scripts/rc1-session-smoke.mjs",
46
48
  "prepublishOnly": "npm run check && npm test"
47
49
  },
48
50
  "license": "MIT",