--- phase: 01-daemon-decomposition plan: 02 type: execute wave: 1 depends_on: [] files_modified: - src/daemon/channels.ts - src/daemon/agents.ts - src/daemon/routing.ts - src/daemon/index.ts autonomous: true must_haves: truths: - "Channel adapter registration (Telegram, Discord, Slack, WhatsApp, WebChat, Cron, Webhooks, Gmail) works identically when imported from src/daemon/channels.ts" - "Agent cache and factory logic (getOrCreateAgent, sandbox wiring, attachment collector) works identically when imported from src/daemon/routing.ts" - "Agent config registry and router initialization works identically when imported from src/daemon/agents.ts" - "All 1077+ existing tests pass with zero regressions" - "routing.test.ts continues to pass without modification" artifacts: - path: "src/daemon/channels.ts" provides: "Channel adapter registration for all adapters plus automation (cron, webhooks, Gmail)" exports: ["registerChannels"] min_lines: 80 - path: "src/daemon/agents.ts" provides: "Agent config registry loading, agent router creation, sandbox manager init" exports: ["initAgents"] min_lines: 30 - path: "src/daemon/routing.ts" provides: "Message router with agent cache, sandbox wiring, audio transcription, command handling" exports: ["createMessageRouter"] min_lines: 180 key_links: - from: "src/daemon/index.ts" to: "src/daemon/channels.ts" via: "import and call registerChannels" pattern: "import.*from.*['\"]\\./channels\\.js['\"]" - from: "src/daemon/index.ts" to: "src/daemon/agents.ts" via: "import and call initAgents" pattern: "import.*from.*['\"]\\./agents\\.js['\"]" - from: "src/daemon/index.ts" to: "src/daemon/routing.ts" via: "import and call createMessageRouter" pattern: "import.*from.*['\"]\\./routing\\.js['\"]" - from: "src/daemon/routing.test.ts" to: "src/daemon/routing.ts" via: "tests use AgentRouter and AgentConfigRegistry from agents module" pattern: "import.*from.*['\"]\\.\\./(agents|daemon)" --- Extract channel adapter setup, agent configuration, and message routing from daemon/index.ts into dedicated modules. Purpose: These three extractions handle the remaining business logic in daemon/index.ts — channel wiring (~100 lines), agent setup (~40 lines), and message routing (~220 lines). After this plan, daemon/index.ts contains only infrastructure wiring. Output: Three new files (channels.ts, agents.ts, routing.ts) that encapsulate channel, agent, and routing concerns. @/home/will/.config/opencode/get-shit-done/workflows/execute-plan.md @/home/will/.config/opencode/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @src/daemon/index.ts @src/daemon/routing.test.ts Task 1: Extract message routing into src/daemon/routing.ts src/daemon/routing.ts, src/daemon/index.ts Create `src/daemon/routing.ts` by moving the `createMessageRouter` function (lines 325-540) verbatim from `daemon/index.ts`. This is a direct function move — the function signature, parameter types, return type, and body are unchanged. Move the entire function including: - The `deps` parameter interface (inline or named) - The `agents` Map - The `getOrCreateAgent` inner function (agent cache, sandbox wiring, media send tool, orchestrator creation) - The `handler` async function (command handling: reset, compact, usage; audio transcription; agent.process; error handling) - The return `{ handler, agents }` Move necessary imports to `routing.ts`: - `import type { AudioTranscriptionConfig } from '../models/media.js';` - `import type { Attachment } from '../channels/types.js';` - `import { isSupportedAudio, transcribeAudio } from '../models/media.js';` - `import { AgentOrchestrator, type DelegationConfig } from '../backends/index.js';` - `import { OutboundAttachmentCollector } from '../backends/native/attachments.js';` - `import type { InboundMessage, OutboundMessage } from '../channels/index.js';` - `import { MemoryStore } from '../memory/index.js';` - `import type { Tool } from '../tools/types.js';` - `import { createMediaSendTool } from '../tools/index.js';` - `import { createSandboxedShellTool, createSandboxedProcessStartTool, SandboxManager } from '../sandbox/index.js';` - `import type { Config } from '../config/index.js';` - `import { ModelRouter } from '../models/index.js';` - `import { ToolRegistry, ToolExecutor } from '../tools/index.js';` - `import { SessionManager } from '../session/index.js';` - `import { AgentConfigRegistry, AgentRouter } from '../agents/index.js';` In `daemon/index.ts`: - Remove the `createMessageRouter` function (lines 318-540) - Remove imports that only served that function - Add `import { createMessageRouter } from './routing.js';` IMPORTANT: The `createMessageRouter` function is NOT part of the public API (it's not exported from daemon/index.ts currently — it's a private `function`, not `export function`). So no re-export needed. Note: `routing.test.ts` tests `AgentRouter` and `AgentConfigRegistry` from `../agents/` — it does NOT import `createMessageRouter` from `./index.js`. So this move doesn't affect that test file. Run `pnpm test:run src/daemon/routing.test.ts` — passes (doesn't import createMessageRouter). Run `pnpm typecheck` — no type errors. - `src/daemon/routing.ts` exists with `createMessageRouter` exported - createMessageRouter removed from daemon/index.ts - routing.test.ts passes without changes - No type errors Task 2: Extract agent config and sandbox setup into src/daemon/agents.ts src/daemon/agents.ts, src/daemon/index.ts Create `src/daemon/agents.ts` with a factory function that encapsulates agent config registry loading, agent router creation, and sandbox manager initialization. Extract from `startDaemon()` lines 749-774 (agent config registry, agent router, sandbox manager) into: ```typescript import type { Config } from '../config/index.js'; import { AgentConfigRegistry, AgentRouter } from '../agents/index.js'; import { DockerSandbox, SandboxManager } from '../sandbox/index.js'; import type { Lifecycle } from './lifecycle.js'; export interface AgentsDeps { config: Config; lifecycle: Lifecycle; } export interface AgentsResult { agentConfigRegistry: AgentConfigRegistry; agentRouter: AgentRouter; sandboxManager?: SandboxManager; } export async function initAgents(deps: AgentsDeps): Promise ``` The function body contains the exact logic from lines 749-774: - Create AgentConfigRegistry, load from config if present - Create AgentRouter from config.routing - Check DockerSandbox.isAvailable(), create SandboxManager if enabled - Register lifecycle shutdown for sandbox cleanup - Return all three In `daemon/index.ts`: - Replace lines 749-774 with: `const { agentConfigRegistry, agentRouter, sandboxManager } = await initAgents({ config, lifecycle });` - Remove imports that moved (AgentConfigRegistry, AgentRouter, DockerSandbox, SandboxManager — but keep SandboxManager type if needed for DaemonContext) - Add `import { initAgents } from './agents.js';` Run `pnpm test:run src/daemon/routing.test.ts` — passes (tests AgentRouter from ../agents/). Run `pnpm typecheck` — no type errors. - `src/daemon/agents.ts` exists with `initAgents` exported - Agent config, router, and sandbox logic removed from `startDaemon()` - All tests pass - No type errors Task 3: Extract channel adapter registration into src/daemon/channels.ts src/daemon/channels.ts, src/daemon/index.ts Create `src/daemon/channels.ts` with a factory function that encapsulates all channel adapter creation and registration. Extract from `startDaemon()` lines 898-973 (Telegram, Discord, Slack, WhatsApp, WebChat adapters, cron scheduler, webhook handler, Gmail watcher) into: ```typescript import type { Config } from '../config/index.js'; import type { HookEngine } from '../hooks/index.js'; import type { PairingManager, ChannelRegistry } from '../channels/index.js'; import { TelegramAdapter, WebChatAdapter, DiscordAdapter, SlackAdapter, WhatsAppAdapter } from '../channels/index.js'; import { CronScheduler, WebhookHandler, GmailWatcher } from '../automation/index.js'; import type { GatewayServer } from '../gateway/index.js'; export interface ChannelsDeps { config: Config; channelRegistry: ChannelRegistry; hookEngine: HookEngine; pairingManager?: PairingManager; gateway: GatewayServer; } export interface ChannelsResult { cronScheduler?: CronScheduler; webhookHandler?: WebhookHandler; gmailWatcher?: GmailWatcher; } export function registerChannels(deps: ChannelsDeps): ChannelsResult ``` The function body contains the exact logic from lines 898-973: - Create and register TelegramAdapter (always) - Create and register DiscordAdapter (if config.discord) - Create and register SlackAdapter (if config.slack) - Create and register WhatsAppAdapter (if config.whatsapp) - Create and register WebChatAdapter (wraps gateway) - Create and register CronScheduler (if cron jobs configured) - Create and register WebhookHandler (if webhooks configured), set on gateway - Create and register GmailWatcher (if configured), set on gateway - Return { cronScheduler, webhookHandler, gmailWatcher } In `daemon/index.ts`: - Replace lines 898-973 with: `const { cronScheduler, webhookHandler, gmailWatcher } = registerChannels({ config, channelRegistry, hookEngine, pairingManager, gateway });` - Remove the adapter imports that moved (TelegramAdapter, DiscordAdapter, SlackAdapter, WhatsAppAdapter, WebChatAdapter, CronScheduler, WebhookHandler, GmailWatcher) - Add `import { registerChannels } from './channels.js';` Note: `gmailWatcher` and `webhookHandler` variables may still be referenced below (e.g., no further usage after registration). Check and remove any dead references. The `cronScheduler` is still needed in startDaemon for the Tier 1 cron tools registration (lines 989-993). So the return value is important. Run `pnpm test:run` — all tests pass. Run `pnpm typecheck` — no type errors. Count lines in daemon/index.ts — should be ~300-350 lines shorter than after Plan 01 completed. - `src/daemon/channels.ts` exists with `registerChannels` exported - All channel adapter registration logic removed from `startDaemon()` - cronScheduler returned so Tier 1 cron tools can still be registered - All tests pass - No type errors Before declaring plan complete: - [ ] `pnpm typecheck` passes with zero errors - [ ] `pnpm test:run` passes all 1077+ tests - [ ] `pnpm test:run src/daemon/routing.test.ts` passes (agent routing tests) - [ ] `src/daemon/routing.ts` exports: createMessageRouter - [ ] `src/daemon/agents.ts` exports: initAgents - [ ] `src/daemon/channels.ts` exports: registerChannels - [ ] daemon/index.ts no longer contains createMessageRouter function body - [ ] daemon/index.ts no longer contains individual adapter creation code - All tasks completed - All verification checks pass - No errors or warnings introduced - Each new module file can be read and understood in isolation - Channel adapter additions would only require editing channels.ts - Agent config additions would only require editing agents.ts After completion, create `.planning/phases/01-daemon-decomposition/01-02-SUMMARY.md`