215 lines
8.8 KiB
TypeScript
215 lines
8.8 KiB
TypeScript
// ── 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<DaemonContext> {
|
|
// ── 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 });
|
|
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<typeof createMessageRouter>['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';
|