前言¶
用 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 / 幻方無官方從屬關係。