- 变更规模:新增 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/ 知识文件,按口径入库)
11 KiB
接续包 · IM 反向通道(平台 → 插件)· 2026-09-26
工作区:
E:/ProgramData/AIProject/ai1net-dsh-server|基线:D:/github/dsh_shenxianHEAD=e6207aa(未 commit) 上游(定案依据):用户 2026-09-26 06:3x 原话 —— 「都需要实现完整,才能让插件接入,否则两边都要返工」 🔴 实现棒已收官(06:5x) —— 反向通道两条都通并已真 HTTP 端到端验证 + 部署 47。 ⛔ 本件取代此前一切「降级三条够用 / 不必急着补」的口径。
✅ 本棒结果(实现棒 · 2026-09-26 06:4x–06:5x)
会话:im-reverse-channel(域锁:src/im · src/web · sdk/im-plugin-host · test/im-plugin-bridge-callback.test.mjs,已释放)
落了什么
| 文件 | 形态 | 说明 |
|---|---|---|
src/im/sdk/callback-bus.ts |
新增 | ImBridgeCallbackBus(per-instance 有界队列 + pending resolve + deadline + 计数)+ attachImBridge() 复合接线 + roomEventOf() 事件形状 + 两个目标解析器 |
src/web/routes/im.ts |
纯插入(+451/-0) |
2 端点 POST /api/im/plugins/bridge/pull(长轮询)· …/bridge/result(回投);register 后 bus.remember();unregister 后 bus.forget();stats 加 imBridge |
src/im/sdk/registry.ts |
纯插入 | 可选注入 callbackStats(供 snapshot() 带出总线读数 —— 因为观测面那行是既有行,不许改) |
src/im/sdk/types.ts |
纯插入 | SpeakRuleContract.onTimeout: 'allow' | 'deny'(缺省 allow) |
src/im/sdk/binding.ts |
微改 | afterAppend 的事件构造改用 roomEventOf(与跨进程同一来源,防分叉) |
src/im/sdk/index.ts |
纯插入 | 门面导出 |
sdk/im-plugin-host/index.mjs |
新增能力 | onSpeakRule() / onEvent() / startCallbacks() / callbacks 读数;长轮询循环(含退避、close() 掐断、守护超时) |
test/im-plugin-bridge-callback.test.mjs |
新增 9 例 | 命中 / 超时放行 / deny / 队列满丢弃 / 计数 / 幂等与冒领 / 事件与私聊不外发 / 守卫不漏 / 未接线逐字不变 |
package.json |
纯插入 | test + verify 各登记 1 处 |
关键设计(已落地,⛔ 别改)
- 方向 = 实例侧拨出长轮询:⛔ 平台不打进实例、⛔ 不开端口、⛔ 不新增凭据(复用
bridgeAuth)。 speak.check同步:deadline =min(契约 timeoutMs, maxSpeakWaitMs=1500);超时 ⇒ 按onTimeout(缺省放行)+bridgeTimeouts+1。event.deliver异步:afterAppend恒同步返回;每实例队列上限 256,满则丢最旧 +bridgeDropped+1。- 长轮询服务端硬上限 20 s(到点回空、客户端立即重拨)—— 防"常驻连接占满额度"(146 同类风险)。
- 🔴
hasPlugins()被复合增强:否则"只有实例内插件登记"时写路径守卫恒假 ⇒speak.check永不触发(静默失效)。 - 🔴 复合不改调用点:
attachImBridge增强既有的pluginBinding对象 ⇒ws.ts/routes/im.ts写路径一行未动。
验收读数(真跑,⛔ 不是推断)
npm run buildrc=0 ✅- 新测试 9 例 fail 0;
im-plugin-bridge(14) /im-backpressure(8) /im-sdkfail 0 ✅ - 真 HTTP e2e
tmp/im-bridge-e2e-20260926.mjs⇒ 28 例 fail 0,决定性四项:④b speak.check 生效:插件回 deny ⇒403 speak-rule且消息未落库 ✅④c 超时 ⇒ 放行,bridgeTimeouts=1(观测面可见)✅④d event.deliver 送达:订阅方真收到message.created✅- ⑤
bus读数进/api/im/plugins/bridge✅
npm run check:layeringrc=0 无新增违规 ✅- 内核四档:
store.ts零改动·routes/im.ts+451/-0 纯插入 ✅ - 部署 47 已做:备份
/opt/dsh/backups/im-callback-predeploy-20260926.tgz(72896 B)→ tar--owner=root --group=root解包 →systemctl restart dshs⇒active,/api/im/stats与/api/im/plugins/bridge在 19100 / 25000 / 3080 等端口回 401(路由存活) ✅
⚠️ 三条如实登记(⛔ 不粉饰)
test/im-sdk.test.mjs仍有 3 红(35 / 36 / 78)—— 与本棒无关:判据要求hub.ts零 diff 与ws.ts零删行,而工作区开工前就已带hub.ts +36/-0、ws.ts +84/-1(第 15 棒背压修复的在途未提交改动)。本棒routes/im.ts实测 -0。- 09 / 08 文档未改「已通」(原 §4-6):
dsh-server-docs不在本棒域锁内 ⇒ 按 R7-边界未动手,留给下一棒。 HOST_SDK_VERSION仍'1.0.0':test/im-plugin-bridge.test.mjs硬断言该常量(域外文件)⇒ 未升版;SDK 文档头标注"2026-09-26 增反向回调"。
§0 一句话
「插件 → 平台」已通;「平台 → 插件」现已同样通 —— 实例侧 SDK 长轮询拉取工作项,
speakRules.check(同步)与 events.onEvent(异步)两条都在真 HTTP 上验证过并已上 47。
§1 现状与缺口(更新后)
| 面 | 同进程 | 跨进程 |
|---|---|---|
发言规则 speakRules.check |
✅ | ✅ 本棒补齐(长轮询 + onTimeout 兜底 + 计数) |
事件 events.onEvent |
✅ | ✅ 本棒补齐(有界队列 + 丢最旧计数) |
面板 render / onAction |
✅ 实例内渲染 | ⛔ 不需要跨进程(维持原判) |
| 登记 / 出向 | — | ✅ 已通(5 端点 + SDK) |
§2 设计(已定项 —— 见上「关键设计」,⛔ 不再讨论)
§3 文件清单与内核约束(🔴 四档不得破)
store.ts 零改动 | hub.ts 纯插入 | ws.ts 只许动投递点 | routes/im.ts 纯插入。
⇒ 本项属「新增能力」⇒ 一律纯插入;⛔ 未接线时行为必须逐字不变(单测 ⑨ 已覆盖)。
§4 施工顺序(已完成 1–7,仅 6 的文档部分受锁所限未做)
§5 验收(判据,⛔ 缺一不算完)—— 除文档外全部达标
- 文档改「已通」:⚠️ 未做(域锁所限,见「如实登记 2」)
§6 回滚
/opt/dsh/backups/im-callback-predeploy-20260926.tgz(推前备份,已就位);
代码回滚 git checkout -- <本棒文件>;服务 systemctl restart dshs。⚠️ 本棒未 commit ⇒ 回滚点即 e6207aa。
🔴 回滚要点:本次是纯插入 ⇒ 回滚只需还原上述备份包并重启,⛔ 不涉及数据迁移。
§7 边界 / 风险
- ⛔ 未接线时行为必须逐字不变(单测 ⑨ 守着)。
- ⚠️ 长轮询连接占用:一条实例一条常驻(服务端硬上限 20 s)—— 若日后观测到连接数异常,先看这里。
- ⚠️
onTimeout缺省'allow'⇒ 回合制插件忘声明'deny',超时即破规则;⇒ 文档与示例必须点名(模板见sdk/im-plugin-host的onSpeakRule()注释)。 - ⛔ 不开端口、⛔ 不要求实例可寻址、⛔ 不新增凭据类型(本设计的立身之本)。
- ⛔ 未 commit / push;⛔ 未动覆盖网络线与投放线的任何结论。
§8 连带待办(同一目标:让插件能真的接进来、不返工)
| # | 项 | 状态 |
|---|---|---|
| 1 | 可跑的最小示例工程(poc/business-plugins-im/ 三个示例是旧契约) |
✅ 已完成(07:3x 收尾棒) ⇒ poc/business-plugins-im/minimal-callbacks/(零依赖 · node self-check.mjs 25 例 fail 0) |
| 2 | 09 §4 tables: [{ 与 08「唯一入口是 package.json#dsh.data.schema」打架(P1) |
✅ 已定口径并落文(07:4x 文档收尾棒) ⇒ 09 §4 加「位置 vs 形状」对照表:两者不是二选一,是同一份 schema 的两个用途(投放/建库读 package.json#dsh.data.schema;运行时读 manifest.tables)⇒ 两处须写同一份。依据 = 源码现读 schema.ts parseDeclFromDir() + im/sdk/host.ts assertManifestShape() |
| 3 | 09 未消 #4 移动端 / #5 错误码·自测清单·变更记录 / #8 内部编号 + 09/08 改「已通」(本轮遗留) | ✅ 全部完成:09 改「已通」6 处 + 新增 §13 移动端专项 / §14 错误码全表 / §15 提包前自测清单 / §16 变更记录;§5 两档参数改自洽表述(消内部编号依赖);08 §4-补 加 09 §13 交叉指针 |
| 4 | 背压阈值生产标定 | 未动 —— 需 admin 会话或真实流量(⛔ 不改代码,改 env) |
§9 下一棒怎么开工(照抄)
# ⓪ 状态
"E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" "E:/ProgramData/AIProject/ai1net-dsh-server/state.py"
# ① 校验本件口径(不符即停)
md5sum "E:/ProgramData/AIProject/ai1net-dsh-server/接续包_IM反向通道实现_20260926.md"
# ② 先判可锁范围(⚠️ 文档库与 src/web 都可能被别的会话持有)
bash "D:/github/dsh_shenxian/dsh-server-docs/07-scripts/preflight-lock.sh" "IM反向通道-收尾-<日期>" \
D:/github/dsh_shenxian/dsh-server-docs/01-规范/09-IM插件SDK与扩展点契约.md
# ③ 抢锁(域按上一步结果取)
bash "D:/github/dsh_shenxian/dsh-server-docs/07-scripts/handoff-guard.sh" --claim-exec "IM反向通道-收尾-<日期>" --domains <域>
下一棒建议范围(按优先级):
- §8-1 可跑最小示例工程(含
onSpeakRule/onEvent跨进程用法)—— 这是"插件作者不再照文档猜"的唯一落点 - §8-3 文档收尾(09 §10.4 / §10.5 / §12.5 改「已通」+ 08 变更记录 +
onTimeout必须点名) - §8-2 表声明入口口径二选一(先定口径再改文)
✅ 收尾棒结果(2026-09-26 07:3x–07:5x)
会话:im-reverse-finish(域锁:poc/business-plugins-im · dsh-server-docs/01-规范,开工 07:33)
| 落了什么 | 形态 | 判据 |
|---|---|---|
poc/business-plugins-im/minimal-callbacks/(5 文件:index.mjs · mock-platform.mjs · self-check.mjs · package.json · README.md) |
新增目录 | node self-check.mjs ⇒ 25 例 · fail 0 · rc=0(零依赖 · 自带模拟平台,不连真平台也能跑) |
dsh-server-docs/01-规范/09-IM插件SDK与扩展点契约.md |
局部改 6 处 | §10.4 标题/表 · §10.5 · §12.1 · §12.3 · §12.4 · §12.5 全部改「已通」;残留扫描 未通|实现待落|定要补|现在先按 ⇒ 零命中 |
dsh-server-docs/01-规范/08-插件开发与对接规范.md |
局部改 2 处 | 顶部 IM 指针增反向回调段(含 onTimeout:'deny' 强制项)+ §11 变更记录补 09-26 行 |
示例覆盖的判据(自检逐条断言,⛔ 不是推断):
① 登记成功 + 拼上实例凭据;② 送上平台的 manifest 无函数(check/onEvent 已被 SDK 剔除);
③ 非当前回合者 ⇒ 回 {ok:false} 且 detail 可读;④ 轮到本人 ⇒ 放行(防规则把正常发言也卡死);
⑤ event.deliver 送达 ⇒ onEvent 真执行 ⇒ 出 1 帧;⑥ 未接线(缺 baseUrl/token)既不抛、也不启长轮询。
约束自查:⛔ 内核四档一行未动(本棒只动 poc/ 与文档,零 src/ 改动);
⛔ 未 commit / push;⛔ 未动其它工作区文件。
⚠️ 如实登记:① 本件(接续包_IM反向通道实现_20260926.md)已被本棒改写 ⇒
其 md5 已变(开工时 = 935c7180b92867e0e2ba20de57e0ae5e)⇒ 下一棒必须重算,⛔ 别拿旧值校验。
② §8-2(表声明入口口径)仍未裁。