// ── External ── import { resolve } from 'path'; import { homedir } from 'os'; import { mkdirSync } from 'fs'; // ── Config & Types ── import type { Config } from '../config/index.js'; import type { ToolRegistry, ToolExecutor, BrowserManager } from '../tools/index.js'; import type { AgentConfigRegistry, AgentRouter } from '../agents/index.js'; import type { SandboxManager } from '../sandbox/index.js'; import { setLogLevel } from '../logger.js'; // ── Daemon Modules ── import { Lifecycle } from './lifecycle.js'; import { createModelRouter } from './models.js'; import { initMemory } from './memory.js'; import { initTools } from './tools.js'; import { initAgents } from './agents.js'; import { createMessageRouter } from './routing.js'; import { registerChannels } from './channels.js'; import { initSkills, initMcp, loadSystemPrompt, initPairingManager, createGateway, startServices } from './services.js'; import { CommandRegistry, registerBuiltinCommands } from '../commands/index.js'; import { ComponentRegistry } from '../intents/index.js'; import { RoutingPolicy } from '../routing/index.js'; // ── Infrastructure ── import type { ModelRouter } from '../models/index.js'; import { SessionStore, SessionManager, parseDuration } from '../session/index.js'; import { HookEngine } from '../hooks/index.js'; import { createSessionTools, createAgentsListTool, createMessageSendTool, createCronTools, createGmailTools, createGcalTools, createGdocsTools, createGdriveTools, createGtasksTools, createMinioShareTool } from '../tools/index.js'; import { ChannelRegistry } from '../channels/index.js'; import type { McpManager } from '../mcp/index.js'; import type { SkillRegistry, SkillInstaller } from '../skills/index.js'; import type { GatewayServer } from '../gateway/index.js'; import { AuditLogger, initAuditLogger } from '../audit/index.js'; import { BackupScheduler } from '../backup/index.js'; export interface DaemonContext { config: Config; lifecycle: Lifecycle; sessionStore: SessionStore; sessionManager: SessionManager; hookEngine: HookEngine; modelRouter: ModelRouter; toolRegistry: ToolRegistry; toolExecutor: ToolExecutor; gateway: GatewayServer; channelRegistry: ChannelRegistry; mcpManager: McpManager; skillRegistry: SkillRegistry; skillInstaller: SkillInstaller; agentConfigRegistry: AgentConfigRegistry; agentRouter: AgentRouter; sandboxManager?: SandboxManager; browserManager?: BrowserManager; } export interface StartDaemonOptions { configPath?: string; } export async function startDaemon(config: Config, options?: StartDaemonOptions): Promise { // ── Log level ── setLogLevel(config.log_level); const lifecycle = new Lifecycle(); // ── Data & Sessions ── const dataDir = process.env.FLYNN_DATA_DIR ?? resolve(homedir(), '.local/share/flynn'); mkdirSync(dataDir, { recursive: true }); // ── Audit Logger ── const auditLoggerInstance = new AuditLogger(config.audit); initAuditLogger(auditLoggerInstance); lifecycle.onShutdown(async () => { await auditLoggerInstance.close(); console.log('Audit logger closed'); }); const sessionStore = new SessionStore(resolve(dataDir, 'sessions.db')); const sessionManager = new SessionManager(sessionStore, { enabled: config.history_index.enabled, maxKeywords: config.history_index.max_keywords, searchLimit: config.history_index.search_limit, minScore: config.history_index.min_score, }); lifecycle.onShutdown(async () => { sessionStore.close(); console.log('Session store closed'); }); const ttlMs = parseDuration(config.sessions?.ttl ?? '30d'); if (ttlMs) { const pruneInterval = setInterval(() => { const cutoff = Math.floor((Date.now() - ttlMs) / 1000); const pruned = sessionStore.pruneStale(cutoff); if (pruned.length > 0) { sessionManager.evictSessions(pruned); console.log(`Pruned ${pruned.length} stale session(s) (TTL: ${config.sessions?.ttl ?? '30d'})`); } }, 3_600_000); lifecycle.onShutdown(async () => { clearInterval(pruneInterval); }); } // ── Core Services ── const hookEngine = new HookEngine(config.hooks); const { toolRegistry, toolExecutor, browserManager } = initTools({ config, lifecycle, hookEngine }); toolExecutor.setExecutionObserver((event) => { if (!event.sessionId) { return; } sessionStore.recordToolExecution(event.sessionId, event.toolName, event.success, event.timestampSeconds); }); const { memoryStore, memoryDir } = await initMemory({ config, dataDir, lifecycle, toolRegistry }); const mcpManager = await initMcp(config, lifecycle, toolRegistry); const { skillRegistry, skillInstaller } = initSkills(config, lifecycle); const { agentConfigRegistry, agentRouter, sandboxManager } = await initAgents({ config, lifecycle }); // Ensure ToolExecutor can enforce sandbox execution at runtime. toolExecutor.setSandboxManager(sandboxManager); const modelRouter = createModelRouter(config); const commandRegistry = new CommandRegistry(); registerBuiltinCommands(commandRegistry); const intentRegistry = new ComponentRegistry({ matchThreshold: config.intents.match_threshold, }); if (config.intents.enabled) { intentRegistry.loadRules(config.intents.rules); } const routingPolicy = new RoutingPolicy({ enabled: config.routing_policy.enabled, fastPathThreshold: config.routing_policy.fast_path_threshold, llmThreshold: config.routing_policy.llm_threshold, defaultPath: config.routing_policy.default_path, }); // Restore persisted model tier const { loadPreferences, savePreference } = await import('../preferences.js'); const prefs = loadPreferences(dataDir); if (prefs.modelTier) { modelRouter.setTier(prefs.modelTier as import('../models/router.js').ModelTier); } modelRouter.setOnTierChange((tier) => savePreference(dataDir, 'modelTier', tier)); const systemPrompt = loadSystemPrompt(config, skillRegistry); // ── Gateway & Channels ── const channelRegistry = new ChannelRegistry(); const pairingStore = config.pairing.enabled ? sessionStore.getPairingStore() : undefined; const pairingManager = initPairingManager(config, pairingStore); let channelAgents: ReturnType['agents'] | null = null; const gateway = createGateway({ config, configPath: options?.configPath, sessionManager, modelRouter, systemPrompt, toolRegistry, toolExecutor, channelRegistry, pairingManager, lifecycle, memoryStore, getChannelAgents: () => channelAgents, commandRegistry, intentRegistry, routingPolicy, }); const messageRouter = createMessageRouter({ sessionManager, modelRouter, systemPrompt, toolRegistry, toolExecutor, config, memoryStore, agentConfigRegistry, agentRouter, sandboxManager, commandRegistry, intentRegistry, routingPolicy, skillRegistry, }); channelRegistry.setMessageHandler(messageRouter.handler); channelAgents = messageRouter.agents; const { cronScheduler } = registerChannels({ config, channelRegistry, hookEngine, pairingManager, gateway }); // ── Tier 1 Tools ── for (const tool of createSessionTools(sessionManager)) { toolRegistry.register(tool); } toolRegistry.register(createAgentsListTool(agentConfigRegistry)); toolRegistry.register(createMessageSendTool(channelRegistry)); if (cronScheduler) { for (const tool of createCronTools(cronScheduler)) { toolRegistry.register(tool); } } if (config.automation.gmail?.enabled) { for (const tool of createGmailTools(config.automation.gmail)) { toolRegistry.register(tool); } } if (config.automation.gcal?.enabled) { for (const tool of createGcalTools(config.automation.gcal)) { toolRegistry.register(tool); } } if (config.automation.gdocs?.enabled) { for (const tool of createGdocsTools(config.automation.gdocs)) { toolRegistry.register(tool); } } if (config.automation.gdrive?.enabled) { for (const tool of createGdriveTools(config.automation.gdrive)) { toolRegistry.register(tool); } } if (config.automation.gtasks?.enabled) { for (const tool of createGtasksTools(config.automation.gtasks)) { toolRegistry.register(tool); } } if (config.backup.minio.enabled) { toolRegistry.register(createMinioShareTool(config.backup)); } // ── Lifecycle ── await startServices({ config, lifecycle, channelRegistry, gateway, modelRouter, memoryDir, dataDir }); const backupScheduler = new BackupScheduler({ dataDir, backupConfig: config.backup, channelLookup: channelRegistry, }); backupScheduler.start(); lifecycle.onShutdown(async () => { backupScheduler.stop(); }); return { config, lifecycle, sessionStore, sessionManager, hookEngine, modelRouter, toolRegistry, toolExecutor, gateway, channelRegistry, mcpManager, skillRegistry, skillInstaller, agentConfigRegistry, agentRouter, sandboxManager, browserManager, }; } // ── Re-exports for backward compatibility ── export { Lifecycle } from './lifecycle.js'; export { createClientFromConfig, anthropicToGitHubModel, createAutoFallbackClient, createModelRouter } from './models.js';