UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

143 lines 4.77 kB
/** * Telemetry Event Schema and In-Memory Store * * Defines the canonical event types emitted by the agent loop, MC conductor, * and ralph launcher. Events are stored in-memory per session and forwarded * to the browser via WebSocket push. Persisted to fortemi-react IndexedDB * on the browser side (#717). * * Emission points: * - src/cli/handlers/ralph-launcher.ts → iteration.*, gate.*, tokens.used * - src/cli/handlers/mc.ts → session.*, mission.*, agent.* * * @issue #716 * @see #717 — fortemi-react storage layer (browser) */ // ============================================================ // In-Memory Event Store // ============================================================ const MAX_EVENTS_PER_SESSION = 10_000; export class TelemetryStore { events = new Map(); listeners = new Set(); /** Ingest a new event */ ingest(event) { const list = this.events.get(event.sessionId) ?? []; list.push(event); // Trim oldest events when exceeding limit if (list.length > MAX_EVENTS_PER_SESSION) { list.splice(0, list.length - MAX_EVENTS_PER_SESSION); } this.events.set(event.sessionId, list); for (const listener of this.listeners) { try { listener(event); } catch { /* ignore listener errors */ } } } /** Get all events for a session, optionally filtered by type */ query(sessionId, opts = {}) { let events = this.events.get(sessionId) ?? []; if (opts.types?.length) { events = events.filter((e) => opts.types.includes(e.type)); } if (opts.since) { events = events.filter((e) => e.timestamp >= opts.since); } if (opts.limit) { events = events.slice(-opts.limit); } return events; } /** Get aggregate metrics for a session */ metrics(sessionId) { const events = this.events.get(sessionId) ?? []; let totalInput = 0; let totalOutput = 0; const tokensByModel = {}; let iterations = 0; let gatePasses = 0; let gateFails = 0; let scopeDone = 0; let scopeTotal = 0; let activeAgents = 0; for (const e of events) { if (e.type === 'tokens.used') { const p = e.payload; totalInput += p.input; totalOutput += p.output; const m = tokensByModel[p.model] ?? { input: 0, output: 0 }; m.input += p.input; m.output += p.output; tokensByModel[p.model] = m; } else if (e.type === 'iteration.complete') { iterations++; } else if (e.type === 'gate.pass') { gatePasses++; } else if (e.type === 'gate.fail') { gateFails++; } else if (e.type === 'scope.unit.complete') { const p = e.payload; scopeDone = p.done; scopeTotal = p.total; } else if (e.type === 'agent.spawn') { activeAgents++; } else if (e.type === 'agent.complete') { activeAgents = Math.max(0, activeAgents - 1); } } const passRate = gatePasses + gateFails > 0 ? Math.round((gatePasses / (gatePasses + gateFails)) * 100) : null; return { sessionId, totalInputTokens: totalInput, totalOutputTokens: totalOutput, tokensByModel, iterations, gatePasses, gateFails, passRate, scopeDone, scopeTotal, activeAgents, }; } /** Subscribe to new events (returns unsubscribe fn) */ subscribe(listener) { this.listeners.add(listener); return () => { this.listeners.delete(listener); }; } /** Clear all events for a session */ clear(sessionId) { this.events.delete(sessionId); } /** Get all known session IDs */ sessions() { return [...this.events.keys()]; } } // Singleton store export const telemetryStore = new TelemetryStore(); // ============================================================ // Event Factory // ============================================================ let eventCounter = 0; export function createEvent(type, sessionId, payload, missionId) { return { id: `evt-${Date.now()}-${++eventCounter}`, sessionId, missionId, timestamp: new Date().toISOString(), type, payload, }; } //# sourceMappingURL=telemetry.js.map