给 DeepSeek Harness 写了个插件:Excel 拖进输入框,AI 自己去读文件
一次「顺手解决小痛点」的插件开发实录:从调研 DSH 插件体系,到逆向官方包类型契约,再到和原生图片拖拽「斗智斗勇」的全过程。
痛点:DSH 的输入框只认图片
用 DeepSeek Harness(下称 DSH) Web GUI 的同学应该都遇到过:想把一个 Excel 拖进输入框让 AI 分析,结果弹出一个错误提示------只支持上传 png 等图片。
但这事儿仔细想想,其实根本不需要「上传文件」:
- DSH 的 Agent 本来就有文件系统工具(read、glob、grep......),给它一个绝对路径它就能自己去读;
- Excel/PDF 这类文件,走「图片附件」通道本来就不通,走「路径引用」通道反而能享受 Agent 的全量技能(xlsx 解析、pandas、大文件处理);
- 文件内容不跨浏览器边界,隐私上也更安全。
于是我写了一个 DSH 插件:dsh-drag-file ------把任意文件拖进输入框,上方显示文件占位 chip,发送的消息实际是这行文件的绝对路径。AI 收到路径,自己去读文件。

效果演示
bash
你(从 Finder 拖一个 Excel 进输入框区域)
↓ 松手
输入框上方出现文件 chip:📄 20260820.xlsx
↓ host 在磁盘上按文件名搜索(工作区 + ~/Downloads 等)
输入框草稿自动填入:
/Users/xxx/Downloads/0818/20260820.xlsx
↓ 你继续打字
/Users/xxx/Downloads/0818/20260820.xlsx
帮我按车型统计各类价格差异,输出一份汇总表
↓ 发送
Agent 收到路径文本 → 自己用文件工具读取 → 开始干活
关键点:发送的就是纯文本。没有隐藏的附件注入,用户在输入框里能看到、能编辑即将发出的路径------所见即所发。
调研:DSH 插件长什么样
DSH 是「一切皆插件」的微内核架构(底层是 Cordis 框架),官方文档里有插件开发教程和 extension cookbook。一个插件就是一个导出 apply(ctx) 函数的 TypeScript 模块:
ts
import type { Context } from '@deepseek-ai/cordis'
export const name = 'my-plugin'
export const inject = ['tools'] // 声明依赖的服务
export function apply(ctx: Context) {
// 通过 ctx 注册能力:工具、事件监听、UI slot......
}
我这个插件是典型的 host + client 双面插件:
- Host 面(Node.js 进程):能访问磁盘。负责「文件名 → 绝对路径」的搜索,以 Typert Remote 服务的形式暴露给浏览器
- Client 面(浏览器):负责拖拽事件捕获、文件 chip 渲染、把路径写进输入框草稿
两端通过 DSH 的 Typert Remote 机制通信(带严格 zod schema 校验的 RPC),共享一个 contract 文件定义 wire 格式。
一个绕不开的浏览器安全限制
有人会问:浏览器拖拽事件里不是有 File 对象吗,为什么不直接拿路径?
因为浏览器出于安全考虑不暴露绝对路径 ------File.name 只有文件名。所以「文件名 → 绝对路径」必须在 host 侧做磁盘搜索,这也是为什么需要 host/client 双面结构:
scss
浏览器 Host (Node.js)
────── ─────────────
拖拽事件:file.name ──────→ dragFile/resolve(sessionId, name)
│
├─ 搜索会话工作区(深度 10)
├─ 搜索 ~/Downloads、~/Desktop、~/Documents(深度 3)
│ (跳过 node_modules/.git 等)
↓
chip 显示候选路径 ←────── { candidates: [{ path, inWorkspace }], truncated }
唯一匹配自动填入草稿
安装使用
已发布到 npm,一行命令:
sh
dsh plugin --profile web add dsh-drag-file
(也可以从 GitHub 安装:dsh plugin --profile web add github:gezg/dsh-drag-file)
重启 web profile、刷新页面即可。默认搜索会话工作区 + ~/Downloads、~/Desktop、~/Documents,都可以配置:
yaml
- id: dsh-drag-file
name: dsh-drag-file
config:
searchDirs: ["~/Downloads", "~/Desktop", "~/Documents"]
workspaceDepth: 10
searchDirsDepth: 3
maxResults: 20
macOS / Linux / Windows 全支持(Windows 的 C:\Users\... 路径 chip 也认)。
写在最后
整个过程最大的感受是:DSH 的「一切皆插件」不是口号------输入框上方的座位(slot)、草稿写入的动作(inputActions)、host 侧的 RPC(Typert Remote)全是公开可扩展的接缝,一个「拖文件」的小功能完全不需要碰框架一行代码。
而插件开发的乐趣恰恰在这种「和原生行为划清边界」的博弈里:捕获阶段拦截、合成事件复位、草稿即真相源------每一个坑背后都是对框架事件流的更深一层理解。
仓库在这里,欢迎 star / PR:
如果这篇对你有帮助,或者你在 DSH 上也踩过类似的坑,评论区聊聊 👋