Drag Any File into DeepSeek Harness and Let the Agent Read It Directly
Wrote a Plugin for DeepSeek Harness: Drag an Excel into the Input Box, and the AI Reads the File Itself
A plugin development record of "casually solving a small pain point": the whole process from researching the DSH plugin system, to reverse-engineering the official package type contract, to "wrestling" with native image drag-and-drop.
Pain Point: DSH's Input Box Only Recognizes Images
Anyone using the DeepSeek Harness (DSH) Web GUI has probably encountered this: you try to drag an Excel file into the input box for the AI to analyze, and an error pops up—only uploading images like png is supported.
But if you think about it carefully, "uploading files" is not needed at all:
- DSH's Agent already has file system tools (read, glob, grep...); give it an absolute path and it can read the file itself;
- Files like Excel/PDF don't work through the "image attachment" channel anyway. Going through the "path reference" channel lets the Agent use its full skill set (xlsx parsing, pandas, large file handling);
- File content never crosses the browser boundary, so it's also more private and secure.
So I wrote a DSH plugin: dsh-drag-file—drag any file into the input box, a file placeholder chip appears above, and the message actually sent is the file's absolute path. The AI receives the path and reads the file itself.
Demo
You (drag an Excel file from Finder into the input box area)
↓ release
A file chip appears above the input box: 📄 20260820.xlsx
↓ host searches the disk by filename (workspace + ~/Downloads, etc.)
The input box draft is automatically filled with:
/Users/xxx/Downloads/0818/20260820.xlsx
↓ you continue typing
/Users/xxx/Downloads/0818/20260820.xlsx
Help me summarize various price differences by car model and output a summary table
↓ send
Agent receives the path text → uses file tools to read it itself → starts working
Key point: what is sent is plain text. There is no hidden attachment injection; the user can see and edit the path about to be sent in the input box—what you see is what gets sent.
Research: What a DSH Plugin Looks Like
DSH is a "everything is a plugin" microkernel architecture (the underlying layer is the Cordis framework). The official documentation has a plugin development tutorial and an extension cookbook. A plugin is a TypeScript module that exports an apply(ctx) function:
import type { Context } from '@deepseek-ai/cordis'
export const name = 'my-plugin'
export const inject = ['tools'] // declare dependent services
export function apply(ctx: Context) {
// register capabilities through ctx: tools, event listeners, UI slots...
}
This plugin is a typical host + client dual-sided plugin:
- Host side (Node.js process): has disk access. Responsible for the "filename → absolute path" search, exposed to the browser as a Typert Remote service.
- Client side (browser): responsible for drag event capture, file chip rendering, and writing the path into the input box draft.
Both sides communicate through DSH's Typert Remote mechanism (RPC with strict zod schema validation), sharing a contract file to define the wire format.
An Unavoidable Browser Security Restriction
Some might ask: doesn't the browser drag event have a File object? Why not just get the path directly?
Because browsers do not expose absolute paths for security reasons—File.name only has the filename. So the "filename → absolute path" resolution must happen via a disk search on the host side, which is why a host/client dual-sided structure is needed:
Browser Host (Node.js)
────── ─────────────
drag event: file.name ──────→ dragFile/resolve(sessionId, name)
│
├─ search session workspace (depth 10)
├─ search ~/Downloads, ~/Desktop, ~/Documents (depth 3)
│ (skip node_modules/.git, etc.)
↓
chip shows candidate paths ←────── { candidates: [{ path, inWorkspace }], truncated }
unique match auto-fills draft
Installation and Usage
Published to npm, one command:
dsh plugin --profile web add dsh-drag-file
(You can also install from GitHub: dsh plugin --profile web add github:gezg/dsh-drag-file)
Restart the web profile and refresh the page. By default, it searches the session workspace + ~/Downloads, ~/Desktop, ~/Documents, all configurable:
- id: dsh-drag-file
name: dsh-drag-file
config:
searchDirs: ["~/Downloads", "~/Desktop", "~/Documents"]
workspaceDepth: 10
searchDirsDepth: 3
maxResults: 20
Full support for macOS / Linux / Windows (Windows C:\Users\... path chips are also recognized).
Final Thoughts
The biggest takeaway from the whole process: DSH's "everything is a plugin" is not just a slogan—the slot above the input box, the draft writing actions (inputActions), and the host-side RPC (Typert Remote) are all publicly extensible seams. A small "drag file" feature doesn't require touching a single line of the framework's code.
The fun of plugin development lies precisely in this game of "drawing boundaries with native behavior": intercepting during the capture phase, resetting synthetic events, treating the draft as the source of truth—every pitfall is a deeper layer of understanding of the framework's event flow.
The repository is here, stars / PRs welcome:
https://github.com/gezg/dsh-drag-file
If this was helpful, or if you've stepped on similar pitfalls with DSH, let's chat in the comments 👋