前言¶
用 dsh 的 Web profile 跑智能体时,会话的工作区是智能体读写文件的地方。但 GUI 本身没有文件入口:想把本地一份 CSV 交给智能体,得先登到服务器上拷进工作区;想确认智能体刚生成了哪些产物,也只能切回终端 ls 一遍。dsh-workspace-upload 补的就是这个缺口——在聊天界面里直接浏览、上传、下载、重命名、新建和删除会话工作区中的文件。
下面按功能、架构、安装、API 的顺序介绍这个插件。
这是什么¶
dsh-workspace-upload(版本 0.1.0,MIT 许可证)由 LI-Huaa 维护,是一个面向 dsh Web GUI 的工作区文件管理插件。
安装后,聊天输入区左侧会出现「文件 (Files)」按钮,点开一个覆盖在应用上的文件管理对话框。插件的所有操作都限定在工作区根目录内,工作区按「当前会话 → cwd → 第一个注册的工作区」的顺序解析。
核心功能¶
对话框里能做的事:
1、浏览工作区及其子目录,带面包屑导航和上级/刷新按钮;
2、多文件分块上传,支持任意大小的文件(默认 640 KiB 一块,遇代理体积限制自适应缩小,最低降到 64 KiB 后重试);
3、下载文件;
4、重命名文件和文件夹;
5、创建文件夹;
6、删除文件和文件夹,删除有两步确认。
安全边界写在宿主侧:所有 path / name 值都会净化并做遏制检查,.. 和绝对路径逃逸一律拒绝;上传写入的是目标目录内一个不会逃逸的隐藏临时文件。
架构:一个包,两半¶
插件是一个包、一条 profile 行、两个半体:
| 半体 | 文件 | 职责 |
|---|---|---|
| 宿主半 | lib/index.js |
在 dsh web server 上注册 GET/POST /api/workspace-upload,覆盖 list / rename / mkdir / delete / download / 分块上传,全部带工作区遏制检查 |
| 客户端半 | lib/client.js |
浏览器 bundle(经 window.__ModuleLoader__.load 加载):触发按钮挂在 conversation.input.left,对话框由按钮自身渲染(position: fixed 覆盖层) |
接线方式:package.json 的 dsh.bundle.patch 指向 cordis.patch.yml,由它在 web 组合里插入名为 workspace-upload 的一行;dsh.client.platform: "web" 把这个包标记为浏览器 roster 条目,bundle 经 exports["./client"] 提供。
安装与启用¶
插件以 dsh profile bundle 的形式安装。在持有该包目录的检出里执行:
dsh plugin --profile web add ./dsh-workspace-upload
# 或从克隆目录:dsh plugin --profile web add /path/to/dsh-workspace-upload
然后重启 web profile(dsh web ...)让加载器读到新行,再刷新浏览器即可。卸载:
dsh plugin --profile web remove dsh-workspace-upload
API 用法¶
宿主半只暴露一个路由 /api/workspace-upload,GET 和 POST 各有分工。
GET:探测与下载¶
不带参数时返回解析后的工作区目录;带 ?sessionId&path&name 时以附件头下载对应文件:
GET /api/workspace-upload
# 无参数 → { "workspace": "<resolved workspace dir>" }
# 带 ?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 }
分块上传¶
大文件走三步协议:先 chunk、再 finish,中途放弃用 abort:
{ "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 }
几个值得知道的细节:
- GUI 默认发 640 KiB 一块,base64 后约 853 KiB,压在 nginx 默认
client_max_body_size1 MiB 之下;如果裸收到413,客户端会把块体积减半重试,最低降到 64 KiB。 - 服务端限制:单请求 32 MiB(一个分块加开销),每分块解码后 8 MiB。
- 分块追加到目标目录内的隐藏临时文件
.dsh-upload-<transferId>,finish时重命名为目标文件;分块必须从 offset 0 起按序到达。 - 已接收 offset 上的重复分块会得到幂等应答,客户端可以安全重试;孤儿传输 30 分钟后清理。
overwrite: true覆盖已有文件,否则跳过并返回status: "skipped"。
旧版批量上传¶
为兼容 API/curl 调用,保留了单次批量 base64 模式:
{ "sessionId"?, "files": [{ "name", "data": "<base64>", "overwrite"? }] }
→ { "workspace": string, "results": [{ name, path?, status, bytes?, error? }] }
适合在脚本里一次性推几个小文件,不必走分块流程。
开发与测试¶
两个测试都不需要 dsh 实例,直接驱动真实的宿主处理器和客户端 bundle 工厂:
node test/protocol.mjs # 宿主协议:list/rename/mkdir/delete/download/分块上传
node test/simulate.mjs # 客户端内核:对 slots fake 做槽位注册
适用场景与注意¶
适合的人群很明确:用 dsh Web profile、且经常要在本地和会话工作区之间搬文件,或者想在 GUI 里查看智能体产物的人。如果你的操作全部发生在终端里,这个插件帮不上什么。
几点注意:
1、插件以当前 dsh 进程的权限运行,能触达该进程能触达的工作区文件。安装前建议检查源码与许可证(MIT),确认无误再用。
2、路由继承 dsh web server 的绑定,默认是 localhost。要把 GUI 暴露到远端,需放在与主应用相同的 TLS/Basic-Auth 反向代理之后;想减少分块次数,可以调高代理的 client_max_body_size。
3、分块必须按序到达。GUI 客户端自己会处理重试与降块,如果绕过它直接写调用方,重试和顺序要自己保证。
结尾¶
回顾一下:dsh-workspace-upload 把「文件怎么进出工作区」这件 Web profile 下原本要切终端的事,收进了聊天界面的一个按钮里;宿主侧的路径遏制和分块幂等设计,让它在有代理体积限制的环境下也能稳定工作。
- GitHub:https://github.com/LI-Huaa/dsh-workspace-upload
- 社区目录页:https://www.skillhub.cn/plugins/LI-Huaa/dsh-workspace-upload
目录为社区维护的独立站点,与 DeepSeek / 幻方无官方从属关系。