engram_core/
insights_handler.rs1use std::sync::Arc;
2
3use serde::Deserialize;
4use serde_json::{Value, json};
5
6use engram_storage::Memory;
7
8use crate::error::CoreError;
9use crate::export_handler::memory_to_portable_json;
10use crate::server::ServerState;
11
12#[derive(Deserialize)]
13struct InsightsParams {
14 action: String,
15 id: Option<String>,
16}
17
18pub async fn handle(state: &Arc<ServerState>, params: Value) -> Result<Value, CoreError> {
19 let parsed: InsightsParams = serde_json::from_value(params)
20 .map_err(|error| CoreError::DispatchError(error.to_string()))?;
21 match parsed.action.as_str() {
22 "list" => handle_list(state).await,
23 "generate" => Err(CoreError::DispatchError(
24 "insight generation requires engram-trainer (Phase 5)".into(),
25 )),
26 "delete" => handle_delete(state, parsed.id).await,
27 other => Err(CoreError::DispatchError(format!(
28 "invalid insights action: {other}"
29 ))),
30 }
31}
32
33async fn handle_list(state: &Arc<ServerState>) -> Result<Value, CoreError> {
34 let state_clone = Arc::clone(state);
35 tokio::task::spawn_blocking(move || {
36 let database = state_clone.database.lock().unwrap();
37 let insights = query_active_insights(&database)?;
38 let count = insights.len();
39 let serialized: Vec<Value> = insights.iter().map(memory_to_portable_json).collect();
40 Ok::<Value, CoreError>(json!({ "insights": serialized, "count": count }))
41 })
42 .await
43 .map_err(|error| CoreError::SocketError(error.to_string()))?
44}
45
46async fn handle_delete(state: &Arc<ServerState>, id: Option<String>) -> Result<Value, CoreError> {
47 let insight_id =
48 id.ok_or_else(|| CoreError::DispatchError("delete requires 'id' parameter".into()))?;
49 let state_clone = Arc::clone(state);
50 tokio::task::spawn_blocking(move || {
51 let database = state_clone.database.lock().unwrap();
52 let memory = database.get_memory(&insight_id)?;
53 if memory.memory_type != "insight" {
54 return Err(CoreError::DispatchError(format!(
55 "memory {} is not an insight",
56 insight_id
57 )));
58 }
59 database.delete_memory(&insight_id)?;
60 Ok::<Value, CoreError>(json!({ "deleted": insight_id }))
61 })
62 .await
63 .map_err(|error| CoreError::SocketError(error.to_string()))?
64}
65
66fn query_active_insights(database: &engram_storage::Database) -> Result<Vec<Memory>, CoreError> {
67 let mut statement = database
68 .connection()
69 .prepare(
70 "SELECT * FROM memories WHERE memory_type = 'insight' \
71 AND superseded_by IS NULL",
72 )
73 .map_err(|error| CoreError::Storage(engram_storage::StorageError::Sqlite(error)))?;
74 let rows = statement
75 .query_map([], engram_storage::row_to_memory)
76 .map_err(|error| CoreError::Storage(engram_storage::StorageError::Sqlite(error)))?;
77 let mut insights = Vec::new();
78 for row in rows {
79 insights.push(
80 row.map_err(|error| CoreError::Storage(engram_storage::StorageError::Sqlite(error)))?,
81 );
82 }
83 Ok(insights)
84}