Channel Plugin Packaging (official / steam)
Channel plugins (Discord, Feishu, Telegram, Slack, WhatsApp, etc.) are distributed with the installer as pre-built artifacts. This document explains their complete path from source code to runtime, packaging differences between build channels (official / steam), and why the steam channel once had missing plugins and how auditing now prevents that.
Two Discovery Directories
At runtime, ChannelPluginLoader discovers channel plugins from two directories, with pluginsDir having higher priority than bundledDir (overrides when type is the same):
| Directory | Path | Read/Write | Source |
|---|---|---|---|
User-installed (pluginsDir) | <userData>/channel-plugins/ | Read/Write | Marketplace download / Local installation |
Built-in (bundledDir) | <resources>/bundled-channel-plugins/ | Read-only | Distributed with installer |
Related code:
packages/desktop/app/main/services/capabilities/integrations/channel/ChannelPluginLoader.ts,packages/desktop/app/main/bootstrap/capabilities-channel.ts.
The resolution of bundledDir is in bootstrap/index.ts:
const channelBundledDir = isDev
? path.join(__dirname, '..', '..', '..', 'elftia-channels', 'dist-plugins') // Dev mode: workspace source directory
: path.join(process.resourcesPath, 'bundled-channel-plugins'); // Packaged mode: copy under resources
ChannelPluginLoader performs an existence check on bundledDir during construction:
this.bundledDir = bundledDir && existsSync(bundledDir) ? bundledDir : null;
Key implication: If packaged <resources>/bundled-channel-plugins/ does not exist, bundledDir is set to null, and discover() will only have the <userData>/channel-plugins/ directory. For new installations, this directory is empty → discovers 0 channel plugins → Channel page has no available plugins.
From Source Code to Installer
Channel plugin source code build artifacts are in the workspace directory:
packages/elftia-channels/dist-plugins/
├── discord/
├── feishu/
├── telegram/
├── slack/
├── whatsapp/
└── … (total ~26)
packages/elftia-channels/dist-pluginsis a pre-built artifact directory, not in the inline build chain ofbuild:official/build:steam— it must already exist before packaging.
During packaging, electron-builder's extraResources copies it to resources/bundled-channel-plugins. Both build channels must declare this:
# Both electron/electron-builder.official.yml and electron/electron-builder.steam.yml need:
extraResources:
- from: packages/elftia-channels/dist-plugins
to: bundled-channel-plugins
filter:
- "**/*"
Packaging Differences Between official and steam
| Item | official | steam |
|---|---|---|
| Config file | electron/electron-builder.official.yml | electron/electron-builder.steam.yml |
renderer-extension/byo-providers | Included (BYO is core promise of official) | Excluded (Steam compliance separation, explicit negative filter match) |
bundled-channel-plugins | Included | Must also be included |
Channel plugins are unrelated to BYO compliance separation: Channel plugins use user's own bot tokens (Discord bot token, Telegram bot token, etc.), not in the «BYO LLM provider key / off-site payment» category that Steam rejects. The only compliance exclusion for Steam channel is renderer-extension/byo-providers; channel plugins are not in the exclusion list.
Previous Bug: steam.yml Missing Configuration
The extraResources in electron-builder.steam.yml once entirely lacked bundled-channel-plugins (never appeared in the full git history). Consequences:
- Steam package had no
resources/bundled-channel-plugins/directory; - At runtime
channelBundledDirpoints to non-existent path →ChannelPluginLoader.bundledDir = null; discover()only scans the empty user directory → discovers 0 channel plugins;- User experience: Steam version cannot use channel plugins, official version works normally.
This omission was not an intentional compliance separation (all intentional exclusions have comments + negative filter matches), but a extraResources that was missed when steam.yml was forked from official.yml.
Audit Fallback: verify:steam-package
scripts/verify-steam-packaging.mts (npm run verify:steam-package, now integrated at the end of build:steam chain) now verifies four invariants:
| Invariant | Meaning |
|---|---|
byo-providers ABSENT | Steam package does not contain renderer-extension/byo-providers (compliant) |
design-studio PRESENT | Steam package contains renderer-extension/design-studio |
channel === 'steam' | Channel metadata is steam |
bundled-channel-plugins PRESENT | Steam package includes at least 1 channel plugin ← newly added |
Exit code contract: 0 all pass; 2 at least one violation; 1 input unavailable.
The audit has two modes (by priority):
- MODE 1 (Already packaged, authoritative): If
release/steam/<platform>-unpacked/…/resources/plugins/renderer-extension/exists, directly audit the real artifact; channel plugin count = number of subdirectories inresources/bundled-channel-plugins/at same level. - MODE 2 (Staged dry-run, fallback): Parse
extraResourcesentries inelectron-builder.steam.ymlmapped tobundled-channel-plugins, count subdirectories in the source directory offrom:; no entry → count as 0 → invariant fails (exactly what this prevents).
Historical lesson: Previous audits only looked at
renderer-extension, channel plugins were entirely out of scope — so when the package had 0 channel plugins, the audit still passed. The newbundled-channel-plugins PRESENTinvariant fills this blind spot.
Modification / Verification Checklist
When adding or adjusting channel plugin packaging:
-
electron-builder.steam.ymlandofficial.ymlboth declarepackages/elftia-channels/dist-plugins → bundled-channel-plugins; - Before packaging,
packages/elftia-channels/dist-plugins/is built and non-empty; - Rebuild:
npm run build:steam:win(note that oldrelease/steam/win-unpacked/is the previous build, MODE 1 audit will use it — must rebuild before auditing); - Verify artifacts:
release/steam/win-unpacked/resources/bundled-channel-plugins/contains all plugin directories; - Audit passes:
npm run verify:steam-packageexit code0,[PASS] bundled-channel-plugins PRESENT; - Runtime verification: Main process log shows
Channel plugin directories … bundledExists: trueandChannel plugin discover: found N plugin(s)(N > 0).