From b6b85f07d05d2467c4a32b5112dced100f4d0a7b Mon Sep 17 00:00:00 2001 From: William Valentin Date: Mon, 2 Feb 2026 21:02:50 -0800 Subject: [PATCH] feat: wire daemon, agent, and telegram bot together Co-Authored-By: Claude Opus 4.5 --- src/daemon/index.ts | 42 +++++++++++++++++++++++++++++++++++++++++- src/index.ts | 10 ++++++++-- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/daemon/index.ts b/src/daemon/index.ts index 8575972..3b3a264 100644 --- a/src/daemon/index.ts +++ b/src/daemon/index.ts @@ -1,14 +1,41 @@ +import { Bot } from 'grammy'; import { Lifecycle } from './lifecycle.js'; import type { Config } from '../config/index.js'; +import { AnthropicClient } from '../models/index.js'; +import { NativeAgent } from '../backends/index.js'; +import { createTelegramBot } from '../frontends/telegram/index.js'; export interface DaemonContext { config: Config; lifecycle: Lifecycle; + bot: Bot; + agent: NativeAgent; } +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.`; + export async function startDaemon(config: Config): Promise { const lifecycle = new Lifecycle(); + // Initialize model client + const modelClient = new AnthropicClient({ + model: config.models.default.model, + }); + + // Initialize native agent + const agent = new NativeAgent({ + modelClient, + systemPrompt: SYSTEM_PROMPT, + }); + + // Initialize Telegram bot + const bot = createTelegramBot({ + telegram: config.telegram, + agent, + }); + // Register signal handlers const signalHandler = () => { lifecycle.shutdown().then(() => process.exit(0)); @@ -22,9 +49,22 @@ export async function startDaemon(config: Config): Promise { process.off('SIGTERM', signalHandler); }); + // Start bot + lifecycle.onShutdown(async () => { + await bot.stop(); + console.log('Telegram bot stopped'); + }); + + // Use long polling (no webhook, no internet exposure) + bot.start({ + onStart: (botInfo) => { + console.log(`Telegram bot started: @${botInfo.username}`); + }, + }); + console.log('Flynn daemon started'); - return { config, lifecycle }; + return { config, lifecycle, bot, agent }; } export { Lifecycle } from './lifecycle.js'; diff --git a/src/index.ts b/src/index.ts index 91df62e..a551cc8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import { loadConfig } from './config/index.js'; import { startDaemon } from './daemon/index.js'; import { resolve } from 'path'; import { homedir } from 'os'; +import { existsSync } from 'fs'; const CONFIG_PATH = process.env.FLYNN_CONFIG ?? resolve(homedir(), '.config/flynn/config.yaml'); @@ -10,12 +11,17 @@ async function main() { console.log('Flynn starting...'); console.log(`Loading config from: ${CONFIG_PATH}`); + if (!existsSync(CONFIG_PATH)) { + console.error(`Config file not found: ${CONFIG_PATH}`); + console.error('Copy config/default.yaml to ~/.config/flynn/config.yaml and configure it.'); + process.exit(1); + } + try { const config = loadConfig(CONFIG_PATH); const daemon = await startDaemon(config); - console.log(`Telegram bot configured for chat IDs: ${config.telegram.allowed_chat_ids.join(', ')}`); - console.log(`Server port: ${config.server.port}`); + console.log(`Allowed Telegram chat IDs: ${config.telegram.allowed_chat_ids.join(', ')}`); // Keep process alive await new Promise((resolve) => {