# dsh-multi-root-explorer(工作区资源管理器) 给 [DeepSeek Harness](https://github.com/anywhere-labs/dsh-desktop) 用的工作区浏览器:它接管左侧栏的工作区区域,换成两个选项卡——**对话**按每个会话真实的工作目录归入目录树,**资源**是一个只读的文件树,点文件用 DSH 原生右侧栏预览。 npm 包名是 `dsh-workspace-explorer`;本仓库由它改名而来,因此已有安装不受影响。 中文 | [English](README.md) ![插件在侧边栏里的样子:已登记的工作区展开为带会话数量的文件夹,上方是「对话 / 资源」选项卡,以及小眼睛、刷新和设置按钮](assets/sidebar.png) > **状态:预览版。** 自动化测试全部通过,但尚未在所有 DSH 版本上做完整实机验收。安装前请先看[已知限制](#已知限制)。 ## 它能做什么 - **固定多根目录。** 你导入的目录就是根,不会随着会话增减被重算成某个公共祖先。 - **会话变成目录树。** 每个会话按它真实的 `cwd` 归位:当前目录的直接会话排在前面,子文件夹排在后面,逐层递归。`cwd` 在根目录之外的历史会话保留独立入口;没有 `cwd` 的会话单独一组。 - **资源只读浏览。** 目录按层加载并分页,打开一个大盘不会先递归扫全盘。点文件交给 DSH 自己的右侧栏预览,主栏对话不受影响。 - **两个选项卡各自独立的隐藏,且完全可逆。** 点标题栏的小眼睛进入选择模式:已隐藏的项会显示出来并处于选中状态,点圆圈改选中/未选中,再点一次小眼睛就只隐藏最终选中的那些。隐藏只影响显示,不动任何文件、目录或会话记录。 - **每个文件夹都有操作菜单。** 右键或右端 `···` 可以在该目录里开启新会话、在系统资源管理器中打开;资源还支持复制绝对路径。 - **可以把文件/文件夹拖出去。** 资源行是标准拖拽源,携带 `text/plain`、`text/uri-list`、`application/x-dsh-workspace-path`,效果固定为 `copy`。 - **支持原生归档。** 归档/取消归档走 DSH 自己的服务,并且和「永久删除」明确分开。 - **卸载后不会丢对话。** 插件运行期间会把有普通会话的目录登记为 DSH 原生工作区,所以停用或卸载插件后,你的对话仍然能在原生界面找到。 ## 环境要求 - DeepSeek Harness `>= 0.2.0-rc.1`(声明在 `dsh.engines.dsh`)。 - 浏览器需支持 **Popover API**(`element.showPopover`)。菜单和悬停卡渲染在顶层;不支持时插件会明确报错并关闭菜单,而不是把菜单放错位置。 - 通过回环地址访问 DSH。插件的 HTTP 路由会拒绝非回环 socket、非回环 `Host` 头和跨站请求。 插件**没有任何运行时依赖**,只用 DSH 客户端模块加载器已经提供的 `react`。 ## 安装 任选一种,最后都需要**重启 Host(或重启应用)**并刷新页面。 ### 1. 插件市场(最省事) 如果它已在社区目录中上架:打开**设置 → 插件市场**,搜索 `dsh-workspace-explorer`,一键安装。市场会替你改好 profile 并提示何时重启。 ### 2. 命令行 ```sh dsh plugin --profile <你的 profile 名> add dsh-workspace-explorer ``` 想直接从本仓库装: ```sh dsh plugin --profile <你的 profile 名> add git+https://github.com/Nethur-auro/dsh-multi-root-explorer.git ``` 把 `<你的 profile 名>` 换成你自己的(常见的是 `web` 和 `desktop`),然后重启 `dsh web`。 ### 3. 手动改 profile 插件管理器本质上就是改你 profile 的 `$DSH_HOME/profiles/<你的 profile 名>/package.json`(默认 `~/.dsh`)。加依赖和 bundle 条目: ```json { "dependencies": { "dsh-workspace-explorer": "git+https://github.com/Nethur-auro/dsh-multi-root-explorer.git" }, "dsh": { "profile": { "bundles": ["dsh-workspace-explorer"] } } } ``` 然后在该目录里 `pnpm install`,再重启 DSH。手动做和插件管理器做的是同一件事,所以除非有特别理由,优先用方式 1 或 2。 ### 怎么确认装上了 看左侧栏:工作区区域应该出现**对话 / Conversations**和**资源 / Resources**两个选项卡,并且有插件自己的标题栏。如果看起来没变化,说明 Host 还没加载新代码——**重启 DSH,而不是只刷新页面**,Host 路由不支持热重载。 ## 使用 1. 点「添加工作区」选目录,需要几个根就加几个。 2. 「对话」页显示文件夹和会话;「资源」页显示文件夹和文件。目录第一次展开时才加载该层。 3. 点文件用右侧栏预览。预览需要一个可用会话作为授权上下文。 4. 文件夹右键或 `···`:在该目录开启新会话、在系统资源管理器中打开、(资源)复制路径。 5. 会话右键:打开、重命名、归档、永久删除。永久删除有风险,见[安全与永久删除](#安全与永久删除)。 6. 用**小眼睛**按选项卡隐藏条目;被你隐藏的东西都能在**设置**里恢复显示。 7. 「显示隐藏项」额外显示点开头的名字、`node_modules` 和 `dsh-acl-recovery`——它们默认是隐藏的。 ## 数据与隐私 插件自己的数据都在 `$DSH_HOME/storages/workspace-explorer/` 下: - `config.json`——版本 3:根目录、目录别名、两份互相独立的隐藏列表、展开状态和偏好。v1/v2 在迁移前会先备份;文件损坏或版本比程序新时会拒绝覆盖,而不是静默替换成空配置。 - `native-bridge.json`——插件自己的幂等与进度记录。它只是记账,**不是**你的会话或原生工作区状态的副本。 写入是串行化 + 原子替换的。移除 bundle 不会删掉这个目录;要清理请先确认没有待完成的新会话操作,并且**绝不要**把删除 DSH 会话数据当成插件清理的一部分。 资源浏览从头到尾都是只读的:插件不会在你的工作区里创建、写入、移动或删除任何文件。请求受注册根、`realpath` 边界(含中间链接)和回环/同源校验限制。 ## 安全与永久删除 **永久删除是一个早就存在、被刻意隔离的功能,而且不可恢复。** 目标 DSH 版本没有官方会话删除接口,所以那一个动作会直接移除会话自己的持久化产物。它依赖具体版本的存储结构,不是 DSH 支持的接口,应当只当作最后手段。请优先使用归档。 停用插件、卸载插件或清除配置,都**不会**触发这条代码路径。 ## 已知限制 - **自动化测试是契约测试,不是实机验收。** 它们用 mock,不碰你的真实会话。「已命名但未发出」的会话跨新 Host 进程是否恢复、卸载后原生列表是否立刻可见、不同缩放下浮层位置是否正确,都还需要在你的版本上验证。不要在正在工作的 profile 上强制退出做持久化测试。 - **拖拽只是提供标准载荷,不保证一定被接收。** 树会写入标准 `DataTransfer` 格式;某个 DSH 版本是否把它变成对话引用或附件,这里没有验证过,插件也不会调用任何未公开的对话接口去强推。 - **选择范围限于已加载的内容。** 资源树是分页懒加载的,父文件夹的「全选」作用于客户端已经加载的后代。树特别大时请先展开或「加载更多」。 - **菜单需要 Popover API。** 浏览器不支持时,插件会给出明确错误提示,而不是显示一个位置错乱的菜单。 - **改源码不等于改了运行中的 Host。** 在本仓库改文件不会更新正在运行的 DSH;Host 路由需要重启。 ## 测试 无依赖、无需安装: ```sh node test/run.mjs # 或 npm test ``` 运行器会找出所有 `*.test.mjs` 和 `selftest.mjs`,用同一个 Node 可执行文件依次跑完。覆盖范围:目录列举与分页、配置与迁移、路由族与信任栅栏、对话树模型、隐藏作用域、拖拽载荷编码、原生桥接的创建/重命名/保存契约。 ## 设计文档 - [缺陷修复方案与验收矩阵](docs/bugfix-design-2026-10-01.md) - [树顺序与分栏独立隐藏设计](docs/tree-visibility-design-2026-10-02.md) ## 许可 [MIT](LICENSE) © 2026 Nethur-auro