diff --git a/src/main/apiServer/server.ts b/src/main/apiServer/server.ts index 9b15e56da0..e59e6bd504 100644 --- a/src/main/apiServer/server.ts +++ b/src/main/apiServer/server.ts @@ -3,7 +3,6 @@ import { createServer } from 'node:http' import { loggerService } from '@logger' import { IpcChannel } from '@shared/IpcChannel' -import { agentService } from '../services/agents' import { windowService } from '../services/WindowService' import { app } from './app' import { config } from './config' @@ -32,11 +31,6 @@ export class ApiServer { // Load config const { port, host } = await config.load() - // Initialize AgentService - logger.info('Initializing AgentService') - await agentService.initialize() - logger.info('AgentService initialized') - // Create server with Express app this.server = createServer(app) this.applyServerTimeouts(this.server) diff --git a/src/main/index.ts b/src/main/index.ts index 27489a26b5..56750e6b61 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -34,6 +34,7 @@ import { TrayService } from './services/TrayService' import { versionService } from './services/VersionService' import { windowService } from './services/WindowService' import { initWebviewHotkeys } from './services/WebviewService' +import { runAsyncFunction } from './utils' const logger = loggerService.withContext('MainEntry') @@ -170,39 +171,33 @@ if (!app.requestSingleInstanceLock()) { //start selection assistant service initSelectionService() - // Initialize Agent Service - try { - await agentService.initialize() - logger.info('Agent service initialized successfully') - } catch (error: any) { - logger.error('Failed to initialize Agent service:', error) - } + runAsyncFunction(async () => { + // Start API server if enabled or if agents exist + try { + const config = await apiServerService.getCurrentConfig() + logger.info('API server config:', config) - // Start API server if enabled or if agents exist - try { - const config = await apiServerService.getCurrentConfig() - logger.info('API server config:', config) - - // Check if there are any agents - let shouldStart = config.enabled - if (!shouldStart) { - try { - const { total } = await agentService.listAgents({ limit: 1 }) - if (total > 0) { - shouldStart = true - logger.info(`Detected ${total} agent(s), auto-starting API server`) + // Check if there are any agents + let shouldStart = config.enabled + if (!shouldStart) { + try { + const { total } = await agentService.listAgents({ limit: 1 }) + if (total > 0) { + shouldStart = true + logger.info(`Detected ${total} agent(s), auto-starting API server`) + } + } catch (error: any) { + logger.warn('Failed to check agent count:', error) } - } catch (error: any) { - logger.warn('Failed to check agent count:', error) } - } - if (shouldStart) { - await apiServerService.start() + if (shouldStart) { + await apiServerService.start() + } + } catch (error: any) { + logger.error('Failed to check/start API server:', error) } - } catch (error: any) { - logger.error('Failed to check/start API server:', error) - } + }) }) registerProtocolClient(app) diff --git a/src/main/services/agents/BaseService.ts b/src/main/services/agents/BaseService.ts index 1c9b438e4a..78bf72a952 100644 --- a/src/main/services/agents/BaseService.ts +++ b/src/main/services/agents/BaseService.ts @@ -1,17 +1,13 @@ -import { type Client, createClient } from '@libsql/client' import { loggerService } from '@logger' import { mcpApiService } from '@main/apiServer/services/mcp' import type { ModelValidationError } from '@main/apiServer/utils' import { validateModelId } from '@main/apiServer/utils' import type { AgentType, MCPTool, SlashCommand, Tool } from '@types' import { objectKeys } from '@types' -import { drizzle, type LibSQLDatabase } from 'drizzle-orm/libsql' import fs from 'fs' import path from 'path' -import { MigrationService } from './database/MigrationService' -import * as schema from './database/schema' -import { dbPath } from './drizzle.config' +import { DatabaseManager } from './database/DatabaseManager' import type { AgentModelField } from './errors' import { AgentModelValidationError } from './errors' import { builtinSlashCommands } from './services/claudecode/commands' @@ -20,22 +16,16 @@ import { builtinTools } from './services/claudecode/tools' const logger = loggerService.withContext('BaseService') /** - * Base service class providing shared database connection and utilities - * for all agent-related services. + * Base service class providing shared utilities for all agent-related services. * * Features: - * - Programmatic schema management (no CLI dependencies) - * - Automatic table creation and migration - * - Schema version tracking and compatibility checks - * - Transaction-based operations for safety - * - Development vs production mode handling - * - Connection retry logic with exponential backoff + * - Database access through DatabaseManager singleton + * - JSON field serialization/deserialization + * - Path validation and creation + * - Model validation + * - MCP tools and slash commands listing */ export abstract class BaseService { - protected static client: Client | null = null - protected static db: LibSQLDatabase | null = null - protected static isInitialized = false - protected static initializationPromise: Promise | null = null protected jsonFields: string[] = [ 'tools', 'mcps', @@ -45,23 +35,6 @@ export abstract class BaseService { 'slash_commands' ] - /** - * Initialize database with retry logic and proper error handling - */ - protected static async initialize(): Promise { - // Return existing initialization if in progress - if (BaseService.initializationPromise) { - return BaseService.initializationPromise - } - - if (BaseService.isInitialized) { - return - } - - BaseService.initializationPromise = BaseService.performInitialization() - return BaseService.initializationPromise - } - public async listMcpTools(agentType: AgentType, ids?: string[]): Promise { const tools: Tool[] = [] if (agentType === 'claude-code') { @@ -101,78 +74,13 @@ export abstract class BaseService { return [] } - private static async performInitialization(): Promise { - const maxRetries = 3 - let lastError: Error - - for (let attempt = 1; attempt <= maxRetries; attempt++) { - try { - logger.info(`Initializing Agent database at: ${dbPath} (attempt ${attempt}/${maxRetries})`) - - // Ensure the database directory exists - const dbDir = path.dirname(dbPath) - if (!fs.existsSync(dbDir)) { - logger.info(`Creating database directory: ${dbDir}`) - fs.mkdirSync(dbDir, { recursive: true }) - } - - BaseService.client = createClient({ - url: `file:${dbPath}` - }) - - BaseService.db = drizzle(BaseService.client, { schema }) - - // Run database migrations - const migrationService = new MigrationService(BaseService.db, BaseService.client) - await migrationService.runMigrations() - - BaseService.isInitialized = true - logger.info('Agent database initialized successfully') - return - } catch (error) { - lastError = error as Error - logger.warn(`Database initialization attempt ${attempt} failed:`, lastError) - - // Clean up on failure - if (BaseService.client) { - try { - BaseService.client.close() - } catch (closeError) { - logger.warn('Failed to close client during cleanup:', closeError as Error) - } - } - BaseService.client = null - BaseService.db = null - - // Wait before retrying (exponential backoff) - if (attempt < maxRetries) { - const delay = Math.pow(2, attempt) * 1000 // 2s, 4s, 8s - logger.info(`Retrying in ${delay}ms...`) - await new Promise((resolve) => setTimeout(resolve, delay)) - } - } - } - - // All retries failed - BaseService.initializationPromise = null - logger.error('Failed to initialize Agent database after all retries:', lastError!) - throw lastError! - } - - protected ensureInitialized(): void { - if (!BaseService.isInitialized || !BaseService.db || !BaseService.client) { - throw new Error('Database not initialized. Call initialize() first.') - } - } - - protected get database(): LibSQLDatabase { - this.ensureInitialized() - return BaseService.db! - } - - protected get rawClient(): Client { - this.ensureInitialized() - return BaseService.client! + /** + * Get database instance + * Automatically waits for initialization to complete + */ + protected async getDatabase() { + const dbManager = await DatabaseManager.getInstance() + return dbManager.getDatabase() } protected serializeJsonFields(data: any): any { @@ -284,7 +192,7 @@ export abstract class BaseService { } /** - * Force re-initialization (for development/testing) + * Validate agent model configuration */ protected async validateAgentModels( agentType: AgentType, @@ -325,22 +233,4 @@ export abstract class BaseService { } } } - - static async reinitialize(): Promise { - BaseService.isInitialized = false - BaseService.initializationPromise = null - - if (BaseService.client) { - try { - BaseService.client.close() - } catch (error) { - logger.warn('Failed to close client during reinitialize:', error as Error) - } - } - - BaseService.client = null - BaseService.db = null - - await BaseService.initialize() - } } diff --git a/src/main/services/agents/database/DatabaseManager.ts b/src/main/services/agents/database/DatabaseManager.ts new file mode 100644 index 0000000000..f4b13971c7 --- /dev/null +++ b/src/main/services/agents/database/DatabaseManager.ts @@ -0,0 +1,156 @@ +import { type Client, createClient } from '@libsql/client' +import { loggerService } from '@logger' +import type { LibSQLDatabase } from 'drizzle-orm/libsql' +import { drizzle } from 'drizzle-orm/libsql' +import fs from 'fs' +import path from 'path' + +import { dbPath } from '../drizzle.config' +import { MigrationService } from './MigrationService' +import * as schema from './schema' + +const logger = loggerService.withContext('DatabaseManager') + +/** + * Database initialization state + */ +enum InitState { + INITIALIZING = 'initializing', + INITIALIZED = 'initialized', + FAILED = 'failed' +} + +/** + * DatabaseManager - Singleton class for managing libsql database connections + * + * Responsibilities: + * - Single source of truth for database connection + * - Thread-safe initialization with state management + * - Automatic migration handling + * - Safe connection cleanup + * - Error recovery and retry logic + * - Windows platform compatibility fixes + */ +export class DatabaseManager { + private static instance: DatabaseManager | null = null + + private client: Client | null = null + private db: LibSQLDatabase | null = null + private state: InitState = InitState.INITIALIZING + + /** + * Get the singleton instance (database initialization starts automatically) + */ + public static async getInstance(): Promise { + if (DatabaseManager.instance) { + return DatabaseManager.instance + } + + const instance = new DatabaseManager() + await instance.initialize() + DatabaseManager.instance = instance + + return instance + } + + /** + * Perform the actual initialization + */ + public async initialize(): Promise { + if (this.state === InitState.INITIALIZED) { + return + } + + try { + logger.info(`Initializing database at: ${dbPath}`) + + // Ensure database directory exists + const dbDir = path.dirname(dbPath) + if (!fs.existsSync(dbDir)) { + logger.info(`Creating database directory: ${dbDir}`) + fs.mkdirSync(dbDir, { recursive: true }) + } + + // Check if database file is corrupted (Windows specific check) + if (fs.existsSync(dbPath)) { + const stats = fs.statSync(dbPath) + if (stats.size === 0) { + logger.warn('Database file is empty, removing corrupted file') + fs.unlinkSync(dbPath) + } + } + + // Create client with platform-specific options + this.client = createClient({ + url: `file:${dbPath}`, + // intMode: 'number' helps avoid some Windows compatibility issues + intMode: 'number' + }) + + // Create drizzle instance + this.db = drizzle(this.client, { schema }) + + // Run migrations + const migrationService = new MigrationService(this.db, this.client) + await migrationService.runMigrations() + + this.state = InitState.INITIALIZED + logger.info('Database initialized successfully') + } catch (error) { + const err = error as Error + logger.error('Database initialization failed:', { + error: err.message, + stack: err.stack + }) + + // Clean up failed initialization + this.cleanupFailedInit() + + // Set failed state + this.state = InitState.FAILED + throw new Error(`Database initialization failed: ${err.message || 'Unknown error'}`) + } + } + + /** + * Clean up after failed initialization + */ + private cleanupFailedInit(): void { + if (this.client) { + try { + // On Windows, closing a partially initialized client can crash + // Wrap in try-catch and ignore errors during cleanup + this.client.close() + } catch (error) { + logger.warn('Failed to close client during cleanup:', error as Error) + } + } + this.client = null + this.db = null + } + + /** + * Get the database instance + * Automatically waits for initialization to complete + * @throws Error if database initialization failed + */ + public getDatabase(): LibSQLDatabase { + return this.db! + } + + /** + * Get the raw client (for advanced operations) + * Automatically waits for initialization to complete + * @throws Error if database initialization failed + */ + public async getClient(): Promise { + return this.client! + } + + /** + * Check if database is initialized + */ + public isInitialized(): boolean { + return this.state === InitState.INITIALIZED + } +} diff --git a/src/main/services/agents/database/index.ts b/src/main/services/agents/database/index.ts index 61b3a9ffcc..43302a6b25 100644 --- a/src/main/services/agents/database/index.ts +++ b/src/main/services/agents/database/index.ts @@ -7,8 +7,14 @@ * Schema evolution is handled by Drizzle Kit migrations. */ +// Database Manager (Singleton) +export * from './DatabaseManager' + // Drizzle ORM schemas export * from './schema' // Repository helpers export * from './sessionMessageRepository' + +// Migration Service +export * from './MigrationService' diff --git a/src/main/services/agents/database/sessionMessageRepository.ts b/src/main/services/agents/database/sessionMessageRepository.ts index 4567c61ec0..a9b1d2e572 100644 --- a/src/main/services/agents/database/sessionMessageRepository.ts +++ b/src/main/services/agents/database/sessionMessageRepository.ts @@ -15,26 +15,16 @@ import { sessionMessagesTable } from './schema' const logger = loggerService.withContext('AgentMessageRepository') -type TxClient = any - export type PersistUserMessageParams = AgentMessageUserPersistPayload & { sessionId: string agentSessionId?: string - tx?: TxClient } export type PersistAssistantMessageParams = AgentMessageAssistantPersistPayload & { sessionId: string agentSessionId: string - tx?: TxClient } -type PersistExchangeParams = AgentMessagePersistExchangePayload & { - tx?: TxClient -} - -type PersistExchangeResult = AgentMessagePersistExchangeResult - class AgentMessageRepository extends BaseService { private static instance: AgentMessageRepository | null = null @@ -87,17 +77,13 @@ class AgentMessageRepository extends BaseService { return deserialized } - private getWriter(tx?: TxClient): TxClient { - return tx ?? this.database - } - private async findExistingMessageRow( - writer: TxClient, sessionId: string, role: string, messageId: string ): Promise { - const candidateRows: SessionMessageRow[] = await writer + const database = await this.getDatabase() + const candidateRows: SessionMessageRow[] = await database .select() .from(sessionMessagesTable) .where(and(eq(sessionMessagesTable.session_id, sessionId), eq(sessionMessagesTable.role, role))) @@ -122,10 +108,7 @@ class AgentMessageRepository extends BaseService { private async upsertMessage( params: PersistUserMessageParams | PersistAssistantMessageParams ): Promise { - await AgentMessageRepository.initialize() - this.ensureInitialized() - - const { sessionId, agentSessionId = '', payload, metadata, createdAt, tx } = params + const { sessionId, agentSessionId = '', payload, metadata, createdAt } = params if (!payload?.message?.role) { throw new Error('Message payload missing role') @@ -135,18 +118,18 @@ class AgentMessageRepository extends BaseService { throw new Error('Message payload missing id') } - const writer = this.getWriter(tx) + const database = await this.getDatabase() const now = createdAt ?? payload.message.createdAt ?? new Date().toISOString() const serializedPayload = this.serializeMessage(payload) const serializedMetadata = this.serializeMetadata(metadata) - const existingRow = await this.findExistingMessageRow(writer, sessionId, payload.message.role, payload.message.id) + const existingRow = await this.findExistingMessageRow(sessionId, payload.message.role, payload.message.id) if (existingRow) { const metadataToPersist = serializedMetadata ?? existingRow.metadata ?? undefined const agentSessionToPersist = agentSessionId || existingRow.agent_session_id || '' - await writer + await database .update(sessionMessagesTable) .set({ content: serializedPayload, @@ -175,7 +158,7 @@ class AgentMessageRepository extends BaseService { updated_at: now } - const [saved] = await writer.insert(sessionMessagesTable).values(insertData).returning() + const [saved] = await database.insert(sessionMessagesTable).values(insertData).returning() return this.deserialize(saved) } @@ -188,49 +171,38 @@ class AgentMessageRepository extends BaseService { return this.upsertMessage(params) } - async persistExchange(params: PersistExchangeParams): Promise { - await AgentMessageRepository.initialize() - this.ensureInitialized() - + async persistExchange(params: AgentMessagePersistExchangePayload): Promise { const { sessionId, agentSessionId, user, assistant } = params - const result = await this.database.transaction(async (tx) => { - const exchangeResult: PersistExchangeResult = {} + const exchangeResult: AgentMessagePersistExchangeResult = {} - if (user?.payload) { - exchangeResult.userMessage = await this.persistUserMessage({ - sessionId, - agentSessionId, - payload: user.payload, - metadata: user.metadata, - createdAt: user.createdAt, - tx - }) - } + if (user?.payload) { + exchangeResult.userMessage = await this.persistUserMessage({ + sessionId, + agentSessionId, + payload: user.payload, + metadata: user.metadata, + createdAt: user.createdAt + }) + } - if (assistant?.payload) { - exchangeResult.assistantMessage = await this.persistAssistantMessage({ - sessionId, - agentSessionId, - payload: assistant.payload, - metadata: assistant.metadata, - createdAt: assistant.createdAt, - tx - }) - } + if (assistant?.payload) { + exchangeResult.assistantMessage = await this.persistAssistantMessage({ + sessionId, + agentSessionId, + payload: assistant.payload, + metadata: assistant.metadata, + createdAt: assistant.createdAt + }) + } - return exchangeResult - }) - - return result + return exchangeResult } async getSessionHistory(sessionId: string): Promise { - await AgentMessageRepository.initialize() - this.ensureInitialized() - try { - const rows = await this.database + const database = await this.getDatabase() + const rows = await database .select() .from(sessionMessagesTable) .where(eq(sessionMessagesTable.session_id, sessionId)) diff --git a/src/main/services/agents/services/AgentService.ts b/src/main/services/agents/services/AgentService.ts index 07ed89a0f3..2faa87bb45 100644 --- a/src/main/services/agents/services/AgentService.ts +++ b/src/main/services/agents/services/AgentService.ts @@ -32,14 +32,8 @@ export class AgentService extends BaseService { return AgentService.instance } - async initialize(): Promise { - await BaseService.initialize() - } - // Agent Methods async createAgent(req: CreateAgentRequest): Promise { - this.ensureInitialized() - const id = `agent_${Date.now()}_${Math.random().toString(36).substring(2, 11)}` const now = new Date().toISOString() @@ -75,8 +69,9 @@ export class AgentService extends BaseService { updated_at: now } - await this.database.insert(agentsTable).values(insertData) - const result = await this.database.select().from(agentsTable).where(eq(agentsTable.id, id)).limit(1) + const database = await this.getDatabase() + await database.insert(agentsTable).values(insertData) + const result = await database.select().from(agentsTable).where(eq(agentsTable.id, id)).limit(1) if (!result[0]) { throw new Error('Failed to create agent') } @@ -86,9 +81,8 @@ export class AgentService extends BaseService { } async getAgent(id: string): Promise { - this.ensureInitialized() - - const result = await this.database.select().from(agentsTable).where(eq(agentsTable.id, id)).limit(1) + const database = await this.getDatabase() + const result = await database.select().from(agentsTable).where(eq(agentsTable.id, id)).limit(1) if (!result[0]) { return null @@ -118,9 +112,9 @@ export class AgentService extends BaseService { } async listAgents(options: ListOptions = {}): Promise<{ agents: AgentEntity[]; total: number }> { - this.ensureInitialized() // Build query with pagination - - const totalResult = await this.database.select({ count: count() }).from(agentsTable) + // Build query with pagination + const database = await this.getDatabase() + const totalResult = await database.select({ count: count() }).from(agentsTable) const sortBy = options.sortBy || 'created_at' const orderBy = options.orderBy || 'desc' @@ -128,7 +122,7 @@ export class AgentService extends BaseService { const sortField = agentsTable[sortBy] const orderFn = orderBy === 'asc' ? asc : desc - const baseQuery = this.database.select().from(agentsTable).orderBy(orderFn(sortField)) + const baseQuery = database.select().from(agentsTable).orderBy(orderFn(sortField)) const result = options.limit !== undefined @@ -151,8 +145,6 @@ export class AgentService extends BaseService { updates: UpdateAgentRequest, options: { replace?: boolean } = {} ): Promise { - this.ensureInitialized() - // Check if agent exists const existing = await this.getAgent(id) if (!existing) { @@ -195,22 +187,21 @@ export class AgentService extends BaseService { } } - await this.database.update(agentsTable).set(updateData).where(eq(agentsTable.id, id)) + const database = await this.getDatabase() + await database.update(agentsTable).set(updateData).where(eq(agentsTable.id, id)) return await this.getAgent(id) } async deleteAgent(id: string): Promise { - this.ensureInitialized() - - const result = await this.database.delete(agentsTable).where(eq(agentsTable.id, id)) + const database = await this.getDatabase() + const result = await database.delete(agentsTable).where(eq(agentsTable.id, id)) return result.rowsAffected > 0 } async agentExists(id: string): Promise { - this.ensureInitialized() - - const result = await this.database + const database = await this.getDatabase() + const result = await database .select({ id: agentsTable.id }) .from(agentsTable) .where(eq(agentsTable.id, id)) diff --git a/src/main/services/agents/services/SessionMessageService.ts b/src/main/services/agents/services/SessionMessageService.ts index 46435fa371..48ef8621ef 100644 --- a/src/main/services/agents/services/SessionMessageService.ts +++ b/src/main/services/agents/services/SessionMessageService.ts @@ -104,14 +104,9 @@ export class SessionMessageService extends BaseService { return SessionMessageService.instance } - async initialize(): Promise { - await BaseService.initialize() - } - async sessionMessageExists(id: number): Promise { - this.ensureInitialized() - - const result = await this.database + const database = await this.getDatabase() + const result = await database .select({ id: sessionMessagesTable.id }) .from(sessionMessagesTable) .where(eq(sessionMessagesTable.id, id)) @@ -124,10 +119,9 @@ export class SessionMessageService extends BaseService { sessionId: string, options: ListOptions = {} ): Promise<{ messages: AgentSessionMessageEntity[] }> { - this.ensureInitialized() - // Get messages with pagination - const baseQuery = this.database + const database = await this.getDatabase() + const baseQuery = database .select() .from(sessionMessagesTable) .where(eq(sessionMessagesTable.session_id, sessionId)) @@ -146,9 +140,8 @@ export class SessionMessageService extends BaseService { } async deleteSessionMessage(sessionId: string, messageId: number): Promise { - this.ensureInitialized() - - const result = await this.database + const database = await this.getDatabase() + const result = await database .delete(sessionMessagesTable) .where(and(eq(sessionMessagesTable.id, messageId), eq(sessionMessagesTable.session_id, sessionId))) @@ -160,8 +153,6 @@ export class SessionMessageService extends BaseService { messageData: CreateSessionMessageRequest, abortController: AbortController ): Promise { - this.ensureInitialized() - return await this.startSessionMessageStream(session, messageData, abortController) } @@ -270,10 +261,9 @@ export class SessionMessageService extends BaseService { } private async getLastAgentSessionId(sessionId: string): Promise { - this.ensureInitialized() - try { - const result = await this.database + const database = await this.getDatabase() + const result = await database .select({ agent_session_id: sessionMessagesTable.agent_session_id }) .from(sessionMessagesTable) .where(and(eq(sessionMessagesTable.session_id, sessionId), not(eq(sessionMessagesTable.agent_session_id, '')))) diff --git a/src/main/services/agents/services/SessionService.ts b/src/main/services/agents/services/SessionService.ts index c9ecf72c32..d933ef8dd9 100644 --- a/src/main/services/agents/services/SessionService.ts +++ b/src/main/services/agents/services/SessionService.ts @@ -30,10 +30,6 @@ export class SessionService extends BaseService { return SessionService.instance } - async initialize(): Promise { - await BaseService.initialize() - } - /** * Override BaseService.listSlashCommands to merge builtin and plugin commands */ @@ -84,13 +80,12 @@ export class SessionService extends BaseService { agentId: string, req: Partial = {} ): Promise { - this.ensureInitialized() - // Validate agent exists - we'll need to import AgentService for this check // For now, we'll skip this validation to avoid circular dependencies // The database foreign key constraint will handle this - const agents = await this.database.select().from(agentsTable).where(eq(agentsTable.id, agentId)).limit(1) + const database = await this.getDatabase() + const agents = await database.select().from(agentsTable).where(eq(agentsTable.id, agentId)).limit(1) if (!agents[0]) { throw new Error('Agent not found') } @@ -135,9 +130,10 @@ export class SessionService extends BaseService { updated_at: now } - await this.database.insert(sessionsTable).values(insertData) + const db = await this.getDatabase() + await db.insert(sessionsTable).values(insertData) - const result = await this.database.select().from(sessionsTable).where(eq(sessionsTable.id, id)).limit(1) + const result = await db.select().from(sessionsTable).where(eq(sessionsTable.id, id)).limit(1) if (!result[0]) { throw new Error('Failed to create session') @@ -148,9 +144,8 @@ export class SessionService extends BaseService { } async getSession(agentId: string, id: string): Promise { - this.ensureInitialized() - - const result = await this.database + const database = await this.getDatabase() + const result = await database .select() .from(sessionsTable) .where(and(eq(sessionsTable.id, id), eq(sessionsTable.agent_id, agentId))) @@ -176,8 +171,6 @@ export class SessionService extends BaseService { agentId?: string, options: ListOptions = {} ): Promise<{ sessions: AgentSessionEntity[]; total: number }> { - this.ensureInitialized() - // Build where conditions const whereConditions: SQL[] = [] if (agentId) { @@ -192,16 +185,13 @@ export class SessionService extends BaseService { : undefined // Get total count - const totalResult = await this.database.select({ count: count() }).from(sessionsTable).where(whereClause) + const database = await this.getDatabase() + const totalResult = await database.select({ count: count() }).from(sessionsTable).where(whereClause) const total = totalResult[0].count // Build list query with pagination - sort by updated_at descending (latest first) - const baseQuery = this.database - .select() - .from(sessionsTable) - .where(whereClause) - .orderBy(desc(sessionsTable.updated_at)) + const baseQuery = database.select().from(sessionsTable).where(whereClause).orderBy(desc(sessionsTable.updated_at)) const result = options.limit !== undefined @@ -220,8 +210,6 @@ export class SessionService extends BaseService { id: string, updates: UpdateSessionRequest ): Promise { - this.ensureInitialized() - // Check if session exists const existing = await this.getSession(agentId, id) if (!existing) { @@ -262,15 +250,15 @@ export class SessionService extends BaseService { } } - await this.database.update(sessionsTable).set(updateData).where(eq(sessionsTable.id, id)) + const database = await this.getDatabase() + await database.update(sessionsTable).set(updateData).where(eq(sessionsTable.id, id)) return await this.getSession(agentId, id) } async deleteSession(agentId: string, id: string): Promise { - this.ensureInitialized() - - const result = await this.database + const database = await this.getDatabase() + const result = await database .delete(sessionsTable) .where(and(eq(sessionsTable.id, id), eq(sessionsTable.agent_id, agentId))) @@ -278,9 +266,8 @@ export class SessionService extends BaseService { } async sessionExists(agentId: string, id: string): Promise { - this.ensureInitialized() - - const result = await this.database + const database = await this.getDatabase() + const result = await database .select({ id: sessionsTable.id }) .from(sessionsTable) .where(and(eq(sessionsTable.id, id), eq(sessionsTable.agent_id, agentId)))