1use std::str::FromStr;
2use std::sync::Arc;
3
4use chrono::Utc;
5use rmcp::handler::server::tool::schema_for_type;
6use rmcp::model::{CallToolResult, Content, JsonObject, Tool};
7use schemars::JsonSchema;
8use serde::Deserialize;
9use serde_json::json;
10use uuid::Uuid;
11
12use crate::config::settings::Settings;
13use crate::store::db::Database;
14use crate::store::memory::{
15 CreateMemoryInput, CreatePromptInput, Scope, SearchQuery, UpdateMemoryInput,
16};
17
18fn tool_response<T: serde::Serialize>(
20 result: crate::error::Result<T>,
21 project: &str,
22) -> CallToolResult {
23 let meta = json!({
24 "project": project,
25 "timestamp": Utc::now().to_rfc3339(),
26 "version": env!("CARGO_PKG_VERSION")
27 });
28
29 match result {
30 Ok(data) => {
31 let envelope = json!({
32 "success": true,
33 "data": data,
34 "error": null,
35 "meta": meta
36 });
37 match Content::json(envelope) {
38 Ok(content) => CallToolResult::success(vec![content]),
39 Err(e) => {
40 let err_envelope = json!({
41 "success": false,
42 "data": null,
43 "error": {
44 "code": "SERIALIZATION_ERROR",
45 "message": e.to_string()
46 },
47 "meta": meta
48 });
49 CallToolResult::error(vec![Content::text(err_envelope.to_string())])
50 }
51 }
52 }
53 Err(e) => {
54 let code = match e {
55 crate::error::MnemeError::NotFound(_) => "NOT_FOUND",
56 crate::error::MnemeError::ProjectRequired => "PROJECT_REQUIRED",
57 crate::error::MnemeError::EmptyQuery => "EMPTY_QUERY",
58 crate::error::MnemeError::InvalidMemoryType(_) => "INVALID_MEMORY_TYPE",
59 crate::error::MnemeError::InvalidImportance(_) => "INVALID_IMPORTANCE",
60 crate::error::MnemeError::InvalidScope(_) => "INVALID_SCOPE",
61 crate::error::MnemeError::InvalidRelationType(_) => "INVALID_RELATION_TYPE",
62 crate::error::MnemeError::RelationAlreadyExists(_, _) => "RELATION_EXISTS",
63 crate::error::MnemeError::SelfRelation(_) => "SELF_RELATION",
64 crate::error::MnemeError::DuplicateDetected(_) => "DUPLICATE",
65 crate::error::MnemeError::Database(_) => "DATABASE_ERROR",
66 crate::error::MnemeError::Migration(_) => "MIGRATION_ERROR",
67 crate::error::MnemeError::Io(_) => "IO_ERROR",
68 crate::error::MnemeError::Serialization(_) => "SERIALIZATION_ERROR",
69 crate::error::MnemeError::Config(_) => "CONFIG_ERROR",
70 crate::error::MnemeError::Http(_) => "HTTP_ERROR",
71 crate::error::MnemeError::Mcp(_) => "MCP_ERROR",
72 crate::error::MnemeError::Embeddings(_) => "EMBEDDINGS_ERROR",
73 crate::error::MnemeError::EmbeddingsDisabled => "EMBEDDINGS_DISABLED",
74 crate::error::MnemeError::PeerNotFound(_) => "PEER_NOT_FOUND",
75 crate::error::MnemeError::SyncFailed { .. } => "SYNC_FAILED",
76 crate::error::MnemeError::SyncDisabled => "SYNC_DISABLED",
77 crate::error::MnemeError::UnsupportedTransport(_) => "UNSUPPORTED_TRANSPORT",
78 crate::error::MnemeError::InvalidSyncFile(_) => "INVALID_SYNC_FILE",
79 crate::error::MnemeError::Compression(_) => "COMPRESSION_ERROR",
80 crate::error::MnemeError::NoRecipientsConfigured => "NO_RECIPIENTS",
81 crate::error::MnemeError::DecryptionFailed => "DECRYPTION_FAILED",
82 crate::error::MnemeError::IdentityNotLoaded => "IDENTITY_NOT_LOADED",
83 crate::error::MnemeError::AlreadyEncrypted(_) => "ALREADY_ENCRYPTED",
84 crate::error::MnemeError::NotEncrypted(_) => "NOT_ENCRYPTED",
85 crate::error::MnemeError::KeyNotFound(_) => "KEY_NOT_FOUND",
86 crate::error::MnemeError::Plugin(_) => "PLUGIN_ERROR",
87 };
88 let envelope = json!({
89 "success": false,
90 "data": null,
91 "error": {
92 "code": code,
93 "message": e.to_string()
94 },
95 "meta": meta
96 });
97 CallToolResult::error(vec![Content::text(envelope.to_string())])
98 }
99 }
100}
101
102pub async fn execute_tool(
104 db: &Database,
105 name: &str,
106 arguments: Option<JsonObject>,
107 project: &str,
108 embeddings: Option<&std::sync::Arc<crate::embeddings::engine::EmbeddingEngine>>,
109 plugins: Option<&crate::plugins::PluginManager>,
110) -> CallToolResult {
111 tracing::debug!("MCP tool call: {} with args: {:?}", name, arguments);
112
113 let args = arguments.unwrap_or_default();
114
115 if let Some(pm) = plugins {
117 if pm.owns_tool(name) {
118 let args_value = serde_json::Value::Object(args);
119 let result = pm.call_tool(name, args_value, project);
120 return tool_response(result, project);
121 }
122 }
123
124 let result = match name {
125 "mem_save" => mem_save(db, args, project, embeddings),
126 "mem_update" => mem_update(db, args, project),
127 "mem_delete" => mem_delete(db, args, project),
128 "mem_restore" => mem_restore(db, args, project),
129 "mem_search" => mem_search(db, args, project),
130 "mem_similar" => mem_similar(db, args, project, embeddings).await,
131 "mem_get" => mem_get(db, args, project),
132 "mem_list" => mem_list(db, args, project),
133 "mem_context" => mem_context(db, args, project),
134 "mem_timeline" => mem_timeline(db, args, project),
135 "mem_session_start" => mem_session_start(db, args, project),
136 "mem_session_end" => mem_session_end(db, args, project),
137 "mem_session_summary" => mem_session_summary(db, args, project),
138 "mem_stats" => mem_stats(db, args, project),
139 "mem_projects" => mem_projects(db, args, project),
140 "mem_conflicts" => mem_conflicts(db, args, project),
141 "mem_save_prompt" => mem_save_prompt(db, args, project),
142 "mem_suggest_topic_key" => mem_suggest_topic_key(db, args, project),
143 "mem_current_project" => mem_current_project(db, args, project),
144 "mem_doctor" => mem_doctor(db, args, project),
145 "mem_save_batch" => mem_save_batch(db, args, project, embeddings),
146 "mem_delete_relation" => mem_delete_relation(db, args, project),
147 "mem_audit" => mem_audit(db, args, project),
148 "mem_deduplicate" => mem_deduplicate(db, args, project),
149 "mem_feedback" => mem_feedback(db, args, project),
150 "mem_deprecate" => mem_deprecate(db, args, project),
151 "mem_graph" => mem_graph(db, args, project),
152 "mem_summarize" => mem_summarize(db, args, project),
153 "mem_inject_context" => mem_inject_context(db, args, project),
154 "mem_forget_project" => mem_forget_project(db, args, project),
155 "mem_health" => mem_health(db, args, project),
156 "mem_remind" => mem_remind(db, args, project),
157 "mem_tag_suggest" => mem_tag_suggest(db, args, project),
158 "mem_knowledge_gaps" => mem_knowledge_gaps(db, args, project),
159 "mem_sync_status" => mem_sync_status(db, args, project),
160 "mem_sync_now" => mem_sync_now(db, args, project),
161 "mem_sync_export" => mem_sync_export(db, args, project),
162 "mem_encrypt" => mem_encrypt(db, args, project),
163 "mem_decrypt" => mem_decrypt(db, args, project),
164 "keys_list" => keys_list(db, args, project),
165 "keys_status" => keys_status(db, args, project),
166 "mem_entity_extract" => mem_entity_extract(db, args, project),
167 "mem_entity_search" => mem_entity_search(db, args, project),
168 "mem_entity_links" => mem_entity_links(db, args, project),
169 "mem_cloud_enroll" => mem_cloud_enroll(db, args, project),
170 "mem_cloud_sync" => mem_cloud_sync(db, args, project),
171 "mem_cloud_status" => mem_cloud_status(db, args, project),
172 "mem_consolidate" => mem_consolidate(db, args, project),
173 "mem_block_set" => mem_block_set(db, args, project),
174 "mem_block_get" => mem_block_get(db, args, project),
175 "mem_block_list" => mem_block_list(db, args, project),
176 "mem_learn_failures" => mem_learn_failures(db, args, project),
177 "mem_session_outcome" => mem_session_outcome(db, args, project),
178 "mem_obsidian_export" => mem_obsidian_export(db, args, project),
179 "mem_watch_scan" => mem_watch_scan(db, args, project),
180 "mem_compress" => mem_compress(db, args, project),
181 "mem_compress_batch" => mem_compress_batch(db, args, project),
182 "mem_compress_context" => mem_compress_context(db, args, project),
183 "mem_temporal_query" => mem_temporal_query(db, args, project),
184 "mem_expand" => mem_expand(db, args, project),
185 "mem_transcript" => mem_transcript(db, args, project),
186 "mem_capture_passive" => mem_capture_passive(db, args, project),
187 "mem_entity_frequent" => mem_entity_frequent(db, args, project),
188 "mem_judge" => mem_judge(db, args, project),
189 "mem_compare" => mem_compare(db, args, project),
190 "mem_conflict_candidates" => mem_conflict_candidates(db, args, project),
191 _ => Err(crate::error::MnemeError::Mcp(format!(
192 "Unknown tool: {}",
193 name
194 ))),
195 };
196
197 tool_response(result, project)
198}
199
200#[derive(Debug, Clone, Deserialize, JsonSchema)]
203struct MemSaveParams {
204 title: String,
205 content: String,
206 #[serde(default)]
207 project: Option<String>,
208 #[serde(rename = "type", default = "default_note")]
209 r#type: String,
210 #[serde(default = "default_medium")]
211 importance: String,
212 #[serde(default)]
213 tags: Vec<String>,
214 #[serde(default)]
215 what: Option<String>,
216 #[serde(default)]
217 why: Option<String>,
218 #[serde(default)]
219 context: Option<String>,
220 #[serde(default)]
221 learned: Option<String>,
222 #[serde(default)]
223 scope: Option<String>,
224 #[serde(default)]
225 topic_key: Option<String>,
226 #[serde(default)]
228 valid_from: Option<String>,
229 #[serde(default)]
231 valid_until: Option<String>,
232 #[serde(default)]
234 provenance: Option<String>,
235}
236
237fn default_note() -> String {
238 "note".into()
239}
240fn default_medium() -> String {
241 "medium".into()
242}
243
244#[derive(Debug, Clone, Deserialize, JsonSchema)]
245struct MemUpdateParams {
246 id: String,
247 #[serde(default)]
248 title: Option<String>,
249 #[serde(default)]
250 content: Option<String>,
251 #[serde(default)]
252 what: Option<String>,
253 #[serde(default)]
254 why: Option<String>,
255 #[serde(default)]
256 context: Option<String>,
257 #[serde(default)]
258 learned: Option<String>,
259 #[serde(rename = "type", default)]
260 r#type: Option<String>,
261 #[serde(default)]
262 importance: Option<String>,
263 #[serde(default)]
264 tags: Option<Vec<String>>,
265 #[serde(default)]
266 scope: Option<String>,
267 #[serde(default)]
268 topic_key: Option<String>,
269}
270
271#[derive(Debug, Clone, Deserialize, JsonSchema)]
272struct MemDeleteParams {
273 id: String,
274 #[serde(default)]
275 hard: bool,
276}
277
278#[derive(Debug, Clone, Deserialize, JsonSchema)]
279struct MemRestoreParams {
280 id: String,
281}
282
283#[derive(Debug, Clone, Deserialize, JsonSchema)]
284struct MemSearchParams {
285 query: String,
286 #[serde(default)]
287 project: Option<String>,
288 #[serde(rename = "type", default)]
289 r#type: Option<String>,
290 #[serde(default = "default_limit_10")]
291 limit: u32,
292}
293
294fn default_limit_10() -> u32 {
295 10
296}
297
298#[derive(Debug, Clone, Deserialize, JsonSchema)]
299struct MemGetParams {
300 id: String,
301}
302
303#[derive(Debug, Clone, Deserialize, JsonSchema)]
304struct MemListParams {
305 #[serde(default)]
306 project: Option<String>,
307 #[serde(rename = "type", default)]
308 r#type: Option<String>,
309 #[serde(default = "default_limit_20")]
310 limit: u32,
311}
312
313fn default_limit_20() -> u32 {
314 20
315}
316
317#[derive(Debug, Clone, Deserialize, JsonSchema)]
318struct MemContextParams {
319 #[serde(default)]
320 project: Option<String>,
321 #[serde(default = "default_limit_10")]
322 limit: u32,
323}
324
325#[derive(Debug, Clone, Deserialize, JsonSchema)]
326struct MemTimelineParams {
327 id: String,
328 #[serde(default = "default_limit_10")]
329 limit: u32,
330}
331
332#[derive(Debug, Clone, Deserialize, JsonSchema)]
333struct MemSessionStartParams {
334 #[serde(default)]
335 project: Option<String>,
336 #[serde(default)]
337 directory: Option<String>,
338}
339
340#[derive(Debug, Clone, Deserialize, JsonSchema)]
341struct MemSessionEndParams {
342 id: String,
343 #[serde(default)]
344 summary: Option<String>,
345}
346
347#[derive(Debug, Clone, Deserialize, JsonSchema)]
348struct MemSessionSummaryParams {
349 #[serde(default)]
350 project: Option<String>,
351}
352
353#[derive(Debug, Clone, Deserialize, JsonSchema)]
354struct MemStatsParams {
355 #[serde(default)]
356 project: Option<String>,
357}
358
359#[derive(Debug, Clone, Deserialize, JsonSchema)]
360struct MemProjectsParams {}
361
362#[derive(Debug, Clone, Deserialize, JsonSchema)]
363struct MemConflictsParams {
364 #[serde(default)]
365 project: Option<String>,
366 #[serde(default = "default_limit_10")]
367 limit: u32,
368}
369
370#[derive(Debug, Clone, Deserialize, JsonSchema)]
371struct MemSavePromptParams {
372 content: String,
373 #[serde(default)]
374 session_id: Option<String>,
375 #[serde(default)]
376 project: Option<String>,
377}
378
379#[derive(Debug, Clone, Deserialize, JsonSchema)]
380struct MemSuggestTopicKeyParams {
381 #[serde(rename = "type")]
382 r#type: String,
383 title: String,
384}
385
386#[derive(Debug, Clone, Deserialize, JsonSchema)]
387struct MemCurrentProjectParams {}
388
389#[derive(Debug, Clone, Deserialize, JsonSchema)]
390struct MemDoctorParams {
391 #[serde(default)]
392 project: Option<String>,
393}
394
395#[derive(Debug, Clone, Deserialize, JsonSchema)]
396struct MemSimilarParams {
397 query: String,
399 #[serde(default)]
400 project: Option<String>,
401 #[serde(default = "default_limit_5")]
402 limit: u32,
403 #[serde(default = "default_threshold_75")]
404 threshold: f32,
405}
406
407fn default_limit_5() -> u32 {
408 5
409}
410fn default_threshold_75() -> f32 {
411 0.75
412}
413
414#[derive(Debug, Clone, Deserialize, JsonSchema)]
415struct BatchMemoryInput {
416 title: String,
417 content: String,
418 #[serde(rename = "type", default = "default_note")]
419 r#type: String,
420 #[serde(default = "default_medium")]
421 importance: String,
422 #[serde(default)]
423 tags: Vec<String>,
424 #[serde(default)]
425 what: Option<String>,
426 #[serde(default)]
427 why: Option<String>,
428 #[serde(default)]
429 context: Option<String>,
430 #[serde(default)]
431 learned: Option<String>,
432 #[serde(default)]
433 scope: Option<String>,
434 #[serde(default)]
435 topic_key: Option<String>,
436}
437
438#[derive(Debug, Clone, Deserialize, JsonSchema)]
439struct MemSaveBatchParams {
440 memories: Vec<BatchMemoryInput>,
441 #[serde(default)]
442 project: Option<String>,
443}
444
445#[derive(Debug, Clone, Deserialize, JsonSchema)]
446struct MemDeleteRelationParams {
447 relation_id: String,
448}
449
450#[derive(Debug, Clone, Deserialize, JsonSchema)]
451struct MemAuditParams {
452 #[serde(default)]
453 project: Option<String>,
454 #[serde(default = "default_days_30")]
455 days_threshold: u32,
456}
457
458fn default_days_30() -> u32 {
459 30
460}
461
462#[derive(Debug, Clone, Deserialize, JsonSchema)]
463struct MemDeduplicateParams {
464 #[serde(default)]
465 project: Option<String>,
466 #[serde(default = "default_threshold_85")]
467 threshold: f64,
468}
469
470fn default_threshold_85() -> f64 {
471 0.85
472}
473
474#[derive(Debug, Clone, Deserialize, JsonSchema)]
475struct MemFeedbackParams {
476 memory_id: String,
477 is_useful: bool,
478 #[serde(default)]
479 reason: Option<String>,
480}
481
482#[derive(Debug, Clone, Deserialize, JsonSchema)]
483struct MemDeprecateParams {
484 memory_id: String,
485 reason: String,
486 #[serde(default)]
487 supersedes_id: Option<String>,
488}
489
490#[derive(Debug, Clone, Deserialize, JsonSchema)]
491struct MemGraphParams {
492 #[serde(default)]
493 project: Option<String>,
494}
495
496#[derive(Debug, Clone, Deserialize, JsonSchema)]
497struct MemSummarizeParams {
498 #[serde(default)]
499 project: Option<String>,
500 #[serde(default)]
501 session_id: Option<String>,
502}
503
504#[derive(Debug, Clone, Deserialize, JsonSchema)]
505struct MemInjectContextParams {
506 #[serde(default)]
507 project: Option<String>,
508 #[serde(default)]
509 file: Option<String>,
510 #[serde(default = "default_limit_10")]
511 limit: u32,
512}
513
514#[derive(Debug, Clone, Deserialize, JsonSchema)]
515struct MemForgetProjectParams {
516 #[serde(default)]
517 project: Option<String>,
518 confirm: bool,
519}
520
521#[derive(Debug, Clone, Deserialize, JsonSchema)]
522struct MemHealthParams {
523 #[serde(default)]
524 project: Option<String>,
525}
526
527#[derive(Debug, Clone, Deserialize, JsonSchema)]
528struct MemRemindParams {
529 #[serde(default)]
530 project: Option<String>,
531 #[serde(default = "default_high")]
532 importance: String,
533}
534
535fn default_high() -> String {
536 "high".into()
537}
538
539#[derive(Debug, Clone, Deserialize, JsonSchema)]
540struct MemTagSuggestParams {
541 #[serde(default)]
542 project: Option<String>,
543 title: String,
544 #[serde(default)]
545 content: Option<String>,
546}
547
548#[derive(Debug, Clone, Deserialize, JsonSchema)]
549struct MemKnowledgeGapsParams {
550 #[serde(default)]
551 project: Option<String>,
552}
553
554#[derive(Debug, Clone, Deserialize, JsonSchema)]
555struct MemSyncStatusParams {
556 #[serde(default)]
557 project: Option<String>,
558}
559
560#[derive(Debug, Clone, Deserialize, JsonSchema)]
561struct MemSyncNowParams {
562 #[serde(default)]
563 project: Option<String>,
564}
565
566#[derive(Debug, Clone, Deserialize, JsonSchema)]
567struct MemSyncExportParams {
568 #[serde(default)]
569 project: Option<String>,
570 #[serde(default)]
571 output: Option<String>,
572}
573
574#[derive(Debug, Clone, Deserialize, JsonSchema)]
575struct MemEncryptParams {
576 memory_id: String,
577}
578
579#[derive(Debug, Clone, Deserialize, JsonSchema)]
580struct MemDecryptParams {
581 memory_id: String,
582}
583
584#[derive(Debug, Clone, Deserialize, JsonSchema)]
585struct KeysListParams {}
586
587#[derive(Debug, Clone, Deserialize, JsonSchema)]
588struct KeysStatusParams {
589 #[serde(default)]
590 project: Option<String>,
591}
592
593fn mem_save(
596 db: &Database,
597 args: JsonObject,
598 project: &str,
599 embeddings: Option<&std::sync::Arc<crate::embeddings::engine::EmbeddingEngine>>,
600) -> crate::error::Result<serde_json::Value> {
601 let params: MemSaveParams = serde_json::from_value(serde_json::Value::Object(args))
602 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
603
604 let project = params.project.unwrap_or_else(|| project.to_string());
605 let memory_type = params.r#type.parse()?;
606 let importance = params.importance.parse()?;
607 let scope = params
608 .scope
609 .as_deref()
610 .map(Scope::from_str)
611 .transpose()?
612 .unwrap_or(Scope::Project);
613
614 let input = CreateMemoryInput {
615 project,
616 scope: Some(scope),
617 title: params.title,
618 content: params.content,
619 what: params.what,
620 why: params.why,
621 context: params.context,
622 learned: params.learned,
623 memory_type,
624 importance,
625 tags: params.tags,
626 topic_key: params.topic_key,
627 capture_prompt: None,
628 encrypt: false,
629 valid_from: params.valid_from.as_deref()
630 .and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
631 .map(|d| d.with_timezone(&chrono::Utc)),
632 valid_until: params.valid_until.as_deref()
633 .and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
634 .map(|d| d.with_timezone(&chrono::Utc)),
635 provenance: params.provenance,
636 };
637
638 let engine = embeddings.map(std::sync::Arc::clone);
639 let embedding_store = db.embeddings();
640 let memory = db.memories().save(input, engine, Some(embedding_store))?;
641 Ok(serde_json::to_value(memory)?)
642}
643
644fn mem_update(
645 db: &Database,
646 args: JsonObject,
647 _project: &str,
648) -> crate::error::Result<serde_json::Value> {
649 let params: MemUpdateParams = serde_json::from_value(serde_json::Value::Object(args))
650 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
651
652 let id =
653 Uuid::parse_str(¶ms.id).map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
654
655 let update = UpdateMemoryInput {
656 title: params.title,
657 content: params.content,
658 what: params.what,
659 why: params.why,
660 context: params.context,
661 learned: params.learned,
662 memory_type: params.r#type.as_deref().map(str::parse).transpose()?,
663 importance: params.importance.as_deref().map(str::parse).transpose()?,
664 tags: params.tags,
665 scope: params.scope.as_deref().map(Scope::from_str).transpose()?,
666 topic_key: params.topic_key,
667 };
668
669 let memory = db.memories().update(id, update)?;
670 Ok(serde_json::to_value(memory)?)
671}
672
673fn mem_delete(
674 db: &Database,
675 args: JsonObject,
676 _project: &str,
677) -> crate::error::Result<serde_json::Value> {
678 let params: MemDeleteParams = serde_json::from_value(serde_json::Value::Object(args))
679 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
680
681 let id =
682 Uuid::parse_str(¶ms.id).map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
683
684 db.memories().delete(id, params.hard)?;
685 Ok(json!({"deleted": true, "id": params.id}))
686}
687
688fn mem_restore(
689 db: &Database,
690 args: JsonObject,
691 _project: &str,
692) -> crate::error::Result<serde_json::Value> {
693 let params: MemRestoreParams = serde_json::from_value(serde_json::Value::Object(args))
694 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
695
696 let id =
697 Uuid::parse_str(¶ms.id).map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
698
699 let memory = db.memories().restore(id)?;
700 Ok(serde_json::to_value(memory)?)
701}
702
703fn mem_search(
704 db: &Database,
705 args: JsonObject,
706 project: &str,
707) -> crate::error::Result<serde_json::Value> {
708 let params: MemSearchParams = serde_json::from_value(serde_json::Value::Object(args))
709 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
710
711 let project = params.project.unwrap_or_else(|| project.to_string());
712 let memory_type = params.r#type.as_deref().map(str::parse).transpose()?;
713
714 let query = SearchQuery {
715 text: params.query,
716 project: Some(project),
717 scope: None,
718 memory_type,
719 importance: None,
720 tags: Vec::new(),
721 limit: params.limit,
722 include_snippet: true,
723 all_projects: false,
724 };
725
726 let weights = crate::store::search::SearchWeights::default();
727 let results = db.memories().search(&query, &weights, None)?;
728 Ok(serde_json::to_value(results)?)
729}
730
731async fn mem_similar(
732 db: &Database,
733 args: JsonObject,
734 project: &str,
735 embeddings: Option<&std::sync::Arc<crate::embeddings::engine::EmbeddingEngine>>,
736) -> crate::error::Result<serde_json::Value> {
737 let params: MemSimilarParams = serde_json::from_value(serde_json::Value::Object(args))
738 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
739
740 let engine = match embeddings {
741 Some(e) => e,
742 None => return Err(crate::error::MnemeError::EmbeddingsDisabled),
743 };
744
745 let project = params.project.unwrap_or_else(|| project.to_string());
746 let embedding_store = db.embeddings();
747
748 let query_embedding = if let Ok(id) = Uuid::parse_str(¶ms.query) {
750 match embedding_store.load(id)? {
751 Some(embedding) => embedding,
752 None => {
753 let memory = db
755 .memories()
756 .get(id)?
757 .ok_or_else(|| crate::error::MnemeError::NotFound(id))?;
758 let text = crate::embeddings::engine::EmbeddingEngine::memory_to_text(&memory);
759 engine.embed(&text).await?
760 }
761 }
762 } else {
763 engine.embed(¶ms.query).await?
764 };
765
766 let all_embeddings = embedding_store.load_all_for_project(&project)?;
767 let mut matches = Vec::new();
768 for (id, embedding) in all_embeddings {
769 let score = crate::embeddings::similarity::cosine_similarity(&query_embedding, &embedding);
770 if score >= params.threshold {
771 matches.push(crate::embeddings::similarity::SemanticMatch {
772 memory_id: id,
773 cosine_score: score,
774 combined_score: f64::from(score),
775 });
776 }
777 }
778 crate::embeddings::similarity::rank_by_combined_score(&mut matches);
779 matches.truncate(params.limit as usize);
780
781 let mut results = Vec::new();
782 for m in matches {
783 if let Some(memory) = db.memories().get(m.memory_id)? {
784 results.push(crate::store::memory::SearchResult {
785 memory,
786 score: m.combined_score,
787 snippet: None,
788 match_type: crate::store::memory::MatchType::Semantic,
789 cosine_score: Some(m.cosine_score),
790 });
791 }
792 }
793
794 Ok(serde_json::to_value(results)?)
795}
796
797fn mem_get(
798 db: &Database,
799 args: JsonObject,
800 _project: &str,
801) -> crate::error::Result<serde_json::Value> {
802 let params: MemGetParams = serde_json::from_value(serde_json::Value::Object(args))
803 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
804
805 let id =
806 Uuid::parse_str(¶ms.id).map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
807
808 match db.memories().get(id)? {
809 Some(memory) => Ok(serde_json::to_value(memory)?),
810 None => Err(crate::error::MnemeError::NotFound(id)),
811 }
812}
813
814fn mem_list(
815 db: &Database,
816 args: JsonObject,
817 project: &str,
818) -> crate::error::Result<serde_json::Value> {
819 let params: MemListParams = serde_json::from_value(serde_json::Value::Object(args))
820 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
821
822 let project = params.project.unwrap_or_else(|| project.to_string());
823 let memory_type = params.r#type.as_deref().map(str::parse).transpose()?;
824
825 let memories =
826 db.memories()
827 .list(&project, memory_type.as_ref(), None, None, params.limit, 0)?;
828 Ok(serde_json::to_value(memories)?)
829}
830
831fn mem_context(
832 db: &Database,
833 args: JsonObject,
834 _project: &str,
835) -> crate::error::Result<serde_json::Value> {
836 let params: MemContextParams = serde_json::from_value(serde_json::Value::Object(args))
837 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
838
839 let project = params.project.unwrap_or_else(|| _project.to_string());
840 let memories = db.memories().context(&project, None, params.limit)?;
841 Ok(serde_json::to_value(memories)?)
842}
843
844fn mem_timeline(
845 db: &Database,
846 args: JsonObject,
847 _project: &str,
848) -> crate::error::Result<serde_json::Value> {
849 let params: MemTimelineParams = serde_json::from_value(serde_json::Value::Object(args))
850 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
851
852 let id =
853 Uuid::parse_str(¶ms.id).map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
854
855 let memory = db
856 .memories()
857 .get(id)?
858 .ok_or_else(|| crate::error::MnemeError::NotFound(id))?;
859
860 let project = memory.project.clone();
861 let memories = db
862 .memories()
863 .list(&project, None, None, None, params.limit, 0)?;
864 Ok(serde_json::to_value(memories)?)
865}
866
867fn mem_session_start(
868 db: &Database,
869 args: JsonObject,
870 project: &str,
871) -> crate::error::Result<serde_json::Value> {
872 let params: MemSessionStartParams = serde_json::from_value(serde_json::Value::Object(args))
873 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
874
875 let project = params.project.unwrap_or_else(|| project.to_string());
876 let current_dir = std::env::current_dir().ok();
877 let dir = params
878 .directory
879 .as_deref()
880 .or_else(|| current_dir.as_ref().and_then(|p| p.to_str()));
881 let session = db.sessions().start(&project, dir)?;
882 Ok(serde_json::to_value(session)?)
883}
884
885fn mem_session_end(
886 db: &Database,
887 args: JsonObject,
888 _project: &str,
889) -> crate::error::Result<serde_json::Value> {
890 let params: MemSessionEndParams = serde_json::from_value(serde_json::Value::Object(args))
891 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
892
893 let id =
894 Uuid::parse_str(¶ms.id).map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
895
896 let session = db.sessions().end(id, params.summary.as_deref())?;
897 Ok(serde_json::to_value(session)?)
898}
899
900fn mem_session_summary(
901 db: &Database,
902 args: JsonObject,
903 project: &str,
904) -> crate::error::Result<serde_json::Value> {
905 let params: MemSessionSummaryParams =
906 serde_json::from_value(serde_json::Value::Object(args))
907 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
908
909 let project = params.project.unwrap_or_else(|| project.to_string());
910 match db.sessions().get_active(&project)? {
911 Some(session) => Ok(serde_json::to_value(session)?),
912 None => Ok(json!(null)),
913 }
914}
915
916fn mem_stats(
917 db: &Database,
918 args: JsonObject,
919 project: &str,
920) -> crate::error::Result<serde_json::Value> {
921 let params: MemStatsParams = serde_json::from_value(serde_json::Value::Object(args))
922 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
923
924 let project = params.project.unwrap_or_else(|| project.to_string());
925 let stats = db.memories().stats(&project)?;
926 Ok(serde_json::to_value(stats)?)
927}
928
929fn mem_projects(
930 db: &Database,
931 _args: JsonObject,
932 _project: &str,
933) -> crate::error::Result<serde_json::Value> {
934 let projects = db.memories().list_projects()?;
935 Ok(serde_json::to_value(projects)?)
936}
937
938fn mem_conflicts(
939 db: &Database,
940 args: JsonObject,
941 project: &str,
942) -> crate::error::Result<serde_json::Value> {
943 let params: MemConflictsParams = serde_json::from_value(serde_json::Value::Object(args))
944 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
945
946 let project = params.project.unwrap_or_else(|| project.to_string());
947 let query = SearchQuery {
948 text: "conflict".to_string(),
949 project: Some(project),
950 scope: None,
951 memory_type: None,
952 importance: None,
953 tags: Vec::new(),
954 limit: params.limit,
955 include_snippet: false,
956 all_projects: false,
957 };
958
959 let weights = crate::store::search::SearchWeights::default();
960 let results = db.memories().search(&query, &weights, None)?;
961 Ok(serde_json::to_value(results)?)
962}
963
964fn mem_save_prompt(
965 db: &Database,
966 args: JsonObject,
967 project: &str,
968) -> crate::error::Result<serde_json::Value> {
969 let params: MemSavePromptParams = serde_json::from_value(serde_json::Value::Object(args))
970 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
971
972 let project = params.project.unwrap_or_else(|| project.to_string());
973 let session_id = params
974 .session_id
975 .as_deref()
976 .map(Uuid::parse_str)
977 .transpose()
978 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
979
980 let input = CreatePromptInput {
981 session_id,
982 content: params.content,
983 project,
984 };
985
986 let store = db.memories();
987 let prompt = store.save_prompt(input)?;
988 Ok(serde_json::to_value(prompt)?)
989}
990
991fn mem_suggest_topic_key(
992 _db: &Database,
993 args: JsonObject,
994 _project: &str,
995) -> crate::error::Result<serde_json::Value> {
996 let params: MemSuggestTopicKeyParams = serde_json::from_value(serde_json::Value::Object(args))
997 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
998
999 let family = match params.r#type.to_lowercase().as_str() {
1000 "architecture" => "architecture",
1001 "decision" => "decision",
1002 "bugfix" => "bug",
1003 "pattern" => "pattern",
1004 "convention" => "convention",
1005 "dependency" => "dependency",
1006 "workflow" => "workflow",
1007 "note" => "note",
1008 "config" => "config",
1009 "discovery" => "discovery",
1010 "learning" => "learning",
1011 _ => "general",
1012 };
1013
1014 let slug = params
1015 .title
1016 .to_lowercase()
1017 .replace(|c: char| !c.is_alphanumeric() && c != ' ', "")
1018 .split_whitespace()
1019 .take(4)
1020 .collect::<Vec<_>>()
1021 .join("-");
1022
1023 let suggested = format!("{}/{}", family, slug);
1024 Ok(json!({"topic_key": suggested}))
1025}
1026
1027fn mem_current_project(
1028 _db: &Database,
1029 _args: JsonObject,
1030 _project: &str,
1031) -> crate::error::Result<serde_json::Value> {
1032 let inferred = Settings::infer_project();
1033 let git_root = Settings::git_toplevel();
1034 let source = if git_root.is_some() {
1035 "git"
1036 } else {
1037 "directory"
1038 };
1039 let path = git_root
1040 .or_else(|| std::env::current_dir().ok())
1041 .map(|p| p.to_string_lossy().to_string())
1042 .unwrap_or_default();
1043
1044 Ok(json!({
1045 "project": inferred,
1046 "source": source,
1047 "path": path
1048 }))
1049}
1050
1051fn mem_doctor(
1052 db: &Database,
1053 args: JsonObject,
1054 project: &str,
1055) -> crate::error::Result<serde_json::Value> {
1056 let params: MemDoctorParams = serde_json::from_value(serde_json::Value::Object(args))
1057 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1058
1059 let project = params.project.unwrap_or_else(|| project.to_string());
1060 let issues: Vec<String> = Vec::new();
1061 let mut checks = Vec::new();
1062
1063 checks.push("Database connection: OK".to_string());
1064
1065 let memory_count = db
1066 .memories()
1067 .stats(&project)
1068 .map(|s| s.total_memories)
1069 .unwrap_or(0);
1070 let session_count = db
1071 .sessions()
1072 .list(&project, 1000)
1073 .map(|s| s.len() as u32)
1074 .unwrap_or(0);
1075
1076 checks.push(format!("Memories: {}", memory_count));
1077 checks.push(format!("Sessions: {}", session_count));
1078 checks.push("Orphaned relations: 0".to_string());
1079
1080 let healthy = issues.is_empty();
1081
1082 Ok(json!({
1083 "healthy": healthy,
1084 "project": project,
1085 "memory_count": memory_count,
1086 "session_count": session_count,
1087 "issues": issues,
1088 "checks": checks
1089 }))
1090}
1091
1092fn mem_save_batch(
1093 db: &Database,
1094 args: JsonObject,
1095 project: &str,
1096 embeddings: Option<&std::sync::Arc<crate::embeddings::engine::EmbeddingEngine>>,
1097) -> crate::error::Result<serde_json::Value> {
1098 let params: MemSaveBatchParams = serde_json::from_value(serde_json::Value::Object(args))
1099 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1100
1101 let project = params.project.unwrap_or_else(|| project.to_string());
1102 let mut inputs = Vec::new();
1103 for item in params.memories {
1104 let memory_type = item.r#type.parse()?;
1105 let importance = item.importance.parse()?;
1106 let scope = item
1107 .scope
1108 .as_deref()
1109 .map(Scope::from_str)
1110 .transpose()?
1111 .unwrap_or(Scope::Project);
1112 inputs.push(CreateMemoryInput {
1113 project: project.clone(),
1114 scope: Some(scope),
1115 title: item.title,
1116 content: item.content,
1117 what: item.what,
1118 why: item.why,
1119 context: item.context,
1120 learned: item.learned,
1121 memory_type,
1122 importance,
1123 tags: item.tags,
1124 topic_key: item.topic_key,
1125 capture_prompt: None,
1126 encrypt: false,
1127 valid_from: None,
1128 valid_until: None,
1129 provenance: None,
1130 });
1131 }
1132
1133 let engine = embeddings.map(std::sync::Arc::clone);
1134 let embedding_store = db.embeddings();
1135 let (saved, duplicates) = db
1136 .memories()
1137 .save_batch(inputs, engine, Some(embedding_store))?;
1138 Ok(json!({
1139 "saved": saved,
1140 "duplicates": duplicates,
1141 "saved_count": saved.len(),
1142 "duplicate_count": duplicates.len()
1143 }))
1144}
1145
1146fn mem_delete_relation(
1147 db: &Database,
1148 args: JsonObject,
1149 _project: &str,
1150) -> crate::error::Result<serde_json::Value> {
1151 let params: MemDeleteRelationParams =
1152 serde_json::from_value(serde_json::Value::Object(args))
1153 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1154
1155 let id = Uuid::parse_str(¶ms.relation_id)
1156 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1157
1158 let deleted = db.memories().delete_relation(id)?;
1159 Ok(json!({"deleted": deleted, "id": params.relation_id}))
1160}
1161
1162fn mem_audit(
1163 db: &Database,
1164 args: JsonObject,
1165 project: &str,
1166) -> crate::error::Result<serde_json::Value> {
1167 let params: MemAuditParams = serde_json::from_value(serde_json::Value::Object(args))
1168 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1169
1170 let project = params.project.unwrap_or_else(|| project.to_string());
1171 let report = db.memories().audit(&project, params.days_threshold)?;
1172 Ok(serde_json::to_value(report)?)
1173}
1174
1175fn mem_deduplicate(
1176 db: &Database,
1177 args: JsonObject,
1178 project: &str,
1179) -> crate::error::Result<serde_json::Value> {
1180 let params: MemDeduplicateParams = serde_json::from_value(serde_json::Value::Object(args))
1181 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1182
1183 let project = params.project.unwrap_or_else(|| project.to_string());
1184 let embedding_store = db.embeddings();
1185 let groups =
1186 db.memories()
1187 .find_duplicates_semantic(&project, params.threshold, &embedding_store)?;
1188 Ok(serde_json::to_value(groups)?)
1189}
1190
1191fn mem_feedback(
1192 db: &Database,
1193 args: JsonObject,
1194 _project: &str,
1195) -> crate::error::Result<serde_json::Value> {
1196 let params: MemFeedbackParams = serde_json::from_value(serde_json::Value::Object(args))
1197 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1198
1199 let memory_id = Uuid::parse_str(¶ms.memory_id)
1200 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1201
1202 let feedback_id =
1203 db.memories()
1204 .add_feedback(memory_id, params.is_useful, params.reason.as_deref())?;
1205 Ok(json!({
1206 "memory_id": params.memory_id,
1207 "feedback_id": feedback_id
1208 }))
1209}
1210
1211fn mem_deprecate(
1212 db: &Database,
1213 args: JsonObject,
1214 _project: &str,
1215) -> crate::error::Result<serde_json::Value> {
1216 let params: MemDeprecateParams = serde_json::from_value(serde_json::Value::Object(args))
1217 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1218
1219 let memory_id = Uuid::parse_str(¶ms.memory_id)
1220 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1221 let supersedes_id = params
1222 .supersedes_id
1223 .as_deref()
1224 .map(Uuid::parse_str)
1225 .transpose()
1226 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1227
1228 let memory = db
1229 .memories()
1230 .deprecate(memory_id, ¶ms.reason, supersedes_id)?;
1231 Ok(serde_json::to_value(memory)?)
1232}
1233
1234fn mem_graph(
1235 db: &Database,
1236 args: JsonObject,
1237 project: &str,
1238) -> crate::error::Result<serde_json::Value> {
1239 let params: MemGraphParams = serde_json::from_value(serde_json::Value::Object(args))
1240 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1241
1242 let project = params.project.unwrap_or_else(|| project.to_string());
1243 let graph = db.memories().get_graph(&project)?;
1244 Ok(serde_json::to_value(graph)?)
1245}
1246
1247fn mem_summarize(
1248 db: &Database,
1249 args: JsonObject,
1250 project: &str,
1251) -> crate::error::Result<serde_json::Value> {
1252 let params: MemSummarizeParams = serde_json::from_value(serde_json::Value::Object(args))
1253 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1254
1255 let project = params.project.unwrap_or_else(|| project.to_string());
1256 let session_id = params
1257 .session_id
1258 .as_deref()
1259 .map(Uuid::parse_str)
1260 .transpose()
1261 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1262
1263 let summary = db.memories().summarize(&project, session_id)?;
1264 Ok(serde_json::to_value(summary)?)
1265}
1266
1267fn mem_inject_context(
1268 db: &Database,
1269 args: JsonObject,
1270 project: &str,
1271) -> crate::error::Result<serde_json::Value> {
1272 let params: MemInjectContextParams = serde_json::from_value(serde_json::Value::Object(args))
1273 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1274
1275 let project = params.project.unwrap_or_else(|| project.to_string());
1276 let context = db
1277 .memories()
1278 .inject_context(&project, params.file.as_deref(), params.limit)?;
1279 Ok(json!({"context": context, "project": project}))
1280}
1281
1282fn mem_forget_project(
1283 db: &Database,
1284 args: JsonObject,
1285 project: &str,
1286) -> crate::error::Result<serde_json::Value> {
1287 let params: MemForgetProjectParams = serde_json::from_value(serde_json::Value::Object(args))
1288 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1289
1290 if !params.confirm {
1291 return Err(crate::error::MnemeError::Config(
1292 "confirm must be true to forget project".into(),
1293 ));
1294 }
1295
1296 let project = params.project.unwrap_or_else(|| project.to_string());
1297 let deleted = db.memories().forget_project(&project)?;
1298 Ok(json!({"deleted": deleted, "project": project}))
1299}
1300
1301fn mem_health(
1302 db: &Database,
1303 args: JsonObject,
1304 _project: &str,
1305) -> crate::error::Result<serde_json::Value> {
1306 let params: MemHealthParams = serde_json::from_value(serde_json::Value::Object(args))
1307 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1308
1309 let report = db.memories().health(params.project.as_deref())?;
1310 Ok(serde_json::to_value(report)?)
1311}
1312
1313fn mem_remind(
1314 db: &Database,
1315 args: JsonObject,
1316 project: &str,
1317) -> crate::error::Result<serde_json::Value> {
1318 let params: MemRemindParams = serde_json::from_value(serde_json::Value::Object(args))
1319 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1320
1321 let project = params.project.unwrap_or_else(|| project.to_string());
1322 let importance = params.importance.parse()?;
1323 let memories = db.memories().remind(&project, &importance)?;
1324 Ok(serde_json::to_value(memories)?)
1325}
1326
1327fn mem_tag_suggest(
1328 db: &Database,
1329 args: JsonObject,
1330 project: &str,
1331) -> crate::error::Result<serde_json::Value> {
1332 let params: MemTagSuggestParams = serde_json::from_value(serde_json::Value::Object(args))
1333 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1334
1335 let project = params.project.unwrap_or_else(|| project.to_string());
1336 let tags = db
1337 .memories()
1338 .suggest_tags(&project, ¶ms.title, params.content.as_deref())?;
1339 Ok(serde_json::to_value(tags)?)
1340}
1341
1342fn mem_knowledge_gaps(
1343 db: &Database,
1344 args: JsonObject,
1345 project: &str,
1346) -> crate::error::Result<serde_json::Value> {
1347 let params: MemKnowledgeGapsParams = serde_json::from_value(serde_json::Value::Object(args))
1348 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1349
1350 let project = params.project.unwrap_or_else(|| project.to_string());
1351 let report = db.memories().knowledge_gaps(&project)?;
1352 Ok(serde_json::to_value(report)?)
1353}
1354
1355fn mem_sync_status(
1356 db: &Database,
1357 args: JsonObject,
1358 project: &str,
1359) -> crate::error::Result<serde_json::Value> {
1360 let params: MemSyncStatusParams = serde_json::from_value(serde_json::Value::Object(args))
1361 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1362
1363 let project = params.project.unwrap_or_else(|| project.to_string());
1364 let peers = db.peers().list(&project)?;
1365 let peer_names: Vec<String> = peers.iter().map(|p| p.name.clone()).collect();
1366
1367 Ok(json!({
1368 "project": project,
1369 "peers": peer_names,
1370 "peer_count": peers.len()
1371 }))
1372}
1373
1374fn mem_sync_now(
1375 db: &Database,
1376 args: JsonObject,
1377 project: &str,
1378) -> crate::error::Result<serde_json::Value> {
1379 let params: MemSyncNowParams = serde_json::from_value(serde_json::Value::Object(args))
1380 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1381
1382 let project = params.project.unwrap_or_else(|| project.to_string());
1383 let settings = Settings::load()?;
1384 let engine = crate::sync::engine::SyncEngine::new(Arc::new(db.clone()), settings.sync)?;
1385
1386 let rt = tokio::runtime::Runtime::new()?;
1387 let results = rt.block_on(async { engine.sync_auto(&project).await })?;
1388
1389 Ok(json!({
1390 "project": project,
1391 "results": results
1392 }))
1393}
1394
1395fn mem_sync_export(
1396 db: &Database,
1397 args: JsonObject,
1398 project: &str,
1399) -> crate::error::Result<serde_json::Value> {
1400 let params: MemSyncExportParams = serde_json::from_value(serde_json::Value::Object(args))
1401 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1402
1403 let project = params.project.unwrap_or_else(|| project.to_string());
1404 let output = params.output.map(std::path::PathBuf::from);
1405 let settings = Settings::load()?;
1406 let engine = crate::sync::engine::SyncEngine::new(Arc::new(db.clone()), settings.sync)?;
1407 let stats = engine.export_project(&project, output)?;
1408
1409 Ok(json!({
1410 "project": project,
1411 "memories_exported": stats.memories_exported,
1412 "bytes_written": stats.bytes_written
1413 }))
1414}
1415
1416fn mem_encrypt(
1417 db: &Database,
1418 args: JsonObject,
1419 _project: &str,
1420) -> crate::error::Result<serde_json::Value> {
1421 let params: MemEncryptParams = serde_json::from_value(serde_json::Value::Object(args))
1422 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1423
1424 let memory_id = Uuid::parse_str(¶ms.memory_id)
1425 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1426
1427 let memory = db.memories().encrypt_existing(memory_id)?;
1428 Ok(json!({ "memory": memory, "encrypted": true }))
1429}
1430
1431fn mem_decrypt(
1432 db: &Database,
1433 args: JsonObject,
1434 _project: &str,
1435) -> crate::error::Result<serde_json::Value> {
1436 let params: MemDecryptParams = serde_json::from_value(serde_json::Value::Object(args))
1437 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1438
1439 let memory_id = Uuid::parse_str(¶ms.memory_id)
1440 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1441
1442 let memory = db.memories().decrypt_existing(memory_id)?;
1443 Ok(json!({ "memory": memory, "decrypted": true }))
1444}
1445
1446fn keys_list(
1447 db: &Database,
1448 _args: JsonObject,
1449 _project: &str,
1450) -> crate::error::Result<serde_json::Value> {
1451 let key_store = crate::crypto::KeyStore::new(db.get_conn());
1452 let keys = key_store.list()?;
1453 Ok(json!({ "keys": keys }))
1454}
1455
1456fn keys_status(
1457 db: &Database,
1458 args: JsonObject,
1459 _project: &str,
1460) -> crate::error::Result<serde_json::Value> {
1461 let params: KeysStatusParams = serde_json::from_value(serde_json::Value::Object(args))
1462 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1463
1464 let key_store = crate::crypto::KeyStore::new(db.get_conn());
1465 let keys = key_store.list().unwrap_or_default();
1466 let keys_count = keys.len() as u32;
1467 let default_key = keys.iter().find(|k| k.is_default).map(|k| k.alias.clone());
1468
1469 let encrypted_memories = if let Some(ref proj) = params.project {
1470 db.memories()
1471 .list(proj, None, None, None, 10000, 0)
1472 .map(|mems| mems.iter().filter(|m| m.is_encrypted).count() as u32)
1473 .unwrap_or(0)
1474 } else {
1475 0u32
1476 };
1477
1478 Ok(json!({
1479 "keys_count": keys_count,
1480 "encrypted_memories": encrypted_memories,
1481 "identity_loaded": false,
1482 "default_key": default_key,
1483 }))
1484}
1485
1486#[derive(Debug, Clone, Deserialize, JsonSchema)]
1489struct MemEntityExtractParams {
1490 id: Option<String>,
1492 text: Option<String>,
1494 #[serde(default)]
1496 #[allow(dead_code)]
1497 project: Option<String>,
1498}
1499
1500#[derive(Debug, Clone, Deserialize, JsonSchema)]
1501struct MemEntitySearchParams {
1502 query: String,
1503 #[serde(default)]
1504 entity_type: Option<String>,
1505 #[serde(default = "default_limit_10")]
1506 limit: u32,
1507}
1508
1509#[derive(Debug, Clone, Deserialize, JsonSchema)]
1510struct MemEntityLinksParams {
1511 id: String,
1513 #[serde(default = "default_limit_10")]
1514 limit: u32,
1515}
1516
1517#[derive(Debug, Clone, Deserialize, JsonSchema)]
1518struct MemEntityFrequentParams {
1519 #[serde(default)]
1520 project: Option<String>,
1521 #[serde(default = "default_limit_20")]
1522 limit: u32,
1523}
1524
1525fn mem_entity_extract(
1526 db: &Database,
1527 args: JsonObject,
1528 project: &str,
1529) -> crate::error::Result<serde_json::Value> {
1530 let params: MemEntityExtractParams = serde_json::from_value(serde_json::Value::Object(args))
1531 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1532
1533 let _project = params.project.as_deref().unwrap_or(project);
1535
1536 let entity_store = db.entities();
1537
1538 if let Some(id_str) = ¶ms.id {
1540 let id = Uuid::parse_str(id_str)
1541 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1542 let memory = db
1543 .memories()
1544 .get(id)?
1545 .ok_or_else(|| crate::error::MnemeError::NotFound(id))?;
1546
1547 if memory.is_encrypted {
1548 return Err(crate::error::MnemeError::Config(
1549 "Cannot extract entities from encrypted memory".into(),
1550 ));
1551 }
1552
1553 let entities = entity_store.extract_and_save(&memory)?;
1554 return Ok(serde_json::to_value(entities)?);
1555 }
1556
1557 if let Some(text) = ¶ms.text {
1559 let raw = crate::store::entities::EntityStore::extract_entities_from_text(text);
1560 return Ok(json!(raw));
1561 }
1562
1563 Err(crate::error::MnemeError::Config(
1564 "Provide either 'id' (memory UUID) or 'text' (raw content)".into(),
1565 ))
1566}
1567
1568fn mem_entity_search(
1569 db: &Database,
1570 args: JsonObject,
1571 _project: &str,
1572) -> crate::error::Result<serde_json::Value> {
1573 let params: MemEntitySearchParams = serde_json::from_value(serde_json::Value::Object(args))
1574 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1575
1576 let entity_type = params
1577 .entity_type
1578 .as_deref()
1579 .map(std::str::FromStr::from_str)
1580 .transpose()?;
1581
1582 let entity_store = db.entities();
1583 let results = entity_store.search_entities(¶ms.query, entity_type.as_ref(), params.limit)?;
1584 Ok(serde_json::to_value(results)?)
1585}
1586
1587fn mem_entity_links(
1588 db: &Database,
1589 args: JsonObject,
1590 _project: &str,
1591) -> crate::error::Result<serde_json::Value> {
1592 let params: MemEntityLinksParams = serde_json::from_value(serde_json::Value::Object(args))
1593 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1594
1595 let id = Uuid::parse_str(¶ms.id)
1596 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1597
1598 let entity_store = db.entities();
1599 let entities = entity_store.get_memory_entities(id)?;
1600 let links = entity_store.get_memory_links(id, params.limit)?;
1601
1602 Ok(json!({
1603 "memory_id": params.id,
1604 "entities": entities,
1605 "links": links.iter().map(|(link, title)| {
1606 json!({
1607 "entity_name": link.entity_name,
1608 "entity_type": link.entity_type.to_string(),
1609 "target_memory_id": link.target_memory_id.to_string(),
1610 "target_title": title,
1611 "link_strength": link.link_strength,
1612 })
1613 }).collect::<Vec<_>>()
1614 }))
1615}
1616
1617#[derive(Debug, Clone, Deserialize, JsonSchema)]
1620struct MemCloudEnrollParams {
1621 server: String,
1623 token: String,
1625 #[serde(default)]
1626 project: Option<String>,
1627}
1628
1629#[derive(Debug, Clone, Deserialize, JsonSchema)]
1630struct MemCloudSyncParams {
1631 #[serde(default)]
1632 project: Option<String>,
1633}
1634
1635#[derive(Debug, Clone, Deserialize, JsonSchema)]
1636struct MemCloudStatusParams {
1637 #[serde(default)]
1638 project: Option<String>,
1639}
1640
1641fn mem_cloud_enroll(
1642 db: &Database,
1643 args: JsonObject,
1644 project: &str,
1645) -> crate::error::Result<serde_json::Value> {
1646 let params: MemCloudEnrollParams = serde_json::from_value(serde_json::Value::Object(args))
1647 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1648 let project = params.project.unwrap_or_else(|| project.to_string());
1649 let settings = crate::config::settings::Settings::load()?;
1650
1651 let orch = crate::cloud::CloudOrchestrator::new(
1652 std::sync::Arc::new(db.clone()),
1655 settings.sync,
1656 );
1657
1658 let rt = tokio::runtime::Runtime::new()?;
1659 let result = rt.block_on(async {
1660 orch.enroll(¶ms.server, ¶ms.token, &project).await
1661 })?;
1662
1663 Ok(serde_json::to_value(result)?)
1664}
1665
1666fn mem_cloud_sync(
1667 db: &Database,
1668 args: JsonObject,
1669 project: &str,
1670) -> crate::error::Result<serde_json::Value> {
1671 let params: MemCloudSyncParams = serde_json::from_value(serde_json::Value::Object(args))
1672 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1673 let project = params.project.unwrap_or_else(|| project.to_string());
1674 let settings = crate::config::settings::Settings::load()?;
1675
1676 let orch = crate::cloud::CloudOrchestrator::new(
1677 std::sync::Arc::new(db.clone()),
1678 settings.sync,
1679 );
1680
1681 let rt = tokio::runtime::Runtime::new()?;
1682 let result = rt.block_on(async {
1683 orch.sync_cloud(&project).await
1684 })?;
1685
1686 Ok(serde_json::to_value(result)?)
1687}
1688
1689fn mem_cloud_status(
1690 db: &Database,
1691 args: JsonObject,
1692 project: &str,
1693) -> crate::error::Result<serde_json::Value> {
1694 let params: MemCloudStatusParams = serde_json::from_value(serde_json::Value::Object(args))
1695 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1696 let project = params.project.unwrap_or_else(|| project.to_string());
1697 let settings = crate::config::settings::Settings::load()?;
1698
1699 let orch = crate::cloud::CloudOrchestrator::new(
1700 std::sync::Arc::new(db.clone()),
1701 settings.sync,
1702 );
1703
1704 orch.cloud_status(&project)
1705}
1706
1707#[derive(Debug, Clone, Deserialize, JsonSchema)]
1710struct MemLearnParams {
1711 #[serde(default)]
1712 project: Option<String>,
1713}
1714
1715#[derive(Debug, Clone, Deserialize, JsonSchema)]
1716struct MemSessionOutcomeParams {
1717 session_id: String,
1719 outcome: String,
1721 #[serde(default)]
1723 failure_reasons: Option<String>,
1724 #[serde(default)]
1726 affected_files: u32,
1727 #[serde(default)]
1729 bugs_introduced: u32,
1730 #[serde(default)]
1732 user_corrections: Option<String>,
1733}
1734
1735fn mem_learn_failures(
1736 db: &Database,
1737 args: JsonObject,
1738 project: &str,
1739) -> crate::error::Result<serde_json::Value> {
1740 let params: MemLearnParams = serde_json::from_value(serde_json::Value::Object(args))
1741 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1742 let learn_db = std::sync::Arc::new(db.clone());
1743 let miner = crate::learn::FailureMiner::new(learn_db);
1744 let project = params.project.unwrap_or_else(|| project.to_string());
1745 let report = miner.mine(&project)?;
1746 Ok(serde_json::to_value(report)?)
1747}
1748
1749fn mem_session_outcome(
1750 db: &Database,
1751 args: JsonObject,
1752 _project: &str,
1753) -> crate::error::Result<serde_json::Value> {
1754 let params: MemSessionOutcomeParams = serde_json::from_value(serde_json::Value::Object(args))
1755 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1756
1757 let session_id = uuid::Uuid::parse_str(¶ms.session_id)
1758 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1759
1760 let outcome = match params.outcome.as_str() {
1761 "success" => crate::learn::SessionOutcome::Success,
1762 "partial" => crate::learn::SessionOutcome::PartialSuccess,
1763 "failure" => {
1764 let reasons: Vec<String> = params
1765 .failure_reasons
1766 .as_deref()
1767 .map(|s| s.split(',').map(|p| p.trim().to_string()).filter(|p| !p.is_empty()).collect())
1768 .unwrap_or_default();
1769 crate::learn::SessionOutcome::Failure { reasons }
1770 }
1771 _ => return Err(crate::error::MnemeError::Config(
1772 format!("Invalid outcome '{}'. Must be: success, partial, failure", params.outcome)
1773 )),
1774 };
1775
1776 let learn_db = std::sync::Arc::new(db.clone());
1777 let miner = crate::learn::FailureMiner::new(learn_db);
1778 miner.record_session_outcome(
1779 session_id,
1780 outcome,
1781 params.affected_files,
1782 params.bugs_introduced,
1783 params.user_corrections.as_deref(),
1784 )?;
1785
1786 Ok(json!({
1787 "session_id": params.session_id,
1788 "outcome": params.outcome,
1789 "recorded": true
1790 }))
1791}
1792
1793#[derive(Debug, Clone, Deserialize, JsonSchema)]
1796struct MemConsolidateParams {
1797 #[serde(default)]
1798 project: Option<String>,
1799 #[serde(default = "default_auto")]
1801 strategy: String,
1802 #[serde(default = "default_30")]
1804 days: u64,
1805 #[serde(default)]
1806 dry_run: bool,
1807}
1808
1809fn default_auto() -> String { "auto".to_string() }
1810fn default_30() -> u64 { 30 }
1811
1812#[derive(Debug, Clone, Deserialize, JsonSchema)]
1813struct MemBlockSetParams {
1814 #[serde(default)]
1815 project: Option<String>,
1816 slot: String,
1817 title: String,
1818 content: String,
1819}
1820
1821#[derive(Debug, Clone, Deserialize, JsonSchema)]
1822struct MemBlockGetParams {
1823 #[serde(default)]
1824 project: Option<String>,
1825 slot: String,
1826}
1827
1828#[derive(Debug, Clone, Deserialize, JsonSchema)]
1829struct MemBlockListParams {
1830 #[serde(default)]
1831 project: Option<String>,
1832}
1833
1834fn mem_consolidate(
1835 db: &Database,
1836 args: JsonObject,
1837 project: &str,
1838) -> crate::error::Result<serde_json::Value> {
1839 let params: MemConsolidateParams = serde_json::from_value(serde_json::Value::Object(args))
1840 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1841 let project = params.project.unwrap_or_else(|| project.to_string());
1842 let strat = match params.strategy.as_str() {
1843 "age" => crate::consolidate::ConsolidationStrategy::Age,
1844 "deprecated" => crate::consolidate::ConsolidationStrategy::Deprecated,
1845 "unused" => crate::consolidate::ConsolidationStrategy::Unused,
1846 _ => crate::consolidate::ConsolidationStrategy::Auto,
1847 };
1848 let eng = crate::consolidate::ConsolidationEngine::new(
1849 std::sync::Arc::new(db.clone()),
1850 );
1851 let result = eng.consolidate(&project, strat, params.days, params.dry_run)?;
1852 Ok(serde_json::to_value(result)?)
1853}
1854
1855fn mem_block_set(
1856 db: &Database,
1857 args: JsonObject,
1858 project: &str,
1859) -> crate::error::Result<serde_json::Value> {
1860 let params: MemBlockSetParams = serde_json::from_value(serde_json::Value::Object(args))
1861 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1862 let project = params.project.unwrap_or_else(|| project.to_string());
1863 let eng = crate::consolidate::ConsolidationEngine::new(
1864 std::sync::Arc::new(db.clone()),
1865 );
1866 let block = eng.set_block(&project, ¶ms.slot, ¶ms.title, ¶ms.content)?;
1867 Ok(serde_json::to_value(block)?)
1868}
1869
1870fn mem_block_get(
1871 db: &Database,
1872 args: JsonObject,
1873 project: &str,
1874) -> crate::error::Result<serde_json::Value> {
1875 let params: MemBlockGetParams = serde_json::from_value(serde_json::Value::Object(args))
1876 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1877 let project = params.project.unwrap_or_else(|| project.to_string());
1878 let eng = crate::consolidate::ConsolidationEngine::new(
1879 std::sync::Arc::new(db.clone()),
1880 );
1881 let block = eng.get_block(&project, ¶ms.slot)?;
1882 Ok(serde_json::to_value(block)?)
1883}
1884
1885fn mem_block_list(
1886 db: &Database,
1887 args: JsonObject,
1888 project: &str,
1889) -> crate::error::Result<serde_json::Value> {
1890 let params: MemBlockListParams = serde_json::from_value(serde_json::Value::Object(args))
1891 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1892 let project = params.project.unwrap_or_else(|| project.to_string());
1893 let eng = crate::consolidate::ConsolidationEngine::new(
1894 std::sync::Arc::new(db.clone()),
1895 );
1896 let blocks = eng.list_blocks(&project)?;
1897 Ok(serde_json::to_value(blocks)?)
1898}
1899
1900#[derive(Debug, Clone, Deserialize, JsonSchema)]
1903struct MemObsidianExportParams {
1904 #[serde(default)]
1905 project: Option<String>,
1906 output: String,
1908}
1909
1910fn mem_obsidian_export(
1911 db: &Database,
1912 args: JsonObject,
1913 project: &str,
1914) -> crate::error::Result<serde_json::Value> {
1915 let params: MemObsidianExportParams = serde_json::from_value(serde_json::Value::Object(args))
1916 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1917
1918 let project = params.project.unwrap_or_else(|| project.to_string());
1919 let output_dir = std::path::PathBuf::from(¶ms.output);
1920
1921 let memories = db.memories().list(&project, None, None, None, 10000, 0)?;
1922 let graph = db.memories().get_graph(&project)?;
1923
1924 let relations: Vec<crate::store::memory::MemoryRelation> = graph
1926 .edges
1927 .into_iter()
1928 .filter_map(|e| {
1929 let source_id = uuid::Uuid::parse_str(&e.source).ok()?;
1930 let target_id = uuid::Uuid::parse_str(&e.target).ok()?;
1931 Some(crate::store::memory::MemoryRelation {
1932 id: uuid::Uuid::nil(),
1933 sync_id: String::new(),
1934 source_id,
1935 target_id,
1936 relation_type: std::str::FromStr::from_str(&e.relation_type).ok()?,
1937 confidence: e.confidence,
1938 judgment_status: "active".to_string(),
1939 reason: None,
1940 evidence: None,
1941 marked_by_actor: "system".to_string(),
1942 created_at: chrono::Utc::now(),
1943 updated_at: chrono::Utc::now(),
1944 })
1945 })
1946 .collect();
1947
1948 let stats = crate::export::obsidian::export_to_obsidian(
1949 &memories,
1950 &relations,
1951 &project,
1952 &output_dir,
1953 )?;
1954
1955 Ok(json!({
1956 "project": project,
1957 "output": output_dir.join("mneme-export").to_string_lossy().to_string(),
1958 "files_written": stats.files_written,
1959 "bytes_written": stats.bytes_written,
1960 "memories_exported": memories.len()
1961 }))
1962}
1963
1964#[derive(Debug, Clone, Deserialize, JsonSchema)]
1967struct MemWatchScanParams {
1968 directory: String,
1970 #[serde(default = "default_md_ext")]
1972 ext: String,
1973 #[serde(default)]
1974 project: Option<String>,
1975}
1976
1977fn default_md_ext() -> String {
1978 ".md".to_string()
1979}
1980
1981fn mem_watch_scan(
1982 db: &Database,
1983 args: JsonObject,
1984 project: &str,
1985) -> crate::error::Result<serde_json::Value> {
1986 let params: MemWatchScanParams = serde_json::from_value(serde_json::Value::Object(args))
1987 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1988
1989 let project = params.project.unwrap_or_else(|| project.to_string());
1990 let dir = std::path::PathBuf::from(¶ms.directory);
1991 let store = db.memories();
1992
1993 let mut watcher = crate::watch::DirectoryWatcher::new(
1994 dir,
1995 params.ext,
1996 3600, store,
1998 project,
1999 );
2000
2001 let rt = tokio::runtime::Runtime::new()?;
2002 let result = rt.block_on(async { watcher.scan().await })?;
2003
2004 Ok(json!({
2005 "indexed": result.indexed,
2006 "skipped": result.skipped,
2007 "errors": result.errors,
2008 "removed": result.removed,
2009 "tracked_files": watcher.tracked_count()
2010 }))
2011}
2012
2013#[derive(Debug, Clone, Deserialize, JsonSchema)]
2016struct MemCompressParams {
2017 id: String,
2019 #[serde(default = "default_smart_summary")]
2021 strategy: String,
2022}
2023
2024fn default_smart_summary() -> String {
2025 "smart_summary".to_string()
2026}
2027
2028#[derive(Debug, Clone, Deserialize, JsonSchema)]
2029struct MemCompressBatchParams {
2030 #[serde(default)]
2032 project: Option<String>,
2033 #[serde(default = "default_smart_summary")]
2035 strategy: String,
2036 #[serde(default = "default_limit_10")]
2037 limit: u32,
2038}
2039
2040#[derive(Debug, Clone, Deserialize, JsonSchema)]
2041struct MemCompressContextParams {
2042 #[serde(default)]
2043 project: Option<String>,
2044 #[serde(default = "default_smart_summary")]
2046 strategy: String,
2047 #[serde(default = "default_limit_10")]
2048 limit: u32,
2049}
2050
2051fn mem_compress(
2052 db: &Database,
2053 args: JsonObject,
2054 _project: &str,
2055) -> crate::error::Result<serde_json::Value> {
2056 let params: MemCompressParams = serde_json::from_value(serde_json::Value::Object(args))
2057 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2058
2059 let id = Uuid::parse_str(¶ms.id)
2060 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
2061 let memory = db.memories().get(id)?
2062 .ok_or_else(|| crate::error::MnemeError::NotFound(id))?;
2063
2064 let strategy: crate::compress::CompressionStrategy = params.strategy.parse()?;
2065 let compressed = crate::compress::CompressionPipeline::compress(&memory, strategy);
2066
2067 Ok(serde_json::to_value(compressed)?)
2068}
2069
2070fn mem_compress_batch(
2071 db: &Database,
2072 args: JsonObject,
2073 project: &str,
2074) -> crate::error::Result<serde_json::Value> {
2075 let params: MemCompressBatchParams = serde_json::from_value(serde_json::Value::Object(args))
2076 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2077
2078 let project = params.project.unwrap_or_else(|| project.to_string());
2079 let strategy: crate::compress::CompressionStrategy = params.strategy.parse()?;
2080
2081 let memories = db.memories().list(&project, None, None, None, params.limit, 0)?;
2082 let mut compressed = Vec::new();
2083 let mut total_ratio = 0.0;
2084
2085 for memory in &memories {
2086 let result = crate::compress::CompressionPipeline::compress(memory, strategy);
2087 total_ratio += result.compression_ratio;
2088 compressed.push(result);
2089 }
2090
2091 let avg_ratio = if !compressed.is_empty() {
2092 total_ratio / compressed.len() as f64
2093 } else {
2094 0.0
2095 };
2096
2097 Ok(json!({
2098 "compressed": compressed,
2099 "count": compressed.len(),
2100 "average_compression_ratio": avg_ratio,
2101 "strategy": params.strategy,
2102 "project": project
2103 }))
2104}
2105
2106fn mem_compress_context(
2107 db: &Database,
2108 args: JsonObject,
2109 project: &str,
2110) -> crate::error::Result<serde_json::Value> {
2111 let params: MemCompressContextParams = serde_json::from_value(serde_json::Value::Object(args))
2112 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2113
2114 let project = params.project.unwrap_or_else(|| project.to_string());
2115 let strategy: crate::compress::CompressionStrategy = params.strategy.parse()?;
2116
2117 let memories = db.memories().context(&project, None, params.limit)?;
2118 let context_block = crate::compress::CompressionPipeline::compress_context_block(&memories, strategy, params.limit as usize);
2119
2120 Ok(json!({
2121 "compressed_context": context_block,
2122 "memory_count": memories.len(),
2123 "strategy": params.strategy,
2124 "project": project
2125 }))
2126}
2127
2128#[derive(Debug, Clone, Deserialize, JsonSchema)]
2131struct MemTemporalQueryParams {
2132 query: String,
2134 at_time: String,
2136 #[serde(default)]
2137 project: Option<String>,
2138 #[serde(default = "default_limit_10")]
2139 limit: u32,
2140}
2141
2142fn mem_temporal_query(
2143 db: &Database,
2144 args: JsonObject,
2145 project: &str,
2146) -> crate::error::Result<serde_json::Value> {
2147 let params: MemTemporalQueryParams = serde_json::from_value(serde_json::Value::Object(args))
2148 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2149
2150 let at_time = chrono::DateTime::parse_from_rfc3339(¶ms.at_time)
2151 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid at_time: {}", e)))?
2152 .with_timezone(&chrono::Utc);
2153 let at_time_str = at_time.to_rfc3339();
2154 let project = params.project.unwrap_or_else(|| project.to_string());
2155
2156 let conn = db.get_conn();
2157 let like_pattern = format!("%{}%", params.query);
2158 let limit_i64 = params.limit as i64;
2159
2160 let results: Vec<serde_json::Value>;
2161 {
2162 let conn_guard = conn.lock().map_err(|_| crate::error::MnemeError::Config("mutex poisoned".into()))?;
2163 let mut stmt = conn_guard.prepare(
2164 "SELECT id, project, scope, title, content, what, why, context, learned,
2165 memory_type, importance, tags, topic_key, access_count, revision_count,
2166 duplicate_count, normalized_hash, created_at, updated_at, last_accessed_at, last_seen_at, deleted_at,
2167 deprecated_at, deprecated_reason, supersedes_id, context_inject_count, origin_peer,
2168 is_encrypted, encrypted_for, valid_from, valid_until, provenance
2169 FROM memories
2170 WHERE project = ?1 AND deleted_at IS NULL
2171 AND (title LIKE ?2 OR content LIKE ?2)
2172 AND (valid_from IS NULL OR valid_from <= ?3)
2173 AND (valid_until IS NULL OR valid_until > ?3)
2174 ORDER BY updated_at DESC
2175 LIMIT ?4"
2176 )?;
2177 let rows = stmt.query_map(
2178 rusqlite::params![project, like_pattern, at_time_str, limit_i64],
2179 crate::store::memory::MemoryStore::row_to_memory,
2180 )?;
2181 results = rows
2182 .filter_map(|r| r.ok())
2183 .map(|m| serde_json::to_value(m).unwrap_or_default())
2184 .collect();
2185 }
2186
2187 Ok(json!({
2188 "results": results,
2189 "count": results.len(),
2190 "query": params.query,
2191 "at_time": params.at_time,
2192 "project": project,
2193 "description": "Facts that were valid at the specified time"
2194 }))
2195}
2196
2197#[derive(Debug, Clone, Deserialize, JsonSchema)]
2200struct MemExpandParams {
2201 id: String,
2203}
2204
2205#[derive(Debug, Clone, Deserialize, JsonSchema)]
2206struct MemTranscriptParams {
2207 session_id: String,
2209 #[serde(default = "default_limit_50")]
2210 limit: u32,
2211}
2212
2213fn default_limit_50() -> u32 { 50 }
2214
2215fn mem_expand(
2216 db: &Database,
2217 args: JsonObject,
2218 _project: &str,
2219) -> crate::error::Result<serde_json::Value> {
2220 let params: MemExpandParams = serde_json::from_value(serde_json::Value::Object(args))
2221 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2222
2223 let id = Uuid::parse_str(¶ms.id)
2224 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
2225
2226 let memory = db.memories().get(id)?
2227 .ok_or_else(|| crate::error::MnemeError::NotFound(id))?;
2228
2229 let entity_store = db.entities();
2231 let entities = entity_store.get_memory_entities(id).ok();
2232 let links = entity_store.get_memory_links(id, 10).ok();
2233
2234 Ok(json!({
2235 "memory": memory,
2236 "entities": entities,
2237 "entity_links": links.map(|l| l.iter().map(|(link, title)| {
2238 json!({
2239 "entity_name": link.entity_name,
2240 "target_title": title,
2241 "link_strength": link.link_strength
2242 })
2243 }).collect::<Vec<_>>()),
2244 "layer": 2,
2245 "instruction": "This is the expanded view (Layer 2). Use this content for detailed context."
2246 }))
2247}
2248
2249fn mem_transcript(
2250 db: &Database,
2251 args: JsonObject,
2252 _project: &str,
2253) -> crate::error::Result<serde_json::Value> {
2254 let params: MemTranscriptParams = serde_json::from_value(serde_json::Value::Object(args))
2255 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2256
2257 let session_id = Uuid::parse_str(¶ms.session_id)
2258 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
2259
2260 let session = db.sessions().get(session_id)?
2261 .ok_or_else(|| crate::error::MnemeError::NotFound(session_id))?;
2262
2263 let conn = db.get_conn();
2265 let prompts: Vec<crate::store::memory::UserPrompt>;
2266 {
2267 let conn_guard = conn.lock().map_err(|_| crate::error::MnemeError::Config("mutex poisoned".into()))?;
2268 let mut stmt = conn_guard.prepare(
2269 "SELECT id, session_id, content, project, created_at
2270 FROM user_prompts WHERE session_id = ?1
2271 ORDER BY created_at ASC
2272 LIMIT ?2"
2273 )?;
2274 let limit_i64 = params.limit as i64;
2275 let rows = stmt.query_map(rusqlite::params![params.session_id, limit_i64], |row| {
2276 Ok(crate::store::memory::UserPrompt {
2277 id: uuid::Uuid::parse_str(&row.get::<_, String>(0)?).map_err(|e| {
2278 rusqlite::Error::FromSqlConversionFailure(0, rusqlite::types::Type::Text, Box::new(e))
2279 })?,
2280 session_id: row.get::<_, Option<String>>(1)?
2281 .and_then(|s| uuid::Uuid::parse_str(&s).ok()),
2282 content: row.get(2)?,
2283 project: row.get(3)?,
2284 created_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>(4)?)
2285 .map_err(|e| {
2286 rusqlite::Error::FromSqlConversionFailure(4, rusqlite::types::Type::Text, Box::new(e))
2287 })?.with_timezone(&chrono::Utc),
2288 })
2289 })?;
2290 prompts = rows.collect::<Result<Vec<_>, _>>()?;
2291 }
2292
2293 let mut transcript_lines = vec![
2295 format!("## Session Transcript: {}", session.id),
2296 format!("Project: {} | Started: {} | Ended: {}",
2297 session.project,
2298 session.started_at.to_rfc3339(),
2299 session.ended_at.map(|d| d.to_rfc3339()).unwrap_or_else(|| "active".to_string())
2300 ),
2301 String::new(),
2302 ];
2303
2304 if let Some(ref summary) = session.summary {
2305 transcript_lines.push(format!("Summary: {}", summary));
2306 transcript_lines.push(String::new());
2307 }
2308
2309 for (i, prompt) in prompts.iter().enumerate() {
2310 transcript_lines.push(format!("### Prompt {} ({})", i + 1, prompt.created_at.to_rfc3339()));
2311 transcript_lines.push(prompt.content.clone());
2312 transcript_lines.push(String::new());
2313 }
2314
2315 let transcript = transcript_lines.join("\n");
2316
2317 Ok(json!({
2318 "session": session,
2319 "prompts": prompts,
2320 "transcript": transcript,
2321 "prompt_count": prompts.len(),
2322 "layer": 3,
2323 "instruction": "This is the raw transcript (Layer 3). Used for full dialogue reconstruction."
2324 }))
2325}
2326
2327#[derive(Debug, Clone, Deserialize, JsonSchema)]
2330struct MemCapturePassiveParams {
2331 text: String,
2333 #[serde(default)]
2334 project: Option<String>,
2335 #[serde(default)]
2337 session_id: Option<String>,
2338}
2339
2340fn mem_capture_passive(
2341 db: &Database,
2342 args: JsonObject,
2343 project: &str,
2344) -> crate::error::Result<serde_json::Value> {
2345 let params: MemCapturePassiveParams = serde_json::from_value(serde_json::Value::Object(args))
2346 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2347
2348 let project = params.project.unwrap_or_else(|| project.to_string());
2349 let session_id = params
2350 .session_id
2351 .as_deref()
2352 .map(Uuid::parse_str)
2353 .transpose()
2354 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
2355
2356 let memories = db.memories().capture_passive(
2357 ¶ms.text,
2358 &project,
2359 session_id,
2360 None,
2361 Some(db.embeddings()),
2362 )?;
2363
2364 Ok(json!({
2365 "captured": memories,
2366 "count": memories.len(),
2367 "project": project
2368 }))
2369}
2370
2371#[derive(Debug, Clone, Deserialize, JsonSchema)]
2374struct MemJudgeParams {
2375 memory_id_a: String,
2377 memory_id_b: String,
2379 candidate_id: Option<i64>,
2381 judged_relation: String,
2383 reasoning: String,
2385}
2386
2387#[derive(Debug, Clone, Deserialize, JsonSchema)]
2388struct MemCompareParams {
2389 memory_id_a: String,
2391 memory_id_b: String,
2393}
2394
2395#[derive(Debug, Clone, Deserialize, JsonSchema)]
2396struct MemConflictCandidatesParams {
2397 #[serde(default)]
2398 project: Option<String>,
2399 #[serde(default = "default_pending")]
2401 status: String,
2402 #[serde(default = "default_limit_20")]
2403 limit: u32,
2404}
2405
2406fn default_pending() -> String {
2407 "pending".to_string()
2408}
2409
2410fn mem_judge(
2411 db: &Database,
2412 args: JsonObject,
2413 _project: &str,
2414) -> crate::error::Result<serde_json::Value> {
2415 let params: MemJudgeParams = serde_json::from_value(serde_json::Value::Object(args))
2416 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2417
2418 let memory_id_a = Uuid::parse_str(¶ms.memory_id_a)
2419 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
2420 let memory_id_b = Uuid::parse_str(¶ms.memory_id_b)
2421 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
2422
2423 if let Some(candidate_id) = params.candidate_id {
2425 let judgment = db.memories().judge_conflict(
2426 candidate_id,
2427 ¶ms.judged_relation,
2428 ¶ms.reasoning,
2429 "agent",
2430 )?;
2431 return Ok(json!({
2432 "judgment": judgment,
2433 "memory_id_a": params.memory_id_a,
2434 "memory_id_b": params.memory_id_b,
2435 "relation": params.judged_relation
2436 }));
2437 }
2438
2439 let context = db.memories().get_conflict_context(memory_id_a, memory_id_b)?;
2441 let memory_a = db.memories().get(memory_id_a)?;
2442 let memory_b = db.memories().get(memory_id_b)?;
2443
2444 Ok(json!({
2445 "context": context,
2446 "memory_a": memory_a,
2447 "memory_b": memory_b,
2448 "suggested_relations": ["compatible", "conflicts_with", "supersedes", "extends", "depends_on"],
2449 "instruction": "Analyze whether memory B conflicts with, extends, supersedes, depends_on, or is compatible with memory A."
2450 }))
2451}
2452
2453fn mem_compare(
2454 db: &Database,
2455 args: JsonObject,
2456 _project: &str,
2457) -> crate::error::Result<serde_json::Value> {
2458 let params: MemCompareParams = serde_json::from_value(serde_json::Value::Object(args))
2459 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2460
2461 let memory_id_a = Uuid::parse_str(¶ms.memory_id_a)
2462 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
2463 let memory_id_b = Uuid::parse_str(¶ms.memory_id_b)
2464 .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
2465
2466 let memory_a = db.memories().get(memory_id_a)?
2467 .ok_or_else(|| crate::error::MnemeError::NotFound(memory_id_a))?;
2468 let memory_b = db.memories().get(memory_id_b)?
2469 .ok_or_else(|| crate::error::MnemeError::NotFound(memory_id_b))?;
2470
2471 let entity_store = db.entities();
2473 let shared_entities = entity_store.search_entities(&memory_a.title, None, 5)?;
2474
2475 let existing_relations = db.memories().get_existing_relations(memory_id_a, memory_id_b).unwrap_or_default();
2477
2478 let comparison = json!({
2480 "memory_a": {
2481 "title": memory_a.title,
2482 "type": memory_a.memory_type.to_string(),
2483 "importance": memory_a.importance.to_string(),
2484 "topic_key": memory_a.topic_key,
2485 "tags": memory_a.tags,
2486 "content_preview": memory_a.content[..memory_a.content.len().min(200)].to_string()
2487 },
2488 "memory_b": {
2489 "title": memory_b.title,
2490 "type": memory_b.memory_type.to_string(),
2491 "importance": memory_b.importance.to_string(),
2492 "topic_key": memory_b.topic_key,
2493 "tags": memory_b.tags,
2494 "content_preview": memory_b.content[..memory_b.content.len().min(200)].to_string()
2495 },
2496 "shared_entities": shared_entities.iter().map(|e| e.entity.entity_name.clone()).collect::<Vec<_>>(),
2497 "existing_relations": existing_relations,
2498 "same_type": memory_a.memory_type == memory_b.memory_type,
2499 "same_topic_key": memory_a.topic_key.is_some() && memory_a.topic_key == memory_b.topic_key,
2500 "same_project": memory_a.project == memory_b.project,
2501 });
2502
2503 Ok(comparison)
2504}
2505
2506fn mem_conflict_candidates(
2507 db: &Database,
2508 args: JsonObject,
2509 project: &str,
2510) -> crate::error::Result<serde_json::Value> {
2511 let params: MemConflictCandidatesParams = serde_json::from_value(serde_json::Value::Object(args))
2512 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2513
2514 let project = params.project.unwrap_or_else(|| project.to_string());
2515 let status = if params.status.is_empty() { "pending" } else { ¶ms.status };
2516 let candidates = db.memories().list_conflict_candidates(&project, Some(status), params.limit)?;
2517 Ok(serde_json::to_value(candidates)?)
2518}
2519
2520fn mem_entity_frequent(
2521 db: &Database,
2522 args: JsonObject,
2523 project: &str,
2524) -> crate::error::Result<serde_json::Value> {
2525 let params: MemEntityFrequentParams = serde_json::from_value(serde_json::Value::Object(args))
2526 .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2527
2528 let project = params.project.unwrap_or_else(|| project.to_string());
2529 let entity_store = db.entities();
2530 let frequent = entity_store.frequent_entities(&project, params.limit)?;
2531 Ok(serde_json::to_value(frequent)?)
2532}
2533
2534pub fn list_tools(plugins: Option<&crate::plugins::PluginManager>) -> Vec<Tool> {
2536 let mut tools = vec![
2537 Tool::new(
2538 "mem_save",
2539 "Save a memory with deduplication and topic_key support",
2540 Arc::new(schema_for_type::<MemSaveParams>()),
2541 ),
2542 Tool::new(
2543 "mem_update",
2544 "Update an existing memory by ID",
2545 Arc::new(schema_for_type::<MemUpdateParams>()),
2546 ),
2547 Tool::new(
2548 "mem_delete",
2549 "Soft-delete a memory (hard delete optional)",
2550 Arc::new(schema_for_type::<MemDeleteParams>()),
2551 ),
2552 Tool::new(
2553 "mem_restore",
2554 "Restore a soft-deleted memory",
2555 Arc::new(schema_for_type::<MemRestoreParams>()),
2556 ),
2557 Tool::new(
2558 "mem_search",
2559 "Hybrid search (FTS5 + fuzzy matching)",
2560 Arc::new(schema_for_type::<MemSearchParams>()),
2561 ),
2562 Tool::new(
2563 "mem_get",
2564 "Get a memory by ID",
2565 Arc::new(schema_for_type::<MemGetParams>()),
2566 ),
2567 Tool::new(
2568 "mem_list",
2569 "List memories with optional filters",
2570 Arc::new(schema_for_type::<MemListParams>()),
2571 ),
2572 Tool::new(
2573 "mem_context",
2574 "Get recent memories for session context",
2575 Arc::new(schema_for_type::<MemContextParams>()),
2576 ),
2577 Tool::new(
2578 "mem_timeline",
2579 "Get memories around a specific memory",
2580 Arc::new(schema_for_type::<MemTimelineParams>()),
2581 ),
2582 Tool::new(
2583 "mem_session_start",
2584 "Start a new work session",
2585 Arc::new(schema_for_type::<MemSessionStartParams>()),
2586 ),
2587 Tool::new(
2588 "mem_session_end",
2589 "End an active session",
2590 Arc::new(schema_for_type::<MemSessionEndParams>()),
2591 ),
2592 Tool::new(
2593 "mem_session_summary",
2594 "Get the active session summary",
2595 Arc::new(schema_for_type::<MemSessionSummaryParams>()),
2596 ),
2597 Tool::new(
2598 "mem_stats",
2599 "Get project statistics",
2600 Arc::new(schema_for_type::<MemStatsParams>()),
2601 ),
2602 Tool::new(
2603 "mem_projects",
2604 "List all projects",
2605 Arc::new(schema_for_type::<MemProjectsParams>()),
2606 ),
2607 Tool::new(
2608 "mem_conflicts",
2609 "Detect potential conflicts",
2610 Arc::new(schema_for_type::<MemConflictsParams>()),
2611 ),
2612 Tool::new(
2613 "mem_save_prompt",
2614 "Save a user prompt",
2615 Arc::new(schema_for_type::<MemSavePromptParams>()),
2616 ),
2617 Tool::new(
2618 "mem_suggest_topic_key",
2619 "Suggest a topic_key from type and title",
2620 Arc::new(schema_for_type::<MemSuggestTopicKeyParams>()),
2621 ),
2622 Tool::new(
2623 "mem_current_project",
2624 "Detect current project from cwd/git",
2625 Arc::new(schema_for_type::<MemCurrentProjectParams>()),
2626 ),
2627 Tool::new(
2628 "mem_doctor",
2629 "Run diagnostics on the database",
2630 Arc::new(schema_for_type::<MemDoctorParams>()),
2631 ),
2632 Tool::new(
2633 "mem_similar",
2634 "Find memories semantically similar to a given memory or text",
2635 Arc::new(schema_for_type::<MemSimilarParams>()),
2636 ),
2637 Tool::new(
2638 "mem_save_batch",
2639 "Save multiple memories in one call",
2640 Arc::new(schema_for_type::<MemSaveBatchParams>()),
2641 ),
2642 Tool::new(
2643 "mem_delete_relation",
2644 "Delete a relation by ID",
2645 Arc::new(schema_for_type::<MemDeleteRelationParams>()),
2646 ),
2647 Tool::new(
2648 "mem_audit",
2649 "Run quality audit on project memories",
2650 Arc::new(schema_for_type::<MemAuditParams>()),
2651 ),
2652 Tool::new(
2653 "mem_deduplicate",
2654 "Find semantically similar memories using embeddings",
2655 Arc::new(schema_for_type::<MemDeduplicateParams>()),
2656 ),
2657 Tool::new(
2658 "mem_feedback",
2659 "Record feedback on a memory",
2660 Arc::new(schema_for_type::<MemFeedbackParams>()),
2661 ),
2662 Tool::new(
2663 "mem_deprecate",
2664 "Mark a memory as deprecated",
2665 Arc::new(schema_for_type::<MemDeprecateParams>()),
2666 ),
2667 Tool::new(
2668 "mem_graph",
2669 "Get knowledge graph for a project",
2670 Arc::new(schema_for_type::<MemGraphParams>()),
2671 ),
2672 Tool::new(
2673 "mem_summarize",
2674 "Generate executive summary for a project or session",
2675 Arc::new(schema_for_type::<MemSummarizeParams>()),
2676 ),
2677 Tool::new(
2678 "mem_inject_context",
2679 "Generate formatted context block for prompt injection",
2680 Arc::new(schema_for_type::<MemInjectContextParams>()),
2681 ),
2682 Tool::new(
2683 "mem_forget_project",
2684 "Hard-delete all memories for a project",
2685 Arc::new(schema_for_type::<MemForgetProjectParams>()),
2686 ),
2687 Tool::new(
2688 "mem_health",
2689 "Get system health report",
2690 Arc::new(schema_for_type::<MemHealthParams>()),
2691 ),
2692 Tool::new(
2693 "mem_remind",
2694 "Get critical/high importance memories as reminders",
2695 Arc::new(schema_for_type::<MemRemindParams>()),
2696 ),
2697 Tool::new(
2698 "mem_tag_suggest",
2699 "Suggest tags based on project tags and content",
2700 Arc::new(schema_for_type::<MemTagSuggestParams>()),
2701 ),
2702 Tool::new(
2703 "mem_knowledge_gaps",
2704 "Analyze knowledge coverage gaps",
2705 Arc::new(schema_for_type::<MemKnowledgeGapsParams>()),
2706 ),
2707 Tool::new(
2708 "mem_sync_status",
2709 "Get sync status for a project",
2710 Arc::new(schema_for_type::<MemSyncStatusParams>()),
2711 ),
2712 Tool::new(
2713 "mem_sync_now",
2714 "Trigger sync with all auto-sync peers",
2715 Arc::new(schema_for_type::<MemSyncNowParams>()),
2716 ),
2717 Tool::new(
2718 "mem_sync_export",
2719 "Export project memories to sync file",
2720 Arc::new(schema_for_type::<MemSyncExportParams>()),
2721 ),
2722 Tool::new(
2723 "mem_encrypt",
2724 "Encrypt an existing memory",
2725 Arc::new(schema_for_type::<MemEncryptParams>()),
2726 ),
2727 Tool::new(
2728 "mem_decrypt",
2729 "Decrypt an existing memory",
2730 Arc::new(schema_for_type::<MemDecryptParams>()),
2731 ),
2732 Tool::new(
2733 "keys_list",
2734 "List registered encryption keys",
2735 Arc::new(schema_for_type::<KeysListParams>()),
2736 ),
2737 Tool::new(
2738 "keys_status",
2739 "Get encryption keys status",
2740 Arc::new(schema_for_type::<KeysStatusParams>()),
2741 ),
2742 Tool::new(
2743 "mem_entity_extract",
2744 "Extract entities from a memory or text",
2745 Arc::new(schema_for_type::<MemEntityExtractParams>()),
2746 ),
2747 Tool::new(
2748 "mem_entity_search",
2749 "Search memories by extracted entity",
2750 Arc::new(schema_for_type::<MemEntitySearchParams>()),
2751 ),
2752 Tool::new(
2753 "mem_entity_links",
2754 "Get entity-linked memories for a given memory",
2755 Arc::new(schema_for_type::<MemEntityLinksParams>()),
2756 ),
2757 Tool::new(
2758 "mem_entity_frequent",
2759 "Get most frequent entities in a project",
2760 Arc::new(schema_for_type::<MemEntityFrequentParams>()),
2761 ),
2762 Tool::new(
2763 "mem_cloud_enroll",
2764 "Enroll this project with a cloud sync server",
2765 Arc::new(schema_for_type::<MemCloudEnrollParams>()),
2766 ),
2767 Tool::new(
2768 "mem_cloud_sync",
2769 "Trigger a full cloud sync cycle",
2770 Arc::new(schema_for_type::<MemCloudSyncParams>()),
2771 ),
2772 Tool::new(
2773 "mem_cloud_status",
2774 "Check cloud sync status and recent sync history",
2775 Arc::new(schema_for_type::<MemCloudStatusParams>()),
2776 ),
2777 Tool::new(
2778 "mem_consolidate",
2779 "Consolidate stale/old/deprecated memories into auto-generated summary memories",
2780 Arc::new(schema_for_type::<MemConsolidateParams>()),
2781 ),
2782 Tool::new(
2783 "mem_block_set",
2784 "Set a memory block (slot: human/persona/workflow)",
2785 Arc::new(schema_for_type::<MemBlockSetParams>()),
2786 ),
2787 Tool::new(
2788 "mem_block_get",
2789 "Get a memory block by slot name",
2790 Arc::new(schema_for_type::<MemBlockGetParams>()),
2791 ),
2792 Tool::new(
2793 "mem_block_list",
2794 "List all memory blocks for a project",
2795 Arc::new(schema_for_type::<MemBlockListParams>()),
2796 ),
2797 Tool::new(
2798 "mem_learn_failures",
2799 "Analyze failed sessions and negative feedback to mine failure patterns and auto-generate corrective memories",
2800 Arc::new(schema_for_type::<MemLearnParams>()),
2801 ),
2802 Tool::new(
2803 "mem_session_outcome",
2804 "Record the outcome of a session (success/partial/failure with reasons)",
2805 Arc::new(schema_for_type::<MemSessionOutcomeParams>()),
2806 ),
2807 Tool::new(
2808 "mem_obsidian_export",
2809 "Export memories to an Obsidian vault (.md files with frontmatter + wikilinks)",
2810 Arc::new(schema_for_type::<MemObsidianExportParams>()),
2811 ),
2812 Tool::new(
2813 "mem_watch_scan",
2814 "Scan a directory and auto-index markdown files as memories",
2815 Arc::new(schema_for_type::<MemWatchScanParams>()),
2816 ),
2817 Tool::new(
2818 "mem_compress",
2819 "Compress a memory's content for token-efficient context injection",
2820 Arc::new(schema_for_type::<MemCompressParams>()),
2821 ),
2822 Tool::new(
2823 "mem_compress_batch",
2824 "Compress multiple memories in a project",
2825 Arc::new(schema_for_type::<MemCompressBatchParams>()),
2826 ),
2827 Tool::new(
2828 "mem_compress_context",
2829 "Generate a compressed context block for prompt injection",
2830 Arc::new(schema_for_type::<MemCompressContextParams>()),
2831 ),
2832 Tool::new(
2833 "mem_temporal_query",
2834 "Query facts that were valid at a specific point in time",
2835 Arc::new(schema_for_type::<MemTemporalQueryParams>()),
2836 ),
2837 Tool::new(
2838 "mem_expand",
2839 "[Layer 2] Get expanded view of a memory with full content, entities, and links",
2840 Arc::new(schema_for_type::<MemExpandParams>()),
2841 ),
2842 Tool::new(
2843 "mem_transcript",
2844 "[Layer 3] Get raw session transcript with all prompts",
2845 Arc::new(schema_for_type::<MemTranscriptParams>()),
2846 ),
2847 Tool::new(
2848 "mem_capture_passive",
2849 "Parse session output and auto-save memories (Key Learnings, Decisions, Architecture, etc.)",
2850 Arc::new(schema_for_type::<MemCapturePassiveParams>()),
2851 ),
2852 Tool::new(
2853 "mem_judge",
2854 "Judge the relationship between two memories (optionally recording the result)",
2855 Arc::new(schema_for_type::<MemJudgeParams>()),
2856 ),
2857 Tool::new(
2858 "mem_compare",
2859 "Compare two memories side-by-side with shared entities and existing relations",
2860 Arc::new(schema_for_type::<MemCompareParams>()),
2861 ),
2862 Tool::new(
2863 "mem_conflict_candidates",
2864 "List auto-detected conflict candidates for LLM judgment",
2865 Arc::new(schema_for_type::<MemConflictCandidatesParams>()),
2866 ),
2867 ];
2868
2869 if let Some(pm) = plugins {
2871 for pt in pm.plugin_tools() {
2872 tools.push(Tool::new(
2873 pt.name,
2874 pt.description,
2875 Arc::new(
2876 pt.input_schema
2877 .as_object()
2878 .cloned()
2879 .unwrap_or_default()
2880 .into_iter()
2881 .collect(),
2882 ),
2883 ));
2884 }
2885 }
2886
2887 tools
2888}