dsh-plugin-bridge 0.2.11 → 0.3.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/README.md +17 -11
- package/README.zh.md +17 -11
- package/docs/design.md +5 -3
- package/docs/guide.zh.md +13 -3
- package/docs/native-webui-feasibility.md +54 -28
- package/lib/api-rpc.d.ts +5 -2
- package/lib/api-rpc.js +10 -2
- package/lib/cli.js +12 -10
- package/lib/client-contract.d.ts +87 -0
- package/lib/client-contract.js +487 -0
- package/lib/client.d.ts +15 -0
- package/lib/client.js +1247 -0
- package/lib/client.js.map +1 -0
- package/lib/command.d.ts +10 -20
- package/lib/command.js +137 -16
- package/lib/host.d.ts +177 -0
- package/lib/host.js +102 -0
- package/lib/index.js +2 -2
- package/lib/migrate.d.ts +15 -38
- package/lib/migrate.js +104 -71
- package/package.json +48 -3
- package/reports/native-workbench-2026-08-25.md +30 -0
- package/reports/native-workbench-2026-08-25.raw.json +34 -0
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ dsh plugin --profile web add dsh-plugin-bridge
|
|
|
34
34
|
Pinned GitHub fallback:
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
dsh plugin --profile web add github:Totoro-qaq/dsh-plugin-bridge#v0.
|
|
37
|
+
dsh plugin --profile web add github:Totoro-qaq/dsh-plugin-bridge#v0.3.0
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
Then type in the official WebUI:
|
|
@@ -47,7 +47,9 @@ Then type in the official WebUI:
|
|
|
47
47
|
/bridge code --go --continue restate and start work in the same target request
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
On DSH rc.7 and later, the official WebUI renders `/bridge` as a native card. **Text** exposes the fixed five sections as ordinary fields and list rows; **Markdown** preserves full source freedom; **Preview** renders Markdown or a complete JSON tree. Long content scrolls inside the card while the action bar stays reachable. **Confirm migration** opens the created target session.
|
|
51
|
+
|
|
52
|
+
UIs that implement the official `conversation.chat.commandview` slot receive the same card automatically. Other custom UIs retain the complete server result, summary-file workflow, and target title/session-ID fallback; UI authors can reuse the framework-free `dsh-plugin-bridge/client-contract` export instead of reimplementing the wire. On an older or non-slot client, correct the printed summary file and run:
|
|
51
53
|
|
|
52
54
|
```text
|
|
53
55
|
/bridge code --go --file <path>
|
|
@@ -78,6 +80,7 @@ The release gate is intentionally small and reproducible; these are regression r
|
|
|
78
80
|
| Confirm / `--continue` target request shape | **2 · 1** to first useful work |
|
|
79
81
|
| Confirm extra, paired nominal median | **+8.1%** vs `--continue` |
|
|
80
82
|
| Summary worker share of clean acceptance components | **20.74% nominal** |
|
|
83
|
+
| Native WebUI repeat gate (preview / target facts) | **3/3 · 3/3**, five facts each |
|
|
81
84
|
|
|
82
85
|
The token percentage varies widely with preset, response length, and cache state. The worker share is composition, not causal overhead versus no Bridge; the stable product claim is one additional confirmation request. Read the [design and evidence boundaries](docs/design.md), [full release report](reports/v0.2.3-e2e-report.md), and [vision report](reports/v0.2.6-rc11-vision-report.md).
|
|
83
86
|
|
|
@@ -105,23 +108,23 @@ The five sections are Goal, Current state, Key decisions and conventions, Key fi
|
|
|
105
108
|
|
|
106
109
|
## Compatibility
|
|
107
110
|
|
|
108
|
-
| DSH baseline |
|
|
111
|
+
| DSH baseline | Server handoff | Native card | Verification boundary |
|
|
109
112
|
|---|---:|---:|---|
|
|
110
|
-
| 0.1.0-rc.6
|
|
111
|
-
| 0.1.0-rc.8 | Yes |
|
|
112
|
-
| 0.1.1-rc.2 | Yes | Yes |
|
|
113
|
+
| 0.1.0-rc.6 | Yes | No | Narrow RPC contract and text compatibility tests |
|
|
114
|
+
| 0.1.0-rc.7 / rc.8 | Yes | Contract-checked | Client-module/command-slot contract plus server fallback |
|
|
115
|
+
| 0.1.1-rc.2 | Yes | Yes | Installed official WebUI: doctor 13/13, edit/confirm/auto-open, three-run repeat gate |
|
|
113
116
|
|
|
114
117
|
CI covers Node.js 22 and 24. Run `/bridge --doctor` after every Harness upgrade; it names missing required gateway methods instead of failing vaguely.
|
|
115
118
|
|
|
116
119
|
Current limits:
|
|
117
120
|
|
|
118
121
|
- installation needs one WebUI restart;
|
|
119
|
-
-
|
|
120
|
-
-
|
|
122
|
+
- the native card auto-opens the created target through the official Session runtime; older clients still receive the title and session ID fallback;
|
|
123
|
+
- progress appears immediately while the worker runs; the current fixed three-run sample took 7.4–12.8 seconds of worker time, while `previewTimeoutMs` remains the hard bound;
|
|
121
124
|
- text-only models cannot inspect unresolved images;
|
|
122
|
-
-
|
|
125
|
+
- the native-card repeat gate is still only three fixed runs, so it is release evidence rather than a statistical guarantee.
|
|
123
126
|
|
|
124
|
-
The server command stays the compatibility core.
|
|
127
|
+
The server command stays the compatibility core. The same package now adds an optional official client half for rendered editing and navigation; if that prerelease client contract fails to load, `/bridge` still returns the complete server result. See the [implementation boundary](docs/native-webui-feasibility.md).
|
|
125
128
|
|
|
126
129
|
## Documentation
|
|
127
130
|
|
|
@@ -129,6 +132,7 @@ The server command stays the compatibility core. A native migration card is tech
|
|
|
129
132
|
- [Chinese install, configuration, rollback, and FAQ](docs/guide.zh.md)
|
|
130
133
|
- [Release acceptance report](reports/v0.2.3-e2e-report.md)
|
|
131
134
|
- [Vision migration report](reports/v0.2.6-rc11-vision-report.md)
|
|
135
|
+
- [Native WebUI repeat acceptance](reports/native-workbench-2026-08-25.md)
|
|
132
136
|
- [Historical compression benchmark](docs/benchmark.md)
|
|
133
137
|
|
|
134
138
|
## Development
|
|
@@ -138,10 +142,12 @@ npm ci
|
|
|
138
142
|
npm run verify
|
|
139
143
|
```
|
|
140
144
|
|
|
141
|
-
`verify` builds and type-checks
|
|
145
|
+
`verify` builds and type-checks both plugin halves, runs 160 tests, checks generated `lib/` and datasets, then packs, installs, and imports the actual npm tarball. Tests spend no model tokens. `prepublishOnly` runs the same gate; GitHub releases also require the tag to match `package.json` before trusted npm publishing.
|
|
142
146
|
|
|
143
147
|
Community listings: [Awesome DSH Plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) · [Awesome DeepSeek Harness](https://github.com/Dominic789654/awesome-deepseek-harness)
|
|
144
148
|
|
|
149
|
+
Ecosystem discovery: [dsh-TUI](https://github.com/ccch1mneyyy/dsh-TUI). Bridge remains a standard DSH plugin; TUI/std conformance is tracked separately.
|
|
150
|
+
|
|
145
151
|
## License
|
|
146
152
|
|
|
147
153
|
MIT
|
package/README.zh.md
CHANGED
|
@@ -34,7 +34,7 @@ dsh plugin --profile web add dsh-plugin-bridge
|
|
|
34
34
|
GitHub 固定版本备用路径:
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
dsh plugin --profile web add github:Totoro-qaq/dsh-plugin-bridge#v0.
|
|
37
|
+
dsh plugin --profile web add github:Totoro-qaq/dsh-plugin-bridge#v0.3.0
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
然后在官方 WebUI 输入:
|
|
@@ -47,7 +47,9 @@ dsh plugin --profile web add github:Totoro-qaq/dsh-plugin-bridge#v0.2.10
|
|
|
47
47
|
/bridge code --go --continue 在同一次目标请求里复述并开始工作
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
DSH rc.7 及以上会在官方 WebUI 原生卡片中渲染 `/bridge`。「文本编辑」把固定五段变成普通文本框和逐条列表;「Markdown」保留完整源码自由;「预览」渲染 Markdown 或完整 JSON 树。长内容只在卡片正文内滚动,操作按钮保持可达;点「确认迁移」后自动打开目标会话。
|
|
51
|
+
|
|
52
|
+
实现官方 `conversation.chat.commandview` slot 的第三方 UI 会自动得到同一张卡片。其他自定义 UI 仍保留完整服务端结果、摘要文件流程和目标标题/session ID 回退;UI 作者还可以复用无 React 的 `dsh-plugin-bridge/client-contract` 导出,而无需重写协议。旧客户端可修改输出里打印的摘要文件,再执行:
|
|
51
53
|
|
|
52
54
|
```text
|
|
53
55
|
/bridge code --go --file <路径>
|
|
@@ -78,6 +80,7 @@ dsh plugin --profile web add github:Totoro-qaq/dsh-plugin-bridge#v0.2.10
|
|
|
78
80
|
| Confirm / `--continue` 到首次有效工作的目标请求数 | **2 · 1** |
|
|
79
81
|
| Confirm 相对 `--continue` 的 nominal 配对中位额外成本 | **+8.1%** |
|
|
80
82
|
| 摘要 worker 在干净验收组件中的 nominal 占比 | **20.74%** |
|
|
83
|
+
| 原生 WebUI 重复门禁(预览 / 目标事实) | **3/3 · 3/3**,每次五项 |
|
|
81
84
|
|
|
82
85
|
token 百分比会随 preset、回复长度和缓存状态大幅波动;worker 占比是组成,不是相对“无 Bridge”的因果开销。稳定结论是默认确认多一个请求。边界和原始证据见[设计与证据说明](docs/design.md)、[完整 release report](reports/v0.2.3-e2e-report.md)和[视觉迁移报告](reports/v0.2.6-rc11-vision-report.md)。
|
|
83
86
|
|
|
@@ -105,23 +108,23 @@ token 百分比会随 preset、回复长度和缓存状态大幅波动;worker
|
|
|
105
108
|
|
|
106
109
|
## 兼容性
|
|
107
110
|
|
|
108
|
-
| DSH 基线 |
|
|
111
|
+
| DSH 基线 | 服务端交接 | 原生卡片 | 验证边界 |
|
|
109
112
|
|---|---:|---:|---|
|
|
110
|
-
| 0.1.0-rc.6
|
|
111
|
-
| 0.1.0-rc.8 | 支持 |
|
|
112
|
-
| 0.1.1-rc.2 | 支持 | 支持 | 官方 WebUI
|
|
113
|
+
| 0.1.0-rc.6 | 支持 | 不支持 | 窄 RPC 契约与文本兼容测试 |
|
|
114
|
+
| 0.1.0-rc.7 / rc.8 | 支持 | 契约核对 | client module / command slot 契约与服务端回退 |
|
|
115
|
+
| 0.1.1-rc.2 | 支持 | 支持 | 官方 WebUI 实装:doctor 13/13、编辑/确认/自动跳转、三次重复门禁 |
|
|
113
116
|
|
|
114
117
|
CI 覆盖 Node.js 22/24。每次升级 Harness 后先跑 `/bridge --doctor`;缺哪个必要网关方法会被直接点名。
|
|
115
118
|
|
|
116
119
|
当前边界:
|
|
117
120
|
|
|
118
121
|
- 安装后需要重启一次 WebUI;
|
|
119
|
-
-
|
|
120
|
-
-
|
|
122
|
+
- 原生卡片通过官方 Session runtime 自动打开目标;旧客户端仍回退为标题和 ID;
|
|
123
|
+
- worker 运行时立即显示进度;本次三次固定样本的 worker 用时为 7.4–12.8 秒,`previewTimeoutMs` 仍是硬上限;
|
|
121
124
|
- 纯文本模型无法读取未解析原图;
|
|
122
|
-
-
|
|
125
|
+
- 原生卡片重复门禁也只有三次固定输入,是发布证据,不是统计保证。
|
|
123
126
|
|
|
124
|
-
|
|
127
|
+
服务端命令仍是兼容核心。同一个包现在附带可选的官方 WebUI client half,负责渲染、编辑和跳转;即使 prerelease 客户端契约加载失败,`/bridge` 的完整服务端结果仍在。详见[实现边界](docs/native-webui-feasibility.md)。
|
|
125
128
|
|
|
126
129
|
## 文档
|
|
127
130
|
|
|
@@ -129,6 +132,7 @@ CI 覆盖 Node.js 22/24。每次升级 Harness 后先跑 `/bridge --doctor`;
|
|
|
129
132
|
- [中文安装、配置、回退与 FAQ](docs/guide.zh.md)
|
|
130
133
|
- [完整 release acceptance](reports/v0.2.3-e2e-report.md)
|
|
131
134
|
- [视觉迁移报告](reports/v0.2.6-rc11-vision-report.md)
|
|
135
|
+
- [原生 WebUI 重复验收](reports/native-workbench-2026-08-25.md)
|
|
132
136
|
- [历史压缩档位 benchmark](docs/benchmark.md)
|
|
133
137
|
|
|
134
138
|
## 开发验证
|
|
@@ -138,10 +142,12 @@ npm ci
|
|
|
138
142
|
npm run verify
|
|
139
143
|
```
|
|
140
144
|
|
|
141
|
-
`verify`
|
|
145
|
+
`verify` 会构建并类型检查插件两端、运行 160 项测试、核对 `lib/` 与数据集,再把真实 npm tarball 打包、安装并导入。测试不消耗模型 token。`prepublishOnly` 使用同一个 gate;GitHub Release 还会先检查 tag 与 `package.json` 版本一致,再走可信 npm 发布。
|
|
142
146
|
|
|
143
147
|
社区收录:[Awesome DSH Plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) · [Awesome DeepSeek Harness](https://github.com/Dominic789654/awesome-deepseek-harness)
|
|
144
148
|
|
|
149
|
+
生态发现入口:[dsh-TUI](https://github.com/ccch1mneyyy/dsh-TUI)。Bridge 仍按标准 DSH 插件安装;TUI/std 一致性适配另行跟踪。
|
|
150
|
+
|
|
145
151
|
## License
|
|
146
152
|
|
|
147
153
|
MIT
|
package/docs/design.md
CHANGED
|
@@ -75,8 +75,10 @@ Stored tool calls are valid only under the preset composition that produced them
|
|
|
75
75
|
|
|
76
76
|
Model and reasoning-effort changes are separate from preset migration and may still happen within one session.
|
|
77
77
|
|
|
78
|
-
##
|
|
78
|
+
## Adapter boundary
|
|
79
79
|
|
|
80
|
-
Bridge
|
|
80
|
+
Bridge's preview, safety, and migration engine depends on the typed `BridgeHost` port, not DSH RPC route names. Product/version-specific mapping is isolated in the in-process `ctx.apiProxy` and HTTP adapters. The legacy `Rpc` function remains accepted through a compatibility wrapper for the 0.2.x line.
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
`/bridge --doctor` checks thirteen required semantic capabilities and names missing capabilities before mutation. Attachment recovery and goal clearing are optional enhancements and do not raise that baseline. Any future adapter must implement `BridgeHost` and pass the same fail-closed fixtures.
|
|
83
|
+
|
|
84
|
+
The server-side slash command remains the compatibility core. The package also ships an optional WebUI client half that occupies the official per-command slot, reuses `MarkdownText` / `JsonTree`, offers lossless Text/Markdown editing, submits the reviewed summary back to the same host command, and opens the created target through the official Session runtime. The React-free `dsh-plugin-bridge/client-contract` export lets other UIs reuse the same parser and wire while unknown UIs retain the server/file fallback. No folding, attachment, goal, or fail-closed policy is reimplemented in the browser. The version boundary and fallback are documented in [native-webui-feasibility.md](native-webui-feasibility.md).
|
package/docs/guide.zh.md
CHANGED
|
@@ -13,7 +13,7 @@ dsh plugin --profile web add dsh-plugin-bridge
|
|
|
13
13
|
需要固定 GitHub tag 或 npm 暂时不可用时:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
dsh plugin --profile web add github:Totoro-qaq/dsh-plugin-bridge#v0.
|
|
16
|
+
dsh plugin --profile web add github:Totoro-qaq/dsh-plugin-bridge#v0.3.0
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
发生了什么(不用手动干预):
|
|
@@ -57,7 +57,7 @@ dsh plugin --profile web remove dsh-plugin-bridge # 重启后生效
|
|
|
57
57
|
|
|
58
58
|
`/bridge code` 会:拉取并折叠会话历史 → 按字符预算取材(用户消息全文 + 最近几轮结论 + 最近一次 compaction 底稿)→ 起一个临时的压缩工人生成**固定五段摘要**(目标 / 当前状态 / 关键决策与约定 / 关键文件 / 下一步)→ 工人用完即归档 → 把摘要贴给你。
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
卡片会立即显示经过秒数;模型耗时取决于取材与路由。本次 `0.1.1-rc.2` 三次固定输入的 worker 用时为 7.4–12.8 秒,超出 `previewTimeoutMs` 仍会被取消。**认真扫一眼**:
|
|
61
61
|
|
|
62
62
|
- 目标对不对;
|
|
63
63
|
- 关键约定在不在——尤其是**数字**,漂移里最主要的一类就是端口被补全成 3000/8080;
|
|
@@ -66,7 +66,17 @@ dsh plugin --profile web remove dsh-plugin-bridge # 重启后生效
|
|
|
66
66
|
|
|
67
67
|
### 3.2 要改摘要
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
rc.7+ 的官方 WebUI 提供三种模式:
|
|
70
|
+
|
|
71
|
+
- 「预览」:渲染 Markdown;完整 JSON 文档显示为可展开的树;
|
|
72
|
+
- 「文本编辑」:把目标、当前状态、关键决策、关键文件、下一步拆成普通字段,列表逐项增删,不需要输入 Markdown 符号;
|
|
73
|
+
- 「Markdown」:保留完整源码和附录修改能力,适合开发者或自定义结构。
|
|
74
|
+
|
|
75
|
+
长内容只在卡片正文内滚动,顶部模式切换、复制和确认按钮不会随内容滚走。非标准五段、JSON 或自定义标题不会被猜测性转换:文本模式会提示改用 Markdown,原稿保持不变。切回「预览」核对,再点「确认迁移」,校对稿会逐字成为目标的唯一事实源并自动打开目标会话。
|
|
76
|
+
|
|
77
|
+
兼容官方 `conversation.chat.commandview` slot 的第三方 UI 会自动加载这张卡片。完全自研 UI 可以导入 `dsh-plugin-bridge/client-contract` 复用纯解析与编辑 contract;没有实现 slot 时仍走下面的服务端/文件回退,不影响迁移。
|
|
78
|
+
|
|
79
|
+
旧客户端或 CLI 仍可走文件回退:预览会把摘要写进一个文件并打印路径。改那个文件,然后:
|
|
70
80
|
|
|
71
81
|
```
|
|
72
82
|
/bridge code --go --file /tmp/dsh-bridge-xxxx/summary-....md
|
|
@@ -1,44 +1,70 @@
|
|
|
1
|
-
# Native WebUI migration card:
|
|
1
|
+
# Native WebUI migration card: implementation boundary
|
|
2
2
|
|
|
3
|
-
Status: **
|
|
3
|
+
Status: **implemented on main; the server command remains the compatibility fallback**.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Verified extension path
|
|
6
6
|
|
|
7
|
-
The official rc.
|
|
7
|
+
The official rc.7 through `0.1.1-rc.2` WebUI contract 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. Bridge uses that path; it does not patch the DOM or fork the WebUI.
|
|
8
8
|
|
|
9
|
-
The
|
|
9
|
+
The host half still injects `commands` and `apiProxy`. The client half registers the `bridge` key in `conversation.chat.commandview`, parses only the host result, renders Markdown or complete JSON, offers lossless five-section Text/Markdown editing, and sends the reviewed summary back through `/bridge --go --summary64`. `recordInput: false` keeps that encoded payload out of the durable command log.
|
|
10
10
|
|
|
11
11
|
## Product shape
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
The native surface expresses migration verification rather than copying a generic dashboard:
|
|
14
14
|
|
|
15
|
-
1.
|
|
16
|
-
2.
|
|
17
|
-
3.
|
|
18
|
-
4.
|
|
19
|
-
5.
|
|
15
|
+
1. the ordinary `/bridge <preset>` command chooses the target and starts the preview;
|
|
16
|
+
2. the running command row immediately shows elapsed time and states that the source is untouched;
|
|
17
|
+
3. the settled card renders the five-section handoff and its warnings;
|
|
18
|
+
4. the user can switch among rendered Preview, non-developer Text fields, and exact Markdown source;
|
|
19
|
+
5. explicit confirmation invokes the same host command and then opens the created session through `ctx.sessions.open`.
|
|
20
20
|
|
|
21
|
-
The
|
|
21
|
+
The server result always retains the created title and session ID, and the file-edit path remains available to older clients and the CLI.
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
The package also exports `dsh-plugin-bridge/client-contract`, a React-free card/parser/editor model. A custom UI that implements the official keyed command-view slot receives the packaged card; another UI can map the pure contract to its own components. A UI that implements neither path still receives the complete server text and file fallback. Bridge never patches an unknown UI DOM to simulate compatibility.
|
|
24
24
|
|
|
25
|
-
|
|
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.
|
|
25
|
+
Custom clients do not need React or any DSH browser package to reuse the wire:
|
|
29
26
|
|
|
30
|
-
|
|
27
|
+
```ts
|
|
28
|
+
import {
|
|
29
|
+
buildBridgeMigrationCommand,
|
|
30
|
+
parseBridgeCard,
|
|
31
|
+
parseBridgeTextProjection,
|
|
32
|
+
} from 'dsh-plugin-bridge/client-contract'
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
const card = parseBridgeCard(commandOutcome)
|
|
35
|
+
if (card.phase === 'preview') {
|
|
36
|
+
const textFields = parseBridgeTextProjection(card.summary) // undefined => keep raw Markdown
|
|
37
|
+
const confirmLine = buildBridgeMigrationCommand(
|
|
38
|
+
card.targetPreset,
|
|
39
|
+
reviewedSummary,
|
|
40
|
+
card.lang,
|
|
41
|
+
card.previewId ?? '',
|
|
42
|
+
)
|
|
43
|
+
// Dispatch confirmLine through this UI's ordinary slash-command transport.
|
|
44
|
+
}
|
|
45
|
+
```
|
|
33
46
|
|
|
34
|
-
|
|
47
|
+
`previewId` binds confirmation to the exact preview that produced the card. Custom UIs should not cache or invent it; an absent ID means the server/client pair is too old for in-card confirmation and should use the printed file workflow.
|
|
35
48
|
|
|
36
|
-
|
|
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.
|
|
49
|
+
## Compatibility boundary
|
|
43
50
|
|
|
44
|
-
|
|
51
|
+
- Client packages and slot contracts remain prerelease and version-coupled. They are optional peers; rc.6 and clients that cannot load the browser half retain the complete server command result.
|
|
52
|
+
- rc.2 guards both the parent `remote` face and `remote.commands`, and its generated command wire requires an explicit image array. Bundle tests pin both details.
|
|
53
|
+
- The browser does not own a second migration engine. History folding, summary generation, attachment policy, target creation, goal pause, and rollback remain host-authoritative.
|
|
54
|
+
- Edited summaries are bounded to 24,000 characters and base64url-encoded for one command invocation. Malformed or oversized payloads fail closed before target creation; emptiness checks do not trim the transmitted Markdown.
|
|
55
|
+
- A WebUI payload is accepted only while the exact preview ID for the matching source session and target preset is pending and unexpired. The preview is consumed before migration; retrying the same completed payload returns the first target result instead of creating another session, even if a newer preview has since been generated.
|
|
56
|
+
- Once a target session exists, an unconfirmed kickoff is returned as that same navigable target with a warning and cached result. Bridge does not restore the preview and create a second target when prompt delivery is ambiguous.
|
|
57
|
+
- The client contract shapes for rc.7, rc.8, `0.1.1-rc.1`, and `0.1.1-rc.2` were compared; `0.1.1-rc.2` has installed official-WebUI evidence.
|
|
58
|
+
|
|
59
|
+
## Acceptance evidence
|
|
60
|
+
|
|
61
|
+
The 2026-08-25 installed-WebUI gate covered:
|
|
62
|
+
|
|
63
|
+
- official client-module load and disposal;
|
|
64
|
+
- wide and narrow layout behavior;
|
|
65
|
+
- server `/bridge --doctor` fallback and native rendering;
|
|
66
|
+
- immediate progress, rendered Markdown, editable text, and JSON tree rendering;
|
|
67
|
+
- no target before confirmation;
|
|
68
|
+
- reviewed-payload migration, paused goal, and automatic target navigation.
|
|
69
|
+
|
|
70
|
+
Three fixed real-model runs preserved five facts in both preview and target, with worker times from 7.4 to 12.8 seconds. See the [native workbench report](../reports/native-workbench-2026-08-25.md). This is release evidence, not a statistical guarantee.
|
package/lib/api-rpc.d.ts
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
* 迁移——不需要端口、不需要 `DSH_WEB_URL`、不需要 bash、也不依赖模型愿不愿意
|
|
8
8
|
* 帮忙调用。
|
|
9
9
|
*
|
|
10
|
-
* `
|
|
11
|
-
* `/bridge`
|
|
10
|
+
* 本文件把这个产品表面收敛到 `BridgeHost`;迁移核心不再认识 RPC 路由名。
|
|
11
|
+
* `/bridge` 命令、CLI(走 HTTP)和评测 harness 仍复用同一套编排。
|
|
12
12
|
*/
|
|
13
|
+
import { type BridgeHost } from './host.ts';
|
|
13
14
|
import { type Rpc } from './rpc.ts';
|
|
14
15
|
/** 一个 unary 网关方法:`(request, signal) => 信封`。 */
|
|
15
16
|
type UnaryMethod = (request: {
|
|
@@ -61,4 +62,6 @@ export declare function probeApiProxy(apiProxy: Partial<ApiProxyLike> | undefine
|
|
|
61
62
|
* @param signal - 派发方的取消信号(命令处理器拿到的那个)。
|
|
62
63
|
*/
|
|
63
64
|
export declare function createApiProxyRpc(apiProxy: ApiProxyLike, signal?: AbortSignal): Rpc;
|
|
65
|
+
/** 当前 DSH 进程内网关的 BridgeHost adapter。 */
|
|
66
|
+
export declare function createApiProxyHost(apiProxy: ApiProxyLike, signal?: AbortSignal): BridgeHost;
|
|
64
67
|
export {};
|
package/lib/api-rpc.js
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
* 迁移——不需要端口、不需要 `DSH_WEB_URL`、不需要 bash、也不依赖模型愿不愿意
|
|
8
8
|
* 帮忙调用。
|
|
9
9
|
*
|
|
10
|
-
* `
|
|
11
|
-
* `/bridge`
|
|
10
|
+
* 本文件把这个产品表面收敛到 `BridgeHost`;迁移核心不再认识 RPC 路由名。
|
|
11
|
+
* `/bridge` 命令、CLI(走 HTTP)和评测 harness 仍复用同一套编排。
|
|
12
12
|
*/
|
|
13
|
+
import { createBridgeHostFromRpc } from './host.js';
|
|
13
14
|
import { RpcError } from './rpc.js';
|
|
14
15
|
/** RPC 方法名 → `ctx.apiProxy` 上的位置。 */
|
|
15
16
|
const ROUTES = {
|
|
@@ -74,3 +75,10 @@ export function createApiProxyRpc(apiProxy, signal) {
|
|
|
74
75
|
return result.value;
|
|
75
76
|
};
|
|
76
77
|
}
|
|
78
|
+
/** 当前 DSH 进程内网关的 BridgeHost adapter。 */
|
|
79
|
+
export function createApiProxyHost(apiProxy, signal) {
|
|
80
|
+
return createBridgeHostFromRpc(createApiProxyRpc(apiProxy, signal), {
|
|
81
|
+
id: 'dsh-api-proxy',
|
|
82
|
+
transport: 'in-process',
|
|
83
|
+
});
|
|
84
|
+
}
|
package/lib/cli.js
CHANGED
|
@@ -19,6 +19,7 @@ import { tmpdir } from 'node:os';
|
|
|
19
19
|
import { join } from 'node:path';
|
|
20
20
|
import { SOURCE_CHAR_BUDGET, SUMMARY_CHAR_BUDGET, estimateSummaryTokens } from './compression.js';
|
|
21
21
|
import { executeMigration, findSession, listPresets, migratedTitle, previewMigration, resolveWorkerModel, titleOf, } from './migrate.js';
|
|
22
|
+
import { createBridgeHostFromRpc } from './host.js';
|
|
22
23
|
import { RpcError, createRpc, resolveApiBase } from './rpc.js';
|
|
23
24
|
const HELP = `dsh-bridge · 跨 preset 会话迁移
|
|
24
25
|
|
|
@@ -151,6 +152,7 @@ async function main(argv) {
|
|
|
151
152
|
const quiet = bool(args, 'quiet') || json;
|
|
152
153
|
const api = resolveApiBase(str(args, 'api'));
|
|
153
154
|
const rpc = createRpc({ api, prefix: 'bridge-cli' });
|
|
155
|
+
const host = createBridgeHostFromRpc(rpc, { id: 'dsh-http-api', transport: 'http' });
|
|
154
156
|
const progress = (message) => {
|
|
155
157
|
if (!quiet)
|
|
156
158
|
process.stderr.write(`… ${message}\n`);
|
|
@@ -162,7 +164,7 @@ async function main(argv) {
|
|
|
162
164
|
case 'doctor': {
|
|
163
165
|
const report = { api, sessionIdFromEnv: process.env.DSH_SESSION_ID ?? null };
|
|
164
166
|
try {
|
|
165
|
-
const presets = await listPresets(
|
|
167
|
+
const presets = await listPresets(host);
|
|
166
168
|
report.gateway = 'ok';
|
|
167
169
|
report.presets = presets.map((p) => p.id);
|
|
168
170
|
}
|
|
@@ -171,7 +173,7 @@ async function main(argv) {
|
|
|
171
173
|
}
|
|
172
174
|
const sessionId = str(args, 'session') ?? process.env.DSH_SESSION_ID;
|
|
173
175
|
if (sessionId) {
|
|
174
|
-
const row = await findSession(
|
|
176
|
+
const row = await findSession(host, sessionId).catch(() => undefined);
|
|
175
177
|
report.session = row ? { sessionId: row.sessionId, agentPreset: row.agentPreset, cwd: row.cwd } : 'not-found';
|
|
176
178
|
}
|
|
177
179
|
out(report, () => {
|
|
@@ -187,8 +189,8 @@ async function main(argv) {
|
|
|
187
189
|
}
|
|
188
190
|
case 'presets': {
|
|
189
191
|
const sessionId = str(args, 'session') ?? process.env.DSH_SESSION_ID;
|
|
190
|
-
const current = sessionId ? (await findSession(
|
|
191
|
-
const presets = await listPresets(
|
|
192
|
+
const current = sessionId ? (await findSession(host, sessionId).catch(() => undefined))?.agentPreset : undefined;
|
|
193
|
+
const presets = await listPresets(host);
|
|
192
194
|
const rows = presets.map((p) => ({ ...p, current: p.id === current }));
|
|
193
195
|
out({ current, presets: rows }, () => {
|
|
194
196
|
const lines = rows.map((p) => {
|
|
@@ -204,7 +206,7 @@ async function main(argv) {
|
|
|
204
206
|
case 'preview': {
|
|
205
207
|
const sessionId = resolveSessionId(args);
|
|
206
208
|
const to = str(args, 'to');
|
|
207
|
-
const result = await previewMigration(
|
|
209
|
+
const result = await previewMigration(host, {
|
|
208
210
|
sessionId,
|
|
209
211
|
tier: tierOf(args),
|
|
210
212
|
provider: str(args, 'provider'),
|
|
@@ -276,8 +278,8 @@ async function main(argv) {
|
|
|
276
278
|
catch (error) {
|
|
277
279
|
throw new UsageError(`读不到摘要文件 ${file}:${error instanceof Error ? error.message : String(error)}`);
|
|
278
280
|
}
|
|
279
|
-
const source = await findSession(
|
|
280
|
-
const result = await executeMigration(
|
|
281
|
+
const source = await findSession(host, sessionId).catch(() => undefined);
|
|
282
|
+
const result = await executeMigration(host, {
|
|
281
283
|
sessionId,
|
|
282
284
|
sourceSession: source,
|
|
283
285
|
to,
|
|
@@ -311,7 +313,7 @@ async function main(argv) {
|
|
|
311
313
|
case 'run': {
|
|
312
314
|
const sessionId = resolveSessionId(args);
|
|
313
315
|
const to = requireTarget(args);
|
|
314
|
-
const preview = await previewMigration(
|
|
316
|
+
const preview = await previewMigration(host, {
|
|
315
317
|
sessionId,
|
|
316
318
|
tier: tierOf(args),
|
|
317
319
|
provider: str(args, 'provider'),
|
|
@@ -323,7 +325,7 @@ async function main(argv) {
|
|
|
323
325
|
...(num(args, 'worker-timeout') === undefined ? {} : { workerTimeoutMs: num(args, 'worker-timeout') }),
|
|
324
326
|
onProgress: progress,
|
|
325
327
|
});
|
|
326
|
-
const result = await executeMigration(
|
|
328
|
+
const result = await executeMigration(host, {
|
|
327
329
|
sessionId,
|
|
328
330
|
sourceSession: preview.sourceSession,
|
|
329
331
|
to,
|
|
@@ -342,7 +344,7 @@ async function main(argv) {
|
|
|
342
344
|
case 'worker-model': {
|
|
343
345
|
// 诊断用:只回答「档位会挑到哪个模型」,不建任何会话。
|
|
344
346
|
const sessionId = resolveSessionId(args);
|
|
345
|
-
const route = await resolveWorkerModel(
|
|
347
|
+
const route = await resolveWorkerModel(host, sessionId, tierOf(args), {
|
|
346
348
|
provider: str(args, 'provider'),
|
|
347
349
|
model: str(args, 'model'),
|
|
348
350
|
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** Pure wire-to-view helpers shared by the native WebUI card and Node tests. */
|
|
2
|
+
export declare const MAX_EDITED_SUMMARY_CHARS = 24000;
|
|
3
|
+
export type BridgeOutcome = {
|
|
4
|
+
kind: 'success' | 'error';
|
|
5
|
+
text?: string;
|
|
6
|
+
} | null;
|
|
7
|
+
export type BridgeCard = {
|
|
8
|
+
phase: 'running';
|
|
9
|
+
} | {
|
|
10
|
+
phase: 'preview';
|
|
11
|
+
lang: 'zh' | 'en';
|
|
12
|
+
sourcePreset: string;
|
|
13
|
+
targetPreset: string;
|
|
14
|
+
previewId?: string;
|
|
15
|
+
summary: string;
|
|
16
|
+
summaryFile?: string;
|
|
17
|
+
stats: string;
|
|
18
|
+
warnings: string[];
|
|
19
|
+
} | {
|
|
20
|
+
phase: 'migrated';
|
|
21
|
+
lang: 'zh' | 'en';
|
|
22
|
+
targetPreset: string;
|
|
23
|
+
title: string;
|
|
24
|
+
sessionId: string;
|
|
25
|
+
details: string[];
|
|
26
|
+
warnings: string[];
|
|
27
|
+
} | {
|
|
28
|
+
phase: 'error';
|
|
29
|
+
text: string;
|
|
30
|
+
} | {
|
|
31
|
+
phase: 'message';
|
|
32
|
+
text: string;
|
|
33
|
+
lang: 'zh' | 'en';
|
|
34
|
+
};
|
|
35
|
+
export type BridgeTextSectionKey = 'goal' | 'currentState' | 'keyDecisions' | 'keyFiles' | 'nextStep';
|
|
36
|
+
export interface BridgeTextSection {
|
|
37
|
+
key: BridgeTextSectionKey;
|
|
38
|
+
label: string;
|
|
39
|
+
kind: 'text' | 'list';
|
|
40
|
+
/** Plain-text editor value. List markers are intentionally omitted. */
|
|
41
|
+
text: string;
|
|
42
|
+
/** Exact source span replaced when this field is edited. */
|
|
43
|
+
bodyStart: number;
|
|
44
|
+
bodyEnd: number;
|
|
45
|
+
items?: BridgeTextListItem[];
|
|
46
|
+
listStyle?: 'bullet' | 'plain';
|
|
47
|
+
textStyle?: 'plain' | 'bullets';
|
|
48
|
+
}
|
|
49
|
+
export interface BridgeTextListItem {
|
|
50
|
+
text: string;
|
|
51
|
+
contentStart: number;
|
|
52
|
+
contentEnd: number;
|
|
53
|
+
itemStart: number;
|
|
54
|
+
itemEnd: number;
|
|
55
|
+
/** Original indentation for wrapped lines, kept when that item is edited. */
|
|
56
|
+
continuationPrefixes?: string[];
|
|
57
|
+
}
|
|
58
|
+
export interface BridgeTextProjection {
|
|
59
|
+
lang: 'zh' | 'en';
|
|
60
|
+
/** The untouched Markdown document used to enter text mode. */
|
|
61
|
+
markdown: string;
|
|
62
|
+
lineEnding: '\n' | '\r\n';
|
|
63
|
+
sections: BridgeTextSection[];
|
|
64
|
+
/** Known appendices are visible but read-only in plain-text mode. */
|
|
65
|
+
opaqueSuffix: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Project only Bridge's exact bilingual five-section schema into plain fields.
|
|
69
|
+
* Unknown structure fails closed; known visual appendices remain opaque Markdown.
|
|
70
|
+
*/
|
|
71
|
+
export declare function parseBridgeTextProjection(markdown: string): BridgeTextProjection | undefined;
|
|
72
|
+
/** Replace one editable body while preserving every byte outside that section. */
|
|
73
|
+
export declare function replaceBridgeTextSection(projection: BridgeTextProjection, key: BridgeTextSectionKey, plainText: string): string;
|
|
74
|
+
/** Replace one list item without rewriting siblings and while retaining wrapped-line indentation. */
|
|
75
|
+
export declare function replaceBridgeTextListItem(projection: BridgeTextProjection, key: BridgeTextSectionKey, index: number, plainText: string): string;
|
|
76
|
+
/** Remove exactly one original list-item span. */
|
|
77
|
+
export declare function removeBridgeTextListItem(projection: BridgeTextProjection, key: BridgeTextSectionKey, index: number): string;
|
|
78
|
+
/** Append one item using the section's existing bullet/plain convention. */
|
|
79
|
+
export declare function appendBridgeTextListItem(projection: BridgeTextProjection, key: BridgeTextSectionKey, plainText: string): string;
|
|
80
|
+
/** Map the official WebUI document language onto Bridge's supported UI copy. */
|
|
81
|
+
export declare function uiLanguageOf(documentLang: string | undefined): 'zh' | 'en';
|
|
82
|
+
/** Convert one durable `/bridge` outcome into the native card's view model. */
|
|
83
|
+
export declare function parseBridgeCard(outcome: BridgeOutcome): BridgeCard;
|
|
84
|
+
/** Return a value only when the complete editor document is valid JSON. */
|
|
85
|
+
export declare function parseJsonDocument(text: string): object | unknown[] | undefined;
|
|
86
|
+
/** Build the hidden-input-safe command used by the native editor confirmation. */
|
|
87
|
+
export declare function buildBridgeMigrationCommand(targetPreset: string, summary: string, lang: 'zh' | 'en', previewId: string): string;
|