dsh-plugin-bridge 0.2.10
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 +146 -0
- package/README.zh.md +146 -0
- package/cordis.patch.yml +18 -0
- package/docs/articles/agent-session-handoff.md +162 -0
- package/docs/benchmark.md +165 -0
- package/docs/design.md +82 -0
- package/docs/guide.zh.md +152 -0
- package/docs/native-webui-feasibility.md +44 -0
- package/docs/plan.md +65 -0
- package/lib/api-rpc.d.ts +64 -0
- package/lib/api-rpc.js +76 -0
- package/lib/cli.d.ts +2 -0
- package/lib/cli.js +368 -0
- package/lib/command.d.ts +92 -0
- package/lib/command.js +377 -0
- package/lib/compression.d.ts +90 -0
- package/lib/compression.js +369 -0
- package/lib/fold.d.ts +39 -0
- package/lib/fold.js +406 -0
- package/lib/index.d.ts +55 -0
- package/lib/index.js +76 -0
- package/lib/migrate.d.ts +176 -0
- package/lib/migrate.js +449 -0
- package/lib/rpc.d.ts +34 -0
- package/lib/rpc.js +76 -0
- package/lib/types.d.ts +64 -0
- package/lib/types.js +9 -0
- package/package.json +106 -0
- package/reports/v0.2.3-e2e-2026-08-20T13-19-13-924Z.raw.json +2727 -0
- package/reports/v0.2.3-e2e-report.md +116 -0
- package/reports/v0.2.6-rc11-vision-report.md +65 -0
package/docs/design.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Bridge design and evidence
|
|
2
|
+
|
|
3
|
+
Bridge treats a preset as a complete tool-and-prompt assembly, not a style selector. A produced session therefore stays locked to the assembly that created its tool history. Migration opens a clean target session and carries a bounded state handoff instead of replaying incompatible calls.
|
|
4
|
+
|
|
5
|
+
## Migration contract
|
|
6
|
+
|
|
7
|
+
The handoff has five fixed sections:
|
|
8
|
+
|
|
9
|
+
1. Goal
|
|
10
|
+
2. Current state
|
|
11
|
+
3. Key decisions and conventions
|
|
12
|
+
4. Key files
|
|
13
|
+
5. Next step
|
|
14
|
+
|
|
15
|
+
The preview is a user-visible protocol step, not debug output. It is written to a file so numbers, paths, and decisions can be corrected before the target exists. Tool traces from the source preset are intentionally omitted.
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
fold source history -> generate five-part handoff -> preview/edit
|
|
19
|
+
-> create clean target -> pause stored goal
|
|
20
|
+
-> inject handoff -> restate -> wait or continue
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The source session is never rewritten. A failed or unsatisfactory target can be archived while the source remains available.
|
|
24
|
+
|
|
25
|
+
## Safety boundary
|
|
26
|
+
|
|
27
|
+
- Preview creates and archives a temporary summary worker, but does not mutate the source or create the target.
|
|
28
|
+
- Migration creates a blank target under the selected preset.
|
|
29
|
+
- A resumable goal may store the handoff, but Bridge pauses it before kickoff. Goal mutation alone does not inject model context, so the kickoff also contains the reviewed handoff.
|
|
30
|
+
- If goal pause fails, Bridge attempts the optional `goal.clear`, cancels the target, and sends no kickoff.
|
|
31
|
+
- `goalRounds` defaults to 1 as a second bound against an upstream autonomous goal loop.
|
|
32
|
+
- Installing Bridge adds no model tool, skill, or persistent prompt text. Ordinary sessions therefore receive zero Bridge prompt tokens.
|
|
33
|
+
|
|
34
|
+
## Confirmation and cost
|
|
35
|
+
|
|
36
|
+
The default flow favors verification: the target restates the handoff in one short request and waits. `--continue` asks the same target request to restate and begin the next step.
|
|
37
|
+
|
|
38
|
+
Across the fixed six-pair release acceptance, confirm used one more target request than `--continue`. Its nominal extra for summary plus first useful work had a paired median of **+8.1%** and a range of **-47.9% to +206.7%**. The summary worker represented **20.74%** of the clean acceptance components by nominal tokens. That share is composition, not causal overhead versus a no-Bridge baseline.
|
|
39
|
+
|
|
40
|
+
`Nominal = uncached input + output`. The processed sensitivity measure also counts cache reads and writes equally; it is not a bill. Preset size, response length, and cache state caused wide variation, so Bridge claims a stable request-shape difference rather than a universal saving percentage.
|
|
41
|
+
|
|
42
|
+
See the [release acceptance report](../reports/v0.2.3-e2e-report.md) and [raw JSON](../reports/v0.2.3-e2e-2026-08-20T13-19-13-924Z.raw.json).
|
|
43
|
+
|
|
44
|
+
## Image evidence policy
|
|
45
|
+
|
|
46
|
+
Image-derived information does not share the compressed-summary budget:
|
|
47
|
+
|
|
48
|
+
- If an image already has an associated assistant response, Bridge copies that response verbatim under `Visual evidence`. The summary worker cannot rewrite it, and the raw image is not resent by default.
|
|
49
|
+
- If no assistant response follows the image, Bridge marks it unresolved. On hosts with durable attachment recovery, it reads the original attachment and tries to include it in the target kickoff.
|
|
50
|
+
- Before kickoff, Bridge copies the source provider, model, and reasoning effort to the blank target. This prevents the summary worker or host default from silently replacing a vision route.
|
|
51
|
+
- A vision-capable target receives the raw image. A text-only target rejects it during prompt admission; Bridge then sends the text handoff with an explicit unresolved warning.
|
|
52
|
+
|
|
53
|
+
The normal summary is capped by `summaryCharBudget` (2,400 characters by default). Verbatim visual evidence has a separate 60,000-character budget and is admitted as complete blocks; an older block may be omitted with a warning, but is never cut mid-statement.
|
|
54
|
+
|
|
55
|
+
The rc.2 visual acceptance used facts visible only in a PNG. Existing visual evidence passed **5/5** without resending the image; the unresolved-image path passed **5/5** with one raw image sent to `deepseek-v4-flash-vision-exp`. See the [vision report](../reports/v0.2.6-rc11-vision-report.md).
|
|
56
|
+
|
|
57
|
+
## Accuracy evidence
|
|
58
|
+
|
|
59
|
+
The repair-driven release gate covered six frozen fixtures and twelve target sessions:
|
|
60
|
+
|
|
61
|
+
| Measure | Result |
|
|
62
|
+
|---|---:|
|
|
63
|
+
| Summary facts | 30/30 |
|
|
64
|
+
| Target restatement facts | 60/60 |
|
|
65
|
+
| First useful work facts | 60/60 |
|
|
66
|
+
| Critical facts | 90/90 |
|
|
67
|
+
| Obsolete-value resurrection | 0 |
|
|
68
|
+
| Exact confirm / continue request count | 6/6 / 6/6 |
|
|
69
|
+
|
|
70
|
+
This is a regression gate, not a population-level accuracy guarantee. Three sources reused real compaction material and three were short sources; targets covered minimal, standard, and code presets. The earlier compression-tier study remains in [benchmark.md](benchmark.md) with its sampling and scoring limitations.
|
|
71
|
+
|
|
72
|
+
## Why not switch in place?
|
|
73
|
+
|
|
74
|
+
Stored tool calls are valid only under the preset composition that produced them. DeepSeek Harness therefore permits `agentPreset.select` only while a session is blank. Bypassing that lock would leave ghost calls that the new tool set cannot execute. Bridge keeps the lock and transfers state to a clean target.
|
|
75
|
+
|
|
76
|
+
Model and reasoning-effort changes are separate from preset migration and may still happen within one session.
|
|
77
|
+
|
|
78
|
+
## Upstream coupling
|
|
79
|
+
|
|
80
|
+
Bridge deliberately uses a narrow host contract. `/bridge --doctor` checks thirteen required gateway methods and names missing methods after a Harness upgrade. Attachment recovery and `goal.clear` are optional enhancements and do not raise that baseline.
|
|
81
|
+
|
|
82
|
+
The server-side slash command remains the compatibility core. A native WebUI card is feasible but would depend on prerelease client-module and slot contracts; the current decision is documented in [native-webui-feasibility.md](native-webui-feasibility.md).
|
package/docs/guide.zh.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Bridge 使用指南:安装与使用
|
|
2
|
+
|
|
3
|
+
> 适用对象:使用官方 `dsh web`(WebUI)的用户。装上、重启、在输入框里打 `/bridge`——没有别的步骤。
|
|
4
|
+
|
|
5
|
+
## 1. 安装
|
|
6
|
+
|
|
7
|
+
前置:已安装 dsh(`dsh --version` 能输出版本,本插件已核对 0.1.0-rc.6 / rc.7 / rc.8,并在 0.1.1-rc.2 完成真实视觉迁移),并有一个可跑的 web profile(跑过一次 `dsh web` 即会自动初始化)。
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
dsh plugin --profile web add dsh-plugin-bridge
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
需要固定 GitHub tag 或 npm 暂时不可用时:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
dsh plugin --profile web add github:Totoro-qaq/dsh-plugin-bridge#v0.2.10
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
发生了什么(不用手动干预):
|
|
20
|
+
|
|
21
|
+
1. dsh 在 profile 目录(`~/.dsh/profiles/web/`)执行 `pnpm add`,拉取本包(`lib/` 已预构建,不触发 pnpm ≥10 的构建脚本白名单);
|
|
22
|
+
2. dsh 检测到本包 package.json 里的 `dsh.bundle.patch` 声明,自动把 `dsh-plugin-bridge` 追加进 `dsh.profile.bundles` 层栈;
|
|
23
|
+
3. **重启 `dsh web`**(插件在启动时挂载)。验证:重启后在任意会话里打 `/bridge`,应当列出可迁入的模式。
|
|
24
|
+
|
|
25
|
+
卸载:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
dsh plugin --profile web remove dsh-plugin-bridge # 重启后生效
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
命令是通过 cordis 的 effect 作用域注册的,插件卸载时随 fiber 一起消失——不会有残留的 `/bridge`。
|
|
32
|
+
|
|
33
|
+
## 2. 什么时候用它
|
|
34
|
+
|
|
35
|
+
你在「创造」模式聊到一半,发现接下来要写代码(或反过来)——模式切换入口是锁的。**锁是对的**(原因见 README「为什么有这个项目」),Bridge 是那个出口:把当前进展压缩成交接摘要,在目标模式下开新会话继续,原会话原地保留。
|
|
36
|
+
|
|
37
|
+
不适合的场景:会话刚开始(没什么可带的,直接开新会话更省事);你只需要换模型或思考强度(那本来就能会话内切,不用迁移)。
|
|
38
|
+
|
|
39
|
+
## 3. 怎么用
|
|
40
|
+
|
|
41
|
+
三条命令,在任意会话的输入框里直接打:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
/bridge 这个会话能迁到哪些模式?
|
|
45
|
+
/bridge code 生成交接摘要给你过目——什么都不改
|
|
46
|
+
/bridge code --go 建新会话、复述交接,然后等我确认
|
|
47
|
+
/bridge code --go --continue 同一轮复述并继续下一步
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`/bridge` 是一条普通的 dsh slash 命令(和 `/compact`、`/goal`、`/plan` 同一套机制)。host 把它路由给命令注册表,**全程不经过模型**,输出由 UI 渲染、不进对话历史。所以:
|
|
51
|
+
|
|
52
|
+
- 不需要用你的话去说服模型做什么,也不需要 bash 或环境变量;
|
|
53
|
+
- `minimal` 这种没有 skill 工具的模式照样能发起迁移;
|
|
54
|
+
- 原会话是真的没被动过——命令结果根本不是一条消息。
|
|
55
|
+
|
|
56
|
+
### 3.1 预览(唯一需要你动脑子的一步)
|
|
57
|
+
|
|
58
|
+
`/bridge code` 会:拉取并折叠会话历史 → 按字符预算取材(用户消息全文 + 最近几轮结论 + 最近一次 compaction 底稿)→ 起一个临时的压缩工人生成**固定五段摘要**(目标 / 当前状态 / 关键决策与约定 / 关键文件 / 下一步)→ 工人用完即归档 → 把摘要贴给你。
|
|
59
|
+
|
|
60
|
+
通常 20–60 秒。**认真扫一眼**:
|
|
61
|
+
|
|
62
|
+
- 目标对不对;
|
|
63
|
+
- 关键约定在不在——尤其是**数字**,漂移里最主要的一类就是端口被补全成 3000/8080;
|
|
64
|
+
- 文件路径是不是都来自本会话;
|
|
65
|
+
- 如果输出里有 `⚠ 取材因预算被裁剪`,说明摘要是基于被裁过的历史写的,要更仔细。
|
|
66
|
+
|
|
67
|
+
### 3.2 要改摘要
|
|
68
|
+
|
|
69
|
+
预览会把摘要写进一个文件并打印路径。改那个文件,然后:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
/bridge code --go --file /tmp/dsh-bridge-xxxx/summary-....md
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**文件是唯一事实源**——比让模型「记住你刚才说要改哪里」可靠得多。
|
|
76
|
+
|
|
77
|
+
### 3.3 执行
|
|
78
|
+
|
|
79
|
+
`/bridge code --go` 会用目标 preset 建新会话、把摘要挂为会话目标(goal)**并立即暂停该目标**、同时把摘要放进首轮提示。新会话只会先**复述它对当前状态的理解**,然后等你确认——复述里丢没丢东西,一眼可见。确认没问题后在目标会话继续说话即可。
|
|
80
|
+
|
|
81
|
+
只有明确想让它立刻开工时,才用 `/bridge code --go --continue`。它会在**同一次目标模型请求**里先复述再开始下一步;goal 仍然保持暂停,所以 round driver 不会在空闲后追加第二轮。
|
|
82
|
+
|
|
83
|
+
### 3.4 不满意怎么办(回退)
|
|
84
|
+
|
|
85
|
+
- 新会话接得不对:直接点回**原会话**继续,它一个字都没动过;新会话归档即可;
|
|
86
|
+
- 摘要生成得不好:在预览那一步就改文件,不必迁过去再后悔;
|
|
87
|
+
- 习惯性建议:迁移前如果工作区有未提交改动,自己先 `git commit` 或记下状态——Bridge 不动你的工作区,但新会话接下来的动作是在同一工作区里进行的。
|
|
88
|
+
|
|
89
|
+
## 4. 命令行(备用路径)
|
|
90
|
+
|
|
91
|
+
同一套引擎也发布成 `dsh-bridge` 命令行,用于命令面覆盖不到的场景:在终端里驱动、批量脚本、或者评测。它走回环 HTTP,读 `DSH_SESSION_ID` / `DSH_WEB_URL`。
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
dsh-bridge doctor # 自检:网关 / 会话身份 / 可用模式
|
|
95
|
+
dsh-bridge preview --to code --session <会话 id>
|
|
96
|
+
dsh-bridge migrate --to code --summary-file <path>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
日常用 `/bridge` 就够了,这条路主要是给排查问题和自动化留的。
|
|
100
|
+
|
|
101
|
+
## 5. 配置
|
|
102
|
+
|
|
103
|
+
配置写在 profile 的 `cordis.patch.yml`,或用它读取的环境变量临时覆盖。**每个键都真的被消费**(0.1 里它们一个都没有消费者,这是 0.2 修掉的问题之一)。
|
|
104
|
+
|
|
105
|
+
| 配置 | 环境变量 | 默认 | 说明 |
|
|
106
|
+
|---|---|---|---|
|
|
107
|
+
| `modelTier` | `DSH_BRIDGE_TIER` | `pro` | 压缩工人档位。实验结论:pro 与 flash 几乎同价(~2K tokens),但 flash 在 8 次里出现过 1 次全灭,离散度大一个数量级;**不要用 flash 迁 minimal**(三次两次全灭) |
|
|
108
|
+
| `sourceCharBudget` | `DSH_BRIDGE_SOURCE_BUDGET` | `60000` | 取材总字符预算(≈30K tokens 输入) |
|
|
109
|
+
| `summaryCharBudget` | `DSH_BRIDGE_SUMMARY_BUDGET` | `2400` | 摘要正文字符预算(≈900 tokens,注入侧成本上限) |
|
|
110
|
+
| `goalRounds` | `DSH_BRIDGE_GOAL_ROUNDS` | `1` | 新会话的自主 goal 轮次上限,见下 |
|
|
111
|
+
| `inject` | `DSH_BRIDGE_INJECT` | `both` | 摘要注入方式,见下 |
|
|
112
|
+
| `lang` | `DSH_BRIDGE_LANG` | `auto` | 摘要语言,auto 跟着会话内容走 |
|
|
113
|
+
| `previewTimeoutMs` | `DSH_BRIDGE_PREVIEW_TIMEOUT` | `180000` | `/bridge <preset>` 等压缩工人的上限 |
|
|
114
|
+
| `workerProvider` / `workerModel` | `DSH_BRIDGE_PROVIDER` / `_MODEL` | — | 直接指定压缩模型(换了 provider 的部署用) |
|
|
115
|
+
|
|
116
|
+
### 关于 `goalRounds`:为什么默认是 1
|
|
117
|
+
|
|
118
|
+
上游 `goal.create` 的部署默认 `maxGoalRounds` 是 **256**,而 `dsh-goal-round-driver` 会在 agent 空闲、目标处于 active 且还有额度时,自动把目标渲染成 `<goal_round>` 提示排一轮进去——一轮跑完还有额度就继续。也就是说:把交接摘要挂成 goal 而不限这个数,等于给新会话开了一个最多 256 轮的自主循环。
|
|
119
|
+
|
|
120
|
+
普通交接只需要一轮理解校验,所以 Bridge 在创建 goal 后立刻 pause,再发送首轮提示。`--continue` 只让这条提示在复述后同轮继续,并不会解锁 goal。想让它之后自主跑多轮,需要先调大 `goalRounds`,再在新会话里手动 resume 目标。
|
|
121
|
+
|
|
122
|
+
### 关于 `inject`:为什么默认 `both`
|
|
123
|
+
|
|
124
|
+
上游 `dsh-goal` 的文档写得很直接:**「Goal mutations do not inject model context」**——挂目标这个动作本身不会把目标放进模型上下文。摘要能被看见,靠的是轮次驱动器把它渲染成提示,或者模型自己调 `get_goal`。Bridge 又会先 pause goal,所以凡是要发 kickoff,就一定把摘要全文放进首轮提示;高级配置即使写 `--inject goal` 也不会让首轮在看不见摘要的情况下运行。
|
|
125
|
+
|
|
126
|
+
默认 `both` 同时保留 goal(持久、可手动 resume)与 prompt(任何 preset 都能看见)。暂停的 goal 不触发模型请求,因此不会重复烧摘要 token;只有日后手动 resume 时,它才会进入 goal round。部署里没挂 goal 服务时会自动降级成 prompt 并给一条警告,而不是失败。
|
|
127
|
+
|
|
128
|
+
## 6. 常见问题
|
|
129
|
+
|
|
130
|
+
**Q:`/bridge` 打了没反应 / 提示未知命令?**
|
|
131
|
+
A:确认重启过 `dsh web`(插件在启动时挂载),并且用的是 `web` profile。命令注册依赖 `commands` 服务、执行依赖 `apiProxy` 服务,两者在官方 web profile 里都在;缺其一插件会挂起等待而不是半挂,日志里能看到。
|
|
132
|
+
|
|
133
|
+
**Q:升级了 dsh 之后还能用吗?**
|
|
134
|
+
A:先打一次 `/bridge --doctor`,它会告诉你这套 host 暴露了十三个网关方法里的哪几个、当前模式是什么、生效配置是什么。全绿就是好的。缺方法它会点名,把那行连同你的 dsh 版本发到 issues 就行。rc.6 / rc.7 / rc.8 已逐条核对;0.1.1-rc.2 的 doctor 13/13、预览、迁移、目标模型继承与原图 kickoff 均实测通过。
|
|
135
|
+
|
|
136
|
+
**Q:图片迁移需要怎么选模型?**
|
|
137
|
+
A:图片已有助手分析时,Bridge 逐字搬这段视觉证据,不需要再次烧视觉 token。图片还没被分析时,源会话应选 `deepseek-v4-flash-vision-exp` 等视觉路由;Bridge 会在 kickoff 前把源 provider/model/reasoning effort 复制到目标,再搬原图。文本模型仍不能识图,Bridge 也不会暗中启动本地视觉模型。
|
|
138
|
+
|
|
139
|
+
**Q:迁移后新会话「记得」多少?**
|
|
140
|
+
A:当前 release gate 的 6 份摘要、12 个目标会话达到摘要 30/30、复述 60/60、首次有效工作 60/60,预定义旧值复活为 0。这是修复驱动的小样本回归门禁,不是总体准确率保证。更早 26 组档位实验里,pro 探针可用性为 95%;两组证据都提示数字与端口需要在预览里重点检查。
|
|
141
|
+
|
|
142
|
+
**Q:为什么不在原会话直接切模式?**
|
|
143
|
+
A:官方在网关层硬锁,且锁得对:历史里的工具调用只在原工具集下合法,换了组合会留下「幽灵调用」,不报错但质量静默劣化。Bridge 选择搬家而不是绕锁。
|
|
144
|
+
|
|
145
|
+
**Q:这和「开个新会话把标题复制过去」有什么区别?**
|
|
146
|
+
A:那是裸重开。A/B 实测(4 条成对 run):迁进**无工具**的 preset,裸重开探针只剩 1/5(真失忆);迁进**有工具**的 preset,裸重开看似能答,实际是 agent 首轮发起 25+ 次工具调用、烧掉百万级 tokens 从 host 日志里翻回来的,且执行照样漂。摘要的价值是**在任何 preset 下用一份固定的、可预算的代价记得**。数据与这组对照的已知弱点见 README「A/B 验证」与 benchmark §10。
|
|
147
|
+
|
|
148
|
+
**Q:迁移花多少钱?**
|
|
149
|
+
A:压缩工人实测约 1.6K 输入 + 0.7K 输出;目标请求还会带 ≤900 tokens 的摘要,以及该 preset 本来就有的系统提示。默认模式额外花一个只复述的确认轮;`--continue` 把复述和工作合并在同一目标轮。token 大头通常仍是后续 agentic 工作,不是压缩。
|
|
150
|
+
|
|
151
|
+
**Q:会不会往我每次请求的提示词里塞东西?**
|
|
152
|
+
A:不会。这个插件不注册技能、也不注册工具,对模型的提示词零贡献;不迁移就是零开销。
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Native WebUI migration card: feasibility note
|
|
2
|
+
|
|
3
|
+
Status: **feasible, intentionally not shipped in v0.2.10**.
|
|
4
|
+
|
|
5
|
+
## What was verified
|
|
6
|
+
|
|
7
|
+
The official rc.8 WebUI supports third-party browser modules through a package-level `dsh.client` manifest. Its client runtime exposes a slot registry, and official plugins register occupants such as conversation views, input docks, settings items, and command renderers. This provides a real extension path; DOM patching or a forked WebUI is not required.
|
|
8
|
+
|
|
9
|
+
The existing Bridge package is host-side only. It injects `commands` and `apiProxy`, and that slash-command path already works in the official WebUI. A client face could call the same host RPCs and render the same five-section preview without changing migration semantics.
|
|
10
|
+
|
|
11
|
+
## Product shape
|
|
12
|
+
|
|
13
|
+
A Bridge-native surface should express migration verification rather than copy a generic dashboard:
|
|
14
|
+
|
|
15
|
+
1. choose a target preset;
|
|
16
|
+
2. generate the five-section preview;
|
|
17
|
+
3. show number, path, image, and truncation warnings beside the preview;
|
|
18
|
+
4. require explicit confirmation;
|
|
19
|
+
5. show the created title and session ID, with copy/open actions only when the host exposes stable navigation.
|
|
20
|
+
|
|
21
|
+
The slash command remains available as the universal fallback.
|
|
22
|
+
|
|
23
|
+
## Why it is not a P0 dependency
|
|
24
|
+
|
|
25
|
+
- The client packages and slot contracts are still prerelease and version-coupled to the Harness WebUI.
|
|
26
|
+
- Adding a browser face introduces React and multiple DSH client peer dependencies to a package that currently needs only the host contract.
|
|
27
|
+
- A UI must not create a second migration implementation. The host-side preview, safety checks, and fail-closed behavior need to stay authoritative.
|
|
28
|
+
- Current WebUI navigation does not provide Bridge with a stable, documented way to jump to the newly created session, so a card cannot yet promise seamless navigation.
|
|
29
|
+
|
|
30
|
+
## Recommended implementation boundary
|
|
31
|
+
|
|
32
|
+
When the client contract stabilizes, prefer either an optional client entry in this package or a thin `dsh-plugin-bridge-ui` companion. Both should consume Bridge RPC results and never reimplement history folding, summary creation, attachment policy, or goal safety in the browser.
|
|
33
|
+
|
|
34
|
+
Prototype acceptance criteria:
|
|
35
|
+
|
|
36
|
+
- loads and disposes through the official client-module lifecycle;
|
|
37
|
+
- works on both wide and narrow official WebUI layouts;
|
|
38
|
+
- exposes the exact same preview file and warnings as `/bridge`;
|
|
39
|
+
- creates no target before confirmation;
|
|
40
|
+
- proves cleanup after plugin removal and restart;
|
|
41
|
+
- keeps `/bridge --doctor` and the host command usable when the client face fails;
|
|
42
|
+
- pins and tests every supported DSH client-package version.
|
|
43
|
+
|
|
44
|
+
Until those criteria can be maintained across releases, the official slash command is the more stable and lower-cost product surface.
|
package/docs/plan.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Bridge · 跨模式会话迁移
|
|
2
|
+
|
|
3
|
+
> **这是一份设计文档,记录的是当初怎么想的,不是当前实现的说明书。**
|
|
4
|
+
> 现状与本文的两处出入,见 README「已知局限与待办」:
|
|
5
|
+
> 1. 「紧跟会话结束发起以命中 provider 侧热缓存」**没有落地**——压缩工人是**另起一个会话**,
|
|
6
|
+
> 前缀与原会话无关,拿不到那份热缓存。上游 compaction 是靠原样重放会话前缀才做到的,
|
|
7
|
+
> 要做对得在进程内调 `ctx.llm.stream()`。
|
|
8
|
+
> 2. 「goal 挂载(持久投影)」需要修正:上游 `dsh-goal` 明确「Goal mutations do not inject
|
|
9
|
+
> model context」——挂目标不等于摘要进上下文。0.2 起默认同时把摘要放进首轮提示。
|
|
10
|
+
>
|
|
11
|
+
> 实现细节以 `src/` 与 README 为准。
|
|
12
|
+
|
|
13
|
+
## 背景共识(三轮讨论的结论)
|
|
14
|
+
|
|
15
|
+
1. **官方锁定是对的**。preset = 系统提示词 + 工具集 + 插件的整套组装;会话历史里的工具调用只在原工具集下合法。中途换组装会留下新组合无法执行的"幽灵调用",且系统提示词前后不一致。官方在网关层硬锁(`agent-preset-locked`),标注为**产品规则而非机制约束**——硬锁比静默劣化诚实。模型/思考强度可会话内切(换脑子不影响历史合法性),模式不可切(换手脚破坏历史)。
|
|
16
|
+
2. **插件的正确形态是"搬家"而非"绕锁"**。三条路径里只有摘要迁移成立:读历史 → 生成结构化交接摘要 → 新 preset 建新会话 → 摘要注入。工具存根层(违反挂载模型)和历史重写(append-only 日志)都不可行。
|
|
17
|
+
3. **上下文稳定性 = 迁状态不迁痕迹**。固定 schema 摘要 + 文件路径引用(自愈细节)+ goal 挂载(持久投影)+ 人在回路预览 + 原会话冻结可回退。无损原理上不存在,"有损 + 可预览 + 可验证 + 可回退" = 实用稳定。
|
|
18
|
+
|
|
19
|
+
## Token 成本设计
|
|
20
|
+
|
|
21
|
+
- **生成侧**:复用最近 compaction 摘要当底稿(官方已付过费),叠加用户消息全文(意图锚点)与最近 3 轮细节;专用指令去掉本模式特有细节、保留决策理由;输入预算 ~30K tokens;紧跟会话结束发起以命中 provider 侧热缓存。
|
|
22
|
+
- **压缩工人**:临时 minimal preset 会话 + 便宜模型生成摘要,用户会话的模型/模式一概不动;工人用完归档。
|
|
23
|
+
- **注入侧**:摘要硬预算 ≤1K tokens(固定 schema:目标 ≤2 句 / 状态 ≤5 句 / 决策 ≤5 条 / 文件 ≤10 路径 / 下一步 ≤2 句);验证手段现成——StatsLine 直接显示新会话首轮 Token 出入与缓存命中率。
|
|
24
|
+
- **零静默**:确认弹窗 = 摘要全文(可编辑)+ 目标模式 + 压缩模型档位(flash 省 / 跟随当前 / pro 准)+ 成本预估;不在后台迁移,不替用户切当前会话任何东西。
|
|
25
|
+
|
|
26
|
+
## 回退设计
|
|
27
|
+
|
|
28
|
+
- **对话层**:迁移 = 新建会话,原会话是不可变只读事实。回退 = 点回原会话;新会话不满意就归档。branch 而非 rollback。
|
|
29
|
+
- **文件层**:迁移时记录 git HEAD SHA 与工作区脏/净状态进摘要;不替用户执行任何 git 写操作,用户自行 diff/reset;新会话可先用只读权限跑首轮"报到",确认后再放开。
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Part A · GUI 参考实现(已完成,见姊妹项目桌面客户端)
|
|
34
|
+
|
|
35
|
+
**入口**:ModeSelector 锁定态静态徽章旁加「迁移」按钮(仅非空白会话显示)。
|
|
36
|
+
|
|
37
|
+
**BridgeModal 确认弹窗**:
|
|
38
|
+
- 目标模式下拉(presets 排除当前与 broken)
|
|
39
|
+
- 压缩模型档位:flash(省,默认)/ 跟随当前会话 / pro(准)
|
|
40
|
+
- 摘要区:先「生成摘要预览」→ 生成后可编辑 → 「确认迁移」
|
|
41
|
+
- 成本预估行:基于 tokenUsage 显示「本次摘要预计输入 ≤ N tokens」
|
|
42
|
+
- 明示:原会话不动;迁移后自动切到新会话
|
|
43
|
+
|
|
44
|
+
**执行管线**(全部走 apiproxy 现有 RPC,不动 harness):
|
|
45
|
+
1. `session.history` 分页拉取(最近 3 页 + 最新 compaction 块)
|
|
46
|
+
2. 压缩工人:`session.create`(minimal) → `session.selectModel`(档位) → `session.prompt`(压缩指令+取材) → 轮询至完成 → 读末条 assistant 文本 → `workspace.archiveSession`(工人)
|
|
47
|
+
3. `session.create`(目标 preset) → `goal.create`(摘要) → `session.prompt`(交接指令)
|
|
48
|
+
4. 激活新会话
|
|
49
|
+
|
|
50
|
+
**组件**:`src/components/Conversation/BridgeModal.tsx`;useHarness 加 `bridgeEstimate` / `bridgeSummarize` / `bridgeExecute`;i18n 双语;样式入客户端样式表。
|
|
51
|
+
|
|
52
|
+
**验收**:WebKit 实测——锁定会话出现迁移入口;弹窗生成摘要可编辑;确认后新会话带 goal 与首轮"报到";原会话仍在;typecheck/check/responsive 全绿。
|
|
53
|
+
|
|
54
|
+
## Part B · dsh-plugin 工程(本仓库)
|
|
55
|
+
|
|
56
|
+
**名字**:`dsh-plugin-bridge`。
|
|
57
|
+
|
|
58
|
+
**形态**:把 Part A 验证过的摘要 prompt、固定 schema、剪枝管线抽成独立插件,官方 WebUI 用户经 `dsh plugin --profile web add github:<owner>/dsh-plugin-bridge#main` 安装。插件消费同一 RPC 面(history/create/prompt/goal/selectModel/archive),与 GUI 版共用语义、不返工。
|
|
59
|
+
|
|
60
|
+
**候选能力**:
|
|
61
|
+
- slash 命令 `/bridge <preset>`(官方 slash 语义:palette 列表在客户端,执行即把 `/bridge xxx` 当普通 prompt 发出)
|
|
62
|
+
- composer dock 入口按钮(挂官方 `conversation.composer.dock` slot)
|
|
63
|
+
- 摘要模板可配(settings 命名空间 `bridge`:`maxTokens`、`model` 档位、`schema` 字段开关)
|
|
64
|
+
|
|
65
|
+
**调研清单(开源前)**:官方插件的打包/加载/删除机制(`dsh plugin add/remove` 到底写什么、cordis.patch.yml 的角色、加载时机与重启要求)、插件如何注册 slash 命令与 UI slot、签名/信任模型。
|
package/lib/api-rpc.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 把 host 进程内的 `ctx.apiProxy` 适配成 `Rpc`。
|
|
3
|
+
*
|
|
4
|
+
* 网关不只是一个 HTTP 端点:`@deepseek-ai/dsh-host-apiproxy` 把整套 ApiProxy
|
|
5
|
+
* 作为 cordis 服务提供为 `ctx.apiProxy`(web bundle 的 `api-gateway` 行挂载),
|
|
6
|
+
* 方法签名就是 `(request, signal) => RpcResponse`。所以插件在进程内就能完成
|
|
7
|
+
* 迁移——不需要端口、不需要 `DSH_WEB_URL`、不需要 bash、也不依赖模型愿不愿意
|
|
8
|
+
* 帮忙调用。
|
|
9
|
+
*
|
|
10
|
+
* `migrate.ts` 只认注入进来的 `Rpc`,于是同一套编排在三个地方复用:
|
|
11
|
+
* `/bridge` 命令(这里)、CLI(走 HTTP)、评测 harness。
|
|
12
|
+
*/
|
|
13
|
+
import { type Rpc } from './rpc.ts';
|
|
14
|
+
/** 一个 unary 网关方法:`(request, signal) => 信封`。 */
|
|
15
|
+
type UnaryMethod = (request: {
|
|
16
|
+
rpcId: string;
|
|
17
|
+
payload: unknown;
|
|
18
|
+
}, signal?: AbortSignal) => Promise<{
|
|
19
|
+
result?: RpcResult;
|
|
20
|
+
}> | {
|
|
21
|
+
result?: RpcResult;
|
|
22
|
+
};
|
|
23
|
+
interface RpcResult {
|
|
24
|
+
ok?: boolean;
|
|
25
|
+
value?: unknown;
|
|
26
|
+
error?: {
|
|
27
|
+
code?: string;
|
|
28
|
+
message?: string;
|
|
29
|
+
details?: unknown;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* `ctx.apiProxy` 的结构化最小契约。
|
|
34
|
+
*
|
|
35
|
+
* 故意不 import `@deepseek-ai/dsh-host-apiproxy` 的类型:那会多一个 peer 依赖,
|
|
36
|
+
* 而这里只用到十来个方法。结构化声明也让上游加字段不至于把我们编译挂掉。
|
|
37
|
+
*/
|
|
38
|
+
export interface ApiProxyLike {
|
|
39
|
+
sessions: Record<string, UnaryMethod>;
|
|
40
|
+
workspace: Record<string, UnaryMethod>;
|
|
41
|
+
goals: Record<string, UnaryMethod>;
|
|
42
|
+
agentPresets: Record<string, UnaryMethod>;
|
|
43
|
+
}
|
|
44
|
+
/** 主迁移链路必需的方法名(测试与 doctor 用);不含可选图片读取与 fail-closed 增强。 */
|
|
45
|
+
export declare const SUPPORTED_METHODS: readonly string[];
|
|
46
|
+
/** 一个方法在当前 host 上是否可达。 */
|
|
47
|
+
export interface MethodProbe {
|
|
48
|
+
method: string;
|
|
49
|
+
available: boolean;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 探测这套 host 的网关面是否还是本插件预期的形状。
|
|
53
|
+
*
|
|
54
|
+
* 上游是 developer preview,README 明说会有破坏性变更。与其等用户报「插件用不了」,
|
|
55
|
+
* 不如让 `/bridge --doctor` 直接把缺了哪个方法说出来。只读方法表,不发起任何调用。
|
|
56
|
+
*/
|
|
57
|
+
export declare function probeApiProxy(apiProxy: Partial<ApiProxyLike> | undefined): MethodProbe[];
|
|
58
|
+
/**
|
|
59
|
+
* 建一个直连 `ctx.apiProxy` 的 Rpc。
|
|
60
|
+
* @param apiProxy - host 上的网关服务。
|
|
61
|
+
* @param signal - 派发方的取消信号(命令处理器拿到的那个)。
|
|
62
|
+
*/
|
|
63
|
+
export declare function createApiProxyRpc(apiProxy: ApiProxyLike, signal?: AbortSignal): Rpc;
|
|
64
|
+
export {};
|
package/lib/api-rpc.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 把 host 进程内的 `ctx.apiProxy` 适配成 `Rpc`。
|
|
3
|
+
*
|
|
4
|
+
* 网关不只是一个 HTTP 端点:`@deepseek-ai/dsh-host-apiproxy` 把整套 ApiProxy
|
|
5
|
+
* 作为 cordis 服务提供为 `ctx.apiProxy`(web bundle 的 `api-gateway` 行挂载),
|
|
6
|
+
* 方法签名就是 `(request, signal) => RpcResponse`。所以插件在进程内就能完成
|
|
7
|
+
* 迁移——不需要端口、不需要 `DSH_WEB_URL`、不需要 bash、也不依赖模型愿不愿意
|
|
8
|
+
* 帮忙调用。
|
|
9
|
+
*
|
|
10
|
+
* `migrate.ts` 只认注入进来的 `Rpc`,于是同一套编排在三个地方复用:
|
|
11
|
+
* `/bridge` 命令(这里)、CLI(走 HTTP)、评测 harness。
|
|
12
|
+
*/
|
|
13
|
+
import { RpcError } from './rpc.js';
|
|
14
|
+
/** RPC 方法名 → `ctx.apiProxy` 上的位置。 */
|
|
15
|
+
const ROUTES = {
|
|
16
|
+
'session.list': ['sessions', 'list'],
|
|
17
|
+
'session.create': ['sessions', 'create'],
|
|
18
|
+
'session.history': ['sessions', 'history'],
|
|
19
|
+
'session.models': ['sessions', 'models'],
|
|
20
|
+
'session.selectModel': ['sessions', 'selectModel'],
|
|
21
|
+
'session.prompt': ['sessions', 'prompt'],
|
|
22
|
+
'session.cancel': ['sessions', 'cancel'],
|
|
23
|
+
'session.rename': ['sessions', 'rename'],
|
|
24
|
+
// rc.8 optional surface: rc.6/rc.7 没有图片时仍保持完整兼容。
|
|
25
|
+
'session.attachment': ['sessions', 'attachment'],
|
|
26
|
+
'workspace.list': ['workspace', 'list'],
|
|
27
|
+
'workspace.archiveSession': ['workspace', 'archiveSession'],
|
|
28
|
+
'agentPreset.list': ['agentPresets', 'list'],
|
|
29
|
+
'goal.create': ['goals', 'create'],
|
|
30
|
+
'goal.pause': ['goals', 'pause'],
|
|
31
|
+
// pause 失败时的可选 fail-closed 兜底;rc.7+ 有此路由,但不抬高 doctor 基线。
|
|
32
|
+
'goal.clear': ['goals', 'clear'],
|
|
33
|
+
};
|
|
34
|
+
const OPTIONAL_METHODS = new Set(['session.attachment', 'goal.clear']);
|
|
35
|
+
/** 主迁移链路必需的方法名(测试与 doctor 用);不含可选图片读取与 fail-closed 增强。 */
|
|
36
|
+
export const SUPPORTED_METHODS = Object.keys(ROUTES).filter((method) => !OPTIONAL_METHODS.has(method));
|
|
37
|
+
/**
|
|
38
|
+
* 探测这套 host 的网关面是否还是本插件预期的形状。
|
|
39
|
+
*
|
|
40
|
+
* 上游是 developer preview,README 明说会有破坏性变更。与其等用户报「插件用不了」,
|
|
41
|
+
* 不如让 `/bridge --doctor` 直接把缺了哪个方法说出来。只读方法表,不发起任何调用。
|
|
42
|
+
*/
|
|
43
|
+
export function probeApiProxy(apiProxy) {
|
|
44
|
+
return SUPPORTED_METHODS.map((method) => {
|
|
45
|
+
const route = ROUTES[method];
|
|
46
|
+
const domain = route?.[0];
|
|
47
|
+
const key = route?.[1];
|
|
48
|
+
const fn = domain && key ? apiProxy?.[domain]?.[key] : undefined;
|
|
49
|
+
return { method, available: typeof fn === 'function' };
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 建一个直连 `ctx.apiProxy` 的 Rpc。
|
|
54
|
+
* @param apiProxy - host 上的网关服务。
|
|
55
|
+
* @param signal - 派发方的取消信号(命令处理器拿到的那个)。
|
|
56
|
+
*/
|
|
57
|
+
export function createApiProxyRpc(apiProxy, signal) {
|
|
58
|
+
let seq = 0;
|
|
59
|
+
return async function rpc(method, payload = {}) {
|
|
60
|
+
const route = ROUTES[method];
|
|
61
|
+
if (!route)
|
|
62
|
+
throw new RpcError(method, 'unsupported', `进程内网关适配器没有映射 ${method}`);
|
|
63
|
+
const [domain, key] = route;
|
|
64
|
+
const fn = apiProxy[domain]?.[key];
|
|
65
|
+
if (typeof fn !== 'function') {
|
|
66
|
+
throw new RpcError(method, 'unavailable', `这套部署的 apiProxy 上没有 ${domain}.${key}`);
|
|
67
|
+
}
|
|
68
|
+
seq += 1;
|
|
69
|
+
const envelope = await fn({ rpcId: `bridge-cmd-${seq}`, payload }, signal);
|
|
70
|
+
const result = envelope?.result;
|
|
71
|
+
if (!result?.ok) {
|
|
72
|
+
throw new RpcError(method, result?.error?.code ?? 'unknown', result?.error?.message ?? '(网关未给出说明)', result?.error?.details);
|
|
73
|
+
}
|
|
74
|
+
return result.value;
|
|
75
|
+
};
|
|
76
|
+
}
|
package/lib/cli.d.ts
ADDED