#!/usr/bin/env node // ═══════════════════════════════════════════════════════════════════════════ // Runtime Trace — Graf över agentens resonemang // "När något går fel ska man kunna spela upp exakt hur agenten resonerade" // ═══════════════════════════════════════════════════════════════════════════ class RuntimeTrace { constructor(taskId) { this.taskId = taskId; this.nodes = []; this.edges = []; this.startTime = Date.now(); } /** * Lägg till en nod i tracen */ addNode(phase, data = {}) { const node = { id: `${phase}-${this.nodes.length}`, phase, timestamp: Date.now() - this.startTime, data, status: 'running' }; this.nodes.push(node); // Skapa edge från föregående nod if (this.nodes.length > 1) { const prevNode = this.nodes[this.nodes.length - 2]; this.edges.push({ from: prevNode.id, to: node.id, type: 'sequence' }); } return node; } /** * Markera nod som klar */ completeNode(nodeId, result = {}) { const node = this.nodes.find(n => n.id === nodeId); if (node) { node.status = 'completed'; node.result = result; node.duration = Date.now() - this.startTime - node.timestamp; } return node; } /** * Markera nod som blockerad */ blockNode(nodeId, reason) { const node = this.nodes.find(n => n.id === nodeId); if (node) { node.status = 'blocked'; node.blockReason = reason; node.duration = Date.now() - this.startTime - node.timestamp; } return node; } /** * Markera nod som eskalerad */ escalateNode(nodeId, reason) { const node = this.nodes.find(n => n.id === nodeId); if (node) { node.status = 'escalated'; node.escalationReason = reason; node.duration = Date.now() - this.startTime - node.timestamp; } return node; } /** * Generera textuell representation */ toText() { const lines = [ `Task: ${this.taskId}`, `Duration: ${Date.now() - this.startTime}ms`, `Nodes: ${this.nodes.length}`, '' ]; for (const node of this.nodes) { const status = node.status === 'completed' ? '✅' : node.status === 'blocked' ? '❌' : node.status === 'escalated' ? '⚠️' : '⏳'; lines.push(`${status} ${node.phase} (${node.timestamp}ms)`); if (node.duration) { lines.push(` Duration: ${node.duration}ms`); } if (node.result) { lines.push(` Result: ${JSON.stringify(node.result)}`); } if (node.blockReason) { lines.push(` Blocked: ${node.blockReason}`); } if (node.escalationReason) { lines.push(` Escalated: ${node.escalationReason}`); } if (node.data && Object.keys(node.data).length > 0) { lines.push(` Data: ${JSON.stringify(node.data)}`); } } return lines.join('\n'); } /** * Generera JSON-representation */ toJSON() { return { taskId: this.taskId, startTime: this.startTime, endTime: Date.now(), duration: Date.now() - this.startTime, nodes: this.nodes, edges: this.edges }; } /** * Spela upp tracen steg för steg */ async replay(delayMs = 1000) { console.log(`\n=== Replaying Trace: ${this.taskId} ===\n`); for (const node of this.nodes) { const status = node.status === 'completed' ? '✅' : node.status === 'blocked' ? '❌' : node.status === 'escalated' ? '⚠️' : '⏳'; console.log(`${status} ${node.phase}`); if (node.blockReason) { console.log(` ❌ Blocked: ${node.blockReason}`); } if (node.escalationReason) { console.log(` ⚠️ Escalated: ${node.escalationReason}`); } await new Promise(resolve => setTimeout(resolve, delayMs)); } console.log('\n=== Replay Complete ==='); } /** * Hitta vägen till en specifik nod */ findPathTo(targetPhase) { const targetNode = this.nodes.find(n => n.phase === targetPhase); if (!targetNode) return null; const path = []; let current = targetNode; while (current) { path.unshift(current); const incomingEdge = this.edges.find(e => e.to === current.id); current = incomingEdge ? this.nodes.find(n => n.id === incomingEdge.from) : null; } return path; } /** * Hitta alla blockeringar */ findBlocks() { return this.nodes.filter(n => n.status === 'blocked'); } /** * Hitta alla eskaleringar */ findEscalations() { return this.nodes.filter(n => n.status === 'escalated'); } } // Exempel if (process.argv[1] === new URL(import.meta.url).pathname) { const trace = new RuntimeTrace('test-task'); // Simulera ett flöde const planner = trace.addNode('PLANNER', { steps: 3 }); trace.completeNode(planner.id, { plan: ['step1', 'step2', 'step3'] }); const context = trace.addNode('CONTEXT', { docs: 5 }); trace.completeNode(context.id, { confidence: 0.8 }); const memory = trace.addNode('MEMORY', { queries: 2 }); trace.completeNode(memory.id, { results: 3 }); const eos = trace.addNode('EOS', { rules: 12 }); trace.completeNode(eos.id, { passed: 12, failed: 0 }); const developer = trace.addNode('DEVELOPER', { files: 1 }); trace.completeNode(developer.id, { changed: 1 }); const reviewer = trace.addNode('REVIEWER', { checks: 5 }); trace.completeNode(reviewer.id, { passed: 5 }); const commit = trace.addNode('COMMIT'); trace.completeNode(commit.id, { hash: 'abc123' }); console.log(trace.toText()); console.log('\n=== JSON ==='); console.log(JSON.stringify(trace.toJSON(), null, 2)); } export { RuntimeTrace };