// CC Sesija A §2.1 Tasks A1 + A4 — memory + KG + identity Tauri commands. // // Architecture: thin HTTP proxies to local sidecar (Fastify, port from ServiceState). // Tauri Rust shell intentionally has no direct sqlite/sqlite-vec dependencies — all // substrate access goes through the sidecar so that the storage layer (FrameStore, // HybridSearch, KnowledgeGraph, wiki-compiler) lives in one process. // // Command surface (4 commands here; wiki commands moved to commands/wiki.rs at A4): // recall_memory → GET /api/memory/search // save_memory → POST /api/memory/frames // search_entities → GET /api/memory/graph // get_identity → GET /api/identity (route not yet in sidecar — graceful 404 // → placeholder; A1.1 follow-up adds route) // // Shared HTTP helpers extracted to commands/http.rs at A4 (rule of two — wiki.rs is // the second consumer). use serde_json::{json, Value}; use tauri::State; use crate::commands::http::{http_get, http_post, parse_json, sidecar_url}; use crate::service::ServiceState; /// Recall memory frames matching `query`. Optional `scope` (all|personal|workspace), /// `limit` (default sidecar-side ~20), `workspace_id` (workspace mind to search). #[tauri::command] pub async fn recall_memory( state: State<'_, ServiceState>, query: String, scope: Option, limit: Option, workspace_id: Option, ) -> Result { let mut url = format!( "{}?q={}", sidecar_url(state.port, "/api/memory/search"), urlencoding::encode(&query) ); if let Some(s) = scope { url.push_str(&format!("&scope={}", urlencoding::encode(&s))); } if let Some(l) = limit { url.push_str(&format!("&limit={}", l)); } if let Some(ws) = workspace_id { url.push_str(&format!("&workspace={}", urlencoding::encode(&ws))); } let resp = http_get(&url).await?; parse_json(resp).await } /// Persist a new memory frame. `content` is required. Optional `workspace_id`, /// `importance` (low|normal|high|critical per @waggle/core Importance enum), `source` /// (one of: user_stated, tool_verified, agent_inferred, import, system). #[tauri::command] pub async fn save_memory( state: State<'_, ServiceState>, content: String, workspace_id: Option, importance: Option, source: Option, ) -> Result { let mut body = json!({ "content": content }); if let Some(ws) = workspace_id { body["workspace"] = json!(ws); } if let Some(imp) = importance { body["importance"] = json!(imp); } if let Some(src) = source { body["source"] = json!(src); } let url = sidecar_url(state.port, "/api/memory/frames"); let resp = http_post(&url, &body).await?; parse_json(resp).await } /// Search the knowledge graph for entities/relations. Returns `{nodes, edges}` shape /// from sidecar's KnowledgeGraph layer. `scope` defaults to "all" if not provided. #[tauri::command] pub async fn search_entities( state: State<'_, ServiceState>, workspace_id: Option, scope: Option, ) -> Result { let mut url = sidecar_url(state.port, "/api/memory/graph").to_string(); let mut params: Vec = Vec::new(); if let Some(ws) = workspace_id { params.push(format!("workspace={}", urlencoding::encode(&ws))); } if let Some(s) = scope { params.push(format!("scope={}", urlencoding::encode(&s))); } if !params.is_empty() { url.push('?'); url.push_str(¶ms.join("&")); } let resp = http_get(&url).await?; parse_json(resp).await } /// Get the user identity record from sidecar's IdentityLayer. /// /// Backed by /api/identity sidecar route (A1.1 shipped 2026-04-30). The route /// always returns a 200 with either configured: true + IdentityLayer fields, /// or configured: false + null fields + optional _note. The 404 + transport- /// error fallbacks here remain as defense-in-depth — they were the original /// pre-A1.1 placeholders and now only fire on hard sidecar outages. #[tauri::command] pub async fn get_identity(state: State<'_, ServiceState>) -> Result { let url = sidecar_url(state.port, "/api/identity"); match http_get(&url).await { Ok(resp) if resp.status().as_u16() == 404 => Ok(identity_placeholder( "sidecar route 404 (unexpected post-A1.1)", )), Ok(resp) => parse_json(resp).await, Err(_) => Ok(identity_placeholder("sidecar unreachable")), } } fn identity_placeholder(note: &str) -> Value { json!({ "configured": false, "name": null, "role": null, "department": null, "personality": null, "capabilities": null, "system_prompt": null, "created_at": null, "updated_at": null, "_note": note }) }