deepstrike_core/context/
dashboard.rs1use crate::types::message::Message;
2
3#[derive(Debug, Clone, Default)]
4pub struct EventSurface {
5 pub pending_events: Vec<serde_json::Value>,
6 pub active_risks: Vec<serde_json::Value>,
7 pub recent_event_decisions: Vec<serde_json::Value>,
8}
9
10#[derive(Debug, Clone, Default)]
11pub struct KnowledgeSurface {
12 pub active_questions: Vec<String>,
13 pub evidence_packs: Vec<serde_json::Value>,
14 pub citations: Vec<String>,
15}
16
17#[derive(Debug, Clone)]
25pub struct Dashboard {
26 pub rho: f64,
27 pub token_budget: u32,
28 pub goal_progress: String,
29 pub error_count: u32,
30 pub depth: u32,
31 pub interrupt_requested: bool,
32 pub plan: Vec<String>,
33 pub event_surface: EventSurface,
34 pub knowledge_surface: KnowledgeSurface,
35 pub scratchpad: String,
36}
37
38impl Default for Dashboard {
39 fn default() -> Self {
40 Self {
41 rho: 0.0,
42 token_budget: 0,
43 goal_progress: String::new(),
44 error_count: 0,
45 depth: 0,
46 interrupt_requested: false,
47 plan: Vec::new(),
48 event_surface: EventSurface::default(),
49 knowledge_surface: KnowledgeSurface::default(),
50 scratchpad: String::new(),
51 }
52 }
53}
54
55impl Dashboard {
56 pub fn format_compact(&self) -> String {
60 let has_progress = !self.goal_progress.is_empty();
61 let has_plan = !self.plan.is_empty();
62 let has_scratchpad = !self.scratchpad.is_empty();
63 let has_questions = !self.knowledge_surface.active_questions.is_empty();
64 let has_activity = self.error_count > 0 || self.depth > 0 || self.interrupt_requested;
65
66 if !has_progress && !has_plan && !has_scratchpad && !has_questions && !has_activity {
67 return String::new();
68 }
69
70 let mut parts: Vec<String> = Vec::new();
71 parts.push(format!(
72 "[AGENT STATE] rho={:.3} turn={} errors={} interrupt={}",
73 self.rho, self.depth, self.error_count, self.interrupt_requested
74 ));
75 if has_progress {
76 parts.push(format!("goal_progress: {}", self.goal_progress));
77 }
78 if has_plan {
79 let plan = self
80 .plan
81 .iter()
82 .enumerate()
83 .map(|(i, s)| format!(" {}. {}", i + 1, s))
84 .collect::<Vec<_>>()
85 .join("\n");
86 parts.push(format!("plan:\n{plan}"));
87 }
88 if has_questions {
89 parts.push(format!(
90 "active_questions: {}",
91 self.knowledge_surface.active_questions.join(", ")
92 ));
93 }
94 if has_scratchpad {
95 parts.push(format!("scratchpad: {}", self.scratchpad));
96 }
97 parts.join("\n")
98 }
99
100 pub fn format_message(&self) -> Message {
101 let plan_str = if self.plan.is_empty() {
102 "(none)".to_string()
103 } else {
104 self.plan
105 .iter()
106 .enumerate()
107 .map(|(i, s)| format!(" {}. {}", i + 1, s))
108 .collect::<Vec<_>>()
109 .join("\n")
110 };
111
112 let questions = if self.knowledge_surface.active_questions.is_empty() {
113 "(none)".to_string()
114 } else {
115 self.knowledge_surface.active_questions.join(", ")
116 };
117
118 let content = format!(
119 "[DASHBOARD]\nrho={:.3} budget={} errors={} depth={} interrupt={}\ngoal_progress: {}\nplan:\n{}\nactive_questions: {}\nscratchpad: {}",
120 self.rho,
121 self.token_budget,
122 self.error_count,
123 self.depth,
124 self.interrupt_requested,
125 self.goal_progress,
126 plan_str,
127 questions,
128 self.scratchpad,
129 );
130
131 Message::system(content)
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use crate::types::message::Role;
139
140 #[test]
141 fn format_message_produces_system_message() {
142 let d = Dashboard::default();
143 let msg = d.format_message();
144 assert_eq!(msg.role, Role::System);
145 if let crate::types::message::Content::Text(ref t) = msg.content {
146 assert!(t.contains("[DASHBOARD]"));
147 }
148 }
149}