채널 플러그인 패키징 (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는 official의 핵심 약속) | 제외 (Steam 규정 준수 분리, 명시적 음수 filter 매칭) |
bundled-channel-plugins | 포함 | 반드시 포함해야 함 |
채널 플러그인은 BYO 규정 준수 분리와 무관합니다: 채널 플러그인은 사용자의 자체 봇 토큰 (Discord 봇 토큰, Telegram 봇 토큰 등)을 사용하므로, Steam이 거부하는 "BYO LLM 제공업체 키 / 외부 결제" 범주에 해당하지 않습니다. 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 버전에서 채널 플러그인 사용 불가, official 버전은 정상 작동.
이 누락은 의도적인 규정 준수 분리가 아니었으며 (모든 의도적 제외에는 주석 + 음수 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) 표시.