Files
flynn/.planning/phases/01-daemon-decomposition/01-02-PLAN.md
T

12 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
phase plan type wave depends_on files_modified autonomous must_haves
01-daemon-decomposition 02 execute 1
src/daemon/channels.ts
src/daemon/agents.ts
src/daemon/routing.ts
src/daemon/index.ts
true
truths artifacts key_links
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
path provides exports min_lines
src/daemon/channels.ts Channel adapter registration for all adapters plus automation (cron, webhooks, Gmail)
registerChannels
80
path provides exports min_lines
src/daemon/agents.ts Agent config registry loading, agent router creation, sandbox manager init
initAgents
30
path provides exports min_lines
src/daemon/routing.ts Message router with agent cache, sandbox wiring, audio transcription, command handling
createMessageRouter
180
from to via pattern
src/daemon/index.ts src/daemon/channels.ts import and call registerChannels import.*from.*['"]./channels.js['"]
from to via pattern
src/daemon/index.ts src/daemon/agents.ts import and call initAgents import.*from.*['"]./agents.js['"]
from to via pattern
src/daemon/index.ts src/daemon/routing.ts import and call createMessageRouter import.*from.*['"]./routing.js['"]
from to via pattern
src/daemon/routing.test.ts src/daemon/routing.ts tests use AgentRouter and AgentConfigRegistry from agents module 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.

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

@.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:

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'; 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:

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

<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>
After completion, create `.planning/phases/01-daemon-decomposition/01-02-SUMMARY.md`