feat(server): refactor handlers, router, state management, and memory tools

This commit is contained in:
Riz Ashraf committed 2026-10-02 07:27:37 +01:00
1 parent 87ddb01063
commit a083719cf1
36 files changed
+1899 -597

No files matched your search

+68 -18
View File
@@ -19,6 +19,7 @@ pub struct ProjectStores {
pub pr_checklists: Store<Vec<PrChecklistItem>>,
pub context_workspaces: Store<Vec<ContextWorkspace>>,
pub pinned_files: Store<Vec<PinnedFile>>,
pub snapshots: Store<Vec<StateSnapshot>>,
}
pub struct CodeStores {
@@ -51,7 +52,7 @@ pub struct MemoryState {
pub graph: Store<KnowledgeGraph>,
pub search_index: RwLock<MemoryIndex>,
pub vector_db: tokio::sync::RwLock<Option<VectorDB>>,
pub project: ProjectStores,
pub code: CodeStores,
pub env: EnvironmentStores,
@@ -59,6 +60,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>,
}
impl MemoryState {
@@ -69,6 +71,7 @@ impl MemoryState {
let db = crate::db::init_redb(&base);
Self {
ollama: Arc::new(crate::ollama::OllamaClient::new_from_env()),
clipboard_watch_mode: tokio::sync::RwLock::new(false),
graph: Store::new("knowledge_graph_master", db.clone()),
base_dir: base.clone(),
@@ -84,13 +87,14 @@ impl MemoryState {
}
}),
vector_db: tokio::sync::RwLock::new(None),
project: ProjectStores {
tasks: Store::new("tasks", db.clone()),
milestones: Store::new("milestones", db.clone()),
pr_checklists: Store::new("pr_checklists", db.clone()),
context_workspaces: Store::new("context_workspaces", db.clone()),
pinned_files: Store::new("pinned_files", db.clone()),
snapshots: Store::new("state_snapshots", db.clone()),
},
code: CodeStores {
ledger: Store::new("audit_ledger", db.clone()),
@@ -211,6 +215,31 @@ impl MemoryState {
*w = idx;
}
}
pub fn record_activity(&self, category: &str, summary: &str, details: Option<&str>) {
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let activity = ActivityRecord {
timestamp: ts,
category: category.to_string(),
summary: summary.to_string(),
details: details.map(|s| s.to_string()),
};
let record = serde_json::to_value(&activity).unwrap_or_default();
self.telemetry.recent_activities.modify(|activities| {
activities.push_front(record.clone());
if activities.len() > 100 {
activities.pop_back();
}
});
let _ = self.activity_tx.send(record.to_string());
}
}
#[cfg(test)]
@@ -276,8 +305,7 @@ mod tests {
}
}
use crate::embedding::{generate_embedding_async, generate_embeddings_async, cosine_similarity};
use crate::embedding::{cosine_similarity, generate_embedding_async, generate_embeddings_async};
pub struct UnifiedSearchResult {
pub id: String,
@@ -296,10 +324,17 @@ impl SearchService {
Self { state }
}
pub async fn semantic_search(&self, query: &str, _filter_namespace: Option<&str>, limit: usize) -> crate::error::Result<Vec<UnifiedSearchResult>> {
let query_emb = generate_embedding_async(query.to_string()).await.unwrap_or_default();
pub async fn semantic_search(
&self,
query: &str,
_filter_namespace: Option<&str>,
limit: usize,
) -> crate::error::Result<Vec<UnifiedSearchResult>> {
let query_emb = generate_embedding_async(query.to_string())
.await
.unwrap_or_default();
let mut results = Vec::new();
let mut vdb_search = false;
if let Some(vdb) = &*self.state.vector_db.read().await {
vdb_search = true;
@@ -315,24 +350,28 @@ impl SearchService {
}
}
}
if !vdb_search {
let mut texts_to_embed = Vec::new();
let mut metadata = Vec::new();
let snippets = self.state.code.snippets.read_with(|snips| snips.clone());
for snippet in snippets {
let combined = format!("{} {} {}", snippet.name, snippet.description, snippet.code);
texts_to_embed.push(combined);
metadata.push((snippet.name, "snippet".to_string(), snippet.description));
}
let sticky = self.state.code.sticky.read_with(|s| s.clone());
for note in sticky {
texts_to_embed.push(note.content.clone());
metadata.push(("StickyNote".to_string(), "sticky".to_string(), note.content.chars().take(200).collect::<String>()));
metadata.push((
"StickyNote".to_string(),
"sticky".to_string(),
note.content.chars().take(200).collect::<String>(),
));
}
if let Ok(embeddings) = generate_embeddings_async(texts_to_embed).await {
for (emb, meta) in embeddings.into_iter().zip(metadata) {
let sim = cosine_similarity(&query_emb, &emb);
@@ -345,18 +384,29 @@ impl SearchService {
});
}
}
results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal));
results.sort_by(|a, b| {
b.score
.partial_cmp(&a.score)
.unwrap_or(std::cmp::Ordering::Equal)
});
results.truncate(limit);
}
Ok(results)
}
pub fn keyword_search(&self, query: &str, filter_namespace: Option<&str>, limit: usize) -> crate::error::Result<Vec<UnifiedSearchResult>> {
pub fn keyword_search(
&self,
query: &str,
filter_namespace: Option<&str>,
limit: usize,
) -> crate::error::Result<Vec<UnifiedSearchResult>> {
let idx = self.state.get_search_index();
let matches = idx.search(query, filter_namespace).map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
let matches = idx
.search(query, filter_namespace)
.map_err(|e| crate::error::AppError::Internal(e.to_string()))?;
let mut results = Vec::new();
for (id, doc_type, title, body, score) in matches.into_iter().take(limit) {
results.push(UnifiedSearchResult {