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

321 lines (297 loc) 9.27 kB
/** * Converts AiwgEvent objects into platform-agnostic AiwgMessage structures * that platform adapters translate to native rendering. * * @implements @.aiwg/architecture/adrs/ADR-messaging-bot-mode.md */ import { Severity, ButtonStyle } from './types.mjs'; /** * @typedef {Object} AiwgMessage * @property {string} title * @property {string} body * @property {'info'|'warning'|'critical'} severity * @property {Array<{label: string, value: string, inline: boolean}>} fields * @property {Array<{id: string, label: string, style: string, command: string}>} [actions] * @property {string} [threadId] * @property {string} project * @property {string} timestamp * @property {string} [codeBlock] * @property {string} [linkUrl] * @property {string} [linkText] */ /** * Severity colors for Slack/Discord */ const SEVERITY_COLORS = { [Severity.INFO]: '#36a64f', // Green [Severity.WARNING]: '#daa520', // Goldenrod [Severity.CRITICAL]: '#dc3545', // Red }; /** * Severity emoji for Telegram */ const SEVERITY_EMOJI = { [Severity.INFO]: '\u2705', // Green check [Severity.WARNING]: '\u26a0\ufe0f', // Warning [Severity.CRITICAL]: '\ud83d\udea8', // Siren }; /** * Format an AiwgEvent into an AiwgMessage. * * @param {import('./event-bus.mjs').AiwgEvent} event * @returns {AiwgMessage} */ export function formatEvent(event) { const formatter = EVENT_FORMATTERS[event.topic] || formatGenericEvent; return formatter(event); } /** * Get the color for a severity level. * * @param {'info'|'warning'|'critical'} severity * @returns {string} */ export function getSeverityColor(severity) { return SEVERITY_COLORS[severity] || SEVERITY_COLORS[Severity.INFO]; } /** * Get the emoji for a severity level. * * @param {'info'|'warning'|'critical'} severity * @returns {string} */ export function getSeverityEmoji(severity) { return SEVERITY_EMOJI[severity] || SEVERITY_EMOJI[Severity.INFO]; } // ============================================================================ // Event-specific formatters // ============================================================================ const EVENT_FORMATTERS = { 'ralph.started': formatRalphStarted, 'ralph.iteration': formatRalphIteration, 'ralph.completed': formatRalphCompleted, 'ralph.failed': formatRalphFailed, 'ralph.aborted': formatRalphAborted, 'gate.pending': formatGatePending, 'gate.approved': formatGateApproved, 'gate.rejected': formatGateRejected, 'gate.timeout': formatGateTimeout, 'security.critical': formatSecurityCritical, 'health.degraded': formatHealthDegraded, 'health.recovered': formatHealthRecovered, 'daemon.started': formatDaemonStarted, }; function formatRalphStarted(event) { const d = event.details; return { title: `Ralph Loop Started`, body: event.summary, severity: Severity.INFO, fields: [ { label: 'Objective', value: d.objective || 'N/A', inline: false }, { label: 'Max Iterations', value: String(d.maxIterations || 'N/A'), inline: true }, { label: 'Loop ID', value: event.loopId || 'N/A', inline: true }, ], project: event.project, timestamp: event.timestamp, }; } function formatRalphIteration(event) { const d = event.details; const progress = d.progress != null ? `${d.progress}%` : 'N/A'; return { title: `Iteration ${d.iteration}/${d.maxIterations}`, body: event.summary, severity: d.progress >= 80 ? Severity.INFO : Severity.WARNING, fields: [ { label: 'Progress', value: progress, inline: true }, { label: 'Status', value: d.status || 'in_progress', inline: true }, ], threadId: event.loopId, project: event.project, timestamp: event.timestamp, }; } function formatRalphCompleted(event) { const d = event.details; return { title: `Ralph Loop Completed`, body: event.summary, severity: Severity.INFO, fields: [ { label: 'Iterations', value: String(d.iterations || 'N/A'), inline: true }, { label: 'Result', value: d.success ? 'Success' : 'Partial', inline: true }, { label: 'Loop ID', value: event.loopId || 'N/A', inline: true }, ], threadId: event.loopId, project: event.project, timestamp: event.timestamp, }; } function formatRalphFailed(event) { const d = event.details; return { title: `Ralph Loop Failed`, body: event.summary, severity: Severity.CRITICAL, fields: [ { label: 'Reason', value: d.reason || 'Unknown', inline: false }, { label: 'Iterations', value: String(d.iterations || 'N/A'), inline: true }, { label: 'Loop ID', value: event.loopId || 'N/A', inline: true }, ], codeBlock: d.lastError || undefined, threadId: event.loopId, project: event.project, timestamp: event.timestamp, }; } function formatRalphAborted(event) { return { title: `Ralph Loop Aborted`, body: event.summary, severity: Severity.WARNING, fields: [ { label: 'Reason', value: event.details.reason || 'User request', inline: false }, { label: 'Loop ID', value: event.loopId || 'N/A', inline: true }, ], threadId: event.loopId, project: event.project, timestamp: event.timestamp, }; } function formatGatePending(event) { const d = event.details; return { title: `HITL Gate: Approval Required`, body: event.summary, severity: Severity.WARNING, fields: [ { label: 'Gate', value: d.gateName || event.gateId || 'N/A', inline: true }, { label: 'Quality Score', value: d.qualityScore != null ? `${d.qualityScore}%` : 'N/A', inline: true }, { label: 'Artifacts', value: String(d.artifactCount || 0), inline: true }, ], actions: [ { id: `approve_${event.gateId}`, label: 'Approve', style: ButtonStyle.PRIMARY, command: `approve ${event.gateId}`, }, { id: `reject_${event.gateId}`, label: 'Reject', style: ButtonStyle.DANGER, command: `reject ${event.gateId}`, }, ], project: event.project, timestamp: event.timestamp, }; } function formatGateApproved(event) { return { title: `Gate Approved`, body: event.summary, severity: Severity.INFO, fields: [ { label: 'Gate', value: event.gateId || 'N/A', inline: true }, { label: 'Approved By', value: event.details.approvedBy || 'N/A', inline: true }, ], project: event.project, timestamp: event.timestamp, }; } function formatGateRejected(event) { return { title: `Gate Rejected`, body: event.summary, severity: Severity.WARNING, fields: [ { label: 'Gate', value: event.gateId || 'N/A', inline: true }, { label: 'Rejected By', value: event.details.rejectedBy || 'N/A', inline: true }, { label: 'Reason', value: event.details.reason || 'N/A', inline: false }, ], project: event.project, timestamp: event.timestamp, }; } function formatGateTimeout(event) { return { title: `Gate Timed Out`, body: event.summary, severity: Severity.CRITICAL, fields: [ { label: 'Gate', value: event.gateId || 'N/A', inline: true }, { label: 'Action', value: event.details.timeoutAction || 'block', inline: true }, ], project: event.project, timestamp: event.timestamp, }; } function formatSecurityCritical(event) { return { title: `Security Alert: Critical Finding`, body: event.summary, severity: Severity.CRITICAL, fields: [ { label: 'Finding', value: event.details.finding || 'N/A', inline: false }, { label: 'File', value: event.details.file || 'N/A', inline: true }, { label: 'Severity', value: 'CRITICAL', inline: true }, ], project: event.project, timestamp: event.timestamp, }; } function formatHealthDegraded(event) { return { title: `System Health Degraded`, body: event.summary, severity: Severity.WARNING, fields: [ { label: 'Component', value: event.details.component || 'N/A', inline: true }, { label: 'Status', value: event.details.status || 'degraded', inline: true }, ], project: event.project, timestamp: event.timestamp, }; } function formatHealthRecovered(event) { return { title: `System Health Recovered`, body: event.summary, severity: Severity.INFO, fields: [ { label: 'Component', value: event.details.component || 'N/A', inline: true }, ], project: event.project, timestamp: event.timestamp, }; } function formatDaemonStarted(event) { const d = event.details; return { title: `AIWG Daemon Started`, body: event.summary, severity: Severity.INFO, fields: [ { label: 'PID', value: String(d.pid || process.pid), inline: true }, { label: 'Adapters', value: (d.adapters || []).join(', ') || 'none', inline: true }, ], project: event.project, timestamp: event.timestamp, }; } function formatGenericEvent(event) { return { title: event.topic, body: event.summary, severity: event.severity || Severity.INFO, fields: Object.entries(event.details || {}).map(([key, value]) => ({ label: key, value: String(value), inline: true, })), project: event.project, timestamp: event.timestamp, }; }