@wszhoho/dsh-file-attachment 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +76 -0
- package/cordis.patch.yml +2 -2
- package/lib/client.js +8 -8
- package/lib/index.js +10 -10
- package/package.json +4 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wszhoho
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# dsh-file-attachment
|
|
2
|
+
|
|
3
|
+
DeepSeek Harness (dsh) web GUI 插件:在会话输入框中**拖入或 Ctrl+V 粘贴文档(支持多文件,一次可拖入/粘贴多个文件)**时,浏览器端批量读取全文,保存到**当前会话工作区根目录下的 `.dsh-file-attachment/`**,并在输入框光标处插入 `@<保存副本绝对路径>` 引用。agent 读取 `@路径` 时直接读到落盘副本,不再依赖原始文件位置。
|
|
4
|
+
|
|
5
|
+
图片文件完全沿用 dsh web 既有草稿图片流程,不拦截、不改动。
|
|
6
|
+
|
|
7
|
+
## 行为一览
|
|
8
|
+
|
|
9
|
+
| 操作 | 行为 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| 拖入/粘贴 **文档**(docx/xlsx/pdf/md/txt/rpk 等非图片文件) | 浏览器读全文 → base64 → 宿主落盘 `<会话工作区>/.dsh-file-attachment/<时间戳>-<文件名>` → 光标插入 `@绝对路径`,底部 toast 提示保存目录 |
|
|
12
|
+
| 一次拖入/粘贴 **多个文件**(支持多文件) | 批量处理:逐个读取全文 → base64 → 落盘 → 在光标处一次性插入全部 `@` 引用,toast 汇总保存数量与目录 |
|
|
13
|
+
| 拖入 **目录** | 拒绝,toast 提示「不支持拖入目录」,不插入引用 |
|
|
14
|
+
| 拖入/粘贴 **图片** | 不拦截,原样走 dsh web 既有草稿图片流程(零变化) |
|
|
15
|
+
| 粘贴 **纯文本**(含从地址栏复制的目录路径字符串) | 不改写,完全原生行为 |
|
|
16
|
+
| 重复拖入同名文件(同一秒内) | 追加 `-1`、`-2` 序号,绝不覆盖 |
|
|
17
|
+
| 单文件 > 50MB | 跳过并提示 |
|
|
18
|
+
|
|
19
|
+
### 文件命名
|
|
20
|
+
|
|
21
|
+
`<时间戳>-<清洗后文件名>`,例如 `2026-02-11T15-04-05-说明.docx`;时间戳为 `2026-02-11T15-04-05` 格式(去掉冒号/毫秒/Z,各平台文件名安全且可排序)。同名冲突在扩展名前追加 `-N`。
|
|
22
|
+
|
|
23
|
+
### 项目根解析
|
|
24
|
+
|
|
25
|
+
保存目录的根 = **当前会话工作区**(`session.header.cwd`),回退顺序:会话 cwd → `sandboxPolicy.workspaceRoot` → `process.cwd()`。每个项目(工作区)都各自维护自己的 `.dsh-file-attachment/` 目录。
|
|
26
|
+
|
|
27
|
+
### 跨平台
|
|
28
|
+
|
|
29
|
+
- 保存路径用 Node `path.join`(平台分隔符自适应)
|
|
30
|
+
- 时间戳/文件名清洗同时对 Windows 与 POSIX 命名规则安全
|
|
31
|
+
- Host 半直接 `node:fs/promises` 写字节,无 shell 依赖
|
|
32
|
+
|
|
33
|
+
## 安装(官方 `dsh plugin` 方式,推荐)
|
|
34
|
+
|
|
35
|
+
本仓库是一个标准 dsh 插件包(声明了 `dsh.bundle.patch`)。用官方 CLI 安装,pnpm 会自动处理依赖、并把本包加入 profile 的 `dsh.profile.bundles` 层:
|
|
36
|
+
|
|
37
|
+
```powershell
|
|
38
|
+
# 在仓库父目录执行(<parent> 换成放置仓库的实际目录,如 E:\wszhoho)
|
|
39
|
+
cd <parent>
|
|
40
|
+
dsh plugin --profile web add ./dsh-file-attachment
|
|
41
|
+
# 重启 web GUI:dsh web(或从托盘重启)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- `dsh plugin add` 把参数转发给 profile 目录的 pnpm 执行,`./dsh-file-attachment` 相对路径等价 pnpm link;
|
|
45
|
+
- 安装后自动进入 `dsh.profile.bundles`,无需手改 profile `package.json`;
|
|
46
|
+
- 改完 `lib/*.js` 后重启 `dsh web` 即生效,适合边改边验;
|
|
47
|
+
- 推送到 GitHub 后,其他人用 `dsh plugin --profile web add github:<你的账号>/dsh-file-attachment` 即可安装;
|
|
48
|
+
- 升级:`dsh plugin --profile web update dsh-file-attachment`。
|
|
49
|
+
|
|
50
|
+
## 架构
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
packages/dsh-file-attachment/
|
|
54
|
+
├── package.json # dsh.client 声明 + bundle patch 指向
|
|
55
|
+
├── cordis.patch.yml # bundle 补丁:插入本插件行
|
|
56
|
+
└── lib/
|
|
57
|
+
├── index.js # Host 半:webServer 路由(POST /dsh-file-attachment/save),node:fs/promises 写盘
|
|
58
|
+
└── client.js # Client 半:document 级 capture drop/paste/dragenter 监听 + 槽组件
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- **Host 半**:`webServer.register` 前缀路由 `/dsh-file-attachment`,POST `/dsh-file-attachment/save` 接收 `{name, data(base64), sessionId}`,base64 解码后用 `node:fs/promises` 写盘到会话工作区 `.dsh-file-attachment/`,返回 `{ok, value:{path,dir,name,size}}`。
|
|
62
|
+
- **Client 半**:`conversation.composer.dock`(list 槽,同步桥状态,不渲染文本)+ `shell.overlay`(list 槽,toast 通知展示),document 级 capture 监听先于应用 bubble 监听执行。
|
|
63
|
+
- **通知**:`shell.overlay` frame-wide 浮动 toast(不参与文档流,不破坏布局),4 秒自动消失。
|
|
64
|
+
- **浮层**:拖入任何文件时 capture 阶段拦截应用自带「拖入图片…」DropOverlay(文案面向图片,对文档是误导)。
|
|
65
|
+
|
|
66
|
+
## 开发说明
|
|
67
|
+
|
|
68
|
+
- `lib/*.js` 使用 **UTF-8 with BOM**(项目约定);`package.json`、`cordis.patch.yml` 使用 **UTF-8 无 BOM**。
|
|
69
|
+
- Host 半用 `webServer.register({ kind: 'prefix', path: '/dsh-file-attachment', handler })` 收文件(参考 `dsh-upload-file` 同架构的 `/dsh-upload` 路由),纯 ESM 无构建,不依赖装饰器/远程反射。
|
|
70
|
+
- Client 半保存用 `fetch('/dsh-file-attachment/save', { method: 'POST', body: JSON.stringify({ name, data, sessionId }) })`,信封为 `{ ok, value | error }`。
|
|
71
|
+
- 项目根 = 会话工作区(`session.header.cwd` → `sandboxPolicy.workspaceRoot` → `process.cwd()` 兜底)。
|
|
72
|
+
- 50MB 上限两侧一致(client 跳过 + host 校验)。
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
MIT
|
package/cordis.patch.yml
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# dsh-
|
|
1
|
+
# dsh-file-attachment 补丁行:挂载本插件(Host 半提供 file-attachment 服务写临时目录;Client 半监听拖拽/粘贴)
|
|
2
2
|
- insert:
|
|
3
|
-
- id: dsh-
|
|
3
|
+
- id: dsh-file-attachment
|
|
4
4
|
name: '@wszhoho/dsh-file-attachment'
|
package/lib/client.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
// @
|
|
1
|
+
// @wszhoho/dsh-file-attachment 浏览器半(client 半,全部功能在此)。
|
|
2
2
|
// 拖放/粘贴文件进输入框:
|
|
3
3
|
// 1) 纯图片 —— 不拦截事件,原样交给既有图片拖放路径(行为零变化);
|
|
4
4
|
// 2) 文档(非图片文件)—— capture 阶段接管:浏览器端读取全文 → base64 →
|
|
5
|
-
//
|
|
5
|
+
// fetch POST /dsh-file-attachment/save 落盘到 <会话工作区>/.dsh-file-attachment/ →
|
|
6
6
|
// 在草稿光标处插入 @绝对路径 引用;粘贴文件走同一核心;
|
|
7
7
|
// 3) 目录 —— 拒绝并 toast 提示「不支持拖入目录」(需求确认:不插引用);
|
|
8
8
|
// 4) 纯文本粘贴(含目录路径字符串)—— 完全原生,不做任何改写。
|
|
9
9
|
// 5) 拖入任何文件时在 capture 阶段拦截应用自带「拖入图片…」DropOverlay 浮层。
|
|
10
10
|
// 通知:shell.overlay 浮动 toast(frame-wide,fixed 定位,点击穿透,不破坏布局)。
|
|
11
11
|
window.__ModuleLoader__.load({
|
|
12
|
-
id: '@
|
|
12
|
+
id: '@wszhoho/dsh-file-attachment',
|
|
13
13
|
factory: (require) => {
|
|
14
14
|
const module = { exports: {} }
|
|
15
15
|
const exports = module.exports
|
|
@@ -17,7 +17,7 @@ window.__ModuleLoader__.load({
|
|
|
17
17
|
const react = require('react')
|
|
18
18
|
|
|
19
19
|
// Client 服务依赖:槽位 / 输入机 / 会话(与官方 dsh-upload-file 同范式)。
|
|
20
|
-
// 保存文件不走 remote,而是 fetch POST 宿主 webServer 路由 /dsh-
|
|
20
|
+
// 保存文件不走 remote,而是 fetch POST 宿主 webServer 路由 /dsh-file-attachment/save。
|
|
21
21
|
const inject = [
|
|
22
22
|
"slots",
|
|
23
23
|
"conversation",
|
|
@@ -55,7 +55,7 @@ window.__ModuleLoader__.load({
|
|
|
55
55
|
if (typeof fetch !== 'function') throw new Error('当前环境无 fetch,无法上传')
|
|
56
56
|
let response
|
|
57
57
|
try {
|
|
58
|
-
response = await fetch('/dsh-
|
|
58
|
+
response = await fetch('/dsh-file-attachment/save', {
|
|
59
59
|
method: 'POST',
|
|
60
60
|
headers: { 'content-type': 'application/json' },
|
|
61
61
|
body: JSON.stringify({ name: fileName, data: b64, sessionId: sessionId }),
|
|
@@ -428,11 +428,11 @@ window.__ModuleLoader__.load({
|
|
|
428
428
|
const slots = ctx.get('slots')
|
|
429
429
|
if (slots !== undefined) {
|
|
430
430
|
slots.inject('conversation.composer.dock', () => slots.register(
|
|
431
|
-
{ name: 'conversation.composer.dock', id: 'dsh-
|
|
431
|
+
{ name: 'conversation.composer.dock', id: 'dsh-file-attachment', order: 300 },
|
|
432
432
|
FaBridge
|
|
433
433
|
))
|
|
434
434
|
slots.inject('shell.overlay', () => slots.register(
|
|
435
|
-
{ name: 'shell.overlay', id: 'dsh-
|
|
435
|
+
{ name: 'shell.overlay', id: 'dsh-file-attachment-toast', order: 100 },
|
|
436
436
|
FaToast
|
|
437
437
|
))
|
|
438
438
|
}
|
|
@@ -447,7 +447,7 @@ window.__ModuleLoader__.load({
|
|
|
447
447
|
document.removeEventListener('drop', onDrop, true)
|
|
448
448
|
document.removeEventListener('paste', onPaste, true)
|
|
449
449
|
}
|
|
450
|
-
}, 'dsh-
|
|
450
|
+
}, 'dsh-file-attachment: document dragenter/drop/paste listeners')
|
|
451
451
|
}
|
|
452
452
|
|
|
453
453
|
exports.apply = apply
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// @
|
|
2
|
-
// Client 半读取文件全文(base64)后 POST /dsh-
|
|
3
|
-
// 写入 <会话工作区根>/.dsh-
|
|
1
|
+
// @wszhoho/dsh-file-attachment Host 半:webServer HTTP 路由方案(参考 dsh-upload-file 同架构)。
|
|
2
|
+
// Client 半读取文件全文(base64)后 POST /dsh-file-attachment/save 到这里,
|
|
3
|
+
// 写入 <会话工作区根>/.dsh-file-attachment/<时间戳>-<文件名> 并返回 { path, dir, name, size }。
|
|
4
4
|
// 项目根解析:Client 传 sessionId → sessions.get(sessionId).header.cwd →
|
|
5
5
|
// sandboxPolicy.workspaceRoot → process.cwd()(三平台绝对路径,Node path.join 自适应分隔符)。
|
|
6
6
|
// 纯 ESM 无构建:不依赖 TypertRemoteService/装饰器,原生 Node 可直接加载。
|
|
@@ -8,11 +8,11 @@ import { access, mkdir, writeFile } from 'node:fs/promises'
|
|
|
8
8
|
import { join } from 'node:path'
|
|
9
9
|
|
|
10
10
|
/** 本包声明依赖的 Host 服务。 */
|
|
11
|
-
export const name = 'dsh-
|
|
11
|
+
export const name = 'dsh-file-attachment'
|
|
12
12
|
export const inject = ['webServer']
|
|
13
13
|
|
|
14
14
|
// 临时目录名:落在会话工作区根下;每个项目都能看到自己引用过的文档
|
|
15
|
-
const ATTACHMENT_DIR = '.dsh-
|
|
15
|
+
const ATTACHMENT_DIR = '.dsh-file-attachment'
|
|
16
16
|
// 单文件上限 50MB(与 Client 半一致)
|
|
17
17
|
const MAX_BYTES = 50 * 1024 * 1024
|
|
18
18
|
// 请求体上限:base64(50MB) + 信封余量
|
|
@@ -140,16 +140,16 @@ async function handleSave(root, body) {
|
|
|
140
140
|
return { path: target, dir, name: t + '-' + safe, size: bytes.length }
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
/** 注册 /dsh-
|
|
143
|
+
/** 注册 /dsh-file-attachment 前缀路由(save POST)。 */
|
|
144
144
|
function registerRoutes(ctx) {
|
|
145
145
|
const webserver = ctx.get('webServer')
|
|
146
146
|
if (webserver === undefined) return
|
|
147
147
|
webserver.register({
|
|
148
148
|
kind: 'prefix',
|
|
149
|
-
path: '/dsh-
|
|
149
|
+
path: '/dsh-file-attachment',
|
|
150
150
|
handler: async (req, res) => {
|
|
151
151
|
const pathname = new URL(req.url ?? '/', 'http://x').pathname
|
|
152
|
-
if (req.method === 'POST' && pathname === '/dsh-
|
|
152
|
+
if (req.method === 'POST' && pathname === '/dsh-file-attachment/save') {
|
|
153
153
|
const body = await readJsonBody(req, MAX_BODY_BYTES)
|
|
154
154
|
if (body === null || typeof body !== 'object') {
|
|
155
155
|
json(res, { ok: false, error: '请求体必须是 JSON(68MB 内)' }, 400)
|
|
@@ -173,7 +173,7 @@ function registerRoutes(ctx) {
|
|
|
173
173
|
return
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
|
-
json(res, { ok: false, error: 'only POST /dsh-
|
|
176
|
+
json(res, { ok: false, error: 'only POST /dsh-file-attachment/save is allowed' }, 405)
|
|
177
177
|
},
|
|
178
178
|
})
|
|
179
179
|
}
|
|
@@ -183,5 +183,5 @@ function registerRoutes(ctx) {
|
|
|
183
183
|
*/
|
|
184
184
|
export function apply(ctx, config = {}) {
|
|
185
185
|
registerRoutes(ctx)
|
|
186
|
-
console.log('[dsh-
|
|
186
|
+
console.log('[dsh-file-attachment] host loaded (webServer routes)')
|
|
187
187
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wszhoho/dsh-file-attachment",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "拖拽/粘贴文件(支持多文件)进输入框:图片沿用原有草稿图片流程;文档读取内容存入项目根 .dsh-file-attachment 临时目录并插入 @绝对路径引用",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
7
7
|
"plugin",
|
|
8
8
|
"file-attachment",
|
|
9
9
|
"upload",
|
|
10
|
-
"drag-drop"
|
|
10
|
+
"drag-drop",
|
|
11
|
+
"multi-file"
|
|
11
12
|
],
|
|
12
13
|
"type": "module",
|
|
13
14
|
"main": "lib/index.js",
|