Files
dsh_ai1net_server/交付物/IM插件侧-S5落地与四轮回执-20260926.md
T
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 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/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

212 lines
20 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.
# S5「第一块迁移:IM 插件侧」· 落地与回执(2026-09-26 · 第 43 棒)
> **执行依据**:本线入口 `接续入口_插件投放与分库线_20260922.md` §2 第 42 棒块「本线下一项」|方案 `交付物/功能打包到基础插件-分步实施方案-20260926.md §3.10.7 + §4 避坑总表`|规格书 `D:/github/dsh_shenxian/dsh-server-docs/05-交接单/交接单_包规格书_20260926.md §〇/§五 A/§五 C`。
> **会话**:`插件投放与分库线-43`(域锁持有者已复读断言)。
> **读数**:`tmp/p43/读数-20260926.txt`(**76 ✅ / 0 ❌**)· `tmp/p43/尺读数-S5-20260926.txt`。
---
## 一、一句话结论
**IM 插件侧已按「照 IM 的接入方式重写」的方式整块进了包** —— 包 `@dsh-local/ai1net` 现在自带**三条精确路由**(`/api/ai1net/im/rooms` · `messages` · `longpoll`)+ IM 插件侧 SDK(含同进程 `core` 模式),并把**两条残留**补在了代码里:**每插件令牌(fail-closed)** 与 **实例级单例长轮询闸**。
🔴 **最强的一条证据 = 迁移前后「同一组输入 ⇒ 平台侧效果流水逐行完全相同」**(A=13 行 / B=13 行,逐行逐字符相同)—— 旧 SDK 直连平台面、新 SDK 走包面转平台面,两条路对同一个平台产生了**字面一样的行为**。
⚠️ **但必须与「已通」严格分开(详见 §六)**:**包内那一半是真实代码**(凭据解析 / 出示 / 本机 fail-closed 拦截 / 单例闸);**平台侧那一半(令牌签发 + 桥端点校验 + 启用集校验)本棒是沙箱建模,真实 `im.ts` 里尚未实现** ⇒ 属投放线待落地。⛔ **不能据此断言「真机投放已通」**。
---
## 二、S5-1「照抄」逐段对照表(⛔ 判据 = 与上游对得上)
| 段 | 上游权威(只读照抄来源) | 本包落点 | 是否逐字/同口径 |
|---|---|---|---|
| 环境变量名 | `sdk/im-plugin-host/index.mjs`:`DSH_PLATFORM_IM_URL` / `DSH_IM_INSTANCE_TOKEN` | `lib/im-plugin-sdk.mjs` `ENV_BASE_URL` / `ENV_INSTANCE_TOKEN` | ✅ 逐字(机验 ⑦) |
| 插件身份头 | `src/web/routes/im.ts:291`:`x-dsh-im-plugin-id`(值 = **包名**) | `PLUGIN_ID_HEADER` | ✅ 逐字(机验 ⑦) |
| 实例令牌头 | 同上:`x-dsh-im-instance-token` | `INSTANCE_TOKEN_HEADER` | ✅ 逐字(机验 ⑦) |
| 长轮询硬上限 | `SDK: SERVER_POLL_HOLD_MS = 20_000` | `SERVER_POLL_HOLD_MS = 20_000` | ✅ 同值(机验 ⑦) |
| 平台面 8 条路径 | `plugins/register`(341) · `unregister`(368) · `out/frame`(382) · `out/message`(424) · `bridge`(465) · `bridge/pull`(484) · `bridge/result`(504) + `im/rooms`/`rooms/:id/messages` | `PLATFORM_ROUTES`(八条,全在 `/api/im/` 下) | ✅ 同口径(机验 ⑦) |
| 方法面 | `ready/unregister/onSpeakRule/onEvent/startCallbacks/frame/say/rooms/messages/subscribe/close` | `createAi1netImHost()` 同名同形 | ✅ 逐条 |
| `pluginIdOf` 口径 | `src/im/sdk/host.ts:112`(包名 → 下划线形态) | 沙箱 `pluginIdOf` **逐字照抄** | ✅ 逐字(仅沙箱侧用;⚠️ 与令牌绑定的"包名"是两个口径,见下) |
### 四条面(装配 / 运行 / 取数 / 回调)逐条复现
| 面 | 判据 | 结果 | 原始输出位置 |
|---|---|---|---|
| 装配 | 端A 客户端形态 + 端B 平台形态 **各三条前提成立** | ✅ `rc=0` ×2 | 尺读数 §S5-1 |
| 运行 | 靶子**未出现于启动失败面** | ⚠️ `rc=0`(**空真**,见 §五) | 尺读数 §运行 |
| 取数 | 真实 import + 真实模块求值 | ✅ `rc=0` | 尺读数 §S5-2 |
| 回调 | 真实 `apply(ctx)`,spy ctx **逐字记录 4 条路由** | ✅ `rc=0` | 尺读数 §S5-3 |
🔴 **回调面抓到的四条路由(原文)**:`{"path":"/api/ai1net/ping","methods":["GET"]}` · `{"path":"/api/ai1net/im/rooms","methods":["GET"]}` · `{"path":"/api/ai1net/im/messages","methods":["GET","POST"]}` · `{"path":"/api/ai1net/im/longpoll","methods":["GET"]}` —— 全部**精确路径**(⛔ 无通配 / 无参数 / 无尾斜杠),对应 §4-B2「桌面端只支持精确路径」。
### M5 功能面(经包内真实路由转发到平台面)
`POST kind=register ⇒ 200`(出向前提)|`GET rooms ⇒ count=2` 且**只含自己参与的 r1/r2**(⛔ 不含别人的 r3)|`GET messages?roomId=r1 ⇒ count=2 · cursor=2`|`POST kind=frame ⇒ 200` 且**帧不落库**(消息数 2→2)|`POST kind=message ⇒ seq=3` 且**真落库**|`say(r2 非成员) ⇒ outbound-denied`|`payload 非对象 ⇒ 400 bad-payload`(**包内先拦,⛔ 不打平台**)|`kind 非法 ⇒ 400 bad-kind`。
---
## 三、S5-2 残留①:**每插件令牌**("插件是否真在该实例启用")
> **残留原文**(`交付物/全功能承载判定-底层插件能否承-20260926.md §2.6`):「⚠️ **不校验"该插件是否真在该实例启用"**(`im.ts:305-307` 自述:与 `/api/im/data/*` 同源,现靠**逐次审计兜底**)⇒ 补法 = **每插件令牌**,属投放线。」
### 落点与语义
- 令牌形如 `p1.<base64url(pluginId\0instanceKey\0exp)>.<hmacSha256(secret, body).base64url.slice(0,32)>` ⇒ 🔴 **把 `(实例, 包名)` 签死** ⇒ 借别人的令牌改个身份头**不可能**(机验:`token-plugin-mismatch`)。
- 🔴 **三态凭据语义**(本棒实测踩出来的关键一条):`undefined`(**没带**身份头)= 本包自己 ⇒ 允许用**本机 env**;`''`(**带了**身份头却没带凭据)⇒ **必须拒**。⇒ 杜绝「包替任何插件签身份」这条漏洞(首版有,已修,见 §七)。
- 拒绝口径:`plugin-not-enabled` → **403**;`instance-token-rejected` / `plugin-token-rejected` → **401**;`longpoll-*` → **409**;`platform-not-configured` → **503**;`bad-*` → **400**;其余 **502**。
### 负控(⛔ 判据 = 未启用必须拒,**且平台零调用**)
| # | 输入 | 期望 | 实测 |
|---|---|---|---|
| ① | 不带令牌 | 拒 | ✅ **403 `plugin-not-enabled`** + **平台侧一次都没被调**(`calls` 保持 8) |
| ② | 借**别人**的令牌 | 拒 | ✅ 403 + 借令牌那次**平台侧也零调用** |
| ③ | 签名伪造 | 拒 | ✅ 包内闸拒 + **平台侧零调用**(包内先拦) |
| ④ | 令牌合法但**不在启用集** | 拒(**由平台侧拒**) | ✅ 403 `plugin-not-enabled`,**且这条确实打到了平台** ⇒ 证明平台侧那道闸真在起作用(⛔ 不是包内假绿) |
| ⑤ | **正控**:正确令牌 | 过 | ✅ 200 ⇒ 证明"拒"不是**空拒** |
| ⑥ | 直测平台桥端点(绕开包) | — | ✅ 实例令牌错 ⇒ **401 `instance-token-rejected`**;无每插件令牌 ⇒ **401 `plugin-token-rejected`** |
| ⑦ | 调用方带**错的**实例令牌 | 仍 200 | ✅ 因为实例令牌**由包注入**(`heads()` **始终**覆盖),插件不必也不该持它 |
🔴 **一处口径澄清(本棒纠正了一个易混点)**:**每插件令牌闸只作用于桥端点**(`plugins/*` · `bridge/*` · `out/*`)。**会话面**(`/api/im/rooms`、`/rooms/:id/messages`)上游走 **`imAuth`**,**没有**这道闸 ⇒ ⛔ **拿会话面测令牌闸会假绿**(首版就这么打错过,已改打 `POST /api/ai1net/im/messages`)。
---
## 四、S5-3 残留②:**一个实例只允许一条常驻长轮询**
> **残留原文**:「⚠️ **一条实例只该有一条常驻长轮询**(`im.ts:482` 原文:⛔ 别每插件一条)—— 直接关系**连接额度**。」
### 落点
- `ResidentLongPollGate`(**实例级单例闸**):`claim(pluginId, at)` ⇒ **同插件幂等**(热重启自愈)/**异插件拒**且回 `holder`(便于改走同进程分发);`release`(**仅持有者**);`isOwner`;`status{holder, since, refusals, idempotentReclaims}`。
- 包内导出单例 `instanceLongPollGate` ⇒ 同一实例内所有插件**共用一把闸**(这是"单例"的落点)。
### 负控(⛔ 判据 = 故意起两条 ⇒ 第二条必须被检出,**且真的没起**)
| # | 输入 | 实测 |
|---|---|---|
| ① | 第一条 `claim` | ✅ 200 · `holder=@dsh-local/im-plugin-demo` |
| ② | 第二条(**另一插件**)`claim` | ✅ **409 `longpoll-already-running`**,且**回"是谁持有"** |
| ③ | 非持有者 `poll` | ✅ 409 `longpoll-not-owner`(第二条**真的没起**) |
| ④ | 同插件**重入** `claim` | ✅ 200 且 `idempotent=true`(热重启自愈) |
| ⑤ | owner `poll` ⇒ 取工作项 ⇒ 回投 | ✅ 1 条 + 200;**同 callId 二次回投 ⇒ `unknown-call-id`**(幂等,⛔ 不重复 resolve) |
| ⑥ | 持有者 `release` ⇒ 别的插件 `claim` | ✅ 200(⛔ **不是永久占用**) |
| ⑦ | **同进程 `core` 模式**复跑 | ✅ 第二条 `startCallbacks` **乐观置位后回滚为 false**、**一次 poll 都没发**(`polls=0`)、闸记 `refusals=1`、持有者**没被顶掉** |
---
## 五、S5-4 回归:迁移前后**同输入 ⇒ 同行为**
**做法**:`scenario(host)` 刻意**不用定时器**(消除时序噪声)⇒ 同一组输入跑两遍 —— **Run A** = 上游原物 SDK 直连**平台面(迁移前口径:只认实例令牌,无令牌闸)**;**Run B** = 包内新 SDK → **包面(真实 `apply(ctx)`)** → **平台面(迁移后口径:令牌闸 + 启用集)**。比对**平台侧效果流水**(确定性文本,⛔ 无时间 / 无随机数)与顶层语义。
| 判据 | 结果 |
|---|---|
| 两边都真的产生了平台侧效果 | ✅ A=13 · B=13 |
| 🔴 **效果流水逐行完全相同** | ✅ **逐行逐字符相同**(原文见 `tmp/p43/读数-20260926.txt` ⑤ 段) |
| 顶层语义 14 项逐项一致 | ✅ 全过(`ready` / `rooms.count` / `messages.count·cursor` / `frame` / `say(r1).seq=3` / `say(r2)=outbound-denied` / 增量拉取 `1` 与 `0` / `unregister` / `subscribe`) |
🔴 **两边**同起点**是这条判据成立的前提** —— `platformPre/platformPost.resetSeed()` 必须各调一次;首版只在一侧复位 ⇒ 前序测试动过的房间被当成"迁移前后的行为差异"(实测症状 `A=2 B=3`),已修(见 §七-④)。
---
## 六、🔴 真实 vs 建模(逐条标注 · ⛔ 不许把建模当"已通")
| 项 | 真实行为(读码 / 只读取证) | 本棒做法 | 是否 = 真实 |
|---|---|---|---|
| **包内那一半(凭据解析/出示/本机 fail-closed 拦截/单例闸)** | 新增代码,**就在包里跑**(真实 apply + 真实 HTTP 分派) | 真实代码 | ✅ **真实** |
| **平台侧那一半(令牌签发 + 桥端点校验 + 启用集校验)** | **真实 `im.ts` 里目前没有这道闸**(残留① 的存在就是因为它没有) | 沙箱 `platform-mock` 里建模(`requirePluginToken` 开关:`false`=迁移前口径/`true`=迁移后口径) | ⚠️ **建模** |
| 桥端点 8 条路径 / 身份头 / 长轮询硬上限 | 真端点存在且在跑(`im.ts` 逐条实读) | 逐字照抄 + **沙箱本机 http** | ⚠️ 口径真实、**传输建模** |
| `pluginIdOf` 形态映射 | 上游 SDK 里逐字实现 | **逐字照抄** | ✅ 真实(照抄) |
| 会话面鉴权 | 真平台走 **`imAuth`**(实例令牌 → 用户身份) | 沙箱同口径建模(`u1`) | ⚠️ **建模** |
| 长轮询"只许一条"的**强制点** | 上游只有**自述**(`im.ts:482` 原文),**平台侧无强制点** | 强制点落在**包内闸**(这正是残留② 的补法) | ✅ 真实(补法落在包内) |
| `bridge/pull` 工作项投递 | 真平台按实例排队 | 沙箱同口径(`pushWork`) | ⚠️ **建模** |
| 部署形态(包怎么进实例 / 谁签发令牌) | 属**投放线**,本棒 ⛔ 不碰 | ⛔ 不做 | ⛔ **未覆盖** |
🔴 **结论纪律**:本棒能断言的只有 —— 「**照 IM 接入方式重写的那套插件侧逻辑,在既有桥口径下四条面齐全、两条残留的补法在包内成立、迁移前后行为逐行一致**」。⛔ **不能**据此断言「真机投放已通」「平台侧令牌闸已上线」「跨机 IM 已通」—— 平台侧那一半与部署形态**都还在沙箱外**。
---
## 七、§4 避坑总表对表(本棒相关类目逐条)
| # | 现象 | 本棒怎么处理 | 证据 |
|---|---|---|---|
| **B1** | 桌面端 `pending (waiting for service: webServer)` | 只用 `connection` 一处扩展点;⛔ 零 `webServer` 引用 | 门禁 ①c「零 `webServer` 引用」✅ |
| **B2** | API 注册了却 404(桌面端**只认精确路径**) | 三条 M5 路由**逐条列精确路径**,并**运行时真挂一次**断言 | 门禁 ①e「四条路径互不重复 / 每条有 methods」+ 回调面 4 条原文 |
| **B3** | 「放进一个包」落了地一半 | 包内**零直连平台库路径**(正则红线扫描)+ 平台侧动作全部走桥 | 门禁 ①e「零直连平台库路径」✅ |
| **A2** | HTTP 路由**静默 404**(`inject` 未声明) | `inject = ['connection']` **逐字声明** | 门禁 ①c「逐字声明」✅ + 取数面 |
| **A6** | 一个可选插件把壳搞得起不来 | 全部装配失败路径**降级 warn,⛔ 不抛** | 包内 8 条 warn **原文已打印**(全部是预期拒绝,无一条装配失败) |
| **A5/E2** | 改了却没生效(UI 侧) | ⚠️ 本棒**未取到浏览器级**,故**不断言 UI 生效** | §八「未取到」 |
| **E1** | 测试跑的是构建产物 | 门禁 `--check-only` **rc=0**;尺子直接打 `lib/` 实件 | 尺读数 ⓪ ✅ |
| **E6/R3** | `exports.default` 禁用 | 门禁 ①b「零 `exports.default`」✅(S2 基线未破) | 尺读数 ⓪ ✅ |
| **D3** | 列限声明改了却没生效 | 门禁 ①d 回读 `DECL_DIGEST=88c752a3e12597b3…`(**8 表 / 29 列**,S3 基线未破) | 尺读数 ⓪ ✅ |
---
## 八、尺子回归(⛔ 未改尺子一行)/ 未取到(如实)
| 面 | 靶子 / 入口 | rc | 说明 |
|---|---|---|---|
| 包内门禁 | `node scripts/build.mjs --check-only`(包目录) | **0** | 「✅ 门禁通过」;新增 **①e M5 门禁** 全绿,`M5_DIGEST=55cdc0d5fc8eb99d` |
| 装配 · 端A 客户端形态 | `--profile-json tmp/p43/端A-客户端形态/package.json` | **0** | 解析到 `dsh-plugin-ai1net/lib/index.js` 20798 B |
| 装配 · 端B 平台形态 | `--profile-json tmp/p43/端B-平台形态/package.json` | **0** | 同上(两端同一份产物) |
| 取数 | `--target @dsh-local/ai1net --entry lib/index.js` | **0** | 真实 import;`inject=['connection']` ✅ |
| 回调 | 同上 | **0** | 真实 `apply(ctx)`,`routeCalls` 逐字 4 条 |
| `v-neg.sh all` | 尺子自身(⛔ 靶子非本包) | **0** | 装配/运行/取数/回调 **四条红/绿双态均成立** ⇒ 尺子仍能判红 |
| `v-baseline.sh` | 47 共享层 + 本机 desktop profile | **0** | 平台侧 4 包;客户端侧 4 bundle |
| `v-ui.sh` | 浏览器级 | **3** | ⚠️ **未取到**(见下) |
⇒ **S2 / S3 / S4 的绿没被本棒弄红**。
### ⚠️ 未取到 / 不成立(如实)
| 项 | 结果 | 原因 |
|---|---|---|
| **`v-ui.sh`(浏览器级)** | ⚠️ **rc=3 未取到** | CDP `127.0.0.1:9330` 不可达 ⇒ 本机无运行中的真壳;启动器属**客户端线**,本线 ⛔ 不代启 |
| `v-plugin.sh 运行` 面 | ⚠️ **rc=0 但属"空真"** | 可用启动日志 mtime = **2026-09-25T09:19:43**,**早于本包存在** ⇒ "无失败条目"**不构成证据** |
| **M5 UI 卡片(`plugins.item` 槽 + 三条路由的界面)** | ⚠️ **未取到** | 同上,需真壳(同 S2/S3/S4 同一笔欠账) |
| **真机投放 / 平台侧令牌闸上线** | ⛔ **刻意不做** | 属硬边界「⛔ 不代用户起实例 / 不碰把包装进去」+ 属投放线 |
| **实机 `requirePluginToken` 开关** | ⚠️ **仅沙箱** | 真实 `im.ts` 尚无该闸(= 残留① 的本体) |
⇒ ⛔ **以上五项都不折算成"已知 / 通过"**。
---
## 九、两处自纠 + 六条本棒踩出来的坑(如实)
| # | 症状 | 根因 | 处置 |
|---|---|---|---|
| ① | 平台回 `bad-manifest` / `bad-args` | `callPlatform(path,{body: obj})` 直接给 `fetch` ⇒ body 变 `"[object Object]"` | 在 SDK `platform()` 内**统一 `JSON.stringify`** |
| ② | 读接口被误判 502 | `/api/im/rooms` 回 `{rooms,count,mode,meId}` **无 `ok` 字段** ⇒ `reply()` 把"缺 ok"当失败 | 改成 `failed = out==null \|\| out.ok===false`(**缺 ok 不算失败**) |
| ③ | 负控**假绿**(拿会话面测令牌闸) | 会话面走 `imAuth`,**没有**桥身份闸 ⇒ 打它测不出东西 | 改打**出向** `POST /api/ai1net/im/messages`(桥端点,有闸) |
| ④ | 回归比对"迁前迁后不一致"(`A=2 B=3`) | **只有一侧复位** ⇒ 前序测试动过的房间被当成行为差异 | 加 `resetSeed()`,**两侧各调一次**(同起点) |
| ⑤ | 「包替任何插件签身份」漏洞 | host 侧从**本机 env 回退**取令牌 ⇒ 调用方不带凭据也能被"代表" | 引入**三态凭据语义**(`undefined` / `''` / 值)⇒ `''` **必拒** |
| ⑥ | 门禁 ①e「→ 应为 4 处…得到 2」 | 注册写在 `for` 循环里 ⇒ **文本计数 ≠ 注册数** | 门禁从"数文本"升级为「**运行时真挂一次再断言**」(spy ctx 捕获 `seen[]`) |
⚠️ **首版另有一处无效负控**:`pluginId` 口径混用(`pluginIdOf` 下划线形态 vs 头里的**包名**)⇒ 闸键与身份头**对不上**,此时负控会"通过"但原因是错的。已统一为**包名**(`callerOf` / 闸键 / 令牌载荷)。
---
## 十、硬边界自查 / 遗留 / 回滚
- ⛔ **未改官方 dsh 主程序与缓存**(R2):只**读** `D:/github/dsh_shenxian/src/web/routes/im.ts`、`sdk/im-plugin-host/index.mjs`、`src/im/sdk/host.ts`(照抄来源),**零写**。
- ⛔ **未重造分发与更新机制**:M5 只做「插件侧路由 + SDK」,分发仍走 S4 的拉取器。
- ⛔ **未代用户起实例**;⛔ **未把包装进任何 profile**(装配用的是 **`tmp/p43/` 下的沙箱 profile**)。
- ⛔ **未改客户端工作区** `dsh-ai1net-desktop` / `dsh-desktop-0.1.7rc2` 任何文件(只读引用;`端A` 是 profile **副本**)。
- ⛔ **包内零直连平台库路径**(正则红线扫描 ✅);⛔ **未 commit / 未 push**。
- ⚠️ **红线门禁**:本棒**未触发**任何一项 —— 未重启控制面、未中断在线用户、改动文件 **6 个(< 10)**、**无不可逆操作**(沙箱可整体 `mv` 归档)。
- 🔴 **停点**:**S4 的 P1 已解除**;**S9 的 P2 仍待拍板,⛔ 本棒未跨过**。本棒**未发现新停点**。
- **本棒改动的包内文件(6 个)**:`lib/im-plugin-sdk.mjs`(**新建** 47,964 B)|`lib/index.js`(20,798 B)|`lib/client.js`(17,210 B)|`scripts/build.mjs`(23,007 B,新增 ①e 门禁 + ①c 改为"零裸标识符 import"+ 语法门禁扩到 `.mjs`)|`package.json`(`exports["./im-plugin-sdk"]`)|`README.md`。
- **遗留(如实)**:① **浏览器级 / M5 卡片仍欠一笔真壳取证**(客户端线窗口)② **平台侧令牌闸与"启用集"校验尚未进真实 `im.ts`**(投放线)③ **M5 界面只到"能渲染一个只读面板"**,交互面(发消息 / 开关回调)留后续块 ④ 沙箱 `platform-mock` 的 `requirePluginToken` **只是开关**,不是真平台实现。
- **回滚**:本棒**无生产副作用** ⇒ 回滚 = `mv tmp/p43/ 归档/`(⛔ 不用 `rm`);包内改动可用 `git` 逆转(若包目录未入 git ⇒ 以 `lib/im-plugin-sdk.mjs` 删除 + 其余 5 文件回退到 S4 版本为准)。
---
## 十一、出处
- 规格书:`D:/github/dsh_shenxian/dsh-server-docs/05-交接单/交接单_包规格书_20260926.md`(§〇 路径约定 / §五 A 冻结规格 / §五 C 指针)
- 方案:`交付物/功能打包到基础插件-分步实施方案-20260926.md §3.10.7`(S5-1…S5-4)+ `§4 避坑总表`(A/B/D/E 类)
- 残留两条原文:`交付物/全功能承载判定-底层插件能否承-20260926.md §2.6`
- 上游权威(只读):`D:/github/dsh_shenxian/src/web/routes/im.ts`|`D:/github/dsh_shenxian/sdk/im-plugin-host/index.mjs`|`src/im/sdk/host.ts:112`
- 前置回执:`交付物/分发与更新-S4落地与三轮回执-20260926.md`(§八 真实 vs 建模**已逐条继承**)
- 本棒产物:包内 `E:/ProgramData/AIProject/dsh-plugin-ai1net/{lib/im-plugin-sdk.mjs,lib/index.js,lib/client.js,scripts/build.mjs,package.json,README.md}`
- 本棒读数(⛔ 不入库):`tmp/p43/platform-mock.mjs`|`tmp/p43/run-s5.mjs`|`tmp/p43/run-scales.sh`|`tmp/p43/读数-20260926.txt`|`tmp/p43/尺读数-S5-20260926.txt`|`tmp/p43/端A-客户端形态/`|`tmp/p43/端B-平台形态/`