feat: event-driven parity, Windows shell detection, dirty build timestamps, and gatekeeper integration
This commit is contained in:
1 parent
64857f9d5e
commit
f4dcf3acbf
33 files changed
+3501
-143
No files matched your search
+99
-10
@@ -96,7 +96,62 @@ pub struct AppState {
|
||||
|
||||
pub async fn ttl_sweeper_worker(state: Arc<MemoryState>) {
|
||||
loop {
|
||||
state.ttl_notify.notified().await;
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
|
||||
let mut next_expiry: Option<u64> = None;
|
||||
|
||||
state.project.tasks.read_with(|tasks| {
|
||||
for t in tasks.iter() {
|
||||
if let Some(exp) = t.expires_at {
|
||||
if t.is_active() {
|
||||
next_expiry = Some(next_expiry.map_or(exp, |curr| curr.min(exp)));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
state.telemetry.handoff_memos.read_with(|memos| {
|
||||
for m in memos.iter() {
|
||||
if let Some(exp) = m.expires_at {
|
||||
next_expiry = Some(next_expiry.map_or(exp, |curr| curr.min(exp)));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
state.telemetry.session_summaries.read_with(|summaries| {
|
||||
for s in summaries.iter() {
|
||||
if let Some(exp) = s.expires_at {
|
||||
next_expiry = Some(next_expiry.map_or(exp, |curr| curr.min(exp)));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
state.env.gates.read_with(|gates| {
|
||||
for g in gates.iter() {
|
||||
if let Some(exp) = g.expires_at {
|
||||
next_expiry = Some(next_expiry.map_or(exp, |curr| curr.min(exp)));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let sleep_duration = match next_expiry {
|
||||
Some(exp) if exp > now => {
|
||||
let diff = exp - now;
|
||||
std::time::Duration::from_secs(diff.min(60).max(1))
|
||||
}
|
||||
Some(_) => std::time::Duration::from_millis(50),
|
||||
None => std::time::Duration::from_secs(60),
|
||||
};
|
||||
|
||||
tokio::select! {
|
||||
_ = state.shutdown_notify.notified() => break,
|
||||
_ = state.ttl_notify.notified() => {},
|
||||
_ = tokio::time::sleep(sleep_duration) => {},
|
||||
}
|
||||
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
@@ -139,12 +194,18 @@ pub async fn ttl_sweeper_worker(state: Arc<MemoryState>) {
|
||||
state.telemetry.session_summaries.modify(|summaries| {
|
||||
summaries.retain(|s| s.expires_at.is_none_or(|exp| exp > now));
|
||||
});
|
||||
state.env.gates.modify(|gates| {
|
||||
gates.retain(|g| g.expires_at.is_none_or(|exp| exp > now));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn index_committer_worker(state: Arc<MemoryState>) {
|
||||
loop {
|
||||
state.index_commit_notify.notified().await;
|
||||
tokio::select! {
|
||||
_ = state.shutdown_notify.notified() => break,
|
||||
_ = state.index_commit_notify.notified() => {},
|
||||
}
|
||||
let idx = state.search_index.read().await.clone();
|
||||
let _ = idx.commit().await;
|
||||
}
|
||||
@@ -152,7 +213,10 @@ pub async fn index_committer_worker(state: Arc<MemoryState>) {
|
||||
|
||||
pub async fn condense_graph_worker(state: Arc<MemoryState>) {
|
||||
loop {
|
||||
state.condense_notify.notified().await;
|
||||
tokio::select! {
|
||||
_ = state.shutdown_notify.notified() => break,
|
||||
_ = state.condense_notify.notified() => {},
|
||||
}
|
||||
|
||||
let threshold: usize = std::env::var("MCP_MEMORY_CONDENSE_THRESHOLD")
|
||||
.unwrap_or_else(|_| "100".to_string())
|
||||
@@ -224,6 +288,7 @@ pub async fn run_server(state: Arc<MemoryState>) -> Result<(), Box<dyn std::erro
|
||||
tokio::spawn(ttl_sweeper_worker(Arc::clone(&state)));
|
||||
tokio::spawn(condense_graph_worker(Arc::clone(&state)));
|
||||
crate::watcher::spawn_watcher(Arc::clone(&state));
|
||||
crate::handlers::vision::spawn_clipboard_listener(Arc::clone(&state));
|
||||
let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
|
||||
let app_state = Arc::new(AppState {
|
||||
handler: Arc::new(MemoryHandler::new(Arc::clone(&state))),
|
||||
@@ -372,14 +437,38 @@ pub async fn run_server(state: Arc<MemoryState>) -> Result<(), Box<dyn std::erro
|
||||
let _ = nvim_udp_state.handler.state.event_bus_tx.send(event);
|
||||
}
|
||||
|
||||
if payload.event.starts_with("agent_") {
|
||||
let topic = format!("nvim:ui:{}", payload.event);
|
||||
let event = crate::state::GenericEvent {
|
||||
topic,
|
||||
if payload.event.starts_with("agent_") || payload.event.starts_with("diff_") {
|
||||
let payload_val = serde_json::json!(&payload);
|
||||
// 1. General event topic (e.g. nvim:ui:agent_prompt_response, nvim:ui:agent_diff_accepted)
|
||||
let _ = nvim_udp_state.handler.state.event_bus_tx.send(crate::state::GenericEvent {
|
||||
topic: format!("nvim:ui:{}", payload.event),
|
||||
session_id: Some(payload.session_id.clone()),
|
||||
payload: serde_json::json!(&payload),
|
||||
};
|
||||
let _ = nvim_udp_state.handler.state.event_bus_tx.send(event);
|
||||
payload: payload_val.clone(),
|
||||
});
|
||||
|
||||
// 2. Correlated request_id topic (e.g. nvim:ui:agent_prompt_response:REQ_ID)
|
||||
if let Some(ref req_id) = payload.request_id {
|
||||
let _ = nvim_udp_state.handler.state.event_bus_tx.send(crate::state::GenericEvent {
|
||||
topic: format!("nvim:ui:{}:{}", payload.event, req_id),
|
||||
session_id: Some(payload.session_id.clone()),
|
||||
payload: payload_val.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Correlated diff_id topics
|
||||
if let Some(ref diff_id) = payload.diff_id {
|
||||
let _ = nvim_udp_state.handler.state.event_bus_tx.send(crate::state::GenericEvent {
|
||||
topic: format!("nvim:ui:{}:{}", payload.event, diff_id),
|
||||
session_id: Some(payload.session_id.clone()),
|
||||
payload: payload_val.clone(),
|
||||
});
|
||||
// General diff decision topic
|
||||
let _ = nvim_udp_state.handler.state.event_bus_tx.send(crate::state::GenericEvent {
|
||||
topic: format!("nvim:ui:diff_decision:{}", diff_id),
|
||||
session_id: Some(payload.session_id.clone()),
|
||||
payload: payload_val.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user