feat: wire daemon, agent, and telegram bot together

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
William Valentin
2026-02-02 21:02:50 -08:00
parent d0fe4c5b35
commit b6b85f07d0
2 changed files with 49 additions and 3 deletions
+41 -1
View File
@@ -1,14 +1,41 @@
import { Bot } from 'grammy';
import { Lifecycle } from './lifecycle.js'; import { Lifecycle } from './lifecycle.js';
import type { Config } from '../config/index.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 { export interface DaemonContext {
config: Config; config: Config;
lifecycle: Lifecycle; 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<DaemonContext> { export async function startDaemon(config: Config): Promise<DaemonContext> {
const lifecycle = new Lifecycle(); 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 // Register signal handlers
const signalHandler = () => { const signalHandler = () => {
lifecycle.shutdown().then(() => process.exit(0)); lifecycle.shutdown().then(() => process.exit(0));
@@ -22,9 +49,22 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
process.off('SIGTERM', signalHandler); 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'); console.log('Flynn daemon started');
return { config, lifecycle }; return { config, lifecycle, bot, agent };
} }
export { Lifecycle } from './lifecycle.js'; export { Lifecycle } from './lifecycle.js';
+8 -2
View File
@@ -2,6 +2,7 @@ import { loadConfig } from './config/index.js';
import { startDaemon } from './daemon/index.js'; import { startDaemon } from './daemon/index.js';
import { resolve } from 'path'; import { resolve } from 'path';
import { homedir } from 'os'; import { homedir } from 'os';
import { existsSync } from 'fs';
const CONFIG_PATH = process.env.FLYNN_CONFIG const CONFIG_PATH = process.env.FLYNN_CONFIG
?? resolve(homedir(), '.config/flynn/config.yaml'); ?? resolve(homedir(), '.config/flynn/config.yaml');
@@ -10,12 +11,17 @@ async function main() {
console.log('Flynn starting...'); console.log('Flynn starting...');
console.log(`Loading config from: ${CONFIG_PATH}`); 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 { try {
const config = loadConfig(CONFIG_PATH); const config = loadConfig(CONFIG_PATH);
const daemon = await startDaemon(config); const daemon = await startDaemon(config);
console.log(`Telegram bot configured for chat IDs: ${config.telegram.allowed_chat_ids.join(', ')}`); console.log(`Allowed Telegram chat IDs: ${config.telegram.allowed_chat_ids.join(', ')}`);
console.log(`Server port: ${config.server.port}`);
// Keep process alive // Keep process alive
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {