1pub mod briefing;
9pub mod entities;
10pub mod events;
11pub mod import;
12pub mod memory;
13pub mod memory_type;
14pub mod narrative;
15pub mod onboarding;
16pub mod pages;
17pub mod requests;
18pub mod responses;
19pub mod sources;
20pub mod system_info;
21pub mod working_memory;
22
23pub use briefing::{BriefingResponse, ContradictionItem};
25pub use entities::{
26 Entity, EntityDetail, EntitySearchResult, EntitySuggestion, Observation, RecentRelation,
27 Relation, RelationWithEntity,
28};
29pub use memory::{
30 ActivityBadge, ActivityKind, AgentActivityRow, AgentConnection, DomainInfo,
31 EnrichmentStatusResponse, EnrichmentStepStatus, HomeStats, IndexedFileInfo, MemoryItem,
32 MemoryStats, MemoryVersionItem, PageChange, PageChangeKind, Profile, RecentActivityItem,
33 RejectionRecord, RetrievalEvent, SearchResult, SessionSnapshot, SnapshotCapture,
34 SnapshotCaptureWithContent, Space, TopMemory, TypeBreakdown,
35};
36pub use memory_type::{MEMORY_TYPE_CAPTURE_DESCRIPTION, MEMORY_TYPE_FILTER_DESCRIPTION};
37pub use narrative::NarrativeResponse;
38pub use pages::Page;
39pub use responses::{
40 ExportStats, ListMemoryRevisionsResponse, ListPageRevisionsResponse, ListRefinementsResponse,
41 MemoryDetail, MemoryRevisionEntry, PageChangelogEntry, PendingRevision, ProposalAction,
42 RefinementPayload, RefinementProposalSummary, RejectRefinementResponse,
43};
44pub use sources::{MemoryType, RawDocument, SourceType, StabilityTier, SyncStatus};
45
46use serde::{Deserialize, Serialize};
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct ChangelogEntry {
51 pub version: i64,
52 pub at: i64,
54 pub delta: String,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub source_agent: Option<String>,
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub incoming_source_id: Option<String>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct PageSource {
68 pub page_id: String,
69 pub memory_source_id: String,
70 pub linked_at: i64,
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub link_reason: Option<String>,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct PageSourceWithMemory {
79 pub source: PageSource,
80 pub memory: Option<crate::memory::MemoryItem>,
81}
82
83pub fn version() -> &'static str {
85 env!("CARGO_PKG_VERSION")
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn version_is_set() {
94 assert!(!version().is_empty());
95 }
96
97 #[test]
98 fn memory_type_roundtrip() {
99 for variant in [
100 MemoryType::Identity,
101 MemoryType::Preference,
102 MemoryType::Decision,
103 MemoryType::Lesson,
104 MemoryType::Gotcha,
105 MemoryType::Fact,
106 ] {
107 let s = variant.to_string();
108 let parsed: MemoryType = s.parse().unwrap();
109 assert_eq!(parsed, variant);
110 }
111 }
112
113 #[test]
114 fn search_result_serializes() {
115 let sr = SearchResult {
116 id: "1".into(),
117 content: "test".into(),
118 source: "memory".into(),
119 source_id: "mem_abc".into(),
120 title: "Test".into(),
121 url: None,
122 chunk_index: 0,
123 last_modified: 1000,
124 score: 0.9,
125 chunk_type: None,
126 language: None,
127 semantic_unit: None,
128 memory_type: Some("fact".into()),
129 domain: None,
130 source_agent: None,
131 confidence: Some(0.8),
132 confirmed: Some(true),
133 stability: None,
134 supersedes: None,
135 summary: None,
136 entity_id: None,
137 entity_name: None,
138 quality: None,
139 is_archived: false,
140 is_recap: false,
141 structured_fields: None,
142 retrieval_cue: None,
143 source_text: None,
144 raw_score: 0.0,
145 version: 0,
146 pending_revision: false,
147 merged_from: None,
148 last_delta_summary: None,
149 };
150 let json = serde_json::to_string(&sr).unwrap();
151 assert!(json.contains("mem_abc"));
152 assert!(!json.contains("entity_id"));
154 }
155
156 #[test]
157 fn raw_document_default() {
158 let doc = RawDocument::default();
159 assert_eq!(doc.enrichment_status, "raw");
160 assert_eq!(doc.supersede_mode, "hide");
161 assert!(!doc.pending_revision);
162 assert!(!doc.is_recap);
163 }
164
165 #[test]
166 fn stability_tier_mapping() {
167 use sources::stability_tier;
168 assert_eq!(stability_tier(Some("identity")), StabilityTier::Protected);
169 assert_eq!(stability_tier(Some("preference")), StabilityTier::Protected);
170 assert_eq!(stability_tier(Some("fact")), StabilityTier::Standard);
171 assert_eq!(stability_tier(Some("decision")), StabilityTier::Standard);
172 assert_eq!(stability_tier(Some("lesson")), StabilityTier::Standard);
173 assert_eq!(stability_tier(Some("gotcha")), StabilityTier::Standard);
174 assert_eq!(stability_tier(Some("goal")), StabilityTier::Protected);
177 assert_eq!(stability_tier(None), StabilityTier::Ephemeral);
178 }
179}
180
181#[cfg(test)]
182mod retrieval_event_tests {
183 use super::*;
184
185 #[test]
186 fn retrieval_event_roundtrips() {
187 let e = RetrievalEvent {
188 timestamp_ms: 1_700_000_000_000,
189 agent_name: "claude-code".into(),
190 query: Some("origin positioning".into()),
191 page_titles: vec!["Origin positioning".into(), "Daemon architecture".into()],
192 page_ids: vec![],
193 memory_snippets: vec![],
194 };
195 let s = serde_json::to_string(&e).unwrap();
196 let back: RetrievalEvent = serde_json::from_str(&s).unwrap();
197 assert_eq!(back.agent_name, "claude-code");
198 assert_eq!(back.page_titles.len(), 2);
199 assert_eq!(back.query.as_deref(), Some("origin positioning"));
200 }
201
202 #[test]
203 fn retrieval_event_omits_none_query() {
204 let e = RetrievalEvent {
205 timestamp_ms: 1_700_000_000_000,
206 agent_name: "claude-code".into(),
207 query: None,
208 page_titles: vec![],
209 page_ids: vec![],
210 memory_snippets: vec![],
211 };
212 let s = serde_json::to_string(&e).unwrap();
213 assert!(
214 !s.contains("\"query\""),
215 "expected None query to be skipped on the wire, got: {s}",
216 );
217 let back: RetrievalEvent = serde_json::from_str(&s).unwrap();
218 assert_eq!(back.query, None);
219 assert!(back.page_titles.is_empty());
220 }
221
222 #[test]
223 fn page_change_roundtrips() {
224 let c = PageChange {
225 page_id: "page_abc".into(),
226 title: "Wiki-style prose pages".into(),
227 change_kind: PageChangeKind::Revised,
228 changed_at_ms: 1_700_000_000_000,
229 };
230 let s = serde_json::to_string(&c).unwrap();
231 assert!(
232 s.contains("\"change_kind\":\"revised\""),
233 "expected snake_case change_kind on the wire, got: {s}",
234 );
235 let back: PageChange = serde_json::from_str(&s).unwrap();
236 assert_eq!(back.change_kind, PageChangeKind::Revised);
237 }
238}