diff --git a/justfile b/justfile index e34e950..7ac4b33 100644 --- a/justfile +++ b/justfile @@ -35,12 +35,8 @@ deploy-win: deploy-server deploy-stub-win deploy-nvim-win deploy-rules-win # Stop the background server stop port="3000": @Write-Host "Shutting down MCP server gracefully on port {{port}}..." -ForegroundColor Cyan - -$token = Get-Content "$env:USERPROFILE\.gemini\mcp_memory\admin.token" -ErrorAction SilentlyContinue; if ($token) { Invoke-RestMethod -Uri "http://127.0.0.1:{{port}}/shutdown" -Method Post -Headers @{ Authorization = "Bearer $token" } -ErrorAction SilentlyContinue } else { Invoke-RestMethod -Uri "http://127.0.0.1:{{port}}/shutdown" -Method Post -ErrorAction SilentlyContinue } - $stopped = $false; for ($i = 1; $i -le 10; $i++) { if (-not (Get-NetTCPConnection -LocalPort {{port}} -State Listen -ErrorAction SilentlyContinue)) { $stopped = $true; Write-Host '✅ Server on port {{port}} shut down gracefully.' -ForegroundColor Green; break }; Start-Sleep -Seconds 1 } - if (-not $stopped -and (Get-NetTCPConnection -LocalPort {{port}} -State Listen -ErrorAction SilentlyContinue)) { Write-Host '⚠️ Server did not exit gracefully within 10s. Force killing process...' -ForegroundColor Yellow; Get-NetTCPConnection -LocalPort {{port}} -State Listen -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess | ForEach-Object { Stop-Process -Id $_ -Force -ErrorAction SilentlyContinue }; Start-Sleep -Seconds 2 } - for ($i = 1; $i -le 5; $i++) { if (-not (Get-NetTCPConnection -LocalPort {{port}} -State Listen -ErrorAction SilentlyContinue)) { break }; Start-Sleep -Seconds 1 } - Start-Sleep -Seconds 2 - Write-Host '✅ Shutdown complete. Port {{port}} is free.' -ForegroundColor Green + -& { $token = Get-Content "$env:USERPROFILE\.gemini\mcp_memory\admin.token" -ErrorAction SilentlyContinue; if ($token) { $hdr = @{ Authorization = "Bearer $($token.Trim())" }; Invoke-RestMethod -Uri "http://127.0.0.1:{{port}}/shutdown" -Method Post -Headers $hdr -ErrorAction SilentlyContinue } else { Invoke-RestMethod -Uri "http://127.0.0.1:{{port}}/shutdown" -Method Post -ErrorAction SilentlyContinue }; $stopped = $false; for ($i = 1; $i -le 10; $i++) { $conn = Get-NetTCPConnection -LocalPort {{port}} -State Listen -ErrorAction SilentlyContinue; $proc = Get-Process mcp-memory-server -ErrorAction SilentlyContinue; if (-not $conn -and -not $proc) { $stopped = $true; Write-Host '✅ Server process and port {{port}} fully terminated.' -ForegroundColor Green; break }; Start-Sleep -Seconds 1 }; if (-not $stopped) { Write-Host '⚠️ Server process did not exit within 10s. Force killing process...' -ForegroundColor Yellow; Get-Process mcp-memory-server -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue; Get-NetTCPConnection -LocalPort {{port}} -State Listen -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess | ForEach-Object { Stop-Process -Id $_ -Force -ErrorAction SilentlyContinue }; Start-Sleep -Seconds 2 } } + @Write-Host "✅ Shutdown complete. Port {{port}} and process are free." -ForegroundColor Green # Start the server in the background (nohup equivalent) start port="3000": @@ -71,6 +67,8 @@ build-stub-win: # ========================================================= deploy-server: @Write-Host "Deploying Windows Server..." -ForegroundColor Cyan + -Stop-Process -Name mcp-memory-server -Force -ErrorAction SilentlyContinue + Start-Sleep -Seconds 1 -if (Test-Path "C:\Users\reazul.ashraf\.local\bin\mcp-memory-server.exe") { Move-Item -Force "C:\Users\reazul.ashraf\.local\bin\mcp-memory-server.exe" "C:\Users\reazul.ashraf\.local\bin\mcp-memory-server.exe.old" -ErrorAction SilentlyContinue; Remove-Item -Force "C:\Users\reazul.ashraf\.local\bin\mcp-memory-server.exe.old" -ErrorAction SilentlyContinue } Copy-Item -Force target\release\mcp-memory-server.exe "C:\Users\reazul.ashraf\.local\bin\" diff --git a/server/src/api/setup.rs b/server/src/api/setup.rs index 5935e87..d1ddf7a 100644 --- a/server/src/api/setup.rs +++ b/server/src/api/setup.rs @@ -137,6 +137,12 @@ pub fn create_router(app_state: Arc) -> Router { if let Some(tx) = state.shutdown_tx.lock().unwrap().take() { let _ = tx.send(()); } + + tokio::spawn(async move { + tokio::time::sleep(std::time::Duration::from_millis(200)).await; + std::process::exit(0); + }); + (axum::http::StatusCode::OK, "Shutting down...").into_response() }, ), diff --git a/server/src/dashboard.html b/server/src/dashboard.html index c930865..f92a524 100644 --- a/server/src/dashboard.html +++ b/server/src/dashboard.html @@ -1141,13 +1141,21 @@ const feed = document.getElementById('activity-feed'); if (!feed) return; feed.innerHTML = ''; - [...history].reverse().forEach(item => { + + // Sort history by timestamp descending so newest entries are at top + const sorted = [...history].sort((a, b) => { + const tA = (typeof a === 'object' && a) ? (a.timestamp || a.time || 0) : 0; + const tB = (typeof b === 'object' && b) ? (b.timestamp || b.time || 0) : 0; + return tB - tA; + }); + + sorted.forEach(item => { const div = document.createElement('div'); div.className = 'feed-entry'; div.innerHTML = parseActivityPayload(item); feed.appendChild(div); }); - if (history.length > 0) { + if (sorted.length > 0) { feed.scrollTop = 0; } } catch(e) { diff --git a/server/src/lib.rs b/server/src/lib.rs index 518ba1e..edf812f 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -392,7 +392,9 @@ pub async fn run_server(state: Arc) -> Result<(), Box Option { diff --git a/server/src/router.rs b/server/src/router.rs index 2c5b243..ce9c7b3 100644 --- a/server/src/router.rs +++ b/server/src/router.rs @@ -822,19 +822,26 @@ impl MemoryHandler { pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) -> String { let detail = match name { "log_code_change" => { - if let Some(file) = args.get("file").or_else(|| args.get("path")).and_then(|v| v.as_str()) { - if let Some(summary) = args.get("summary").and_then(|v| v.as_str()) { - format!("{}: {}", file, summary) - } else { - file.to_string() - } - } else { - args.get("summary").and_then(|v| v.as_str()).unwrap_or("").to_string() + let file = args.get("file_path") + .or_else(|| args.get("file")) + .or_else(|| args.get("path")) + .or_else(|| args.get("target_file")) + .and_then(|v| v.as_str()); + let summary = args.get("summary") + .or_else(|| args.get("description")) + .and_then(|v| v.as_str()); + + match (file, summary) { + (Some(f), Some(s)) => format!("{}: {}", f, s), + (Some(f), None) => f.to_string(), + (None, Some(s)) => s.to_string(), + (None, None) => "".to_string(), } } "log_decision" => { args.get("title") .or_else(|| args.get("decision")) + .or_else(|| args.get("summary")) .and_then(|v| v.as_str()) .unwrap_or("") .to_string() @@ -842,14 +849,15 @@ pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) -> "log_error_fix" => { args.get("error") .or_else(|| args.get("summary")) + .or_else(|| args.get("solution")) .and_then(|v| v.as_str()) .unwrap_or("") .to_string() } "log_tech_debt" => { - if let Some(summary) = args.get("summary").and_then(|v| v.as_str()) { + if let Some(summary) = args.get("summary").or_else(|| args.get("description")).and_then(|v| v.as_str()) { summary.to_string() - } else if let Some(file) = args.get("file").and_then(|v| v.as_str()) { + } else if let Some(file) = args.get("file_path").or_else(|| args.get("file")).and_then(|v| v.as_str()) { file.to_string() } else { "".to_string() @@ -859,7 +867,7 @@ pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) -> if let Some(entities) = args.get("entities").and_then(|v| v.as_array()) { let names: Vec<&str> = entities .iter() - .filter_map(|e| e.get("name").and_then(|n| n.as_str())) + .filter_map(|e| e.get("name").or_else(|| e.get("entity_name")).and_then(|n| n.as_str())) .take(3) .collect(); if !names.is_empty() { @@ -871,7 +879,7 @@ pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) -> } else { format!("{} entity/entities", entities.len()) } - } else if let Some(name) = args.get("name").and_then(|v| v.as_str()) { + } else if let Some(name) = args.get("name").or_else(|| args.get("entity_name")).and_then(|v| v.as_str()) { name.to_string() } else { "".to_string() @@ -882,7 +890,7 @@ pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) -> if let Some(first) = relations.first() { let from = first.get("from").and_then(|v| v.as_str()).unwrap_or("?"); let to = first.get("to").and_then(|v| v.as_str()).unwrap_or("?"); - let pred = first.get("predicate").and_then(|v| v.as_str()).unwrap_or("->"); + let pred = first.get("relation_type").or_else(|| first.get("predicate")).and_then(|v| v.as_str()).unwrap_or("->"); if relations.len() > 1 { format!("{} {} {} (+{} more)", from, pred, to, relations.len() - 1) } else { @@ -943,8 +951,10 @@ pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) -> if let Some(title) = args .get("title") .or_else(|| args.get("summary")) + .or_else(|| args.get("description")) .or_else(|| args.get("name")) .or_else(|| args.get("query")) + .or_else(|| args.get("file_path")) .or_else(|| args.get("file")) .or_else(|| args.get("path")) .and_then(|v| v.as_str())