05ed037fe8
- DNS: pilot.landvex.com -> 16.170.83.169 - TLS: Let's Encrypt certificate (expires 2026-09-30) - Nginx: reverse proxy with SSL termination - API: https://pilot.landvex.com/api/v1/missions - UI: https://pilot.landvex.com/ - Upload: POST /api/v1/missions/import (multipart/form-data) Verified: ✅ https://pilot.landvex.com/health ✅ https://pilot.landvex.com/version ✅ https://pilot.landvex.com/api/v1/missions (list) ✅ https://pilot.landvex.com/api/v1/missions/:id (get) ✅ POST /api/v1/missions/import (video upload) ✅ UI loads with title 'LandveX Intelligence Lab' Next: Pilot 001 — Break the system!
230 lines
6.1 KiB
JavaScript
230 lines
6.1 KiB
JavaScript
#!/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 };
|