316 lines
8.0 KiB
JavaScript
316 lines
8.0 KiB
JavaScript
|
|
#!/usr/bin/env node
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
// Agent Runtime v2 — Med Runtime Trace och Acceptance Tests
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
import { RuntimeTrace } from './runtime-trace.mjs';
|
||
|
|
|
||
|
|
const AGENT_ROLES = {
|
||
|
|
PLANNER: {
|
||
|
|
id: 'planner',
|
||
|
|
name: 'Planner',
|
||
|
|
description: 'Bryter ned arbete och planerar',
|
||
|
|
tools: ['plan', 'estimate', 'prioritize'],
|
||
|
|
permissions: ['read', 'plan'],
|
||
|
|
eosChecks: ['goal-alignment', 'dependency-check']
|
||
|
|
},
|
||
|
|
DEVELOPER: {
|
||
|
|
id: 'developer',
|
||
|
|
name: 'Developer',
|
||
|
|
description: 'Skriver kod',
|
||
|
|
tools: ['read', 'write', 'edit', 'exec'],
|
||
|
|
permissions: ['read', 'write', 'edit'],
|
||
|
|
eosChecks: ['no-hardcoded-secrets', 'test-required', 'no-direct-prod']
|
||
|
|
},
|
||
|
|
REVIEWER: {
|
||
|
|
id: 'reviewer',
|
||
|
|
name: 'Reviewer',
|
||
|
|
description: 'Granskar kod och kör EOS/SIL',
|
||
|
|
tools: ['read', 'analyze', 'verify'],
|
||
|
|
permissions: ['read', 'analyze'],
|
||
|
|
eosChecks: ['all-eos-rules', 'security-scan', 'dependency-audit']
|
||
|
|
},
|
||
|
|
OPERATOR: {
|
||
|
|
id: 'operator',
|
||
|
|
name: 'Operator',
|
||
|
|
description: 'Deployment, drift och rollback',
|
||
|
|
tools: ['deploy', 'monitor', 'rollback'],
|
||
|
|
permissions: ['read', 'deploy'],
|
||
|
|
eosChecks: ['readiness-gate', 'release-gate', 'backup-verified']
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
class AgentRuntimeV2 {
|
||
|
|
constructor(task) {
|
||
|
|
this.task = task;
|
||
|
|
this.trace = new RuntimeTrace(task.id);
|
||
|
|
this.currentRole = null;
|
||
|
|
this.context = {};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Kör en agent genom hela flödet med trace
|
||
|
|
*/
|
||
|
|
async execute() {
|
||
|
|
console.log(`=== Agent Runtime: ${this.task.id} ===`);
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Steg 1: Planner
|
||
|
|
await this.runPlanner();
|
||
|
|
|
||
|
|
// Steg 2: Context Builder
|
||
|
|
await this.buildContext();
|
||
|
|
|
||
|
|
// Steg 3: Project Memory
|
||
|
|
await this.queryProjectMemory();
|
||
|
|
|
||
|
|
// Steg 4: Knowledge Graph
|
||
|
|
await this.queryKnowledgeGraph();
|
||
|
|
|
||
|
|
// Steg 5: EOS Policy Check
|
||
|
|
const eosResult = await this.runEOSCheck();
|
||
|
|
if (!eosResult.passed) {
|
||
|
|
return this.block('EOS Policy Check failed', eosResult);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Steg 6: Developer
|
||
|
|
const devResult = await this.runDeveloper();
|
||
|
|
|
||
|
|
// Steg 7: Reviewer
|
||
|
|
const reviewResult = await this.runReviewer();
|
||
|
|
if (!reviewResult.passed) {
|
||
|
|
return this.block('Review failed', reviewResult);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Steg 8: Operator
|
||
|
|
const opResult = await this.runOperator();
|
||
|
|
|
||
|
|
// Steg 9: Commit
|
||
|
|
return this.commit();
|
||
|
|
|
||
|
|
} catch (error) {
|
||
|
|
return this.block(`Exception: ${error.message}`, { error: error.stack });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async runPlanner() {
|
||
|
|
this.currentRole = AGENT_ROLES.PLANNER;
|
||
|
|
const node = this.trace.addNode('PLANNER', { task: this.task.description });
|
||
|
|
|
||
|
|
// TODO: Implementera planering
|
||
|
|
this.context.plan = {
|
||
|
|
steps: [],
|
||
|
|
estimatedTime: null,
|
||
|
|
dependencies: []
|
||
|
|
};
|
||
|
|
|
||
|
|
this.trace.completeNode(node.id, { plan: this.context.plan });
|
||
|
|
return this.context.plan;
|
||
|
|
}
|
||
|
|
|
||
|
|
async buildContext() {
|
||
|
|
const node = this.trace.addNode('CONTEXT', { task: this.task.description });
|
||
|
|
|
||
|
|
// TODO: Samla kontext från Git, filer, etc.
|
||
|
|
this.context.git = {
|
||
|
|
branch: null,
|
||
|
|
uncommitted: null,
|
||
|
|
remote: null
|
||
|
|
};
|
||
|
|
|
||
|
|
this.context.confidence = 0.8; // TODO: Beräkna riktig confidence
|
||
|
|
|
||
|
|
this.trace.completeNode(node.id, {
|
||
|
|
confidence: this.context.confidence,
|
||
|
|
git: this.context.git
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
async queryProjectMemory() {
|
||
|
|
const node = this.trace.addNode('MEMORY', { queries: [] });
|
||
|
|
|
||
|
|
// TODO: Sök i MEMORY.md, dagliga filer, ADR
|
||
|
|
this.context.memory = {
|
||
|
|
relevantDecisions: [],
|
||
|
|
similarTasks: [],
|
||
|
|
lessonsLearned: []
|
||
|
|
};
|
||
|
|
|
||
|
|
this.trace.completeNode(node.id, { results: this.context.memory });
|
||
|
|
}
|
||
|
|
|
||
|
|
async queryKnowledgeGraph() {
|
||
|
|
const node = this.trace.addNode('KNOWLEDGE', { queries: [] });
|
||
|
|
|
||
|
|
// TODO: Sök i kunskapsgrafen
|
||
|
|
this.context.knowledge = {
|
||
|
|
relatedCapabilities: [],
|
||
|
|
dependencies: [],
|
||
|
|
impact: []
|
||
|
|
};
|
||
|
|
|
||
|
|
this.trace.completeNode(node.id, { results: this.context.knowledge });
|
||
|
|
}
|
||
|
|
|
||
|
|
async runEOSCheck() {
|
||
|
|
const node = this.trace.addNode('EOS', { rules: [] });
|
||
|
|
|
||
|
|
// TODO: Kör alla EOS-policyer
|
||
|
|
const checks = [
|
||
|
|
'no-hardcoded-secrets',
|
||
|
|
'no-direct-prod',
|
||
|
|
'test-required',
|
||
|
|
'git-required'
|
||
|
|
];
|
||
|
|
|
||
|
|
const results = checks.map(check => ({
|
||
|
|
check,
|
||
|
|
passed: true, // TODO: Verklig kontroll
|
||
|
|
evidence: null
|
||
|
|
}));
|
||
|
|
|
||
|
|
const allPassed = results.every(r => r.passed);
|
||
|
|
|
||
|
|
if (!allPassed) {
|
||
|
|
const failed = results.filter(r => !r.passed);
|
||
|
|
this.trace.blockNode(node.id, `Failed: ${failed.map(f => f.check).join(', ')}`);
|
||
|
|
|
||
|
|
return {
|
||
|
|
passed: false,
|
||
|
|
results
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
this.trace.completeNode(node.id, { passed: results.length, failed: 0 });
|
||
|
|
|
||
|
|
return {
|
||
|
|
passed: true,
|
||
|
|
results
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
async runDeveloper() {
|
||
|
|
this.currentRole = AGENT_ROLES.DEVELOPER;
|
||
|
|
const node = this.trace.addNode('DEVELOPER', { files: [] });
|
||
|
|
|
||
|
|
// TODO: Implementera kod
|
||
|
|
this.context.implementation = {
|
||
|
|
filesChanged: [],
|
||
|
|
testsAdded: [],
|
||
|
|
documentation: []
|
||
|
|
};
|
||
|
|
|
||
|
|
this.trace.completeNode(node.id, { implementation: this.context.implementation });
|
||
|
|
}
|
||
|
|
|
||
|
|
async runReviewer() {
|
||
|
|
this.currentRole = AGENT_ROLES.REVIEWER;
|
||
|
|
const node = this.trace.addNode('REVIEWER', { checks: [] });
|
||
|
|
|
||
|
|
// TODO: Granska kod
|
||
|
|
const checks = [
|
||
|
|
'code-quality',
|
||
|
|
'security-scan',
|
||
|
|
'eos-compliance',
|
||
|
|
'test-coverage'
|
||
|
|
];
|
||
|
|
|
||
|
|
const results = checks.map(check => ({
|
||
|
|
check,
|
||
|
|
passed: true, // TODO: Verklig kontroll
|
||
|
|
evidence: null
|
||
|
|
}));
|
||
|
|
|
||
|
|
const allPassed = results.every(r => r.passed);
|
||
|
|
|
||
|
|
if (!allPassed) {
|
||
|
|
const failed = results.filter(r => !r.passed);
|
||
|
|
this.trace.blockNode(node.id, `Failed: ${failed.map(f => f.check).join(', ')}`);
|
||
|
|
|
||
|
|
return {
|
||
|
|
passed: false,
|
||
|
|
results
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
this.trace.completeNode(node.id, { passed: results.length, failed: 0 });
|
||
|
|
|
||
|
|
return {
|
||
|
|
passed: true,
|
||
|
|
results
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
async runOperator() {
|
||
|
|
this.currentRole = AGENT_ROLES.OPERATOR;
|
||
|
|
const node = this.trace.addNode('OPERATOR', { environment: null });
|
||
|
|
|
||
|
|
// TODO: Förbered deployment
|
||
|
|
this.context.deployment = {
|
||
|
|
environment: null,
|
||
|
|
rollbackPlan: null,
|
||
|
|
healthChecks: []
|
||
|
|
};
|
||
|
|
|
||
|
|
this.trace.completeNode(node.id, { deployment: this.context.deployment });
|
||
|
|
}
|
||
|
|
|
||
|
|
async commit() {
|
||
|
|
const node = this.trace.addNode('COMMIT');
|
||
|
|
|
||
|
|
// TODO: Git commit
|
||
|
|
const result = {
|
||
|
|
status: 'success',
|
||
|
|
commitHash: null
|
||
|
|
};
|
||
|
|
|
||
|
|
this.trace.completeNode(node.id, result);
|
||
|
|
|
||
|
|
return {
|
||
|
|
status: 'success',
|
||
|
|
commitHash: null,
|
||
|
|
trace: this.trace.toJSON()
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
block(reason, details) {
|
||
|
|
// Hitta senaste nod och markera som blockerad
|
||
|
|
const lastNode = this.trace.nodes[this.trace.nodes.length - 1];
|
||
|
|
if (lastNode) {
|
||
|
|
this.trace.blockNode(lastNode.id, reason);
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
status: 'blocked',
|
||
|
|
reason,
|
||
|
|
details,
|
||
|
|
trace: this.trace.toJSON()
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Hämta trace för analys
|
||
|
|
*/
|
||
|
|
getTrace() {
|
||
|
|
return this.trace;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// CLI
|
||
|
|
if (process.argv[1] === new URL(import.meta.url).pathname) {
|
||
|
|
const runtime = new AgentRuntimeV2({
|
||
|
|
id: 'test-task',
|
||
|
|
description: 'Testa Agent Runtime v2'
|
||
|
|
});
|
||
|
|
|
||
|
|
runtime.execute().then(result => {
|
||
|
|
console.log('\n=== Resultat ===');
|
||
|
|
console.log(JSON.stringify(result, null, 2));
|
||
|
|
|
||
|
|
console.log('\n=== Trace ===');
|
||
|
|
console.log(runtime.getTrace().toText());
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
export { AgentRuntimeV2, AGENT_ROLES };
|