feat(dashboard): add assistant activation checklist and guided controls
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, playbook presets with rollback, 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, activation checklist, 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 |
|
||||||
|
|||||||
@@ -5506,6 +5506,25 @@
|
|||||||
"docs/plans/state.json"
|
"docs/plans/state.json"
|
||||||
],
|
],
|
||||||
"test_status": "pnpm typecheck passing"
|
"test_status": "pnpm typecheck passing"
|
||||||
|
},
|
||||||
|
"assistant-activation-checklist-and-setup-defaults": {
|
||||||
|
"status": "completed",
|
||||||
|
"date": "2026-02-18",
|
||||||
|
"updated": "2026-02-18",
|
||||||
|
"summary": "Completed assistant productization pass: Dashboard now includes a 3-step activation checklist with briefing output save controls, playbook/test-send integration, and persisted assistant status cues; runtime config.patch now supports daily briefing output/schedule/prompt/tier updates; setup/operator-pack defaults now enable announce delivery plus memory daily/proactive extraction cadence for assistant-first behavior out of the box.",
|
||||||
|
"files_modified": [
|
||||||
|
"src/gateway/ui/pages/dashboard.js",
|
||||||
|
"src/gateway/ui/style.css",
|
||||||
|
"src/gateway/ui/pages/settings.js",
|
||||||
|
"src/gateway/handlers/config.ts",
|
||||||
|
"src/gateway/handlers/handlers.test.ts",
|
||||||
|
"src/cli/setup/config.ts",
|
||||||
|
"src/cli/setup/config.test.ts",
|
||||||
|
"src/cli/setup/automation.test.ts",
|
||||||
|
"README.md",
|
||||||
|
"docs/plans/state.json"
|
||||||
|
],
|
||||||
|
"test_status": "pnpm test:run src/gateway/handlers/handlers.test.ts src/cli/setup/config.test.ts src/cli/setup/automation.test.ts + pnpm typecheck passing"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"overall_progress": {
|
"overall_progress": {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ let _fastTimer = null;
|
|||||||
let _slowTimer = null;
|
let _slowTimer = null;
|
||||||
let _dashboardClient = null;
|
let _dashboardClient = null;
|
||||||
let _lastPlaybookRollbackPatches = null;
|
let _lastPlaybookRollbackPatches = null;
|
||||||
|
let _lastBriefingTestAt = null;
|
||||||
|
|
||||||
function formatUptime(seconds) {
|
function formatUptime(seconds) {
|
||||||
const d = Math.floor(seconds / 86400);
|
const d = Math.floor(seconds / 86400);
|
||||||
@@ -537,18 +538,21 @@ async function triggerDailyBriefingTest(jobName, statusEl) {
|
|||||||
statusEl.textContent = output;
|
statusEl.textContent = output;
|
||||||
statusEl.className = 'text-sm text-success';
|
statusEl.className = 'text-sm text-success';
|
||||||
}
|
}
|
||||||
return;
|
_lastBriefingTestAt = Date.now();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statusEl) {
|
if (statusEl) {
|
||||||
statusEl.textContent = result?.error ?? 'Failed to trigger briefing.';
|
statusEl.textContent = result?.error ?? 'Failed to trigger briefing.';
|
||||||
statusEl.className = 'text-sm text-error';
|
statusEl.className = 'text-sm text-error';
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (statusEl) {
|
if (statusEl) {
|
||||||
statusEl.textContent = `Trigger error: ${error instanceof Error ? error.message : String(error)}`;
|
statusEl.textContent = `Trigger error: ${error instanceof Error ? error.message : String(error)}`;
|
||||||
statusEl.className = 'text-sm text-error';
|
statusEl.className = 'text-sm text-error';
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -580,6 +584,12 @@ function updateAssistantHealth(configData) {
|
|||||||
? `${briefingOutput.channel}/${briefingOutput.peer}`
|
? `${briefingOutput.channel}/${briefingOutput.peer}`
|
||||||
: 'not configured';
|
: 'not configured';
|
||||||
const briefingReady = dailyBriefing && Boolean(briefingOutput?.channel && briefingOutput?.peer);
|
const briefingReady = dailyBriefing && Boolean(briefingOutput?.channel && briefingOutput?.peer);
|
||||||
|
const playbookLikeReady = announce || (memoryDaily && memoryProactive);
|
||||||
|
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) => `
|
const chip = (label, value) => `
|
||||||
<div class="assistant-chip">
|
<div class="assistant-chip">
|
||||||
@@ -635,6 +645,32 @@ function updateAssistantHealth(configData) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-muted">Executive: announce + voice + aggressive interrupt. Operator: announce + memory-first + steer backlog. Focus: reactive, quieter mode.</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>
|
||||||
|
<div class="assistant-setup">
|
||||||
|
<div class="assistant-preview-title">Assistant Activation Checklist</div>
|
||||||
|
<ul class="assistant-checklist">
|
||||||
|
${checklistRows.map((row) => `
|
||||||
|
<li class="${row.done ? 'done' : ''}">
|
||||||
|
<span class="assistant-check">${row.done ? '✓' : '○'}</span>
|
||||||
|
<span>${escapeHtml(row.label)}</span>
|
||||||
|
</li>
|
||||||
|
`).join('')}
|
||||||
|
</ul>
|
||||||
|
<div class="assistant-setup-grid">
|
||||||
|
<label class="assistant-field">
|
||||||
|
<span>Briefing output channel</span>
|
||||||
|
<input id="assist-brief-channel" type="text" value="${escapeHtml(briefingOutput?.channel ?? '')}" placeholder="telegram" />
|
||||||
|
</label>
|
||||||
|
<label class="assistant-field">
|
||||||
|
<span>Briefing output peer/chat id</span>
|
||||||
|
<input id="assist-brief-peer" type="text" value="${escapeHtml(briefingOutput?.peer ?? '')}" placeholder="123456789" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="assistant-actions">
|
||||||
|
<button class="btn btn-secondary assistant-action-btn" data-action="save-briefing-output">
|
||||||
|
Save Briefing Output
|
||||||
|
</button>
|
||||||
|
</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>
|
||||||
@@ -673,7 +709,6 @@ function updateAssistantHealth(configData) {
|
|||||||
patches = { 'tts.enabled': !ttsEnabled };
|
patches = { 'tts.enabled': !ttsEnabled };
|
||||||
} else if (action === 'test-daily-briefing') {
|
} else if (action === 'test-daily-briefing') {
|
||||||
await triggerDailyBriefingTest(briefingName, statusEl);
|
await triggerDailyBriefingTest(briefingName, statusEl);
|
||||||
return;
|
|
||||||
} else if (action === 'playbook-executive') {
|
} else if (action === 'playbook-executive') {
|
||||||
_lastPlaybookRollbackPatches = buildRollbackPatchesFromSnapshot(snapshot);
|
_lastPlaybookRollbackPatches = buildRollbackPatchesFromSnapshot(snapshot);
|
||||||
patches = buildPlaybookPatches('executive');
|
patches = buildPlaybookPatches('executive');
|
||||||
@@ -693,6 +728,21 @@ function updateAssistantHealth(configData) {
|
|||||||
}
|
}
|
||||||
patches = _lastPlaybookRollbackPatches;
|
patches = _lastPlaybookRollbackPatches;
|
||||||
_lastPlaybookRollbackPatches = null;
|
_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-error';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
patches = {
|
||||||
|
'automation.daily_briefing.output.channel': channel,
|
||||||
|
'automation.daily_briefing.output.peer': peer,
|
||||||
|
'automation.daily_briefing.enabled': true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (!patches) {return;}
|
if (!patches) {return;}
|
||||||
await applyAssistantPatch(patches, statusEl);
|
await applyAssistantPatch(patches, statusEl);
|
||||||
@@ -864,5 +914,6 @@ export const DashboardPage = {
|
|||||||
_lastMetrics = null;
|
_lastMetrics = null;
|
||||||
_dashboardClient = null;
|
_dashboardClient = null;
|
||||||
_lastPlaybookRollbackPatches = null;
|
_lastPlaybookRollbackPatches = null;
|
||||||
|
_lastBriefingTestAt = null;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -127,6 +127,8 @@ async function loadSettings() {
|
|||||||
const proactiveMinToolCalls = Number(memory.proactive_extract?.min_tool_calls ?? 1);
|
const proactiveMinToolCalls = Number(memory.proactive_extract?.min_tool_calls ?? 1);
|
||||||
const ttsEnabled = Boolean(tts.enabled);
|
const ttsEnabled = Boolean(tts.enabled);
|
||||||
const ttsChannelText = Array.isArray(tts.enabled_channels) ? tts.enabled_channels.join(', ') : '';
|
const ttsChannelText = Array.isArray(tts.enabled_channels) ? tts.enabled_channels.join(', ') : '';
|
||||||
|
const briefingOutputChannel = automation.daily_briefing?.output?.channel ?? '';
|
||||||
|
const briefingOutputPeer = automation.daily_briefing?.output?.peer ?? '';
|
||||||
|
|
||||||
// Build config view (redacted JSON)
|
// Build config view (redacted JSON)
|
||||||
const configJson = JSON.stringify(config, null, 2);
|
const configJson = JSON.stringify(config, null, 2);
|
||||||
@@ -171,6 +173,14 @@ async function loadSettings() {
|
|||||||
<span>TTS channels (comma-separated, blank = all)</span>
|
<span>TTS channels (comma-separated, blank = all)</span>
|
||||||
<input id="assist-tts-channels" type="text" value="${escapeHtml(ttsChannelText)}" placeholder="telegram,discord,whatsapp" />
|
<input id="assist-tts-channels" type="text" value="${escapeHtml(ttsChannelText)}" placeholder="telegram,discord,whatsapp" />
|
||||||
</label>
|
</label>
|
||||||
|
<label class="assistant-field">
|
||||||
|
<span>Briefing output channel</span>
|
||||||
|
<input id="assist-briefing-channel" type="text" value="${escapeHtml(briefingOutputChannel)}" placeholder="telegram" />
|
||||||
|
</label>
|
||||||
|
<label class="assistant-field">
|
||||||
|
<span>Briefing output peer/chat id</span>
|
||||||
|
<input id="assist-briefing-peer" type="text" value="${escapeHtml(briefingOutputPeer)}" placeholder="123456789" />
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="assistant-actions">
|
<div class="assistant-actions">
|
||||||
<button id="assistant-mode-save" class="btn btn-primary">Save Assistant Mode</button>
|
<button id="assistant-mode-save" class="btn btn-primary">Save Assistant Mode</button>
|
||||||
@@ -288,6 +298,8 @@ async function saveAssistantMode() {
|
|||||||
const minToolsRaw = Number.parseInt(_el.querySelector('#assist-memory-min-tools')?.value ?? '1', 10);
|
const minToolsRaw = Number.parseInt(_el.querySelector('#assist-memory-min-tools')?.value ?? '1', 10);
|
||||||
const minTools = Number.isFinite(minToolsRaw) ? Math.min(50, Math.max(0, minToolsRaw)) : 1;
|
const minTools = Number.isFinite(minToolsRaw) ? Math.min(50, Math.max(0, minToolsRaw)) : 1;
|
||||||
const ttsChannelsRaw = _el.querySelector('#assist-tts-channels')?.value ?? '';
|
const ttsChannelsRaw = _el.querySelector('#assist-tts-channels')?.value ?? '';
|
||||||
|
const briefingChannel = (_el.querySelector('#assist-briefing-channel')?.value ?? '').trim();
|
||||||
|
const briefingPeer = (_el.querySelector('#assist-briefing-peer')?.value ?? '').trim();
|
||||||
const ttsChannels = ttsChannelsRaw
|
const ttsChannels = ttsChannelsRaw
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((value) => value.trim())
|
.map((value) => value.trim())
|
||||||
@@ -302,6 +314,12 @@ async function saveAssistantMode() {
|
|||||||
'tts.enabled': ttsEnabled,
|
'tts.enabled': ttsEnabled,
|
||||||
'tts.enabled_channels': ttsChannels,
|
'tts.enabled_channels': ttsChannels,
|
||||||
};
|
};
|
||||||
|
if (briefingChannel) {
|
||||||
|
patches['automation.daily_briefing.output.channel'] = briefingChannel;
|
||||||
|
}
|
||||||
|
if (briefingPeer) {
|
||||||
|
patches['automation.daily_briefing.output.peer'] = briefingPeer;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await _client.call('config.patch', { patches });
|
const result = await _client.call('config.patch', { patches });
|
||||||
|
|||||||
@@ -1594,6 +1594,46 @@ tr:hover td {
|
|||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.assistant-setup {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.assistant-checklist {
|
||||||
|
list-style: none;
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assistant-checklist li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.assistant-checklist li.done {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.assistant-check {
|
||||||
|
width: 18px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assistant-setup-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.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