diff --git a/src/services/SessionManager.ts b/src/services/SessionManager.ts deleted file mode 100644 index bb2b6b5..0000000 --- a/src/services/SessionManager.ts +++ /dev/null @@ -1,167 +0,0 @@ -/** - * Session Manager for Telegram conversations - * Handles per-chat session state and memory persistence - */ - -import type { - Session as AdkSession, - BaseSessionService, - MemoryService, -} from "@iqai/adk"; - -interface Message { - role: "user" | "assistant"; - content: string; - timestamp: string; -} - -interface Session { - chatId: string; - userId: string; - username?: string; - history: Message[]; - startedAt: string; -} - -class SessionManager { - private sessions = new Map(); - private memoryService?: MemoryService; - private sessionService?: BaseSessionService; - private adkSession?: AdkSession; - - /** - * Set dependencies after agent creation - */ - setDeps( - memoryService: MemoryService, - sessionService: BaseSessionService, - adkSession: AdkSession, - ): void { - this.memoryService = memoryService; - this.sessionService = sessionService; - this.adkSession = adkSession; - } - - /** - * Get or create a session for a chat - */ - getOrCreate(chatId: string, userId: string, username?: string): Session { - if (!this.sessions.has(chatId)) { - this.sessions.set(chatId, { - chatId, - userId, - username, - history: [], - startedAt: new Date().toISOString(), - }); - } - return this.sessions.get(chatId)!; - } - - /** - * Check if session exists - */ - has(chatId: string): boolean { - return this.sessions.has(chatId); - } - - /** - * Add a message to session history - */ - addMessage( - chatId: string, - role: "user" | "assistant", - content: string, - ): void { - const session = this.sessions.get(chatId); - if (session) { - session.history.push({ - role, - content, - timestamp: new Date().toISOString(), - }); - } - } - - /** - * Save session to memory and reset - */ - async saveAndReset(chatId: string): Promise { - const session = this.sessions.get(chatId); - if (!session || session.history.length === 0) { - return "No conversation to save."; - } - - // Fetch fresh session from session service (has events populated by runner) - if (this.memoryService && this.sessionService && this.adkSession) { - const freshSession = await this.sessionService.getSession( - this.adkSession.appName, - this.adkSession.userId, - this.adkSession.id, - ); - if (freshSession) { - await this.memoryService.addSessionToMemory(freshSession); - } - } else { - console.warn( - "[SessionManager] Memory dependencies not set — session not saved to memory", - ); - } - - // Generate summary - const messageCount = session.history.length; - const duration = this.formatDuration(session.startedAt); - const summary = `${messageCount} messages over ${duration}`; - - // Reset session - this.reset(chatId); - - return summary; - } - - /** - * Reset session without saving - */ - reset(chatId: string): void { - const session = this.sessions.get(chatId); - if (session) { - session.history = []; - session.startedAt = new Date().toISOString(); - } - } - - /** - * Get conversation history - */ - getHistory(chatId: string): Message[] { - return this.sessions.get(chatId)?.history || []; - } - - /** - * Get recent history for context (last N messages) - */ - getRecentHistory(chatId: string, count = 10): Message[] { - const history = this.getHistory(chatId); - return history.slice(-count); - } - - /** - * Format duration between start and now - */ - private formatDuration(startIso: string): string { - const start = new Date(startIso).getTime(); - const now = Date.now(); - const diffMs = now - start; - - const minutes = Math.floor(diffMs / 60000); - const hours = Math.floor(minutes / 60); - - if (hours > 0) { - return `${hours}h ${minutes % 60}m`; - } - return `${minutes}m`; - } -} - -// Singleton instance -export const sessionManager = new SessionManager(); diff --git a/src/services/TelegramService.ts b/src/services/TelegramService.ts index 2dd6cb1..eea6092 100644 --- a/src/services/TelegramService.ts +++ b/src/services/TelegramService.ts @@ -4,9 +4,12 @@ */ import { + type BaseSessionService, createSamplingHandler, + type EnhancedRunner, extractTextFromContent, type LlmRequest, + type MemoryService, } from "@iqai/adk"; import { createClawdAgent } from "../agents/agent.js"; import { getTelegramAgent } from "../agents/telegram-agent/agent.js"; @@ -19,7 +22,6 @@ import { initScheduler, stopScheduler, } from "./SchedulerService.js"; -import { sessionManager } from "./SessionManager.js"; const log = createLogger("Telegram"); @@ -227,13 +229,39 @@ adk-claw pairing approve telegram ${code} Code expires in 1 hour.`; } -const commands: Record = { - "/start": async (chatId, userId) => { - const config = getRawConfig(); +/** + * Format duration between a unix timestamp (seconds) and now + */ +function formatDuration(startTimestamp: number): string { + const diffMs = Date.now() - startTimestamp * 1000; + const minutes = Math.floor(diffMs / 60000); + const hours = Math.floor(minutes / 60); + + if (hours > 0) { + return `${hours}h ${minutes % 60}m`; + } + return `${minutes}m`; +} + +/** + * Create command handlers bound to ADK session lifecycle. + * Uses sessionService.createSession / deleteSession and runner.setSession + * to properly create/destroy ADK sessions instead of local state. + */ +function createCommands(deps: { + runner: EnhancedRunner; + sessionService: BaseSessionService; + memoryService?: MemoryService; +}): Record { + const { runner, sessionService, memoryService } = deps; + + return { + "/start": async (_chatId, userId) => { + const config = getRawConfig(); - // Check if user is already allowed - if (isAllowed("telegram", userId)) { - return `👋 Welcome back! + // Check if user is already allowed + if (isAllowed("telegram", userId)) { + return `👋 Welcome back! I'm ${config.agent.name}, your personal AI assistant. @@ -241,28 +269,65 @@ Commands: /new - Save session & start fresh /reset - Clear session without saving /help - Show available commands`; - } + } + + // User needs pairing + return getPairingResponse(userId, undefined, _chatId); + }, - // User needs pairing - return getPairingResponse(userId, undefined, chatId); - }, + "/new": async (_chatId, _userId) => { + const currentSession = runner.getSession(); - "/new": async (chatId, _userId) => { - const summary = await sessionManager.saveAndReset(chatId); - return `✅ Session saved to memory. + // Save current session to memory if it has events + if (memoryService && currentSession.events.length > 0) { + await memoryService.addSessionToMemory(currentSession); + } + + // Derive summary from ADK session events + const eventCount = currentSession.events.length; + let summary = "No conversation to save."; + if (eventCount > 0) { + const firstTimestamp = currentSession.events[0].timestamp; + const duration = formatDuration(firstTimestamp); + summary = `${eventCount} events over ${duration}`; + } + + // Create a new ADK session and swap it into the runner + const newSession = await sessionService.createSession( + currentSession.appName, + currentSession.userId, + ); + runner.setSession(newSession); + + return `✅ Session saved to memory. 📝 Summary: ${summary} 🆕 New session started!`; - }, + }, + + "/reset": async (_chatId, _userId) => { + const currentSession = runner.getSession(); + + // Delete the old session without saving to memory + await sessionService.deleteSession( + currentSession.appName, + currentSession.userId, + currentSession.id, + ); + + // Create a fresh ADK session + const newSession = await sessionService.createSession( + currentSession.appName, + currentSession.userId, + ); + runner.setSession(newSession); - "/reset": async (chatId, _userId) => { - sessionManager.reset(chatId); - return "🔄 Session cleared (not saved). Fresh start!"; - }, + return "🔄 Session cleared (not saved). Fresh start!"; + }, - "/help": async (_chatId, _userId) => { - return `📚 Available commands: + "/help": async (_chatId, _userId) => { + return `📚 Available commands: /start - Start the bot and pair /new - Save session & start fresh @@ -270,8 +335,9 @@ Commands: /help - Show this help message Just send a message to chat with me!`; - }, -}; + }, + }; +} /** * Start the Telegram bot with MCP sampling @@ -291,10 +357,8 @@ export async function startTelegramBot(): Promise { channel: "telegram", }); - // Wire memory service into session manager for /new command - if (memoryService) { - sessionManager.setDeps(memoryService, sessionService, session); - } + // Create command handlers bound to ADK session lifecycle + const commands = createCommands({ runner, sessionService, memoryService }); // Create sampling handler with command detection const samplingHandler = createSamplingHandler( @@ -327,17 +391,9 @@ export async function startTelegramBot(): Promise { registerUserCommands(config.telegramBotToken, chatId); } - // Track in session - sessionManager.getOrCreate(chatId, userId); - sessionManager.addMessage(chatId, "user", messageText); - - // Get response from agent + // Get response from agent (ADK tracks events in session automatically) try { const response = await runner.ask(messageText); - - // Track response - sessionManager.addMessage(chatId, "assistant", response); - return response; } catch (error) { log.error( diff --git a/src/services/index.ts b/src/services/index.ts index 92cf4e6..894361e 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -3,5 +3,4 @@ */ export { initScheduler, stopScheduler } from "./SchedulerService.js"; -export { sessionManager } from "./SessionManager.js"; export { startTelegramBot } from "./TelegramService.js";