# RoundTable v1.0.0-rc.2

**宿主适配版：把插件从 DeepSeek Harness `0.1.5-rc.3` 抬到 `0.2.0-rc.2`。**
这是一次**跨大版本**的升级，宿主动了两处真正破坏兼容的地方；功能面不变，
但其中一处（设置服务重构）让插件的偏好持久化换了实现。

> **发行渠道：npm + GitHub Release。** 包名 `@y9931666/dsh-plugin-roundtable`
> （作者的 npm 账户名；早期文档里的 `@huanlin/...` 属于另一个用户，不可用）。
> 本版是**预发布**（`1.0.0-rc.2`），进 npm 的 `next` 渠道，安装要带标签：
> `dsh plugin --profile web add @y9931666/dsh-plugin-roundtable@next`
> （源码安装：`git clone https://github.com/9931666/dsh-plugin-roundtable` → `pnpm install` → `pnpm build` → `dsh plugin --profile web add .`）
>
> 宿主基线：**DeepSeek Harness 0.2.0-rc.2**

---

## 0. 一句话

`0.2.0-rc.2` 与 `0.1.5-rc.3` 之间，插件源码只有 **4 个类型错误**，但其中 2 个
指向的是**宿主把「插件设置」这件事重新设计了一遍**——修法不是改一行签名，而是
换掉偏好持久化的实现。另一处是消息来源标记的扩展模型变了。

浏览器半体（`RoundTableView.tsx` / `RoundTableSettings.tsx` 等）**零改动**，
`tsc -p tsconfig.client.json` 一次通过。

---

## 1. 破坏性变更一：`settings` 服务被重构成 `SettingsForms`

### 宿主改了什么

| | 0.1.5-rc.3 | 0.2.0-rc.2 |
| --- | --- | --- |
| 服务形状 | `SettingsScope`：插件注册自己的命名空间 | `SettingsForms extends Service`：把 **profile 配置投影成表单** |
| 关键 API | `settings.register(ns, schema, { base })` → `scope` | `configure()` / `describe()` / `update()` / `replace()` / `mutate()` |
| 插件自管偏好 | `scope.get()` / `scope.update()` / `scope.watch()` | **没有对应物** —— 插件不再拥有注册命名空间的 API |
| `SettingsScope` 类型 | 导出 | **已移除** |

新模型里「插件的可配置面」就是它自己的 Cordis `Config`（由 Loader YAML 提供、
宿主自动生成配置页）。而写 `Config` 会触发 Loader **重载该 entry** ——
对「在设置页改一个偏好」来说太重了：正在开的会会被打断。

### 插件怎么改

新增 **`src/preferences.ts`**：偏好改为**自持久化**，落在 DSH home 下的
`roundtable/preferences.json`。

- `PreferenceStore`（`get` / `update` / `watch` / `ready`）保留了旧 `scope` 的语义，
  所以 `rpc.ts` 的 `prefs.get` / `prefs.set` 两个端点对外**一字未变**，
  客户端设置页与 `RoundTableSettings.tsx` 完全无感；
- 读盘是**惰性且异步**的：`get()` 在读盘完成前返回基准值，不阻塞宿主启动，
  首次加载完成后自动补同步一次 `defaultMode`；
- **读失败绝不抛给调用方**：偏好文件损坏、被手改成非法 JSON，一律退回默认值 +
  一条 warn。偏好坏了不能让整场会议从界面上消失；
- 写入复用 `state.ts` 的 `writeTextAtomic`（宿主 `writeFileAtomic` 优先、本地
  tmp + rename 兜底），并发提交串行化，最后一次写入胜出；
- 净化规则（`sanitizeHiddenPanels` / `sanitizeRolePresets` / `sanitizeSquads`）
  与上限常量一并搬进 `preferences.ts`，`rpc.ts` 继续 re-export —— 测试与工具的
  既有导入路径不变。

`src/index.ts` 里 `ctx.inject(['settings'], …)` 那一段整体删除，
`RoundTableRuntime` 从 `{ scope, stateDir, fallbackPrefs }` 收敛为 `{ prefs, stateDir }`
（内存兜底不再需要：store 自己带基准值）。

顺带把默认值从**两处收敛为一处**：原先 schemastery 的 `PreferenceSchema` 与
`fallbackPrefs` 各写了一遍默认值（本来就有漂移风险），现在只有
`preferences.ts` 的 `defaultPreferences()` 一处。

### 能力清单的变化

`settings` 不再是本插件探测的能力，三处清单同步移除：

```
compatibility.json  capabilities.optional
src/harness-compat.ts  OPTIONAL_CAPABILITIES  /  CAPABILITY_SPECS
```

`@deepseek-ai/dsh-settings` 也从 `peerDependencies` / `devDependencies` /
`baseline.packages` 里移除 —— 源码不再 import 它。

> **副作用（是好事）**：这条路径上**没有任何可选能力可缺**。旧实现里
> 「宿主没挂 settings 服务 → 偏好退回内存态、重启即丢」，现在不存在这个分支。

---

## 2. 破坏性变更二：`MessageSourceMap` 不再有 catch-all 的 `plugin` kind

`0.1.5-rc.3` 的宿主提供 `{ kind: 'plugin', plugin: string }`，本插件用它标注
注入给主持人的 steering 内容。`0.2.0-rc.2` 的
`@deepseek-ai/dsh-llm` 只剩四种基础 kind：

```ts
export interface MessageSourceMap {
  user: { kind: 'user' }
  model: ModelMessageSource
  tool: ToolMessageSource
  'system-prompt': SystemPromptMessageSource
}
```

其余 kind 一律**由生产者在自己的模块里声明**——宿主的 `tool-registry` /
`ptc-mode` / `agent-message` / `subagent-settled` / `user-question-reply` 都是
这么做的。源码里的原文注释写得很直白：

> Merge-extensible sum type — each producer declares its own `kind` in its own
> module; **there is no shared catch-all `plugin` kind**.

所以 `src/members.ts` 按同一模式补上自己的条目，而不是退化成匿名的 `user`：

```ts
declare module '@deepseek-ai/dsh-llm' {
  interface MessageSourceMap {
    roundtable: { kind: 'roundtable'; plugin: string }
  }
}
```

`steerCaptain()` 的 source 相应改为 `{ kind: 'roundtable', plugin: 'dsh-plugin-roundtable' }`。
运行时无影响：`kind` 是给转录消费者做归属展示的标签，未知值一律 fall through。

---

## 3. 清单与文档同步

| 项 | 之前 | 现在 |
| --- | --- | --- |
| `compatibility.json` `recommendedHost` | `0.1.5-rc.3` | `0.2.0-rc.2` |
| `package.json` `roundtable.hostBaseline` | `0.1.5-rc.3` | `0.2.0-rc.2` |
| devDependencies（17 个基线包） | `0.1.5-rc.3` | `0.2.0-rc.2` |
| peerDependencies 区间 | `^0.1.5-rc.1` | `^0.2.0-rc.2` |
| `@deepseek-ai/cordis` | `4.0.2` | `4.0.4`（与宿主同一副本） |
| `@deepseek-ai/schemastery` | `3.18.2` | `3.18.4` |
| `src/version.ts` `HARNESS_RANGE` | `0.1.5-rc.3+` | `0.2.0-rc.2+` |
| 能力清单 | 12 个探测点 | **11 个**（`settings` 移除） |
| 插件版本 | `1.0.0-rc.1` | `1.0.0-rc.2` |

`peerDependencies` 只能有一个区间，而类型基线也只有一个版本，所以本版**整条切到
0.2 线**：`0.1.5-rc.3` 不再对外承诺（源码里的可选能力探测仍保留对旧形状的降级，
但没有回归验证，不作为支持声明）。

---

## 验证记录

| 项 | 结果 |
| --- | --- |
| `tsc -p tsconfig.json --noEmit`（宿主，对 0.2.0-rc.2） | **通过，0 错误** |
| `tsc -p tsconfig.client.json --noEmit`（浏览器） | **通过，0 错误**（零改动） |
| `node --test --test-isolation=none "test/*.test.mjs"` | **150/150 通过，0 失败** |
| `pnpm build`（tsdown + 双端 .d.ts） | 通过（`lib/index.js` 186.7 kB / `lib/client.js` 564.3 kB） |
| `node scripts/compatibility.mjs` | **43/43 通过** |
| `node scripts/verify-package.mjs` | **19/19 通过**（24 个模块全部命中） |
| `node scripts/release.mjs` | **10/10 通过**，产物已出指纹 |
| `node scripts/doctor.mjs` | 宿主 0.2.0-rc.2；混装 ✓；cordis 同一性 ✓ |
| 候选产物 | 53 个文件，425,405 B 打包 / 1,024,000 B 解包（自建打包器） |

> 测试数从 rc.1 的 132 涨到 150，是因为前置版本里有两个测试文件因导出搬移而
> 加载失败（`rpc.test.mjs` / `squads.test.mjs`），本轮修好 re-export 后它们
> 的全部用例都跑起来了。

## 已知边界

- **只有 `0.2.0-rc.2` 一条支持线**。矩阵不再保留 `0.1.5-rc.3` 条目（peer 区间
  只能一条、类型基线只能一个版本）。
- **`pnpm-lock.yaml` 未在本机刷新**：它仍是 `0.1.5-rc.3` / cordis `4.0.2` 时代的
  记录。本机 pwsh 沙箱禁止写工作区、且 pnpm store 数据库损坏（历史已知问题），
  所以无法重新生成。**首次安装请直接 `pnpm install`**（默认会按新的
  `package.json` 重新解析并刷新 lockfile）；能用 pnpm 的机器上跑一次后请把
  刷新后的 lockfile 提交回来。CI 不跑安装，不受影响。
- **前端测试仍为零**：`RoundTableView.tsx` 没有测试覆盖（历史欠账，不影响本版）。
- 历史会议记录（`.roundtable/`）的数据格式**本版没有变动**，
  `meeting.json` 的 `schemaVersion` 与迁移通道照旧。

## 生效方式

- **浏览器半体**：重建后刷新页面即可（本版无改动，但 `lib/client.js` 会随构建刷新）。
- **宿主半体**（`inject` / 事件 / RPC 通道 / 偏好存储）：需要**重启 DSH**。
- **偏好位置变了**：从宿主的 `settings.yaml` 的 `roundtable` 命名空间搬到
  `<DSH_HOME>/roundtable/preferences.json`。旧命名空间里的值**不会自动迁移**，
  设置页重新设一次即可（默认值与旧版一致，不设也能正常用）。
