Files
dsh_shenxian/poc/business-plugins-im/minimal-callbacks/README.md
T

77 lines
4.7 KiB
Markdown
Raw Normal View History

# 最小可跑示例 · IM 插件(含跨进程反向回调)
> 目标:**插件作者有一个能直接跑的起点** —— 不用先啃完契约、不用先连真平台。
> 反向通道(平台 → 插件)已于 **2026-09-26 落地并上 47**;本目录是它的**唯一示例落点**。
## 一键跑
```bash
cd poc/business-plugins-im/minimal-callbacks
node self-check.mjs # 25 例;退出码 0 = 全绿
```
不需要 `npm install`(**零依赖**)、不需要平台、不需要环境变量 —— 自检自带一个
`mock-platform.mjs`(模拟登记 / 长轮询 / 回投 / 出向四个端点),把完整协议跑一遍。
## 文件
| 文件 | 作用 |
|---|---|
| `index.mjs` | **主体**:声明面 `manifest` + 拼装 `start()`;两条回调用 `onSpeakRule()` / `onEvent()` 注册 |
| `mock-platform.mjs` | 极简「平台侧」模拟(⚠️ 只对齐协议形状,不替代真平台) |
| `self-check.mjs` | 自检 25 例:登记 / 规则拒与放行 / 事件送达 / 未接线不变 |
| `package.json` | `type: module`;`npm run self-check` 同义 |
## 覆盖的两条反向回调(跨进程)
| 回调 | 语义 | 本示例怎么用 |
|---|---|---|
| `host.onSpeakRule()` → `speakRules.check` | **同步**:平台**写之前**问插件,带 deadline | **回合制**:没轮到的人发言 ⇒ `{ok:false, detail}` ⇒ 拒发 |
| `host.onEvent()` → `events.onEvent` | **异步**:不阻塞消息流,尽力送达 | 有人发言 ⇒ 广播一帧(`host.frame()`) |
## 🔴 三个必须记住的点
1. **回合制 / 限速型必须显式声明 `onTimeout: 'deny'`**
—— 缺省是 `'allow'`(为了与平台既有口径「无插件 / 插件故障 ⇒ 放行」逐字一致)。
对回合制来说"放行"就等于**规则失效**:你的实例不在线(重启 / 崩溃 / 部署窗口)时,
非当前回合者的消息会照常写入。声明位在 `manifest.speakRules[].onTimeout`。
2. **`manifest` 里不许塞函数**(`check` / `validate` / `onEvent` / `render` / `onAction`)——
跨不了进程,SDK 会自动剔除。函数侧一律走 `onSpeakRule()` / `onEvent()` 注册;
自检里有一条专门断言"送上平台的 manifest 确实没有函数"。
3. **一条实例只该有一条常驻长轮询**(`ready()` 登记后自动启,幂等)——它直接占平台连接额度,
⛔ 别自己再写第二条。收尾用 `await host.close()`(会掐断在途长轮询,否则进程退不出去)。
## ⛔ 不做什么
- **不开端口**:所有调用都是插件**拨出去**的(HTTP 到平台);平台不打进实例、实例不需可寻址。
- **不新增凭据**:复用实例凭据(`DSH_PLATFORM_IM_URL` / `DSH_IM_INSTANCE_TOKEN`,平台起实例时注入)。
- **不把事件当可靠消息流**:事件队列**有界**(满则丢最旧并计数)⇒ 要可靠增量请用
`host.subscribe()`(游标补齐,不会丢)。
- **判不了就拒(fail-closed)要靠"返回 `{ok:false}`",⛔ 不是靠抛错** ——
你的 `check` 抛错时,平台按"插件故障 ⇒ 放行"处理(与同进程语义逐字一致)。
## 接真平台
1. 把 `sdk/im-plugin-host/index.mjs` 拷进你自己的包(单文件、零依赖、可 vendoring),
并把 `index.mjs` 顶部的 import 改成你包内的路径。
2. 保证 `pluginId`(SDK 参数 = **包名**)与 `manifest.packageName` / `manifest.pluginId` **自洽**:
`@dsh-local/im-plugin-demo` → 内部 id `im_plugin_demo`(去 scope、非字母数字 → `_`)。
不自洽 ⇒ `plugin-id-mismatch`;**出向恒 `plugin-unavailable`**。
3. 在实例内运行:`DSH_PLATFORM_IM_URL` / `DSH_IM_INSTANCE_TOKEN` 由平台注入,
⛔ 不要手填、不要硬编码。出向发言前,bot **必须已是该房成员**(进房是房主 / 管理员动作)。
## 与同目录另外三个示例的关系
`mud-rate` / `office-tasks` / `trpg-turn` 写于反向通道落地**之前**,把 `check` / `onEvent`
直接写在 `manifest` 里 —— 那是**同进程**形态,跨不了进程。它们仍有参考价值(声明面、
业务角色、面板),但**函数侧请照本目录写**。
⚠️ 本示例**刻意不声明 `tables`**(零数据面):一是保持"最小",二是
`package.json#dsh.data.schema` 与契约内 `tables:` 两种写法的**口径尚未裁定**(见接续包 §8-2)。
需要数据面时先定口径,别照本示例推。
## 依据
- 契约:`dsh-server-docs/01-规范/09-IM插件SDK与扩展点契约.md`(§10.4 / §12)
- 落地与验收:工作区 `接续包_IM反向通道实现_20260926.md`
- 内核约束(⛔ 不得破):`store.ts` 零改动 / `hub.ts` 纯插入 / `ws.ts` 只动投递点 / `routes/im.ts` 纯插入