feat: event-driven parity, Windows shell detection, dirty build timestamps, and gatekeeper integration

This commit is contained in:
Riz Ashraf committed 2026-10-08 10:52:19 +01:00
1 parent 64857f9d5e
commit f4dcf3acbf
33 files changed
+3501 -143

No files matched your search

+110 -1
View File
@@ -42,6 +42,78 @@ pub struct TelemetryStores {
pub agent_signals: Store<Vec<AgentSignal>>,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct CachedClipboardImage {
pub file_path: String,
pub file_path_wsl: String,
pub captured_at_epoch_ms: u64,
pub age: String,
pub width: u32,
pub height: u32,
pub size_bytes: usize,
pub ocr_text: Option<String>,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct CachedClipboardText {
pub text: String,
pub captured_at_epoch_ms: u64,
pub age: String,
pub char_count: usize,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ClipboardHistoryItem {
Image(CachedClipboardImage),
Text(CachedClipboardText),
}
#[derive(Default)]
pub struct ClipboardCacheState {
pub last_image: Option<CachedClipboardImage>,
pub last_text: Option<CachedClipboardText>,
pub history: std::collections::VecDeque<ClipboardHistoryItem>,
}
impl ClipboardCacheState {
pub fn push_image(&mut self, image: CachedClipboardImage) {
self.last_image = Some(image.clone());
self.history.push_front(ClipboardHistoryItem::Image(image));
if self.history.len() > 20 {
self.history.pop_back();
}
}
pub fn push_text(&mut self, text: CachedClipboardText) {
if let Some(ref prev) = self.last_text {
if prev.text == text.text {
return;
}
}
self.last_text = Some(text.clone());
self.history.push_front(ClipboardHistoryItem::Text(text));
if self.history.len() > 20 {
self.history.pop_back();
}
}
}
pub fn format_age(epoch_ms: u64) -> String {
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
let diff_secs = now_ms.saturating_sub(epoch_ms) / 1000;
if diff_secs < 60 {
format!("{}s ago", diff_secs)
} else if diff_secs < 3600 {
format!("{}m ago", diff_secs / 60)
} else {
format!("{}h ago", diff_secs / 3600)
}
}
pub struct MemoryState {
pub base_dir: PathBuf,
pub index_commit_notify: Arc<tokio::sync::Notify>,
@@ -59,6 +131,7 @@ pub struct MemoryState {
pub activity_tx: tokio::sync::broadcast::Sender<String>,
pub event_bus_tx: tokio::sync::broadcast::Sender<GenericEvent>,
pub ollama: Arc<crate::ollama::OllamaClient>,
pub clipboard_cache: Arc<tokio::sync::RwLock<ClipboardCacheState>>,
}
impl MemoryState {
@@ -133,6 +206,7 @@ impl MemoryState {
activity_tx: tokio::sync::broadcast::channel(100).0,
event_bus_tx: tokio::sync::broadcast::channel(1000).0,
clipboard_cache: Arc::new(tokio::sync::RwLock::new(ClipboardCacheState::default())),
};
// Normalize pre-existing graph entity and relation types
@@ -162,6 +236,19 @@ impl MemoryState {
pub fn modify_graph<F: FnOnce(&mut KnowledgeGraph)>(&self, update_fn: F) {
self.graph.modify(update_fn);
let threshold: usize = std::env::var("MCP_MEMORY_CONDENSE_THRESHOLD")
.unwrap_or_else(|_| "100".to_string())
.parse()
.unwrap_or(100);
let count = self.graph.read_with(|g| g.entities.len());
if count >= threshold {
self.condense_notify.notify_one();
}
let _ = self.event_bus_tx.send(GenericEvent {
topic: "resource:updated".to_string(),
session_id: None,
payload: serde_json::json!({ "uri": "memory://graph" }),
});
}
pub async fn get_search_index(&self) -> MemoryIndex {
@@ -283,7 +370,24 @@ impl MemoryState {
}
pub fn broadcast_task_event(&self, event: TaskEvent) {
let payload_val = serde_json::to_value(&event).unwrap_or_default();
let mut payload_val = serde_json::to_value(&event).unwrap_or_default();
if let Some(obj) = payload_val.as_object_mut() {
let active_task = self.project.tasks.read_with(|tasks| {
tasks
.iter()
.find(|t| t.is_active())
.or_else(|| {
tasks
.iter()
.find(|t| t.status == "pending" && t.parent_id.is_none())
})
.map(|t| t.title.clone())
});
obj.insert(
"active_task".to_string(),
serde_json::to_value(active_task).unwrap_or_default(),
);
}
let summary_str = format!("Task {} -> {}", event.task_id, event.status);
let details_str = payload_val.to_string();
@@ -315,6 +419,11 @@ impl MemoryState {
payload: payload_val,
};
let _ = self.event_bus_tx.send(generic_ev);
let _ = self.event_bus_tx.send(GenericEvent {
topic: "resource:updated".to_string(),
session_id: None,
payload: serde_json::json!({ "uri": "memory://tasks/active" }),
});
let ws_notification = serde_json::json!({
"jsonrpc": "2.0",