dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DSH 的「每个会话一个独立工作目录」插件——即 Codex 那种体验,按项目选择开启, 可以在 Web 界面里点开关,也可以在对话里让 agent 打开,或者直接放一个标记文件。
English | 中文
DSH 会把所选工作区目录交给每个会话,于是同一项目里的两个会话写的是同一批文件。 三个 agent 跑在同一个目录上,后一个会把前一个刚写完的文件"顺手修掉"。
某个目录开启隔离后,在那里新建的每个会话都在自己的 session-<id> 子目录里工作。
这个子目录(而不是项目目录)成为该会话 workspace-write 沙箱的写入根,因此并行会话
互相写不到对方的文件;而项目本身的文件对所有会话仍然可读。
project/ ← 你选择的工作区(每个会话都能读)
├── .dsh-isolate ← 开关(一个空文件;删掉即关闭隔离)
├── src/ … ← 项目自己的文件(对会话只读)
├── session-1a2b3c4d/ ← 会话 A 的沙箱根,只有它能写
└── session-9f8e7d6c/ ← 会话 B 的沙箱根,只有它能写
-2、-3)。AGENTS.md 发现与项目技能查找照常工作——它们都从会话 cwd 向上走。没有改动 DSH 任何源码:插件走标准 Cordis 组合挂载,加载时包装两个接缝,卸载时复原。
插件无需构建,除 DSH 宿主本身外没有运行依赖。仓库包含 Cordis 入口、六个 lib/ 模块、
浏览器半边、bundle patch 和完整测试。已在 DSH 0.1.6-alpha.2 上验证,要求 Node.js 22+。
dsh plugin --profile <profile> add \
https://github.com/sweet-boby/dsh-session-isolation.git
安装后重启一次宿主。插件管理器会安装包、选中 bundle 并应用
cordis.patch.yml;重启是为了让浏览器模块表发现新的 dsh.client bundle。
打开 插件 → 添加插件,粘贴 GitHub 地址:
https://github.com/sweet-boby/dsh-session-isolation
如果当前 DSH 版本的安装器只接受本地 bundle 路径,就先克隆仓库,再粘贴本地目录的绝对路径。
git clone https://github.com/sweet-boby/dsh-session-isolation.git
dsh plugin --profile <profile> add \
/绝对路径/dsh-session-isolation
在 ~/.dsh/profiles/<profile>/package.json 里加依赖并选中 bundle:
{
"dependencies": {
"dsh-session-isolation": "github:sweet-boby/dsh-session-isolation"
},
"dsh": {
"profile": {
"bundles": ["…", "dsh-session-isolation"]
}
}
}
本地开发时,把依赖改成 file:/绝对路径/dsh-session-isolation,然后在 profile 目录安装:
cd ~/.dsh/profiles/<profile>
pnpm install --ignore-scripts
无论用哪种方式安装,最后都要重启宿主。
sessionController、workspaceRegistry、tools 三个服务——所有
base 系 profile 都有。缺任何一个时插件保持 pending,而不是半加载状态。ctx.webServer)是可选依赖:没有它时插件保留策略与工具,只是不提供 GUI 页面。隔离是目录的属性,持久形态就是目录里的 .dsh-isolate 标记文件——设置跟着文件夹走,
用文件管理器也能改。
session_isolation 工具,动作有 status、list、
enable、disable,可选绝对路径 path(默认取当前会话的工作区)。await ctx.isolatedSessions.setIsolated(workspaceId, true) 或
setIsolatedPath(path, true);list() / isIsolated(id) 读取当前状态。.dsh-isolate 文件。在该工作区里新开一个会话,问它的工作目录(pwd,或让它写一个文件)。答案会是一个
session-<id> 子目录;项目根目录在它的文件策略里是只读位置。开两个这样的会话,会得到
两个不同的目录。
浏览器半边是手写的客户端 bundle(lib/client.js),通过 dsh.client 与
exports["./client"] 声明,因此 Web 客户端模块表会像对待打包插件一样提供它。它用
ctx.slots.inject 注册一个 settings.plugins.tab 条目,也就是先等 Settings 侧声明该 slot
再注册——插件激活顺序并不稳定,往未声明的 slot 里注册会让整个浏览器启动失败。
外部插件的浏览器半边无法声明 Remote 命名空间,所以两半通过 Web 服务上的两个具名路由通信:
| 路由 | 方法 | 请求体 | 响应 |
|---|---|---|---|
/session-isolation/workspaces | GET | — | { workspaces: [{ workspaceId, title, path, isolates, sessionCount }], isolatedCount } |
/session-isolation/toggle | POST | { workspaceId, enabled } | 刷新后的列表,或 { error } |
路由本身不持有状态——所有答案都是从标记文件现场投影出来的;它们和其他插件资源路由一样, 是绑定在回环地址上的免认证路由。没有 Web 服务的宿主不会注册它们。
页面出现需要重启一次宿主。 客户端模块表在激活时扫描
dsh.client声明,并按包名缓存 「这不是客户端包」的判定,所以宿主启动之后才出现的 bundle 要等下次启动才会被拾取。 宿主侧改动可以通过配置重载生效,新的客户端 bundle 不行。在此之前,其他开关 (工具、标记文件、宿主服务)都已经是可用的。
两个接缝,加载时包装、卸载时复原;未接管的调用一律委托原实现;接缝若被上游挪动会显式报错
(… this DSH build moved it),而不是静默地什么都不隔离。
ctx.sessionController.create——GUI 调用的 Remote 入口。DSH 解析新会话 cwd 的规则是
workspace.path ?? request.cwd ?? default,而控制器会拒绝同时携带 workspaceId 与 cwd
的请求,所以插件在这一次调用期间把目标工作区实体指向新分配的私有目录。持久记录不动,
重定向在 finally 里移除,创建失败会删掉刚分配的目录。重定向期间实体的自身写入落在一个
可变槽位里,因此本次调用刚记账的会话不会在移除重定向时被回滚。WorkspaceEntity 实际读取的那个 host 对象
(注册表只构造一份并共享)。位于隔离工作区内的会话目录会解析回该工作区根路径,于是原有的
挂载校验、持久化成员裁剪、一次性历史 bootstrap、以及「一个会话只属于一个工作区」不变量
全都继续成立,无需改动源码。隔离状态本身不由插件持久化:每次都从标记文件重算,因此重启、外部编辑、其他工具最终都会收敛到 同一个答案。扫描是串行的;若某次扫描落在"按调用重定向"进行期间,它会合并而不是回退一个当时 看不见的决定。
git 能对上父仓库,但分支仍是共享的。
要做到每个 agent 一条分支需要 worktree,本插件不创建 worktree。正常的代码仓库建议保持关闭:
开了之后 agent 改不到仓库本身的文件。workspace-write 语义一致;会话仍能读项目。
用户也可以为某一轮批准 danger-full-access 绕过边界——那是 DSH 的审批策略,不是本插件的行为。file: 安装是拷贝。 pnpm 对 file: 依赖做拷贝而不是软链,所以改完插件要在下次启动宿主前
重新 pnpm install(或删掉 node_modules/dsh-session-isolation 再装)。| 现象 | 原因 | 处理 |
|---|---|---|
| 设置里没有「工作区隔离」标签页 | 宿主启动时客户端 bundle 还不存在 | 重启一次宿主(见上文重启说明) |
/session-isolation/workspaces 返回 404 | 插件没加载,或宿主没有 Web 服务 | 确认 bundle 已选中且已安装;查看宿主启动日志里 session-isolation fiber 是否失败 |
| 新会话仍在共用项目目录 | 标记不在该工作区自己的目录里,或会话早于标记 | 确认 .dsh-isolate 就在页面显示的工作区路径下;只有之后新建的会话才隔离 |
| 隔离过的会话显示为未分组 | 该宿主上插件没生效 | 重新启用插件;目录与日志都完好 |
| agent 写不了项目根目录的文件 | 设计如此 | 关掉该项目的隔离,或让会话写进自己的目录 |
.dsh-isolate),也可以直接留着——没有插件时它们不起作用。dsh.profile.bundles 与 dependencies 中移除 dsh-session-isolation,
在 profile 目录执行 pnpm install --ignore-scripts。卸载不会触碰任何会话目录与日志。
node --test "tests/*.test.js"
| 测试文件 | 覆盖内容 |
|---|---|
tests/isolation.test.js | 标记策略、目录分配、两个接缝包装、状态缓存、服务 API,以及用桩件驱动的 HTTP 路由 |
tests/integration.test.js | 真实的 dsh-storage → dsh-storage-domain → dsh-workspace 栈加真实 dsh-tools:会话创建、并发会话、成员归属、同一介质上的重启、开关切换 |
tests/tool.test.js | session_isolation 工具对外暴露的 JSON Schema |
tests/client.test.js | 浏览器半边的模块表交接与 slot 声明等待 |
tests/integration.test.js 用绝对路径导入某个 DSH 检出里的构建产物(文件顶部的 CHECKOUT 常量),
检出位置变化时改它即可。
index.js Cordis 插件:接缝包装、服务、工具、可选 Web 路由
lib/isolate.js 标记策略与会话目录分配
lib/state.js 隔离状态缓存:标记扫描、重定向守卫、串行化
lib/patch.js 两个接缝包装 + ctx.isolatedSessions 服务
lib/tool.js session_isolation 模型工具
lib/web.js GUI 页面背后的两个 HTTP 路由
lib/client.js 浏览器半边(设置 → 内置插件 页面)
cordis.patch.yml 插入插件行的 bundle patch