dsh-sidecard-ask 1.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/CHANGELOG.md +79 -0
- package/LICENSE +21 -0
- package/README.md +439 -0
- package/client.js +2268 -0
- package/cordis.patch.yml +38 -0
- package/icon.svg +9 -0
- package/index.js +1242 -0
- package/locale/en.json +15 -0
- package/locale/zh.json +15 -0
- package/package.json +66 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
**改名:`dsh-selection-followup` → `dsh-sidecard-ask`**(显示名「划词追问」→「侧边卡片追问」)。
|
|
6
|
+
|
|
7
|
+
原因(可复核):
|
|
8
|
+
|
|
9
|
+
- `dsh-selection-followup` 在 **DSH 插件生态里已被他人使用**——GitHub 仓库
|
|
10
|
+
`zzx-dear/dsh-selection-followup` 与官方收录索引 `data/plugins/zzx-dear__dsh-selection-followup.yml`
|
|
11
|
+
(category: ui),早于本插件约 20 天。1.0.0 发布时只核对了 npm 名字是否可用(当时为空),
|
|
12
|
+
**没有核对 GitHub 仓库名与收录索引**,这是本项目的疏漏。
|
|
13
|
+
- 新名 `dsh-sidecard-ask` 的 npm 包名与 GitHub 仓库名都已核对为空,并且与既有的
|
|
14
|
+
`dsh-selection-ask` / `dsh-selection-explain` / `dsh-selection-toolbar` / `dsh-quote-selection` /
|
|
15
|
+
`dsh-ui-quote-selection` / `dsh-quote-annotate` / `dsh-selection-memory` / `dsh-plugin-followup`
|
|
16
|
+
在名字与定位上都区分开:本插件由**独立子代理在侧边卡片里流式作答**,不是把选中内容塞进输入框。
|
|
17
|
+
|
|
18
|
+
随之变更的标识(旧 → 新):
|
|
19
|
+
|
|
20
|
+
| 项 | 旧 | 新 |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| npm 包名 / bundle 名 | `dsh-selection-followup` | `dsh-sidecard-ask` |
|
|
23
|
+
| 宿主行 id | `selection-followup` | `sidecard-ask` |
|
|
24
|
+
| 插件自有路由 | `/selection-followup/api` | `/sidecard-ask/api` |
|
|
25
|
+
| 客户端模块 id / 槽位条目 id | `dsh-selection-followup` / `selection-followup` | `dsh-sidecard-ask` / `sidecard-ask` |
|
|
26
|
+
| 侧边卡片类型 | `selection-followup:card` | `sidecard-ask:card` |
|
|
27
|
+
| 用户配置目录 | `<DSH_HOME>/selection-followup/` | `<DSH_HOME>/sidecard-ask/` |
|
|
28
|
+
| 显示名(Plugins 页 / 设置页) | 划词追问 | 侧边卡片追问 |
|
|
29
|
+
|
|
30
|
+
其它:`package.json` 增加中英文关键词(划词 / 选中追问 / 侧边卡片);自测同步改名,仍是 **276 项全通过**;
|
|
31
|
+
旧 npm 包名已 `deprecate` 指向新名字。功能与 API 与 1.0.1 完全一致,**升级只需卸旧装新**(见 README §三)。
|
|
32
|
+
|
|
33
|
+
## 1.0.1
|
|
34
|
+
|
|
35
|
+
**主题:把"版本适配"从字段笔记变成可验证的适配。**
|
|
36
|
+
|
|
37
|
+
- 新增 `tools/compat-probe.mjs`:从 npm 拉取 **13 个 DSH 版本**(0.1.2-rc.1 → 0.1.7-rc.2)
|
|
38
|
+
× **12 个相关包**的已发布产物,解包后按标记字符串判定能力,输出矩阵(结果写进 README §7)。
|
|
39
|
+
据此确认的缺口与适配:
|
|
40
|
+
- **原生右侧栏**(0.1.2 / 0.1.3 缺)→ 承载面探测链已有降级;
|
|
41
|
+
- **主对话提交**(≤0.1.6-alpha.1 既无 `using` 也无 `retain`)→ 新增四级提交阶梯
|
|
42
|
+
`using → retain+release → 槽位标准 prop inputActions(setDraft+submit) → 仅写草稿`,逐级探测并回报实际生效的一级;
|
|
43
|
+
- **归档会话门**(仅 0.1.7-alpha.1+ 存在)→ 修正上一版引入的误伤:不再对归档父会话一刀切拒绝,
|
|
44
|
+
改为优先挑未归档代理、只剩归档候选时照常尝试并在 `start` 事件标注 `parentArchived`;
|
|
45
|
+
- **流式帧**(0.1.2-rc.1 无 `agent/assistant-stream`)→ 新增第二条流式源,桥接该版本持久化的
|
|
46
|
+
`assistant/chunk` 会话事件,与帧源互斥(先到者生效,绝不重复累计文本),并在 `done` 里给出 `streamSource`。
|
|
47
|
+
- **新增槽位阶梯**:浮层 / 设置页 / 会话采集各自在多个等价 `list` 槽位间回退(先到者胜,更好的槽位后到会顶掉兜底),
|
|
48
|
+
诊断里记录真实落点;`single` 槽位一律不碰(避免替换宿主 UI)。
|
|
49
|
+
- **区域锚点缺失时不再静默失效**:检测不到 `data-slot` 时停用区域过滤并在自检里说明(原先 `captureZones:chat` 会永远不触发)。
|
|
50
|
+
- **provider 能力门控**:只发送 provider 声明支持的启动字段(`capabilities.persona/toolFilter`),
|
|
51
|
+
`persona` 不支持时内联进提示词,`toolFilter` 不支持时在卡片上明说"本次继承了会话工具"。
|
|
52
|
+
- **路由注册降级**:`prefix` 路由被拒时退化为逐方法精确路由。
|
|
53
|
+
- 自测从 236 项扩到 **276 项**(verify 53 / contract 105 / smoke 118),新增用例覆盖上面每一条降级路径。
|
|
54
|
+
|
|
55
|
+
## 1.0.0
|
|
56
|
+
|
|
57
|
+
首个版本(发布名 `dsh-sidecard-ask`:npm 上的 `dsh-selection-ask` 已被他人占用)。
|
|
58
|
+
|
|
59
|
+
- **划词追问**:在聊天区 / 任务区选中文本,选区末端就地浮出「追问选中内容」按钮,点击弹出提问框。
|
|
60
|
+
- **两种作答承载**:主对话(引用块进入当前会话,答案原生流式)与独立侧边卡片(子代理在自己的会话里作答,零父上下文),可在提问框临时切换并配置默认值。
|
|
61
|
+
- **侧边承载面适配**:DSH 原生右侧栏 → `dsh-better-sidebar` → 内置浮层卡片,三层能力探测自动降级;未安装侧边卡片插件时功能完整。
|
|
62
|
+
适配器"接受打开但没渲染"时由**渲染证明**在 600ms 后把卡片移到内置浮层,不会留下空 tab。
|
|
63
|
+
- **流式渲染**:宿主半把 `agent/assistant-stream` 帧桥接到 SSE;卡片支持关闭、复制、继续追问、停止作答、重试。
|
|
64
|
+
- **边界处理**:空选/过短、编辑框内选择、跨区选择、超长截断(两端算法一致)、接口失败逐层降级、重复触发抑制、并发上限、请求超时、卸载清理。
|
|
65
|
+
- **配置三层**:内置默认 ← bundle 补丁 ← 用户层(`<DSH_HOME>/sidecard-ask/config.json`,原子写入),设置页可视化编辑 + 运行自检。
|
|
66
|
+
- **零运行时依赖**:宿主半只用 `node:` 内建;客户端半只 `require('react')`。
|
|
67
|
+
- 附带三套可运行自测(236 项):`test/verify.mjs`、`test/contract-test.mjs`、`test/smoke-test.mjs`。
|
|
68
|
+
|
|
69
|
+
### 真实宿主联调中发现并修掉的问题(DSH 0.1.7-rc.2)
|
|
70
|
+
|
|
71
|
+
1. **`tools.restrict()` 会拒绝未知工具名**:硬编码的只读工具白名单里有若干本组合不存在的名字,导致子代理启动即失败。
|
|
72
|
+
现在白名单在调用时与 `tools.schemas()` 求交,交集为空则完全不传 `toolFilter`。
|
|
73
|
+
2. **`req.on('close')` 不是"浏览器断开"**:Node 的 `IncomingMessage` 在请求体读完时就触发 `close`,用它作断线信号会在请求体结束的瞬间取消作答。
|
|
74
|
+
改为只监听 `res.on('close')`,并要求 `res.writableEnded !== true`。
|
|
75
|
+
3. **父会话可能已被归档**:宿主的归档门会拒绝整条子代理血统的每一步(空 turn → 子代理接缝记为 `refusal`),
|
|
76
|
+
而 `agents.roots()[0]` 可能正好落在归档会话上。现在按"提问会话 → 首个未归档活动代理"选择父代理,
|
|
77
|
+
候选全部归档时直接返回 `parent-archived`;其它 `refusal` 也被翻译成可读的 `blocked-step`。
|
|
78
|
+
4. **终止事件必须最后**:末尾多余的 `status{settled}` 会让"最后一个事件是终局"的读法失效,已移除。
|
|
79
|
+
5. **`/config` 之后的 `provenance.persisted` 是启动时的快照**:保存后仍显示未持久化,改为按当前用户层实时计算。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HERO476
|
|
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,439 @@
|
|
|
1
|
+
# dsh-sidecard-ask · 侧边卡片追问
|
|
2
|
+
|
|
3
|
+
在 DSH Web GUI 里**选中一段文本就能就地追问**:选中处浮出「追问选中内容」按钮 → 弹出提问框 →
|
|
4
|
+
答案落在**两个可切换的承载位置**上:
|
|
5
|
+
|
|
6
|
+
- **独立侧边卡片(默认)**:由子代理在**自己的会话**里作答(不继承父上下文、不打扰主对话),答案在卡片里逐字流式渲染,可关闭 / 复制 / 继续追问;
|
|
7
|
+
- **主对话**:追问内容带着引用块进入当前会话,答案随对话原生流式呈现。
|
|
8
|
+
|
|
9
|
+
聊天记录、任务详情、右侧栏内容……只要是能选中的文本都能用;**没装侧边卡片插件也能跑**(自动回退到内置浮层卡片)。
|
|
10
|
+
|
|
11
|
+
> ## 名字改过两次,原因都写在这里(避免再撞名)
|
|
12
|
+
>
|
|
13
|
+
> 1. **`dsh-selection-ask` → ✗**:npm 上已被 `chestnut23` 占用(仓库 `lzbaclz/dsh-selection-ask`)。
|
|
14
|
+
> 2. **`dsh-selection-followup` → ✗**(1.0.0 / 1.0.1 用的名字):npm 上当时是空的,但 **DSH 插件生态里已被 `zzx-dear` 使用**——
|
|
15
|
+
> GitHub 仓库 [`zzx-dear/dsh-selection-followup`](https://github.com/zzx-dear/dsh-selection-followup) 与官方收录索引
|
|
16
|
+
> `data/plugins/zzx-dear__dsh-selection-followup.yml`(category: ui,早于本插件约 20 天)。
|
|
17
|
+
> 我最初只查了 npm 名字是否可用,**漏查了 GitHub 仓库名与收录索引**,这是本项目的失误,已在 1.1.0 修正。
|
|
18
|
+
> 3. **`dsh-sidecard-ask` → ✓**(1.1.0 起):npm 无同名包、GitHub 无同名仓库;
|
|
19
|
+
> 同时它与已有的 8 个"选中文字→引用进输入框/翻译/批注"类插件(`dsh-selection-ask`、`dsh-selection-explain`、
|
|
20
|
+
> `dsh-selection-toolbar`、`dsh-quote-selection`、`dsh-ui-quote-selection`、`dsh-quote-annotate`、
|
|
21
|
+
> `dsh-selection-memory`、`dsh-plugin-followup`)在**名字与定位上都区分开**:本插件的答案是**独立子代理在侧边卡片里流式产出**,
|
|
22
|
+
> 而不是把选中内容塞进输入框。
|
|
23
|
+
>
|
|
24
|
+
> 旧 npm 包名 `dsh-selection-followup` 已 `npm deprecate` 指向新名字;升级只需卸旧装新(见 §三)。
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 一、三条关键路径(先看这个)
|
|
31
|
+
|
|
32
|
+
| 路径 | 操作 | 期望结果 |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| **A. 就地追问(侧边卡片)** | 在聊天区选中一句话 → 点浮出的按钮 → 输入问题 → Enter | 右下角浮出卡片,答案逐字流出;卡片底部有「复制 / 继续追问 / 关闭」 |
|
|
35
|
+
| **B. 主对话追问** | 选中 → 提问框里把「作答位置」切到**主对话** → Enter | 追问以引用块形式出现在主对话,答案由当前会话正常流式输出 |
|
|
36
|
+
| **C. 快捷键 + 降级** | 选中 → 按 `Alt+Q`(默认) | 直接弹出提问框;若侧边作答引擎不可用,卡片给出错误提示与「改到主对话」按钮 |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 二、目录结构与逐文件用途
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
dsh-sidecard-ask/
|
|
44
|
+
├── package.json 清单:包名/版本/导出/dsh.bundle.patch/dsh.client(web 平台、immediately、inject)
|
|
45
|
+
├── cordis.patch.yml bundle 补丁:向 profile 插入 id 为 sidecard-ask 的宿主行,并给出全部默认配置
|
|
46
|
+
├── index.js Host 半(宿主侧):插件自有 API(JSON + SSE)、配置三层合并与持久化、
|
|
47
|
+
│ 独立作答引擎(ctx.subagents 子代理 + agent/assistant-stream 桥接)、请求信任围栏
|
|
48
|
+
├── client.js Client 半(浏览器侧):dsh.client 浏览器产物,单文件、无构建步骤
|
|
49
|
+
│ §1 常量与默认值 §2 i18n §3 线协议客户端 §4 模块级 store §5 选区引擎
|
|
50
|
+
│ §6 两种承载方式 §7 侧边承载面适配 §8 视图工具 §9 组件 §10 插件入口
|
|
51
|
+
├── locale/zh.json 插件元数据的中文显示文案(Plugins 页卡片标题/描述)
|
|
52
|
+
├── locale/en.json 同上,英文
|
|
53
|
+
├── icon.svg Plugins 页卡片图标(<256 KiB,相对路径)
|
|
54
|
+
├── README.md 本文件
|
|
55
|
+
├── CHANGELOG.md 版本变更记录
|
|
56
|
+
├── LICENSE MIT
|
|
57
|
+
└── test/
|
|
58
|
+
├── harness.mjs 测试替身:假宿主 ctx / 假 req·res / 假子代理 provider / 合成浏览器 + 迷你 React
|
|
59
|
+
├── verify.mjs 静态完整性:清单、补丁、两半导出面、i18n 键覆盖、零依赖承诺
|
|
60
|
+
├── contract-test.mjs 两端契约:常量一致性、响应信封形状、SSE 逐帧往返、纯函数等价、槽位注册与渲染
|
|
61
|
+
└── smoke-test.mjs 宿主端到端:流式、截断、取消、持久化与重启、失败分支、并发上限、传输围栏
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**不引入任何外部依赖**:宿主半只 import `node:` 内建模块;客户端半只 `require('react')`(由浏览器模块表提供)。
|
|
65
|
+
`test/` 目录不进 `files`,不会发布。
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 三、安装与启用
|
|
70
|
+
|
|
71
|
+
### 方式 1:从 npm 安装(推荐)
|
|
72
|
+
|
|
73
|
+
```powershell
|
|
74
|
+
# 在 profile 目录(默认 %USERPROFILE%\.dsh\profiles\web)执行
|
|
75
|
+
pnpm add dsh-sidecard-ask
|
|
76
|
+
# 然后把包名写进 profile package.json 的 dsh.profile.bundles 数组
|
|
77
|
+
# "dsh.profile": { "bundles": [ ..., "dsh-sidecard-ask" ] }
|
|
78
|
+
# 重启 DSH(重启后插件行、客户端产物、设置页一起生效)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 方式 2:让 DSH 自己装(本工作区源码)
|
|
82
|
+
|
|
83
|
+
在会话里让 Agent 执行 `plugin_manager` 的 `install_bundle`,target 指向本目录的**绝对路径**:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
plugin_manager(action="install_bundle", target="D:\\Users\\34332\\AI\\dsh-sidecard-ask")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`install_bundle` 会自行完成 profile 的写入与 bundle 选择,**不要**手改 profile 的 `package.json` / `cordis.patch.yml`。
|
|
90
|
+
|
|
91
|
+
### 方式 3:本地联调(junction)
|
|
92
|
+
|
|
93
|
+
```powershell
|
|
94
|
+
cmd /c mklink /J "%USERPROFILE%\.dsh\profiles\web\node_modules\dsh-sidecard-ask" "D:\Users\34332\AI\dsh-sidecard-ask"
|
|
95
|
+
# 再手动把 "dsh-sidecard-ask" 加进 profile package.json 的 dsh.profile.bundles
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 启用与验证
|
|
99
|
+
|
|
100
|
+
1. 重启 DSH(新包需要重启才会加载一行全新的 JavaScript 模块)。
|
|
101
|
+
2. 打开 Web GUI → **设置 → 插件**,应能看到卡片「侧边卡片追问」。
|
|
102
|
+
3. 打开**设置 → 侧边卡片追问**页,点「运行自检」:应显示宿主接口可用、侧边作答引擎可用、provider 列表(本机为 `spawn`)、活动会话代理数。
|
|
103
|
+
4. 若自检显示"宿主接口不可达",说明 `/sidecard-ask/api` 路由没起来——检查 profile 里该 bundle 是否在 `dsh.profile.bundles` 中、以及 DSH 是否重启过。
|
|
104
|
+
|
|
105
|
+
### 从旧名字升级(1.0.x → 1.1.0)
|
|
106
|
+
|
|
107
|
+
```powershell
|
|
108
|
+
# 1) 卸掉旧 bundle(profile 里旧名字是 dsh-selection-followup)
|
|
109
|
+
plugin_manager(action="remove_bundle", target="dsh-selection-followup")
|
|
110
|
+
# 2) 装新名字(本工作区源码)
|
|
111
|
+
plugin_manager(action="install_bundle", target="D:\\Users\\34332\\AI\\dsh-sidecard-ask")
|
|
112
|
+
# 3) 重启 DSH;旧配置目录 <DSH_HOME>\selection-followup 可以删掉(新名字用 <DSH_HOME>\sidecard-ask)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
> 改了 `client.js` 后如果 `pnpm run dev:web` 没有在跑,浏览器需要刷新页面;改了 `index.js` 需要重启 DSH。
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 四、配置项说明表
|
|
120
|
+
|
|
121
|
+
配置有**三层**,优先级从低到高:
|
|
122
|
+
|
|
123
|
+
1. **内置默认值**(`index.js` 的 `DEFAULT_CONFIG`)
|
|
124
|
+
2. **bundle 补丁层**:`cordis.patch.yml` 的 `config:`(改这里需要重启)
|
|
125
|
+
3. **用户层**:设置页保存后写入 `<DSH_HOME>/sidecard-ask/config.json`
|
|
126
|
+
(`DSH_HOME` 未设置或为空白时回退到 `~/.dsh`;**不会**写进程当前目录)
|
|
127
|
+
|
|
128
|
+
设置页点「重置为 patch 配置」会清空用户层。
|
|
129
|
+
|
|
130
|
+
| 配置项 | 类型 / 取值 | 默认 | 作用 | 生效方式 |
|
|
131
|
+
|---|---|---|---|---|
|
|
132
|
+
| `trigger` | `selection` \| `shortcut` \| `both` | `selection` | **触发方式**:选中即浮出按钮 / 只用快捷键 / 两者都要 | 改后立即(客户端读 /state) |
|
|
133
|
+
| `defaultCarrier` | `main` \| `side` | `side` | **默认作答位置**:提问框里仍可临时切换 | 立即 |
|
|
134
|
+
| `sideSurface` | `auto` \| `native-rightbar` \| `better-sidebar` \| `flow` | `auto` | **窗口模式(侧边承载面)**:自动挑选 / 强制 DSH 原生右侧栏 / 强制 dsh-better-sidebar / 强制内置浮层卡片 | 立即 |
|
|
135
|
+
| `maxChars` | 200–60000 | `4000` | **最大字符数**:超出部分头尾保留、中间截断并标注 | 立即 |
|
|
136
|
+
| `shortcut` | 形如 `Alt+Q`、`Ctrl+Shift+K` | `Alt+Q` | **快捷键**:唤起提问框(`trigger` 允许时) | 立即 |
|
|
137
|
+
| `captureZones` | `auto` \| `chat` \| `task` \| `chat+task` | `auto` | 只在哪些区域触发 | 立即 |
|
|
138
|
+
| `showInUnclassified` | 布尔 | `true` | 无法归类的区域是否也触发 | 立即 |
|
|
139
|
+
| `minChars` | 0–200 | `2` | 少于该字符数的选区不触发 | 立即 |
|
|
140
|
+
| `maxConcurrentAsks` | 1–12 | `3` | 侧边卡片并发作答上限,超出返回 `busy`(可重试) | 立即 |
|
|
141
|
+
| `sideTools` | `readonly` \| `inherit` | `readonly` | 侧边作答者的工具权限:只读白名单 / 继承当前会话 | 下一次提问 |
|
|
142
|
+
| `sideTimeoutMs` | 5000–3600000 | `180000` | 单次侧边作答超时 | 下一次提问 |
|
|
143
|
+
| `sideProvider` | 字符串 \| `auto` | `auto` | 指定子代理 provider(`auto` 优先 `spawn`) | 下一次提问 |
|
|
144
|
+
|
|
145
|
+
非法值会被拒绝(设置页报错、接口返回 `invalid-config`),并把被忽略的项列在 `/state` 的 `problems` 里——不会静默吞掉。
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 五、侧边承载面与"侧边卡片插件"适配
|
|
150
|
+
|
|
151
|
+
侧边卡片的渲染面按**能力探测**依次挑选,任何一层不可用都不影响整体可用:
|
|
152
|
+
|
|
153
|
+
| 顺序 | 承载面 | 依赖 | 失败时 |
|
|
154
|
+
|---|---|---|---|
|
|
155
|
+
| 1 | DSH 原生右侧栏 | `ctx.sidebarRightTabs`(注册 tab 类型)+ `ctx.sidebarRight`(`openTab`),并占用槽位 `sidebar.right.pane.tab` / `…tab.title` | 记 `surfaces.native.error`,落到下一层 |
|
|
156
|
+
| 2 | **dsh-better-sidebar**(侧边卡片插件) | 客户端服务 `ctx.betterSidebar`:`registerTab` + `openTab(seed, scope)`;卡片 id 走 `tab.meta.cardId`(依赖其 `features` 含 `tabMeta`) | 同上 |
|
|
157
|
+
| 3 | **内置浮层卡片**(默认兜底) | 只需要 `shell.overlay` 槽位 | ——(这是保底面,永远可用) |
|
|
158
|
+
|
|
159
|
+
- 装了 `dsh-better-sidebar`:卡片以它的 tab 形式出现在它的面板里,关闭卡片会同时 `closeTab`,不残留空 tab。
|
|
160
|
+
- 没装:自动使用内置浮层卡片(右下角卡片栈),功能完全一致——**这条路径是本插件的默认与保底路径**。
|
|
161
|
+
- 强制指定了一个不可用的承载面:回退到内置浮层,并在卡片上标注「已回退」。
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 六、未知 DSH API:占位接口与替换方式
|
|
166
|
+
|
|
167
|
+
开发时以**运行时能力探测**为主,任何"本机没验证到"的接口都在代码里显式留了占位与替换点。它们集中在两处:
|
|
168
|
+
|
|
169
|
+
### 1. `client.js` §7 —— `surfaces.native`(原生右侧栏适配器)
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
// 现状:结构性子集 + try/catch,任何一步不成立就置 error 并降级
|
|
173
|
+
ctx.inject(['sidebarRightTabs', 'sidebarRight'], (injected) => { ... })
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
- **已实测的调用形态**:`registry.register({ id, kind, title, guide: [{ id, order, title, description }] })`、
|
|
177
|
+
`controller.openTab(kind, { params, revealIfOpened })`、槽位 `sidebar.right.pane.tab`(keyed,`inject: sessionId => ({ sessionId })`)。
|
|
178
|
+
- **未逐版本实测的部分**(占位):`guide[].icon`、`canOpen/patterns`、`closeIn/activateIn`。
|
|
179
|
+
替换方式:在 `client.js` 搜索 `PLACEHOLDER: native-rightbar`,按目标 DSH 版本的真实签名补齐;
|
|
180
|
+
补齐前该分支只会记录错误并降级,不会破坏插件。
|
|
181
|
+
|
|
182
|
+
### 2. `client.js` §6/§3 —— 主对话发送与线协议
|
|
183
|
+
|
|
184
|
+
- **已实测**:客户端服务 `ctx.sessions` 的 `using(id, { source:'gateway' }, ref => ref.binding.session.prompt(parts, 'queue'))`。
|
|
185
|
+
- **占位 1(草稿降级)**:`ctx.get('conversation').input.for(scope)` → `{ state.getSnapshot().draft, setDraft(text) }`。
|
|
186
|
+
这是**未出现在服务目录里**的接口(属 harness 内部形态),所以只作为第二顺位降级;搜索 `PLACEHOLDER: composer-draft`。
|
|
187
|
+
- **占位 2(最后兜底)**:前两者都不可用时,插件抛出可读错误并提示用户复制文本,搜索 `PLACEHOLDER: clipboard-fallback`。
|
|
188
|
+
- **线协议**:客户端与宿主半之间是插件**自有的** `/sidecard-ask/api`(`GET /state`、`POST /ask|cancel|config|reset`),
|
|
189
|
+
不依赖任何 harness 内部 RPC;若未来 harness 提供正式的同进程 RPC,替换点就是 `client.js` §3 的 `postJson/streamAsk`。
|
|
190
|
+
|
|
191
|
+
> 约定:所有占位点都写成 `PLACEHOLDER: <名字>` 注释 + 可运行的降级路径,替换时只需改该函数的实现,调用方(卡片、设置页)无需改动。
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 七、DSH 版本适配(近 10 余个版本,**证据来自已发布产物**)
|
|
196
|
+
|
|
197
|
+
不是靠字段笔记,而是把每个版本要用的包从 npm 拉下来、解包、按标记字符串判定:
|
|
198
|
+
|
|
199
|
+
```powershell
|
|
200
|
+
node tools/compat-probe.mjs # 13 个版本 × 12 个包
|
|
201
|
+
node tools/compat-probe.mjs --json tools/.cache/matrix.json
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
脚本会 `npm pack` 相应包到 `tools/.cache/tarballs/`(已在 .gitignore),用内置 tar 读取器在内存里搜索,
|
|
205
|
+
然后打印下面的能力矩阵。**矩阵里的 ❌ 就是插件必须降级的点**,而插件对每一个 ❌ 都有对应分支。
|
|
206
|
+
|
|
207
|
+
### 7.1 能力矩阵(0.1.2-rc.1 → 0.1.7-rc.2)
|
|
208
|
+
|
|
209
|
+
| 能力(依赖的 API) | 0.1.2-rc.1 | 0.1.3-alpha.2 | 0.1.5-alpha.1 … 0.1.5-rc.3 | 0.1.6-alpha.1 | 0.1.6-alpha.2 | 0.1.7-alpha.1 … 0.1.7-rc.2 |
|
|
210
|
+
|---|---|---|---|---|---|---|
|
|
211
|
+
| 客户端产物协议 `window.__ModuleLoader__.load` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
212
|
+
| `shell.overlay`(浮层/触发按钮挂载点) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
213
|
+
| `conversation.input.right`(会话 id 采集) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
214
|
+
| `settings.section`(设置页) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
215
|
+
| `data-slot` 出口锚点(区域归类) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
216
|
+
| 原生右侧栏 `sidebarRightTabs` + `sidebar.right.pane.tab` | **❌** | **❌** | ✅ | ✅ | ✅ | ✅ |
|
|
217
|
+
| 主对话提交 `sessions.using(target, options, operation)` | **❌** | **❌** | **❌** | **❌** | ✅ | ✅ |
|
|
218
|
+
| 主对话提交 `sessions.retain(target, options)` | **❌** | **❌** | **❌** | **❌** | ✅ | ✅ |
|
|
219
|
+
| 归档会话门(拒绝归档血统的每一步) | **❌** | **❌** | **❌** | **❌** | **❌** | ✅ |
|
|
220
|
+
| 进程内流式帧 `agent/assistant-stream` | **❌** | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
221
|
+
| 持久化分块 `assistant/chunk`(会话事件) | **✅** | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
222
|
+
| 子代理 `subagents.start` + `SubagentRun.localAgent` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
223
|
+
| provider 能力面(`toolFilter` / `persona`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
224
|
+
| `tools.schemas()` + `tools.restrict()` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
225
|
+
| 宿主路由 `webServer.register({kind:'prefix'})` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
226
|
+
|
|
227
|
+
### 7.2 每个 ❌ 对应的适配(都在代码里)
|
|
228
|
+
|
|
229
|
+
| 缺口 | 适配实现 | 在真机/测试里的表现 |
|
|
230
|
+
|---|---|---|
|
|
231
|
+
| 0.1.2 / 0.1.3 没有原生右侧栏 | 承载面探测链:原生右侧栏 → `dsh-better-sidebar` → **内置浮层卡片**;不可用的一律不计入 `pickSurface` | `contract-test` 断言 `pickSurface('auto')` 在无插件组合下落到 `flow`;`auto` 在可用时优先原生 |
|
|
232
|
+
| ≤0.1.6-alpha.1 没有 `using`/`retain` | 主对话提交**四级阶梯**(调用时逐级探测,不查版本表):`sessions.using` → `sessions.retain`+`release` → 槽位标准 prop `inputActions`(`setDraft` + `submit`)→ 仅写入输入框并明确提示"请按 Enter" | `contract-test` 逐级断言 `via`:`sessions.using` / `sessions.retain`(并断言 `release()` 被调用)/ `inputActions.submit` / `composer.draft` / 全无时抛 `no-main-carrier` |
|
|
233
|
+
| ≤0.1.6-alpha.2 没有归档门 | 不再因"父会话已归档"直接拒绝:仍优先挑未归档代理,但只有归档候选时**照常尝试**,并在 `start` 事件里带 `parentArchived:true`;真被门拒绝时把空 turn 的 `refusal` 翻译成 `blocked-step` 并说明原因 | `smoke-test`:全归档组合仍能拿到答案;`refusal` 用例断言 `code=blocked-step` 且文案提到归档 |
|
|
234
|
+
| 0.1.2-rc.1 没有进程内帧 | 增加**第二条流式源**:订阅 `session/event` 的持久化 `assistant/chunk`(`{turn,step,chunk}`,形状取自该版本自己的 `chunk-rows.js`),与帧源**互斥**(先说话的那个生效,绝不重复计一次文本) | `smoke-test`:`frameMode:'chunks'` 用例断言 delta 拼出完整答案、`streaming:true`、`streamSource:'chunks'`;同时断言两源并存时文本不重复 |
|
|
235
|
+
| provider 能力面差异 | 只发 provider **声明支持**的启动字段(`capabilities.persona/toolFilter`);`persona` 不支持时**内联进提示词**;`toolFilter` 不支持时在 `start` 事件里报 `toolFilter:'unsupported'`,卡片明说"本 provider 不支持工具白名单" | `smoke-test`:`capabilities:null` 与 `{toolFilter:false,persona:true}` 两种 provider 的字段断言 |
|
|
236
|
+
| 槽位键在老版本可能不同 | 每个注册走**槽位阶梯**(都是 `list` 槽,绝不碰 `single` 槽以免替换宿主 UI):浮层 `shell.overlay → conversation.input.dock → conversation.composer.dock`;设置页 `settings.section → settings.plugins.tab`;会话采集 `conversation.input.right → conversation.input.left → composer.dock → input.dock`。**先到者胜**,更好的槽位后到会顶掉兜底 | `contract-test`:`absentSlots` 模拟未声明的槽位,断言注册落到下一级且诊断里记录了落点 |
|
|
237
|
+
| 没有 `data-slot` 锚点 | 检测到选区但槽位路径为空 → 判定"区域锚点不可用",**停用区域过滤**(否则 `captureZones:chat` 会静默失效)并在设置页自检里标明 | `contract-test`:`shouldOffer(..., {anchors:false})` 断言放宽且返回 `zoneFiltering:'unavailable'` |
|
|
238
|
+
| 路由 kind 不被接受 | `prefix` 注册失败 → 退化为**逐方法精确路由**(`state/ask/cancel/config/reset`),处理器不变 | 前缀路由由 smoke 全流程覆盖;退化分支为纯 fallback(未在真机触发过) |
|
|
239
|
+
|
|
240
|
+
### 7.3 还没做真机验证的部分(如实标注)
|
|
241
|
+
|
|
242
|
+
- 上表所有 ✅/❌ 都是**包内容层面的证据**(字符串/签名存在于该版本的发布产物),不等于"插件在该版本上跑起来过"。
|
|
243
|
+
唯一跑过真机的是 **0.1.7-rc.2**(见 §10.1 的实测记录)。
|
|
244
|
+
- 在 0.1.2-rc.1 … 0.1.6-alpha.2 这几个版本上,我只验证了"所需 API 是否存在 + 插件有为缺失准备的分支",
|
|
245
|
+
**没有**在那些版本上安装并启动过插件。
|
|
246
|
+
- `inputActions` 作为会话槽位标准 prop:13 个版本的 `dsh-client-ui-conversation` 产物里都有该名字,
|
|
247
|
+
0.1.7-rc.2 的槽位目录也把它列为 `conversation.input.right` 的 standardProps;**更早版本是否真的把它下发给该槽位条目未验证**,
|
|
248
|
+
插件对此是探测式使用(取不到就走草稿降级)。
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## 八、边界处理清单(对应需求第六点)
|
|
253
|
+
|
|
254
|
+
| 边界 | 处理 | 代码位置 |
|
|
255
|
+
|---|---|---|
|
|
256
|
+
| **空选 / 过短** | 选区为空、折叠、或短于 `minChars` → 不浮出按钮并清掉上一次的触发态 | `readSelection` / `shouldOffer`(client §5) |
|
|
257
|
+
| **编辑框内选择** | 选区锚点/焦点落在 `input`/`textarea`/`contenteditable` 内 → 视为编辑操作,不触发 | `isEditable`(client §5) |
|
|
258
|
+
| **跨区选择** | 锚点与焦点区域不同 → 标为"跨区选择",按**锚点**区域归类并在徽标上显示;仍可追问 | `readSelection` 的 `cross`、`zoneLabel` |
|
|
259
|
+
| **超长文本** | 头 70% + 尾 30% 保留,中间插入「已省略中间 N 个字符」;提问框与卡片都显示截断徽标;宿主侧再做一次硬上限(60000 字符提示词上限) | `truncateSelection`(两端一致,契约测试断言等价) |
|
|
260
|
+
| **接口失败** | 逐层降级:子代理不可用 → 卡片错误 + 「改到主对话」;主对话发送不可用 → 草稿写入;再不可用 → 明确报错不静默 | `toWireError` / `askInMainConversation` |
|
|
261
|
+
| **重复触发** | 同一选区 + 近似位置在 400ms 内只触发一次;同一卡片 id 的重复请求返回 `duplicate`;并发超限返回 `busy`(可重试) | `selectionSignature`、`runs.has(id)`、`maxConcurrentAsks` |
|
|
262
|
+
| **流式中断** | 浏览器断开 / 点「停止作答」/ 超时 → `AbortController` 取消子代理:有部分文本时以 `done{aborted:true}` 收尾并保留已流出的内容(卡片显示「已停止」),一个字都没流出时才用 `error.code='aborted'` | `handleAsk` 的 `res.on('close')` 判定、`sideTimeoutMs` |
|
|
263
|
+
| **父会话已归档** | 归档门会拒绝**整条子代理血统**里的每一步(表现为「没有发起任何模型请求的空 turn」),但它只存在于 0.1.7-alpha.1+。插件优先挑未归档代理;只剩归档候选时**照常尝试**并在 `start` 事件里带 `parentArchived:true`(旧版本本来就能正常作答,一刀切拒绝会误伤 0.1.2–0.1.6) | `resolveParent` / `archivedSessionIds`(host) |
|
|
264
|
+
| **步骤被宿主拒绝** | 子代理接缝把这种空 turn 记为 `refusal`;插件翻译成 `blocked-step`,文案点明常见原因(会话已归档)并给出「改到主对话」 | `ask()` 的终局映射(host) |
|
|
265
|
+
| **卸载** | 插件卸载时取消全部进行中的作答、注销槽位、移除 DOM 监听、断开流式桥 | `ctx.effect` + `disposers` |
|
|
266
|
+
| **跨站请求** | 插件路由带信任围栏:Host 必须是回环或配置的可信域,`sec-fetch-site: cross-site` 或跨域 `Origin` 一律 403 | `isTrustedRequest`(host) |
|
|
267
|
+
|
|
268
|
+
### 错误码对照(`error` 事件的 `code`)
|
|
269
|
+
|
|
270
|
+
| code | 含义 | 是否可重试 | 建议动作 |
|
|
271
|
+
|---|---|---|---|
|
|
272
|
+
| `bad-request` | 问题或选中文本为空 | 否 | 重新选中/输入 |
|
|
273
|
+
| `busy` | 并发追问达上限 | 是 | 稍后重试 |
|
|
274
|
+
| `duplicate` | 同一卡片 id 已有在跑的任务 | 否 | 等它结束 |
|
|
275
|
+
| `no-side-engine` | 没有可用的子代理 provider | 否 | 「改到主对话」 |
|
|
276
|
+
| `no-parent` | 没有活动会话代理 | 否 | 「改到主对话」 |
|
|
277
|
+
| `blocked-step` | 这一步被宿主的 `agent/pre-step` 拒绝(未发起请求);0.1.7+ 上最常见的原因是会话已归档 | 是 | 换会话或「改到主对话」 |
|
|
278
|
+
| `aborted` | 被取消或超时,且没有已流出的内容 | 是 | 重试 |
|
|
279
|
+
| `engine-error` / 其它 | 子代理启动、模型调用或传输失败 | 是 | 重试或改到主对话 |
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## 九、三条关键路径的自测要点
|
|
284
|
+
|
|
285
|
+
### A. 路径一:就地追问 → 侧边卡片流式作答
|
|
286
|
+
|
|
287
|
+
1. 在聊天区选中一句**非输入框内**的文本(例如助手消息里的半句话);
|
|
288
|
+
2. 选区末端应浮出「💬 追问选中内容」按钮,按钮带区域徽标(聊天区 / 任务区 / 跨区选择);
|
|
289
|
+
3. 点击后按钮消失、提问框出现,焦点在输入框,引文预览显示选中文本;
|
|
290
|
+
4. 输入问题按 Enter → 提问框关闭,右下角出现卡片,状态先为「作答中…」并**逐字增长**;
|
|
291
|
+
5. 结束后状态变「已完成」;底部出现「复制 / 继续追问 / 关闭」;
|
|
292
|
+
6. 点「复制」→ 出现「已复制」提示;点「继续追问」→ 输入第二条问题,卡片内容**重置换行**后继续流式;
|
|
293
|
+
7. 点「关闭」→ 卡片消失;若承载面是 better-sidebar/原生右侧栏,对应 tab 也应关闭。
|
|
294
|
+
|
|
295
|
+
**失败信号**:卡片停在「作答中…」不动 = SSE 帧没到达(看 console 是否有 `/sidecard-ask/api/ask` 报错);`done.streaming=false` = 该版本没有流式帧(属预期降级,卡片会写明)。
|
|
296
|
+
|
|
297
|
+
### B. 路径二:主对话承载
|
|
298
|
+
|
|
299
|
+
1. 选中文本 → 提问框里把「作答位置」切到**主对话** → 输入问题 → Enter;
|
|
300
|
+
2. 主对话应立刻出现一条用户消息,形如:
|
|
301
|
+
|
|
302
|
+
```
|
|
303
|
+
> 选中的原文(逐行引用)
|
|
304
|
+
> …(已截断时会有「已省略约 N 字」)
|
|
305
|
+
|
|
306
|
+
【来源:聊天区】
|
|
307
|
+
你的问题
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
3. 该消息的答案由**当前会话**正常流式输出(这就是"主对话承载"的定义);
|
|
311
|
+
4. 卡片侧显示「已发送到主对话」并说明答案在主对话中;若接口不可用而降级为草稿写入,卡片会明确显示「已填入输入框」,此时需手动按 Enter 发送(**不算失败**,但要能看到这条提示)。
|
|
312
|
+
|
|
313
|
+
### C. 路径三:快捷键 + 引擎不可用的降级
|
|
314
|
+
|
|
315
|
+
1. 选中文本 → 按 `Alt+Q` → 提问框直接出现(无需点按钮);
|
|
316
|
+
2. 在设置页把「触发方式」改成「仅快捷键」→ 再选中文本时**不应**出现按钮,但 `Alt+Q` 仍可用;
|
|
317
|
+
3. 把「侧边卡片承载面」强制设为 `native-rightbar` 或 `better-sidebar`(未安装/未启用时)→ 追问应回退到内置浮层卡片,并提示"已回退";
|
|
318
|
+
4. 用一个没有子代理的 DSH 组合(或临时把 `sideProvider` 设成一个不存在的名字)→ 侧边作答应返回错误卡片:
|
|
319
|
+
文案说明"没有可用的子代理",并给出「改到主对话」按钮;点它应把同一问题转到主对话(不得丢失已选中的文本与问题)。
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## 十、自测脚本
|
|
324
|
+
|
|
325
|
+
```powershell
|
|
326
|
+
cd D:\Users\34332\AI\dsh-sidecard-ask
|
|
327
|
+
node test/verify.mjs # 静态:清单/补丁/导出面/i18n/零依赖
|
|
328
|
+
node test/contract-test.mjs # 契约:常量、信封、SSE 逐帧、纯函数、槽位注册与渲染
|
|
329
|
+
node test/smoke-test.mjs # 端到端:流式/截断/取消/持久化/失败分支/并发/围栏
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
三个脚本都以 `process.exitCode` 反映结果,失败会列出具体条目;测试会把 `DSH_HOME` 指向临时目录,不会污染真实配置。
|
|
333
|
+
当前规模:verify 53 项 + contract 105 项 + smoke 118 项 = **276 项全部通过**。
|
|
334
|
+
|
|
335
|
+
### 10.2 版本能力探测(§7 矩阵的来源)
|
|
336
|
+
|
|
337
|
+
```powershell
|
|
338
|
+
node tools/compat-probe.mjs # 13 个版本 × 12 个包,逐个 npm pack 后按标记判定
|
|
339
|
+
node tools/compat-probe.mjs --json tools/.cache/matrix.json
|
|
340
|
+
node tools/compat-probe.mjs 0.1.7-rc.2 # 也可只探某个版本
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
首次运行会下载约 250 个包到 `tools/.cache/tarballs/`(已 gitignore,之后走缓存);输出的矩阵就是 README §7 的表。
|
|
344
|
+
|
|
345
|
+
### 10.1 对已安装实例做真实联调(本机实测通过)
|
|
346
|
+
|
|
347
|
+
```powershell
|
|
348
|
+
# 1) 宿主接口与能力(archived 父会话检测也在这里)
|
|
349
|
+
(Invoke-WebRequest http://127.0.0.1:8080/sidecard-ask/api/state -UseBasicParsing).Content
|
|
350
|
+
|
|
351
|
+
# 2) 端到端流式作答:把 sessionId 换成当前会话 id($env:DSH_SESSION_ID)
|
|
352
|
+
# 期望事件序列:start → reasoning*/delta* → status → done(done.text 是真实答案)
|
|
353
|
+
$body = @{ id='probe'; question='用一句话说明这句话在说什么。';
|
|
354
|
+
selection='槽位出口会带上 data-slot 标记。'; zone='chat';
|
|
355
|
+
sessionId=$env:DSH_SESSION_ID } | ConvertTo-Json -Compress
|
|
356
|
+
$tmp = Join-Path $env:TEMP 'probe.json'
|
|
357
|
+
[System.IO.File]::WriteAllText($tmp, $body, (New-Object System.Text.UTF8Encoding($false)))
|
|
358
|
+
(Invoke-WebRequest http://127.0.0.1:8080/sidecard-ask/api/ask -Method POST `
|
|
359
|
+
-ContentType 'application/json; charset=utf-8' -InFile $tmp -TimeoutSec 240 -UseBasicParsing).Content
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**2026-09-28 在 DSH 0.1.7-rc.2 上的实测结果**(`provider=spawn`、`sideTools=readonly`):
|
|
363
|
+
`start x1 → reasoning x181 → delta x90 → status x1 → done x1`,耗时 2.6 s,`done.text` 为真实模型答案。
|
|
364
|
+
这条记录同时说明:子代理启动、只读工具白名单(与真实工具表求交后)、流式帧桥接、SSE 线协议、运行结束后的 `dispose()` 都是**在真实宿主上跑通的**,
|
|
365
|
+
不只是单元测试里的替身。
|
|
366
|
+
|
|
367
|
+
> 改过 `index.js` 之后**必须重启 DSH** 才会加载新的宿主半代码:插件行在 profile 启动时已被 Loader 导入,重新启停该行不会重新读盘
|
|
368
|
+
> (本机实测:`set_plugin` 关开、`remove_bundle` + `install_bundle` 都不会刷新已加载的模块;只有进程重启会)。
|
|
369
|
+
> `client.js` 的改动则需要刷新页面(客户端产物带修订号,刷新即重新拉取)。
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## 十一、架构与数据流
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
选中文本 (client §5)
|
|
378
|
+
│ page → data-slot 链 → 区域归类 → 触发按钮
|
|
379
|
+
▼
|
|
380
|
+
提问框 (client §9) ── 作答位置选择 ──┐
|
|
381
|
+
│ │
|
|
382
|
+
│ side │ main
|
|
383
|
+
▼ ▼
|
|
384
|
+
POST /sidecard-ask/api/ask ctx.sessions.using(id).binding.session.prompt(...)
|
|
385
|
+
│ (SSE) │ ↓ 草稿降级 / 复制兜底
|
|
386
|
+
▼ 答案随主对话原生流式
|
|
387
|
+
Host: ctx.subagents.start('spawn')
|
|
388
|
+
→ 子代理(自己的会话、零父上下文)
|
|
389
|
+
→ agent/assistant-stream 帧 → sink.send('delta')
|
|
390
|
+
→ run.result 结算 → sink.send('done') → run.dispose()
|
|
391
|
+
▼
|
|
392
|
+
卡片流式渲染 / 复制 / 继续追问 / 关闭
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**为什么侧边卡片用子代理而不是"另开一个会话"**:子代理由进程内 spawn provider 建立,拥有自己的会话与系统提示、**不继承父上下文**,
|
|
396
|
+
且随 `dispose()` 释放——这正是"独立附属"的语义;同时它不需要往工作区/侧边栏注册新会话,用户不会在会话列表里看到一堆临时条目。
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## 十二、隐私与安全
|
|
401
|
+
|
|
402
|
+
- 选中文本与问题会**发给你自己配置的模型**(走 DSH 既有的模型路由),插件不做任何额外外发。
|
|
403
|
+
- 插件只在本地写一个配置文件:`<DSH_HOME>/sidecard-ask/config.json`(原子写入:先写 `.tmp` 再 rename)。
|
|
404
|
+
- 插件路由带信任围栏(回环 / 可信域 + 同源校验),仅本机页面可用。
|
|
405
|
+
- 侧边作答者默认**只读**(`sideTools: readonly`):白名单只包含读取、搜索、网络查询类工具,不会在后台改你的工作区。
|
|
406
|
+
- 提示词里选中文本被包在 ```` ```text ```` 围栏中并显式声明"这是数据不是指令",降低提示注入面。
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## 十三、发布流程
|
|
411
|
+
|
|
412
|
+
```powershell
|
|
413
|
+
npm whoami # 先确认登录态(改过 2FA/密码会让旧 token 失效)
|
|
414
|
+
node test/verify.mjs; node test/contract-test.mjs; node test/smoke-test.mjs
|
|
415
|
+
npm publish --access public
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
版本号同时出现在三处,必须一致:`package.json` 的 `version`、`index.js` 的 `PLUGIN_VERSION`、`client.js` 的 `api.version`
|
|
419
|
+
(`test/verify.mjs` 会断言前两处;第三处在契约测试中同样被断言)。
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## 十四、已知限制(含未在本机验证的部分)
|
|
424
|
+
|
|
425
|
+
1. **区域归类是启发式的**:`data-slot` 名称来自 harness 自己;其它插件若在自己的面板里不再经由槽位渲染,
|
|
426
|
+
该区域会被归为"其它区域"——用 `captureZones: auto` + `showInUnclassified: true`(默认)仍然可用,或改用 `captureZones` 限制。
|
|
427
|
+
本机实测能识别:`conversation.*`(聊天)、`rightbar` / `sidebar.right.*` / 名字含 task·todo·schedule·team·job·plan 的面板(任务)。
|
|
428
|
+
2. **浏览器内的视觉与交互未在本机验证**:本工作区没有浏览器自动化工具,因此"按钮出现在选区旁""卡片逐字增长""浮层不挡操作"这些**只能由你按第九节点一遍**。
|
|
429
|
+
已做的保障是:只使用 `--dsw-alias-*` 主题令牌、只在槽位内渲染(`shell.overlay` / `settings.section` / `conversation.input.right`)、不写 `document.body`、不 import harness 客户端包。
|
|
430
|
+
3. **原生右侧栏承载面未在真实宿主上验证**:本机 profile 虽已启用 `@deepseek-ai/dsh-client-ui-sidebar-right`,但没有浏览器控件去确认 tab 是否真的渲染。
|
|
431
|
+
为此加了一道**渲染证明**:适配器接受了打开请求后 600ms 内若没有观察到我们的卡片组件挂载,就自动把卡片移到内置浮层(流式内容不丢,因为卡片数据在 store 里)。
|
|
432
|
+
4. **主对话承载的"发送"依赖客户端会话服务**:`ctx.sessions.using(...).prompt(...)` 在会话未被保留/不可用时降级为草稿写入,
|
|
433
|
+
降级时会在卡片上明确显示,需要手动按 Enter。
|
|
434
|
+
5. **宿主半代码更新必须重启 DSH**:本机实测 `set_plugin` 关开与 `remove_bundle` + `install_bundle` 都不会让已加载的模块重新读盘。
|
|
435
|
+
本仓库当前文件比运行中的宿主模块新(1.0.1 的「归档父会话不再一刀切 / 持久化 chunk 流式源 / 能力门控 / 路由降级」),
|
|
436
|
+
**重启后**这些改动才生效;`/state` 的 `version` 与 `capabilities.parent` 字段可以直接确认是否已加载新版。
|
|
437
|
+
6. **除 0.1.7-rc.2 外,其余版本只做了产物层验证**:见 §7.3——插件在 0.1.2-rc.1 … 0.1.6-alpha.2 上安装启动过**没有**得到验证,
|
|
438
|
+
验证到的是"这些版本缺哪些 API + 插件对每个缺失都有分支",分支本身由 276 项自测覆盖。
|
|
439
|
+
|