diff --git a/README.md b/README.md index 4fb95ad..0428500 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, 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 | | **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 6481758..5019438 100644 --- a/docs/plans/state.json +++ b/docs/plans/state.json @@ -5506,6 +5506,25 @@ "docs/plans/state.json" ], "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": { diff --git a/src/gateway/ui/pages/dashboard.js b/src/gateway/ui/pages/dashboard.js index 43b7221..3f6c11f 100644 --- a/src/gateway/ui/pages/dashboard.js +++ b/src/gateway/ui/pages/dashboard.js @@ -9,6 +9,7 @@ let _fastTimer = null; let _slowTimer = null; let _dashboardClient = null; let _lastPlaybookRollbackPatches = null; +let _lastBriefingTestAt = null; function formatUptime(seconds) { const d = Math.floor(seconds / 86400); @@ -537,18 +538,21 @@ async function triggerDailyBriefingTest(jobName, statusEl) { statusEl.textContent = output; statusEl.className = 'text-sm text-success'; } - return; + _lastBriefingTestAt = Date.now(); + return true; } if (statusEl) { statusEl.textContent = result?.error ?? 'Failed to trigger briefing.'; statusEl.className = 'text-sm text-error'; } + return false; } catch (error) { if (statusEl) { statusEl.textContent = `Trigger error: ${error instanceof Error ? error.message : String(error)}`; statusEl.className = 'text-sm text-error'; } + return false; } } @@ -580,6 +584,12 @@ function updateAssistantHealth(configData) { ? `${briefingOutput.channel}/${briefingOutput.peer}` : 'not configured'; 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) => `
@@ -635,6 +645,32 @@ function updateAssistantHealth(configData) {
Executive: announce + voice + aggressive interrupt. Operator: announce + memory-first + steer backlog. Focus: reactive, quieter mode.
+
+
Assistant Activation Checklist
+ +
+ + +
+
+ +
+
Morning Brief Preview
@@ -673,7 +709,6 @@ function updateAssistantHealth(configData) { patches = { 'tts.enabled': !ttsEnabled }; } else if (action === 'test-daily-briefing') { await triggerDailyBriefingTest(briefingName, statusEl); - return; } else if (action === 'playbook-executive') { _lastPlaybookRollbackPatches = buildRollbackPatchesFromSnapshot(snapshot); patches = buildPlaybookPatches('executive'); @@ -693,6 +728,21 @@ function updateAssistantHealth(configData) { } 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-error'; + } + return; + } + patches = { + 'automation.daily_briefing.output.channel': channel, + 'automation.daily_briefing.output.peer': peer, + 'automation.daily_briefing.enabled': true, + }; } if (!patches) {return;} await applyAssistantPatch(patches, statusEl); @@ -864,5 +914,6 @@ export const DashboardPage = { _lastMetrics = null; _dashboardClient = null; _lastPlaybookRollbackPatches = null; + _lastBriefingTestAt = null; }, }; diff --git a/src/gateway/ui/pages/settings.js b/src/gateway/ui/pages/settings.js index 22fd324..719c8c4 100644 --- a/src/gateway/ui/pages/settings.js +++ b/src/gateway/ui/pages/settings.js @@ -127,6 +127,8 @@ async function loadSettings() { const proactiveMinToolCalls = Number(memory.proactive_extract?.min_tool_calls ?? 1); const ttsEnabled = Boolean(tts.enabled); 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) const configJson = JSON.stringify(config, null, 2); @@ -171,6 +173,14 @@ async function loadSettings() { TTS channels (comma-separated, blank = all) + +
@@ -288,6 +298,8 @@ async function saveAssistantMode() { 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 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 .split(',') .map((value) => value.trim()) @@ -302,6 +314,12 @@ async function saveAssistantMode() { 'tts.enabled': ttsEnabled, 'tts.enabled_channels': ttsChannels, }; + if (briefingChannel) { + patches['automation.daily_briefing.output.channel'] = briefingChannel; + } + if (briefingPeer) { + patches['automation.daily_briefing.output.peer'] = briefingPeer; + } try { const result = await _client.call('config.patch', { patches }); diff --git a/src/gateway/ui/style.css b/src/gateway/ui/style.css index d4097c8..2efe31d 100644 --- a/src/gateway/ui/style.css +++ b/src/gateway/ui/style.css @@ -1594,6 +1594,46 @@ tr:hover td { 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 { display: flex; justify-content: space-between;