feat(dashboard): add assistant playbook presets with rollback
This commit is contained in:
@@ -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 |
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user