diff --git a/docs/session-manager-usage.md b/docs/session-manager-usage.md new file mode 100644 index 0000000..62b6913 --- /dev/null +++ b/docs/session-manager-usage.md @@ -0,0 +1,255 @@ +# SessionManager Usage Guide + +## Overview + +The `SessionManager` class provides a generic, reusable way to manage agent sessions with D1 database. It eliminates duplicated CRUD code across all 4 agents (Domain, Deposit, Server, Troubleshoot). + +## Features + +- **CRUD Operations**: get, save, delete, has +- **Expiry Management**: Automatic TTL handling +- **Message Management**: Add messages with automatic trimming (max limit) +- **Type Safety**: Full TypeScript support with generics +- **Extensible**: Specialized managers for agents with extra fields + +## Basic Usage + +### 1. For Simple Sessions (Deposit, Troubleshoot) + +```typescript +import { SessionManager } from '../utils/session-manager'; +import type { DepositSession } from '../types'; + +// Create manager instance +const depositSessionManager = new SessionManager({ + tableName: 'deposit_sessions', + ttlMs: 30 * 60 * 1000, // 30 minutes + maxMessages: 20 +}); + +// Get or create session +let session = await depositSessionManager.get(db, userId); +if (!session) { + session = depositSessionManager.create(userId, 'collecting_amount'); +} + +// Add message +depositSessionManager.addMessage(session, 'user', userMessage); + +// Save session +await depositSessionManager.save(db, session); + +// Delete session +await depositSessionManager.delete(db, userId); + +// Check if session exists +const hasSession = await depositSessionManager.has(db, userId); +``` + +### 2. For Sessions with Extra Fields (Domain, Server) + +```typescript +import { DomainSessionManager } from '../utils/session-manager'; +import type { DomainSession } from '../types'; + +// Use specialized manager +const domainSessionManager = new DomainSessionManager({ + tableName: 'domain_sessions', + ttlMs: 60 * 60 * 1000, // 1 hour + maxMessages: 20 +}); + +// Create with additional fields +const session = domainSessionManager.create(userId, 'gathering', { + target_domain: 'example.com' +}); + +// The manager handles target_domain serialization automatically +await domainSessionManager.save(db, session); +``` + +## Specialized Managers + +### DomainSessionManager + +Handles `target_domain` field for Domain Agent. + +```typescript +export class DomainSessionManager extends SessionManager { + protected parseAdditionalFields(result: Record): Partial { + return { + target_domain: result.target_domain as string | undefined, + }; + } + + protected getAdditionalColumns(session: DomainSession): Record { + return { + target_domain: session.target_domain || null, + }; + } +} +``` + +### ServerSessionManager + +Handles `last_recommendation` field for Server Agent. + +```typescript +export class ServerSessionManager extends SessionManager { + protected parseAdditionalFields(result: Record): Partial { + return { + last_recommendation: result.last_recommendation + ? JSON.parse(result.last_recommendation as string) + : undefined, + }; + } + + protected getAdditionalColumns(session: ServerSession): Record { + return { + last_recommendation: session.last_recommendation + ? JSON.stringify(session.last_recommendation) + : null, + }; + } +} +``` + +## API Reference + +### Constructor + +```typescript +constructor(config: SessionManagerConfig) +``` + +**Parameters:** +- `config.tableName`: D1 table name (e.g., 'domain_sessions') +- `config.ttlMs`: Session TTL in milliseconds +- `config.maxMessages`: Maximum messages to keep in session + +### Methods + +#### get(db, userId) +Get session from D1. Returns null if not found or expired. + +```typescript +async get(db: D1Database, userId: string): Promise +``` + +#### save(db, session) +Save session to D1 (insert or replace). Automatically updates `updated_at` and `expires_at`. + +```typescript +async save(db: D1Database, session: T): Promise +``` + +#### delete(db, userId) +Delete session from D1. + +```typescript +async delete(db: D1Database, userId: string): Promise +``` + +#### has(db, userId) +Check if active session exists (lightweight query). + +```typescript +async has(db: D1Database, userId: string): Promise +``` + +#### create(userId, status, additionalFields?) +Create a new session object (not saved to DB yet). + +```typescript +create(userId: string, status: string, additionalFields?: Partial): T +``` + +#### isExpired(session) +Check if session is expired. + +```typescript +isExpired(session: T): boolean +``` + +#### addMessage(session, role, content) +Add message to session with automatic trimming. + +```typescript +addMessage(session: T, role: 'user' | 'assistant', content: string): void +``` + +#### getOrCreate(db, userId, initialStatus) +Get existing session or create new one (convenience method). + +```typescript +async getOrCreate(db: D1Database, userId: string, initialStatus: string): Promise +``` + +## Migration Guide + +### Before (domain-agent.ts) + +```typescript +// 160 lines of duplicated CRUD code +async function getDomainSession(db: D1Database, userId: string): Promise { + // ... 40 lines +} + +async function saveDomainSession(db: D1Database, session: DomainSession): Promise { + // ... 30 lines +} + +async function deleteDomainSession(db: D1Database, userId: string): Promise { + // ... 10 lines +} + +function createDomainSession(userId: string, status: DomainSessionStatus): DomainSession { + // ... 10 lines +} + +function isSessionExpired(session: DomainSession): boolean { + // ... 5 lines +} + +function addMessageToSession(session: DomainSession, role: 'user' | 'assistant', content: string): void { + // ... 15 lines +} + +async function hasDomainSession(db: D1Database, userId: string): Promise { + // ... 10 lines +} +``` + +### After (with SessionManager) + +```typescript +import { DomainSessionManager } from '../utils/session-manager'; + +const sessionManager = new DomainSessionManager({ + tableName: 'domain_sessions', + ttlMs: 60 * 60 * 1000, + maxMessages: 20 +}); + +// All CRUD operations now use the manager +const session = await sessionManager.get(db, userId); +sessionManager.addMessage(session, 'user', message); +await sessionManager.save(db, session); +``` + +**Result**: ~160 lines → ~10 lines per agent = **~600 lines saved** across 4 agents! + +## Next Steps + +1. **Task 19**: Refactor `domain-agent.ts` to use `DomainSessionManager` +2. **Task 20**: Refactor `server-agent.ts` to use `ServerSessionManager` +3. **Task 21**: Refactor `deposit-agent.ts` to use `SessionManager` +4. **Task 22**: Refactor `troubleshoot-agent.ts` to use `SessionManager` + +## Notes + +- The base `SessionManager` works for simple sessions (Deposit, Troubleshoot) +- Specialized managers (`DomainSessionManager`, `ServerSessionManager`) handle extra fields +- All session operations are logged with structured logging +- Expired sessions are automatically filtered out in `get()` method +- Message trimming prevents memory bloat (configurable `maxMessages`)