频道插件打包(official / steam)
频道插件(Discord、Feishu、Telegram、Slack、WhatsApp……)以预构建产物的形式随安装包分发。本文说明它们从源码到运行时的完整路径、不同构建渠道(official / steam)的打包差异,以及为什么 steam 渠道曾经漏打、现在如何用审计兜底。
两个发现目录
运行时由 ChannelPluginLoader 从两个目录发现频道插件,pluginsDir 优先级高于 bundledDir(同 type 时覆盖):
| 目录 | 路径 | 读写 | 来源 |
|---|---|---|---|
用户安装(pluginsDir) | <userData>/channel-plugins/ | 读写 | Marketplace 下载 / 本地安装 |
内置(bundledDir) | <resources>/bundled-channel-plugins/ | 只读 | 随安装包分发 |
相关代码:
packages/desktop/app/main/services/capabilities/integrations/channel/ChannelPluginLoader.ts、packages/desktop/app/main/bootstrap/capabilities-channel.ts。
bundledDir 的解析见 bootstrap/index.ts:
const channelBundledDir = isDev
? path.join(__dirname, '..', '..', '..', 'elftia-channels', 'dist-plugins') // 开发态:workspace 源目录
: path.join(process.resourcesPath, 'bundled-channel-plugins'); // 打包态:resources 下的拷贝
ChannelPluginLoader 构造时对 bundledDir 做存在性判断:
this.bundledDir = bundledDir && existsSync(bundledDir) ? bundledDir : null;
关键含义:如果打包态 <resources>/bundled-channel-plugins/ 不存在,bundledDir 会被置为 null,discover() 就只剩 <userData>/channel-plugins/ 一个目录。对全新安装的用户,该目录为空 → 发现 0 个频道插件 → 频道页没有任何可用插件。
从源码到安装包
频道插件源码构建产物落在 workspace 目录:
packages/elftia-channels/dist-plugins/
├── discord/
├── feishu/
├── telegram/
├── slack/
├── whatsapp/
└── …(共 ~26 个)
packages/elftia-channels/dist-plugins是预构建产物目录,不在build:official/build:steam的内联构建链里——打包前需已存在。
打包时由 electron-builder 的 extraResources 把它拷进 resources/bundled-channel-plugins。两个构建渠道都必须声明这一条:
# electron/electron-builder.official.yml 与 electron/electron-builder.steam.yml 都需要:
extraResources:
- from: packages/elftia-channels/dist-plugins
to: bundled-channel-plugins
filter:
- "**/*"
official 与 steam 的打包差异
| 项 | official | steam |
|---|---|---|
| 配置文件 | electron/electron-builder.official.yml | electron/electron-builder.steam.yml |
renderer-extension/byo-providers | 打入(BYO 是官网版核心承诺) | 排除(Steam 合规切割,filter 显式负向匹配) |
bundled-channel-plugins | 打入 | 必须同样打入 |
频道插件与 BYO 合规切割无关:频道插件使用用户自己的 bot token(Discord bot token、Telegram bot token 等),不属于 Steam 拒审的「BYO LLM provider key / 站外付费」范畴。Steam 渠道唯一的合规排除是 renderer-extension/byo-providers,频道插件不在排除之列。
曾经的故障:steam.yml 漏配
electron-builder.steam.yml 的 extraResources 一度整条缺失 bundled-channel-plugins(全 git 历史里从未出现过)。后果:
- Steam 包里没有
resources/bundled-channel-plugins/目录; - 运行时
channelBundledDir指向不存在的路径 →ChannelPluginLoader.bundledDir = null; discover()只扫空的用户目录 → 发现 0 个频道插件;- 用户现象:Steam 版无法使用频道插件,官网版正常。
这条排除不是有意的合规切割(所有有意排除都带注释 + filter 负向匹配),而是 steam.yml 从 official.yml 分叉时遗漏的一条 extraResources。
审计兜底:verify:steam-package
scripts/verify-steam-packaging.mts(npm run verify:steam-package,已接入 build:steam 链尾)现在校验四条不变量:
| 不变量 | 含义 |
|---|---|
byo-providers ABSENT | Steam 包不含 renderer-extension/byo-providers(合规) |
design-studio PRESENT | Steam 包含 renderer-extension/design-studio |
channel === 'steam' | 渠道元数据为 steam |
bundled-channel-plugins PRESENT | Steam 包至少打入 1 个频道插件 ← 本次新增 |
退出码契约:0 全过;2 至少一条违反;1 输入不可用。
审计有两种模式(按优先级):
- MODE 1(已打包,权威):若存在
release/steam/<platform>-unpacked/…/resources/plugins/renderer-extension/,直接审计真实产物;频道插件数 = 同级resources/bundled-channel-plugins/的子目录数。 - MODE 2(暂存干跑,回退):解析
electron-builder.steam.yml里映射到bundled-channel-plugins的extraResources条目,统计其from:源目录的子目录数;没有这条 → 计为 0 → 不变量失败(正是这次要拦的漏配)。
历史教训:之前的审计只看
renderer-extension,频道插件整体不在其视野里——所以包里 0 个频道插件时审计依然全绿。新增的bundled-channel-plugins PRESENT不变量堵上了这个盲区。
修改 / 验证清单
新增或调整频道插件打包时:
-
electron-builder.steam.yml与official.yml都声明了packages/elftia-channels/dist-plugins → bundled-channel-plugins; - 打包前
packages/elftia-channels/dist-plugins/已构建且非空; - 重新打包:
npm run build:steam:win(注意旧的release/steam/win-unpacked/是上次产物,审计 MODE 1 会以它为准——务必重新打包后再审计); - 产物核对:
release/steam/win-unpacked/resources/bundled-channel-plugins/下有全部插件目录; - 审计通过:
npm run verify:steam-package退出码0、[PASS] bundled-channel-plugins PRESENT; - 运行时核对:主进程日志
Channel plugin directories … bundledExists: true与Channel plugin discover: found N plugin(s)(N > 0)。