DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

Zenjibad /

dsh-drop-any-file

Verified

Accept any file type in the DeepSeek Harness (DSH) web chat drag-and-drop: non-image files are saved into the active session's workspace for the agent to read and use.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: master@959a233c

dsh-drop-any-file · Accept Any File Type in the DeepSeek Harness (DSH) Web Chat

Extend the DeepSeek Harness (DSH) Web UI chat's drag-and-drop so that every file type is accepted — the built-in composer only lets images (PNG/JPG/WebP/GIF) attach inline and rejects everything else. Dropping a non-image file saves it into the active session's workspace so the agent can read and use it. 让 DSH 聊天支持拖拽任意类型文件:非图片文件将保存到当前会话工作区,供 Agent 读取使用。

中文文档: README.zh.md · LLM index: llms.txt · Agent guide: AGENTS.md

dsh-plugin DeepSeek Harness license install

Keywords: dsh-plugin · deepseek-harness-plugin · drag-drop · file-upload · workspace · attachment · any-file · 拖拽 · 任意文件


📑 Table of Contents

  • ✨ Features
  • 🏗️ How it works
  • 🚀 Quick start
  • ⚙️ Configuration
  • ❓ FAQ
  • ⚠️ Security notes
  • 📦 Project structure
  • 🙏 Credits

✨ Features

Feature Description
📥 Any file type Drop .pdf, .zip, .txt, .csv, .bin, … — nothing is rejected with an "only PNG/JPG" error
💾 Saves into the workspace Each dropped non-image file is written into the active session's workspace directory, where the agent can read/search/use it with its normal file tools
🖼️ Images untouched Drops that contain only images keep the built-in inline-attach behaviour — nothing changes for what already worked
🚫 Composer rejection bypassed Capture-phase window listeners intercept the drop before the composer's image-only handler runs, so its rejection toast never fires
🫳 Drop overlay + toast A themed full-window overlay appears while dragging ("Drop to save to workspace"); a toast confirms what was saved (or why it failed)
🛡️ Name sanitisation + size caps File names are reduced to a basename and stripped of illegal path characters; payload/file sizes are capped (80 MB body / 60 MB file)
🌗 Theme-aware Overlay and toast use --dsw-alias-* design tokens; follows light/dark automatically
♨️ Survives restarts Real profile-bundled plugin: install once with dsh plugin add, auto-loads on every DSH boot — no per-session define, no cordis_define

🏗️ How it works

User drags a non-image file over the chat window
                                  │
Client bundle (browser)           ▼
  └─ capture-phase listeners on window: dragenter / dragover / dragleave / drop
       (registered from the conversation.input.dock seat, which carries sessionId)
  └─ drop contains a non-image file? → preventDefault + stopPropagation
       (the composer's image-only handler never sees the event)
  └─ FileReader → base64 → fetch POST /drop-any-file/api
       body: { sessionId, fileName, base64 }
                                  │
Host half (DSH process)           ▼
  └─ webServer route POST /drop-any-file/api
  └─ resolve target dir: sessions.get(sessionId).header.cwd  (the workspace)
  └─ sanitise basename → decode base64 → node:fs writeFile
  └─ returns { ok, saved: { name, path, bytes } }
                                  │
Client bundle (browser)           ▼
  └─ toast: "Saved to workspace: <name>" (or the error)
  • Pure pull from the browser: no events, no push — the client posts only when the user drops a file; the host answers with JSON ({ok:false,error} on any failure, never a non-JSON 500).
  • Image-only drops pass through: the handler returns early without stopPropagation, so the composer's own inline-attach path still runs for images.
  • Persistence: ships dsh.bundle (cordis.patch.yml) + dsh.client (exports["./client"], bundled) so it installs as a real profile plugin that the DSH client-modules scanner loads on every boot.

🚀 Quick start

Standard install: dsh plugin add (persists across restarts)

Install the package from this GitHub repo:

# local directory (from the parent of this repo):
dsh plugin --profile web add ./dsh-drop-any-file

# or directly from GitHub (any DSH machine):
dsh plugin --profile web add github:Zenjibad/dsh-drop-any-file
# or:
dsh plugin --profile web add git+https://github.com/Zenjibad/dsh-drop-any-file.git

dsh plugin add is a pnpm add into the profile plus a dsh.profile.bundles reconcile: seeing this package's dsh.bundle declaration, it appends dsh-drop-any-file to the bundle stack. Restart DSH, then hard-refresh the browser tab (Ctrl+F5). On boot the client-modules scanner resolves exports["./client"] and the drop handler activates. No per-session define, survives restarts.

⚠️ Important: after installing (or updating) a client plugin, a hard page refresh (Ctrl+F5) is required — the DSH client HMR only hot-swaps already-loaded bundles and does not pull in new bundles into an open tab.

Manual profile mount (alternative)

  1. git clone https://github.com/Zenjibad/dsh-drop-any-file.git (any location).
  2. Add to ~/.dsh/profiles/web/package.json dependencies: "dsh-drop-any-file": "link:<repo-path>", then pnpm install in the profile dir.
  3. Restart DSH.

Requirements

  • Any DSH web session with an active workspace (the drop target is the current session's cwd).
  • No session open → nothing to save into → the plugin is inert (the composer keeps its default behaviour).

⚙️ Configuration

No config file, no persisted settings. Behaviour is fixed by constants in the source:

Knob Location Default
Max POST body (base64) MAX_BODY_BYTES in src/index.ts 80 MB (≈ 60 MB file)
Max saved file MAX_FILE_BYTES in src/index.ts 60 MB
Save target resolved per drop sessions.get(sessionId).header.cwd (the session's workspace)
Drop interception src/client/index.tsx capture-phase dragenter/dragover/dragleave/drop on window
Toast duration src/client/index.tsx 6 s

❓ FAQ

Q: I dropped a file and it still says "Only PNG, JPG, WebP, and GIF images are supported"? A: The plugin isn't loaded in your current page. Restart DSH (if the host half isn't mounted yet), then hard-refresh the browser tab (Ctrl+F5). New client bundles only appear on a full page reload — the HMR client does not add new bundles to an already-open tab.

Q: I dropped a file but nothing happened / no toast? A: The drop must happen over the chat window with a session open, and must contain at least one non-image file. If the drop is image-only, the built-in attach path runs instead (by design). Check the browser console for drop-any-file save error entries.

Q: Where does the file go? A: Into the active session's workspace directory (sessions.get(sessionId).header.cwd) — the same directory the agent works in, so the agent can read it with read/glob/grep etc. The response includes the absolute path in the success toast context.

Q: Does it handle multiple files at once? A: Yes — every non-image file in the drop is saved in order; the toast lists each saved name and counts failures.

Q: Can I drop a file larger than the cap? A: No — the host rejects files over 60 MB with 413 and the client shows a failure toast. Raise MAX_FILE_BYTES (and MAX_BODY_BYTES) in src/index.ts and rebuild if you need bigger files.

Q: My old dynamic plugin is still active after installing this? A: This plugin is packaged, not dynamic — there is no dynamic twin. If you previously installed a dynamic drop-handler, stop it (cordis_stop / cordis_undefine) to avoid double interception.

Q: How do I remove it? A: dsh plugin --profile web rm dsh-drop-any-file (or delete the profile dependency + bundle entry) and restart DSH.

⚠️ Security notes

  • Path traversal blocked: the file name is reduced to path.basename() and every illegal path character (<>:"/\\|?* and control chars) is replaced, so a hostile name can never escape the workspace.
  • Scoped writes: files are written only into the resolved session's cwd — never to arbitrary paths.
  • Size caps: the request body is capped at 80 MB and the decoded file at 60 MB, so the host never buffers unbounded payloads.
  • No extra I/O: the plugin performs no network calls, no shell execution, and no disk scanning — only the requested file write.
  • Base64 over same-origin HTTP: the file bytes travel as base64 JSON to a same-origin route (the DSH webServer); no third-party endpoint is involved.

📦 Project structure

dsh-drop-any-file/
├── src/
│   ├── index.ts            # host half: body read, session-cwd resolve, sanitise, writeFile, /drop-any-file/api route
│   └── client/index.tsx    # client bundle: capture-phase drop listeners, overlay, toast, POST to the host route
├── cordis.patch.yml        # dsh.bundle patch (inserts the plugin row on boot)
├── tsdown.config.ts        # bundles host (node ESM) + client (CJS ModuleLoader)
├── package.json            # name, exports["./client"], dsh.client + dsh.bundle
├── lib/                    # build output (index.js, client.js)
├── AGENTS.md               # repository guide for AI agents
├── llms.txt / llms-full.txt
├── README.md / README.zh.md
└── LICENSE

🙏 Credits

  • DeepSeek Harness — the DSH plugin/dynamic runtime, Slots, theme, webServer, client-modules.
  • headroom-stats-plugin — reference for the packaged client-plugin build pattern (tsdown host/client split, cordis.patch.yml, dsh.client).
  • DSH-better-sidebar — another reference for the packaged client-plugin build pattern.

📄 License

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit 959a233cfa65

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout