fix: ensure authenticated graceful shutdown exits process and sort live activity feed by timestamp descending
This commit is contained in:
1 parent
3efee96230
commit
91d5565141
5 files changed
+46
-22
No files matched your search
@@ -35,12 +35,8 @@ deploy-win: deploy-server deploy-stub-win deploy-nvim-win deploy-rules-win
|
|||||||
# Stop the background server
|
# Stop the background server
|
||||||
stop port="3000":
|
stop port="3000":
|
||||||
@Write-Host "Shutting down MCP server gracefully on port {{port}}..." -ForegroundColor Cyan
|
@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 }
|
-& { $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 } }
|
||||||
$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 }
|
@Write-Host "✅ Shutdown complete. Port {{port}} and process are free." -ForegroundColor Green
|
||||||
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
|
|
||||||
|
|
||||||
# Start the server in the background (nohup equivalent)
|
# Start the server in the background (nohup equivalent)
|
||||||
start port="3000":
|
start port="3000":
|
||||||
@@ -71,6 +67,8 @@ build-stub-win:
|
|||||||
# =========================================================
|
# =========================================================
|
||||||
deploy-server:
|
deploy-server:
|
||||||
@Write-Host "Deploying Windows Server..." -ForegroundColor Cyan
|
@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 }
|
-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\"
|
Copy-Item -Force target\release\mcp-memory-server.exe "C:\Users\reazul.ashraf\.local\bin\"
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,12 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
|
|||||||
if let Some(tx) = state.shutdown_tx.lock().unwrap().take() {
|
if let Some(tx) = state.shutdown_tx.lock().unwrap().take() {
|
||||||
let _ = tx.send(());
|
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()
|
(axum::http::StatusCode::OK, "Shutting down...").into_response()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1141,13 +1141,21 @@
|
|||||||
const feed = document.getElementById('activity-feed');
|
const feed = document.getElementById('activity-feed');
|
||||||
if (!feed) return;
|
if (!feed) return;
|
||||||
feed.innerHTML = '';
|
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');
|
const div = document.createElement('div');
|
||||||
div.className = 'feed-entry';
|
div.className = 'feed-entry';
|
||||||
div.innerHTML = parseActivityPayload(item);
|
div.innerHTML = parseActivityPayload(item);
|
||||||
feed.appendChild(div);
|
feed.appendChild(div);
|
||||||
});
|
});
|
||||||
if (history.length > 0) {
|
if (sorted.length > 0) {
|
||||||
feed.scrollTop = 0;
|
feed.scrollTop = 0;
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
|||||||
+3
-1
@@ -392,7 +392,9 @@ pub async fn run_server(state: Arc<MemoryState>) -> Result<(), Box<dyn std::erro
|
|||||||
.join(".gemini/mcp_memory/daemon_error.log");
|
.join(".gemini/mcp_memory/daemon_error.log");
|
||||||
let _ = tokio::fs::write(&log_path, format!("Server crashed: {}\n", e)).await;
|
let _ = tokio::fs::write(&log_path, format!("Server crashed: {}\n", e)).await;
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
|
tracing::info!("axum::serve graceful shutdown complete. Terminating process cleanly.");
|
||||||
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_logging(app_name: &str) -> Option<tracing_appender::non_blocking::WorkerGuard> {
|
pub fn init_logging(app_name: &str) -> Option<tracing_appender::non_blocking::WorkerGuard> {
|
||||||
|
|||||||
+23
-13
@@ -822,19 +822,26 @@ impl MemoryHandler {
|
|||||||
pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) -> String {
|
pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) -> String {
|
||||||
let detail = match name {
|
let detail = match name {
|
||||||
"log_code_change" => {
|
"log_code_change" => {
|
||||||
if let Some(file) = args.get("file").or_else(|| args.get("path")).and_then(|v| v.as_str()) {
|
let file = args.get("file_path")
|
||||||
if let Some(summary) = args.get("summary").and_then(|v| v.as_str()) {
|
.or_else(|| args.get("file"))
|
||||||
format!("{}: {}", file, summary)
|
.or_else(|| args.get("path"))
|
||||||
} else {
|
.or_else(|| args.get("target_file"))
|
||||||
file.to_string()
|
.and_then(|v| v.as_str());
|
||||||
}
|
let summary = args.get("summary")
|
||||||
} else {
|
.or_else(|| args.get("description"))
|
||||||
args.get("summary").and_then(|v| v.as_str()).unwrap_or("").to_string()
|
.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" => {
|
"log_decision" => {
|
||||||
args.get("title")
|
args.get("title")
|
||||||
.or_else(|| args.get("decision"))
|
.or_else(|| args.get("decision"))
|
||||||
|
.or_else(|| args.get("summary"))
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_string()
|
.to_string()
|
||||||
@@ -842,14 +849,15 @@ pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) ->
|
|||||||
"log_error_fix" => {
|
"log_error_fix" => {
|
||||||
args.get("error")
|
args.get("error")
|
||||||
.or_else(|| args.get("summary"))
|
.or_else(|| args.get("summary"))
|
||||||
|
.or_else(|| args.get("solution"))
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
"log_tech_debt" => {
|
"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()
|
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()
|
file.to_string()
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
"".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()) {
|
if let Some(entities) = args.get("entities").and_then(|v| v.as_array()) {
|
||||||
let names: Vec<&str> = entities
|
let names: Vec<&str> = entities
|
||||||
.iter()
|
.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)
|
.take(3)
|
||||||
.collect();
|
.collect();
|
||||||
if !names.is_empty() {
|
if !names.is_empty() {
|
||||||
@@ -871,7 +879,7 @@ pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) ->
|
|||||||
} else {
|
} else {
|
||||||
format!("{} entity/entities", entities.len())
|
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()
|
name.to_string()
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
@@ -882,7 +890,7 @@ pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) ->
|
|||||||
if let Some(first) = relations.first() {
|
if let Some(first) = relations.first() {
|
||||||
let from = first.get("from").and_then(|v| v.as_str()).unwrap_or("?");
|
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 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 {
|
if relations.len() > 1 {
|
||||||
format!("{} {} {} (+{} more)", from, pred, to, relations.len() - 1)
|
format!("{} {} {} (+{} more)", from, pred, to, relations.len() - 1)
|
||||||
} else {
|
} else {
|
||||||
@@ -943,8 +951,10 @@ pub fn format_tool_activity_description(name: &str, args: &serde_json::Value) ->
|
|||||||
if let Some(title) = args
|
if let Some(title) = args
|
||||||
.get("title")
|
.get("title")
|
||||||
.or_else(|| args.get("summary"))
|
.or_else(|| args.get("summary"))
|
||||||
|
.or_else(|| args.get("description"))
|
||||||
.or_else(|| args.get("name"))
|
.or_else(|| args.get("name"))
|
||||||
.or_else(|| args.get("query"))
|
.or_else(|| args.get("query"))
|
||||||
|
.or_else(|| args.get("file_path"))
|
||||||
.or_else(|| args.get("file"))
|
.or_else(|| args.get("file"))
|
||||||
.or_else(|| args.get("path"))
|
.or_else(|| args.get("path"))
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
|
|||||||
Reference in new issue
Block a user