dsh-workbuddy-files 0.1.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 +184 -0
- package/cordis.patch.yml +24 -0
- package/lib/client.js +984 -0
- package/lib/index.js +348 -0
- package/package.json +88 -0
- package/src/client/app.ts +130 -0
- package/src/client/at-source.ts +57 -0
- package/src/client/components/file-cards.tsx +47 -0
- package/src/client/components/overlay.tsx +36 -0
- package/src/client/components/pick-button.tsx +55 -0
- package/src/client/css.ts +32 -0
- package/src/client/definitions.ts +83 -0
- package/src/client/index.ts +15 -0
- package/src/client/lib/bus.ts +34 -0
- package/src/client/lib/drop.ts +235 -0
- package/src/client/lib/icons.ts +26 -0
- package/src/client/lib/insert.ts +107 -0
- package/src/client/lib/transfer.ts +113 -0
- package/src/client/types.ts +60 -0
- package/src/host/index.ts +253 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-workbuddy-files contributors
|
|
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,184 @@
|
|
|
1
|
+
# dsh-workbuddy-files
|
|
2
|
+
|
|
3
|
+
WorkBuddy 风格的 DeepSeek Harness (DSH) Web 插件:**拖拽 / 粘贴文件与文件夹 → 全屏接收遮罩 → 输入框原生引用气泡(File Pill)→ 发送后对话区文件卡片**,Agent 通过 `read_document` 工具或任意读取工具直接消费引用。
|
|
4
|
+
|
|
5
|
+
> 本插件同时存在两个形态:
|
|
6
|
+
> - **本目录**:可发布/可安装的正式插件包(TypeScript,Cordis,Host + Client 双半侧);
|
|
7
|
+
> - **动态演示版**:会话内动态 Cordis 插件(pluginId `wbdrop-1`),当前版本 pkg-5,与正式包行为一致,拖拽/粘贴/`@`/卡片立即可体验。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 功能对照(与 WorkBuddy 对齐)
|
|
12
|
+
|
|
13
|
+
| 需求 | 实现 |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| 拖拽任意文件到输入框(PDF/Word/Excel/图片/代码…) | ✅ 统一接管:**所有**文件拖入都显示本插件全屏「松开以接收文件」遮罩并走气泡管线 |
|
|
16
|
+
| 截图/文件粘贴(Ctrl+V) | ✅ 统一接管:图片与文件粘贴都走气泡管线(拖入即缓存 + 引用绝对路径) |
|
|
17
|
+
| `@` 弹出菜单搜索并引用文件/文件夹 | ✅ 工作区文件由 DSH 官方 `@` 源提供;本插件追加「文件缓存」分组 |
|
|
18
|
+
| 光标位置原生引用气泡 | ✅ 输入机 `insertReference` 铸造原生 occurrence 气泡(文件名 + 类型图标) |
|
|
19
|
+
| 气泡整体 Backspace/Delete 一次删除 | ✅ 原生 occurrence 语义(整段删除、撤销/重做一体) |
|
|
20
|
+
| 发送后对话区文件卡片 | ✅ `conversation.chat.turnTail` 链式槽按轮次渲染「📎 消息引用的文件」卡片,点击经 `openFile` 打开 |
|
|
21
|
+
| 引用附带绝对路径 | ✅ **拖入瞬间**按预分配路径(`~/.dsh-drops/<批次>/<相对路径>`)在光标处插入气泡,文件**后台异步缓存**,引用即 `@"C:\...\file.pdf"` |
|
|
22
|
+
| 文件夹拖拽保留目录树 | ✅ `webkitGetAsEntry` 递归遍历;顶层文件夹一个 `📁` 气泡,内部树结构原样落盘 |
|
|
23
|
+
| Agent 可读性 | ✅ Host 注册 `read_document` 工具:文本返回内容、目录返回树、图片/二进制/大文件返回元数据与建议 |
|
|
24
|
+
|
|
25
|
+
> 设计取舍:v1 曾实现 Chromium「FS Access API 零上传直引」(拖入时零字节、发送时才落地)。实测中发送时物化偶发「直引落地失败」会阻断发送,且浏览器安全模型拿不到拖入文件的绝对路径。为保证**拖入必可用、发送必成功**,现统一为「拖入即缓存 + 真实绝对路径」——localhost 场景下缓存即本地拷贝,无网络开销;零上传直引作为后续可选增强(见「后续方向」)。
|
|
26
|
+
|
|
27
|
+
## 架构
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
浏览器(Client 半侧) DSH Host(Node 进程)
|
|
31
|
+
┌──────────────────────────────┐ ┌──────────────────────────────┐
|
|
32
|
+
│ window 拖拽/粘贴监听(capture)│ │ 私有 RPC / webServer 路由 │
|
|
33
|
+
│ 任何文件拖拽 → stopPropagation│ │ save → 二进制落盘 │
|
|
34
|
+
│ (原生图片遮罩不再激活) │ ─────► │ list/stat/home │
|
|
35
|
+
│ 全屏遮罩「松开以接收文件」 │ │ │
|
|
36
|
+
│ │ │ ~/.dsh-drops/<批次>/<相对路径>│
|
|
37
|
+
│ 目录树解析(webkitGetAsEntry) │ │ (目录树结构保留,绝对路径) │
|
|
38
|
+
│ → 立即上传(拖入即缓存) │ │ │
|
|
39
|
+
│ │ │ read_document 工具 │
|
|
40
|
+
│ 气泡注入管线 │ │ 解析引用 → 内容/树/元数据 │
|
|
41
|
+
│ conversation.input.shell(id)│ └──────────────────────────────┘
|
|
42
|
+
│ shell.state.getSnapshot() │
|
|
43
|
+
│ shell.insertReference( │
|
|
44
|
+
│ {source:'workbuddy', │
|
|
45
|
+
│ ref, label, appearance}, │
|
|
46
|
+
│ {start,end,draftRev}) ← │ 草稿修订号 CAS + 重试(8 次)
|
|
47
|
+
│ │
|
|
48
|
+
│ @ 触发源「workbuddy」 │
|
|
49
|
+
│ candidates: drops.list → │
|
|
50
|
+
│ onPick → insert │
|
|
51
|
+
│ codec.serialize: 恒等 │
|
|
52
|
+
│ (拖入时已落地,发送必成功) │
|
|
53
|
+
│ │
|
|
54
|
+
│ conversationEvents 定义 │
|
|
55
|
+
│ workbuddy-file-refs │
|
|
56
|
+
│ (按轮次聚合用户消息引用) │
|
|
57
|
+
│ │
|
|
58
|
+
│ Slot 注册 │
|
|
59
|
+
│ shell.overlay(遮罩+toast) │
|
|
60
|
+
│ conversation.input.left │
|
|
61
|
+
│ (📎 选择文件/文件夹) │
|
|
62
|
+
│ conversation.chat.turnTail │
|
|
63
|
+
│ (文件卡片,select 精确匹配)│
|
|
64
|
+
└──────────────────────────────┘
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 为什么气泡是「原生」的
|
|
68
|
+
|
|
69
|
+
DSH 的输入状态机(`dsh-client-ui-input-trigger` + `InputMachine`)内置**引用气泡(occurrence)**机制:官方 `@` 文件引用、`/` 命令都以气泡呈现,气泡在草稿中占用原子区间 —— Backspace 一次删除、撤销/重做、提交时序列化都走同一套机器。插件通过 `conversation.input.shell(sessionId).insertReference(reference, span)` 铸造气泡(id 寻址服务面,不经过 scoped context),`span` 携带 `draftRev` 做 CAS:插入期间用户继续输入导致修订号变化时自动重读状态重试;非 plain 阶段(提交中)被拒则 toast 提示。
|
|
70
|
+
|
|
71
|
+
### 与原生图片轨道的互斥共存
|
|
72
|
+
|
|
73
|
+
DSH 原生的图片拖放轨道在 document 上监听 dragenter 并无条件显示全屏遮罩。本插件在 **window capture 阶段(传播第一站)** 从 dragenter 起 `stopPropagation()`,原生轨道感知不到被接管的拖拽,其遮罩不会激活,也不会因收不到收尾事件而卡住页面;本插件的遮罩自身在 dragleave/dragend/drop 时必然归零,页面随时可恢复。
|
|
74
|
+
|
|
75
|
+
### 可靠性设计
|
|
76
|
+
|
|
77
|
+
- **拖入即插气泡**:最终缓存路径在拖入时即可确定(`<dropsRoot>/<批次>/<文件名或目录树>`),气泡**立即**插入光标处,不等上传;文件落盘在后台异步进行(本地写盘,用户无感),完成/失败以 toast 告知;
|
|
78
|
+
- 发送时 `codec.serialize` 恒等,**发送消息不可能因文件未落地而失败**;极端情况下(超大文件拖入后立即发送)Agent 读取时文件可能仍在写入,`read_document` 对不存在路径返回明确错误,稍候重试即可;
|
|
79
|
+
- 气泡插入失败(输入区忙/无会话)自动降级:直接写草稿文本(`setDraft`)/ 聚焦 textarea 时 `execCommand`,保证输入框有内容;
|
|
80
|
+
- Host 写盘加固:fs 服务写 base64 临时文本 + PowerShell 从文件解码写盘(不依赖 stdin 管道),目录自动创建、临时文件自动清理;
|
|
81
|
+
- 单文件上限(动态版 ~31MB / 正式包 256MB 可配)超出时跳过并提示,其余文件正常接收。
|
|
82
|
+
|
|
83
|
+
## 项目结构
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
dsh-workbuddy-files/
|
|
87
|
+
├── package.json # dsh.client 声明 + dsh.bundle.patch 声明
|
|
88
|
+
├── cordis.patch.yml # bundle patch:把 host 半侧挂进配置树
|
|
89
|
+
├── tsconfig.json
|
|
90
|
+
├── tsdown.config.ts # 构建 lib/index.js(host)+ lib/client.js(client)
|
|
91
|
+
├── README.md
|
|
92
|
+
└── src/
|
|
93
|
+
├── host/
|
|
94
|
+
│ └── index.ts # /workbuddy-drops 路由 + read_document 工具
|
|
95
|
+
└── client/
|
|
96
|
+
├── index.ts # 副作用外壳:__ModuleLoader__.load
|
|
97
|
+
├── app.ts # factory:require('react') + 组装插件
|
|
98
|
+
├── at-source.ts # @ 触发源(文件缓存分组)
|
|
99
|
+
├── definitions.ts # 会话事件定义 + turnTail selector
|
|
100
|
+
├── css.ts
|
|
101
|
+
├── types.ts
|
|
102
|
+
├── components/
|
|
103
|
+
│ ├── overlay.tsx # 全屏拖拽遮罩 + toast
|
|
104
|
+
│ ├── pick-button.tsx # 📎 选择文件/文件夹
|
|
105
|
+
│ └── file-cards.tsx # 对话区文件卡片
|
|
106
|
+
└── lib/
|
|
107
|
+
├── bus.ts # 拖拽/提示总线
|
|
108
|
+
├── insert.ts # 气泡注入管线(核心)
|
|
109
|
+
├── drop.ts # 拖拽/粘贴处理
|
|
110
|
+
├── transfer.ts # 目录树遍历 + 上传 + 引用格式
|
|
111
|
+
└── icons.ts # 类型图标
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 安装
|
|
115
|
+
|
|
116
|
+
前置:Node 18+、pnpm(`dsh plugin` 内部转发给 pnpm)。
|
|
117
|
+
|
|
118
|
+
### 方式一:本地项目安装(开发/验证,推荐先走这条)
|
|
119
|
+
|
|
120
|
+
```powershell
|
|
121
|
+
cd F:\dsh工作空间\dsh-workbuddy-files
|
|
122
|
+
npm install # 安装 tsdown(0.22+)/ typescript 等构建依赖
|
|
123
|
+
npm run bundle # 产出 lib/index.js 与 lib/client.js
|
|
124
|
+
npm run typecheck # 可选:类型检查(已通过)
|
|
125
|
+
|
|
126
|
+
# link 安装:符号链接(改完代码重新 bundle + 重启 dsh 即生效)
|
|
127
|
+
dsh plugin --profile web add link:..\dsh-workbuddy-files
|
|
128
|
+
# 或拷贝安装(发布前验证打包产物用):
|
|
129
|
+
dsh plugin --profile web add file:..\dsh-workbuddy-files
|
|
130
|
+
|
|
131
|
+
dsh web # 重启 web 界面
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`dsh plugin add` 会:在 `$DSH_HOME/profiles/web` 里 `pnpm add` 该包,并因为本包声明了 `dsh.bundle.patch`,自动把 `dsh-workbuddy-files` 追加进 profile 的 `dsh.profile.bundles` 层栈(可在 `~/.dsh/profiles/web/package.json` 中核对)。卸载:`dsh plugin --profile web remove dsh-workbuddy-files`。
|
|
135
|
+
|
|
136
|
+
### 方式二:npm 发布后安装
|
|
137
|
+
|
|
138
|
+
```powershell
|
|
139
|
+
dsh plugin --profile web add dsh-workbuddy-files
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 配置(可选)
|
|
143
|
+
|
|
144
|
+
`cordis.patch.yml` 中 host 半侧的 `config`(浏览器半侧目前收不到配置,走代码默认值):
|
|
145
|
+
|
|
146
|
+
```yaml
|
|
147
|
+
config:
|
|
148
|
+
dropsDir: '~/.dsh-drops' # 缓存根目录
|
|
149
|
+
maxFileBytes: 268435456 # 单文件上限(默认 256MB)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 依赖说明
|
|
153
|
+
|
|
154
|
+
- Host 半侧硬依赖 `webServer` 服务(`inject: ['webServer']`),写文件用 `node:fs`(真实插件非沙箱环境,与 dsh-pet 同款写法);
|
|
155
|
+
- Client 半侧在运行时探测 `sessions` / `conversation` / `inputTriggers` / `conversationEvents` / `slots` 服务,缺失时优雅降级(例如无 `conversation` 时气泡退化为纯文本插入)。
|
|
156
|
+
|
|
157
|
+
### 沙箱模式说明
|
|
158
|
+
|
|
159
|
+
- **正式插件包**:Host 半侧用 `node:fs` 直写,不经过 DSH 的沙箱化 shell/fs 服务(沙箱只约束模型工具的执行,不约束插件自身的 Node 代码)——因此 `read-only` / `workspace-write` / `danger-full-access` 任何会话模式下,拖入即缓存都能正常工作;
|
|
160
|
+
- **动态演示版**:动态插件环境没有 Node 文件系统,二进制写盘需借用沙箱化 shell 服务;当部署的沙箱 runner 不可用(如 Windows ACL runner 配置异常)或缓存目录位于会话 workspace 之外时,写盘命令需要 `danger-full-access` 策略(演示版已显式携带,与当前会话文件策略一致);
|
|
161
|
+
- **折中方案**:将 `dropsDir` 配置到 workspace 内(如 `<workspace>/.dsh-drops`),workspace-write 沙箱即可放行;或让插件先继承会话模式、被沙箱拒绝时再升级重试并提示。
|
|
162
|
+
|
|
163
|
+
## 测试步骤(逐条验收)
|
|
164
|
+
|
|
165
|
+
1. **拖拽文件**:从资源管理器把 `report.pdf` 拖入页面 → 全屏虚线遮罩「松开以接收文件」(不是原生的图片遮罩)→ 松手 → 输入框光标处出现 `📕 report.pdf` 气泡;`~/.dsh-drops/drop-*` 下文件已落地。
|
|
166
|
+
2. **拖拽图片**:拖入一张 PNG → 同一个遮罩 → `🖼️` 气泡(引用 `~/.dsh-drops` 下的真实路径);Agent 可用 `read_image` 查看。
|
|
167
|
+
3. **光标位置**:输入「请分析 」后把光标停在句号前拖入文件 → 气泡插在光标处,而非末尾。
|
|
168
|
+
4. **气泡整体删除**:气泡前/后按 Backspace / Delete → 整个气泡一次删除;Ctrl+Z 完整还原。
|
|
169
|
+
5. **粘贴**:资源管理器复制一个 `.py` 文件 → 输入框 Ctrl+V → `💻` 代码气泡;微信/QQ 截图 Ctrl+V → `🖼️` 图片气泡(统一体验)。
|
|
170
|
+
6. **文件夹**:拖入一个含子目录的文件夹 → 一个 `📁` 文件夹气泡;`~/.dsh-drops/<批次>/<文件夹>/` 树结构完整。
|
|
171
|
+
7. **取消拖拽**:拖入后把文件拖出窗口 / 按 Esc → 遮罩消失,页面完全恢复,无残留覆盖。
|
|
172
|
+
8. **@ 菜单**:输入 `@` → 官方「文件与文件夹」组可搜工作区文件;「文件缓存 · ~/.dsh-drops」组列出拖入过的文件,可搜索、可选中插入。
|
|
173
|
+
9. **文件卡片**:发送带引用的消息,等本轮结束 → 轮次尾部出现「📎 消息引用的文件」卡片(图标+名称+大小),点击卡片打开文件。
|
|
174
|
+
10. **read_document**:对 Agent 说「用 read_document 读取我刚引用的文件并总结」→ Agent 调用 `read_document` 拿到文本内容(或目录树/元数据/建议)。
|
|
175
|
+
11. **超大文件**:拖入一个 >31MB(动态版)的文件 → toast 明确提示「超过 31MB 上限」,其余文件正常接收。
|
|
176
|
+
12. **卸载清理**:`dsh plugin --profile web remove dsh-workbuddy-files` 后重启,遮罩/按钮/卡片全部消失;`~/.dsh-drops` 按需手动清理。
|
|
177
|
+
|
|
178
|
+
## 已知限制与后续方向
|
|
179
|
+
|
|
180
|
+
- 动态演示版上传走 `host.call` + base64(单文件 ≤ ~31MB),正式包走 fetch 二进制流(≤ 256MB 可配);
|
|
181
|
+
- turnTail 为链式槽(先匹配者胜出):当同一轮次既有用户引用又有 Agent 产出文件时,与官方「产出文件」行按注册顺序互斥 —— 扩展:合并渲染或注册到更早优先级;
|
|
182
|
+
- 二进制/超大文件的内容读取依赖宿主侧转换(read_document 返回元数据与建议);
|
|
183
|
+
- 未做多语言字典注册(当前界面文案为中文),扩展点:`locale.register('workbuddy', { zh, en })`;
|
|
184
|
+
- 零上传直引(FS Access API 句柄 + 发送时物化 + 持久化句柄到 IndexedDB)可作为可选增强回补:默认仍走「拖入即缓存」的可靠路径,直引仅用于超大目录/远程部署场景。
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# dsh-workbuddy-files 的 bundle patch —— 把插件挂进 DSH 配置树
|
|
3
|
+
# ============================================================================
|
|
4
|
+
#
|
|
5
|
+
# 【这是什么】本文件是 dsh-workbuddy-files 的“挂载声明”。DSH 启动时按 bundle
|
|
6
|
+
# 层栈把各个插件的 patch 叠加起来组成配置树。
|
|
7
|
+
# 【工作原理】
|
|
8
|
+
# - insert:往配置树里添加新的插件行(entry)
|
|
9
|
+
# - 每个 entry 有 id(行标识,patch 覆盖时按它定位)和 name(插件模块)
|
|
10
|
+
# - name: 'dsh-workbuddy-files' 会解析包入口 lib/index.js(Host 半侧)
|
|
11
|
+
# - 因为 package.json 声明了 dsh.client,DSH 的模块系统同时会把本包扫描为
|
|
12
|
+
# “浏览器插件”,提供 /plugins/dsh-workbuddy-files/client.js 给页面
|
|
13
|
+
# 【config 说明】这里写的 config 会传给 host 半侧的 apply(ctx, config)。
|
|
14
|
+
# (浏览器半侧目前收不到这些配置,client 里的参数走代码默认值)
|
|
15
|
+
# ============================================================================
|
|
16
|
+
|
|
17
|
+
- insert:
|
|
18
|
+
- id: workbuddy-files # 插件行 id(唯一)
|
|
19
|
+
name: 'dsh-workbuddy-files' # 模块名:解析到 lib/index.js
|
|
20
|
+
config:
|
|
21
|
+
# 拖拽/粘贴文件的落地目录(默认 ~/.dsh-drops;支持 ~ 前缀)
|
|
22
|
+
dropsDir: '~/.dsh-drops'
|
|
23
|
+
# 单文件大小上限(字节,默认 31MB —— host.call JSON 通道的保守上限)
|
|
24
|
+
maxFileBytes: 32505856
|