Files
dsh_shenxian/poc/business-plugins-im/minimal-callbacks/README.md
T
admin 5ec99ce227
build / build-and-scan (push) Canceled after 0s
chore: 文档库全量重新入库(2026-10-20)
2026-10-20 15:05:55 +08:00

78 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 最小可跑示例 · 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` 纯插入