Bundle
dsh-file-uploader
dsh 文件上传解析插件:在对话框“+”菜单与拖拽中接收 txt/md/代码/Word/Excel/PDF 文件,解析后随下一条消息注入模型
- Source
- kamilhzn
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 5 days ago
Readme
# dsh-file-uploader
dsh 的文件能力插件:**上传本地文件**(txt、md、各类代码文件、Word、Excel、PDF)与 **@ 引用目录文件**
(仿 Claude Code / Codex):上传的文件与 `@` 引用的文件都会解析为文本后随下一条消息注入给模型。
## 功能
- **一键上传按钮**:输入框工具行的回形针按钮,点击**直接**打开系统文件选择框(多选),
与 Trae / Kimi 的上传入口一致。
- **拖拽上传**:把文件拖进窗口任意位置即可加入(非图片文件;纯图片拖放仍走内置图片条带)。
- **文件条带**:待发送文件以 chip 显示在输入框上方(名称 / 大小 / 解析状态 / 字符数),
可单个删除;发送后自动清空。
- **@ 引用目录文件**:在输入框输入 `@` 弹出候选菜单,**默认即当前对话框会话的工作区目录**
(dsh 会话创建时记录在 `Session.header.cwd`,经 `?session=` 按会话解析),按文件名
(相对路径)列候选并即时过滤;选中后在消息中插入引用,发送时把该文件解析文本内联进
用户消息(`[引用文件] path\n<内容>`),语义与上传一致。根目录优先级为
**当前会话工作区 > `rootDir` > 宿主进程 cwd**,多会话相互隔离。
- **多格式解析**:txt / md / 代码与配置文件(按扩展名或内容嗅探,编码自动检测:
UTF-8 / GBK / GB18030 / UTF-16,按 BOM 与字节特征回退)、
docx(Word 文本抽取)、xlsx / xls(SheetJS 逐工作表转 CSV)、pdf(pdf-parse 文本抽取)。
- **内容注入**:文件解析文本在下一轮第一步作为独立「上传文件」消息注入模型,
与用户消息一起进入会话日志(模型可见 ⟺ 已记录),并在注入后取走(attach-once)。
## 安装
```sh
dsh plugin --profile web add dsh-file-uploader
dsh web
```
> 重启后浏览器会通过 `/plugins/dsh-file-uploader/client.js` 拉取客户端 bundle(`dsh.client`
> 扫描 + `window.__DSH_BOOT__` 注入),宿主行 `dsh-file-uploader` 从 profile 解析 node 半。
> 卸载:`dsh plugin --profile web remove dsh-file-uploader`。
## 配置
行 `dsh-file-uploader` 的 `config` 全部可由 cordis.yml 覆盖(见 `cordis.patch.yml`):
| 键 | 默认 | 说明 |
| --- | --- | --- |
| `maxFileBytes` | 10485760 (10MB) | 单个文件大小上限 |
| `maxTotalBytes` | 52428800 (50MB) | 单会话待发送文件总大小上限 |
| `maxFilesPerMessage` | 20 | 单条消息最多附带文件数 |
| `maxCharsPerFile` | 200000 | 单个文件解析文本字符上限(超出截断) |
| `maxCharsPerMessage` | 500000 | 一次注入的文件内容总字符上限 |
| `ttlMs` | 1800000 (30min) | 上传文件内存保留时长,到期自动清理 |
| `authToken` | "" (空) | 上传路由鉴权令牌;空 = 仅同源校验(本机/局域网信任)。设置后除 `/dsh-file-upload/config` 外所有路由要求 `x-auth-token` 头匹配 |
| `persistDir` | "" (空) | 可选持久化目录;空 = 仅内存(TTL 后清理)。设置后待发送文件落盘,宿主重启/插件重载不丢(取走即删除) |
| `rootDir` | "" (空) | `@` 引用的**回退**根目录;仅当会话无工作区目录(`Session.header.cwd` 缺失)时生效。空 = 回退到宿主进程 `process.cwd()`。列出/读取被严格限制在解析出的根目录内(路径穿越会被拒绝) |
| `refMaxDepth` | 6 | `@` 列举时目录递归最大深度(根以下) |
| `refMaxEntries` | 2000 | `@` 列举的可引用文件条数上限(超出截断) |
### 部署示例(局域网 / 服务器)
```yaml
# profile cordis.patch.yml 覆盖行(按 id 整体替换 config)
- id: dsh-file-uploader
config:
authToken: "change-me-to-a-long-random-string"
persistDir: "/var/lib/dsh-file-uploader"
rootDir: "/path/to/your/project" # @ 的回退引用根(会话无工作区目录时)
maxFileBytes: 20971520
```
## 安全
上传路由 `/dsh-file-upload/*` 的鉴权分两档:
- **默认(未配置 `authToken`)**:同源校验——请求带 `Origin` 头时必须与请求 Host 同源,
否则 403。这挡住浏览器 CSRF(恶意网页借用户浏览器向本地服务上传),适用于本机/局域网信任环境。
- **配置 `authToken`**:所有路由(除 `/config` 引导端点)要求 `x-auth-token` 头匹配,否则 401。
令牌经 `GET /dsh-file-upload/config` 下发,客户端启动时拉取并附带。令牌模式兼容反向代理部署
(Origin 与内部 Host 不一致时仍可用)。
**边界**:令牌经同源端点下发,凡能访问服务者均可取到——它防的是「非页面客户端/偶然访问」,
不是强秘密。跨源网页因浏览器同源策略无法读取令牌响应,天然防 CSRF。真正的多用户/公网部署应在
反向代理层做认证(如 nginx basic auth / OAuth),并把 `authToken` 作为共享密钥配合使用。
## 持久化
默认待发送文件仅暂存于宿主内存(TTL 后清理,宿主重启即丢)。配置 `persistDir` 后:
- 每个会话一个 JSON 文件落盘,宿主重启/插件重载自动恢复;
- 文件被取走(发送注入)、清空或 TTL 过期时,对应落盘文件同步删除;
- 隐私注意:落盘文件包含文件解析后的文本,目录需受控(建议仅本机可读)。
## 开发与验证
```sh
pnpm install # 安装依赖(esbuild 构建脚本需放行:pnpm-workspace.yaml 已声明)
pnpm build # esbuild 产出 lib/index.js(宿主)+ lib/client.js(浏览器 bundle)
npx tsc --noEmit # 类型检查
# 解析逻辑验证(构造 txt/md/docx/xlsx/pdf 样例断言)
node node_modules/esbuild/bin/esbuild scripts/test-parse.mjs --bundle --format=esm --platform=node --external:fflate --external:xlsx --external:pdf-parse --outfile=.test/test-parse.mjs --log-level=warning
node .test/test-parse.mjs
# 宿主集成验证(真实 cordis Context + 桩 webServer:上传路由 / pre-step 注入 / attach-once / 删除 / 鉴权 / 持久化 / @ 引用路由)
node node_modules/esbuild/bin/esbuild scripts/test-integration.mjs --bundle --format=esm --platform=node --external:fflate --external:xlsx --external:pdf-parse --external:@deepseek-ai/* --outfile=.test/test-integration.mjs --log-level=warning
node .test/test-integration.mjs
# @ 引用目录数据源验证(列举 / 忽略规则 / 深度与条数截断 / 路径穿越防护)
node node_modules/esbuild/bin/esbuild scripts/test-dir.mjs --bundle --format=esm --platform=node --external:fflate --external:xlsx --external:pdf-parse --external:@deepseek-ai/* --outfile=.test/test-dir.mjs --log-level=warning
node .test/test-dir.mjs
# 组合配置验证(不启动服务)
dsh web --dump-config | Select-String dsh-file-uploader
```
浏览器 bundle 格式与官方 `@deepseek-ai/dsh-client-*` 一致:`window.__ModuleLoader__.load({ id, factory })`,
平台模块(react / cordis / ui-slots / ui-primitives / client-runtime)经 factory 的 `require` 从模块表解析。
## 已知限制
- **二进制/图片文件不解析**:非文本类型的文件上传会得到明确错误;混合拖放批次中的图片会被跳过
(纯图片批次仍走内置图片条带)。`@` 引用只列出可解析(text/docx/xlsx/pdf)的文件,不可解析的
不会出现在候选里。
- **`@` 引用限制在解析出的根目录内**:根为当前会话工作区(`Session.header.cwd`),
会话无工作区时才回退到 `rootDir` / 进程 cwd;`/dir`、`/file` 均做路径穿越防护
(`..` / 绝对路径被拒),默认忽略 `.git` / `node_modules` / `dist` / `build` / `coverage`
/ `.next` / `.nuxt` 与点文件点目录,并按 `refMaxDepth` / `refMaxEntries` 封顶。
- **扫描件 PDF**(图片型)无法抽取文本,上传会返回“PDF 未提取到文本”。
Install
dsh plugin --profile web add github:kamilhzn/dsh-file-upload
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-file-uploader from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.