import Database from 'better-sqlite3' import { homedir } from 'os' import { join } from 'path' import type { SessionMeta } from '../../types/session.js' import { openSessionsDb } from './db.js' const HERMES_DB = join(homedir(), '.hermes', 'state.db') const DEFAULT_DB_PATH = join(homedir(), '.claude-work', 'sessions.db') export interface HermesSessionRow { id: string model: string | null started_at: number | null ended_at: number | null end_reason: string | null message_count: number tool_call_count: number input_tokens: number | null output_tokens: number | null estimated_cost_usd: number | null title: string | null api_call_count: number | null } export interface HermesMessageRow { session_id: string role: string content: string | null tool_name: string | null timestamp: number | null } /** * Lê sessões do state.db do Hermes Agent */ export function readHermesSessions(): HermesSessionRow[] { const db = new Database(HERMES_DB, { readonly: true }) try { return db.prepare(` SELECT id, model, started_at, ended_at, end_reason, message_count, tool_call_count, input_tokens, output_tokens, estimated_cost_usd, title, api_call_count FROM sessions ORDER BY started_at DESC `).all() as HermesSessionRow[] } finally { db.close() } } /** * Lê mensagens de uma sessão Hermes */ export function readHermesMessages(sessionId: string): HermesMessageRow[] { const db = new Database(HERMES_DB, { readonly: true }) try { return db.prepare(` SELECT session_id, role, content, tool_name, timestamp FROM messages WHERE session_id = ? ORDER BY timestamp ASC `).all(sessionId) as HermesMessageRow[] } finally { db.close() } } function toHermesMeta(row: HermesSessionRow, messages: HermesMessageRow[]): SessionMeta { const userMsgs = messages.filter(m => m.role === 'user') const assistantMsgs = messages.filter(m => m.role === 'assistant') const toolMsgs = messages.filter(m => m.role === 'tool') const firstUser = userMsgs[0] const toolsUsed = [...new Set(messages.filter(m => m.tool_name).map(m => m.tool_name!))] return { session_id: row.id, source: 'hermes', project_path: null, project_slug: null, jsonl_path: null, model: row.model ?? null, started_at: row.started_at ? new Date(row.started_at * 1000).toISOString() : new Date(0).toISOString(), ended_at: row.ended_at ? new Date(row.ended_at * 1000).toISOString() : null, duration_sec: row.started_at && row.ended_at ? Math.round(row.ended_at - row.started_at) : null, event_count: messages.length, user_messages: userMsgs.length, assistant_msgs: assistantMsgs.length, tool_calls: toolMsgs.length, input_tokens: row.input_tokens ?? null, output_tokens: row.output_tokens ?? null, estimated_cost: row.estimated_cost_usd ?? null, first_prompt: firstUser?.content?.slice(0, 500) ?? null, tools_used: toolsUsed, skills_invoked: [], outcome: row.end_reason === 'completed' ? 'completed' : row.end_reason === 'interrupted' || row.end_reason === 'stopped' ? 'interrupted' : row.end_reason === 'error' ? 'error' : 'unknown', permission_mode: null, file_size: 0, indexed_at: new Date().toISOString(), } } export interface IndexResult { indexed: number failed: number } /** * Indexa todas as sessões Hermes no state.db para o sessions.db unificado */ export async function indexHermesSessions( options: { dbPath?: string } = {}, ): Promise { let indexed = 0 let failed = 0 const db = openSessionsDb(options.dbPath ?? DEFAULT_DB_PATH) try { const sessions = readHermesSessions() let batch: SessionMeta[] = [] const BATCH = 50 for (const session of sessions) { try { const messages = readHermesMessages(session.id) const meta = toHermesMeta(session, messages) batch.push(meta) if (batch.length >= BATCH) { db.upsertMany(batch) indexed += batch.length batch = [] } } catch (err) { failed++ console.error(`[hermes-indexer] erro em sessão ${session.id}:`, err) } } if (batch.length > 0) { db.upsertMany(batch) indexed += batch.length } } finally { db.close() } return { indexed, failed } }