- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次) - .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…), 目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪 - .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/) - .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*) - .gitignore 补:备份件(*.bak-*) - 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
5.4 KiB
交付物 · IM 插件跨进程桥(2026-09-25 · 第 2 棒)
工作区:
E:/ProgramData/AIProject/aliyun-dsh-server|基线:D:/github/dsh_shenxian上游:群组插件对接单(只读)+ 本工作区交付物/IM插件接入完备性审计-20260925.md(本棒即其整改) 用户口径:「按照你的方案处理,完善 IM 功能让群组会话的插件可以开发」
§0 一句话
上一棒查出:插件跑实例内、IM 宿主在平台进程 ⇒ 缺跨进程那一层,插件「登记不上、够不到出向」。 本棒把那座桥建起来 ⇒ 「插件单向发起」的接入现在通了(登记 / 读 / 出向); 「平台要回头叫插件」的还没通(发言规则 / 事件回调)—— 这是唯一剩余缺口,已如实登记。
§1 交付(3 新 3 改)
| 件 | 内容 |
|---|---|
src/im/sdk/registry.ts(新) |
ImPluginBridgeRegistry:按实例隔离(每实例一份宿主)· room_type 全局唯一(跨实例同名具名拒)· 形状闸先于占用(失败不留残登记)· authorizeOutbound(bot 必须是该房成员)· unregisterInstance / snapshot |
sdk/im-plugin-host/index.mjs(新) |
插件侧 SDK(零依赖单文件):ready() 登记 / frame() 广播 / say() bot 发言 / rooms() / messages() / subscribe() 轮询增量订阅 / sanitizeManifest() 剔函数 |
test/im-plugin-bridge.test.mjs(新) |
14 例 |
src/web/routes/im.ts(+230) |
5 个端点:plugins/register · plugins/unregister · plugins/out/frame · plugins/out/message · plugins/bridge(观测) |
src/im/sdk/index.ts |
门面导出 |
package.json |
test / verify 各登记 1 处(diff 仅 2 行) |
§2 判据读数
npm run buildrc=0node --test test/im-plugin-bridge.test.mjs= 14 / 14 过 / 0 败npm test= 618 / 608 过 / 5 败 / 5 跳过(基线 604/594 ⇒ +14 全为本棒;5 败仍是投放线tar EBUSY,未增一败)npm run check:layeringrc=0 ✅ 无新增违规
§3 插件作者现在能做什么
import { createImPluginHost } from '@dshs/im-plugin-host'
const host = await createImPluginHost({ pluginId: '@dsh-local/im-plugin-demo', manifest: { /* 声明面 */ } })
await host.ready()
await host.frame({ roomId, channel: 'doc.cursor', data: { at: 42 } }) // 高频广播(不落库)
await host.say({ roomId, botId: 'bot_demo', payload: { kind: 'text' } }) // 落库发言
host.subscribe(roomId, (msg) => { /* 增量消息 */ }, { intervalMs: 1500 }) // 订阅
✅ 能做:纯数据型 · 纯界面型 · 机器人助手型 · 聊天增强型(卡片/公告/投票) · 实时协同型(帧广播部分) ❌ 还不能:回合制 / 限速型(发言规则走同步回调)—— 降级三条见 §5。
§4 两条关键自决(可推翻)
- 登记只收「声明面」:
check/onEvent/render/onAction是函数,跨不了进程 ⇒ 只传声明,函数侧回调留在实例内;SDK 的sanitizeManifest自动剔除(测试断言"序列化后不含function")。 - 出向发言多一道闸:同进程时 bot 必然由房主加进成员表;跨进程必须自己查 ⇒
authorizeOutbound,且未注入成员判定 ⇒ fail-closed 拒(⛔ 不"判不了就放行")。 - 不做"平台反向打进实例"(要实例可寻址 + 心跳,脆)⇒ 改用插件拨出,符合既有"拨出式"纪律(⛔ 插件不开端口)。
§5 反向通道:🔴 已定必须补(2026-09-26 用户拍板 —— 本条覆盖下表原「本期不支持」的口径)
🔴 用户裁定原话:「都需要实现完整,才能让插件接入,否则两边都要返工」 ⇒ 下表两行的"怎么办(降级)"已作废:
speakRules.check与events.onEvent两条都要补齐。 做法 = 实例侧 SDK 长轮询拉取(保持"拨出式",⛔ 平台不打进实例、⛔ 不需实例可寻址)。 规格 ⇒ 平台工作区接续包_IM反向通道实现_20260926.md。⛔ 原会话"不必急着补"的判断被用户推翻。
| 项 | 原「本期不支持」原因(已作废) | 现状 |
|---|---|---|
发言规则 speakRules.check |
是"平台写之前同步问插件"的回调 ⇒ 需反向通道 | 🔴 已定要补(同步语义 + deadline;超时缺省放行、契约可声明 onTimeout:'deny') |
events.onEvent 回调 |
同上 | 🔴 已定要补(异步语义,⛔ 不阻塞消息流) |
| 面板 render / onAction | 不需跨进程(面板在实例内渲染,函数本来就在自己进程里) | 要写数据/发言 ⇒ 用出向 |
| 出向的审计归属 | 本口不校验"该插件是否真在该实例启用"(与 /api/im/data/* 现状同源) | 补法 = 把 pluginId 绑进装配凭据 ⇒ 投放线面 |
§6 未做 / 边界
⛔ 未部署 · 未 commit / push · 未动 47/106 · 未动云安全组(D8 仍需你在控制台操作)· 未改 A–E 五单结论。
🔴 下一棒已登记(2026-09-26):反向通道实现 = 接续包_IM反向通道实现_20260926.md(用户已拍板必须补)。
⚠️ 云安全组操作(D8)仍为用户主动搁置项(交付物/端口决策与设备中继可行性-20260926.md),⛔ 不当待办。