dsh-fish-orb 0.0.0-stage → 0.1.1
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/CHANGELOG.md +48 -0
- package/LICENSE +21 -0
- package/README.md +112 -2
- package/cordis.patch.yml +9 -0
- package/docs/DEVELOPMENT.md +178 -0
- package/icon.svg +6 -0
- package/lib/client.js +1315 -0
- package/lib/index.js +1203 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +62 -4
- package/tools/smoke-test.mjs +122 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# 更新日志 · dsh-fish-orb
|
|
2
|
+
|
|
3
|
+
## 0.1.1(2026-10-19)
|
|
4
|
+
|
|
5
|
+
- 补 `repository` / `homepage` / `bugs` 字段:npm 页面与 README 能点回 GitHub 仓库
|
|
6
|
+
- 无功能变更(纯元数据)
|
|
7
|
+
|
|
8
|
+
## 0.1.0(2026-10-18)
|
|
9
|
+
|
|
10
|
+
首个可用版本。分阶段落地(P0 → P5),每段都实测过再往上垒。
|
|
11
|
+
|
|
12
|
+
### 悬浮球
|
|
13
|
+
|
|
14
|
+
- 页面内常驻圆角矩形小球(默认 44×44),位置在右下偏上 —— **刻意避开小鲸鱼挂件**
|
|
15
|
+
- **可拖动**:按住移动,松手落盘;位移 < 4px 视为点击(拖动不会误触展开)
|
|
16
|
+
- **位置与大小可自主调节**:位置靠拖动,大小用面板里的滑块(32–64px)
|
|
17
|
+
- 位置、大小、目标会话都存进 `$DSH_HOME/.fish-orb-state.json`,**刷新/重启后不变**
|
|
18
|
+
- 禁用插件时 DOM 会被撤掉(关掉开关球就消失)
|
|
19
|
+
|
|
20
|
+
### 面板
|
|
21
|
+
|
|
22
|
+
- 点球展开 / 再点收起 / `Esc` 关闭
|
|
23
|
+
- 靠右边缘时向左展开,靠左时向右展开,不超出视口
|
|
24
|
+
- 输入框:`Enter` 发送,`Shift+Enter` 换行(`isComposing` 期间不误发)
|
|
25
|
+
|
|
26
|
+
### 派发
|
|
27
|
+
|
|
28
|
+
- 面板里选**目标会话**(只列工作区里真实存在的会话,并标注「运行中」/「未打开」)
|
|
29
|
+
- 发送后通过 `agent.followup()` 把消息**真投递**进目标会话
|
|
30
|
+
- 目标会话没打开 / 没选目标会话 → **明确报错**,不静默失败
|
|
31
|
+
- 投递走 `createUserMessage()`(动态 import + 等价兜底),不会写出缺 `role` 的坏消息
|
|
32
|
+
|
|
33
|
+
### 通知
|
|
34
|
+
|
|
35
|
+
- 面板关着时收到新任务 → 球右上角**未读小红点**
|
|
36
|
+
- 打开面板即视为已读(红点消失),任务列表显示 `result` 摘要
|
|
37
|
+
|
|
38
|
+
### 未做(明确标记)
|
|
39
|
+
|
|
40
|
+
- ❌ **会话 AI 的真实回答回流** —— 需要读会话消息流,接口不确定、风险高。当前 `result` 只表示"已投递"
|
|
41
|
+
- ❌ 跨应用置顶 / 跨应用划词 / 桌面控制(形态限制,见 `docs/DEVELOPMENT.md` 第 24 条)
|
|
42
|
+
- ❌ 划词工具栏(P6,可选,未做)
|
|
43
|
+
|
|
44
|
+
### 已知限制
|
|
45
|
+
|
|
46
|
+
- 只有**正在运行**的会话能收到派发(DSH 的会话必须先打开)
|
|
47
|
+
- 派发依赖 `agents` 服务;会话列表依赖 `sessionQuery`(缺失时退到 `sessionController` / `agents.list`)
|
|
48
|
+
- 只在 **Windows + DSH 0.2.1-rc.2** 上验证过
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 k423Charotte
|
|
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
CHANGED
|
@@ -1,3 +1,113 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-fish-orb · 小肥鱼球
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> DSH 页面内的悬浮球:接一句话、派给后台会话跑、把结果收回来。
|
|
4
|
+
> **只在 DSH 页面里,不碰桌面控制** —— 跨应用置顶/划词需要原生窗口与系统级钩子,Web 插件做不到。
|
|
5
|
+
|
|
6
|
+
## 现在能做什么(0.1.0)
|
|
7
|
+
|
|
8
|
+
| 能力 | 状态 |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| 页面内常驻小球,**可拖动**、边缘不跑出视口 | ✅ |
|
|
11
|
+
| 位置 + 大小**可自主调节**,落盘后刷新/重启不变 | ✅ |
|
|
12
|
+
| 点球展开面板,`Esc` 关闭,靠边自动反向展开 | ✅ |
|
|
13
|
+
| 面板里选**目标会话**(标注「运行中」/「未打开」) | ✅ |
|
|
14
|
+
| 发一句话 → **真投递**进目标会话 | ✅ |
|
|
15
|
+
| 目标会话没打开 / 没选 → **明确报错** | ✅ |
|
|
16
|
+
| 面板关着时收到新任务 → 球上**未读小红点** | ✅ |
|
|
17
|
+
| 会话 AI 的**真实回答**回流 | ❌ 未做(需读会话消息流) |
|
|
18
|
+
| 划词工具栏 | ❌ 未做(可选) |
|
|
19
|
+
|
|
20
|
+
## 安装
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
plugin_manager action: install_bundle target: <本包目录的绝对路径>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
看返回的 **`application`** 字段(`applied` 才算生效),别拿"终端没报错"当成功。
|
|
27
|
+
|
|
28
|
+
- 改 `lib/client.js` → **刷新页面**(F5)生效
|
|
29
|
+
- 改 `lib/index.js`(Host 半)→ **必须重启 DSH**
|
|
30
|
+
- 重装/升级:对同一个 `target` 再 `install_bundle` 一次
|
|
31
|
+
|
|
32
|
+
## 用法
|
|
33
|
+
|
|
34
|
+
1. **拖动**:按住球移动,松手保存。位移 < 4px 算点击(不会误触展开)
|
|
35
|
+
2. **打开面板**:轻点球
|
|
36
|
+
3. **选会话**:面板里「派给哪个会话」——只有标了「运行中」的会话**能收到派发**(DSH 里没打开的会话派不出去)
|
|
37
|
+
4. **发送**:`Enter` 发送,`Shift+Enter` 换行
|
|
38
|
+
5. **调大小**:面板里的「球大小」滑块(32–64px)
|
|
39
|
+
6. **关面板**:再点一次球,或按 `Esc`
|
|
40
|
+
|
|
41
|
+
## 状态文件
|
|
42
|
+
|
|
43
|
+
`$DSH_HOME/.fish-orb-state.json`(Windows 是 `%USERPROFILE%\.dsh\.fish-orb-state.json`)
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"version": 1,
|
|
48
|
+
"ball": { "right": 18, "bottom": 132 },
|
|
49
|
+
"size": 44,
|
|
50
|
+
"enabled": true,
|
|
51
|
+
"target_session_id": "",
|
|
52
|
+
"tasks": [
|
|
53
|
+
{ "id": "t1", "text": "…", "status": "done", "createdAt": 1792253757743,
|
|
54
|
+
"finishedAt": 1792253760743, "result": "已投递给会话 …", "sessionId": "…", "readAt": null }
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- 写入是**原子写**(随机临时名 + `wx` 独占 + `fsync` + 失败清理),写盘失败**不抛**
|
|
60
|
+
- 文件坏了会**自愈**(字段缺失/越界都补默认值)
|
|
61
|
+
- `tasks` 只保留最近 20 条,`result` 截断到 400 字
|
|
62
|
+
|
|
63
|
+
## 接口(Host 半)
|
|
64
|
+
|
|
65
|
+
| 方法 | 路径 | 用途 |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| GET | `/fish-orb/state.json` | 读球位置 / 大小 / 目标会话 / 任务 |
|
|
68
|
+
| PUT | `/fish-orb/state.json` | 合并写(只改传进来的字段) |
|
|
69
|
+
| GET | `/fish-orb/sessions.json` | 可选目标会话(工作区里真实存在的那些) |
|
|
70
|
+
| POST | `/fish-orb/dispatch.json` | 派一句话(`{ text }`) |
|
|
71
|
+
| GET | `/fish-orb/tasks.json` | 任务列表 + `unreadCount`(面板轮询它) |
|
|
72
|
+
| POST | `/fish-orb/tasks/read.json` | 标记已读(不传 `ids` = 全标) |
|
|
73
|
+
| GET | `/fish-orb/health.json` | 自检:webServer / agents 是否可用 |
|
|
74
|
+
|
|
75
|
+
全部走**来源校验**(Host 必须是回环;带 Origin/Referer 的必须是本机或 `dsh-app:`/`file:`/`chrome-extension:`)。
|
|
76
|
+
从命令行直接 `curl` 会被宿主的信任栅栏挡成 401 —— **这是正常的**,页面内 `fetch` 才带得上认证。
|
|
77
|
+
|
|
78
|
+
## 自检
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
node tools/smoke-test.mjs
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
离线可跑(不碰真实状态文件)。运行时行为仍需在 DSH 里验。
|
|
85
|
+
|
|
86
|
+
## 已知限制
|
|
87
|
+
|
|
88
|
+
- 只有**正在运行**的会话能收到派发
|
|
89
|
+
- 只在 **Windows + DSH 0.2.1-rc.2** 上验证过
|
|
90
|
+
- 会话标题取不到时会退到显示**工作目录名**
|
|
91
|
+
- 孤儿会话(磁盘有、工作区索引里没有)**不会**出现在下拉里 —— 这是故意的,否则会看到一堆"不存在的会话"
|
|
92
|
+
|
|
93
|
+
## 开发
|
|
94
|
+
|
|
95
|
+
踩坑清单(共 24 条,每条都有根因和取证)见 **[docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)**。
|
|
96
|
+
里面最值得先看的是:
|
|
97
|
+
|
|
98
|
+
- 第 17 条:`createUserMessage` 缺 `role` 会**永久污染会话**
|
|
99
|
+
- 第 18 条:`source.kind` **不能**是 `"plugin"`
|
|
100
|
+
- 第 10 条:`ctx.get('webServer')` 可能还没就绪 → 要二次探测
|
|
101
|
+
- 第 15 条:磁盘会话 ≠ 侧边栏会话(孤儿会话)
|
|
102
|
+
|
|
103
|
+
## 设计参考(只读,不拷代码)
|
|
104
|
+
|
|
105
|
+
- **骨架**:`bili-duty` —— Host + Client 双面插件的完整工程(路由、设置页、调度、测试、发布)
|
|
106
|
+
- **形态**:`dsh-whale-widget` —— 页面内常驻挂件、吸附、音效
|
|
107
|
+
- **产品设计**:[deepseek-harness-orb](https://github.com/mini-yifan/deepseek-harness-orb) ——
|
|
108
|
+
它是 fork 整个 dsh 的产物(50+ 包 workspace),本机是打包好的 `app.asar`,**代码搬不过来**;
|
|
109
|
+
值得学的是设计:双轨派发、球上答题、相对坐标
|
|
110
|
+
|
|
111
|
+
## 许可
|
|
112
|
+
|
|
113
|
+
MIT
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# dsh-fish-orb —— DSH bundle patch
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# 把 Host 行插入内核。安装走 plugin_manager 的 install_bundle,不要手改 profile。
|
|
5
|
+
# ============================================================================
|
|
6
|
+
|
|
7
|
+
- insert:
|
|
8
|
+
- id: dsh-fish-orb
|
|
9
|
+
name: dsh-fish-orb
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# 开发笔记 · dsh-fish-orb
|
|
2
|
+
|
|
3
|
+
> 每一条都是**实际踩过、有证据**的,不是猜测。写下来是为了不再踩第二次。
|
|
4
|
+
> 按「现象 → 根因 → 怎么修」记。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 一、Client 半(页面侧)
|
|
9
|
+
|
|
10
|
+
### 1. `--dsw-alias-label-primary-foreground` 这个 token **不存在**
|
|
11
|
+
|
|
12
|
+
**现象**:球的文字/白点取不到颜色(声明被整条丢弃)。
|
|
13
|
+
**根因**:我凭印象写了一个 token 名,它不在当前主题表里。
|
|
14
|
+
**取证方式**:`cordis_inspect_query` → `platform: client` / `provider: Theme` / `method: listTokens`,里面只有 14 个 token:
|
|
15
|
+
`bg-base / bg-layer-1 / bg-layer-2 / bg-overlay / border-l1 / border-l2 / brand-primary /
|
|
16
|
+
label-primary / label-secondary / state-{error,idle,success,warn}-primary / specific-sidebar-fill`。
|
|
17
|
+
**修法**:只用表里真实存在的 token,并且**每个 `var()` 都带 fallback**。
|
|
18
|
+
|
|
19
|
+
### 2. `--dsw-alias-brand-primary` 在浅色主题下**接近黑**
|
|
20
|
+
|
|
21
|
+
**现象**:拿它当球底色 → 一颗黑球。
|
|
22
|
+
**根因**:它是"品牌强调色",不是"浅色底"。浅色主题下它偏偏是深色。
|
|
23
|
+
**修法**:球底色改用 `--dsw-alias-bg-layer-1`(浅);品牌色只用在需要被注意的地方(滑块 accent 等)。
|
|
24
|
+
|
|
25
|
+
### 3. `--dsw-alias-bg-overlay` 是**深色浮层色**
|
|
26
|
+
|
|
27
|
+
**现象**:换成它当球底 → 球变成深色的圆。
|
|
28
|
+
**根因**:`bg-overlay` 是"弹层/遮罩"背景,深色主题下更深。
|
|
29
|
+
**修法**:球底用 `bg-layer-1`;对话框/面板底才用 `bg-overlay`(且仅当它确实是浅色的语境)。
|
|
30
|
+
|
|
31
|
+
### 4. `border-radius` 走 `<style>` 时表现怪异
|
|
32
|
+
|
|
33
|
+
**现象**:`border-radius: 50%` 渲染成了**圆角矩形**,原因未查明。
|
|
34
|
+
**修法**:**关键样式一律写成元素级 inline style**(`el.style.cssText`),不再依赖 `<style>` 表。
|
|
35
|
+
这条顺带解决了另一个隐患:不受 DSH 全局 CSS 影响。
|
|
36
|
+
|
|
37
|
+
### 5. SVG `<text>` + `dominant-baseline` 对 emoji 垂直居中**不可靠**
|
|
38
|
+
|
|
39
|
+
**现象**:`<text dominant-baseline="central">🐟</text>` 渲染出来**字符偏下**。
|
|
40
|
+
**根因**:SVG 文字的基线对齐依赖字体度量,emoji 尤其不听话。
|
|
41
|
+
**修法**:**别用 SVG 排文字**。要么用 HTML 元素 + `display:flex; align-items:center; justify-content:center`,
|
|
42
|
+
要么把字符放在 HTML 层(本插件最终就是"HTML 元素 + flex 居中")。
|
|
43
|
+
|
|
44
|
+
### 6. 坐标必须 `Math.round`
|
|
45
|
+
|
|
46
|
+
**现象**:状态文件里写进了 `bottom: 440.0000305175781`。
|
|
47
|
+
**根因**:`clientX` / `clientY` 是浮点数,直接存进 JSON 就是长小数。
|
|
48
|
+
**修法**:拖动与渲染两处都 `Math.round`。
|
|
49
|
+
|
|
50
|
+
### 7. 拖动必须 `setPointerCapture`
|
|
51
|
+
|
|
52
|
+
**现象**(预防性,实现时就加上了):指针移出球之后 `pointermove` / `pointerup` 丢失,拖动"粘住"。
|
|
53
|
+
**修法**:`pointerdown` 里 `ball.setPointerCapture(e.pointerId)`,`pointerup` 里 `releasePointerCapture`。
|
|
54
|
+
|
|
55
|
+
### 8. 拖动完会误触点击
|
|
56
|
+
|
|
57
|
+
**现象**:拖完球,面板自己开了。
|
|
58
|
+
**修法**:**位移阈值 4px** —— `Math.abs(dx) + Math.abs(dy) < 4` 才算点击,否则算拖动。
|
|
59
|
+
|
|
60
|
+
### 9. 容器 `pointer-events: none` 与球 `pointer-events: auto` 必须成对
|
|
61
|
+
|
|
62
|
+
**现象**(bili-duty 的教训,本插件沿用):漏一个就整页点不动 —— 覆盖全屏的容器把鼠标事件全吃了。
|
|
63
|
+
**修法**:`#fish-orb-root { pointer-events:none }` + `.fo-ball { pointer-events:auto }`,面板同样 `auto`。
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 二、Host 半(进程侧)
|
|
68
|
+
|
|
69
|
+
### 10. `ctx.get('webServer')` 可能返回 `undefined`
|
|
70
|
+
|
|
71
|
+
**现象**:fiber 明明 active,但 `/fish-orb/*` 全部 404。
|
|
72
|
+
**根因**:冷启动时 `apply` 可能跑在 webServer 就绪**之前**。热重载时服务早就绪,所以开发中完全看不出来。
|
|
73
|
+
**修法**:**二次探测** —— 先试一次 `ctx.get('webServer')`;拿不到就 `ctx.inject(['webServer'], cb)` 等服务就绪。
|
|
74
|
+
(照抄 bili-duty `lib/index.js:1267-1317`。)
|
|
75
|
+
|
|
76
|
+
### 11. 路由的 disposer 必须交给 `ctx.effect`
|
|
77
|
+
|
|
78
|
+
**现象**(bili-duty 实测):禁用/重载插件后旧路由没释放,下次激活被以 `duplicate exact route` 拒绝。
|
|
79
|
+
**修法**:`ctx.effect(() => { const dispose = webServer.register(route); return () => dispose() })`。
|
|
80
|
+
|
|
81
|
+
### 12. 写盘失败**绝不抛**
|
|
82
|
+
|
|
83
|
+
**根因**:有插件因为 `appendFileSync` 抛 `EBUSY` 没被捕获,**把整个 DSH 宿主带走了**(dsh-bili-scroll issue #1)。
|
|
84
|
+
**修法**:原子写(随机临时名 + `wx` 独占 + `fsync` + 失败清理),**只返回 `{ok, error}`,不 throw**。
|
|
85
|
+
|
|
86
|
+
### 13. `agents.list()` 只给"活着的 agent",不是全量会话
|
|
87
|
+
|
|
88
|
+
**现象**:会话下拉**空的**,或者只有一个。
|
|
89
|
+
**根因**:`agents.list()` 返回的是"当前进程里活着的 agent",而侧边栏列的是"全部持久化会话"。
|
|
90
|
+
**修法**:三层来源 ——
|
|
91
|
+
1. `sessionQuery.listSessions()`(**全量**,首选)
|
|
92
|
+
2. `sessionController.list()`(兜底)
|
|
93
|
+
3. `agents.list()`(**只用来标"谁活着"**,因为只有活着的能收派发)
|
|
94
|
+
|
|
95
|
+
### 14. `listSessions()` 只给 id,**标题要另外批量取**
|
|
96
|
+
|
|
97
|
+
**现象**:下拉里全是「(未命名)」。
|
|
98
|
+
**修法**:`sessionQuery.readTitleSnapshots(ids, signal)` 批量补标题,再用**宽容解析**挖(结构没有公开契约,
|
|
99
|
+
实测见过 `{ sessionId, observation: { title: { val: "…" } } }` 这种套娃)。
|
|
100
|
+
挖不到时退到**工作目录名**,至少能区分是哪个会话。
|
|
101
|
+
|
|
102
|
+
### 15. 磁盘上的会话 ≠ 侧边栏显示的会话(**孤儿会话**)
|
|
103
|
+
|
|
104
|
+
**现象**:下拉里出现一批"不存在的会话"(如标题为「你是一名代码安全审计员。请…」),侧边栏里根本看不到。
|
|
105
|
+
**取证**:`~/.dsh/sessions/--E-AI-deepseek-…--/<uuid>/` 目录真实存在,但
|
|
106
|
+
`~/.dsh/storages/workspace.json` 的 `tables.workspaces[*].sessionIds` 里**没有这些 id**。
|
|
107
|
+
**根因**:`sessionQuery.listSessions()` 返回**磁盘上所有会话**,其中一批**没登记进任何工作区**;
|
|
108
|
+
侧边栏按工作区索引显示,所以用户看不到它们。
|
|
109
|
+
**修法**:读 `workspace.json`,只保留"在工作区 `sessionIds` 里、且不在 `global.archivedSessionIds` 里"的会话。
|
|
110
|
+
另外 id 前缀不一致(`session-<uuid>` vs 裸 uuid),比较前统一去前缀。
|
|
111
|
+
|
|
112
|
+
### 16. `const` 声明后又重新赋值 → `Assignment to constant variable`
|
|
113
|
+
|
|
114
|
+
**现象**:整个会话列表接口 500(我在写 debug 回吐时自己造成的)。
|
|
115
|
+
**注意**:`node --check` **查不出**这种错 —— 它是运行时错误。所以语法通过 ≠ 能跑。
|
|
116
|
+
**修法**:需要重新赋值的变量一律 `let`。
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 三、投递(本轮最关键的一条)
|
|
121
|
+
|
|
122
|
+
### 17. `createUserMessage` 必须补齐 `role` 与 `id`
|
|
123
|
+
|
|
124
|
+
**根因**(bili-duty 的**最贵教训**,本项目沿用):手拼裸对象投递,会往会话里存一条**没有 `role`** 的消息,
|
|
125
|
+
此后该会话**每一次**请求都被网关拒绝:`messages[N]: missing field 'role'`。会话**永久损坏**。
|
|
126
|
+
**修法**:
|
|
127
|
+
```js
|
|
128
|
+
import { randomUUID } from 'node:crypto'
|
|
129
|
+
let createUserMessage = (input) => ({ ...input, role: 'user', id: randomUUID() }) // 等价兜底
|
|
130
|
+
void import('@deepseek-ai/dsh-llm').then((mod) => {
|
|
131
|
+
if (typeof mod?.createUserMessage === 'function') createUserMessage = mod.createUserMessage
|
|
132
|
+
}).catch(() => { /* 保持兜底 */ })
|
|
133
|
+
```
|
|
134
|
+
用**动态 import + 同步兜底**而不是顶层静态 import:静态 import 一旦失败会让**整个插件模块**加载不了,
|
|
135
|
+
而这条路只影响"派发"一件事。
|
|
136
|
+
|
|
137
|
+
### 18. `source.kind` **不能**是 `"plugin"`
|
|
138
|
+
|
|
139
|
+
**根因**:DSH 的 session format v4 校验明确拒绝(`format v4 message requires a producer-owned source kind`),
|
|
140
|
+
而且**只吃 `kind` 一个字段**,多塞没用。
|
|
141
|
+
**修法**:用自己的名字 —— `source: { kind: 'fish-orb' }`。
|
|
142
|
+
(对照:官方 dsh-schedule 用 `{ kind: "schedule" }`。)
|
|
143
|
+
|
|
144
|
+
### 19. 目标会话必须**活着**,否则派不出去
|
|
145
|
+
|
|
146
|
+
**现象**:选了会话却投递失败。
|
|
147
|
+
**修法**:`agents.get(id)` 拿不到就是没在运行 —— 返回明确错误「目标会话未运行,请先在 DSH 里打开它再试」,
|
|
148
|
+
并在下拉里标注「(运行中)」/「(未打开)」,让用户**选之前就知道**。
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 四、工程与流程
|
|
153
|
+
|
|
154
|
+
### 20. `install_bundle` 的 `application` 字段才是生效判据
|
|
155
|
+
|
|
156
|
+
**修法**:装完看返回的 `application`(`applied` 才算生效),**不要**拿"终端没报错"当成功。
|
|
157
|
+
|
|
158
|
+
### 21. 改 `client.js` 只需刷新页面;改 `lib/index.js`(Host)必须重启 DSH
|
|
159
|
+
|
|
160
|
+
**根因**:Host 半是进程里的模块,替换已装包后要重启才能加载新一代 JS。
|
|
161
|
+
**修法**:`tools/restart-dsh.ps1`(自动探测 DSH 位置);打包更新用 `install_bundle` 重装同一个 target。
|
|
162
|
+
|
|
163
|
+
### 22. 包目录是 `link:` 装的,改文件即时可见 —— 但**模块不会自动重载**
|
|
164
|
+
|
|
165
|
+
- `client.js`:F5 重新拉取 → 生效
|
|
166
|
+
- `lib/index.js`:需要重启进程 → 生效
|
|
167
|
+
|
|
168
|
+
### 23. Chrome DevTools 会拦粘贴
|
|
169
|
+
|
|
170
|
+
**现象**:在 Console 里粘贴代码,出现 `Warning: Don't paste code into the DevTools Console…`,命令不执行。
|
|
171
|
+
**修法**:先**手打** `allow pasting` 回车,再粘贴。
|
|
172
|
+
|
|
173
|
+
### 24. 本项目的形态限制(写给未来的自己)
|
|
174
|
+
|
|
175
|
+
- ❌ **不能**跨应用置顶(那是 Electron BrowserWindow / 原生窗口)
|
|
176
|
+
- ❌ **不能**跨应用划词(需要系统级全局钩子,在主进程)
|
|
177
|
+
- ✅ **能**做的:页面内悬浮球、拖动吸附、面板、派发、结果回流
|
|
178
|
+
- 参考的 deepseek-harness-orb 是 **fork 整个 dsh** 的产物,本机是打包好的 `app.asar`,**代码搬不过来** —— 搬设计,不搬代码。
|
package/icon.svg
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="dsh-fish-orb">
|
|
2
|
+
<rect x="2" y="2" width="60" height="60" rx="14" fill="#3D5AFE"/>
|
|
3
|
+
<circle cx="32" cy="32" r="15" fill="#FFFFFF" opacity="0.95"/>
|
|
4
|
+
<circle cx="32" cy="32" r="6.5" fill="#3D5AFE"/>
|
|
5
|
+
<circle cx="45" cy="19" r="4" fill="#7CFFB2"/>
|
|
6
|
+
</svg>
|