import { randomUUID } from 'node:crypto'; import { appendFile, mkdir } from 'node:fs/promises'; import { dirname, join } from 'node:path'; import { homedir, hostname } from 'node:os'; import { TEMPLATE_AGENT_ID, TEMPLATE_AGENT_NAME, TEMPLATE_AGENT_NAMESPACE } from '../../metadata.js'; import { getSupabaseClient } from '../../supabase.js'; export type TemplateLogLevel = 'info' | 'warn' | 'error' | 'debug'; export type TemplateLogStatus = 'started' | 'accepted' | 'completed' | 'failed'; export type TemplateLogEntry = { agentId: string; agentName: string; namespace: string; runId: string; requestId: string; surface: string; event: string; action?: string; level: TemplateLogLevel; status: TemplateLogStatus; details?: Record; durationMs?: number; createdAt: string; }; export const TEMPLATE_LOG_PATH = join(homedir(), '.config', 'sin', TEMPLATE_AGENT_ID, 'events.ndjson'); export const TEMPLATE_TELEMETRY_TABLE = 'fleet_telemetry'; export class TemplateLogger { readonly runId: string; constructor(runId: string = randomUUID()) { this.runId = runId; } async log(input: { surface: string; event: string; level?: TemplateLogLevel; status?: TemplateLogStatus; action?: string; details?: Record; durationMs?: number; requestId?: string; }) { const entry: TemplateLogEntry = { agentId: TEMPLATE_AGENT_ID, agentName: TEMPLATE_AGENT_NAME, namespace: TEMPLATE_AGENT_NAMESPACE, runId: this.runId, requestId: input.requestId || randomUUID(), surface: input.surface, event: input.event, action: input.action, level: input.level || 'info', status: input.status || 'completed', details: input.details, durationMs: input.durationMs, createdAt: new Date().toISOString(), }; await mkdir(dirname(TEMPLATE_LOG_PATH), { recursive: true }); await appendFile(TEMPLATE_LOG_PATH, `${JSON.stringify(entry)}\n`, 'utf8'); try { await getSupabaseClient().from(TEMPLATE_TELEMETRY_TABLE).insert({ agent_id: entry.agentId, run_id: entry.runId, level: entry.level, event: entry.event, details: { ...(entry.details || {}), action: entry.action, requestId: entry.requestId, surface: entry.surface, status: entry.status, hostname: hostname(), }, duration_ms: entry.durationMs, created_at: entry.createdAt, }); } catch {} const line = `[${entry.createdAt}] [${entry.agentId}] [${entry.surface}] [${entry.level.toUpperCase()}] ${entry.event}`; if (entry.level === 'error') { console.error(line, entry.details || ''); } else if (entry.level === 'warn') { console.warn(line, entry.details || ''); } else { console.log(line, entry.details || ''); } return entry; } started(surface: string, event: string, details?: Record, requestId?: string, action?: string) { return this.log({ surface, event, details, requestId, action, level: 'info', status: 'started' }); } accepted(surface: string, event: string, details?: Record, requestId?: string, action?: string) { return this.log({ surface, event, details, requestId, action, level: 'info', status: 'accepted' }); } succeeded( surface: string, event: string, details?: Record, durationMs?: number, requestId?: string, action?: string, ) { return this.log({ surface, event, details, durationMs, requestId, action, level: 'info', status: 'completed', }); } failed(surface: string, event: string, error: unknown, durationMs?: number, requestId?: string, action?: string) { const details = error instanceof Error ? { error: error.message } : { error: String(error) }; return this.log({ surface, event, details, durationMs, requestId, action, level: 'error', status: 'failed', }); } } export function createTemplateLogger(runId?: string) { return new TemplateLogger(runId); }