插件目录 / Developer / dsh-workspace-upload
dsh-workspace-upload
未验证 LI-Huaa
功能简介
A simple plugin to address the pain point of inconvenient file management when deploying DeepSeek Harness in the cloud. 一个简单的插件,用于在云端部署deepseek harness时管理文件。
未验证 — 尚未实测
A simple plugin to address the pain point of inconvenient file management when deploying DeepSeek Harness in the cloud. 一个简单的插件,用于在云端部署deepseek harness时管理文件。 尚未验证——请自行安装测试。
「未验证」表示我们的自动化 CI 尚未安装过该插件。功能描述与版本兼容性均为作者声明。这不是安全审计,也不代表对第三方代码的背书。
README
dsh-workspace-upload
English | 中文
A workspace file-manager plugin for the dsh
Web profile — browse, upload, download, rename, create and delete files in the
session's workspace, right from the chat UI.
Features
- A 文件 (Files) button in the composer's left controls opens a file-manager
dialog over the app:- browse the workspace and its subdirectories (breadcrumb navigation, up /
refresh); - upload — multi-file, chunked, any size (640 KiB chunks, adaptive shrink
under proxy body limits); - download files;
- rename files and folders;
- create folders;
- delete files and folders (two-step confirm).
- browse the workspace and its subdirectories (breadcrumb navigation, up /
- Every operation is scoped to the workspace root: host-side path containment
rejects any..or absolute-path escape. - The workspace resolves to the session's workspace (session → cwd → first
registered workspace).
Directory structure
dsh-workspace-upload/
├── cordis.patch.yml # profile patch layer: inserts the plugin row
├── package.json # dsh.bundle / dsh.client declarations, exports
├── lib/
│ ├── index.js # host half: /api/workspace-upload file-manager route
│ └── client.js # client half: browser bundle (button + dialog)
├── test/
│ ├── protocol.mjs # host protocol tests (real handler, no dsh needed)
│ └── simulate.mjs # client kernel simulation (slot registration fake)
├── LICENSE # MIT
├── .gitignore
├── README.md # English
└── README-zh.md # 中文
Architecture
One package, one profile row, two halves:
| Half | File | Role |
|---|---|---|
| Host | lib/index.js |
Registers GET/POST /api/workspace-upload on the dsh web server: list / rename / mkdir / delete / download / chunked upload, all with workspace containment. |
| Client | lib/client.js |
Browser bundle (window.__ModuleLoader__.load): the trigger button in conversation.input.left; the dialog renders from the button itself (plain local state, position: fixed overlay). |
Wiring:
package.json→dsh.bundle.patchpoints atcordis.patch.yml(the profile
layer that inserts the row) anddsh.client.platform: "web"marks the
package as a browser-roster entry whose bundle ships viaexports["./client"].cordis.patch.ymlinserts one row,workspace-upload, into the web
composition.
API
GET /api/workspace-upload
→ { "workspace": "<resolved workspace dir>" } (no params)
→ file download with attachment headers (?sessionId&path&name)
File-manager modes (JSON POST; path is a workspace-relative directory"" / "sub" / "sub/deep", name is always one path segment):
{ "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 }
Chunked upload (any file size; the GUI sends 640 KiB chunks — base64 bodies
~853 KiB, under the nginx default client_max_body_size of 1 MiB; on a bare413 the client halves the chunk size down to 64 KiB and retries):
{ "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 }
Chunks are appended to a hidden .dsh-upload-<transferId> temp file inside the
target directory and renamed on finish. Chunks must arrive in order from
offset 0; a repeated chunk at an already-received offset is answered
idempotently (safe client retry). Orphaned transfers are swept after
30 minutes.
A legacy single-shot batch mode is kept for API/curl compatibility:
{ "sessionId"?, "files": [{ "name", "data": "<base64>", "overwrite"? }] }
→ { "workspace": string, "results": [{ name, path?, status, bytes?, error? }] }
Limits: 32 MiB per request (one chunk + overhead), 8 MiB decoded per chunk.overwrite: true replaces an existing file; otherwise the file is skipped withstatus: "skipped".
Install
The package installs as a dsh profile bundle. From the checkout that owns the
package directory:
dsh plugin --profile web add ./dsh-workspace-upload
# or from a clone: dsh plugin --profile web add /path/to/dsh-workspace-upload
Then restart the web profile (dsh web ...) so the loader picks up the new
row, and refresh the browser. Removing the plugin:
dsh plugin --profile web remove dsh-workspace-upload
Development
Both tests run without a dsh instance (they exercise the real host handler and
client bundle factory directly):
node test/protocol.mjs # host protocol: list/rename/mkdir/delete/download/chunked upload
node test/simulate.mjs # client kernel: slot registration against a faithful slots fake
Security notes
- All
path/namevalues are sanitized and containment-checked on the host;
uploads use hidden temp files that never escape the target directory. - The route inherits the dsh web server's bind (localhost by default); when
exposing the GUI remotely, put it behind the same TLS/Basic-Auth reverse
proxy as the rest of the app (and raiseclient_max_body_sizeif you want
fewer, larger chunks).
安装
装一次目录插件,之后本站所有插件都能让 DeepSeek Harness 自动找、自动装:
dsh plugin add dshbase-catalog 然后对 agent 说「帮我装 dsh-workspace-upload」,它会在目录里找到并自动安装。文档:dshbase-catalog · 已验证场景包。
该插件是 GitHub 源码(未发 npm)——直接从仓库装:
Web profile:
dsh plugin --profile web add github:LI-Huaa/dsh-workspace-upload Headless(CLI)profile:
dsh plugin --profile headless add github:LI-Huaa/dsh-workspace-upload 实测报告
尚未 L3 验证——若已跑过,见下方失败备注。
2026-08-27 · 标记 webonly 备注:验证: web-only;待 L4 web CDP;L4 web CDP runtime-fail on dsh 0.1.0-rc.8. 浏览全部待验证失败 →