文档库:目录改为编号制(01-规范/02-架构设计/03-数据库/04-调整方案/
05-交接单/06-ops/07-scripts/08-skills/09-archive),顶层散文件归入 01-规范/;
INDEX.md 与 docs-manifest.json 重刷(档案 146 篇);旧目录名引用全量对齐。
IM 线:src/im/**(SDK / hub / store / presence / ws / gateway-token)、
src/web/routes/im.ts、src/db/plugin-data/**、src/supervisor/plugin-assembly.ts
及对应 test/**。
插件线:poc/{im-agent-bridge,im-connection-gateway,im-conversation-tabs,
business-plugins-im,carbon-mcp-probe}、src/web/routes/{sessions,overlay-device}.ts、
src/net/relay/{device-grant,instance-credential}.ts。
仓库卫生:清出 40 个历史误入库 / 已改名文件(34 个交接单归档 + 6 个旧结构,
本地均有副本);dsh-server-docs/.gitignore 补 tmp/;交接单不入库(政策)。
This commit is contained in:
1 parent
3d8f50e366
commit
e6207aa691
239 files changed
+34477
-14633
No files matched your search
@@ -0,0 +1,134 @@
|
||||
# IM 插件 SDK 与扩展点契约
|
||||
|
||||
> 面向要给 IM 房间加场景能力的插件作者。内核只提供通用地基,**场景差异全部下沉到这里**。
|
||||
> 依据:`05-交接单/IM群组-04-插件SDK与扩展点契约.md`|需求基线:`04-调整方案/142-IM群组对话-需求基线与方案.md §3.6`
|
||||
|
||||
## 1. 这是什么
|
||||
|
||||
IM 房间内核(`src/im/{types,db,store,clock,hub,ws}.ts`)只定义**消息信封**(`id` / `seq` / `ts` /
|
||||
作者 / `via` / `visibility`)与房间生命周期。办公群的任务卡、MUD 的指令流、跑团的回合制,
|
||||
**都不写进内核** —— 写法是注册一个插件,声明它要用到哪些扩展点。
|
||||
|
||||
SDK = `src/im/sdk/`:
|
||||
|
||||
| 文件 | 内容 |
|
||||
|---|---|
|
||||
| `types.ts` | 七个扩展点的契约类型 + 两档传输 + 数据面声明 |
|
||||
| `host.ts` | 宿主实现:注册面、超时熔断、预算继承、声明式建表、审计 |
|
||||
| `index.ts` | 对外 Facade(插件 `import` 这一个就够) |
|
||||
|
||||
## 2. 七个扩展点
|
||||
|
||||
| # | 扩展点 | 契约 | 一句话 |
|
||||
|---|---|---|---|
|
||||
| 1 | 房间类型注册 | `RoomTypeContract` | 声明 `room_type` + 默认配置 + 走哪一档传输 |
|
||||
| 2 | 消息载荷 | `PayloadContract` | 插件自己定义 `payload` 结构与校验 |
|
||||
| 3 | 成员角色 | `RoleContract` | 业务角色(DM / 玩家 / 负责人);⛔ 不提升内核权限 |
|
||||
| 4 | 发言规则 hook | `SpeakRuleContract` | 自由并发 / 回合制 / 限速 |
|
||||
| 5 | 能力机器人 | `BotContract` | 注册 bot + @ 路由(形态 A 的来源) |
|
||||
| 6 | 事件订阅 | `EventSubscriber` | **拨出式**:宿主调插件,⛔ 插件不开监听端口 |
|
||||
| 7 | 房间内 UI | `PanelContract` | 声明面板并挂 dsh 既有 slots |
|
||||
|
||||
**无插件 ⇒ 默认自由并发**:不注册发言规则时,房间行为与改造前一致,⛔ 不报错。
|
||||
|
||||
## 3. 三条平台保护约束
|
||||
|
||||
写在契约里、由宿主强制执行 —— 不是文档约定:
|
||||
|
||||
1. **插件不直连 DB**:要读写数据只能经注入的 `ImDataPort`(`insert` / `update` / `remove` /
|
||||
`find` / `count`)。插件侧代码里出现 SQL 即违约。
|
||||
2. **插件故障不拖垮房间**:每个扩展点**必须**声明 `timeoutMs` + `circuit`(缺一注册不通过)。
|
||||
回调挂起 ⇒ 超时记失败并把该次交给内核默认行为;连续失败 ⇒ 开熔断,冷却期内不再调用插件。
|
||||
3. **插件能力吃预算**:发言判定走**内核**提供的 `ImBudgetPort`。插件只能申请,⛔ 不得自带一套限速。
|
||||
|
||||
## 4. 写一个插件
|
||||
|
||||
最小形状 = 一个 `manifest` 对象 + 一个 `apply(ctx, host)`:
|
||||
|
||||
```js
|
||||
export const manifest = {
|
||||
pluginId: 'im_plugin_demo', // = 包名去 scope、小写、连字符转下划线
|
||||
label: '示例',
|
||||
packageName: '@dsh-local/im-plugin-demo',
|
||||
version: '0.1.0',
|
||||
|
||||
roomTypes: [{
|
||||
type: 'demo', label: '示例房',
|
||||
transport: 'chat', // 或 'realtime'
|
||||
defaultConfig: { topic: '' },
|
||||
timeoutMs: 50, circuit: { threshold: 5, cooldownMs: 30_000 }, // 🔴 必填
|
||||
}],
|
||||
|
||||
payloads: [{
|
||||
kind: 'ping',
|
||||
validate: (p) => typeof p?.text === 'string',
|
||||
timeoutMs: 50, circuit: { threshold: 5, cooldownMs: 30_000 },
|
||||
}],
|
||||
|
||||
tables: [{
|
||||
name: 'pings', scope: 'room', schemaVersion: 1,
|
||||
columns: [{ name: 'text', type: 'text' }], // 归属列 room_id / user_id 由内核自动补
|
||||
}],
|
||||
}
|
||||
|
||||
export function apply(ctx, host) {
|
||||
host.register(manifest)
|
||||
ctx?.on?.('dispose', () => host.unregister(manifest.pluginId))
|
||||
}
|
||||
```
|
||||
|
||||
打包与投递**复用既有通道**(`cordis.patch.yml` + `package.json#dsh` → 门户候选池 →
|
||||
实例「能力管理」自助启用)。⛔ SDK 不新造分发机制。
|
||||
|
||||
## 5. 两档传输
|
||||
|
||||
| 档 | `fanoutBatch` | `heartbeatMs` | `maxJitterMs` | `latencyBudgetMs` |
|
||||
|---|---|---|---|---|
|
||||
| `chat`(档案 107 口径) | 200 | 25 000 | 1 000 | 1 000 |
|
||||
| `realtime`(档案 108 游戏口径) | 50 | 5 000 | **20** | 100 |
|
||||
|
||||
房间类型声明自己走哪一档;**两档参数不同源**,⛔ 不是同一份参数改个名。
|
||||
未声明 ⇒ 回落 `chat`(⛔ 不默认给实时档)。
|
||||
|
||||
## 6. 数据面
|
||||
|
||||
**判落点,再谈建表**(`03-数据库/DB-03-插件数据面规范.md`):
|
||||
|
||||
| 数据种类 | 落点 | 归属列 |
|
||||
|---|---|---|
|
||||
| 跟着**房间**走的(角色卡 / 地图 / 任务卡) | 该插件自己的库 `dshs_pl_<pluginId>`,表 `p_<pluginId>_*` | `room_id` |
|
||||
| 只属于**某用户自己**的 | 同一个库 | `user_id` |
|
||||
| **缓存 / 临时**(丢了可重建) | 实例本地 | — ⛔ 不得当权威 |
|
||||
|
||||
规则:
|
||||
|
||||
- **建表 = 声明式**:字段只用中性类型(`text` / `integer` / `bigint` / `real` / `boolean` /
|
||||
`json` / `timestamp` / `uuid`),内核翻译成 sqlite + pg 两套 DDL。⛔ 禁裸 `CREATE TABLE`、
|
||||
厂商专有类型、触发器、存储过程、跨前缀 `JOIN`。
|
||||
- **迁移只增不减**:`schemaVersion` 必填;只允许加列(带默认值)/ 加表 / 加索引。
|
||||
任一步失败 ⇒ **拒绝启用**(⛔ 不做半迁移)。
|
||||
- **访问必走内核 API**:房间维度**自动注入 ACL**(复用 `canSee`);跨前缀 / 内核表
|
||||
**看不到也查不了**;事务仅限同前缀多表。
|
||||
- **卸载数据默认保留 30 天**;`DROP TABLE` 不可逆 ⇒ 需 admin 显式确认 + 先出受影响清单。
|
||||
|
||||
## 7. 三个示例插件
|
||||
|
||||
`poc/business-plugins-im/` 下三类场景各一个,可直接照抄:
|
||||
|
||||
| 目录 | 包名 | 类型 | 演示的扩展点 |
|
||||
|---|---|---|---|
|
||||
| `office-tasks/` | `@dsh-local/im-plugin-office` | `office`(聊天档) | 任务卡载荷 + 面板 + 事件落库 |
|
||||
| `mud-rate/` | `@dsh-local/im-plugin-mud` | `mud`(**实时档**) | **限速**发言规则 + 指令流载荷 |
|
||||
| `trpg-turn/` | `@dsh-local/im-plugin-trpg` | `trpg`(**实时档**) | **回合制**发言规则 + 业务角色 + 骰子载荷 |
|
||||
|
||||
## 8. 怎么验
|
||||
|
||||
```bash
|
||||
export PATH="E:/ProgramData/.workbuddy/binaries/node/versions/22.22.2-3:$PATH"
|
||||
npm run build
|
||||
node --test test/im-sdk.test.mjs # 期望:退出码 0(66 用例全过)
|
||||
git diff -- src/im/store.ts src/im/hub.ts src/web/routes/im.ts # 期望:无输出
|
||||
```
|
||||
|
||||
最后一条是**硬判据**:非聊天场景跑通**不改内核**(142 §五-6)。若必须改内核 ⇒ 扩展点没设计对,
|
||||
回头改扩展点设计,⛔ 不是改内核。
|
||||
Reference in new issue
Block a user