From a3595f02776bf271b9d1bed3894f0b8f5e20584e Mon Sep 17 00:00:00 2001 From: DexterFromLab Date: Sun, 15 Mar 2026 16:59:20 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20agent=20status=20indicators=20persist=20?= =?UTF-8?q?after=20stop=20=E2=80=94=20sync=20btmsg=20+=20optimistic=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Green borders in GroupAgentsPanel and health dots stayed active after stopping agents because btmsg DB status was never set to 'stopped'. - Add terminal state guard in agent store (done/error cannot revert to active) - Optimistic updateAgentStatus('done') on all stop paths (AgentPane, StatusBar Stop All, AgentSession onAgentStop) - Sync btmsg agent status to 'stopped' in agent-dispatcher on every terminal transition (agent_stopped, agent_error, cost done/error, sidecar crash) - Sync btmsg optimistically in UI stop handlers for immediate card border update - Add Stop All button to StatusBar with wake scheduler kill --- src/lib/agent-dispatcher.ts | 17 ++++- src/lib/components/Agent/AgentPane.svelte | 7 ++ src/lib/components/StatusBar/StatusBar.svelte | 70 ++++++++++++++++++- .../components/Workspace/AgentSession.svelte | 4 +- src/lib/stores/agents.svelte.ts | 6 ++ 5 files changed, 100 insertions(+), 4 deletions(-) diff --git a/src/lib/agent-dispatcher.ts b/src/lib/agent-dispatcher.ts index dd1e5e6..5f024f8 100644 --- a/src/lib/agent-dispatcher.ts +++ b/src/lib/agent-dispatcher.ts @@ -37,10 +37,18 @@ import { clearSubagentRoutes, } from './utils/subagent-router'; import { indexMessage } from './adapters/search-bridge'; -import { recordHeartbeat } from './adapters/btmsg-bridge'; +import { recordHeartbeat, setAgentStatus as setBtmsgAgentStatus } from './adapters/btmsg-bridge'; import { logAuditEvent } from './adapters/audit-bridge'; import type { AgentId } from './types/ids'; +/** Sync btmsg agent status to 'stopped' when a session reaches terminal state */ +function syncBtmsgStopped(sessionId: SessionIdType): void { + const projectId = getSessionProjectId(sessionId); + if (projectId) { + setBtmsgAgentStatus(projectId as unknown as AgentId, 'stopped').catch(() => {}); + } +} + // Re-export public API consumed by other modules export { registerSessionProject, waitForPendingPersistence } from './utils/session-persistence'; @@ -99,6 +107,7 @@ export async function startAgentDispatcher(): Promise { case 'agent_stopped': updateAgentStatus(sessionId, 'done'); + syncBtmsgStopped(sessionId); tel.info('agent_stopped', { sessionId }); notify('success', `Agent ${sessionId.slice(0, 8)} completed`); addNotification('Agent complete', `Session ${sessionId.slice(0, 8)} finished`, 'agent_complete', getSessionProjectId(sessionId) ?? undefined); @@ -111,6 +120,7 @@ export async function startAgentDispatcher(): Promise { const errorMsg = msg.message ?? 'Unknown'; const classified = classifyError(errorMsg); updateAgentStatus(sessionId, 'error', errorMsg); + syncBtmsgStopped(sessionId); tel.error('agent_error', { sessionId, error: errorMsg, errorType: classified.type }); // Show type-specific toast @@ -148,10 +158,11 @@ export async function startAgentDispatcher(): Promise { if (restarting) return; restarting = true; - // Mark all running sessions as errored + // Mark all running sessions as errored + sync btmsg for (const session of getAgentSessions()) { if (session.status === 'running' || session.status === 'starting') { updateAgentStatus(session.id, 'error', 'Sidecar crashed'); + syncBtmsgStopped(session.id); } } @@ -278,6 +289,7 @@ function handleAgentEvent(sessionId: SessionIdType, event: Record 0 ? `Retrying in ~${costClassified.retryDelaySec}s...` : ''}`); @@ -290,6 +302,7 @@ function handleAgentEvent(sessionId: SessionIdType, event: Record {}); stopAgent(sessionId).catch(() => {}); } diff --git a/src/lib/components/StatusBar/StatusBar.svelte b/src/lib/components/StatusBar/StatusBar.svelte index ff9f0bf..35fccb0 100644 --- a/src/lib/components/StatusBar/StatusBar.svelte +++ b/src/lib/components/StatusBar/StatusBar.svelte @@ -1,8 +1,13 @@