feat(dashboard): add assistant playbook presets with rollback

This commit is contained in:
William Valentin
2026-02-18 12:11:30 -08:00
parent 7a9ac34618
commit 4267eae16c
4 changed files with 135 additions and 1 deletions
+1 -1
View File
@@ -487,7 +487,7 @@ Flynn includes a built-in web control dashboard served by the WebSocket gateway.
| Page | Description | | 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 | | **Chat** | Session selector, streaming tool events, markdown rendering with syntax highlighting |
| **Sessions** | Browse all sessions, view message history, delete sessions | | **Sessions** | Browse all sessions, view message history, delete sessions |
| **Usage** | Token usage summary cards, per-session breakdown table, auto-refresh | | **Usage** | Token usage summary cards, per-session breakdown table, auto-refresh |
+13
View File
@@ -5493,6 +5493,19 @@
"docs/plans/state.json" "docs/plans/state.json"
], ],
"test_status": "pnpm typecheck passing" "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": { "overall_progress": {
+106
View File
@@ -8,6 +8,7 @@
let _fastTimer = null; let _fastTimer = null;
let _slowTimer = null; let _slowTimer = null;
let _dashboardClient = null; let _dashboardClient = null;
let _lastPlaybookRollbackPatches = null;
function formatUptime(seconds) { function formatUptime(seconds) {
const d = Math.floor(seconds / 86400); const d = Math.floor(seconds / 86400);
@@ -55,6 +56,71 @@ function escapeHtml(str) {
return div.innerHTML; 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 ───────────────────────────────────────── // ── Initial full render ─────────────────────────────────────────
function renderSkeleton(el) { function renderSkeleton(el) {
@@ -490,6 +556,8 @@ function updateAssistantHealth(configData) {
const el = document.getElementById('ops-assistant-health'); const el = document.getElementById('ops-assistant-health');
if (!el) {return;} if (!el) {return;}
const snapshot = getAssistantStateSnapshot(configData);
const automation = configData?.automation ?? {}; const automation = configData?.automation ?? {};
const memory = configData?.memory ?? {}; const memory = configData?.memory ?? {};
const tts = configData?.tts ?? {}; const tts = configData?.tts ?? {};
@@ -549,6 +617,24 @@ function updateAssistantHealth(configData) {
${ttsEnabled ? 'Disable TTS' : 'Enable TTS'} ${ttsEnabled ? 'Disable TTS' : 'Enable TTS'}
</button> </button>
</div> </div>
<div class="assistant-playbooks">
<div class="assistant-preview-title">Assistant Playbooks</div>
<div class="assistant-playbook-grid">
<button class="btn btn-secondary assistant-action-btn" data-action="playbook-executive">
Executive
</button>
<button class="btn btn-secondary assistant-action-btn" data-action="playbook-operator">
Operator
</button>
<button class="btn btn-secondary assistant-action-btn" data-action="playbook-focus">
Focus
</button>
<button class="btn btn-secondary assistant-action-btn" data-action="playbook-undo" ${_lastPlaybookRollbackPatches ? '' : 'disabled'}>
Undo Last Playbook
</button>
</div>
<div class="text-sm text-muted">Executive: announce + voice + aggressive interrupt. Operator: announce + memory-first + steer backlog. Focus: reactive, quieter mode.</div>
</div>
<div class="assistant-preview"> <div class="assistant-preview">
<div class="assistant-preview-header"> <div class="assistant-preview-header">
<div class="assistant-preview-title">Morning Brief Preview</div> <div class="assistant-preview-title">Morning Brief Preview</div>
@@ -588,6 +674,25 @@ function updateAssistantHealth(configData) {
} else if (action === 'test-daily-briefing') { } else if (action === 'test-daily-briefing') {
await triggerDailyBriefingTest(briefingName, statusEl); await triggerDailyBriefingTest(briefingName, statusEl);
return; 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;} if (!patches) {return;}
await applyAssistantPatch(patches, statusEl); await applyAssistantPatch(patches, statusEl);
@@ -758,5 +863,6 @@ export const DashboardPage = {
_lastHealth = null; _lastHealth = null;
_lastMetrics = null; _lastMetrics = null;
_dashboardClient = null; _dashboardClient = null;
_lastPlaybookRollbackPatches = null;
}, },
}; };
+15
View File
@@ -1579,6 +1579,21 @@ tr:hover td {
background-color: var(--bg-secondary); 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 { .assistant-preview-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;