Imported from guazi04/opencode-lark (
src/channel/AGENTS.md). Install upstream withnpx skills add guazi04/opencode-lark --skill channel. Copyright stays with the author.
channel/
The plugin system that makes opencode-lark platform-agnostic. Define the contract here; implement it per platform.
Files
types.ts
Defines the ChannelPlugin interface and all its sub-adapter interfaces. This is the only contract file — if you're adding a new chat platform, start here.
Adapter breakdown:
| Adapter | Responsibility |
|---|---|
ChannelConfigAdapter |
List accounts, resolve credentials (required) |
ChannelGatewayAdapter |
Start/stop platform connections (WebSocket, polling) |
ChannelMessagingAdapter |
Normalize inbound events, format outbound text |
ChannelOutboundAdapter |
sendText, sendCard to a thread |
ChannelStreamingAdapter |
Create streaming sessions, coalesce rapid updates |
ChannelThreadingAdapter |
Map platform thread IDs to session IDs |
All adapters except config are optional. A read-only notification bot only needs config + outbound.
manager.ts
ChannelManager holds the registry of active plugins. Key methods:
register(plugin)— adds a plugin. Logs a message and overwrites if the sameplugin.idis registered twice.startAll()— iterates registered plugins sequentially (awaits each), callsgateway.startAccount()for each. Error isolation: one plugin failing does not prevent others from starting.stopAll()— graceful shutdown, called from SIGTERM handler insrc/index.ts
feishu/feishu-plugin.ts
The Feishu implementation of ChannelPlugin. Wires together:
FeishuApiClient→ config + outbound adaptersFeishuWsClient→ gateway adapterMessageHandler+CommandHandler→ messaging adapterStreamingBridge→ streaming adapterSessionManager→ threading adapter
Don't add Feishu-specific logic here. This file is glue only. Business logic lives in src/handler/ and src/feishu/.
Adding a new channel
- Create
src/channel/{platform}/with a{platform}-plugin.tsfile. - Implement
ChannelPluginfromtypes.ts. - In
src/index.tsPhase 6, instantiate and register it:channelManager.register(new YourPlugin(...)). startAll()picks it up automatically.
Gotchas
ChannelIdis a branded string type. Don't use raw strings whereChannelIdis expected.startAll()awaits each plugin start sequentially. If startup order matters between plugins, they are naturally ordered by registration order. Error isolation ensures one failing plugin doesn't block others.