sgr_agent/
agent_runtime.rs1#[async_trait::async_trait]
10pub trait AgentRuntime: Send + Sync {
11 fn phase(&self) -> &str {
13 ""
14 }
15
16 fn threat_score(&self, _path: &str) -> f32 {
18 0.0
19 }
20
21 fn sender_trust(&self, _email: &str) -> SenderTrust {
23 SenderTrust::Unknown
24 }
25
26 fn skill_name(&self) -> &str {
28 ""
29 }
30
31 fn inbox_count(&self) -> usize {
33 0
34 }
35
36 fn has_otp(&self) -> bool {
38 false
39 }
40
41 fn has_threat(&self) -> bool {
43 false
44 }
45
46 fn intent(&self) -> &str {
48 ""
49 }
50
51 fn context_summary(&self) -> String {
53 let mut parts = Vec::new();
54 let phase = self.phase();
55 if !phase.is_empty() {
56 parts.push(format!("phase={}", phase));
57 }
58 if self.inbox_count() > 0 {
59 parts.push(format!("inbox={}", self.inbox_count()));
60 }
61 if self.has_otp() {
62 parts.push("otp=true".to_string());
63 }
64 if self.has_threat() {
65 parts.push("threat=true".to_string());
66 }
67 let skill = self.skill_name();
68 if !skill.is_empty() {
69 parts.push(format!("skill={}", skill));
70 }
71 parts.join(" | ")
72 }
73}
74
75#[derive(Debug, Clone, PartialEq)]
77pub enum SenderTrust {
78 Trusted,
80 Mismatch,
82 Unknown,
84 Blocked,
86}
87
88#[derive(Default)]
91pub struct SimpleRuntime {
92 pub phase: String,
93 pub intent: String,
94 pub inbox_count: usize,
95 pub has_otp: bool,
96 pub has_threat: bool,
97 pub skill_name: String,
98}
99
100#[async_trait::async_trait]
101impl AgentRuntime for SimpleRuntime {
102 fn phase(&self) -> &str {
103 &self.phase
104 }
105 fn skill_name(&self) -> &str {
106 &self.skill_name
107 }
108 fn inbox_count(&self) -> usize {
109 self.inbox_count
110 }
111 fn has_otp(&self) -> bool {
112 self.has_otp
113 }
114 fn has_threat(&self) -> bool {
115 self.has_threat
116 }
117 fn intent(&self) -> &str {
118 &self.intent
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn context_summary_empty() {
128 let rt = SimpleRuntime::default();
129 assert_eq!(rt.context_summary(), "");
130 }
131
132 #[test]
133 fn context_summary_full() {
134 let rt = SimpleRuntime {
135 phase: "Acting".into(),
136 inbox_count: 3,
137 has_otp: true,
138 has_threat: false,
139 skill_name: "inbox-processing".into(),
140 intent: "intent_inbox".into(),
141 };
142 let s = rt.context_summary();
143 assert!(s.contains("phase=Acting"));
144 assert!(s.contains("inbox=3"));
145 assert!(s.contains("otp=true"));
146 assert!(s.contains("skill=inbox-processing"));
147 assert!(!s.contains("threat"));
148 }
149
150 #[test]
151 fn context_summary_threat_only() {
152 let rt = SimpleRuntime {
153 has_threat: true,
154 ..Default::default()
155 };
156 let s = rt.context_summary();
157 assert!(s.contains("threat=true"));
158 assert!(!s.contains("otp"));
159 assert!(!s.contains("inbox"));
160 }
161
162 #[test]
163 fn context_summary_zero_inbox_hidden() {
164 let rt = SimpleRuntime {
165 inbox_count: 0,
166 ..Default::default()
167 };
168 assert!(!rt.context_summary().contains("inbox"));
169 }
170
171 #[test]
172 fn sender_trust_defaults_unknown() {
173 let rt = SimpleRuntime::default();
174 assert_eq!(rt.sender_trust("anyone@example.com"), SenderTrust::Unknown);
175 }
176
177 #[test]
178 fn default_runtime_all_safe() {
179 let rt = SimpleRuntime::default();
180 assert_eq!(rt.phase(), "");
181 assert_eq!(rt.inbox_count(), 0);
182 assert!(!rt.has_otp());
183 assert!(!rt.has_threat());
184 assert_eq!(rt.threat_score("any_file"), 0.0);
185 }
186}