Skip to main content

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):

DirectoryPathRead/WriteSource
User-installed (pluginsDir)<userData>/channel-plugins/Read/WriteMarketplace download / Local installation
Built-in (bundledDir)<resources>/bundled-channel-plugins/Read-onlyDistributed 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-plugins is a pre-built artifact directory, not in the inline build chain of build: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

Itemofficialsteam
Config fileelectron/electron-builder.official.ymlelectron/electron-builder.steam.yml
renderer-extension/byo-providersIncluded (BYO is core promise of official)Excluded (Steam compliance separation, explicit negative filter match)
bundled-channel-pluginsIncludedMust 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:

  1. Steam package had no resources/bundled-channel-plugins/ directory;
  2. At runtime channelBundledDir points to non-existent path → ChannelPluginLoader.bundledDir = null;
  3. discover() only scans the empty user directory → discovers 0 channel plugins;
  4. 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:

InvariantMeaning
byo-providers ABSENTSteam package does not contain renderer-extension/byo-providers (compliant)
design-studio PRESENTSteam package contains renderer-extension/design-studio
channel === 'steam'Channel metadata is steam
bundled-channel-plugins PRESENTSteam 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 in resources/bundled-channel-plugins/ at same level.
  • MODE 2 (Staged dry-run, fallback): Parse extraResources entries in electron-builder.steam.yml mapped to bundled-channel-plugins, count subdirectories in the source directory of from:; 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 new bundled-channel-plugins PRESENT invariant fills this blind spot.

Modification / Verification Checklist

When adding or adjusting channel plugin packaging:

  • electron-builder.steam.yml and official.yml both declare packages/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 old release/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-package exit code 0, [PASS] bundled-channel-plugins PRESENT;
  • Runtime verification: Main process log shows Channel plugin directories … bundledExists: true and Channel plugin discover: found N plugin(s) (N > 0).