/** * System views: Runtime and Connectors. * @module modules/system * * Read-only truth, one source. Both views render `GET /api/runtime/status` and * nothing else: no config read, no client-side model registry, no credential * ever reaching the DOM. Editing daemon configuration from the browser is not a * capability of this surface; setup is a CLI action. */ /* eslint-env browser */ const RUNTIME_STATUS_ENDPOINT = '/api/runtime/status'; /** Shown when the daemon booted without a health service. Never a fake score. */ const HEALTH_UNAVAILABLE = 'unavailable'; export type RuntimeBackend = 'claude' | 'codex' | 'cline'; export type RuntimeConnectorState = 'connected' | 'disconnected' | 'unknown'; export interface RuntimeConnectorStatus { name: string; enabled: boolean; state: RuntimeConnectorState; } export interface RuntimeStatusSnapshot { running: boolean; backend: RuntimeBackend; model: string; startedAt: number; health: { score: number; status: string } | null; connectors: RuntimeConnectorStatus[]; } export interface SystemViewOptions { /** Test seam; defaults to fetching the authoritative endpoint. */ fetchStatus?: () => Promise; } export function escapeHtml(value: unknown): string { return String(value ?? '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } async function defaultFetchStatus(): Promise { const response = await fetch(RUNTIME_STATUS_ENDPOINT); if (!response.ok) { throw new Error('HTTP ' + response.status); } return (await response.json()) as RuntimeStatusSnapshot; } function skeleton(rows: number): string { let html = '
'; for (let i = 0; i < rows; i++) { html += '
'; } html += '
'; return html; } /** * Failure names the endpoint that failed and offers a retry. A System view that * silently shows nothing is indistinguishable from a healthy empty runtime. */ function errorCard(error: unknown): string { const detail = error instanceof Error ? error.message : String(error ?? 'Unknown error'); return ( '' ); } function formatStartedAt(startedAt: number): string { if (!Number.isFinite(startedAt) || startedAt <= 0) return 'unknown'; return new Date(startedAt).toLocaleString(); } function formatUptime(startedAt: number): string { if (!Number.isFinite(startedAt) || startedAt <= 0) return 'unknown'; const seconds = Math.max(0, Math.floor((Date.now() - startedAt) / 1000)); const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); const minutes = Math.floor((seconds % 3600) / 60); if (days > 0) return days + 'd ' + hours + 'h'; if (hours > 0) return hours + 'h ' + minutes + 'm'; return minutes + 'm'; } function statTile(label: string, value: string, tone = 'neutral'): string { return ( '
' + '' + escapeHtml(label) + '' + '' + escapeHtml(value) + '' + '
' ); } export function renderRuntimeSnapshot(snapshot: RuntimeStatusSnapshot): string { const health = snapshot.health; const healthValue = health ? health.status + ' - ' + health.score : HEALTH_UNAVAILABLE; const healthTone = health ? health.status === 'healthy' ? 'ok' : health.status === 'degraded' ? 'warn' : 'bad' : 'muted'; return ( '
' + '

Runtime

' + '

Reported by the running daemon; some fields reflect boot-time state. Configuration is edited via the CLI or the config file.

' + '
' + statTile('Daemon', snapshot.running ? 'running' : 'stopped', snapshot.running ? 'ok' : 'bad') + statTile('Backend', snapshot.backend) + statTile('Model', snapshot.model) + statTile('Health', healthValue, healthTone) + statTile('Uptime', formatUptime(snapshot.startedAt)) + statTile('Started', formatStartedAt(snapshot.startedAt)) + '
' + '
' ); } const CONNECTOR_STATE_TONE: Record = { connected: 'ok', disconnected: 'muted', unknown: 'warn', }; /** * What a connector state actually means. 'connected' is not a live probe: it * says the connector registered when the daemon booted, so the chip carries the * narrower claim rather than letting the word oversell itself. */ const CONNECTOR_STATE_TITLE: Record = { connected: 'Registered at daemon boot', disconnected: 'Disabled in config', unknown: 'Enabled in config but not registered at boot', }; export function renderConnectorsSnapshot(snapshot: RuntimeStatusSnapshot): string { const connectors = snapshot.connectors ?? []; if (connectors.length === 0) { return ( '
' + '

Connectors

' + '
' + '

No connectors configured

' + '

Add one from a terminal:

' + 'mama connector add <name>' + '
' + '
' ); } let rows = ''; for (const connector of connectors) { rows += '
  • ' + '' + '' + escapeHtml(connector.name) + '' + '' + escapeHtml(connector.state) + '' + (connector.enabled ? '' : 'off') + '
  • '; } return ( '
    ' + '

    Connectors

    ' + '

    Read-only. Add or remove connectors with mama connector add.

    ' + '
      ' + rows + '
    ' + '
    ' ); } async function renderView( container: HTMLElement | null, options: SystemViewOptions, toHtml: (snapshot: RuntimeStatusSnapshot) => string, skeletonRows: number ): Promise { if (!container) return; const fetchStatus = options.fetchStatus ?? defaultFetchStatus; container.innerHTML = skeleton(skeletonRows); try { const snapshot = await fetchStatus(); container.innerHTML = toHtml(snapshot); } catch (error) { container.innerHTML = errorCard(error); const retry = container.querySelector('[data-retry]'); if (retry) { retry.addEventListener('click', () => { void renderView(container, options, toHtml, skeletonRows); }); } } } export function renderRuntimeView( container: HTMLElement | null, options: SystemViewOptions = {} ): Promise { return renderView(container, options, renderRuntimeSnapshot, 3); } export function renderConnectorsView( container: HTMLElement | null, options: SystemViewOptions = {} ): Promise { return renderView(container, options, renderConnectorsSnapshot, 5); }