npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dsh-session-check

v0.1.3

Published

Read-only diagnosis of stored DeepSeek Harness sessions (migration gates, including two that call the official v0 validator), offline projection-cache maintenance, and a read-only cache-footprint diagnostic plugin.

Readme

dsh-session-check

English | 中文

只读诊断工具:告诉你哪些已存储的 DeepSeek Harness 会话会被格式迁移拒绝,以及被哪一道闸门拒绝。另带第二个命令 dsh-projcache,用于回收会话投影缓存占用的空间。

作者 @Robin1987China

两个命令,两个承诺:

| 命令 | 承诺 | |---|---| | dsh-session-check scan | 只读:哪些会话会被格式迁移拒绝,各自卡在哪道闸门 | | dsh-projcache survey | 只读:投影缓存里存了什么,有多少可回收 | | dsh-projcache apply | 写入——所有护栏通过后才写,且逐文件留备份 |

本工具诊断的症状

中文: 升级后老会话打不开 · 侧栏列得出来但点开就失败 · 升级后历史像没了 · SessionFormatUnsupportedMigrationError · source v0 artifact remains unchanged · 会话格式迁移失败

English: a session will not open after upgrading · the sidebar lists it but clicking fails · SessionFormatUnsupportedMigrationError · uses unsupported descriptor version

它做什么

读取指定目录下每一个 session.jsonl.zstd,解析后报告它踩中了哪些迁移闸门。

它不写任何文件。 没有修复子命令——改写会话日志是破坏性操作,而一个不可能损坏你历史的诊断工具,才是你能放心先跑的那个。

它不做什么

  • 修复、迁移、移动任何文件
  • 保证会话可读——它只报告自己知道的闸门。扫描干净是必要条件,不是充分条件
  • 不是官方工具

用法

npx dsh-session-check scan ~/.dsh/sessions

scan 会找出所有已存储的日志名,不只是 v0 时代的 session.jsonl.zstd。它以前只匹配那一个名字,于是静默漏掉 session.v3.jsonl.zstd——在核对用的语料里 41 份中有 9 份——而版本统计看起来仍然是完整的。扫描看不见的日志,就是它无法警告的日志。

输出示例:

sessions scanned   : 32
loader would refuse: 20
packed chunk runs  : 8562 (not session events)
format versions    : {"0":32}

gates hit, by session count:
   20  stale-descriptor

投影缓存(dsh-projcache

上面的 scan 看的是会话日志;dsh-projcache 看的是另一半持久化数据:<DSH_HOME>/storages/session_projcache/sessions/,每个会话一个格式化过的 JSON 文档。

它对应的症状: harness 进程用久了内存越来越大 · 会话删了但 storages/ 还在涨 · 某条会话首条消息巨大,于是每次启动都要为它付费

dsh-projcache survey    # 只报告,不写任何文件
dsh-projcache apply     # 回收确定无主的记录 + 裁剪超长行

它只做两件事:

  1. 回收日志已消失的记录。 记录只能通过「由存活或已存储的会话 header 构造出的身份」被读到;日志一旦没了,任何调用方都再也构造不出那个身份——这条记录只是占着字节,并且每次启动都被解析一遍。只有当日志在你传入的 sessions 根目录里确定不存在时才删除,且先写备份。
  2. 裁剪超长的 titleInput 前缀。 这一行把会话首条合格用户消息整条存了下来,而它唯一的读者只要 5 个词 / 40 字节(你装配里的 fallbackMaxWords / fallbackMaxBytes)。工具把文本替换成一个 UTF-8 安全的前缀(默认 4096 字节)——永远保留前缀而不是后缀,且不切断码点。

它不做什么

  • 不加 apply 就绝不写入。 survey 逐字节只读,测试套件对此有断言。
  • 无法逐字节复现的文档,它拒绝写。 存储格式是 JSON.stringify({version, record}, null, 2) + '\n';只要某个记录不能这样往返一致,就说明工具对后端格式的模型已经过期,直接停手。这也是为什么非 JSON 后端会被拒绝而不是被猜着改。
  • sessions 根不存在、或里面一条日志都没有时,它拒绝写入。 --force覆盖不了这一条,所以写错路径不可能删掉你的整个存储。
  • 裁剪会改变 fallback 标题时,它跳过该记录。 工具会用原文本和裁剪后文本各算一次 fallback 标题,不等就跳过。
  • 它不是永久修复。 任何一次从日志的完整重放——记录被删、domain 版本号抬升、titleInputstateVersion 变化——都会把那一行按原尺寸重建。再跑一次 apply 即可,或用开机体检留意。真正的修复必须在采集处裁剪,那属于上游。

在 harness 停止时运行:进程运行期间,这个存储是内存里的一张活表。

参数

| 参数 | 默认 | 含义 | |---|---|---| | --store DIR | $DSH_HOME/storages | 存储后端根目录 | | --sessions DIR | $DSH_HOME/sessions | 用来判定「无主」的 sessions 根目录 | | --clamp-bytes N | 4096 | 存储的 titleInput 前缀预算 | | --fallback-words N | 5 | 你装配里的 fallbackMaxWords,用于不变量校验 | | --fallback-bytes N | 40 | 你装配里的 fallbackMaxBytes,用于不变量校验 | | --max-orphan-fraction F | 0.5 | 无主记录占比超过此值则拒绝回收 | | --force | 关 | 覆盖占比护栏 | | --json | 关 | 机器可读输出 |

每个被改动的文件旁边都会留下 <id>.json.bak.<stamp>。loader 只读 *.json,所以备份不会被加载,直到你自己删掉。

以上全部证据——裁剪能扛过 harness 自己的回写、回收、记录缺失的最坏情况、以及护栏矩阵——都在 docs/verification.md,含原始 JSON 报告。

开机体检(dsh-session-check/diag

一个只读的 Cordis 插件:当投影缓存里可回收的字节超过阈值(默认 1 MiB)时,在启动时打印一行。

projection cache: 8.0 MB reclaimable — 2 of 2 records store a titleInput text (largest 8388608 chars,
8.0 MB total), while the fallback title reads only the first 4096 bytes. Reclaim offline with:
npx -y -p dsh-session-check dsh-projcache survey  (then `apply`)

挂载方式(bundle 行):

- insert:
    - id: projcache-diag
      name: 'dsh-session-check/diag'

dsh-community-fixes 这个 bundle 已经挂了它。它之所以放在这个 CLI 包里,是因为「报告」和「回收」应该共用一份实现。

它只用 KvTable.entries() 读内存里的表,从不写入;测量有截止时间(默认 50 ms),超时会写 (partial scan: …),而不是把半次扫描当成总数报出来。

为什么它写 stderr,而不是只写 ctx.logger

因为在出厂装配里 ctx.logger 根本看不见。Cordis 内置的 LoggerService 只装了一个 exporter——一个内存环形缓冲(@deepseek-ai/cordisLoggerService 构造函数)——而没有任何已发布包注册 console sink。在 0.1.5-rc.1 上实测:这一行插件里的 logger.warndsh web 运行时 stdout / stderr 都没有任何输出,而同一次回调里的 process.stderr.write 有。这里仍然保留 logger 调用,是为了让真的接了 sink 的部署也能收到。

这一点与本事无关地值得知道:只通过 ctx.logger 报告问题的插件,在标准安装里等于没报告给任何人。

三道闸门

每道闸门对应迁移链里的一个校验器,都从已安装的包里读出来。输出里的 file:line 是契约的一部分:升级后请先重读它,再信任闸门。

| 闸门 | 检测什么 | 对应的官方校验器 | |---|---|---| | retired-source-kind | 消息来源的 kind 已不在接受集合里 | dsh-session-format-v2-to-v3/lib/index.js:123 | | stale-descriptor | subagent/descriptor 的 version 不是 3 | dsh-session-format-v0-to-v1/lib/index.js:1586 | | incomplete-inserted | 插入的收件箱消息缺 id/role/content/source | dsh-session-format-v0-to-v1/lib/index.js:283 与 :715 | | v0-unknown-event-type | 事件类型不在冻结的 v0 清单里——v0→v1 迁移即使该事件带 ignorable: true 也照样拒绝 | 直接调用 dsh-session-format-v0-to-v1assertReleasedEventPayload | | v0-unknown-payload-member | data 里有该类型的 v0 disposition 未声明的成员(例如 permission/preset + origin) | 直接调用 dsh-session-format-v0-to-v1assertReleasedEventPayload |

其中两道闸门不是「镜像」

前三道闸门各自重新实现一条判定,并标注它读自哪个 file:line,因为扫描必须在没装 harness 的机器上也能跑。两道 v0-* 闸门不重新实现任何东西:它们直接调用官方的 assertReleasedEventPayload(event, 0)——也就是 v0→v1 迁移自己对每个事件跑的那个函数——并按类别报告它的拒绝。

这一点很重要:另一种做法是把冻结的 v0 清单(0.1.5-rc.1 里 51 个事件类型)抄进这个包,而抄本会过期,过期的抄本会产生假阳性——这恰恰是这个工具最不能犯的错。

你需要知道的后果:

  • 跑不了时它会明说。 没装 @deepseek-ai/dsh-session-format-v0-to-v1,或用了 --no-validator,输出会是 official validator : UNAVAILABLE -- <原因>,而不是一份看起来干净的结论。
  • 只对 format v0 日志运行。 更高版本不经过 v0→v1 迁移;拿 v0 校验器去跑它,报出来的是那个版本新增的事件类型和 payload 成员,全都不是拒绝原因。
  • subagent/descriptor 的拒绝归到 stale-descriptor 并从这两道闸门里剔除。 校验器也会拒它;两边都算会让每个计数翻倍。
  • scan 会报告它用的是哪份清单,并且当清单没有被冻结、或派生出的类型列表与 disposition 不再一致时给出警告。

绝不要手工修补冻结的 v0 清单

如果一条日志因为含你的构建不认识的事件类型而被拒,最诱人的做法是改掉已安装包里的清单好让日志打开。不要这么做。 手工修补过的清单会让日志变得可读,同时把失败换成一个更糟的:

  • dsh-session/lib/index.js:270 只在事件带 ignorable: true 时才保留未知类型;否则它派生出的更高版本会拒绝该日志。所以在 v0 边界上放行一个未知类型,只是把拒绝挪到了写入路径——在那里它是静默的,而不是响亮的。
  • #6614 的一手报告测到的正是这个:在沙箱里修补冻结的 v0 清单之后,append 返回成功,但磁盘上什么都没落、也没有派生 v3。清晰的拒绝严格优于半修好的状态。

scan 会打印清单的形态(51 types, frozen=true, list-matches-dispositions=true),被改过就会警告。上游为这件事提出的正式入口是 #1538 里的 ignorable 写入面;请重装这个包,而不是去改它。

为什么闸门比看起来更窄

有两种看起来显然正确的实现其实是错的,都是拿官方校验器反证才发现的:

  1. session/title 也带 data.source.kind,但它记录的是标题怎么生成的(fallback 还是模型),assertSource 根本看不到它。深度遍历所有 source 对象会报出不可能发生的违规
  2. 一份日志里有两种行。 会话事件带数字 seq;分片压缩记录带 seq0time0,走另一条解码路径(decodePackedRun)。把分片记录喂给事件校验器,会凭空造出一个失败

在一个 32 份日志的真实语料上,这两处任一写错,就会把 20 个真正被拦的会话报成 30 个——等于告诉用户“你的历史坏了”,而它其实没坏。

拿官方校验器交叉验证

扫描器只做预测,最终由 harness 判定。想在你机器上确认两者一致,把带数字 seq 的事件喂给官方校验器:

import { assertReleasedEventPayload } from '@deepseek-ai/dsh-session-format-v0-to-v1'

for (const event of events) assertReleasedEventPayload(event, 0)

被拦的日志必须在扫描器指名的那个事件上抛出;干净的日志必须全部通过

dsh-session-doctor 的区别

两个工具都扫描已存储的会话,但覆盖的是不同的失败类。这个区别是可测量的,不是主观判断。

| | dsh-session-check(本工具) | dsh-session-doctor | |---|---|---| | 失败类 | 格式迁移闸门 —— SessionFormatUnsupportedMigrationError | 消息形状损坏 —— SessionPersistenceCorruptionError | | 典型触发 | 从旧版本升级上来 | 某个插件写出了畸形的工具结果 | | 报告什么 | unsupported descriptor versionunknown historical event typecannot safely transform unclassified message source | must contain one tool-result block | | 是否写文件 | 不写 | 会修复,带逐文件备份 |

在同一个 32 份日志的真实语料上:[email protected] scanscanned=32 clean=32 corrupt=0,而本工具报 20 个被拦 —— 全部落在 stale-descriptor 闸门上。两者都没错:它们查的是不同的东西,一份会话完全可能过了一个、挂在另一个上。

如果你的症状是「history unavailable … must contain one tool-result block」,那你要的不是这个工具。

依赖

  • Node >=22.19
  • zstd CLI(在 PATH 上)
  • 已安装的 harness 包(用于闸门常量与交叉验证)

开发

npm test

测试跑在合成事件上,不需要真实会话日志

许可证

MIT