dsh-workspace-upload:把工作区文件管理搬进 dsh 聊天界面

前言

用 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.jsondsh.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_size 1 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 / 幻方无官方从属关系。

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜