Channel Plugins
Channel plugins integrate messaging platforms (Discord, Slack, WhatsApp, etc.) with SimpleClaw’s Gateway.Channel Plugin Interface
A channel plugin implements theChannelPlugin interface with adapters for different aspects:
import type { ChannelPlugin } from "simpleclaw/plugin-sdk";
export interface ChannelPlugin<ResolvedAccount = any, Probe = unknown, Audit = unknown> {
id: string; // Unique channel ID
meta: ChannelMeta; // Display info
capabilities: ChannelCapabilities; // Feature flags
// Required adapters
config: ChannelConfigAdapter;
// Optional adapters
onboarding?: ChannelOnboardingAdapter;
setup?: ChannelSetupAdapter;
pairing?: ChannelPairingAdapter;
security?: ChannelSecurityAdapter;
groups?: ChannelGroupAdapter;
mentions?: ChannelMentionAdapter;
outbound?: ChannelOutboundAdapter;
status?: ChannelStatusAdapter;
gateway?: ChannelGatewayAdapter;
auth?: ChannelAuthAdapter;
streaming?: ChannelStreamingAdapter;
threading?: ChannelThreadingAdapter;
messaging?: ChannelMessagingAdapter;
directory?: ChannelDirectoryAdapter;
resolver?: ChannelResolverAdapter;
actions?: ChannelMessageActionAdapter;
heartbeat?: ChannelHeartbeatAdapter;
agentTools?: ChannelAgentToolFactory | ChannelAgentTool[];
}
Core Components
Meta and Capabilities
const myChannel: ChannelPlugin = {
id: "mychannel",
meta: {
label: "MyChannel",
detailLabel: "MyChannel messaging",
systemImage: "message.circle.fill", // SF Symbol
},
capabilities: {
outbound: true, // Can send messages
groups: true, // Supports group chats
threads: true, // Supports threaded replies
reactions: true, // Can add emoji reactions
media: ["image", "video", "audio", "document"],
streaming: false, // Streaming message updates
voiceCall: false, // Voice calling support
},
};
Config Adapter
Manages account configuration:import type { ChannelConfigAdapter } from "simpleclaw/plugin-sdk";
import { z } from "zod";
const AccountConfigSchema = z.object({
enabled: z.boolean().default(true),
apiKey: z.string(),
webhookSecret: z.string().optional(),
allowFrom: z.array(z.string()).default([]),
});
type ResolvedAccount = z.infer<typeof AccountConfigSchema> & {
accountId: string;
};
const config: ChannelConfigAdapter<ResolvedAccount> = {
// List all account IDs from config
listAccountIds(config) {
return Object.keys(config.channels?.mychannel?.accounts || {});
},
// Get default account ID
resolveDefaultAccountId(config) {
return "default";
},
// Resolve account config
resolveAccount(config, accountId) {
const accounts = config.channels?.mychannel?.accounts || {};
const account = accounts[accountId];
if (!account) {
throw new Error(`Account ${accountId} not found`);
}
return {
accountId,
...account,
};
},
};
Outbound Adapter
Sends messages to the platform:import type { ChannelOutboundAdapter } from "simpleclaw/plugin-sdk";
const outbound: ChannelOutboundAdapter = {
async send(ctx, message) {
const { account } = ctx;
const { to, text, media, threadId } = message;
// Send text message
if (text) {
await platformApi.sendMessage({
apiKey: account.apiKey,
chatId: to,
text,
threadId,
});
}
// Send media attachments
if (media?.length) {
for (const item of media) {
await platformApi.sendMedia({
apiKey: account.apiKey,
chatId: to,
url: item.url,
mime: item.mime,
caption: item.caption,
threadId,
});
}
}
},
async sendReaction(ctx, params) {
await platformApi.react({
apiKey: ctx.account.apiKey,
messageId: params.messageId,
emoji: params.emoji,
});
},
async updateMessage(ctx, params) {
await platformApi.editMessage({
apiKey: ctx.account.apiKey,
messageId: params.messageId,
newText: params.text,
});
},
};
Gateway Adapter
Handles incoming messages:import type { ChannelGatewayAdapter } from "simpleclaw/plugin-sdk";
const gateway: ChannelGatewayAdapter = {
async start(ctx) {
const { account, logger, handleInbound } = ctx;
// Initialize platform client
const client = createPlatformClient({
apiKey: account.apiKey,
webhookSecret: account.webhookSecret,
});
// Listen for messages
client.on("message", async (msg) => {
await handleInbound({
from: msg.sender.id,
text: msg.text,
timestamp: msg.timestamp,
messageId: msg.id,
threadId: msg.threadId,
media: msg.attachments?.map(att => ({
url: att.url,
mime: att.mimeType,
size: att.size,
})),
});
});
// Listen for reactions
client.on("reaction", async (reaction) => {
logger.info("Reaction received", { reaction });
});
await client.connect();
// Store client for cleanup
ctx.state.client = client;
},
async stop(ctx) {
await ctx.state.client?.disconnect();
},
};
Security Adapter
Access control and DM policy:import type { ChannelSecurityAdapter } from "simpleclaw/plugin-sdk";
import { isNormalizedSenderAllowed } from "simpleclaw/plugin-sdk";
const security: ChannelSecurityAdapter = {
resolveSenderAllowed(ctx, sender) {
const { account } = ctx;
const allowFrom = account.allowFrom || [];
// Check if sender is in allowlist
return isNormalizedSenderAllowed({
senderId: sender.id,
senderName: sender.name,
allowFrom,
});
},
resolveDmPolicy(ctx) {
// Return DM policy: "open", "pairing", or "blocked"
return ctx.account.dmPolicy || "pairing";
},
resolveGroupPolicy(ctx, groupId) {
const groups = ctx.account.groups || {};
const groupConfig = groups[groupId] || groups["*"];
return {
allowed: !!groupConfig,
requireMention: groupConfig?.requireMention ?? true,
};
},
};
Status Adapter
Health monitoring:import type { ChannelStatusAdapter } from "simpleclaw/plugin-sdk";
import { buildBaseAccountStatusSnapshot } from "simpleclaw/plugin-sdk";
const status: ChannelStatusAdapter = {
async buildAccountSnapshot(ctx, accountId) {
const account = ctx.resolveAccount(accountId);
const state = ctx.getState(accountId);
return buildBaseAccountStatusSnapshot({
accountId,
enabled: account.enabled,
configured: !!account.apiKey,
connected: state.connected,
running: state.running,
lastError: state.lastError?.message,
lastConnectedAt: state.lastConnectedAt,
});
},
async probe(ctx, accountId) {
// Perform active health check
try {
const response = await platformApi.getMe({
apiKey: ctx.account.apiKey,
});
return {
ok: true,
user: response.username,
};
} catch (error) {
return {
ok: false,
error: error.message,
};
}
},
};
Pairing Adapter
DM pairing flow:import type { ChannelPairingAdapter } from "simpleclaw/plugin-sdk";
const pairing: ChannelPairingAdapter = {
async requestPairing(ctx, params) {
const code = generatePairingCode();
// Send pairing code to user
await platformApi.sendMessage({
apiKey: ctx.account.apiKey,
chatId: params.peerId,
text: `Your pairing code: ${code}\n\nRun: simpleclaw pairing approve mychannel ${code}`,
});
return {
code,
expiresAt: Date.now() + 300000, // 5 minutes
};
},
async approvePairing(ctx, params) {
// Add to allowlist
await ctx.updateConfig((config) => {
const allowFrom = config.channels.mychannel.accounts[ctx.accountId].allowFrom || [];
allowFrom.push(params.peerId);
return config;
});
},
};
Advanced Features
Threading Support
import type { ChannelThreadingAdapter } from "simpleclaw/plugin-sdk";
const threading: ChannelThreadingAdapter = {
resolveThreadId(ctx, message) {
// Extract thread ID from message
return message.threadId || message.replyToMessageId;
},
formatThreadReply(ctx, params) {
return {
threadId: params.threadId,
replyToMessageId: params.messageId,
};
},
};
Message Actions
import type { ChannelMessageActionAdapter } from "simpleclaw/plugin-sdk";
const actions: ChannelMessageActionAdapter = {
listActions(ctx) {
return [
{
id: "retry",
label: "Retry",
icon: "arrow.clockwise",
},
{
id: "edit",
label: "Edit",
icon: "pencil",
},
];
},
async handleAction(ctx, params) {
if (params.actionId === "retry") {
await ctx.handleInbound({
from: params.message.from,
text: params.message.text,
});
}
},
};
Plugin Configuration Schema
import { buildChannelConfigSchema } from "simpleclaw/plugin-sdk";
import { z } from "zod";
const schema = buildChannelConfigSchema({
schema: {
enabled: z.boolean().default(false),
accounts: z.record(
z.object({
apiKey: z.string(),
webhookSecret: z.string().optional(),
allowFrom: z.array(z.string()).default([]),
dmPolicy: z.enum(["open", "pairing", "blocked"]).default("pairing"),
})
),
},
uiHints: {
"enabled": {
label: "Enable MyChannel",
help: "Connect to MyChannel messaging",
},
"accounts.*.apiKey": {
label: "API Key",
sensitive: true,
help: "Get this from MyChannel dashboard",
},
"accounts.*.dmPolicy": {
label: "DM Policy",
help: "How to handle direct messages from unknown senders",
},
},
});
Next Steps
Provider Plugins
Build LLM and tool provider plugins
Plugin SDK
Complete SDK reference