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 ContradictionDismissResponse, ExportStats, ListMemoryRevisionsResponse,
41 ListPageRevisionsResponse, ListRefinementsResponse, MemoryDetail, MemoryRevisionEntry,
42 OrphanLink, OrphanLinksResponse, PageChangelogEntry, PendingRevision, PendingRevisionItem,
43 ProposalAction, RefinementPayload, RefinementProposalSummary, RejectRefinementResponse,
44 RevisionAcceptResponse, RevisionDismissResponse,
45};
46pub use sources::{MemoryType, RawDocument, SourceType, StabilityTier, SyncStatus};
47
48use serde::{Deserialize, Serialize};
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct ChangelogEntry {
53 pub version: i64,
54 pub at: i64,
56 pub delta: String,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub source_agent: Option<String>,
61 #[serde(skip_serializing_if = "Option::is_none")]
63 pub incoming_source_id: Option<String>,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct PageSource {
70 pub page_id: String,
71 pub memory_source_id: String,
72 pub linked_at: i64,
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub link_reason: Option<String>,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct PageSourceWithMemory {
81 pub source: PageSource,
82 pub memory: Option<crate::memory::MemoryItem>,
83}
84
85pub fn version() -> &'static str {
87 env!("CARGO_PKG_VERSION")
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn version_is_set() {
96 assert!(!version().is_empty());
97 }
98
99 #[test]
100 fn memory_type_roundtrip() {
101 for variant in [
102 MemoryType::Identity,
103 MemoryType::Preference,
104 MemoryType::Decision,
105 MemoryType::Lesson,
106 MemoryType::Gotcha,
107 MemoryType::Fact,
108 ] {
109 let s = variant.to_string();
110 let parsed: MemoryType = s.parse().unwrap();
111 assert_eq!(parsed, variant);
112 }
113 }
114
115 #[test]
116 fn search_result_serializes() {
117 let sr = SearchResult {
118 id: "1".into(),
119 content: "test".into(),
120 source: "memory".into(),
121 source_id: "mem_abc".into(),
122 title: "Test".into(),
123 url: None,
124 chunk_index: 0,
125 last_modified: 1000,
126 score: 0.9,
127 chunk_type: None,
128 language: None,
129 semantic_unit: None,
130 memory_type: Some("fact".into()),
131 space: None,
132 source_agent: None,
133 confidence: Some(0.8),
134 confirmed: Some(true),
135 stability: None,
136 supersedes: None,
137 summary: None,
138 entity_id: None,
139 entity_name: None,
140 quality: None,
141 is_archived: false,
142 is_recap: false,
143 structured_fields: None,
144 retrieval_cue: None,
145 source_text: None,
146 raw_score: 0.0,
147 version: 0,
148 pending_revision: false,
149 merged_from: None,
150 last_delta_summary: None,
151 };
152 let json = serde_json::to_string(&sr).unwrap();
153 assert!(json.contains("mem_abc"));
154 assert!(!json.contains("entity_id"));
156 }
157
158 #[test]
159 fn raw_document_default() {
160 let doc = RawDocument::default();
161 assert_eq!(doc.enrichment_status, "raw");
162 assert_eq!(doc.supersede_mode, "hide");
163 assert!(!doc.pending_revision);
164 assert!(!doc.is_recap);
165 }
166
167 #[test]
168 fn stability_tier_mapping() {
169 use sources::stability_tier;
170 assert_eq!(stability_tier(Some("identity")), StabilityTier::Protected);
171 assert_eq!(stability_tier(Some("preference")), StabilityTier::Protected);
172 assert_eq!(stability_tier(Some("fact")), StabilityTier::Standard);
173 assert_eq!(stability_tier(Some("decision")), StabilityTier::Standard);
174 assert_eq!(stability_tier(Some("lesson")), StabilityTier::Standard);
175 assert_eq!(stability_tier(Some("gotcha")), StabilityTier::Standard);
176 assert_eq!(stability_tier(Some("goal")), StabilityTier::Protected);
179 assert_eq!(stability_tier(None), StabilityTier::Ephemeral);
180 }
181}
182
183#[cfg(test)]
184mod retrieval_event_tests {
185 use super::*;
186
187 #[test]
188 fn retrieval_event_roundtrips() {
189 let e = RetrievalEvent {
190 timestamp_ms: 1_700_000_000_000,
191 agent_name: "claude-code".into(),
192 query: Some("origin positioning".into()),
193 page_titles: vec!["Origin positioning".into(), "Daemon architecture".into()],
194 page_ids: vec![],
195 memory_snippets: vec![],
196 };
197 let s = serde_json::to_string(&e).unwrap();
198 let back: RetrievalEvent = serde_json::from_str(&s).unwrap();
199 assert_eq!(back.agent_name, "claude-code");
200 assert_eq!(back.page_titles.len(), 2);
201 assert_eq!(back.query.as_deref(), Some("origin positioning"));
202 }
203
204 #[test]
205 fn retrieval_event_omits_none_query() {
206 let e = RetrievalEvent {
207 timestamp_ms: 1_700_000_000_000,
208 agent_name: "claude-code".into(),
209 query: None,
210 page_titles: vec![],
211 page_ids: vec![],
212 memory_snippets: vec![],
213 };
214 let s = serde_json::to_string(&e).unwrap();
215 assert!(
216 !s.contains("\"query\""),
217 "expected None query to be skipped on the wire, got: {s}",
218 );
219 let back: RetrievalEvent = serde_json::from_str(&s).unwrap();
220 assert_eq!(back.query, None);
221 assert!(back.page_titles.is_empty());
222 }
223
224 #[test]
225 fn page_change_roundtrips() {
226 let c = PageChange {
227 page_id: "page_abc".into(),
228 title: "Wiki-style prose pages".into(),
229 change_kind: PageChangeKind::Revised,
230 changed_at_ms: 1_700_000_000_000,
231 };
232 let s = serde_json::to_string(&c).unwrap();
233 assert!(
234 s.contains("\"change_kind\":\"revised\""),
235 "expected snake_case change_kind on the wire, got: {s}",
236 );
237 let back: PageChange = serde_json::from_str(&s).unwrap();
238 assert_eq!(back.change_kind, PageChangeKind::Revised);
239 }
240}