dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
为 DeepSeek Harness 提供工作区级会话隔离:同一工作区里多个会话各自写自己的产出目录,拒绝覆盖其他会话的文件。
DeepSeek Harness 插件:让同一个工作区里的多个会话互不覆盖对方的产出。
一句话:给同一个工作区里的每个会话划一条写入边界。
DeepSeek Harness 的会话,在"对话"这一层本来就是隔离的——每个会话有自己独立的事件日志、独立的 agent、独立的上下文窗口。唯一被共享的东西是工作目录。
于是在同一个工作区里开多个会话时,会出这种事(下面是一个真实案例):
在同一个工作区里开了两个会话,分别做两个不相干的项目(下面叫 A 项目和 B 项目);
会话 A 建了一份"目录级"文档 README-文件索引.md,用来索引它自己那批产出;
会话 B 看到这个工作区,按"这个目录是我的项目"的假设去改这份索引,把它自己项目的内容也塞了进去;
结果一份文档里混着两个项目的内容。甚至出现了这样的标题:
## 五之三、B 项目(独立于 A 项目)
—— 一边声明"独立于 A 项目",一边被写进同一个索引。
并行跑的时候更直接:两个会话写同一个路径,后写的把先写的整文件覆盖。
这不是"会话隔离坏了",而是多个写入方共享了同一批文件。
<工作区>/.sessions/<会话短id>/bash 里的 >、tee、python -c "open(...,'w')" 写文件不会被守卫拦住(详见"已知边界");两条机制,都挂在 DSH 自己的扩展接缝上,没有一行 hack:
agent/pre-step)每个会话在第一步收到一条注入说明,内容是:
每个会话只注入一次。
ctx.tools.guard)write / edit / str_replace_editor / apply_patch 在落地之前检查目标路径:
tools/result 里按"文件确实落到磁盘上"记账,失败或被拒绝的调用不会产生归属。header.origin === 'subagent')沿 parentSession 上溯到顶层会话,
所以整棵委派树算同一个归属:父会话和它的子代理可以自由互相修改产出;归属只在该顶层会话存活期间生效。会话结束或应用重启后自动放行——避免"文件被永久锁死"这种反向麻烦。
dsh plugin --profile web add /绝对路径/dsh-session-isolation
本插件零第三方依赖,只用 node: 内置模块,所以不需要构建步骤、也不需要联网。
⚠️ 桌面版真正的 DSH_HOME 是
~/Library/Application Support/dsh-desktop/harness
不是 ~/.dsh。 直接在终端跑 dsh plugin 会装进 ~/.dsh,对桌面版不生效。
要么用桌面版内置的插件市场安装,要么显式指定 home:
DSH_HOME="$HOME/Library/Application Support/dsh-desktop/harness" \
dsh plugin --profile web add /绝对路径/dsh-session-isolation
装完重启 DSH(CLI 安装不会热加载)。
dsh --profile web --dump-config | grep -A2 session-isolation
应能看到:
# == dsh-session-isolation
- id: session-isolation
name: dsh-session-isolation
重启后,在任意工作区开一个会话并让它做事,然后:
ls "<工作区>/.sessions"
应该出现以会话短 id 命名的目录(短 id 就是 $DSH_SESSION_ID 去掉 session- 前缀后的第一段)。
全部通过环境变量:
| 变量 | 默认 | 作用 |
|---|---|---|
DSH_SESSION_ISOLATION | guard | guard = 约定 + 守卫;advise = 只注入约定、不拒绝;off = 完全关闭 |
DSH_SESSION_ISOLATION_DIR | .sessions | 产出目录名 |
DSH_SESSION_ISOLATION_MKDIR | 1 | 设 0 则不主动创建产出目录,只告知路径 |
用 advise 或 off 可以随时把插件软化/静默掉,不用卸载。
只拦文件工具,不拦 bash。
bash 里的 > / tee / python -c "open(...,'w')" 这类写文件不会被守卫拦下——
可靠地解析 shell 命令很困难,硬拦会误伤正常命令。这条路径只由机制 1 的约定覆盖,没有强制。
归属记录是进程内内存,DSH 重启后清空。 重启后已存在的文件重新变成"无主",第一个写入它的会话会取得归属。
不做物理隔离。 见上文"它不做什么"。
只对顶层会话之间生效。 子代理按血缘并入其父会话,不独立成归属。
node test/logic.test.mjs
21 项离线测试,用一个最小 fake ctx 复现 ctx.agents.get / ctx.tools.guard / ctx.on
三个接缝,覆盖:
edit / str_replace_editor 同样受守卫pre-step 只注入一次,reject 决策不被污染dsh plugin --profile web remove dsh-session-isolation
| 依赖的 DSH 接缝 | 参考位置 |
|---|---|
ctx.on('agent/pre-step', handler, { prepend: true }) | packages/context/time-context/src/index.ts |
ctx.tools.guard(fn),返回字符串即拒绝 | packages/core/tools/src/index.ts |
ctx.on('tools/result', (exec) => …) | packages/context/agent-instructions/src/index.ts |
agent.session.header 的 cwd / id / origin / parentSession | packages/core/session/src/types.ts |
子代理写 parentSession | packages/subagent/subagent/src/child-agent.ts |
消息来源 { kind: '<插件名>', form: 'snapshot', sections: [...] }(会话格式 v4 拒绝 kind: 'plugin') | packages/context/time-context/src/index.ts |
本插件刻意不 import 任何 @deepseek-ai/* 包:以本地路径安装时 peer 依赖不一定解析得到,
而运行期的消息 id 只是普通字符串(brand 只存在于类型层),所以手工构造消息与工厂函数等价。
MIT