@kidli1412/dsh-token-heatmap 0.1.2 → 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
@@ -21,7 +21,7 @@ A DeepSeek Harness web plugin: a GitHub-style daily token-usage heatmap of the *
21
21
 
22
22
  ## 安装 / Install
23
23
 
24
- 需要 `web` profile(`@deepseek-ai/dsh >= 0.1.0-rc.6`)与 `pnpm`。
24
+ 需要 `web` profile 与 `pnpm`。DSH 兼容版本见下方「兼容性 / Compatibility」;运行于 `@deepseek-ai/dsh >= 0.1.2-alpha.4`(0.1.2 版本线)。
25
25
 
26
26
  从 npm 安装:
27
27
 
@@ -59,6 +59,13 @@ dsh plugin --profile web remove @kidli1412/dsh-token-heatmap
59
59
  - 无会话/无工作区时(`input.dock` 需要会话上下文)统计卡不渲染。
60
60
  - 服务端与客户端都随 `dsh web` 启动加载,因此新增/更新插件后需要重启。
61
61
 
62
+ ## 兼容性 / Compatibility
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 的精确逐版本兼容证据;仅范围声明不会恢复上架)。插件使用的客户端注入(`dsh-api-remotes` / `dsh-client-connection` / `dsh-client-locale` / `dsh-client-ui-conversation` / `dsh-client-ui-settings`)与 Host 服务(`settings` namespace、`webServer` 精确路由)在这条版本线上保持稳定。
65
+ - **Node**:`^22.19.0 || >=24.0.0`(与 DSH 一致)。
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 会话。客户端注入模块列表同步为新架构模块(见上)。
68
+
62
69
  ## License
63
70
 
64
71
  MIT。聚合与回环端点实现参考了 [dsh-usage-stats](https://github.com/Ychris12138/dsh-usage-stats)(MIT © Ychris12138)。
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
@@ -37,7 +37,6 @@ import { join, dirname } from "node:path";
37
37
  import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
38
38
  import { applyUsageDelta, createUsageState, mergeInto, renderUsage, zeroBuckets } from "./usage.js";
39
39
  import { DEFAULT_CONFIG, parseConfig } from "./config.js";
40
- import { settingsNamespace } from "@deepseek-ai/dsh-settings";
41
40
  import z from "@deepseek-ai/schemastery";
42
41
 
43
42
  /** Stable Cordis plugin name. */
@@ -56,8 +55,11 @@ const inject = ["webServer", "sessions", "sessionPersistence", "settings"];
56
55
  * namespace. The card edits `enabled` + `colorScheme` through the settings
57
56
  * scope; the legacy `<DSH_HOME>/storages/token-heatmap-config.json` document
58
57
  * is migrated once at startup (see migrateLegacyConfig).
58
+ * DSH 0.1.2 起 `@deepseek-ai/dsh-settings` 不再导出 settingsNamespace 帮助函数:
59
+ * namespace 直接以字面量传入(register/get/update 内部仍按
60
+ * /^[a-z][a-z0-9-]*$/ 校验)。
59
61
  */
60
- const SETTINGS_NAMESPACE = settingsNamespace("token-heatmap");
62
+ const SETTINGS_NAMESPACE = "token-heatmap";
61
63
 
62
64
  /**
63
65
  * Durable display preferences; also the wire envelope the browser scope
@@ -387,6 +389,31 @@ async function handleConfig(ctx, req, res) {
387
389
  }
388
390
  //#endregion
389
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
+
390
417
  /**
391
418
  * Collect per-day usage across live and persisted sessions, incrementally.
392
419
  *
@@ -398,6 +425,9 @@ async function handleConfig(ctx, req, res) {
398
425
  * the log was truncated/rewritten, so the session is refolded from scratch.
399
426
  * Sessions that vanished are dropped, and a session switching between
400
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.
401
431
  */
402
432
  export async function collectUsage(ctx) {
403
433
  return withLock(async () => {
@@ -415,9 +445,9 @@ export async function collectUsage(ctx) {
415
445
  state.currentModel = null;
416
446
  state.consumed = 0;
417
447
  }
418
- const count = session.events.length;
448
+ const { count, events } = liveSessionEvents(session, state.consumed ?? 0);
419
449
  if ((state.consumed ?? 0) < count) {
420
- applyUsageDelta(state, session.events.slice(state.consumed ?? 0));
450
+ applyUsageDelta(state, events);
421
451
  state.consumed = count;
422
452
  }
423
453
  state.kind = "live";
@@ -426,7 +456,10 @@ export async function collectUsage(ctx) {
426
456
  }
427
457
  const persistence = ctx.get("sessionPersistence");
428
458
  const persistedIds = new Set();
429
- if (persistence !== void 0) {
459
+ const canEnumeratePersisted = persistence !== void 0 && (
460
+ typeof persistence.list === "function" || typeof persistence.listSnapshots === "function"
461
+ );
462
+ if (canEnumeratePersisted) {
430
463
  // Prefer the backend's opaque per-log revisions (no file I/O in the
431
464
  // plugin, works for any backend that exposes listSnapshots).
432
465
  let snapshots = null;
@@ -480,9 +513,16 @@ export async function collectUsage(ctx) {
480
513
  }
481
514
  cache.sessions[meta.id] = state;
482
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");
483
523
  }
484
524
  for (const id of Object.keys(cache.sessions)) {
485
- if (!attached.has(id) && !persistedIds.has(id)) delete cache.sessions[id];
525
+ if (!attached.has(id) && !persistedIds.has(id) && canEnumeratePersisted) delete cache.sessions[id];
486
526
  }
487
527
  const byDay = new Map();
488
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.2",
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",
@@ -17,8 +17,8 @@
17
17
  "LICENSE"
18
18
  ],
19
19
  "dependencies": {
20
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
21
- "@deepseek-ai/schemastery": "^3.18.1"
20
+ "@deepseek-ai/dsh-settings": "^0.1.2-rc.1",
21
+ "@deepseek-ai/schemastery": "^3.18.2"
22
22
  },
23
23
  "dsh": {
24
24
  "bundle": {
@@ -27,15 +27,24 @@
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
  ]
36
+ },
37
+ "compatibility": {
38
+ "dshReleases": {
39
+ "0.1.2-alpha.4": "compatible",
40
+ "0.1.2-alpha.5": "compatible",
41
+ "0.1.2-rc.1": "compatible"
42
+ }
34
43
  }
35
44
  },
36
45
  "scripts": {
37
46
  "check": "node --check lib/usage.js && node --check lib/config.js && node --check lib/index.js && node --check lib/client.js",
38
- "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",
39
48
  "prepublishOnly": "npm run check && npm test"
40
49
  },
41
50
  "license": "MIT",