前言¶
Long-running agent tasks accumulate intermediate files. The files themselves cost nothing on disk; the cost is that every later glob, grep and directory listing has to wade through them, which pushes irrelevant content into context and makes the agent re-derive what it already did.
DSH already has most of the machinery to prevent this. Its workspace-write sandbox grants writes under the workspace root or a platform temporary root (writableRoots returns the workspace root, /tmp and os.tmpdir()), and the workspace change summary already excludes temporary roots (isTemporaryPath — “scratch paths that never enter the change summary”). What is missing is the part that routes scratch work there, and the part that remembers it.
这个插件就是这两部分:路由 scratch 工作到临时根目录,并记住这些文件。
插件定位¶
dsh-scratch-vault 是一个 DeepSeek Harness 工作流插件,由 TINGOD2006 维护。它的核心价值是让 agent 工作区只保留真实交付物,临时脚本、一次性诊断、转换脚本等执行完后归档到插件管理的 vault 中,不污染仓库。
解决的问题¶
- 工作区污染:agent 写入的临时文件(
_probe_models.py、tmp_convert.mjs、apply-paths.ps1等)会混入仓库,让后续的文件遍历、上下文注入效率下降 - 重复推导:agent 需要重新推导已经做过的操作,因为临时文件干扰了上下文
- 历史遗留:插件启用前已经存在的临时文件没有地方管理
核心功能¶
插件在 DSH 中提供了 L2(纪律、环境、内联执行)和 L3(拦截)能力,以及一个 L4 归档 vault。
L2 — discipline, environment, inline execution¶
- 系统提示词:在系统提示词中明确告诉模型,操作脚本应运行在 scratch root,而不是工作区
- 环境变量:每个 shell 调用注入
$DSH_SCRATCH和$DSH_VAULT,命名空间按会话重建,始终指向当前会话目录 run_script({ language, code, description })工具:在 scratch root 写一个文件并执行,通过ctx.shell运行,受会话沙箱策略约束。这是”我只想运行一段代码”的正确答案
L3 — interception(拦截)¶
tools/execute 是唯一能绕过分发、修改 tool body 接收内容的水利枢纽。当 write 或 edit 目标是工作区内的 scratch 脚本时,其 file_path 被重写为会话 scratch root,工具结果返回真实绝对路径 + vault entry id。引用该脚本 shell 命令也会被重写。
安全保证:
- 映射是纯函数:
scratchPathFor是纯函数,DSH 并行分发 tool 调用,避免竞态条件 - 仅在文件不存在时重写:遗留文件仍在磁盘上,保持原有行为
Shell 重写是保守的:遇到 glob 元字符、扩展名(x.py.bak)、shell 变量引用($env:X\a.py、$DSH_SCRATCH/a.py、%TEMP%\a.py),或相对路径且无法确定工作目录(cd $env:WORK、cd %WORK%、bare cd、cd ~/proj)时,不重写。
重写仅发生在程序位置:解释器或运行器的参数槽(python、node、pwsh、bash、ruby、lua 等)或命令字本身(.\_probe.ps1)。注释中的脚本名、commit message、grep 模式、单引号 shell payload 是数据,不重写。
L4 — the vault¶
每个拦截的脚本在 vault 目录下归档,附带 JSONL ledger,以及 scratch_list、scratch_show、scratch_promote(复制回工作区作为真实交付物)和 scratch_purge。
scratch_import retrofits 启用插件前已存在于工作区的 residue:收集指定路径,逐字节验证 vault 副本,再删除原文件。两道防护:仅收集 UTF-8 round trip 存活的字节;不删除被 classifier 保护或位于 src/、node_modules/ 或 excludeGlobs allowlist 中的文件。
vault 写入在插件自身进程内完成,<vaultDir> 位于 DSH_HOME 下,在会话工作区和沙箱可写根之外。agent 通过 shell 运行的程序无法写入 vault,沙箱化工具也无法写入。收集仅在工作区边界的主机侧完成,这正是这些工具运行的位置。
ledger 是追加式,maxVaultEntries trim 等必须重写 ledger 的操作写入临时文件再重命名,中断不会截断历史。读取从以 ledger stat 为键的内存缓存提供,其他写入者(包括共享 vault 的另一个 DSH 窗口)改变签名后下次读取会重新加载。
安装与启用¶
从官方仓库安装:
dsh plugin --profile desktop add github:TINGOD2006/dsh-scratch-vault
安装后重启 DSH。
从本地 checkout 安装:
dsh plugin --profile desktop add link:C:/path/to/dsh-scratch-vault
Windows 上使用正斜杠,否则 pnpm 会把盘符拼入 profile 目录导致插件安装但不激活。
要求:Node ≥ 22,以及一个挂载 tools 服务的 DSH 构建。插件声明无任何依赖——甚至没有 @deepseek-ai/dsh-tools。链接插件的裸 specifiers 解析到其真实路径,不会触及 profile 的 node_modules,所以一次 host import 足够触发加载失败。工具定义在 lib/tools.js 中手工组装,与 defineTool 的形状完全一致。
编辑 lib/ 下的任何文件需要完整重启 DSH。加载器在启动时构建模块图,按解析 URL 缓存每个模块;禁用/启用 bundle 不会重新导入文件。
典型用法¶
启用插件后,agent 写入的临时脚本会自动归档到 vault。
before after
repo/ repo/
├── src/ ├── src/
├── _probe_models.py ← noise ├── package.json
├── _recon_api.py ← noise └── (deliverables only)
├── tmp_convert.mjs ← noise
├── apply-paths.ps1 ← noise vault/
└── package.json └── entries/20261012…-a1b2c3/script
通过 $DSH_SCRATCH 和 $DSH_VAULT 环境变量,模型知道在 scratch root 中操作。run_script 工具可直接运行代码而不污染工作区。
通过 scratch_promote,可以将任意归档脚本复制回工作区作为真实交付物。
通过 scratch_import,可以批量迁移启用插件前已存在的临时文件到 vault,并验证字节一致性。
适用场景与注意¶
适合:
- 长时间运行的 agent 任务,需要大量中间文件
- 需要保持工作区整洁,只提交真实交付物
- 需要归档和追溯临时脚本
注意:
- 插件以当前 dsh 进程权限运行,安装前应检查源码与许可证(MIT)
- 需要 Node ≥ 22,以及支持挂载
tools服务的 DSH 构建 - 编辑
lib/下文件需要完整重启 DSH - vault 位于
DSH_HOME,在沙箱边界之外,agent 无法直接修改
总结¶
dsh-scratch-vault 通过 L2/L3/L4 的分层设计,将临时脚本从工作区隔离到 scratch root,再归档到插件拥有的 vault,让工作区只保留真实交付物。它补全了 DSH 原有的 sandbox 和 change summary 机制,解决了长任务中的文件污染和重复推导问题。安装简单,无依赖,适合需要保持工作区洁净的 agent 工作流场景。
- 插件目录:https://www.skillhub.cn/plugins/TINGOD2006/dsh-scratch-vault
- GitHub:https://github.com/TINGOD2006/dsh-scratch-vault