DeepSeek Harness Hub
← 返回列表

KaguraSayuki/dsh-file-download

DeepSeek Harnessspec-screened在 GitHub 查看 ↗
未验证

给 DeepSeek HarnessDSHWeb GUI 加上文件浏览器、下载面与按模式放行的上传:浏览运行…

尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/16 · 已提供中文文档

DSH Web GUI 的已认证浏览器下载:从官方交付物行、已交付文件卡片和侧边栏文件树将会话工作区文件流式传输到浏览器。远程友好,无需主机桌面。

综合分
29.9
GitHub 分
29.9
用户评分
★ Stars
0
周下载量
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add KaguraSayuki/dsh-file-download
该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/17(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

dsh-file-download

给 DeepSeek Harness(DSH)Web GUI 加上文件浏览器、下载面与按模式放行的上传:浏览运行 agent 的那台机器(包括会话工作区之外的一切)、下载单个文件、把文件夹打包成 ZIP、把多选条目一次性打包下载,并上传文件(覆盖前会询问)。它通过官方页签注册接口扩展官方侧边栏,而不是改 DSH。

license
DSH

为什么需要它

DSH 的原生文件动作是宿主侧的:「用默认程序打开」「在文件管理器中显示」都需要运行 agent 的那台机器上有桌面环境。可当 Web GUI 是从另一台设备打开的——手机、平板、局域网里的笔记本,或者反代后面的无头云主机——那个桌面要么不存在,要么根本不是正在看页面的这台设备。文件在服务器上,浏览器却存不下来。

官方侧边栏的文件树还被限制在会话工作区内,而且完全没法把文件夹交出来。本插件补上缺失的"读"这一面:一个从工作区出发、但能沿任意绝对路径走遍该文件系统可读范围的浏览器,外加文件下载与文件夹归档。

它加了什么

| 界面 | 入口 |
|---|---|
| 官方右侧栏 | 通过 ctx.sidebarRightTabs 注册的 文件 页签:从项目根一路走到 /、筛选当前目录、显示隐藏项、在官方预览里打开文件、复制路径、把路径插入输入框、下载文件、把文件夹打包成 ZIP、勾选多项一起打包,以及上传文件(覆盖已有文件前会询问) |
| 每个有交付或改动的收尾回合 | 一个「下载本轮文件」下拉,外加一个打开服务端渲染浏览页的入口。原有的「打开」行为完全不变 |
| 每张交付文件卡片 | 在卡片原有的分体「打开」控件旁多一个下载段。左侧按钮依旧默认走打开 |
| 侧边栏文件树每一行 | 悬停时出现的下载按钮:文件本身,或目录的流式 ZIP |
| 任意设备、无需客户端 JS | 服务端渲染的浏览页(/api/workspace.download/browse),带面包屑、逐文件下载与逐目录 ZIP。页面不带任何脚本,所以从不打开完整 GUI 的手机也能取到文件 |

单个下载的字节不经过 JavaScript:每个动作都是把一个同源 URL 交给浏览器自己的下载管理器。多选则用隐藏表单 POST,让浏览器把一个 ZIP 直接流到磁盘,而不是先在页面内存里缓冲。

安装

要求:DSH 0.1.5 线的 0.1.5-rc.1 或更高、Node 20+、web profile。

本地检出
dsh plugin --profile web add /path/to/dsh-file-download

或直接从 GitHub 安装
dsh plugin --profile web add github:KaguraSayuki/dsh-file-download

dsh plugin add 会把包装进 profile 并追加到 dsh.profile.bundles。之后重启 dsh web:客户端 roster 是启动时扫描的。

卸载:

dsh plugin --profile web remove dsh-file-download

使用

打开右侧栏,在页签引导里选 文件。

- 项目 跳到会话工作区根目录,根目录 跳到 /。面包屑行同时就是路径框:点行尾的铅笔进入输入,输入的内容默认相对当前所在目录解析,以 / 开头按绝对路径、以 ~ 开头按宿主主目录。输入时实时给出补全:方向键选择、Tab 采用、回车前往。
- 显示隐藏项 打开点开头的文件(默认隐藏)。筛选框在当前目录内收窄结果,不会跳走。
- 点文件夹进入;点文件在官方预览页签里打开。
- 行内动作:预览、下载、压缩下载(目录)、复制路径、插入路径到输入框。工作区内的路径插入为 @相对路径 引用,工作区外插入绝对路径。
- 勾选多行后,底栏提供 打包下载所选,把选中的文件与整个文件夹打成同一个 ZIP 流。

工作原理

整个插件是一条双面 Cordis 行,没有构建步骤,也没有运行时依赖。

宿主路由

所有路由都注册在 Connection 的精确 Fetch 表上,因此都继承与其他 /api 完全相同的 Host/Origin 与浏览器会话检查;没有一条是无认证侧门。

| 路由 | 作用 |
|---|---|
| GET /api/workspace.download/list?sessionId=&path= | 任意可读目录的 JSON 列表:{ path, parent, workspaceRoot, entries, truncated } |
| GET\|HEAD /api/workspace.download?sessionId=&path= | 单个普通文件,按 256 KiB 窗口流式返回,带 Content-Disposition: attachment、ETag,以及 Range / If-Range 支持,浏览器的下载管理器据此续传 |
| GET /api/workspace.download/browse?sessionId=&path= | 无脚本的 HTML 列表页,带面包屑与逐条目的下载/ZIP 链接 |
| GET\|HEAD /api/workspace.download/archive?sessionId=&path= | 一个目录子树,流式打包为 ZIP |
| POST /api/workspace.download/archive | 多选内容打包成一个 ZIP;接受 JSON,或名为 payload 的表单字段 |
| POST /api/workspace.download/upload?sessionId=&path=&name=&mode=&overwrite= | 把一个文件上传进指定目录;请求体就是文件字节 |

path 缺失或为空表示会话工作区根目录。相对路径以它为基准解析;绝对路径可以离开它。

访问模式

读取永远不受限:任何模式下,浏览、预览、下载、打包都能到达服务账号可读的每个文件夹。模式只约束上传;默认取会话自身的沙箱策略,并可在浏览器自己的选择器里更改。

| 模式 | 浏览 / 预览 / 下载 / ZIP | 上传 |
|---|---|---|
| read-only 只读 | 全部文件夹 | 拒绝 |
| workspace-write 工作区写入 | 全部文件夹 | 仅会话工作区 |
| danger-full-access 完全访问 | 全部文件夹 | 账号可写的任意位置 |

浏览器以会话解析出的模式为起点,并把每个会话的覆盖选择记在 localStorage;两者不一致时工具栏会标出来。模式绝不只由客户端把关——宿主在每次上传时重新解析模式并重新做包含性检查。

上传体积上限 64 MiB。文本请求体走组合文件系统的原子 writeText,因此后端会再施加一次它自己的沙箱策略;二进制请求体在该契约里没有字节写入口,会在目标旁边经临时文件 + rename 落盘,这对"后端执行世界就是本机"(本地 provider)是成立的。不带 overwrite=1 时,已存在的文件会以 409 拒绝,交给浏览器询问。

浏览器界面

文件页签是通过 ctx.sidebarRightTabs 注册的真实页签类型,页签体注册在 keyed 的 sidebar.right.pane.tab slot 上——和官方「文件」页签用的是同一套扩展点,因此没有改动任何官方代码。它是页面类型,所以不会与预览争抢文件地址。

回合下拉是官方 conversation.chat.turnTail 链式 slot 的占用者,读取的是官方交付行读取的同一份 Turn 数据,因此两份列表不可能不一致。

交付卡片与文件树按钮是对官方已发布钩子(data-presented-file、data-files-entry、data-files-path)的 DOM 装饰。每个注入节点都追加在容器末尾,而不是插进 React 管理的兄弟节点之间;MutationObserver 会在重渲染后重新施加装饰。卸载插件会移除全部注入节点。

传输、续传与压缩

文件按 256 KiB 一个窗口读取。路由对 Range 请求回 206 与请求的那段字节,带上由文件版本与大小派生的 ETag,并尊重 If-Range:若文件已变,续传会被拒绝并改发完整文件。这正是浏览器自带下载管理器能续传的原因——客户端不需要自己实现。

ZIP 直接基于 Node 的 zlib 手写:每条目一个 local record,条目内容按窗口直接流入 raw deflate 流,再用 data descriptor 收尾,最后是中央目录与结束记录。全程不缓冲整个文件,所以无论单个条目多大,归档内存都是一个 deflate 窗口加一个读窗口。条目按 zlib 的常规默认等级压缩。本已压缩的格式(图片、视频、音频、压缩包、Office 三件套、PDF)直接 store;其余条目先用 level 1 对首窗口试压一次,因此未知的二进制格式若压不动就直接 store,不会白烧 level 9 反而变大。归档响应声明 Accept-Ranges: none,因为现场生成的 ZIP 没有可用于续传的稳定字节身份。

安全

本插件能读 运行 dsh 的操作系统账号可读的任意文件,而不只是会话工作区。这是有意为之——无头主机必须能把工作区旁边的文件交出来——但它比官方文件树的能力更宽,应当是一个自觉的选择。

- 需要认证,不是匿名。 包括上传在内的每条路由都在 Connection 的栅栏之后,和 /api/remote.mux 一样。未认证的访问者无法列举、抓取或写入任何内容。
- 写操作受模式约束且有边界。 唯一的写动作是把一个文件上传进一个已存在的目录。没有重命名、删除、移动、原地编辑、改权限,也没有 shell。read-only 拒绝一切写入;workspace-write 把写入限制在工作区内;danger-full-access 允许任意可写位置。宿主每次请求都重新检查,客户端从不作为唯一防线。
- 不回显路径。 错误体是固定的短字符串;解析后的路径或堆栈永不离开进程。
- 不带脚本。 浏览页不含 JavaScript 与任何外部资源;响应带 default-src 'none'、frame-ancestors 'none' 与 Referrer-Policy: no-referrer。所有插值的名称都做 HTML 转义。
- 有上限。 列表 5000 条、归档 20000 条 / 总计 2 GiB、多选 200 条、单次上传 64 MiB;超限直接拒绝而不是静默截断。单个归档条目没有单独上限——它是流式写入的,不缓冲。
- 仍需要会话。 请求必须指名一个真实会话;它的工作区根目录是默认目录。

如果这个读取范围对某个部署来说太宽,就不要在那里安装本插件,或者把 dsh web 绑在回环地址并放在带认证的反向代理之后。卸载插件即移除该能力。

兼容性

DSH 是移动靶,所以本插件组合的每个钩子都由 contract 测试针对 profile 实际运行的 DSH 安装重新核对:

DSH_MODULES=/path/to/node_modules npm run contract

当前对 DSH 0.1.5-rc.1 全部通过。升级 DSH 后跑一次 npm test;变红的那一项会直接点出移动了的上游钩子。

开发

npm test          # 静态校验 + 单元测试 + 上游 contract
npm run validate  # 只查 manifest、patch 与 bundle 接线
npm run unit      # 只跑单元测试(宿主路由 + 浏览器半区)
npm run contract  # 只跑上游钩子 contract

目录:

| 路径 | 作用 |
|---|---|
| lib/index.js | 宿主半区:会话作用域解析、ctx.fs 读取、JSON 列表、流式下载、HTML 浏览页、ZIP 写入器、失败映射 |
| lib/client.js | 浏览器半区:侧边栏页签类型与页签体、回合下拉、DOM 装饰 |
| cordis.patch.yml | 本 bundle 挂载的唯一一条双面行 |
| tests/host.test.mjs | 用假 ctx.fs 验证路由行为,含 ZIP 结构解析 |
| tests/client.test.mjs | 浏览器半区:模块加载注册、页签注册、URL 与地址构造、装饰过程 |
| tests/validate.mjs | 静态接线检查 |
| tests/contract.mjs | 上游钩子检查 |
| tests/hygiene.test.mjs | 对已跟踪文件的泄漏防护 |

浏览器半区是手写的 window.__ModuleLoader__.load({ id, factory }) 形态,只依赖平台种子 react 与 react/jsx-runtime。没有打包器、没有 prepare 脚本、没有需要安装的依赖。

已知边界

- 写入口刻意很窄。 上传只能往已存在的目录里加文件;不能建目录、重命名、移动、删除或原地编辑。建目录请交给 agent 或 shell 里的 mkdir。
- 续传由浏览器负责。 路由提供 Range,下载管理器据此续传;不用它的客户端就从头再来。现场生成的 ZIP 无法续传,因为两次请求之间没有稳定的字节身份。
- 归档上限是硬限制。 超过 20000 条或总计 2 GiB 的目录会被拒绝,而不是流式输出。总量上限是为了让所有偏移落在当前写出的 32 位字段内,这也是不需要 ZIP64 记录的原因;要抬高它就得实现 ZIP64。
- 符号链接与特殊文件会被跳过,列举与归档都不跟随。
- 二进制上传假定本地执行世界。 文本上传始终走组合文件系统;二进制上传由宿主进程写盘,因此如果后端执行世界不是本机(远程工作区),文本上传仍正确、二进制会上传到错误的位置。
- 全部下载是一串并发下载。 一轮文件很多时浏览器可能询问权限或拦掉多余的下载;逐个下载始终可靠。
- 回合文件列表受官方行约束。 只有官方交付行会显示的文件才会被列出:成功的首方变更工具与显式 present 声明。

许可证

MIT © 2026 KaguraSayuki

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

💬 加入 DPharness 群聊

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

点击加入 QQ 群
DPharness 群聊二维码,手机 QQ 扫码进群
扫码进群