diff --git a/MIGRATION_PLAN.md b/MIGRATION_PLAN.md deleted file mode 100644 index 543e2184a..000000000 --- a/MIGRATION_PLAN.md +++ /dev/null @@ -1,694 +0,0 @@ -# Migration Plan: Moving Twitch Plugin to Clawdbot Core - -> **Source Reference** -> - **Original Plugin Directory:** `/Users/jayden/projects/clawdbot-twitch.tidyup` -> - **Original Branch:** `tidyup` -> - **Created By:** Claude (Sonnet 4.5) on 2026-01-24 -> - **Issue:** External plugin cannot dispatch inbound messages to Clawdbot's agent system - ---- - -## Problem Statement - -The Twitch plugin is currently an **external plugin** that cannot dispatch inbound messages to Clawdbot's agent system because: - -1. External plugins don't have access to `dispatchInboundMessage` from the core -2. The `ChannelGatewayContext` doesn't provide a message dispatch mechanism -3. Only core channels (built into Clawdbot) can properly route inbound messages - -## Solution - -Move the Twitch plugin into **Clawdbot core** as a built-in channel, following the same pattern as Discord, WhatsApp, Telegram, etc. - ---- - -## Files in External Plugin (to be migrated) - -``` -src/ -├── actions.ts # Message action handlers -├── config.ts # Account config resolution -├── outbound.ts # Outbound message adapter -├── plugin.ts # Main plugin definition (twitchPlugin) -├── plugin-sdk.ts # ❌ DELETE - duplicated types, use core instead -├── probe.ts # Connection probing -├── resolver.ts # Username -> user ID resolution -├── send.ts # Send message functions -├── status.ts # Status issues collection -├── twitch-client.ts # Twitch IRC client wrapper -├── types.ts # ❌ DELETE - mostly duplicated types -├── index.ts # ❌ DELETE - plugin wrapper, not needed for core -├── access-control.ts # Access control (allowlist, roles) -├── cli/ -│ └── test-connect.ts # CLI command for testing -└── utils/ - ├── markdown.ts # Markdown stripping for Twitch - └── twitch.ts # Twitch utilities - -Tests (all need migration): -├── *.test.ts # All test files -``` - ---- - -## Detailed Migration Steps - -### Phase 1: Preparation & Analysis - -#### 1.1 Audit Duplicated Types -**Status: ✅ Complete** - -The following files contain duplicated types that should be removed and imported from core: - -- **`plugin-sdk.ts`** - Entire file is duplicated (ClawdbotPluginApi, config schema) -- **`types.ts`** - Contains many types duplicated from core: - - `ChannelMeta`, `ChatCapabilities`, `ChannelOutboundAdapter` - - `ChannelOutboundContext`, `OutboundDeliveryResult` - - `ChannelPlugin`, `ChannelAccountSnapshot` - - `ChannelStatusAdapter`, `ChannelGatewayAdapter` - - `ChannelGatewayContext`, `ChannelLogSink` - - `ChannelMessageActionAdapter`, `ChannelMessageActionContext` - - Plus plugin-specific wrapper types (PluginAPI, CoreConfig stubs) - -**Keep in `types.ts`:** -- `TwitchAccountConfig` - Twitch-specific config -- `TwitchChannelConfig` - Twitch channel config -- `TwitchRole` - Twitch role enum -- `TwitchTarget` - Twitch send target -- `TwitchChatMessage` - Twitch message structure -- `TwitchPluginConfig` - Plugin-specific config (if still needed) -- `SendResult` - Twitch send result -- `ProviderLogger` - Simple logger interface (or use core's) - -#### 1.2 Document Twitch-Specific Types to Preserve -```typescript -// Twitch-specific types that MUST be kept -export type TwitchRole = "moderator" | "owner" | "vip" | "subscriber" | "all"; - -export interface TwitchAccountConfig { - username: string; - token: string; - clientId?: string; - channel?: string; - enabled?: boolean; - allowFrom?: Array; - allowedRoles?: TwitchRole[]; - requireMention?: boolean; - clientSecret?: string; - refreshToken?: string; - expiresIn?: number | null; - obtainmentTimestamp?: number; -} - -export interface TwitchChatMessage { - username: string; - userId?: string; - message: string; - channel: string; - displayName?: string; - id?: string; - timestamp?: Date; - isMod?: boolean; - isOwner?: boolean; - isVip?: boolean; - isSub?: boolean; - chatType?: "direct" | "group"; -} -``` - ---- - -### Phase 2: Core Directory Structure - -#### 2.1 Create Directory Structure in Clawdbot Core -```bash -cd opensrc/repos/github.com/clawdbot/clawdbot -mkdir -p src/twitch -mkdir -p src/twitch/monitor -``` - -#### 2.2 Target File Structure (following Discord pattern) -``` -src/twitch/ -├── channel.ts # Channel plugin definition (replaces plugin.ts) -├── config.ts # Account config (keep from plugin) -├── access-control.ts # Access control (keep from plugin) -├── probe.ts # Connection probing (keep from plugin) -├── resolver.ts # Username resolution (keep from plugin) -├── status.ts # Status issues (keep from plugin) -├── send.ts # Outbound send (keep from plugin) -├── send.types.ts # Send-specific types -├── actions.ts # Message actions (keep from plugin) -├── twitch-client.ts # Twitch client wrapper (keep from plugin) -├── types.ts # Twitch-specific types ONLY -├── utils/ -│ ├── markdown.ts # Markdown utilities (keep from plugin) -│ └── twitch.ts # Twitch utilities (keep from plugin) -└── monitor/ - ├── provider.ts # Inbound message provider (NEW) - ├── message-handler.ts # Message processing (NEW) - ├── reply-delivery.ts # Reply delivery (NEW) - ├── format.ts # Message formatting (NEW) - └── loggers.ts # Twitch-specific loggers (NEW) -``` - ---- - -### Phase 3: Type Deduping - -#### 3.1 Remove `plugin-sdk.ts` Entirely -This file is 100% duplicated from core. Delete it after migration. - -#### 3.2 Refactor `types.ts` - -**BEFORE (current plugin):** -```typescript -// types.ts - ~465 lines with many duplicates -export interface ChannelMeta { /* duplicate */ } -export interface ChatCapabilities { /* duplicate */ } -export interface TwitchAccountConfig { /* keep */ } -export interface TwitchChatMessage { /* keep */ } -// ... many more duplicates -``` - -**AFTER (core version):** -```typescript -// types.ts - ~100 lines, Twitch-specific only -import type { - ChannelMeta, - ChatCapabilities, - // ... import from core -} from "../channels/plugins/types.core.js"; - -export type TwitchRole = "moderator" | "owner" | "vip" | "subscriber" | "all"; - -export interface TwitchAccountConfig { - username: string; - token: string; - // ... Twitch-specific fields -} - -export interface TwitchChatMessage { - username: string; - message: string; - // ... Twitch-specific fields -} -``` - -#### 3.3 Update All Imports Across Files - -**Pattern for imports:** -```typescript -// OLD (plugin): -import type { ChannelMeta, CoreConfig } from "./types.js"; -import type { ChannelOutboundAdapter } from "./types.js"; - -// NEW (core): -import type { ChannelMeta, ChatCapabilities } from "../channels/plugins/types.core.js"; -import type { ChannelOutboundAdapter } from "../channels/plugins/types.adapters.js"; -import type { ClawdbotConfig } from "../config/config.js"; -import type { TwitchAccountConfig, TwitchChatMessage } from "./types.js"; -``` - ---- - -### Phase 4: Core Channel Registration - -#### 4.1 Update `src/channels/registry.ts` -```typescript -// ADD "twitch" to CHAT_CHANNEL_ORDER -export const CHAT_CHANNEL_ORDER = [ - "telegram", - "whatsapp", - "discord", - "twitch", // ← ADD HERE - "slack", - "signal", - "imessage", -] as const; - -// ADD metadata -const CHAT_CHANNEL_META: Record = { - // ... existing channels - twitch: { - id: "twitch", - label: "Twitch", - selectionLabel: "Twitch (Chat)", - detailLabel: "Twitch Chat", - docsPath: "/channels/twitch", - docsLabel: "twitch", - blurb: "connects to Twitch chat for live stream interaction.", - systemImage: "video.badge.plus", - }, - // ... existing channels -}; -``` - -#### 4.2 Update `src/utils/message-channel.ts` -```typescript -// Add "twitch" to INTERNAL_MESSAGE_CHANNEL union if needed -// Add to GatewayClientName if Twitch connects via gateway -``` - -#### 4.3 Register Channel Plugin -Create or update channel plugin registration (typically in channel index file): -```typescript -// src/twitch/index.ts or integration point -import { twitchPlugin } from "./channel.js"; -import { registerChannelPlugin } from "../channels/plugins/index.js"; - -export function registerTwitchChannel() { - registerChannelPlugin("twitch", twitchPlugin); -} -``` - ---- - -### Phase 5: Inbound Message Routing - -#### 5.1 Create Message Provider (NEW file) -**`src/twitch/monitor/provider.ts`** - -This is the CRITICAL piece that was missing. Follow Discord's pattern: -```typescript -// Reference: src/discord/monitor/provider.ts -import { twitchClientManager } from "../twitch-client.js"; -import { createTwitchMessageHandler } from "./message-handler.js"; - -export async function monitorTwitchChannel(params: { - cfg: ClawdbotConfig; - accountId: string; - account: TwitchAccountConfig; - runtime: RuntimeEnv; - abortSignal: AbortSignal; - log?: ChannelLogSink; -}) { - const clientManager = new TwitchClientManager({ - info: (msg) => params.log?.info(`[twitch] ${msg}`), - warn: (msg) => params.log?.warn(`[twitch] ${msg}`), - error: (msg) => params.log?.error(`[twitch] ${msg}`), - }); - - // Create message handler that calls dispatchInboundMessage - const messageHandler = createTwitchMessageHandler({ - cfg: params.cfg, - accountId: params.accountId, - account: params.account, - runtime: params.runtime, - log: params.log, - }); - - // Set up message listener - clientManager.onMessage(params.account, async (message) => { - await messageHandler(message); - }); - - await clientManager.getClient(params.account); - return { - close: async () => { - await clientManager.close(); - }, - }; -} -``` - -#### 5.2 Create Message Handler (NEW file) -**`src/twitch/monitor/message-handler.ts`** - -```typescript -import { dispatchInboundMessage } from "../../auto-reply/dispatch.js"; -import { createReplyDispatcher } from "../../auto-reply/reply/reply-dispatcher.js"; -import type { MsgContext } from "../../auto-reply/templating.js"; -import { resolveAgentRoute } from "../../routing/resolve-route.js"; -import type { TwitchChatMessage } from "../types.js"; - -export async function createTwitchMessageHandler(params: { - cfg: ClawdbotConfig; - accountId: string; - account: TwitchAccountConfig; - runtime: RuntimeEnv; - log?: ChannelLogSink; -}) { - return async (message: TwitchChatMessage) => { - // 1. Resolve route - const route = resolveAgentRoute({ - cfg: params.cfg, - channel: "twitch", - accountId: params.accountId, - peer: { - kind: "group", // Twitch chat is always group-like - id: message.channel, - }, - }); - - // 2. Build MsgContext - const ctx: MsgContext = { - Body: message.message, - RawBody: message.message, - CommandBody: message.message, - From: `twitch:${message.userId ?? message.username}`, - To: route.sessionKey, - SessionKey: route.sessionKey, - AccountId: params.accountId, - ChatType: "group", - SenderName: message.displayName ?? message.username, - SenderId: message.userId ?? message.username, - SenderUsername: message.username, - Provider: "twitch" as const, - Surface: "twitch" as const, - WasMentioned: message.message.includes(params.account.username), // crude check - MessageSid: message.id ?? `${Date.now()}`, - Timestamp: message.timestamp?.getTime() ?? Date.now(), - CommandAuthorized: true, // TODO: access control check - CommandSource: "text" as const, - OriginatingChannel: "twitch" as const, - OriginatingTo: message.channel, - }; - - // 3. Create dispatcher for replies - const dispatcher = createReplyDispatcher({ - responsePrefix: "", // TODO: from config - onError: (err) => { - params.log?.error(`[twitch] dispatch failed: ${String(err)}`); - }, - deliver: async (payload) => { - // Send reply via Twitch client - // TODO: implement sendTwitchReply - params.log?.info(`[twitch] Reply: ${payload.text?.slice(0, 50)}...`); - }, - }); - - // 4. Dispatch to agent - await dispatchInboundMessage({ - ctx, - cfg: params.cfg, - dispatcher, - replyOptions: { - disableBlockStreaming: true, // Twitch doesn't support streaming - }, - }); - }; -} -``` - -#### 5.3 Update `plugin.ts` → `channel.ts` -Rename and refactor to use the new monitor: -```typescript -// src/twitch/channel.ts (was plugin.ts) -export const twitchPlugin: ChannelPlugin = { - id: "twitch", // Use simple ID like core channels - meta: { - id: "twitch", - label: "Twitch", - // ... - }, - // ... adapters (config, outbound, actions, resolver, status) - - // GATEWAY - Use new monitor - gateway: { - startAccount: async (ctx) => { - const { monitorTwitchChannel } = await import("./monitor/provider.js"); - const monitor = await monitorTwitchChannel({ - cfg: ctx.cfg, - accountId: ctx.accountId, - account: ctx.account as TwitchAccountConfig, - runtime: ctx.runtime, - abortSignal: ctx.abortSignal, - log: ctx.log, - }); - // Store monitor for cleanup - ctx.setStatus({ running: true, lastStartAt: Date.now() }); - }, - stopAccount: async (ctx) => { - // Cleanup monitor - ctx.setStatus({ running: false, lastStopAt: Date.now() }); - }, - }, -}; -``` - ---- - -### Phase 6: Plugin Wrapper Removal - -#### 6.1 Delete `index.ts` (plugin wrapper) -The `index.ts` file contains plugin wrapper code that's not needed for core: -```typescript -// DELETE THIS FILE: -const plugin = { - id: "clawdbot-twitch", - name: "Twitch", - configSchema: emptyPluginConfigSchema(), - register(api) { /* ... */ }, -}; -export default plugin; -``` - -#### 6.2 Remove Plugin Config Schema -Delete `emptyPluginConfigSchema()` and all plugin config schema code. Core channels don't use this. - ---- - -### Phase 7: Dependencies & Package Updates - -#### 7.1 Review Dependencies in `package.json` - -**Current plugin dependencies:** -```json -{ - "dependencies": { - "@twurple/cli": "^7.1.0", - "@twurple/auth": "^7.1.0", - "@twurple/chat": "^7.1.0", - "@twurple/common": "^7.1.0" - } -} -``` - -**Action:** Add these to Clawdbot core's `package.json`: -```json -{ - "dependencies": { - "@twurple/cli": "^7.1.0", - "@twurple/auth": "^7.1.0", - "@twurple/chat": "^7.1.0", - "@twurple/common": "^7.1.0" - } -} -``` - -#### 7.2 Remove Plugin-Specific Files -``` -DELETE: -- src/index.ts (plugin wrapper) -- src/plugin-sdk.ts (duplicated types) -- clawdbot.plugin.json (not needed for core) -``` - ---- - -### Phase 8: Testing & Validation - -#### 8.1 Build Verification -```bash -cd opensrc/repos/github.com/clawdbot/clawdbot -pnpm build -``` - -#### 8.2 Type Verification -```bash -pnpm run type-check # or tsc --noEmit -``` - -#### 8.3 Lint Verification -```bash -pnpm lint -``` - -#### 8.4 Test Verification -```bash -pnpm test -- src/twitch -``` - -#### 8.5 Integration Testing -1. Update user config to use core Twitch channel: - ```json - { - "channels": { - "twitch": { - "enabled": true, - "accounts": { - "default": { - "username": "clawdbot", - "token": "...", - "clientId": "...", - "channel": "jaydencarey", - "requireMention": true - } - } - } - } - } - ``` - -2. Start gateway: - ```bash - clawdbot gateway run - ``` - -3. Send test message in Twitch chat: - ``` - @clawdbot hello - ``` - -4. Verify: - - Gateway connects to Twitch - - Messages are received - - Messages are dispatched to agent - - Agent responds - - Response is sent back to Twitch chat - ---- - -### Phase 9: Documentation - -#### 9.1 Add Channel Documentation -Create `docs/channels/twitch.md` following the pattern of other channels: -```markdown -# Twitch - -## Setup - -1. Create a Twitch application at [Twitch Dev Portal](https://dev.twitch.tv/console) -2. Get your Client ID and generate a token -3. Configure Clawdbot: - -\`\`\`bash -clawdbot channels login --channel twitch -\`\`\` - -## Configuration - -\`\`\`json -{ - "channels": { - "twitch": { - "accounts": { - "default": { - "username": "yourbot", - "token": "...", - "clientId": "...", - "channel": "yourchannel", - "requireMention": true - } - } - } - } -} -\`\`\` - -## Features - -- **Chat Types:** Group (channel chat) -- **Mentions:** Require @mention by default -- **Roles:** Support for mods, VIPs, subs -- **Access Control:** Allowlist by user ID or role -``` - -#### 9.2 Update Main README -Add Twitch to the list of supported channels. - -#### 9.3 Update Channel Selection Docs -Update any documentation that lists available channels. - ---- - -## Summary of Key Changes - -### Files Deleted -- `src/index.ts` - Plugin wrapper -- `src/plugin-sdk.ts` - Duplicated types -- `clawdbot.plugin.json` - Plugin manifest - -### Files Created (Core) -- `src/twitch/monitor/provider.ts` - Inbound message provider -- `src/twitch/monitor/message-handler.ts` - Message processing with dispatchInboundMessage -- `src/twitch/monitor/reply-delivery.ts` - Reply delivery -- `src/twitch/monitor/format.ts` - Message formatting -- `src/twitch/monitor/loggers.ts` - Twitch loggers - -### Files Modified -- `src/twitch/types.ts` - Remove duplicated types, keep only Twitch-specific -- `src/twitch/plugin.ts` → `src/twitch/channel.ts` - Rename and refactor -- All other `.ts` files - Update imports to use core paths -- `src/channels/registry.ts` - Add "twitch" to channel list -- `package.json` - Add @twurple dependencies - -### Critical Change: Inbound Routing -**Before (plugin):** -```typescript -// Only logged messages -console.info(`[twitch] Received message: ${message.message}`); -``` - -**After (core):** -```typescript -// Actually routes messages to agent -await dispatchInboundMessage({ ctx, cfg, dispatcher, replyOptions }); -``` - ---- - -## Checklist - -### Pre-Migration -- [ ] Ensure Clawdbot core is at latest commit -- [ ] Create a new branch for the migration -- [ ] Document any Twitch plugin-specific features that need special handling - -### Migration -- [ ] Create `src/twitch` directory structure -- [ ] Move and adapt files from plugin to core -- [ ] Remove `plugin-sdk.ts` (all duplicates) -- [ ] Refactor `types.ts` to only contain Twitch-specific types -- [ ] Update all imports across files -- [ ] Delete `src/index.ts` (plugin wrapper) -- [ ] Rename `plugin.ts` → `channel.ts` and refactor -- [ ] Create `monitor/provider.ts` with inbound routing -- [ ] Create `monitor/message-handler.ts` with dispatchInboundMessage -- [ ] Update `src/channels/registry.ts` to add "twitch" -- [ ] Update `package.json` with @twurple dependencies -- [ ] Move tests to core and update imports - -### Post-Migration -- [ ] Run `pnpm install` to install new dependencies -- [ ] Run `pnpm build` and fix any build errors -- [ ] Run `pnpm lint` and fix any lint errors -- [ ] Run `pnpm test` and fix any test failures -- [ ] Test Twitch connection manually -- [ ] Test message routing with a real Twitch chat -- [ ] Add/update documentation -- [ ] Update user config to use core Twitch channel -- [ ] Clean up old plugin directory -- [ ] Submit PR to Clawdbot repo - ---- - -## Estimated Effort - -- **Type deduping:** 2-3 hours -- **File migration & import updates:** 3-4 hours -- **Inbound routing implementation:** 4-5 hours (critical path) -- **Testing & debugging:** 3-4 hours -- **Documentation:** 1-2 hours - -**Total:** ~13-18 hours - ---- - -## References - -- Discord channel implementation: `opensrc/repos/github.com/clawdbot/clawdbot/src/discord/` -- Core channel types: `opensrc/repos/github.com/clawdbot/clawdbot/src/channels/plugins/types.core.ts` -- Channel registry: `opensrc/repos/github.com/clawdbot/clawdbot/src/channels/registry.ts` -- Message dispatch: `opensrc/repos/github.com/clawdbot/clawdbot/src/auto-reply/dispatch.ts`