Preface¶
When using dsh’s Web profile to run an agent, the session workspace is where the agent reads and writes files. But the GUI itself has no file entry point: if you want to hand a local CSV to the agent, you first need to log into the server and copy it into the workspace; if you want to confirm which artifacts the agent just generated, you can only switch back to the terminal and run ls. dsh-workspace-upload fills this gap by letting you browse, upload, download, rename, create, and delete files in the session workspace directly from the chat interface.
The following sections introduce this plugin in the order of features, architecture, installation, and API.
What This Is¶
dsh-workspace-upload (version 0.1.0, MIT license) is maintained by LI-Huaa and is a workspace file management plugin for the dsh Web GUI.
After installation, a “Files” button appears to the left of the chat input area. Clicking it opens a file management dialog that overlays the application. All plugin operations are confined to the workspace root directory, and the workspace is resolved in the order: current session → cwd → first registered workspace.
Core Features¶
Things you can do in the dialog:
1、 browse the workspace and its subdirectories, with breadcrumb navigation and up/refresh buttons;
2、 upload multiple files using chunked upload, supporting files of any size (default 640 KiB per chunk; automatically reduces chunk size when hitting proxy body-size limits, down to a minimum of 64 KiB before retrying);
3、 download files;
4、 rename files and folders;
5、 create folders;
6、 delete files and folders, with two-step confirmation.
The safety boundary is enforced on the host side: all path / name values are sanitized and checked for containment; .. and absolute-path escapes are always rejected. Uploads are written to a hidden temporary file inside the target directory that cannot escape.
Architecture: One Package, Two Halves¶
The plugin is one package, one profile line, and two halves:
| Half | File | Responsibility |
|---|---|---|
| Host half | lib/index.js |
Registers GET/POST /api/workspace-upload on the dsh web server, covering list / rename / mkdir / delete / download / chunked upload; all operations include workspace containment checks |
| Client half | lib/client.js |
Browser bundle (loaded via window.__ModuleLoader__.load): the trigger button is attached to conversation.input.left; the dialog is rendered by the button itself as a position: fixed overlay |
Wiring: the dsh.bundle.patch field in package.json points to cordis.patch.yml, which inserts a line named workspace-upload into the web composition; dsh.client.platform: "web" marks this package as a browser roster entry, and the bundle is provided via exports["./client"].
Installation and Enabling¶
The plugin is installed as a dsh profile bundle. In a checkout containing the plugin directory, run:
dsh plugin --profile web add ./dsh-workspace-upload
# 或从克隆目录:dsh plugin --profile web add /path/to/dsh-workspace-upload
Then restart the web profile (dsh web ...) so the loader picks up the new line, and refresh the browser. To uninstall:
dsh plugin --profile web remove dsh-workspace-upload
API Usage¶
The host half exposes only one route, /api/workspace-upload; GET and POST have separate responsibilities.
GET: Probing and Download¶
Without parameters, it returns the resolved workspace directory. With ?sessionId&path&name, it downloads the corresponding file with attachment headers:
GET /api/workspace-upload
# 无参数 → { "workspace": "<resolved workspace dir>" }
# 带 ?sessionId&path&name → 以附件头返回文件内容
JSON POST: File Management Operations¶
path is a workspace-relative directory ("", "sub", "sub/deep"), and name is always a single 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¶
Large files use a three-step protocol: first chunk, then finish; abort is used to cancel mid-transfer:
{ "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 }
A few details worth knowing:
- The GUI client defaults to sending 640 KiB per chunk; after base64 encoding this is about 853 KiB, below nginx’s default
client_max_body_sizeof 1 MiB. If a raw413is received, the client halves the chunk size and retries, down to a minimum of 64 KiB. - Server-side limits: 32 MiB per request (one chunk plus overhead), and 8 MiB per chunk after decoding.
- Chunks are appended to a hidden temporary file
.dsh-upload-<transferId>inside the target directory;finishrenames it to the target file. Chunks must arrive in order starting at offset 0. - Duplicate chunks for an already received offset receive an idempotent response, so the client can safely retry. Orphaned transfers are cleaned up after 30 minutes.
overwrite: trueoverwrites an existing file; otherwise the upload is skipped and returnsstatus: "skipped".
Legacy Batch Upload¶
For API/curl compatibility, a single-shot batch base64 mode is retained:
{ "sessionId"?, "files": [{ "name", "data": "<base64>", "overwrite"? }] }
→ { "workspace": string, "results": [{ name, path?, status, bytes?, error? }] }
This is suitable for pushing a few small files at once from scripts, without going through the chunked flow.
Development and Testing¶
Both tests do not require a dsh instance; they directly drive the real host handlers and the client bundle factory:
node test/protocol.mjs # 宿主协议:list/rename/mkdir/delete/download/分块上传
node test/simulate.mjs # 客户端内核:对 slots fake 做槽位注册
Use Cases and Cautions¶
The target audience is clear: people using the dsh Web profile who frequently need to move files between their local machine and a session workspace, or who want to inspect agent artifacts in the GUI. If all your work happens in the terminal, this plugin will not help much.
Points to note:
1、 The plugin runs with the permissions of the current dsh process and can access the workspace files that the process can access. Before installing, it is advisable to review the source code and license (MIT) and confirm it meets your requirements.
2、 The route inherits the dsh web server’s binding, which is localhost by default. To expose the GUI remotely, place it behind the same TLS/Basic-Auth reverse proxy as the main application. To reduce the number of chunks, you can raise the proxy’s client_max_body_size.
3、 Chunks must arrive in order. The GUI client handles retries and chunk reduction on its own; if you bypass it and write direct calls, you are responsible for guaranteeing retries and ordering.
Conclusion¶
To summarize: dsh-workspace-upload brings the “getting files into and out of the workspace” task—which under the Web profile used to require switching to a terminal—into a single button in the chat interface. The host-side path containment and idempotent chunking design allow it to work stably even in environments with proxy body-size limits.
- GitHub: https://github.com/LI-Huaa/dsh-workspace-upload
- Community directory page: https://www.skillhub.cn/plugins/LI-Huaa/dsh-workspace-upload
The directory is a community-maintained independent site and has no official affiliation with DeepSeek / 幻方.