---
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)"
---

<objective>
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.
</objective>

<execution_context>
@/home/will/.config/opencode/get-shit-done/workflows/execute-plan.md
@/home/will/.config/opencode/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md

@src/daemon/index.ts
@src/daemon/routing.test.ts
</context>

<tasks>

<task type="auto">
  <name>Task 1: Extract message routing into src/daemon/routing.ts</name>
  <files>src/daemon/routing.ts, src/daemon/index.ts</files>
  <action>
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.
  </action>
  <verify>
Run `pnpm test:run src/daemon/routing.test.ts` — passes (doesn't import createMessageRouter).
Run `pnpm typecheck` — no type errors.
  </verify>
  <done>
- `src/daemon/routing.ts` exists with `createMessageRouter` exported
- createMessageRouter removed from daemon/index.ts
- routing.test.ts passes without changes
- No type errors
  </done>
</task>

<task type="auto">
  <name>Task 2: Extract agent config and sandbox setup into src/daemon/agents.ts</name>
  <files>src/daemon/agents.ts, src/daemon/index.ts</files>
  <action>
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<AgentsResult>
```

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';`
  </action>
  <verify>
Run `pnpm test:run src/daemon/routing.test.ts` — passes (tests AgentRouter from ../agents/).
Run `pnpm typecheck` — no type errors.
  </verify>
  <done>
- `src/daemon/agents.ts` exists with `initAgents` exported
- Agent config, router, and sandbox logic removed from `startDaemon()`
- All tests pass
- No type errors
  </done>
</task>

<task type="auto">
  <name>Task 3: Extract channel adapter registration into src/daemon/channels.ts</name>
  <files>src/daemon/channels.ts, src/daemon/index.ts</files>
  <action>
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.
  </action>
  <verify>
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.
  </verify>
  <done>
- `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
  </done>
</task>

</tasks>

<verification>
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
</verification>

<success_criteria>
- 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
</success_criteria>

<output>
After completion, create `.planning/phases/01-daemon-decomposition/01-02-SUMMARY.md`
</output>
