@240xu/dsh-session-lazy-view

sessionnpmMIT

Read-only lazy session viewer: lists sessions with stat only, then decompresses just the tail Zstandard frames to inspect recent events; includes search, stats, and export in a standalone /lazyview panel. Requires Node.js 24+.

下载量 / Downloads
664

Install

dsh plugin --profile web add @240xu/dsh-session-lazy-view

Sandbox test

This plugin has not been tested yet. Submit a test →

README

@240xu/dsh-session-lazy-view

怎么打开:安装后访问 http://<dsh-web-地址>/lazyview 即是独立面板页(无需找侧边栏入口);
DSH 宿主侧边栏没有本插件入口是有意设计(纯只读工具,不污染主界面)。
装了 dsh-devkit 的话,Ctrl+K 命令面板里有「打开」入口(devkit 侧接线)。

DSH web 插件:会话惰性查看器(纯只读)。列出 ~/.dsh/sessions 下全部会话(只 stat 不解压),

并可只解压任意会话文件的最后 1–2 个 zstd 帧快速查看最近发生的事件,不碰整份文件。

不做删除、不做归档、不是 Trajectory 替代品;对 ~/.dsh/sessions 零写入。

原理

session.v3.jsonl.zstd 是连续多帧的独立 zstd 帧(魔数 0x28 B5 2F FD):

帧 0 恰好一行 session header JSON;之后每帧一批 newline-delimited JSON 事件,

最后一帧结束于 EOF。因此从文件尾部读一个窗口、扫帧魔数、只解压候选帧,

即可拿到最近事件,成本与文件总大小无关(旧格式 session.jsonl.zstd

为单帧,等效于解压整份,但同样走这条路径)。

安装

零 npm 依赖(只用 node:fs / node:zlib / node:path / node:url;

@deepseek-ai/schemastery 由 DSH profile 树自带,无需安装)。

# 方式 A(推荐):DSH 官方插件命令(内部走 pnpm,正确登记 lockfile 与 bundles)
dsh plugin --profile web add @240xu/dsh-session-lazy-view

# 方式 B:npm pack 直解(profile 是 pnpm workspace,不要在 profile 内直接 npm i ——
# workspace 根的 link:/ 依赖会让 npm 报 EUNSUPPORTEDPROTOCOL)
cd ~/.dsh/profiles/web/node_modules
npm pack @240xu/dsh-session-lazy-view
mkdir -p @240xu/dsh-session-lazy-view
tar -xzf 240xu-dsh-session-lazy-view-*.tgz -C @240xu/dsh-session-lazy-view --strip-components=1
# 然后把 "@240xu/dsh-session-lazy-view" 加进 profile package.json 的
# dsh.profile.bundles 列表(lockfile/package-map 不一致时 pnpm install 会补齐)

# 方式 C:从源码拷贝(开发)
git clone https://github.com/240xu/dsh-session-lazy-view && cp -r dsh-session-lazy-view ~/.dsh/profiles/web/node_modules/

然后在 DSH web profile 的 cordis.patch.yml 里挂载(写法与

dsh-archived-sessions 完全一致)。若该文件已存在其它 - insert: 条目,

把下面 - id: 两行并入现有列表即可:

- insert:
    - id: dsh-session-lazy-view
      name: '@240xu/dsh-session-lazy-view'

重启 DSH web 实例后生效。

端点

前缀 /lazyview(受与 archived-sessions 相同的 loopback / same-origin 信任门保护):

端点 · 说明

GET /lazyview · HTML 面板:按项目分组列出会话,>10MB 标红,每行 tail 按钮展开最近 2 帧,支持“上一页”

GET /lazyview/api/list · JSON:{root, projects:[{project, sessions:[{sessionId, path, artifact, format, bytes, mtime, big}]}]},纯 stat

GET /lazyview/api/tail?path=<相对路径>&frames=2&skip=0 · JSON:从尾部解压 frames 帧;skip 为翻页偏移(跳过最新 skip 帧后再取 frames 帧)。帧内事件已文本化(role + 截断到 2000 字符的 text / tool-call / tool-result)

GET /lazyview/api/search?path=<相对路径>&q=<关键词>&max=20 · 在单个会话内全文检索;逐帧解压并在帧间让出事件循环,客户端断开即中止

GET /lazyview/api/stats?path=<相对路径>&fast=1 · 会话统计;fast=1 只数帧魔数和字节,默认解压全部帧统计事件类型

GET /lazyview/api/export?path=<相对路径>&frames=2 · 把最近 N 帧导出为 Markdown 下载;内存生成,不写会话目录

path 必须是 /api/list 返回的 path(相对 sessions 根,且必须以

session.v3.jsonl.zstd 或 session.jsonl.zstd 结尾);路径逃逸检查在服务端。

解析失败(坏帧、旧格式、窗口截断等)一律返回

{ok:false, error:{code, message}} 结构化错误,不会 500 崩面板。

已知限制

  • 旧格式 session.jsonl.zstd 单帧等于整份文件,tail 读取没有惰性收益。
  • 尾部窗口默认 1MB,最多增长到 64MB;极端情况下(单帧超过 64MB)返回

truncatedScan: true 且可能少帧,不会死循环。

  • 帧魔数理论上可能出现在压缩数据内部造成假候选;假候选解压失败后以

per-frame error 呈现,不影响其余帧。

  • 事件文本化只覆盖已验证的形状(user/assistant/system message、tool call/result),

其余事件类型输出紧凑 JSON 摘要。

  • 未做缓存:每次 tail 请求都重新读窗口,对调试场景足够。

离线验证

./verify.sh

用 node 直接 import ./lib/frames.js,对 ~/.dsh/sessions 下任一

.zstd 会话执行“读最后 2 帧”,打印事件条数与首条 role。

v0.2.0 新增(端点细节)

端点 · 说明

GET /lazyview/api/search?path=&q=&max=20 · 会话内全文搜索:流式逐帧解压做大小写不敏感子串匹配,命中返回 {frameIndex, seq, type, role, snippet}(命中点前后各 60 字符窗口)。达 max 截断;默认最多扫 500 帧,未扫完返回 partial:true。每帧之间 setImmediate 让出事件循环,客户端断开(AbortSignal)即中止扫描

GET /lazyview/api/stats?path=[&fast=1] · 会话统计:帧数、总事件数、按 type 分组计数、首/末事件时间、文件字节数、session header。fast=1 只流式扫描帧魔数(不解压、内存有界)给帧数/字节数

GET /lazyview/api/export?path=&frames=N · 把最近 N 帧(≤20)导出为 Markdown 文本下载(Content-Disposition: attachment)。文本在内存中生成直接响应,不写任何文件

面板集成:每个会话行新增「搜索」按钮(展开输入框 + 命中结果列表,命中

标注帧号/seq/type/片段,新搜索自动取消上一次);打开会话顶部新增「统计」

小节(先展示 fast 帧数/字节,按钮展开全量解压统计)与「导出 md」下载链接。

测试:npm test(即 node --test "test/*.test.js";零依赖,用 node:zlib 自造多帧 fixture,

覆盖搜索命中/截断/abort、stats 分组、export 渲染)。

性能取舍说明:

  • 解压是同步的(zstdDecompressSync),全量扫描在帧与帧之间用

setImmediate 让出事件循环(参照 dsh-src ZSTD_DECODE_YIELD_INTERVAL_MS

思路),单个大文件的搜索/统计不会长时间阻塞 web server;但**一次请求

内整份文件会被读进内存**(read-only,读完即释放),超大文件请优先

?fast=1 或改用 tail。

  • 搜索只覆盖事件文本化后的可见文本(describeEvent,单条截断到 2000

字符);无法文本化的行仍按原文参与匹配。

  • 全量统计对大文件是秒级操作(每帧解压一次);面板默认只拉 fast 统计,

全量按需展开。

v0.2.1 评审修复

  • 可发现性(P0):README 与面板页顶部均加显著说明——面板是 /lazyview

独立页,宿主侧边栏无入口是有意设计(纯只读工具);面板页新增

「复制面板链接」按钮;README 注明装了 dsh-devkit 可用 Ctrl+K 打开。

  • S1(P1):360px 表格溢出修复——session id 列截断省略(title 悬停看全),

mtime 列在 ≤480px 隐藏(hide-sm)。

  • S2(P2):全部按钮触控目标提升到 ≥44×44px。
  • S3(P2):统计小节预留固定高度占位(fast→full 替换前锁定高度),防 CLS。
  • S5(P3):#status 加 role="status" aria-live="polite",读屏可闻。

v0.3.0 Timeline 视图

按 turn 分组的正序事件时间线(vscode Timeline 式阅读投影),设计文档见

timeline-design.md:

  • 会话打开视图顶部新增「时间线」按钮,与帧流互斥切换;数据复用

/api/tail 翻页到文件头后按 seq 正序拼接(无需新端点)。

  • 分组规则:有 turn/start 事件时它开新组;历史格式以 user/message

为组边界。组默认折叠一行摘要,展开显示全部事件;折叠状态存内存。

  • Go to Message 深链:/lazyview?session=<id>&seq=<n> 自动打开会话、

进时间线、展开目标组并高亮首个 seq≥n 的事件。

  • message-ops 遮蔽标注:事件自带 surfaceOp(op:"replace")时,被替换

区间内的事件在时间线上渲染为半透明 +「已遮蔽」徽标;纯本地计算,

不依赖 message-ops 端点。只读铁律不变。

  • 纯函数(深链解析 / 遮蔽计算 / 分组)抽到 lib/timeline.js,一份两用:

ESM 供测试,服务端剥 export 内联进面板。

v0.3.2 v4 会话支持

  • 用户实测发现:DSH 新会话写 session.v4.jsonl.zstd(帧 0 header

version:4),面板此前只探测 v3/legacy,新会话全部不可见。

  • 修复:artifact 名单统一抽到 lib/artifact.js(session.vN.jsonl.zstd

形状正则),目录探测与 ?path= 校验共用;未来新版本号自动纳入,

frames.js 无版本硬校验(header.version 原样透传),无需再改。

  • 面板 format 列区分 v4-multiframe / v3-multiframe / legacy-single-frame。

v0.3.3 可访问性补齐

  • 时间线分组折叠头(.ghead)从纯点击 div 改为键盘可达:role="button"
  • tabindex="0" + Enter/Space 触发 + aria-expanded 与折叠 Map 同步。

0.3.4 · Bug 猎场修复

  • [P1] 深链白屏:?session= 访问在 sessions.map 回调外引用回调参数 s →

ReferenceError 被 catch 吞掉,面板一个会话都不渲染(0.3.0–0.3.3 全中)。改为在

project 的 sessions 内匹配 + 表格行定位打开按钮。

  • [P1] 遮蔽标注失效:applySurfaceOps 只认 fromSeq/toSeq(message-ops 从没写过

这种拼写)——真实数据是 start/end(新引擎)与 startSeq/endSeq(leg

…(truncated)

Rendered from the README published on npm. Formatted only, not edited.

Versions

Published on npm::12,最后更新 2026-10-21

Links

GitHub · Homepage · npm

Same category