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-rollback

v0.2.0

Published

File-mutation rollback plugin for DeepSeek Harness — checkpoints write/edit pre-images via git blobs or snapshots and restores them on demand through a model-facing rollback_files tool and a /rollback command. Opt-in bundle: capture rides the tools/result

Readme

dsh-rollback

npm version License: MIT

English | 中文

DeepSeek Harness 的文件变更回滚插件:将 write/edit 变更在其结果中报告的改前映像记录为检查点,并对黑盒 bash/run_code 调用做前向快照使其文件变更可 diff,把每个改前映像存入工作区 git 对象库或快照存储,并通过面向模型的 rollback_files 工具和面向人的 /rollback 命令提供还原。它不注册任何服务,也不改动循环代码——捕获搭在文档化的 tools/* 扩展点上(tools/result 观察事件与 tools/execute around 钩子),还原直接写文件(绝不经过 fs 策略 seam 或沙箱,因为撤销一次变更不应被该变更当初通过的策略所门控)。

安装

本包是可安装的 bundle(声明了 dsh.bundle),直接接入 profile,无需改动 harness。你只需要一个 dsh CLI;运行时 peer 包(@deepseek-ai/dsh-tools、@deepseek-ai/cordis 等)从 dsh 安装本身解析,无需额外安装任何东西。

前置条件

  • 机器上有 dsh CLI(dsh plugin 内部调用 pnpm,所以 pnpm 需在 PATH 中)。
  • 一个要安装进去的 profile——下面的 demo 首次使用时自动初始化,可换成任意名字。

从 npm 安装(推荐)

dsh plugin --profile demo add dsh-rollback

其他来源用法相同:

# 直接从 git 安装(源码由 prepare 脚本构建;建议锁定 commit)
dsh plugin --profile demo add github:you/dsh-rollback#<sha>

# 或从本地 tarball
dsh plugin --profile demo add ./dsh-rollback-<version>.tgz

验证安装

  1. $DSH_HOME/profiles/demo/($DSH_HOME 默认为 ~/.dsh)下的 profile manifest 中,dependencies 和 dsh.profile.bundles 都会出现 dsh-rollback——因为包声明了 dsh.bundle,reconciler 会自动加入。等价的手动补丁层行:
- id: rollback
  name: dsh-rollback
  config:
    mode: auto          # auto | git | snapshot
    storeDir: ''        # '' = <harness home>/rollback
    maxRecords: 200
    gitPath: git
  1. 启动一个会话。以下任一现象都说明插件已生效:
    • 模型的工具列表里能看到 rollback_files;
    • 在还没发生任何变更时输入 /rollback,得到 rollback: nothing to restore(而不是"未知命令"报错)。

使用教程

30 秒快速上手

  1. 打开一个工作目录在 git 仓库内的会话。
  2. write 一个文件 notes.md,内容为 hello。
  3. 再次 write 它为 goodbye——第一次的内容已被静默记录。
  4. 对模型说"把你刚覆盖的文件还原"(它会调用 rollback_files),或自己输入 /rollback。
  5. 查看 notes.md:内容又变回 hello。

面向人 — /rollback [count]

在聊天输入框输入(base web 与 headless profile 挂载了命令所需命令注册表):

  • /rollback —— 撤销本会话工作目录中最近一条被捕获的变更;
  • /rollback 3 —— 撤销最近三条。

输出逐文件列出还原动作:

rollback: restored 2 file mutation(s):
  restored  /ws/src/lib/parse.ts
  deleted   /ws/src/lib/generated.ts

人手动回滚还会告诉模型。命令的生命周期(command/run/command/done)是 log-only、永不进入模型上下文的,因此插件会为下一个 pre-step 排入一条面向模型的 notice(走 Agent.inject):逐条列出它重写或删除了哪些路径,并说明模型手里的这些文件副本已经过期。没有这条链路时,人的回滚会在模型背后改写文件,而模型的 transcript 仍在描述回滚前的世界——它甚至会继续针对已经不存在的路径干活。设 notifyModel: false 可让手动回滚保持静默。

面向模型 — rollback_files

新增一个面向模型的工具 rollback_files {count}(无提示词章节)。它用于让模型撤销自己犯下的 write/edit 错误,而不是回头麻烦用户。还原限定在调用方会话的工作目录内,输出为逐文件摘要——不会回显还原后的文件内容。

捕获范围

同一 store 有两条捕获路径:携带 before 改前映像的成功 write/edit 工具结果,以及根 bash/run_code 调用造成的文件变更(在 git 仓库内前向快照后 diff,见行为)。通过 str_replace_editor、裸子进程或非 git 仓库内的 bash 调用产生的变更没有可恢复的改前映像,不会被捕获。会话只能还原位于自身工作目录下或其自身的记录。

效果演示(前后对比)

一次 write 覆盖、一次撤销。同一个文件,四种状态:

| 步骤 | 动作 | notes.md | |---|---|---| | 1 | 初始状态 | hello | | 2 | 模型 write 写入错误编辑——改前映像被捕获 | goodbye | | 3 | 模型调用 rollback_files {"count": 1} | (透明) | | 4 | 逐字节还原 | hello |

完整 transcript:

# 1. 初始状态
$ cat /ws/notes.md
hello

# 2. 模型覆盖文件;tools/result 携带改前映像 "hello",
#    捕获监听器将其记录进 git 对象库
> tool/call   write {"path": "/ws/notes.md", "content": "goodbye"}
> tool/result {"path": "/ws/notes.md", "before": "hello", ...}

# 3. 模型意识到写错了,撤销该变更
> tool/call   rollback_files {"count": 1}
> tool/result "rollback: restored 1 file mutation(s):
               restored  /ws/notes.md"

# 4. 还原为变更前内容
$ cat /ws/notes.md
hello

底层实现:git hash-object -w 将改前映像写入 git 对象库(零索引/分支/工作树污染),同时向持久化 manifest.jsonl 追加一行——因此重启后同一撤销依然可用。

工作原理

flowchart TD
    A["write/edit 工具结果"] --> B["tools/result 观察事件"]
    B --> C{结果带 before 改前映像?}
    C -- 否 --> X[忽略]
    C -- 是 --> S[CheckpointStore 捕获]
    A2["bash / run_code 调用"] --> B2["tools/execute 前向快照 + diff"]
    B2 --> C2{工作区是 git 仓库?}
    C2 -- 否 --> X2[跳过并告警]
    C2 -- 是 --> S
    S --> G{工作区是 git 仓库?}
    G -- 是 --> BLOB["git hash-object -w 存 blob"]
    G -- 否 --> P["写 storeDir/snapshots/ 快照"]
    S --> MF["追加 manifest.jsonl"]
    U["模型调 rollback_files / 用户 /rollback"] --> RS["restore 按 session.cwd 作用域"]
    RS --> RR["git cat-file / 快照 / 删除文件"]

上图的完整示例与前后对比 transcript 见效果演示。

插件(namespace: rollback)

函数/命名空间插件(name / inject / Config / apply),不是服务。它与 dsh-tool-call-timeout-policy 同属循环卫生 guard 家族:在文档化的 tools/* 扩展点之上叠加安全网,而非触碰 agent 循环。

Config

| 键 | 类型 | 默认值 | 含义 | |---|---|---|---| | mode | 'auto' \| 'git' \| 'snapshot' | 'auto' | git 将每个改前映像记录为 git blob(需要仓库;非仓库路径大声失败且不捕获任何内容);snapshot 始终将改前映像复制到 storeDir/snapshots/ 下;auto 在工作区是仓库时按文件选用 git,否则用快照。 | | storeDir | string | '' | 持有持久化 manifest.jsonl 与 snapshots/ 的根目录。为空时解析为 Harness home 下的 rollback。 | | maxRecords | number | 200 | 每个 store 内存记录的上限;超出后丢弃最旧的(持久化 manifest 保留全部)。 | | gitPath | string | 'git' | Git 可执行文件名或绝对路径。 | | mutationTools | string[] | ['bash', 'run_code'] | 需要前向快照的工具名——不带 before 改前映像的黑盒变更调用。只对根派发做快照(Code Mode 嵌套子调用由它们的 run_code 父级覆盖),且仅在 git 仓库内。 | | notifyModel | boolean | true | 人手动 /rollback 之后注入一条面向模型的 notice,让模型知道文件在它掌控之外被改动了。模型自己调 rollback_files 不需要这条通知(其调用与结果本来就在 transcript 里)。 |

行为

捕获。 tools/result 监听器将成功的 write/edit 结果转换为检查点:结果的 before 字段即改前内容(null 记录文件原本不存在)。blob 改前映像通过 git hash-object -w --stdin 写入文件所在仓库(向上探测 .git 发现仓库根,按目录缓存)——零索引/分支/工作树污染,由 git 自身内容寻址并去重。每条记录以一行 JSONL 追加到 manifest.jsonl;插件加载时的 store 回放恢复内存列表,因此还原在重启后依然可用。只捕获绝对本地展示路径;相对或远程展示路径(非本地文件系统后端)被忽略。

前向快照(bash / run_code)。 tools/execute around 钩子会在根 bash(或 run_code)调用执行前快照工作区。候选集由 git 一次调用枚举(git ls-files -co --exclude-standard):已跟踪文件 + 未被忽略的未跟踪文件——由忽略规则而非手写遍历决定范围,构建产物与 vendor 目录因此零成本。.git 与 node_modules 永不快照(也因此永不被还原),插件自身 store 目录也被排除。随后所有候选文件在一次批量调用中保留为原始 git blob(git hash-object -w --no-filters --stdin-paths):--no-filters 保证改前映像字节级精确(否则 git 会做 CRLF/属性归一化,还原时改写换行符而不是复现文件);批量调用把「每文件一个进程」降为「每次快照固定几个进程」。mtime + size 快速路径跳过自上次快照以来未变化的文件。调用结束后重新枚举候选集并 diff,每个变更文件都会被记录为检查点——修改或删除的文件保留变更前 blob,新建文件记录为 absent。快照限定在会话工作目录内且要求 git 仓库,否则跳过并告警。Code Mode 嵌套子调用由它们的 run_code 父级覆盖,因此一次变更永不会被重复记录。

还原。 restore(count, under) 重新物化最近的 count 条路径位于 under(调用方 agent 的会话工作目录)之下或其自身的记录:blob 通过 git cat-file blob <hash>,snapshot 从 storeDir/snapshots/<ref>,absent 则删除文件。写入是原子的(临时文件 + rename)并创建父目录。被还原的记录从内存列表移除;manifest 保持只追加,因此重启会回放同样的记录,后续还原会重新应用完全相同的改前映像(幂等,不会双重撤销)。

暴露。

  • rollback_files 工具——面向模型的还原,参数 count(整数,默认 1)。注册在 ctx.tools 上;非并发安全。当调用执行没有会话工作目录时拒绝执行。
  • /rollback [count] 命令——对接收方 agent 的会话执行同样的还原;命令子组件仅在组合了命令注册表时激活(base web 与 headless profile 挂载 dsh-commands)。非空还原之后它注入一条面向模型的 notice(见模型体验);没有 inbox 的 agent、或被拒绝的注入,都不会影响命令自身的返回结果。

为什么用 git,为什么直接 spawn

Git blob 与 ccAgent 使用同一机制:hash-object -w 写入改前映像而不触碰索引、引用或工作树;cat-file 原样还原字节;未被引用的 blob 由 git 自身的 gc 回收。Git 通过 node:child_process 直接 spawn(绝不经过 ctx.shell 或 ctx.subprocess):还原是刻意的系统级撤销,因此不能被它所撤销的沙箱或 shell 策略所限制。

模型体验

面向模型的还原工具

模型所见

本插件新增一个面向模型的工具 rollback_files(整数参数 count,字符串输出),无提示词章节。它不改变任何其他工具的 schema 或系统提示词。

/rollback 命令本身永不进入模型——命令生命周期是 log-only——但完成的人手动回滚会:插件注入一条 plugin 来源的 notice 消息,逐条列出它 restored/deleted 的路径,并附带"模型手里这些路径的副本已过期、必须重新读取"的说明。注入的上下文排在下一个 pre-step,但不唤醒 driver,所以空闲会话会在下一次请求时带上它。

Token 影响

正常运行零 token。一次 rollback_files 调用会加入其小型的工具/结果对;一次人手动 /rollback 会加入一条短 notice(一行抬头、每路径一行、一行指示;绝不回显文件内容)。捕获本身对模型不可见。

KV Cache 影响

只追加;新增的工具 schema、结果与 notice 都跟随可复用请求前缀,不使现有 KV-cache 条目失效。

已知限制与延后工作

  • str_replace_editor 与裸子进程不被记录 —— bash/run_code 的文件变更已由前向快照捕获,但仅在 git 仓库内(非仓库工作区跳过快照并告警),且 bash 调用只快照会话工作目录,它在别处改动的文件不会被捕获。plan 范围批量备份(执行前捕获 plan 触碰的每个文件)是对应的泛化方向,已延后。
  • 前向快照字节级精确,write/edit 仍限文本 —— bash/run_code 的改前映像以原始字节保留(--no-filters),因此二进制文件与 CRLF 文件都能精确往返;write/edit 路径仍然携带工具自身的 before 字符串,按 fs 工具的契约是文本。
  • 被忽略的路径永不被捕获 —— 前向快照的候选集来自 git 的忽略规则,因此既未跟踪、又被排除的路径(dist/、build/、coverage/ 等)不会被 bash/run_code 记录为检查点,也无法回滚;git 已跟踪的文件即使被 .gitignore 匹配,也照样会被捕获。
  • 大仓库的首次快照要付 O(文件数) 成本 —— 前向快照为每个候选文件存一个 git blob,因此一个仓库在某会话中的首次快照主要开销在 git 写 loose object 上。批量调用消除了「每文件一个进程」(每次快照固定几次 git 调用),但字节仍需每文件读一次、存一次;同一会话中之后的快照复用 mtime + size 缓存。这一首次成本随文件系统与杀毒软件对 .git/objects 的扫描而波动。
  • 一个批量哈希无法寻址的路径会让该次快照失效 —— 路径以换行分隔传给 git(--stdin-paths),因此文件名中含换行(POSIX 合法;NTFS 拒绝)会让整批失败,而批量失败会中止整次快照而不是跳过该文件:只要工作区里存在这样的路径,该仓库的 bash/run_code 变更就全部不被捕获,且每次调用都会告警。
  • 还原限定工作区 —— 调用方 agent 会话工作目录之外的记录永不被该调用方还原;没有跨目录或全局还原入口。
  • 还原不做陈旧校验,直接覆盖 —— 改前映像被无条件写入。若文件在该变更被捕获之后又被改过(人手动编辑、另一个 agent、后续命令),还原会静默丢弃这些更新的内容:既没有冲突检测,也不备份被替换掉的状态。
  • 工作目录相同的多个会话可互相撤销 —— 作用域是目录而不是会话。记录里不带会话标识,因此一个会话里的 /rollback 或 rollback_files 也会选中任何其他同工作目录会话(子 agent、另一个窗口)产生的变更。
  • 手动回滚的 notice 只是提示,不是约束 —— 它告知模型,但不限制模型。真正阻止模型覆盖掉回滚结果的是 harness 本身:还原会替换文件(新 inode/mtime/ctime),于是 fs-observation-policy 的版本守卫会把模型对同一路径的下一次 write/edit 判为 stale 并强制重读。没挂该策略的 profile 只剩 notice 这一层;notifyModel: false 会连 notice 一起去掉。
  • 只追加 manifest,无修剪 —— 被还原的记录仍留在 manifest.jsonl 中并在回放时重新出现(幂等重复还原,不会双重撤销),但长期运行的 harness home 会无压缩地增长。回放还会把内存列表裁剪到 maxRecords,因此重启后只有最新的 maxRecords 条可还原,尽管文件里保留了全部记录。
  • 多进程共享同一 store 时 seq 会冲突 —— 序号是进程内的(回放到 manifest 末尾后自增)。两个 harness 进程共用一个 store 目录会写出重复序号,破坏同伴插件断言的严格递增不变量,也让「按条数还原时丢弃哪些记录」变得含混。
  • 捕获的持久性是尽力而为 —— manifest 追加既不做 fsync、也不被等待(捕获挂在观察事件上 fire-and-forget),因此变更与落盘之间发生崩溃可能丢掉最新记录,且只体现为一条告警。
  • git gc 可能回收长期 blob —— 默认 gc 在保留窗口后回收未被引用的对象;早于该窗口的检查点可能无法还原。保活 ref 命名空间已延后。
  • 失败时不自动还原 —— restoreOnFailure 在 v1 中刻意不提供;自动还原需先将失败归因到具体变更。

开发

pnpm install     # peer 包从 npm 发布版解析
pnpm run build   # tsdown -> lib/(ESM + d.mts),自包含构建
pnpm test        # vitest,23 个 store 级测试 + 7 个 notice 测试

许可证

MIT