wm_tools/expansion/
patterns.rs1#![forbid(unsafe_code)]
4
5use async_trait::async_trait;
6
7use serde_json::{Value, json};
8use std::sync::Arc;
9use wm_core::{Context, EffectRow, Galaxy, Gana, Resource, Tool, ToolStats};
10use wm_memory::{AssociationStore, MemoryStore};
11
12use super::common::{galaxy_name, parse_galaxy};
13
14pub struct PatternSearchTool {
15 store: Arc<MemoryStore>,
16 stats: ToolStats,
17 effects: EffectRow,
18}
19
20impl PatternSearchTool {
21 pub fn new(store: Arc<MemoryStore>) -> Self {
22 Self {
23 store,
24 stats: ToolStats::default(),
25 effects: EffectRow::read_only(vec![Resource::Galaxy("universal".into())]),
26 }
27 }
28}
29
30#[async_trait]
31impl Tool for PatternSearchTool {
32 fn name(&self) -> &str {
33 "pattern.search"
34 }
35 fn gana(&self) -> Gana {
36 Gana::Ox
37 }
38 fn effects(&self) -> &EffectRow {
39 &self.effects
40 }
41 fn description(&self) -> &str {
42 "Search for patterns in memory content across galaxies"
43 }
44 async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
45 let pattern = args.get("pattern").and_then(|v| v.as_str()).unwrap_or("");
46 let galaxies = args.get("galaxies").and_then(|v| v.as_array());
47 let limit = args
48 .get("limit")
49 .and_then(serde_json::Value::as_u64)
50 .unwrap_or(20) as usize;
51 let galaxies_to_search: Vec<Galaxy> = match galaxies {
52 Some(arr) => {
53 let mut parsed = Vec::new();
54 for g in arr {
55 if let Some(name) = g.as_str() {
56 parsed.push(parse_galaxy(name)?);
57 }
58 }
59 parsed
60 }
61 None => Galaxy::memory_galaxies().to_vec(),
62 };
63 let mut matches = Vec::new();
64 for galaxy in &galaxies_to_search {
65 let memories = self.store.scan(*galaxy, 500)?;
66 for mem in memories {
67 if mem.content.to_lowercase().contains(&pattern.to_lowercase()) {
68 matches.push(json!({
69 "galaxy": galaxy_name(*galaxy),
70 "id": mem.metadata.id,
71 "content_preview": mem.content.chars().take(100).collect::<String>(),
72 }));
73 if matches.len() >= limit {
74 break;
75 }
76 }
77 }
78 if matches.len() >= limit {
79 break;
80 }
81 }
82 Ok(json!({
83 "status": "success",
84 "pattern": pattern,
85 "matches": matches.len(),
86 "results": matches,
87 }))
88 }
89 fn stats(&self) -> &ToolStats {
90 &self.stats
91 }
92}
93
94pub struct SalienceSpotlightTool {
96 store: Arc<MemoryStore>,
97 stats: ToolStats,
98 effects: EffectRow,
99}
100
101impl SalienceSpotlightTool {
102 pub fn new(store: Arc<MemoryStore>) -> Self {
103 Self {
104 store,
105 stats: ToolStats::default(),
106 effects: EffectRow::read_only(vec![Resource::Galaxy("universal".into())]),
107 }
108 }
109}
110
111#[async_trait]
112impl Tool for SalienceSpotlightTool {
113 fn name(&self) -> &str {
114 "salience.spotlight"
115 }
116 fn gana(&self) -> Gana {
117 Gana::Ox
118 }
119 fn effects(&self) -> &EffectRow {
120 &self.effects
121 }
122 fn description(&self) -> &str {
123 "Find high-importance memories across all galaxies"
124 }
125 async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
126 let min_importance = args
127 .get("min_importance")
128 .and_then(serde_json::Value::as_f64)
129 .unwrap_or(0.8) as f32;
130 let limit = args
131 .get("limit")
132 .and_then(serde_json::Value::as_u64)
133 .unwrap_or(20) as usize;
134 let mut spotlighted = Vec::new();
135 for galaxy in Galaxy::memory_galaxies() {
136 let memories = self.store.scan(galaxy, 200)?;
137 for mem in memories {
138 if mem.metadata.importance >= min_importance {
139 spotlighted.push(json!({
140 "galaxy": galaxy_name(galaxy),
141 "id": mem.metadata.id,
142 "importance": mem.metadata.importance,
143 "content_preview": mem.content.chars().take(80).collect::<String>(),
144 }));
145 }
146 }
147 }
148 spotlighted.sort_by(|a, b| {
149 b["importance"]
150 .as_f64()
151 .unwrap_or(0.0)
152 .partial_cmp(&a["importance"].as_f64().unwrap_or(0.0))
153 .unwrap_or(std::cmp::Ordering::Equal)
154 });
155 spotlighted.truncate(limit);
156 Ok(json!({
157 "status": "success",
158 "min_importance": min_importance,
159 "count": spotlighted.len(),
160 "spotlight": spotlighted,
161 }))
162 }
163 fn stats(&self) -> &ToolStats {
164 &self.stats
165 }
166}
167
168pub struct SerendipitySurfaceTool {
170 store: Arc<MemoryStore>,
171 stats: ToolStats,
172 effects: EffectRow,
173}
174
175impl SerendipitySurfaceTool {
176 pub fn new(store: Arc<MemoryStore>) -> Self {
177 Self {
178 store,
179 stats: ToolStats::default(),
180 effects: EffectRow::read_only(vec![Resource::Galaxy("associations".into())]),
181 }
182 }
183}
184
185#[async_trait]
186impl Tool for SerendipitySurfaceTool {
187 fn name(&self) -> &str {
188 "serendipity.surface"
189 }
190 fn gana(&self) -> Gana {
191 Gana::Star
192 }
193 fn effects(&self) -> &EffectRow {
194 &self.effects
195 }
196 fn description(&self) -> &str {
197 "Surface unexpected cross-galaxy connections from associations"
198 }
199 async fn call(&self, _ctx: &mut Context, _args: Value) -> wm_core::Result<Value> {
200 let env = self.store.env();
201 let assoc_store = AssociationStore::open(env)?;
202 let total = assoc_store.count(env)?;
203
204 let mut cross_galaxy: Vec<Value> = Vec::new();
206 for galaxy in Galaxy::memory_galaxies() {
207 let memories = self.store.scan(galaxy, 50)?;
208 for mem in &memories {
209 let assocs = assoc_store.find_from(env, mem.metadata.id)?;
210 for assoc in &assocs {
211 for other_galaxy in Galaxy::memory_galaxies() {
213 if other_galaxy != galaxy {
214 if let Ok(Some(_)) = self.store.get(other_galaxy, assoc.target) {
215 cross_galaxy.push(json!({
216 "source_galaxy": galaxy_name(galaxy),
217 "target_galaxy": galaxy_name(other_galaxy),
218 "weight": assoc.weight,
219 "link_type": assoc.link_type.as_str(),
220 "association_type": assoc.association_type,
221 }));
222 }
223 }
224 }
225 }
226 }
227 }
228
229 Ok(json!({
230 "status": "success",
231 "total_associations": total,
232 "cross_galaxy_links": cross_galaxy.len(),
233 "serendipities": cross_galaxy.into_iter().take(20).collect::<Vec<_>>(),
234 }))
235 }
236 fn stats(&self) -> &ToolStats {
237 &self.stats
238 }
239}