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

273 lines
12 KiB
Markdown

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