# dsh-workspace-upload [English](README.md) | 中文 面向 [dsh](https://github.com/deepseek-ai/deepseek-harness) Web profile 的工作区文件管理器插件——直接在聊天界面中浏览、上传、下载、重命名、新建和删除会话工作区中的文件。 ## 功能特性 * 输入框左侧的 **文件按钮**(位于模式/命令选择器右侧)点击后打开文件管理弹窗: * 浏览工作区及其子目录(面包屑导航、上级、刷新); * **上传**——多文件、分块、不限大小(640 KiB 分块,遇到代理体积限制时自适应缩小); * **下载**文件; * **重命名**文件与文件夹; * **新建文件夹**; * **删除**文件与文件夹(两步确认)。 * 所有操作都被限制在工作区根目录内:host 端路径包含校验会拒绝任何 `..` 或绝对路径逃逸。 * 工作区按会话解析(会话工作区 → cwd → 第一个已注册工作区)。 ## 目录结构 ``` dsh-workspace-upload/ ├── cordis.patch.yml # profile 补丁层:插入插件行 ├── package.json # dsh.bundle / dsh.client 声明、exports ├── lib/ │ ├── index.js # host 半区:/api/workspace-upload 文件管理路由 │ └── client.js # client 半区:浏览器 bundle(按钮 + 弹窗) ├── test/ │ ├── protocol.mjs # host 协议测试(真实 handler,无需 dsh 实例) │ └── simulate.mjs # client 内核模拟(插槽注册假服务) ├── LICENSE # MIT ├── .gitignore ├── README.md # English └── README-zh.md # 中文 ``` ## 架构 一个包、一行 profile 配置、两个半区: | 半区 | 文件 | 作用 | | --- | --- | --- | | Host | `lib/index.js` | 在 dsh web 服务器上注册 `GET/POST /api/workspace-upload`:list / rename / mkdir / delete / download / 分块上传,全部带工作区路径包含校验。 | | Client | `lib/client.js` | 浏览器 bundle(`window.__ModuleLoader__.load`):触发按钮注册在 `conversation.input.left`;弹窗由按钮组件自身渲染(纯本地状态,`position: fixed` 遮罩)。 | 接线方式: * `package.json` 中 `dsh.bundle.patch` 指向 `cordis.patch.yml`(插入插件行的 profile 层),`dsh.client.platform: "web"` 将该包标记为浏览器注册表条目,其 bundle 通过 `exports["./client"]` 提供。 * `cordis.patch.yml` 向 web 组合配置插入一行 `workspace-upload`。 ## API ``` GET /api/workspace-upload → { "workspace": "<解析出的工作区目录>" } (无参数) → 带附件头的文件下载 (?sessionId&path&name) ``` 文件管理模式(JSON POST;`path` 是相对工作区的目录,`""` / `"sub"` / `"sub/deep"`,`name` 始终是单个路径段): ``` { "mode": "list", "sessionId"?, "path"? } → { workspace, path, entries:[{name,type,size,mtime}] } { "mode": "rename", "sessionId"?, "path"?, "name", "newName" } → { ok, from, to } { "mode": "mkdir", "sessionId"?, "path"?, "name" } → { ok, path } { "mode": "delete", "sessionId"?, "path"?, "name" } → { ok, deleted } ``` 分块上传(不限大小;GUI 每块 640 KiB——base64 请求体约 853 KiB,低于 nginx 默认的 `client_max_body_size` 1 MiB;遇到裸 `413` 时客户端把块大小减半重试,最低 64 KiB): ``` { "mode": "chunk", "sessionId"?, "path"?, "transferId", "name", "offset", "data", "total" } → { received } { "mode": "finish", "sessionId"?, "path"?, "transferId", "name", "total", "overwrite"? } → { status, path, bytes } { "mode": "abort", "transferId" } → { aborted } ``` 分块追加写入目标目录内的隐藏临时文件 `.dsh-upload-`,finish 时重命名为最终文件名。分块必须从 offset 0 按序到达;已接收偏移的重复分块会幂等返回当前偏移(客户端可安全重试)。超过 30 分钟无活动的孤儿传输会被自动清扫。 为兼容 API/curl 保留旧的单次批量模式: ``` { "sessionId"?, "files": [{ "name", "data": "", "overwrite"? }] } → { "workspace": string, "results": [{ name, path?, status, bytes?, error? }] } ``` 限制:单请求 32 MiB(一个分块 + 开销),单分块解码后 8 MiB。`overwrite: true` 覆盖已存在文件,否则以 `status: "skipped"` 跳过。 ## 安装 该包作为 dsh profile bundle 安装。在拥有包目录的检出目录下执行: ```sh dsh plugin --profile web add ./dsh-workspace-upload # 或从克隆中: dsh plugin --profile web add /path/to/dsh-workspace-upload ``` 然后重启 web profile(`dsh web ...`)让加载器识别新行,并刷新浏览器。卸载插件: ```sh dsh plugin --profile web remove dsh-workspace-upload ``` ## 开发 两个测试都不需要 dsh 实例(直接驱动真实的 host handler 与 client bundle 工厂): ```sh node test/protocol.mjs # host 协议:list/rename/mkdir/delete/download/分块上传 node test/simulate.mjs # client 内核:插槽注册(忠实模拟的 slots 假服务) ``` ## 安全说明 * 所有 `path` / `name` 都会在 host 端进行净化与包含性校验;上传使用隐藏临时文件,绝不会逃逸出目标目录。 * 路由继承 dsh web 服务器的绑定(默认 localhost);远程暴露 GUI 时,应与应用其余部分一样置于 TLS + Basic Auth 反向代理之后(如需更少、更大的分块,可调大 `client_max_body_size`)。