跪拜 Guibai
← Back to the summary

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:

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.

image.png

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:

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 reasonsFile.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 👋