diff --git a/server/src/api/ws.rs b/server/src/api/ws.rs index 1908d44..39f0d2b 100644 --- a/server/src/api/ws.rs +++ b/server/src/api/ws.rs @@ -119,7 +119,7 @@ pub async fn handle_socket(socket: WebSocket, state: Arc, _client_type tracing::info!("Received non-text message from websocket: {:?}", other); } Err(e) => { - tracing::error!("Websocket receive error: {}", e); + tracing::warn!("Websocket receive error: {}", e); break; } } diff --git a/server/src/dashboard.html b/server/src/dashboard.html index 6ce7331..58a72e6 100644 --- a/server/src/dashboard.html +++ b/server/src/dashboard.html @@ -549,6 +549,9 @@ // Resize graph when becoming visible again network.redraw(); network.fit(); + } else if (tabId === 'activity-tab') { + const feed = document.getElementById('activity-feed'); + feed.scrollTop = feed.scrollHeight; } } @@ -929,14 +932,21 @@ try { const data = JSON.parse(event.data); if (data.type === 'activity') { + // Check scroll state before appending + const isScrolledToBottom = feed.scrollHeight - feed.clientHeight <= feed.scrollTop + 20; + const div = document.createElement('div'); div.className = 'feed-entry'; const timeStr = new Date(data.data.time).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit', second:'2-digit'}); div.innerHTML = `[${timeStr}] ${data.data.message || data.data.data || data.data}`; feed.appendChild(div); + // Enforce max history + while (feed.children.length > MAX_ACTIVITY_HISTORY) { + feed.removeChild(feed.firstChild); + } + // Auto-scroll logic - const isScrolledToBottom = feed.scrollHeight - feed.clientHeight <= feed.scrollTop + 20; if (isScrolledToBottom) { feed.scrollTop = feed.scrollHeight; } diff --git a/server/src/handlers/workspaces.rs b/server/src/handlers/workspaces.rs index 6ad5843..f0eb8c0 100644 --- a/server/src/handlers/workspaces.rs +++ b/server/src/handlers/workspaces.rs @@ -278,7 +278,7 @@ impl McpTool for ListContextWorkspacesHandler { let req: ListContextWorkspacesTool = serde_json::from_value(args).map_err(|e| e.to_string())?; let data = state.context_workspaces.read_with(|ws| { - let filtered: Vec<_> = ws.iter().filter(|w| w.namespace == req.namespace).collect(); + let filtered: Vec<_> = ws.iter().filter(|w| req.namespace.as_ref().map_or(true, |ns| &w.namespace == ns)).collect(); serde_json::to_string(&filtered).unwrap_or_default() }); Ok(data.to_string()) diff --git a/server/src/tools.rs b/server/src/tools.rs index cce974b..5a18231 100644 --- a/server/src/tools.rs +++ b/server/src/tools.rs @@ -85,7 +85,7 @@ pub struct OpenNodesTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct LogCodeChangeTool { /// The path of the file that was changed. - #[serde(rename = "filePath")] + #[serde(alias = "filePath")] pub file_path: String, /// A description of the change. pub description: String, @@ -508,7 +508,7 @@ pub struct LoadContextWorkspaceTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct ListContextWorkspacesTool { /// The namespace to list workspaces for. - pub namespace: String, + pub namespace: Option, } /// Search across all memory stores (Graph, Tasks, Snippets, ADRs, etc.).