/** * Flynn Live Ops Dashboard * * Shows core counters, model performance, event stream, active requests, * and channel status. Fast metrics refresh every 3s, slow health every 10s. */ let _fastTimer = null; let _slowTimer = null; let _dashboardClient = null; let _lastPlaybookRollbackPatches = null; let _lastBriefingTestAt = null; let _assistantSaveState = null; let _lastAssistantConfig = null; let _assistantManualOverrides = new Set(); let _assistantModelDefaultsDraft = null; let _assistantDraftState = new Map(); let _assistantDraftTouchedAt = 0; let _lastCouncilTask = ''; let _lastCouncilResult = null; let _lastCouncilError = null; let _lastServices = []; let _serviceConfigState = { open: false, serviceName: null, status: null, tone: 'neutral', advancedPatch: '', }; const MODEL_DEFAULT_TASK_KEYS = ['compaction', 'memory_extraction', 'classification', 'tool_summarisation', 'complex_reasoning']; const MODEL_DEFAULT_TIER_KEYS = ['default', 'fast', 'complex', 'local']; const HEARTBEAT_CHECK_KEYS = ['gateway', 'model', 'channels', 'memory', 'disk', 'process_memory', 'backup', 'provider_errors']; const SERVICE_TOGGLE_PATCH_PATHS = { heartbeat: 'automation.heartbeat.enabled', daily_briefing: 'automation.daily_briefing.enabled', gmail: 'automation.gmail.enabled', gcal: 'automation.gcal.enabled', gdocs: 'automation.gdocs.enabled', gdrive: 'automation.gdrive.enabled', gtasks: 'automation.gtasks.enabled', backup: 'backup.enabled', audio_transcription: 'audio.enabled', sandbox: 'sandbox.enabled', }; const ASSISTANT_DRAFT_TTL_MS = 2 * 60 * 1000; function formatUptime(seconds) { const d = Math.floor(seconds / 86400); const h = Math.floor((seconds % 86400) / 3600); const m = Math.floor((seconds % 3600) / 60); const s = seconds % 60; const parts = []; if (d > 0) {parts.push(`${d}d`);} if (h > 0) {parts.push(`${h}h`);} if (m > 0) {parts.push(`${m}m`);} parts.push(`${s}s`); return parts.join(' '); } function timeAgo(timestamp) { const secs = Math.floor((Date.now() - timestamp) / 1000); if (secs < 60) {return `${secs}s ago`;} if (secs < 3600) {return `${Math.floor(secs / 60)}m ago`;} return `${Math.floor(secs / 3600)}h ago`; } function formatTime(timestamp) { const d = new Date(timestamp); return d.toLocaleTimeString('en-GB', { hour12: false }); } function formatDay(day) { const parsed = new Date(`${day}T00:00:00`); if (Number.isNaN(parsed.getTime())) {return day;} return parsed.toLocaleDateString('en-GB', { day: '2-digit', month: 'short' }); } function formatNumber(value) { return (value ?? 0).toLocaleString(); } function formatSessionDurationFromMessages(avgMessagesPerSession) { if (!avgMessagesPerSession || avgMessagesPerSession <= 0) {return '—';} return `${avgMessagesPerSession.toFixed(1)} msgs/session`; } function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } function getAssistantStateSnapshot(configData) { const automation = configData?.automation ?? {}; const memory = configData?.memory ?? {}; const tts = configData?.tts ?? {}; const queue = configData?.server?.queue ?? {}; return { announce: (automation.delivery_mode ?? 'shared_session') === 'announce', dailyBriefing: Boolean(automation.daily_briefing?.enabled), memoryDaily: Boolean(memory.daily_log?.enabled), memoryProactive: Boolean(memory.proactive_extract?.enabled), memoryMinToolCalls: Number(memory.proactive_extract?.min_tool_calls ?? 1), ttsEnabled: Boolean(tts.enabled), ttsChannels: Array.isArray(tts.enabled_channels) ? tts.enabled_channels : [], queueMode: queue.mode ?? 'collect', }; } function buildPlaybookPatches(playbook) { if (playbook === 'executive') { const patches = { 'automation.delivery_mode': 'announce', 'automation.daily_briefing.enabled': true, 'memory.daily_log.enabled': true, 'memory.proactive_extract.enabled': true, 'memory.proactive_extract.min_tool_calls': 1, 'tts.enabled': true, 'tts.enabled_channels': [], 'server.queue.mode': 'interrupt', }; for (const key of _assistantManualOverrides) { delete patches[key]; } return patches; } if (playbook === 'operator') { const patches = { 'automation.delivery_mode': 'announce', 'automation.daily_briefing.enabled': true, 'memory.daily_log.enabled': true, 'memory.proactive_extract.enabled': true, 'memory.proactive_extract.min_tool_calls': 2, 'tts.enabled': false, 'server.queue.mode': 'steer_backlog', }; for (const key of _assistantManualOverrides) { delete patches[key]; } return patches; } const patches = { 'automation.delivery_mode': 'shared_session', 'automation.daily_briefing.enabled': false, 'memory.daily_log.enabled': false, 'memory.proactive_extract.enabled': false, 'memory.proactive_extract.min_tool_calls': 3, 'tts.enabled': false, 'server.queue.mode': 'collect', }; for (const key of _assistantManualOverrides) { delete patches[key]; } return patches; } function buildRollbackPatchesFromSnapshot(snapshot) { return { 'automation.delivery_mode': snapshot.announce ? 'announce' : 'shared_session', 'automation.daily_briefing.enabled': snapshot.dailyBriefing, 'memory.daily_log.enabled': snapshot.memoryDaily, 'memory.proactive_extract.enabled': snapshot.memoryProactive, 'memory.proactive_extract.min_tool_calls': Number.isFinite(snapshot.memoryMinToolCalls) ? snapshot.memoryMinToolCalls : 1, 'tts.enabled': snapshot.ttsEnabled, 'tts.enabled_channels': snapshot.ttsChannels, 'server.queue.mode': snapshot.queueMode, }; } function getByPath(obj, dottedPath) { if (!obj || typeof obj !== 'object') {return undefined;} const parts = dottedPath.split('.'); let cursor = obj; for (const part of parts) { if (!cursor || typeof cursor !== 'object' || !(part in cursor)) { return undefined; } cursor = cursor[part]; } return cursor; } function valuesMatch(expected, actual) { if (Array.isArray(expected) && Array.isArray(actual)) { if (expected.length !== actual.length) {return false;} for (let i = 0; i < expected.length; i++) { if (expected[i] !== actual[i]) {return false;} } return true; } return expected === actual; } function valuesMatchForPath(path, expected, actual) { if (valuesMatch(expected, actual)) { return true; } // Some optional string paths are normalized by config handlers: // writing "" is persisted as "unset" (undefined). if (typeof expected === 'string' && expected.trim().length === 0 && (actual === undefined || actual === null)) { const optionalStringSuffixes = ['scaffold_path']; if (optionalStringSuffixes.some((suffix) => path.endsWith(suffix))) { return true; } } return false; } function setAssistantSaveState(message, tone = 'neutral') { _assistantSaveState = { message, tone, at: Date.now(), }; } function writeAssistantDraftValue(control) { if (!control || !control.id) {return;} const isCheckbox = control.tagName === 'INPUT' && control.type === 'checkbox'; if (isCheckbox) { _assistantDraftState.set(control.id, { kind: 'checkbox', value: Boolean(control.checked) }); } else if (control.tagName === 'SELECT') { const selectedOption = Array.from(control.options ?? []).find((option) => option.selected); _assistantDraftState.set(control.id, { kind: 'value', value: selectedOption?.value ?? '' }); } else { _assistantDraftState.set(control.id, { kind: 'value', value: control.value ?? '' }); } _assistantDraftTouchedAt = Date.now(); } function bindAssistantDraftTracking(rootEl) { const controls = rootEl.querySelectorAll('input[id], select[id], textarea[id]'); controls.forEach((control) => { control.addEventListener('input', () => writeAssistantDraftValue(control)); control.addEventListener('change', () => writeAssistantDraftValue(control)); }); } function applyAssistantDraftState(rootEl) { if (_assistantDraftState.size === 0) {return;} const now = Date.now(); if (_assistantDraftTouchedAt > 0 && (now - _assistantDraftTouchedAt) > ASSISTANT_DRAFT_TTL_MS) { _assistantDraftState = new Map(); _assistantDraftTouchedAt = 0; return; } for (const [id, draft] of _assistantDraftState.entries()) { const control = rootEl.querySelector(`#${id}`); if (!control) {continue;} const isCheckbox = control.tagName === 'INPUT' && control.type === 'checkbox'; if (draft.kind === 'checkbox' && isCheckbox) { control.checked = Boolean(draft.value); } else if (control.tagName === 'SELECT') { const options = Array.from(control.options ?? []); const desired = String(draft.value ?? ''); let matchedIndex = -1; for (let i = 0; i < options.length; i++) { const option = options[i]; const isSelected = option.value === desired; option.selected = isSelected; if (isSelected) { matchedIndex = i; } } if (options.length > 0) { const fallbackIndex = matchedIndex >= 0 ? matchedIndex : 0; options[fallbackIndex].selected = true; if ('selectedIndex' in control) { control.selectedIndex = fallbackIndex; } } try { control.value = desired; } catch { // Some DOM shims expose readonly value on select nodes. } } else if ('value' in control) { try { control.value = String(draft.value ?? ''); } catch { // Ignore rare non-writable value surfaces from non-browser DOM shims. } } } } function renderAssistantSaveState() { if (!_assistantSaveState) { return '
No recent save action.
'; } const toneClass = _assistantSaveState.tone === 'success' ? 'text-green-500' : _assistantSaveState.tone === 'warning' ? 'text-amber-500' : _assistantSaveState.tone === 'error' ? 'text-red-500' : 'text-zinc-500'; const at = new Date(_assistantSaveState.at).toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit', second: '2-digit', }); return `
${escapeHtml(_assistantSaveState.message)} (at ${escapeHtml(at)})
`; } function extractCouncilResultFromOutput(output) { if (typeof output !== 'string' || output.trim().length === 0) { return null; } const marker = '{"pipeline_version"'; const idx = output.lastIndexOf(marker); if (idx < 0) { return null; } try { return JSON.parse(output.slice(idx)); } catch { return null; } } // ── Initial full render ───────────────────────────────────────── function renderSkeleton(el) { el.innerHTML = `

Live Ops Dashboard

Core Counters

Loading...

Model Performance

Loading...

Session Analytics

Loading...

Context Health

Loading...

Assistant Health

Loading...

Event Stream

Loading events...

Active Requests

Loading...

Services

Click a service card to configure.
Loading...
`; } // ── Section updaters (targeted DOM updates) ───────────────────── function updateCounters(metrics, health) { const el = document.getElementById('ops-counters'); if (!el) {return;} const sessions = health?.sessions ?? 0; const errCount = metrics?.errors ?? 0; const cards = [ { label: 'Messages Processed', value: String(metrics?.messagesProcessed ?? 0), cls: '' }, { label: 'Active Sessions', value: String(sessions), cls: '' }, { label: 'Queue Depth', value: String(metrics?.queueDepth ?? 0), cls: '' }, { label: 'Uptime', value: formatUptime(metrics?.uptime ?? 0), cls: '' }, { label: 'Active Requests', value: String(metrics?.activeRequests ?? 0), cls: '' }, { label: 'Errors', value: String(errCount), cls: errCount > 0 ? 'text-red-500' : '' }, ]; el.innerHTML = cards.map(c => `
${c.label}
${c.value}
`, ).join(''); } function updateModelTable(metrics) { const el = document.getElementById('ops-model-table'); if (!el) {return;} const mc = metrics?.modelCalls; const calls = mc?.recentCalls ?? []; if (calls.length === 0) { el.innerHTML = '
No model calls recorded yet
'; return; } const totalCalls = mc.total ?? 0; const avgLatency = mc.avgLatency ?? 0; const errorRate = mc.errorRate ?? 0; const summaryHtml = `
Total Calls: ${totalCalls}
Avg Latency: ${avgLatency}ms
Error Rate: ${(errorRate * 100).toFixed(2)}%
`; // Show newest first const rows = [...calls].reverse().map(c => { const status = c.error ? '' : ''; return ` ${timeAgo(c.timestamp)} ${escapeHtml(c.provider)} ${c.latency}ms ${c.tokensPerSec.toFixed(1)} ${c.inputTokens}/${c.outputTokens} ${status} `; }).join(''); el.innerHTML = ` ${summaryHtml}
${rows}
Time Provider Latency Tokens/sec In/Out Status
`; } function updateEvents(eventsData) { const el = document.getElementById('ops-events'); if (!el) {return;} const events = eventsData?.events ?? []; if (events.length === 0) { el.innerHTML = '
No events recorded yet
'; return; } // Events come newest-first from the API; show newest at bottom for log feel const reversed = [...events].reverse(); el.innerHTML = reversed.map(e => { const time = formatTime(e.timestamp); const level = (e.level || 'info').toUpperCase(); const levelColor = e.level === 'error' ? 'text-red-500' : e.level === 'warn' ? 'text-amber-500' : 'text-zinc-400'; return `
[${time}] [${level}] ${escapeHtml(e.source)}: ${escapeHtml(e.message)}
`; }).join(''); // Auto-scroll to bottom el.scrollTop = el.scrollHeight; } function updateActiveRequests(requestsData) { const el = document.getElementById('ops-requests'); if (!el) {return;} const requests = requestsData?.requests ?? []; if (requests.length === 0) { el.innerHTML = '
No active requests
'; return; } const rows = requests.map(r => { const duration = r.durationMs < 1000 ? `${r.durationMs}ms` : `${(r.durationMs / 1000).toFixed(1)}s`; const started = formatTime(r.startedAt); return ` ${escapeHtml(r.sessionId)} ${escapeHtml(r.channel)} ${duration} ${started} `; }).join(''); el.innerHTML = `
${rows}
Session Channel Duration Started
`; } function updateSessionAnalytics(analyticsData) { const el = document.getElementById('ops-session-analytics'); if (!el) {return;} const daily = analyticsData?.daily ?? []; const topSessions = analyticsData?.topSessions ?? []; const topTools = analyticsData?.topTools ?? []; const topTopics = analyticsData?.topTopics ?? []; const totalSessions = analyticsData?.totalSessions ?? 0; const totalMessages = analyticsData?.totalMessages ?? 0; const avgMessagesPerSession = analyticsData?.averageMessagesPerSession ?? 0; const summaryHtml = `
Sessions (Window)
${formatNumber(totalSessions)}
Messages (Window)
${formatNumber(totalMessages)}
Avg Session
${formatSessionDurationFromMessages(avgMessagesPerSession)}
`; const topToolsHtml = topTools.length > 0 ? `
${topTools.map((tool) => `
${escapeHtml(tool.toolName)} (${formatNumber(tool.executions)})
`, ).join('')}
` : '
No tool usage captured in this window
'; const topTopicsHtml = topTopics.length > 0 ? `
${topTopics.map((topic) => `
${escapeHtml(topic.topic)} (${formatNumber(topic.occurrences)})
`, ).join('')}
` : '
No indexed topics captured in this window
'; const topSessionsHtml = topSessions.length > 0 ? `
${topSessions.map((session) => ` `).join('')}
Session Messages Last Active
${escapeHtml(session.sessionId)} ${formatNumber(session.messages)} ${timeAgo(session.lastActivity * 1000)}
` : '
No session activity in this window
'; const dailyHtml = daily.length > 0 ? `
${daily.slice(0, 7).map((row) => ` `).join('')}
Day Sessions Messages
${formatDay(row.day)} ${formatNumber(row.sessions)} ${formatNumber(row.messages)}
` : '
No daily activity in this window
'; el.innerHTML = ` ${summaryHtml}
Top Tools
${topToolsHtml}
Top Topics
${topTopicsHtml}
Top Sessions
${topSessionsHtml}
Daily Trend (Last 7 Rows)
${dailyHtml}
`; } function updateContextHealth(contextData) { const el = document.getElementById('ops-context-health'); if (!el) {return;} const sessions = contextData?.sessions ?? []; if (sessions.length === 0) { el.innerHTML = '
No active context usage snapshots
'; return; } const sorted = [...sessions].sort((a, b) => (b.budget?.usagePct ?? 0) - (a.budget?.usagePct ?? 0)); const top = sorted.slice(0, 8); const highest = top[0]?.budget?.usagePct ?? 0; const overThreshold = sessions.filter(s => (s.budget?.shouldCompact ?? false)).length; const summary = `
Highest Usage
${highest.toFixed(1)}%
Sessions Near Limit
${overThreshold}
Active Snapshots
${sessions.length}
`; const rows = top.map((entry) => { const budget = entry.budget ?? {}; const usage = budget.usagePct ?? 0; const cls = usage >= 95 ? 'text-red-500' : usage >= 85 ? 'text-amber-500' : ''; return ` ${escapeHtml(entry.sessionId)} ${usage.toFixed(1)}% ${formatNumber(budget.estimatedTokens ?? 0)} / ${formatNumber(budget.contextWindow ?? 0)} ${budget.shouldCompact ? 'yes' : 'no'} `; }).join(''); el.innerHTML = ` ${summary}
${rows}
Session Usage Estimated Tokens Should Compact
`; } async function applyAssistantPatch(patches, statusEl) { if (!_dashboardClient) { setAssistantSaveState('Save skipped: dashboard client unavailable.', 'error'); return; } if (statusEl) { statusEl.textContent = 'Saving...'; statusEl.className = 'text-sm text-zinc-500'; } try { const result = await _dashboardClient.call('config.patch', { patches }); const rejected = result?.rejected ?? []; const persistError = result?.persistError; const applied = result?.applied ?? []; const persisted = result?.persisted === true; let message = ''; let tone = 'neutral'; if (statusEl) { if (persistError) { message = `Save failed: ${persistError}`; tone = 'error'; } else if (rejected.length > 0) { message = `Rejected: ${rejected.join(', ')}`; tone = 'error'; } else if (!persisted) { message = `Runtime-only save (${applied.length} updated, file persistence unavailable)`; tone = 'warning'; } else { // Verify read-after-write so UI cannot claim persistence when value did not stick. try { const fresh = await _dashboardClient.call('config.get'); const mismatches = []; for (const [key, value] of Object.entries(patches)) { const actual = getByPath(fresh, key); if (!valuesMatchForPath(key, value, actual)) { mismatches.push(`${key} expected=${JSON.stringify(value)} actual=${JSON.stringify(actual)}`); } } if (mismatches.length > 0) { message = `Saved response received but read-back mismatch: ${mismatches.join('; ')}`; tone = 'error'; } else { message = `Saved to runtime + config file (${applied.length} updated)`; tone = 'success'; } } catch (verifyError) { message = `Saved response received, but verification failed: ${verifyError instanceof Error ? verifyError.message : String(verifyError)}`; tone = 'warning'; } } setAssistantSaveState(message, tone); statusEl.textContent = message; statusEl.className = `text-sm ${tone === 'success' ? 'text-green-500' : tone === 'warning' ? 'text-amber-500' : tone === 'error' ? 'text-red-500' : 'text-zinc-500'}`; } return { persisted, applied, rejected, persistError }; } catch (error) { const msg = `Save error: ${error instanceof Error ? error.message : String(error)}`; setAssistantSaveState(msg, 'error'); if (statusEl) { statusEl.textContent = msg; statusEl.className = 'text-sm text-red-500'; } return { persisted: false, applied: [], rejected: [], persistError: msg }; } } async function triggerDailyBriefingTest(jobName, statusEl) { if (!_dashboardClient) {return;} if (statusEl) { statusEl.textContent = 'Triggering test briefing...'; statusEl.className = 'text-sm text-zinc-500'; } try { const result = await _dashboardClient.call('tools.invoke', { tool: 'cron.trigger', args: { name: jobName }, }); if (result?.success) { const output = typeof result.output === 'string' ? result.output : 'Triggered.'; if (statusEl) { statusEl.textContent = output; statusEl.className = 'text-sm text-green-500'; } _lastBriefingTestAt = Date.now(); return true; } if (statusEl) { statusEl.textContent = result?.error ?? 'Failed to trigger briefing.'; statusEl.className = 'text-sm text-red-500'; } return false; } catch (error) { if (statusEl) { statusEl.textContent = `Trigger error: ${error instanceof Error ? error.message : String(error)}`; statusEl.className = 'text-sm text-red-500'; } return false; } } async function triggerCouncilRun(task, statusEl) { if (!_dashboardClient) {return false;} if (!task) { if (statusEl) { statusEl.textContent = 'Task is required.'; statusEl.className = 'text-sm text-red-500'; } return false; } if (statusEl) { statusEl.textContent = 'Running council...'; statusEl.className = 'text-sm text-zinc-500'; } try { const result = await _dashboardClient.call('tools.invoke', { tool: 'council.run', args: { task }, }); if (!result?.success) { _lastCouncilError = result?.error ?? 'Council run failed.'; _lastCouncilResult = null; if (statusEl) { statusEl.textContent = _lastCouncilError; statusEl.className = 'text-sm text-red-500'; } return false; } const parsed = extractCouncilResultFromOutput(result.output); if (!parsed) { _lastCouncilError = 'Council run succeeded but output could not be parsed.'; _lastCouncilResult = null; if (statusEl) { statusEl.textContent = _lastCouncilError; statusEl.className = 'text-sm text-amber-500'; } return false; } _lastCouncilTask = task; _lastCouncilResult = parsed; _lastCouncilError = null; if (statusEl) { statusEl.textContent = `Council run complete: ${parsed.stop_snapshot?.stop_reason ?? 'ok'}`; statusEl.className = 'text-sm text-green-500'; } return true; } catch (error) { _lastCouncilError = `Council run error: ${error instanceof Error ? error.message : String(error)}`; _lastCouncilResult = null; if (statusEl) { statusEl.textContent = _lastCouncilError; statusEl.className = 'text-sm text-red-500'; } return false; } } function updateAssistantHealth(configData) { const el = document.getElementById('ops-assistant-health'); if (!el) {return;} _assistantModelDefaultsDraft = readAssistantModelDefaultsDraft(el) ?? _assistantModelDefaultsDraft; const snapshot = getAssistantStateSnapshot(configData); const automation = configData?.automation ?? {}; const memory = configData?.memory ?? {}; const tts = configData?.tts ?? {}; const deliveryMode = automation.delivery_mode ?? 'shared_session'; const announce = deliveryMode === 'announce'; const dailyBriefing = Boolean(automation.daily_briefing?.enabled); const memoryDaily = Boolean(memory.daily_log?.enabled); const memoryProactive = Boolean(memory.proactive_extract?.enabled); const proactiveThreshold = Number(memory.proactive_extract?.min_tool_calls ?? 1); const ttsEnabled = Boolean(tts.enabled); const briefing = automation.daily_briefing ?? {}; const briefingName = briefing.name ?? 'daily-briefing'; const briefingSchedule = briefing.schedule ?? '0 8 * * *'; const briefingPrompt = briefing.prompt ?? ''; const briefingOutput = briefing.output ?? null; const briefingModelTier = briefing.model_tier ?? 'default'; const briefingTimezone = briefing.timezone ?? 'system'; const briefingOutputLabel = briefingOutput?.channel && briefingOutput?.peer ? `${briefingOutput.channel}/${briefingOutput.peer}` : 'not configured'; const briefingReady = dailyBriefing && Boolean(briefingOutput?.channel && briefingOutput?.peer); const playbookLikeReady = announce || (memoryDaily && memoryProactive); const modelTier = _assistantModelDefaultsDraft?.primaryTier ?? configData?.agents?.primary_tier ?? 'default'; const delegation = configData?.agents?.delegation ?? {}; const backgroundModels = configData?.agents?.background_models ?? {}; const councils = configData?.councils ?? {}; const councilsDefaults = councils.defaults ?? {}; const councilsGroups = councils.groups ?? {}; const councilsD = councilsGroups.D ?? {}; const councilsP = councilsGroups.P ?? {}; const tiers = configData?.models ?? {}; const modelCatalog = configData?.__modelCatalog ?? []; const providerList = modelCatalog.length > 0 ? modelCatalog.map((entry) => entry.provider) : ['anthropic', 'openai', 'gemini', 'openrouter', 'github', 'xai', 'ollama', 'llamacpp', 'bedrock', 'zhipuai', 'minimax', 'moonshot', 'synthetic']; const modelOptionsByProvider = Object.fromEntries(modelCatalog.map((entry) => [entry.provider, entry.models ?? []])); const checklistRows = [ { label: 'Set briefing output channel + peer', done: Boolean(briefingOutput?.channel && briefingOutput?.peer) }, { label: 'Enable assistant behavior profile', done: playbookLikeReady }, { label: 'Send a test morning briefing', done: _lastBriefingTestAt !== null }, ]; const chip = (label, value) => `
${escapeHtml(label)} ${value ? 'ON' : 'OFF'}
`; const tierOption = (selected) => ['fast', 'default', 'complex', 'local'] .map((tier) => ``) .join(''); const providerOption = (selected) => providerList .map((provider) => ``) .join(''); const modelDataList = (id, provider, selected) => { const options = modelOptionsByProvider[provider] ?? []; return ` ${options.map((model) => ``).join('')} `; }; const taskRows = [ { key: 'compaction', label: 'Compaction' }, { key: 'memory_extraction', label: 'Memory extraction' }, { key: 'classification', label: 'Classification' }, { key: 'tool_summarisation', label: 'Tool summarisation' }, { key: 'complex_reasoning', label: 'Complex reasoning' }, ]; const councilConversations = Array.isArray(_lastCouncilResult?.conversations) ? _lastCouncilResult.conversations : []; const councilSummary = _lastCouncilResult?.stop_snapshot ? `Last run: ${_lastCouncilResult.stop_snapshot.stop_reason} (round ${_lastCouncilResult.stop_snapshot.round_reached})` : (_lastCouncilError ? `Last run failed: ${_lastCouncilError}` : 'No council run yet in this dashboard session.'); el.innerHTML = `
${chip('Announce Mode', announce)} ${chip('Daily Briefing', dailyBriefing)} ${chip('Memory Daily Log', memoryDaily)} ${chip('Proactive Extract', memoryProactive)} ${chip('TTS Replies', ttsEnabled)}
Extract Threshold ${Number.isFinite(proactiveThreshold) ? proactiveThreshold : 1}
Assistant Playbooks
Executive: announce + voice + aggressive interrupt. Operator: announce + memory-first + steer backlog. Focus: reactive, quieter mode.
Model Tier Defaults
Tier provider/model definitions
${MODEL_DEFAULT_TIER_KEYS.map((tier) => { const cfg = tiers?.[tier] ?? {}; const provider = _assistantModelDefaultsDraft?.tiers?.[tier]?.provider ?? cfg.provider ?? tiers?.default?.provider ?? 'openai'; const model = _assistantModelDefaultsDraft?.tiers?.[tier]?.model ?? cfg.model ?? ''; return `
${escapeHtml(tier)} tier
`; }).join('')}
Delegation tiers + background model overrides
${taskRows.map((task) => { const background = backgroundModels?.[task.key] ?? {}; const draftTask = _assistantModelDefaultsDraft?.tasks?.[task.key] ?? {}; const delegationTier = draftTask.delegationTier ?? delegation?.[task.key] ?? 'fast'; const backgroundEnabled = draftTask.backgroundEnabled ?? Boolean(backgroundModels?.[task.key] && background?.enabled !== false); return `
${escapeHtml(task.label)}
`; }).join('')}
Councils
On-demand council orchestration settings and council role model tiers.
Council Conversations
${escapeHtml(councilSummary)}
${councilConversations.length === 0 ? '
No conversation log yet.
' : councilConversations.map((turn, idx) => `
#${idx + 1} ${escapeHtml(turn.call_id)} · ${escapeHtml(turn.agent)} @ ${escapeHtml(turn.tier)}
Prompt payload
${escapeHtml(JSON.stringify(turn.prompt_payload, null, 2))}
Response
${escapeHtml(String(turn.response ?? ''))}
`).join('')}
Assistant Activation Checklist
${checklistRows.map((row) => `
${row.done ? '✓' : '○'} ${escapeHtml(row.label)}
`).join('')}
Morning Brief Preview
name: ${escapeHtml(briefingName)} schedule: ${escapeHtml(briefingSchedule)} timezone: ${escapeHtml(briefingTimezone)} tier: ${escapeHtml(briefingModelTier)} output: ${escapeHtml(briefingOutputLabel)}
${escapeHtml(briefingPrompt || 'No daily briefing prompt configured.')}
${briefingReady ? '' : '
Enable daily briefing and set output channel/peer to test-send.
'}
${renderAssistantSaveState()} `; // Preserve local unsaved form edits across periodic dashboard refreshes. applyAssistantDraftState(el); const updateModelOptions = (inputId, provider) => { const input = el.querySelector(`#${inputId}`); const list = el.querySelector(`#${inputId}-list`); if (!input || !list) {return;} const options = modelOptionsByProvider[provider] ?? []; list.innerHTML = options.map((model) => ``).join(''); }; const tierRows = ['default', 'fast', 'complex', 'local']; for (const tier of tierRows) { const providerSelect = el.querySelector(`#assist-tier-${tier}-provider`); if (!providerSelect) {continue;} providerSelect.addEventListener('change', () => { updateModelOptions(`assist-tier-${tier}-model`, providerSelect.value); }); } const taskRowsForModels = MODEL_DEFAULT_TASK_KEYS; for (const task of taskRowsForModels) { const providerSelect = el.querySelector(`#assist-bg-${task}-provider`); if (!providerSelect) {continue;} providerSelect.addEventListener('change', () => { updateModelOptions(`assist-bg-${task}-model`, providerSelect.value); }); } // Refresh datalist options after draft re-application in case provider selects were restored. for (const tier of tierRows) { const providerSelect = el.querySelector(`#assist-tier-${tier}-provider`); if (!providerSelect) {continue;} updateModelOptions(`assist-tier-${tier}-model`, providerSelect.value); } for (const task of taskRowsForModels) { const providerSelect = el.querySelector(`#assist-bg-${task}-provider`); if (!providerSelect) {continue;} updateModelOptions(`assist-bg-${task}-model`, providerSelect.value); } bindAssistantDraftTracking(el); const statusEl = el.querySelector('#ops-assistant-status'); const buttons = el.querySelectorAll('.assistant-action-btn'); buttons.forEach((button) => { button.addEventListener('click', async () => { const rawAction = button.getAttribute('data-action'); const action = rawAction === 'save-council' ? 'save-councils' : rawAction; let patches = null; if (action === 'toggle-announce') { patches = { 'automation.delivery_mode': announce ? 'shared_session' : 'announce' }; _assistantManualOverrides.add('automation.delivery_mode'); } else if (action === 'toggle-daily-briefing') { patches = { 'automation.daily_briefing.enabled': !dailyBriefing }; _assistantManualOverrides.add('automation.daily_briefing.enabled'); } else if (action === 'toggle-memory-daily') { patches = { 'memory.daily_log.enabled': !memoryDaily }; _assistantManualOverrides.add('memory.daily_log.enabled'); } else if (action === 'toggle-memory-proactive') { patches = { 'memory.proactive_extract.enabled': !memoryProactive }; _assistantManualOverrides.add('memory.proactive_extract.enabled'); } else if (action === 'toggle-tts') { patches = { 'tts.enabled': !ttsEnabled }; _assistantManualOverrides.add('tts.enabled'); } else if (action === 'test-daily-briefing') { await triggerDailyBriefingTest(briefingName, statusEl); } else if (action === 'playbook-executive') { _lastPlaybookRollbackPatches = buildRollbackPatchesFromSnapshot(snapshot); patches = buildPlaybookPatches('executive'); } else if (action === 'playbook-operator') { _lastPlaybookRollbackPatches = buildRollbackPatchesFromSnapshot(snapshot); patches = buildPlaybookPatches('operator'); } else if (action === 'playbook-focus') { _lastPlaybookRollbackPatches = buildRollbackPatchesFromSnapshot(snapshot); patches = buildPlaybookPatches('focus'); } else if (action === 'playbook-undo') { if (!_lastPlaybookRollbackPatches) { if (statusEl) { statusEl.textContent = 'No playbook changes to undo.'; statusEl.className = 'text-sm text-zinc-500'; } return; } patches = _lastPlaybookRollbackPatches; _lastPlaybookRollbackPatches = null; } else if (action === 'save-briefing-output') { const channel = (el.querySelector('#assist-brief-channel')?.value ?? '').trim(); const peer = (el.querySelector('#assist-brief-peer')?.value ?? '').trim(); if (!channel || !peer) { if (statusEl) { statusEl.textContent = 'Briefing output channel and peer are required.'; statusEl.className = 'text-sm text-red-500'; } return; } patches = { 'automation.daily_briefing.output.channel': channel, 'automation.daily_briefing.output.peer': peer, 'automation.daily_briefing.enabled': true, }; _assistantManualOverrides.add('automation.daily_briefing.enabled'); } else if (action === 'save-model-defaults') { const tasks = MODEL_DEFAULT_TASK_KEYS; patches = { 'agents.primary_tier': (el.querySelector('#assist-primary-tier')?.value ?? 'default'), }; const tiers = MODEL_DEFAULT_TIER_KEYS; for (const tier of tiers) { const provider = (el.querySelector(`#assist-tier-${tier}-provider`)?.value ?? '').trim(); const model = (el.querySelector(`#assist-tier-${tier}-model`)?.value ?? '').trim(); if (!provider || (!model && tier !== 'default')) { continue; } if (provider) { patches[`models.${tier}.provider`] = provider; } if (model) { patches[`models.${tier}.model`] = model; } } for (const task of tasks) { const delegationTier = el.querySelector(`#assist-delegation-${task}`)?.value ?? 'fast'; const enabled = Boolean(el.querySelector(`#assist-bg-${task}-enabled`)?.checked); const provider = (el.querySelector(`#assist-bg-${task}-provider`)?.value ?? '').trim(); const model = (el.querySelector(`#assist-bg-${task}-model`)?.value ?? '').trim(); const fallbackTier = el.querySelector(`#assist-bg-${task}-fallback`)?.value ?? 'fast'; patches[`agents.delegation.${task}`] = delegationTier; patches[`agents.background_models.${task}.enabled`] = enabled; if (provider) { patches[`agents.background_models.${task}.provider`] = provider; } if (model) { patches[`agents.background_models.${task}.model`] = model; } patches[`agents.background_models.${task}.fallback_tier`] = fallbackTier; } } else if (action === 'save-councils') { const enabled = Boolean(el.querySelector('#assist-councils-enabled')?.checked); const dTier = el.querySelector('#assist-council-d-tier')?.value ?? 'complex'; const pTier = el.querySelector('#assist-council-p-tier')?.value ?? 'complex'; const metaTier = el.querySelector('#assist-council-meta-tier')?.value ?? 'complex'; const dArbiter = (el.querySelector('#assist-council-d-arbiter')?.value ?? '').trim(); const dFreethinker = (el.querySelector('#assist-council-d-freethinker')?.value ?? '').trim(); const pArbiter = (el.querySelector('#assist-council-p-arbiter')?.value ?? '').trim(); const pFreethinker = (el.querySelector('#assist-council-p-freethinker')?.value ?? '').trim(); const metaArbiter = (el.querySelector('#assist-council-meta-arbiter')?.value ?? '').trim(); const scaffoldPath = (el.querySelector('#assist-council-scaffold')?.value ?? '').trim(); const maxRoundsRaw = Number(el.querySelector('#assist-council-max-rounds')?.value ?? 2); const maxRounds = Number.isFinite(maxRoundsRaw) ? Math.max(1, Math.min(6, Math.floor(maxRoundsRaw))) : 2; if (!dArbiter || !dFreethinker || !pArbiter || !pFreethinker || !metaArbiter) { if (statusEl) { statusEl.textContent = 'All council agent names are required.'; statusEl.className = 'text-sm text-red-500'; } return; } patches = { 'councils.enabled': enabled, 'councils.defaults.max_rounds': maxRounds, 'councils.groups.D.model_tier': dTier, 'councils.groups.P.model_tier': pTier, 'councils.meta_model_tier': metaTier, 'councils.groups.D.arbiter_agent': dArbiter, 'councils.groups.D.freethinker_agent': dFreethinker, 'councils.groups.P.arbiter_agent': pArbiter, 'councils.groups.P.freethinker_agent': pFreethinker, 'councils.meta_arbiter_agent': metaArbiter, 'councils.scaffold_path': scaffoldPath, }; } else if (action === 'run-council') { const councilTask = (el.querySelector('#assist-council-task')?.value ?? '').trim(); const councilStatusEl = el.querySelector('#assist-council-status'); const ok = await triggerCouncilRun(councilTask, councilStatusEl); if (ok && _lastAssistantConfig) { updateAssistantHealth(_lastAssistantConfig); } return; } if (!patches) {return;} const patchResult = await applyAssistantPatch(patches, statusEl); if (action === 'save-model-defaults' && patchResult.applied.length > 0 && patchResult.rejected.length === 0) { _assistantModelDefaultsDraft = null; } else if (action === 'save-model-defaults') { _assistantModelDefaultsDraft = readAssistantModelDefaultsDraft(el) ?? _assistantModelDefaultsDraft; } // Force immediate refresh of slow sections after applying. const refreshed = await fetchSlow(_dashboardClient); if (refreshed) { if (refreshed.config) { _lastAssistantConfig = refreshed.config; } updateServices(refreshed.services); updateSessionAnalytics(refreshed.sessionAnalytics); updateContextHealth(refreshed.contextUsage); // Only re-render assistant controls from a confirmed config snapshot. if (refreshed.config) { updateAssistantHealth(_lastAssistantConfig); } else if (_lastAssistantConfig) { updateAssistantHealth(_lastAssistantConfig); } } }); }); } function readAssistantModelDefaultsDraft(rootEl) { const primaryTier = rootEl.querySelector('#assist-primary-tier')?.value; if (!primaryTier) { return null; } const tiers = {}; for (const tier of MODEL_DEFAULT_TIER_KEYS) { const provider = (rootEl.querySelector(`#assist-tier-${tier}-provider`)?.value ?? '').trim(); const model = (rootEl.querySelector(`#assist-tier-${tier}-model`)?.value ?? '').trim(); if (!provider && !model) {continue;} tiers[tier] = { provider, model }; } const tasks = {}; for (const task of MODEL_DEFAULT_TASK_KEYS) { tasks[task] = { delegationTier: rootEl.querySelector(`#assist-delegation-${task}`)?.value ?? 'fast', backgroundEnabled: Boolean(rootEl.querySelector(`#assist-bg-${task}-enabled`)?.checked), provider: (rootEl.querySelector(`#assist-bg-${task}-provider`)?.value ?? '').trim(), model: (rootEl.querySelector(`#assist-bg-${task}-model`)?.value ?? '').trim(), fallbackTier: rootEl.querySelector(`#assist-bg-${task}-fallback`)?.value ?? 'fast', }; } return { primaryTier, tiers, tasks, }; } function _updateChannels(channelsData) { const el = document.getElementById('ops-channels'); if (!el) {return;} const channels = channelsData?.channels ?? []; if (channels.length === 0) { el.innerHTML = '
No channels registered
'; return; } el.innerHTML = channels.map(ch => `
${escapeHtml(ch.name)}
`, ).join(''); } function updateServices(servicesData) { const el = document.getElementById('ops-services'); if (!el) {return;} const services = servicesData?.services ?? []; _lastServices = services; if (services.length === 0) { el.innerHTML = '
No services configured
'; renderServiceConfigModal(); return; } el.innerHTML = services.map(svc => { const typeIcon = svc.type === 'channel' ? '📡' : svc.type === 'automation' ? '⚙️' : '🔧'; const borderColor = svc.status === 'connected' ? 'border-l-green-500' : svc.status === 'configured' ? 'border-l-blue-500' : svc.status === 'error' ? 'border-l-red-500' : 'border-l-zinc-600 opacity-60'; const statusColor = svc.status === 'connected' ? 'text-green-500' : svc.status === 'configured' ? 'text-blue-500' : svc.status === 'error' ? 'text-red-500' : 'text-zinc-500'; const itemCount = svc.itemCount ? ` (${svc.itemCount})` : ''; return ``; }).join(''); el.querySelectorAll('.service-card').forEach((card) => { card.addEventListener('click', () => { const serviceName = card.getAttribute('data-service-name'); if (!serviceName) {return;} _serviceConfigState.open = true; _serviceConfigState.serviceName = serviceName; _serviceConfigState.status = null; _serviceConfigState.tone = 'neutral'; renderServiceConfigModal(); }); }); renderServiceConfigModal(); } function getConfigValue(path, fallbackValue) { const value = getByPath(_lastAssistantConfig, path); return value === undefined ? fallbackValue : value; } function renderServiceConfigModal() { const root = document.getElementById('ops-service-config-modal-root'); if (!root) {return;} if (!_serviceConfigState.open || !_serviceConfigState.serviceName) { root.innerHTML = ''; return; } const service = _lastServices.find((svc) => svc.name === _serviceConfigState.serviceName); if (!service) { _serviceConfigState.open = false; root.innerHTML = ''; return; } const quickTogglePath = SERVICE_TOGGLE_PATCH_PATHS[service.name]; const hasQuickToggle = Boolean(quickTogglePath); const quickToggleValue = hasQuickToggle ? Boolean(getConfigValue(quickTogglePath, false)) : false; const heartbeatSection = service.name === 'heartbeat' ? `
Checks
${HEARTBEAT_CHECK_KEYS.map((check) => { const selected = Array.isArray(getConfigValue('automation.heartbeat.checks', HEARTBEAT_CHECK_KEYS)) && getConfigValue('automation.heartbeat.checks', HEARTBEAT_CHECK_KEYS).includes(check); return ` `; }).join('')}
` : ''; const toneClass = _serviceConfigState.tone === 'success' ? 'text-green-500' : _serviceConfigState.tone === 'error' ? 'text-red-500' : 'text-zinc-500'; root.innerHTML = `
Configure ${escapeHtml(service.name)}
${escapeHtml(service.description ?? '')}
Quick Settings
${hasQuickToggle ? ` ` : '
No quick toggle available for this service.
'} ${heartbeatSection}
Advanced Patch (optional JSON)
${escapeHtml(_serviceConfigState.status ?? '')}
`; const closeModal = () => { _serviceConfigState.open = false; renderServiceConfigModal(); }; root.querySelector('#svc-config-close')?.addEventListener('click', closeModal); root.querySelector('#svc-config-cancel')?.addEventListener('click', closeModal); root.querySelector('#svc-config-overlay')?.addEventListener('click', closeModal); root.querySelector('#svc-config-save')?.addEventListener('click', async () => { if (!_dashboardClient || !_serviceConfigState.serviceName) {return;} const patches = {}; if (quickTogglePath) { patches[quickTogglePath] = Boolean(root.querySelector('#svc-quick-enabled')?.checked); } if (_serviceConfigState.serviceName === 'heartbeat') { patches['automation.heartbeat.interval'] = (root.querySelector('#svc-heartbeat-interval')?.value ?? '5m').trim(); patches['automation.heartbeat.notify_cooldown'] = (root.querySelector('#svc-heartbeat-notify-cooldown')?.value ?? '30m').trim(); patches['automation.heartbeat.failure_threshold'] = Number(root.querySelector('#svc-heartbeat-failure-threshold')?.value ?? 2); patches['automation.heartbeat.disk_threshold_mb'] = Number(root.querySelector('#svc-heartbeat-disk-threshold')?.value ?? 100); patches['automation.heartbeat.checks'] = HEARTBEAT_CHECK_KEYS.filter( (check) => Boolean(root.querySelector(`[data-heartbeat-check="${check}"]`)?.checked), ); } const advancedRaw = (root.querySelector('#svc-advanced-patch')?.value ?? '').trim(); _serviceConfigState.advancedPatch = advancedRaw; if (advancedRaw.length > 0) { try { const parsed = JSON.parse(advancedRaw); if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { throw new Error('Advanced patch must be a JSON object'); } Object.assign(patches, parsed); } catch (error) { _serviceConfigState.status = error instanceof Error ? error.message : String(error); _serviceConfigState.tone = 'error'; renderServiceConfigModal(); return; } } if (Object.keys(patches).length === 0) { _serviceConfigState.status = 'No changes to save.'; _serviceConfigState.tone = 'neutral'; renderServiceConfigModal(); return; } try { const result = await _dashboardClient.call('config.patch', { patches }); const applied = Array.isArray(result?.applied) ? result.applied : []; const rejected = Array.isArray(result?.rejected) ? result.rejected : []; if (applied.length === 0) { _serviceConfigState.status = rejected.length > 0 ? `No changes applied. Rejected: ${rejected.join(', ')}` : 'No changes were applied.'; _serviceConfigState.tone = 'error'; renderServiceConfigModal(); return; } _serviceConfigState.status = `Saved ${applied.length} setting(s).${rejected.length > 0 ? ` Rejected: ${rejected.join(', ')}` : ''}`; _serviceConfigState.tone = rejected.length > 0 ? 'error' : 'success'; const refreshed = await fetchSlow(_dashboardClient); if (refreshed) { updateServices(refreshed.services); updateSessionAnalytics(refreshed.sessionAnalytics); updateContextHealth(refreshed.contextUsage); if (refreshed.config) { _lastAssistantConfig = refreshed.config; updateAssistantHealth(_lastAssistantConfig); } } renderServiceConfigModal(); } catch (error) { _serviceConfigState.status = `Save failed: ${error instanceof Error ? error.message : String(error)}`; _serviceConfigState.tone = 'error'; renderServiceConfigModal(); } }); } // ── Data fetching ─────────────────────────────────────────────── async function fetchFast(client) { try { const [metrics, eventsData, requestsData] = await Promise.all([ client.call('system.metrics'), client.call('system.events', { limit: 50 }), client.call('system.activeRequests'), ]); return { metrics, eventsData, requestsData }; } catch { return null; } } async function fetchSlow(client) { const [health, services, sessionAnalytics, contextUsage, config, modelCatalog] = await Promise.allSettled([ client.call('system.health'), client.call('system.services'), client.call('system.sessionAnalytics', { days: 14, topLimit: 5 }), client.call('system.contextUsage'), client.call('config.get'), client.call('system.modelCatalog'), ]); const unwrap = (result) => (result.status === 'fulfilled' ? result.value : null); const configValue = unwrap(config); const modelCatalogValue = unwrap(modelCatalog); if (configValue && typeof configValue === 'object') { configValue.__modelCatalog = Array.isArray(modelCatalogValue?.providers) ? modelCatalogValue.providers : []; } return { health: unwrap(health), services: unwrap(services), sessionAnalytics: unwrap(sessionAnalytics), contextUsage: unwrap(contextUsage), config: configValue, }; } // ── Main load function ────────────────────────────────────────── let _lastHealth = null; let _lastMetrics = null; async function loadDashboard(el, client) { _dashboardClient = client; renderSkeleton(el); // Fetch everything initially const [fast, slow] = await Promise.all([ fetchFast(client), fetchSlow(client), ]); _lastHealth = slow?.health ?? null; _lastMetrics = fast?.metrics ?? null; if (fast) { updateCounters(fast.metrics, _lastHealth); updateModelTable(fast.metrics); updateEvents(fast.eventsData); updateActiveRequests(fast.requestsData); } if (slow?.config) { _lastAssistantConfig = slow.config; } if (slow?.services) { updateServices(slow.services); } if (slow?.sessionAnalytics) { updateSessionAnalytics(slow.sessionAnalytics); } if (slow?.contextUsage) { updateContextHealth(slow.contextUsage); } if (slow?.config) { updateAssistantHealth(_lastAssistantConfig); } // Fast refresh: 3 seconds for metrics, events, requests _fastTimer = setInterval(async () => { const data = await fetchFast(client); if (data) { _lastMetrics = data.metrics; updateCounters(data.metrics, _lastHealth); updateModelTable(data.metrics); updateEvents(data.eventsData); updateActiveRequests(data.requestsData); } }, 3000); // Slow refresh: 10 seconds for health, services _slowTimer = setInterval(async () => { const data = await fetchSlow(client); if (data.health) { _lastHealth = data.health; updateCounters(_lastMetrics, data.health); } if (data.config) { _lastAssistantConfig = data.config; } if (data.services) { updateServices(data.services); } if (data.sessionAnalytics) { updateSessionAnalytics(data.sessionAnalytics); } if (data.contextUsage) { updateContextHealth(data.contextUsage); } if (data.config) { updateAssistantHealth(_lastAssistantConfig); } }, 10000); } export const DashboardPage = { async render(el, client) { await loadDashboard(el, client); }, teardown() { if (_fastTimer) { clearInterval(_fastTimer); _fastTimer = null; } if (_slowTimer) { clearInterval(_slowTimer); _slowTimer = null; } _lastHealth = null; _lastMetrics = null; _dashboardClient = null; _lastPlaybookRollbackPatches = null; _lastBriefingTestAt = null; _assistantSaveState = null; _lastAssistantConfig = null; _assistantManualOverrides = new Set(); _assistantModelDefaultsDraft = null; _assistantDraftState = new Map(); _assistantDraftTouchedAt = 0; }, };