From 5ca035d438173e931f6bca08d663f6cd6e993bcf Mon Sep 17 00:00:00 2001 From: Hibryda Date: Fri, 6 Mar 2026 12:19:35 +0100 Subject: [PATCH] feat(v2): add sidecar crash detection, restart UI, and auto-scroll lock Phase 3 polish: dispatcher listens for sidecar-exited events and marks running sessions as error. AgentPane shows "Restart Sidecar" button on error. Auto-scroll disables when user scrolls >50px from bottom with "Scroll to bottom" button. Added agent_restart Tauri command. --- v2/src-tauri/src/sidecar.rs | 6 ++ v2/src/lib/adapters/agent-bridge.ts | 4 ++ v2/src/lib/agent-dispatcher.ts | 45 ++++++++++++--- v2/src/lib/components/Agent/AgentPane.svelte | 60 +++++++++++++++++++- 4 files changed, 104 insertions(+), 11 deletions(-) diff --git a/v2/src-tauri/src/sidecar.rs b/v2/src-tauri/src/sidecar.rs index 1a50bb6..3f073b5 100644 --- a/v2/src-tauri/src/sidecar.rs +++ b/v2/src-tauri/src/sidecar.rs @@ -159,6 +159,12 @@ impl SidecarManager { self.send_message(&msg) } + pub fn restart(&self, app: &AppHandle) -> Result<(), String> { + log::info!("Restarting sidecar"); + let _ = self.shutdown(); + self.start(app) + } + pub fn shutdown(&self) -> Result<(), String> { let mut child_lock = self.child.lock().unwrap(); if let Some(ref mut child) = *child_lock { diff --git a/v2/src/lib/adapters/agent-bridge.ts b/v2/src/lib/adapters/agent-bridge.ts index 4eb261b..07ed88c 100644 --- a/v2/src/lib/adapters/agent-bridge.ts +++ b/v2/src/lib/adapters/agent-bridge.ts @@ -25,6 +25,10 @@ export async function isAgentReady(): Promise { return invoke('agent_ready'); } +export async function restartAgent(): Promise { + return invoke('agent_restart'); +} + export interface SidecarMessage { type: string; sessionId?: string; diff --git a/v2/src/lib/agent-dispatcher.ts b/v2/src/lib/agent-dispatcher.ts index 40313fd..e25c980 100644 --- a/v2/src/lib/agent-dispatcher.ts +++ b/v2/src/lib/agent-dispatcher.ts @@ -1,7 +1,7 @@ // Agent Dispatcher — connects sidecar bridge events to agent store // Single listener that routes sidecar messages to the correct agent session -import { onSidecarMessage, type SidecarMessage } from './adapters/agent-bridge'; +import { onSidecarMessage, onSidecarExited, type SidecarMessage } from './adapters/agent-bridge'; import { adaptSDKMessage } from './adapters/sdk-messages'; import type { InitContent, CostContent } from './adapters/sdk-messages'; import { @@ -10,14 +10,29 @@ import { setAgentModel, appendAgentMessages, updateAgentCost, + getAgentSessions, } from './stores/agents.svelte'; -let unlistenFn: (() => void) | null = null; +let unlistenMsg: (() => void) | null = null; +let unlistenExit: (() => void) | null = null; + +// Sidecar liveness — checked by UI components +let sidecarAlive = true; +export function isSidecarAlive(): boolean { + return sidecarAlive; +} +export function setSidecarAlive(alive: boolean): void { + sidecarAlive = alive; +} export async function startAgentDispatcher(): Promise { - if (unlistenFn) return; + if (unlistenMsg) return; + + sidecarAlive = true; + + unlistenMsg = await onSidecarMessage((msg: SidecarMessage) => { + sidecarAlive = true; - unlistenFn = await onSidecarMessage((msg: SidecarMessage) => { const sessionId = msg.sessionId; if (!sessionId) return; @@ -39,10 +54,19 @@ export async function startAgentDispatcher(): Promise { break; case 'agent_log': - // Debug logging — could route to a log panel later break; } }); + + unlistenExit = await onSidecarExited(() => { + sidecarAlive = false; + // Mark all running sessions as errored + for (const session of getAgentSessions()) { + if (session.status === 'running' || session.status === 'starting') { + updateAgentStatus(session.id, 'error', 'Sidecar crashed'); + } + } + }); } function handleAgentEvent(sessionId: string, event: Record): void { @@ -76,15 +100,18 @@ function handleAgentEvent(sessionId: string, event: Record): vo } } - // Append all messages to the session history if (messages.length > 0) { appendAgentMessages(sessionId, messages); } } export function stopAgentDispatcher(): void { - if (unlistenFn) { - unlistenFn(); - unlistenFn = null; + if (unlistenMsg) { + unlistenMsg(); + unlistenMsg = null; + } + if (unlistenExit) { + unlistenExit(); + unlistenExit = null; } } diff --git a/v2/src/lib/components/Agent/AgentPane.svelte b/v2/src/lib/components/Agent/AgentPane.svelte index b24e7ca..98b41b8 100644 --- a/v2/src/lib/components/Agent/AgentPane.svelte +++ b/v2/src/lib/components/Agent/AgentPane.svelte @@ -1,12 +1,13 @@