DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

mabaoguo9527 /

mabaoguo9527/dsh-file-explorer

Verified

Workspace file explorer docked in the DeepSeek Harness sidebar — lazy file tree, Settings toggle, drag-to-resize. Also shipped as a single-session Cordis dynamic plugin.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHubProject homepage
READMESource: main@a6165e49

dsh-plugin-file-explorer

停靠在 DeepSeek Harness 侧边栏里的工作目录文件浏览器。

License: MIT DeepSeek Harness Node Plugin form

English | 中文

它把当前会话工作目录的懒加载目录树放在侧边栏左下角——就在 设置 那一行的正上方—— 自带筛选、刷新、设置 → 通用里的开关,以及可拖动的高度边缘。宽度跟随侧边栏列。

文件浏览器停靠在侧边栏脚部

面板停靠在侧边栏脚部,位于 Cordis Plugin 与 设置 上方。宽度与侧边栏一致,高度可拖动上边缘调整。

┌──────────────────────────────┐
│  新会话                       │
│  工作区                 ⌕ ⋯   │
│  ▾ DeepseekWorkSpace         │
│      会话一             2m    │
│      会话二             4d    │
│                              │
│ ──────────────────────────── │  ← 拖动这条边调整高度
│  ▸  文件浏览器            ↻  │
│  筛选文件…                    │
│  ▾ 📁 src                    │
│    ▸ 📁 client               │
│      📄 index.js             │
│      📄 package.json         │
│  ▸ 📁 test                   │
│ ──────────────────────────── │
│  Cordis Plugin     1 running │
│  ⚙ 设置                      │
└──────────────────────────────┘

目录

  • 功能
  • 前置要求
  • 两种安装方式
  • 作为 profile 插件安装
  • 作为动态插件安装
  • 使用方式
  • 配置
  • 安全
  • 实现原理
  • HTTP 接口
  • 兼容性
  • 疑难排查
  • 卸载
  • 开发
  • 参与贡献
  • 许可证

功能

停靠而非浮窗 渲染在侧边栏自己的脚部座位上,使用侧边栏的底色——没有卡片、边框、圆角或阴影,看起来就是这一列的一部分。
宽度跟随侧边栏 面板测量座位容器,并用 ResizeObserver 跟踪变化,所以拖动侧边栏边缘时面板始终对齐。
拖动调整高度 上边缘是一条 8px 拖拽热区(ns-resize)。默认高度约为侧边栏的一半,并被限制在脚部分区以上的空间内,标题行永远不会被裁掉。
懒加载目录树 只读取你展开的层级。目录优先排序,展开状态在重渲染后保留,筛选框按名称过滤已加载层级。
内存态开关 设置 → 通用 → 文件浏览器。面板标题行左侧的箭头是同一个开关的快捷方式——它把面板收起到只剩标题行。
只读且有围栏 宿主半只列目录项,拒绝会话工作目录之外的任何路径,每次列举上限 800 项。
双语 通过 ctx.locale 注册英文与简体中文字典,面板跟随界面语言。
感知收起态 侧边栏收起成 56px 竖条时,面板完全不渲染。
安装无需构建 lib/ 以构建产物形式提交,git 依赖可以直接用。

前置要求

  • 带 Web GUI 的 DeepSeek Harness(dsh web)。开发与验证基于 @deepseek-ai/dsh 0.1.5-rc.1 及其自带的 web profile。
  • Node.js ≥ 20(宿主半使用了全局 URL 与 Buffer.byteLength)。
  • 宿主需要提供 fs 与 webServer 服务——两者都属于标准 Web profile。
  • 本插件不使用网络、不需要 API Key、不读取任何凭据。

两种安装方式

profile 插件 动态插件
形态 真实的 npm 风格包,作为 composition 的一行挂载 两段纯 JS,通过 Cordis 工具加载
生命周期 跨重启存活,属于你的 profile 只存在于当前 DSH 进程
安装 一条命令 —— dsh plugin --profile web add …(自动激活) 让你的 agent 加载 dynamic-plugin/
是否改动 profile 是 否
适用 日常使用 单个会话里先试试

作为 profile 插件安装

1. 把包装进 profile

CLI 会把 profile 名之后的参数原样转发给 profile 目录里的 pnpm,而插件的模块解析正是 从这个 profile 目录出发的:

# 直接从 Git 仓库安装(无需发布 npm,lib/ 已是构建产物):
dsh plugin --profile web add github:mabaoguo9527/dsh-file-explorer

# 固定某个发布版本:
dsh plugin --profile web add github:mabaoguo9527/dsh-file-explorer#v0.1.2

# 或者从本地检出安装——开发时用这个:
dsh plugin --profile web add /absolute/path/to/dsh-file-explorer

2. 重启 Web UI

安装就到这里。本包声明了 dsh.bundle,所以 CLI 会自动把它追加到 dsh.profile.bundles(profile 的有序层列表),并把它的 cordis.patch.yml 作为一个层应用:

// $DSH_HOME/profiles/web/package.json,由 `dsh plugin add` 写入
"dsh": {
  "profile": {
    "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "dsh-plugin-file-explorer"],
    "patchReload": "live"
  }
}

层列表在启动时读取,所以重启一次 dsh web。想在不启动服务的前提下检查组合结果:

dsh --profile web --dump-config | grep -A2 dsh-plugin-file-explorer

备选:自己挂载 composition 行

如果你不想重启,或者想在正式安装前先评估,同一行也可以作为你自己掌控的 patch 层应用。 仓库已经把它放在 cordis.patch.yml 里:

- insert:
    - id: file-explorer
      name: 'dsh-plugin-file-explorer'

先不改任何文件试一次,把该文件当作额外的 patch 层传入:

dsh --profile web --patch ./node_modules/dsh-plugin-file-explorer/cordis.patch.yml

不走 bundle 路线时长期生效:把这条 entry 合并进 profile 本来就会加载的 patch 文件 $DSH_HOME/profiles/web/cordis.patch.yml($DSH_HOME 默认是 ~/.dsh)。该文件默认内容 是空数组 [],所以多数情况下直接整体替换;如果里面已经有内容,就把这个 - insert: 条目追加到既有的顶层数组里,而不要新增一个 YAML 文档。

patch 文件是被监听的(自定义 profile 默认 patchReload: live),所以这条路线无需重启: 刷新浏览器页面即可。

两条路线只能选一条。既手动挂载了这一行、又把包留在 dsh.profile.bundles 里,会导致同一个 row id 被插入两次。

验证

打开 GUI:面板出现在 设置 行上方,设置 → 通用里会多出一个 文件浏览器 开关。 想不用 UI 确认组合结果:

dsh --profile web --dump-config | grep -A2 dsh-plugin-file-explorer

作为动态插件安装

dynamic-plugin/ 用单会话的 Cordis 动态插件实现了同样的功能: host.js 与 client.js,都是纯 JavaScript,不需要包、不需要 composition 行、不改 profile。

把这两个文件交给 DSH agent,让它定义并运行插件即可——agent 会用两半代码调用 cordis_define,然后调用 cordis_run。面板立刻出现;DSH 进程重启后消失,因为动态插件 是进程内的。具体提示词与和 profile 插件的差异见 dynamic-plugin/README.md。

使用方式

操作 结果
点击目录行 展开或收起该层,首次展开时才读取
点击文件行 选中它,底部显示它相对工作目录的路径
筛选文件… 按名称过滤已加载的层级;目录仍可继续展开
↻ 重新读取根目录以及所有已展开的层级
标题行的箭头 把面板收起到只剩标题行,或再次展开
拖动上边缘 调整高度;不会超过侧边栏脚部,也不会低于 120px
设置 → 通用 → 文件浏览器 整块开关

标题行显示工作目录的末级目录名,悬停可看到绝对路径。

面板在运行中的应用里就是这样一块:面板在运行中的应用里

配置

插件不接受任何插件配置。它唯一的偏好就是那个开关,而且该状态刻意保持在内存中: 它存在于插件 fiber 里,插件(重新)加载时重置为开,不会写入 settings.yaml。动态插件 本身就是进程内的,而一个布尔值不值得单开一个 settings 命名空间,所以这个开关是有意做成 会话级的。

高度同理,保存在内存中,重新加载后回到默认高度。

安全

宿主半之所以存在,是因为浏览器读不到 harness 的文件系统。它的全部对外面就是一个只读路由, 限制都是刻意设计的:

  • 收容校验:每个请求都会先把会话工作目录解析为根,再对解析后的目标做 fs.contains(root, target) 判断,因此逃逸出根的 .. 段与符号链接会被 400 拒绝, 而不是被渲染出来。
  • 只读元数据:只使用 fs.stat 与 fs.listDir,从不读取文件内容,也没有任何写入、 重命名、移动或删除的接口。
  • 有上界:一次列举最多返回 800 项。
  • 默认本地:路由由 harness 的 Web 服务器提供,因此继承该服务器的绑定地址与可信主机 策略。如果你把 GUI 绑定到局域网网卡,那么任何能访问 GUI 的人都能列举工作目录内的目录。 除非你确实需要,否则请绑定回环地址。
  • 不碰凭据:插件从不访问 ctx.credentials,也不发起任何对外网络请求。

实现原理

一个功能,两半实现。

宿主半 —— lib/index.js。 一个 Cordis 插件(apply(ctx) + inject = ['webServer', 'fs']),注册一个 exact 路由 /dsh-file-explorer/tree。 处理函数通过组合出来的 fs 服务解析目标目录、执行上面的收容与数量限制,然后返回 JSON。 注册放在 ctx.effect(...) 里,所以卸载插件时会释放该路径,而不是留下悬空的处理函数。

浏览器半 —— lib/client.js。 以客户端模块系统从 exports["./client"] 提供的 构建产物格式发布,也就是交给 window.__ModuleLoader__.load({ id, factory }) 的一个工厂函数。它只请求基线模块 react,这也是 dsh.client.external 为空、完全不需要打包器的原因。它导出的 inject = ['slots', 'locale'] 让 Cordis 在 apply 执行前先等待座位注册表与字典注册表。

它坐在哪里。 两个增量座位,都是 replaceRisk: none:

座位 用途 注册
sidebar.footer.action 侧边栏脚部、设置 上方那一行 id: dsh-file-explorer、order: -100
settings.general.item 设置 → 通用 里的一个偏好行 id: dsh-file-explorer、order: 30

为什么面板用绝对定位而不是普通流。 sidebar.footer.action 是一个横向 flex 行,而 内置占用者(Cordis Plugin 触发器)是 flex: none; width: 100%——它独占整行。于是任何 在流内的兄弟条目都会被压成精确的 0 宽度:高度保留(留下一片空白),但每个子元素都被 挤成只剩自己的内边距并被裁掉。因此这里的条目是一个零尺寸 flex 项,只作为定位锚点, 面板本身脱离该行绝对定位:

  • left: calc(-1 * var(--dsh-sidebar-inline-padding)) 贴到侧边栏列的左边缘;
  • 锚点上的 bottom: 0 就是脚部分区的顶部边缘,于是面板向上生长,正好落在内置触发器 上方,不需要任何写死的偏移;
  • 该列的 overflow: hidden 会把面板裁在侧边栏内,因此它永远不会溢到会话栏。

宽度。 侧边栏不向座位条目暴露宽度——既没有对应的 slot prop,也没有 CSS 变量。因此面板 从自己的节点向外找第一个有真实盒子的祖先(座位容器),在其宽度上左右各补一次继承来的 --dsh-sidebar-inline-padding,并用 ResizeObserver 跟踪变化。以上任何一步失败时,都会 回退到 CSS 里的固定宽度,面板照常渲染。

高度。 上边缘使用 pointer capture 拖拽。拖拽的起始状态放在插件闭包里而不是组件内, 因为 React 每次渲染都会重建组件内的绑定,否则拖到一半的拖拽会被重置。

HTTP 接口

浏览器半使用这个路由;它足够稳定,可以直接脚本化调用。

GET /dsh-file-explorer/tree?base=<绝对路径>&path=<相对 base 的路径>

base 是会话工作目录(. 表示回退到文件系统后端自己的默认值)。path 相对 base; . 表示 base 本身。

// 200
{
  "path": "/Users/you/project/src",     // 解析后的绝对目录
  "entries": [
    { "name": "client",       "type": "directory", "size": null },
    { "name": "package.json", "type": "file",      "size": 812 }
  ]
}

type 取值 file、directory、other。后端不上报大小时 size 为 null。错误返回 { "error": "<消息>" },状态码为 400(超出工作目录、不是目录)、404(不存在)、 405(方法不允许)或 500(后端失败)。

兼容性

  • 基于 @deepseek-ai/dsh 0.1.5-rc.1(Web profile)构建与验证。
  • 只使用有文档的接缝:fs 与 webServer 服务、slots 与 locale 客户端服务、 sidebar.footer.action 与 settings.general.item 座位、dsh.client 包声明,以及 用于清理的 ctx.effect。
  • 唯一的结构性假设是上面描述的宽度测量。它写得比较防御,失败时退化为固定宽度而不是报错; 但如果未来版本改变了侧边栏的 DOM 嵌套,请预期退化为回退宽度。
  • 主题颜色都取自已有的 Design Token 变量(--dsw-specific-sidebar-fill、--dsw-alias-*), 因此浅色/深色主题自动跟随,无需额外处理。

疑难排查

面板不见了。 按顺序检查:composition 行是否存在(dsh --profile web --dump-config);侧边栏是否被收起成 56px 竖条;设置 → 通用 → 文件浏览器 开关是否为开(重新加载插件会把它恢复为开)。

面板在,但是空的并带一行错误。 错误文本就是宿主半原样返回的内容。path is outside the working directory 通常意味着会话的 工作目录在面板打开期间变了——按 ↻。path not found 意味着该目录被移动或删除了。

重启 dsh web 后面板消失了。 动态插件方式下这是预期行为:动态插件是进程内的。想跨重启保留,请按 profile 插件方式安装。

主题看起来不对。 面板使用与侧边栏本身相同的表面色与文本色 token。自定义主题若重定义了它们,面板会跟随;如果 发现某个 token 缺失,请提 issue。

拖动时感觉卡住。 拖拽使用 pointer capture,所以拖到面板外也会继续。若浏览器丢掉了 capture,松手重新拖即可; 高度在每次 move 时都已提交。

卸载

# 1. 从 $DSH_HOME/profiles/web/cordis.patch.yml 中删掉该 `- insert:` 条目(或整块)
# 2. 移除包
dsh plugin --profile web remove dsh-plugin-file-explorer
# 3. 若 profile 使用 patchReload: startup,重启 dsh web

动态插件方式则在侧边栏 设置 上方的 Cordis 面板里停止或移除该插件。

开发

dsh-file-explorer/
├── lib/
│   ├── index.js          # 宿主半 —— 目录列举路由
│   └── client.js         # 浏览器半 —— 停靠面板与设置行
├── cordis.patch.yml      # 挂载插件的 composition 行
├── dynamic-plugin/       # 同一功能的单会话动态插件版
├── test/                 # node:test 冒烟测试(不需要浏览器)
└── .github/workflows/    # CI:语法检查 + 测试

没有构建步骤:lib/client.js 本身就是 bundle,直接按客户端模块系统提供的格式编写。 检查命令:

npm run check   # 对两半执行 node --check
npm test        # node:test 冒烟测试

验证状态

  • 动态插件版本已在 0.1.5-rc.1 的真实 Web GUI 中端到端使用过:加载目录、展开、 筛选、设置开关、宽度跟随与拖动调整高度。

  • profile 插件包由 npm test 覆盖——宿主半针对真实临时目录运行(列举、越界拒绝、 404、405),浏览器 bundle 会被实际求值并断言其两处注册;另外还把 package.json 与内置 dsh.client 解析器的规则做了交叉核对(dsh-client-modules:platform 必须是 字符串、Loader row 名必须是裸包名、exports["./client"] 必须是字符串或 { default: string }、 不得声明 external 请求)。

  • profile 插件包已在隔离实例(独立 DSH_HOME)上、对本包进入 dsh.profile.bundles 的真实服务端做了端到端验证:组合树里有 file-explorer 行; 路由返回 200 与正确目录列表(越界路径 400、不存在 404、非 GET 405); 浏览器 bundle 由 /plugins/??dsh-plugin-file-explorer/client.js 正常提供。 要对活的 GUI 迭代开发,把本地检出作为本地依赖安装 (dsh plugin --profile web add /absolute/path/to/dsh-file-explorer),启动命令里保留 --patch,改完 lib/client.js 后刷新页面即可。

参与贡献

欢迎提 issue 与 PR。请保持本插件赖以成立的两条不变量:浏览器半除了基线 react 模块之外 不得依赖任何东西;宿主半必须严格只读,并且被限制在工作目录内。提交 PR 前请先跑 npm run check && npm test。

如果你愿意为这个 README 补一张真实会话里的面板截图,非常欢迎。

许可证

MIT © 2026 mabaoguo9527

致谢

  • DeepSeek Harness 以及本插件所接入的 Cordis 插件框架。
  • 插件遵循 harness 自身的约定——apply(ctx) 模块、基于座位的 UI 注册、ctx.locale 字典、ctx.effect 清理——参见官方教程 Your first plugin。
  • 面板样式对齐内置工作区浏览器(ui-workspace)的度量:36px 分区标题行、28px 圆形图标按钮、 28px 行高与 8px 圆角。
—/ 5

No ratings yet

Verified DSH bundle

Commit a6165e49a605

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout