feat: integrate model router, session persistence, and hook engine

- NativeAgent now loads/saves messages to SessionStore
- Daemon creates ModelRouter with fallback chain support
- Telegram bot handles confirmation callbacks from HookEngine
- Session data stored in ~/.local/share/flynn/sessions.db

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
William Valentin
2026-02-05 00:05:42 -08:00
parent 26bd6ce65d
commit 6e6c263e14
3 changed files with 143 additions and 12 deletions
+80 -8
View File
@@ -1,39 +1,111 @@
import { Bot } from 'grammy';
import { Lifecycle } from './lifecycle.js';
import type { Config } from '../config/index.js';
import { AnthropicClient } from '../models/index.js';
import { AnthropicClient, OpenAIClient, OllamaClient, ModelRouter } from '../models/index.js';
import { NativeAgent } from '../backends/index.js';
import { createTelegramBot } from '../frontends/telegram/index.js';
import { SessionStore } from '../session/index.js';
import { HookEngine } from '../hooks/index.js';
import { resolve } from 'path';
import { homedir } from 'os';
import { mkdirSync } from 'fs';
export interface DaemonContext {
config: Config;
lifecycle: Lifecycle;
bot: Bot;
agent: NativeAgent;
sessionStore: SessionStore;
hookEngine: HookEngine;
modelRouter: ModelRouter;
}
const SYSTEM_PROMPT = `You are Flynn, a helpful personal AI assistant. You are direct, concise, and helpful. You can help with a variety of tasks including answering questions, providing information, and having conversations.
Keep responses focused and avoid unnecessary verbosity. Use markdown formatting when it improves readability.`;
function createModelRouter(config: Config): ModelRouter {
const models = config.models;
// Create default client (required)
const defaultClient = new AnthropicClient({
model: models.default.model,
});
// Create optional tier clients
let fastClient;
let complexClient;
let localClient;
if (models.fast) {
fastClient = new AnthropicClient({ model: models.fast.model });
}
if (models.complex) {
complexClient = new AnthropicClient({ model: models.complex.model });
}
if (models.local) {
if (models.local.provider === 'ollama') {
localClient = new OllamaClient({
model: models.local.model,
host: models.local.endpoint,
});
}
}
// Build fallback chain
const fallbackChain = [];
for (const providerName of models.fallback_chain) {
if (providerName === 'openai') {
fallbackChain.push(new OpenAIClient({ model: 'gpt-4o' }));
} else if (providerName === 'local' && localClient) {
fallbackChain.push(localClient);
}
}
return new ModelRouter({
default: defaultClient,
fast: fastClient,
complex: complexClient,
local: localClient,
fallbackChain,
});
}
export async function startDaemon(config: Config): Promise<DaemonContext> {
const lifecycle = new Lifecycle();
// Initialize model client
const modelClient = new AnthropicClient({
model: config.models.default.model,
// Ensure data directory exists
const dataDir = resolve(homedir(), '.local/share/flynn');
mkdirSync(dataDir, { recursive: true });
// Initialize session store
const sessionStore = new SessionStore(resolve(dataDir, 'sessions.db'));
lifecycle.onShutdown(async () => {
sessionStore.close();
console.log('Session store closed');
});
// Initialize native agent
// Initialize hook engine
const hookEngine = new HookEngine(config.hooks);
// Initialize model router
const modelRouter = createModelRouter(config);
// Initialize native agent with session persistence
const agent = new NativeAgent({
modelClient,
modelClient: modelRouter,
systemPrompt: SYSTEM_PROMPT,
sessionStore,
sessionId: `telegram-${config.telegram.allowed_chat_ids[0]}`,
});
// Initialize Telegram bot
// Initialize Telegram bot with hook engine
const bot = createTelegramBot({
telegram: config.telegram,
agent,
hookEngine,
});
// Register signal handlers
@@ -64,7 +136,7 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
console.log('Flynn daemon started');
return { config, lifecycle, bot, agent };
return { config, lifecycle, bot, agent, sessionStore, hookEngine, modelRouter };
}
export { Lifecycle } from './lifecycle.js';