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 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.2.10
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
- The preview is editable. Correct a number or path in the printed summary file, then run:
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 | Text handoff | Raw unresolved image | Verification boundary |
111
+ | DSH baseline | Server handoff | Native card | Verification boundary |
109
112
  |---|---:|---:|---|
110
- | 0.1.0-rc.6 / rc.7 | Yes | Optional gateway unavailable | Narrow RPC contract and text compatibility tests |
111
- | 0.1.0-rc.8 | Yes | Host-dependent | Real install, restart, command lifecycle, and migration baseline |
112
- | 0.1.1-rc.2 | Yes | Yes | Official WebUI migration with `deepseek-v4-flash-vision-exp`, 13/13 doctor methods, and 5/5 vision gates |
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
- - Bridge prints the created title and session ID because stable plugin-driven session navigation is not yet available;
120
- - preview normally takes 2060 seconds and is bounded by `previewTimeoutMs`;
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.412.8 seconds of worker time, while `previewTimeoutMs` remains the hard bound;
121
124
  - text-only models cannot inspect unresolved images;
122
- - each release-acceptance cell currently has one run, so the tables are release evidence rather than statistical guarantees.
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. A native migration card is technically possible through official client modules and slots, but is intentionally deferred until that prerelease contract is stable; see the [feasibility note](docs/native-webui-feasibility.md).
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 the project, runs 125 fake-host 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.
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.2.10
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 / rc.7 | 支持 | 无可选附件网关 | 窄 RPC 契约与文本兼容测试 |
111
- | 0.1.0-rc.8 | 支持 | 取决于 host | 真实安装、重启、命令生命周期和迁移基线 |
112
- | 0.1.1-rc.2 | 支持 | 支持 | 官方 WebUI + `deepseek-v4-flash-vision-exp`,doctor 13/13,视觉 gate 5/5 |
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
- - 官方尚无稳定的插件跳转目标会话接口,Bridge 会打印新会话标题和 ID;
120
- - 预览通常 2060 秒,并受 `previewTimeoutMs` 限制;
122
+ - 原生卡片通过官方 Session runtime 自动打开目标;旧客户端仍回退为标题和 ID;
123
+ - worker 运行时立即显示进度;本次三次固定样本的 worker 用时为 7.412.8 秒,`previewTimeoutMs` 仍是硬上限;
121
124
  - 纯文本模型无法读取未解析原图;
122
- - release acceptance 每个 cell 目前只有一次运行,表格是发布证据,不是统计保证。
125
+ - 原生卡片重复门禁也只有三次固定输入,是发布证据,不是统计保证。
123
126
 
124
- 服务端命令仍是兼容核心。官方 client module 与 slot 已证明原生迁移卡片可行,但其 prerelease 契约还不稳定,因此暂不把它塞进 v0.2.10;详见[可行性记录](docs/native-webui-feasibility.md)。
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` 会构建、类型检查、运行 125 fake-host 测试、核对 `lib/` 与数据集,再把真实 npm tarball 打包、安装并导入。测试不消耗模型 token。`prepublishOnly` 使用同一个 gate;GitHub Release 还会先检查 tag 与 `package.json` 版本一致,再走可信 npm 发布。
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
- ## Upstream coupling
78
+ ## Adapter boundary
79
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.
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
- 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).
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.2.10
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
- 通常 2060 秒。**认真扫一眼**:
60
+ 卡片会立即显示经过秒数;模型耗时取决于取材与路由。本次 `0.1.1-rc.2` 三次固定输入的 worker 用时为 7.412.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: feasibility note
1
+ # Native WebUI migration card: implementation boundary
2
2
 
3
- Status: **feasible, intentionally not shipped in v0.2.10**.
3
+ Status: **implemented on main; the server command remains the compatibility fallback**.
4
4
 
5
- ## What was verified
5
+ ## Verified extension path
6
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.
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 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.
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
- A Bridge-native surface should express migration verification rather than copy a generic dashboard:
13
+ The native surface expresses migration verification rather than copying a generic dashboard:
14
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.
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 slash command remains available as the universal fallback.
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
- ## Why it is not a P0 dependency
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
- - 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.
25
+ Custom clients do not need React or any DSH browser package to reuse the wire:
29
26
 
30
- ## Recommended implementation boundary
27
+ ```ts
28
+ import {
29
+ buildBridgeMigrationCommand,
30
+ parseBridgeCard,
31
+ parseBridgeTextProjection,
32
+ } from 'dsh-plugin-bridge/client-contract'
31
33
 
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.
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
- Prototype acceptance criteria:
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
- - 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.
49
+ ## Compatibility boundary
43
50
 
44
- Until those criteria can be maintained across releases, the official slash command is the more stable and lower-cost product surface.
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
- * `migrate.ts` 只认注入进来的 `Rpc`,于是同一套编排在三个地方复用:
11
- * `/bridge` 命令(这里)、CLI(走 HTTP)、评测 harness
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
- * `migrate.ts` 只认注入进来的 `Rpc`,于是同一套编排在三个地方复用:
11
- * `/bridge` 命令(这里)、CLI(走 HTTP)、评测 harness
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(rpc);
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(rpc, sessionId).catch(() => undefined);
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(rpc, sessionId).catch(() => undefined))?.agentPreset : undefined;
191
- const presets = await listPresets(rpc);
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(rpc, {
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(rpc, sessionId).catch(() => undefined);
280
- const result = await executeMigration(rpc, {
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(rpc, {
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(rpc, {
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(rpc, sessionId, tierOf(args), {
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;