diff --git a/README.md b/README.md index 1c156ce..4fb95ad 100644 --- a/README.md +++ b/README.md @@ -487,7 +487,7 @@ Flynn includes a built-in web control dashboard served by the WebSocket gateway. | Page | Description | |------|-------------| -| **Dashboard** | System health cards, channel status, usage stats, assistant-health quick actions, and daily briefing preview/test-send. Auto-refreshes every 10s | +| **Dashboard** | System health cards, channel status, usage stats, assistant-health quick actions, playbook presets with rollback, and daily briefing preview/test-send. Auto-refreshes every 10s | | **Chat** | Session selector, streaming tool events, markdown rendering with syntax highlighting | | **Sessions** | Browse all sessions, view message history, delete sessions | | **Usage** | Token usage summary cards, per-session breakdown table, auto-refresh | diff --git a/docs/plans/state.json b/docs/plans/state.json index f58c2d4..6481758 100644 --- a/docs/plans/state.json +++ b/docs/plans/state.json @@ -5493,6 +5493,19 @@ "docs/plans/state.json" ], "test_status": "pnpm typecheck passing" + }, + "dashboard-assistant-playbooks-with-rollback": { + "status": "completed", + "date": "2026-02-18", + "updated": "2026-02-18", + "summary": "Added one-click Assistant Playbooks to Live Ops Dashboard (Executive, Operator, Focus) with safe runtime config.patch bundles and an Undo Last Playbook rollback action. Playbooks tune delivery mode, briefing/memory cadence, TTS, and queue mode for distinct assistant behaviors.", + "files_modified": [ + "src/gateway/ui/pages/dashboard.js", + "src/gateway/ui/style.css", + "README.md", + "docs/plans/state.json" + ], + "test_status": "pnpm typecheck passing" } }, "overall_progress": { diff --git a/src/gateway/ui/pages/dashboard.js b/src/gateway/ui/pages/dashboard.js index 0b7c0f7..43b7221 100644 --- a/src/gateway/ui/pages/dashboard.js +++ b/src/gateway/ui/pages/dashboard.js @@ -8,6 +8,7 @@ let _fastTimer = null; let _slowTimer = null; let _dashboardClient = null; +let _lastPlaybookRollbackPatches = null; function formatUptime(seconds) { const d = Math.floor(seconds / 86400); @@ -55,6 +56,71 @@ function escapeHtml(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') { + return { + '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', + }; + } + if (playbook === 'operator') { + return { + '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', + }; + } + return { + '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', + }; +} + +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, + }; +} + // ── Initial full render ───────────────────────────────────────── function renderSkeleton(el) { @@ -490,6 +556,8 @@ function updateAssistantHealth(configData) { const el = document.getElementById('ops-assistant-health'); if (!el) {return;} + const snapshot = getAssistantStateSnapshot(configData); + const automation = configData?.automation ?? {}; const memory = configData?.memory ?? {}; const tts = configData?.tts ?? {}; @@ -549,6 +617,24 @@ function updateAssistantHealth(configData) { ${ttsEnabled ? 'Disable TTS' : 'Enable TTS'} +
+
Assistant Playbooks
+
+ + + + +
+
Executive: announce + voice + aggressive interrupt. Operator: announce + memory-first + steer backlog. Focus: reactive, quieter mode.
+
Morning Brief Preview
@@ -588,6 +674,25 @@ function updateAssistantHealth(configData) { } else if (action === 'test-daily-briefing') { await triggerDailyBriefingTest(briefingName, statusEl); return; + } 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-muted'; + } + return; + } + patches = _lastPlaybookRollbackPatches; + _lastPlaybookRollbackPatches = null; } if (!patches) {return;} await applyAssistantPatch(patches, statusEl); @@ -758,5 +863,6 @@ export const DashboardPage = { _lastHealth = null; _lastMetrics = null; _dashboardClient = null; + _lastPlaybookRollbackPatches = null; }, }; diff --git a/src/gateway/ui/style.css b/src/gateway/ui/style.css index 78364e5..d4097c8 100644 --- a/src/gateway/ui/style.css +++ b/src/gateway/ui/style.css @@ -1579,6 +1579,21 @@ tr:hover td { background-color: var(--bg-secondary); } +.assistant-playbooks { + margin-top: 12px; + padding: 12px; + border: 1px solid var(--border); + border-radius: var(--radius); + background-color: var(--bg-secondary); +} + +.assistant-playbook-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 8px; + margin: 10px 0; +} + .assistant-preview-header { display: flex; justify-content: space-between;