GLFzr / dsh-file-upload

Listed

DSH 拖拽文件转路径插件:Codex 式拖拽,路径自动插入输入框(Drop File to Path for DeepSeek Harness)

mainTool View source

Installation

npx -y @deepseek-ai/dsh plugin --profile web add github:GLFzr/dsh-file-upload

This installation command is an unverified starting point generated from the GitHub repository address.

README

Maintainer-authored documentation snapshot.

View on GitHub ↗
Commit baa5699Synced Aug 18, 2026

DSH 文件上传插件(File Upload)v2.1.0

把 DSH Web GUI 原版只支持图片的附件通道,扩展为任意文件上传:拖入任意文件(PDF、3MF、ZIP、Excel、代码、图片……)→ 文件上传/引用到本机中转目录 → 输入框出现一个蓝色文件 chip → agent 可以直接读取该文件。

定位说明(v2.0.0 起):本插件的本质是文件上传功能,不是"路径展示"。路径只是内部机制——agent 读取文件必须依赖磁盘路径,浏览器又拿不到(见下文"为什么必须上传"),所以插件负责把文件物化到本机、并把路径交给 agent。用户界面不再出现任何路径概念:chip 只显示文件名,用户只需知道"文件上传好了,agent 能读了"。

本仓库是持久化 profile 插件dsh.bundle + dsh.client 双声明):按官方方式安装一次,DSH 重启后自动生效

功能特性

  • 🖱️ 页面任意位置拖入任意文件(多文件、图片均可);拖拽时全屏提示"松开以接收文件"
  • 📦 任意文件类型:原版"仅图片"附件通道被完全静默接管,任何文件都可拖入
  • 📍 本机文件零上传直引:Chromium(Edge/Chrome)拖拽会经 entry API 暴露源文件路径——直接引用,零上传、零复制(你电脑里那份文件就是唯一的一份);仅当浏览器拿不到路径(如 Firefox)时才走上传中转
  • 🔵 文件以一个整体 chip 插入输入框:蓝色文字(只显示文件名+格式)、不可被局部删改(Backspace/Delete 一次删除整个引用)
  • 📐 篮筐宽度随文件名自适应:插入时用 composer 真实字体实测文件名宽度,pill 精确贴合(误差 <1 个字符)
  • 🙈 内部细节全部隐藏.b64 后缀、_1/_2 重名序号、中转目录路径——用户界面永远看不到;提交给 agent 的才是完整真实路径
  • ♻️ 两级去重(先查后传,防误复用)begin 快路径命中条件 = 同名 + 同大小 + manifest 登记的源文件修改时间与本次拖入一致——重复拖同一文件仍秒回(零上传);但编辑过且字节数没变的文件绝不误复用(v2.1.0 起,新/旧文件首次拖入都先完整上传,由 end 的 sha256 逐字节比对裁决:内容相同复用旧路径,内容不同落 _1 副本)
  • 📇 中转目录清单(manifest):插件把每个自己写入的文件登记到 ~/.dsh-dropbox/.dsh-manifest.json(原名、源修改时间、sha256、是否自动编号副本)——清理面板的"冗余副本"标记只认清单,不会把 notes_2024.txt 这类自然命名误判为副本(v2.1.0 起)
  • 🧹 失败即清理:上传任一步失败,客户端立即调用 abort 释放宿主会话,不再滞留内存到超时(v2.1.0 起)
  • 🛡️ 完整性校验:每块精确长度校验、分块必须齐全、解码后字节数必须与声明一致——截断/缺块/伪造数据一律拒绝,绝不落盘损坏文件
  • 插入点待机圆环:上传真正需要等待时,在文件将要出现的位置(光标右侧一个字符处)显示 DeepSeek 蓝色转圈环(渐隐拖尾、持续转动);瞬间完成的路径(本机直引、秒回去重、极小文件)不出圈;chip 插入的同一瞬间圆环立即消失
  • 🚀 ~4MB 分块上传,上限 512MB;请求体上限 16MB/次;失败右下角红色提示 4 秒
  • 🗑️ 侧边栏垃圾桶图标(设置图标上方):打开上传目录清理浮层——文件清单、一键清理冗余 _N 副本、按大小清理、清空全部,操作前确认,避免中转目录无限膨胀

为什么必须上传/中转?(浏览器限制)

这是 WebUI 与 Hermes/Codex 之类本地 CLI 的本质区别,不是本插件的缺陷:

  • 浏览器是沙盒:网页里的 JavaScript 拿不到本地文件的磁盘路径(安全模型,防止网页偷读你的磁盘)。Hermes/Codex 直接跑在你的电脑上,可以随便读 C:\...\xxx.pdf;DSH 的 Web GUI 是一个浏览器网页,它手里的文件只是一个内存 File 对象
  • agent 只能读磁盘路径:agent 读取文件靠的是磁盘路径。浏览器无法把一个内存文件"变成"磁盘路径交给 agent,所以插件必须先把文件物化到本机中转目录(默认 ~/.dsh-dropbox),agent 才能访问。
  • 唯一的例外(零上传):Chromium 系浏览器(Edge/Chrome)拖拽本机文件时,DataTransferItem.webkitGetAsEntry() 能探测到源文件路径——此时插件直接引用原路径,零上传、零复制。Firefox、或从非本机来源(网页、压缩包内)拖拽时拿不到路径,才必须走上传兜底。
  • 大文件的代价:新文件第一次拖入必须完整上传(几百 MB 也要传),这是浏览器限制决定的。为了把重复成本降到零,插件做了两级去重(见工作原理)。

上传目录清理

中转目录会随着使用积累文件(尤其大文件)。侧边栏底部、设置图标上方的垃圾桶图标(悬停显示"清理上传目录")打开清理浮层:

  • 文件清单:名称、大小、修改时间;冗余副本自动标注——v2.1.0 起只认插件清单(.dsh-manifest.json)里登记的自动编号副本,自然命名文件(如 notes_2024.txt)绝不会被误标
  • 清理冗余副本:一键删除所有清单登记的 _N 副本(推荐——你的电脑里已有原文件,这些副本是纯浪费)
  • 按大小清理:输入阈值(MB),删除大于该值的文件
  • 清空全部:删除中转目录里所有文件(高危,二次确认)
  • 任何操作前都会显示"将删除 N 个文件、释放 X",并提示历史会话引用的文件会失效——清理前请确认没有正在使用的会话

.b64 是什么?(内部细节,用户无需关心)

浏览器无法保证任意二进制文件的原始字节能无损地穿越 JSON/文本通道,所以回退上传的二进制文件(PDF、3MF、ZIP、图片等)落盘时以 base64 文本保存,文件名追加 .b64 作为标记——agent 看到 .b64 就知道要先解码再使用:

import base64
with open('xxx.pdf.b64') as f: raw = base64.b64decode(f.read())
with open('xxx.pdf', 'wb') as f: f.write(raw)

文本类文件(.md/.txt/.json 等)直接写原文,不会.b64本机文件走原路径引用时不经过中转目录,天然没有 .b64。所有这些都是内部机制——输入框里的 chip 只显示文件名,提交给 agent 的才是完整真实路径。

安装(官方方式,一次永久)

# 在 deepseek-harness 仓库根目录(源码版):
pnpm dsh plugin --profile web add ./plugins/file-upload

# 或任意 dsh 安装(npx 版亦可):
dsh plugin --profile web add <本仓库路径>

dsh plugin 会 pnpm link 本包到 $DSH_HOME/profiles/web 并追加 dsh.profile.bundles。之后无论用 npx 版还是源码版启动 DSH,插件都自动加载

验证:dsh web --dump-config 应出现 # == dsh-file-upload 层。

从旧名 dsh-drop-file-to-path 升级:插件 id 与路由已更名为 file-upload,重装后旧版本无需卸载(同一 bundle 行被覆盖)。

工作原理

部件位置职责
Host 半(lib/index.js宿主进程,inject: ['webServer']HTTP 路由 /api/file-upload/begin|chunk|end|abort|list|clean:分块校验、两级去重(lastModified+sha256)、manifest 清单、Origin 校验、node:fs 落盘、清理接口
Client 半(lib/client.js浏览器(__ModuleLoader__ bundle,dsh.client 声明)全局拖拽监听(capture 接管)、fetch 分块上传、待机圆环、注册 trigger source、经 conversation.input.insertReference 插入文件 chip、侧边栏清理入口

文件 chip

文件引用走 composer 原生的参考引用机制(U+FFFC 占位符 + occurrence 表 + 提交时 codec 序列化):占位符在草稿里只占一个字符,因此整个引用是不可分割的整体;提交时经插件注册的 file-upload source 的 codec 把占位符展开为真实路径(含 .b64)。

篮筐宽度自适应:插件在插入前用 composer 的真实字体实测文件名宽度,算出 NBSP 空格数 pad,随 chip 一起插入(占位符 + pad 个 NBSP)。chip 单元格宽度 = 基础 1em + pad×空格宽 ≈ 文件名宽——先有文件名,后有篮筐。为此需要两处配套(都在本 DSH 发行版内):

  • 加宽字体:用 tools/patch-chip-font.mjsDshChipCell 的 U+FFFC advance 从 composer 默认 4em 改为 1em(同名字体族、后声明者生效,textarea/镜像/背板三层共享同一 advance,对齐关系不变);
  • 核心 composer 扩展dsh-client-ui-conversation):occurrence 支持 pad 扩展占位区间——插入、序列化、复制/剪切投影、Backspace/Delete 整删、backdrop 渲染全部按 [offset, offset+1+pad) 整段处理。

无该 composer 扩展的公开版 DSH(降级模式)pad 缺失时插件自动退回 [offset, offset+1)——chip 为固定 1em 宽(文件名按 composer 默认样式显示),提交/复制/刷新草稿全部正确,不会损坏草稿

上传、校验与去重

  • 分块:4194303 字节(4MiB−1,3 的倍数)——每满块的 base64 无中间填充符,拼接流可无损解码(4MiB 整块会在流中间嵌入 ==,Node/Python 解码器都会在首个 == 处截断,>4MB 文件会损坏;v1.2.1 起修复)。
  • 完整性chunk 阶段校验每块精确长度与 base64 合法性;end 阶段校验分块齐全、总量一致、解码后字节数 === 声明大小——截断/缺块/伪造数据一律拒绝,绝不落盘损坏文件。
  • 两级去重(防误复用)begin 快路径命中需要「同名 + 同大小 + manifest 登记的源修改时间一致」——重复拖同一文件秒回、零上传;编辑过但字节数未变的文件不会走快路径(v2.1.0 修复的误复用问题),而是完整上传后由 end 的 sha256 逐字节比对裁决:内容相同复用旧路径、不产生新副本;内容不同落 _1 副本,绝不复用错误内容。v2.1.0 之前上传的旧文件首次重拖也会走完整上传(缺 manifest 记录),由 end 哈希兜底并补登记。
  • 清单登记:每次落盘(含去重复用命中)都把文件登记进 .dsh-manifest.json(原名、源修改时间、sha256、是否自动编号副本);清理时同步注销。清单缺失或损坏时所有行为保守化(不做快路径复用、不标冗余副本),不影响上传本身。

待机圆环(插入点加载动画)

  • 触发:只有真正需要等待的上传才显示——上传开始后延迟 150ms,若期间完成(极小文件、秒回去重)则从不出现;超过 150ms 才在插入点显示。
  • 位置:直接测量 composer 自己的 mirror 镜像层(与输入框同字体、同宽度、同滚动,天然对齐),定位到光标/草稿末尾,圆心再右移一个字符宽("第二个字"位置)——不遮住已输入的文字。
  • 外观:16px DeepSeek 蓝(#4d6bfe)弧线圆环,渐隐拖尾 + 光晕,0.85s/圈持续旋转。
  • 消失:chip 插入的同一瞬间圆环立即移除(命令式 DOM 直接挂在 document.body,不经过 React/slot,无中间环节可吞掉它)。

清理入口

侧边栏脚部的垃圾桶图标sidebar.footer.action 槽位,渲染在设置图标上方;悬停显示"清理上传目录")点击后弹出右下角清理浮层,复用同一套清理逻辑(/api/file-upload/list + /clean)。

依赖官方服务:webServer(HTTP 路由)、slots(shell.overlay + conversation.composer.dock + sidebar.footer.action)、inputTriggers(source 注册与序列化)、conversation/sessions(按 session 解析输入门面)。除上述核心 occurrence 扩展外不修改 dsh 源码。

版本历史

  • v2.1.0(当前):修复一批实测发现的问题——
    • P1 快路径误复用:begin 快路径从"同名同大小即复用"改为"同名同大小 + 源修改时间一致才复用",编辑过但字节数没变的文件不再被静默引用旧内容(File.lastModified ↔ manifest 登记时间,1s 容差)
    • P2 清理误标:新增 .dsh-manifest.json 清单,冗余副本标记只认清单登记的自动编号副本,notes_2024.txt 这类自然命名不再被误标、不会被误删
    • P3 失败泄漏:客户端上传任一步失败立即 abort 宿主会话,不再滞留内存到 10 分钟超时
    • P4 静默丢弃:移除 16 个文件上限截断,一次拖更多文件也不会丢引用
    • P5 插入乱序:多文件 chip 严格按拖拽顺序插入(此前按完成顺序)
    • P6 busy 卡死:composer 忙时插入失败改为定时重试,不再等可能不来的事件
    • P7 sanitize 兜底:纯非法字符文件名(如 ::::)落盘为 file 而非 ____
    • P8 跨源防护:HTTP 路由拒绝跨源请求(Origin ≠ Host 时 403);无 Origin 的本地调用不受影响
  • v2.0.0:正式更名 dsh-file-upload——定位从"拖拽文件转路径"改为"任意文件上传"(把原版仅图片的附件通道扩展为任意文件);用户界面移除全部"路径"概念(ready 卡片不再显示/复制路径,提示文案统一为"接收文件");内部机制(上传、两级去重、完整性校验、待机圆环、清理入口)与 v1.4.8 完全一致
  • v1.4.8:待机圆环最终形态——延迟 150ms 显示(瞬间完成不出圈)、镜像层测量定位(光标右侧一个字符)、chip 插入瞬间同步消失
  • v1.4.0:清理入口改为侧边栏垃圾桶图标(设置图标上方,悬停提示);移除设置内导航页
  • v1.3.0:新增上传目录清理功能(文件清单、冗余副本/按大小/清空三种清理模式、删除前确认)
  • v1.2.3:恢复 begin 先查后传快速去重(同名同大小秒回,零上传);end 内容哈希兜底保留
  • v1.2.2:chip 样式机制回退为全局规则(v1.2.0/1.2.1 的动态规则存在时序 bug)
  • v1.2.1:修复 >4MB 文件解码截断(分块改为 3 的倍数)、end 完整性校验、草稿投影 pad 回退、Windows 保留名 sanitize
  • v1.1.0:路径以蓝色 chip 整体插入、篮筐宽度自适应、begin 快速去重

开发

node --check lib/index.js lib/client.js   # 语法检查
node --test tests/host.test.mjs           # 宿主行为测试(27 例:上传/完整性/两级去重/防误复用/清单/清理/跨源/安全)

CI(.github/workflows/ci.yml)在 push/PR 时自动执行以上两步。

文件说明

├── lib/index.js            # Host 半:HTTP 上传路由 + 校验/去重/清理 + node:fs 落盘
├── lib/client.js           # Client 半:全局拖拽 + 待机圆环 + 文件 chip + 侧边栏清理入口
├── tools/patch-chip-font.mjs  # 生成加宽 DshChipCell 字体的工具(改宽度后重跑)
├── tests/host.test.mjs     # 宿主行为测试(node --test,免 DSH 服务器)
├── chip-cell-font.b64      # 加宽字体产物(client.js 内嵌同一份)
├── cordis.patch.yml        # bundle 补丁层(insert 插件行)
├── .github/workflows/ci.yml # CI:语法检查 + 宿主测试
├── .gitignore
└── package.json            # dsh.bundle.patch + dsh.client 声明

落盘与读取约定

  • 中转目录:C:\Users\<用户>\.dsh-dropbox\os.homedir() 解析;测试可用 DSH_DROPBOX_DIR 覆盖)
  • 清单文件:~/.dsh-dropbox/.dsh-manifest.json(插件内部登记,勿手动编辑;删除中转文件时建议用清理面板,保证清单同步)
  • .b64 解码(agent 侧):见上文 .b64 说明

已知限制(v2.1.0)

  • 上传内存:单文件上传期间,宿主进程在内存中持有该文件全部 base64 分块(512MB 文件 ≈ 683MB 字符串),随后在 end 落盘。单机单用户够用;并发多个大文件会明显吃内存(架构级优化待后续版本)。
  • 本机直引路径(零上传):依赖 Chromium webkitGetAsEntry() 返回的真实磁盘路径,无存在性/大小校验;OneDrive 占位文件、虚拟文件系统拖入时可能得到不可读路径(此时会看到 agent 报文件不存在,建议改用"复制文件再拖")。Firefox 等拿不到路径的浏览器自动走上传兜底,无此问题。
  • 清理不可逆:清理浮层删除的是中转目录副本,历史会话引用的路径会失效——操作前弹窗会明确提示。

License

MIT

Project files and signals

Shown items are public repository signals detected in the directory snapshot.

TestsDetected

Repository information

Language
JavaScript
License
MIT
Latest release
v2.1.0
Last updated
Aug 16, 2026, 6:34 AM

Install deliberately

Review source code, permissions, lifecycle hooks, dependencies and network access. Test untrusted plugins in an isolated environment.