mirror of
https://github.com/samvallad33/vestige.git
synced 2026-05-08 15:22:37 +02:00
feat: Vestige v2.0.0 "Cognitive Leap" — 3D dashboard, HyDE search, WebSocket events
The biggest release in Vestige history. Complete visual and cognitive overhaul. Dashboard: - SvelteKit 2 + Three.js 3D neural visualization at localhost:3927/dashboard - 7 interactive pages: Graph, Memories, Timeline, Feed, Explore, Intentions, Stats - WebSocket event bus with 16 event types, real-time 3D animations - Bloom post-processing, GPU instanced rendering, force-directed layout - Dream visualization mode, FSRS retention curves, command palette (Cmd+K) - Keyboard shortcuts, responsive mobile layout, PWA installable - Single binary deployment via include_dir! (22MB) Engine: - HyDE query expansion (intent classification + 3-5 semantic variants + centroid) - fastembed 5.11 with optional Nomic v2 MoE + Qwen3 reranker + Metal GPU - Emotional memory module (#29) - Criterion benchmark suite Backend: - Axum WebSocket at /ws with heartbeat + event broadcast - 7 new REST endpoints for cognitive operations - Event emission from MCP tools via shared broadcast channel - CORS for SvelteKit dev mode Distribution: - GitHub issue templates (bug report, feature request) - CHANGELOG with comprehensive v2.0 release notes - README updated with dashboard docs, architecture diagram, comparison table 734 tests passing, zero warnings, 22MB release binary. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
26cee040a5
commit
c2d28f3433
321 changed files with 32695 additions and 4727 deletions
|
|
@ -1,13 +1,22 @@
|
|||
//! stdio Transport for MCP
|
||||
//!
|
||||
//! Handles JSON-RPC communication over stdin/stdout.
|
||||
//! v1.9.2: Async tokio I/O with heartbeat and error resilience.
|
||||
|
||||
use std::io::{self, BufRead, BufReader, Write};
|
||||
use tracing::{debug, error, warn};
|
||||
use std::io;
|
||||
use std::time::Duration;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use super::types::{JsonRpcError, JsonRpcRequest, JsonRpcResponse};
|
||||
use crate::server::McpServer;
|
||||
|
||||
/// Maximum consecutive I/O errors before giving up
|
||||
const MAX_CONSECUTIVE_ERRORS: u32 = 5;
|
||||
|
||||
/// Heartbeat interval — sends a ping notification to keep the connection alive
|
||||
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(30);
|
||||
|
||||
/// stdio Transport for MCP server
|
||||
pub struct StdioTransport;
|
||||
|
||||
|
|
@ -16,66 +25,109 @@ impl StdioTransport {
|
|||
Self
|
||||
}
|
||||
|
||||
/// Run the MCP server over stdio
|
||||
/// Run the MCP server over stdio with heartbeat and error resilience
|
||||
pub async fn run(self, mut server: McpServer) -> Result<(), io::Error> {
|
||||
let stdin = io::stdin();
|
||||
let stdout = io::stdout();
|
||||
let stdin = tokio::io::stdin();
|
||||
let stdout = tokio::io::stdout();
|
||||
|
||||
let reader = BufReader::new(stdin.lock());
|
||||
let mut stdout = stdout.lock();
|
||||
let mut reader = BufReader::new(stdin);
|
||||
let mut stdout = stdout;
|
||||
let mut consecutive_errors: u32 = 0;
|
||||
let mut line_buf = String::new();
|
||||
|
||||
for line in reader.lines() {
|
||||
let line = match line {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
error!("Failed to read line: {}", e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
loop {
|
||||
line_buf.clear();
|
||||
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
tokio::select! {
|
||||
result = reader.read_line(&mut line_buf) => {
|
||||
match result {
|
||||
Ok(0) => {
|
||||
// Clean EOF — stdin closed
|
||||
info!("stdin closed (EOF), shutting down");
|
||||
break;
|
||||
}
|
||||
Ok(_) => {
|
||||
consecutive_errors = 0;
|
||||
let line = line_buf.trim();
|
||||
|
||||
debug!("Received: {} bytes", line.len());
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse JSON-RPC request
|
||||
let request: JsonRpcRequest = match serde_json::from_str(&line) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
warn!("Failed to parse request: {}", e);
|
||||
let error_response = JsonRpcResponse::error(None, JsonRpcError::parse_error());
|
||||
match serde_json::to_string(&error_response) {
|
||||
Ok(response_json) => {
|
||||
writeln!(stdout, "{}", response_json)?;
|
||||
stdout.flush()?;
|
||||
debug!("Received: {} bytes", line.len());
|
||||
|
||||
// Parse JSON-RPC request
|
||||
let request: JsonRpcRequest = match serde_json::from_str(line) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
warn!("Failed to parse request: {}", e);
|
||||
let error_response = JsonRpcResponse::error(None, JsonRpcError::parse_error());
|
||||
match serde_json::to_string(&error_response) {
|
||||
Ok(response_json) => {
|
||||
let out = format!("{}\n", response_json);
|
||||
stdout.write_all(out.as_bytes()).await?;
|
||||
stdout.flush().await?;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to serialize error response: {}", e);
|
||||
let fallback = "{\"jsonrpc\":\"2.0\",\"id\":null,\"error\":{\"code\":-32603,\"message\":\"Internal error\"}}\n";
|
||||
let _ = stdout.write_all(fallback.as_bytes()).await;
|
||||
let _ = stdout.flush().await;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Handle the request
|
||||
if let Some(response) = server.handle_request(request).await {
|
||||
match serde_json::to_string(&response) {
|
||||
Ok(response_json) => {
|
||||
debug!("Sending: {} bytes", response_json.len());
|
||||
let out = format!("{}\n", response_json);
|
||||
stdout.write_all(out.as_bytes()).await?;
|
||||
stdout.flush().await?;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to serialize response: {}", e);
|
||||
let fallback = "{\"jsonrpc\":\"2.0\",\"id\":null,\"error\":{\"code\":-32603,\"message\":\"Internal error\"}}\n";
|
||||
let _ = stdout.write_all(fallback.as_bytes()).await;
|
||||
let _ = stdout.flush().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to serialize error response: {}", e);
|
||||
// Send a minimal error response so client doesn't hang
|
||||
let fallback = r#"{"jsonrpc":"2.0","id":null,"error":{"code":-32603,"message":"Internal error"}}"#;
|
||||
let _ = writeln!(stdout, "{}", fallback);
|
||||
let _ = stdout.flush();
|
||||
consecutive_errors += 1;
|
||||
warn!(
|
||||
"I/O error reading stdin ({}/{}): {}",
|
||||
consecutive_errors, MAX_CONSECUTIVE_ERRORS, e
|
||||
);
|
||||
if consecutive_errors >= MAX_CONSECUTIVE_ERRORS {
|
||||
error!(
|
||||
"Too many consecutive I/O errors ({}), shutting down",
|
||||
consecutive_errors
|
||||
);
|
||||
break;
|
||||
}
|
||||
// Brief pause before retrying
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Handle the request
|
||||
if let Some(response) = server.handle_request(request).await {
|
||||
match serde_json::to_string(&response) {
|
||||
Ok(response_json) => {
|
||||
debug!("Sending: {} bytes", response_json.len());
|
||||
writeln!(stdout, "{}", response_json)?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to serialize response: {}", e);
|
||||
// Send a minimal error response so client doesn't hang
|
||||
let fallback = r#"{"jsonrpc":"2.0","id":null,"error":{"code":-32603,"message":"Internal error"}}"#;
|
||||
let _ = writeln!(stdout, "{}", fallback);
|
||||
let _ = stdout.flush();
|
||||
_ = tokio::time::sleep(HEARTBEAT_INTERVAL) => {
|
||||
// Send a heartbeat ping notification to keep the connection alive
|
||||
let ping = "{\"jsonrpc\":\"2.0\",\"method\":\"notifications/ping\"}\n";
|
||||
if let Err(e) = stdout.write_all(ping.as_bytes()).await {
|
||||
warn!("Failed to send heartbeat ping: {}", e);
|
||||
consecutive_errors += 1;
|
||||
if consecutive_errors >= MAX_CONSECUTIVE_ERRORS {
|
||||
error!("Too many consecutive errors, shutting down");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
let _ = stdout.flush().await;
|
||||
debug!("Heartbeat ping sent");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue