1use std::collections::HashMap;
4use std::sync::Arc;
5use tokio::sync::{mpsc, oneshot, RwLock};
6
7use super::client::{ClientId, ClientInstance};
8use super::easy::{EasyId, EasyInstance, EasyStatus};
9use super::server::{ServerId, ServerInstance};
10use super::task::{ScheduledTask, TaskId};
11use crate::server::connection::ConnectionId;
12
13struct PendingTaskRegistration(Option<tokio::task::JoinHandle<()>>);
16
17impl PendingTaskRegistration {
18 fn into_handle(mut self) -> tokio::task::JoinHandle<()> {
19 self.0.take().expect("registration owns its task")
20 }
21}
22
23impl Drop for PendingTaskRegistration {
24 fn drop(&mut self) {
25 if let Some(handle) = &self.0 {
26 handle.abort();
27 }
28 }
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum ConversationSource {
34 User,
36 Network {
38 server_id: ServerId,
39 connection_id: Option<ConnectionId>,
40 },
41 Client { client_id: ClientId },
43 Task { task_name: String },
45 Scripting,
47}
48
49impl ConversationSource {
50 pub fn display_label(&self) -> String {
51 match self {
52 ConversationSource::User => "[User]".to_string(),
53 ConversationSource::Network {
54 server_id,
55 connection_id,
56 } => {
57 if let Some(conn_id) = connection_id {
58 format!("[Net #{}:{}]", server_id.as_u32(), conn_id)
59 } else {
60 format!("[Net #{}]", server_id.as_u32())
61 }
62 }
63 ConversationSource::Task { task_name } => format!("[Task:{}]", task_name),
64 ConversationSource::Client { client_id } => format!("[Client #{}]", client_id.as_u32()),
65 ConversationSource::Scripting => "[Scripting]".to_string(),
66 }
67 }
68}
69
70#[derive(Debug, Clone)]
72pub struct ConversationInfo {
73 pub id: String,
75 pub source: ConversationSource,
77 pub details: String,
79 pub start_time: crate::utils::clock::Instant,
81 pub end_time: Option<crate::utils::clock::Instant>,
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum Mode {
88 Server,
90 Client,
92 Idle,
94}
95
96impl Mode {
97 pub fn as_str(&self) -> &'static str {
98 match self {
99 Self::Server => "Server",
100 Self::Client => "Client",
101 Self::Idle => "Idle",
102 }
103 }
104}
105
106impl std::fmt::Display for Mode {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 write!(f, "{}", self.as_str())
109 }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum ScriptingMode {
115 On,
117 Off,
119 Python,
121 JavaScript,
123 Go,
125 Perl,
127}
128
129impl ScriptingMode {
130 pub fn as_str(&self) -> &'static str {
131 match self {
132 Self::On => "On",
133 Self::Off => "Off",
134 Self::Python => "Python",
135 Self::JavaScript => "JavaScript",
136 Self::Go => "Go",
137 Self::Perl => "Perl",
138 }
139 }
140}
141
142impl std::fmt::Display for ScriptingMode {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 write!(f, "{}", self.as_str())
145 }
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
150pub enum WebSearchMode {
151 #[default]
153 On,
154 Off,
156 Ask,
158}
159
160impl WebSearchMode {
161 pub fn as_str(&self) -> &'static str {
162 match self {
163 Self::On => "on",
164 Self::Off => "off",
165 Self::Ask => "ask",
166 }
167 }
168}
169
170impl std::fmt::Display for WebSearchMode {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 write!(f, "{}", self.as_str())
173 }
174}
175
176impl std::str::FromStr for WebSearchMode {
177 type Err = String;
178
179 fn from_str(s: &str) -> Result<Self, Self::Err> {
180 match s.to_lowercase().as_str() {
181 "on" | "enabled" | "enable" | "true" => Ok(Self::On),
182 "off" | "disabled" | "disable" | "false" => Ok(Self::Off),
183 "ask" => Ok(Self::Ask),
184 _ => Err(format!("Invalid web search mode: {}", s)),
185 }
186 }
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
191pub enum EventHandlerMode {
192 #[default]
194 Any,
195 Script,
197 Static,
199 Llm,
201}
202
203impl EventHandlerMode {
204 pub fn as_str(&self) -> &'static str {
205 match self {
206 Self::Any => "ANY",
207 Self::Script => "SCRIPT",
208 Self::Static => "STATIC",
209 Self::Llm => "LLM",
210 }
211 }
212}
213
214impl std::fmt::Display for EventHandlerMode {
215 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216 write!(f, "{}", self.as_str())
217 }
218}
219
220impl std::str::FromStr for EventHandlerMode {
221 type Err = String;
222
223 fn from_str(s: &str) -> Result<Self, Self::Err> {
224 match s.to_lowercase().as_str() {
225 "any" => Ok(Self::Any),
226 "script" => Ok(Self::Script),
227 "static" => Ok(Self::Static),
228 "llm" => Ok(Self::Llm),
229 _ => Err(format!("Invalid event handler mode: {}", s)),
230 }
231 }
232}
233
234pub struct WebApprovalRequest {
236 pub url: String,
237 pub response_tx: oneshot::Sender<WebApprovalResponse>,
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub enum WebApprovalResponse {
243 Allow,
245 Deny,
247 AlwaysAllow,
249}
250
251#[derive(Clone, Debug, serde::Serialize)]
258pub struct AccessLogEntry {
259 pub id: u64,
261 pub unix_ms: u64,
263 #[serde(skip_serializing_if = "Option::is_none")]
267 pub server_id: Option<u32>,
268 #[serde(skip_serializing_if = "Option::is_none")]
270 pub client_id: Option<u32>,
271 pub protocol: String,
273 pub connection_id: Option<u32>,
275 pub event_type: String,
277 pub request: serde_json::Value,
279 pub response: Vec<serde_json::Value>,
286}
287
288impl AccessLogEntry {
289 pub fn owner(&self) -> Option<AccessLogOwner> {
291 match (self.server_id, self.client_id) {
292 (Some(id), _) => Some(AccessLogOwner::Server(id)),
293 (None, Some(id)) => Some(AccessLogOwner::Client(id)),
294 (None, None) => None,
295 }
296 }
297}
298
299#[derive(Debug, Clone, Copy, PartialEq, Eq)]
301pub enum AccessLogOwner {
302 Server(u32),
303 Client(u32),
304}
305
306const ACCESS_LOG_CAPACITY: usize = 1000;
308
309#[derive(Debug, Clone)]
313pub struct DueFeedback {
314 pub server_id: Option<ServerId>,
316 pub client_id: Option<super::ClientId>,
318 pub instructions: String,
320 pub instruction: String,
322 pub memory: String,
324 pub entries: Vec<serde_json::Value>,
326}
327
328#[derive(Clone)]
330pub struct AppState {
331 inner: Arc<RwLock<AppStateInner>>,
332 pub llm_activity: super::llm_activity::LlmActivityTracker,
333}
334
335struct AppStateInner {
336 mode: Mode,
338 servers: HashMap<ServerId, ServerInstance>,
340 clients: HashMap<ClientId, ClientInstance>,
342 #[cfg(unix)]
344 tor_clients: HashMap<ClientId, Arc<arti_client::TorClient<tor_rtcompat::PreferredRuntime>>>,
345 easy_instances: HashMap<EasyId, EasyInstance>,
347 server_to_easy: HashMap<ServerId, EasyId>,
349 client_to_easy: HashMap<ClientId, EasyId>,
351 next_unified_id: u32,
354 #[allow(dead_code)]
355 next_server_id: u32,
357 #[allow(dead_code)]
358 next_client_id: u32,
360 ollama_model: Option<String>,
362 llm_client: Option<crate::llm::OllamaClient>,
364 rate_limiter: crate::llm::RateLimiter,
366 scripting_env: crate::scripting::ScriptingEnvironment,
368 selected_scripting_mode: ScriptingMode,
370 event_handler_mode: EventHandlerMode,
372 web_search_mode: WebSearchMode,
374 web_approval_tx: Option<mpsc::UnboundedSender<WebApprovalRequest>>,
376 include_disabled_protocols: bool,
378 min_stability: Option<crate::protocol::metadata::DevelopmentState>,
381 ollama_url: String,
384 instance_id: String,
386 tasks: HashMap<TaskId, ScheduledTask>,
388 scheduled_executions: HashMap<TaskId, tokio::task::JoinHandle<()>>,
390 peer_tasks: HashMap<(ServerId, u32), Vec<tokio::task::JoinHandle<()>>>,
392 next_task_id: u64,
394 user_conversation_state: Option<Arc<std::sync::Mutex<crate::llm::ConversationState>>>,
396 task_names: HashMap<String, TaskId>,
398 system_capabilities: crate::privilege::SystemCapabilities,
400 conversations: Vec<ConversationInfo>,
402 total_input_tokens: u64,
404 total_output_tokens: u64,
406 total_llm_calls: u64,
408 _database_manager: crate::state::DatabaseManager,
410 documented_server_protocols: std::collections::HashSet<String>,
412 documented_client_protocols: std::collections::HashSet<String>,
414 access_logs: std::collections::VecDeque<AccessLogEntry>,
416 next_access_log_id: u64,
418 pipes: HashMap<crate::pipe::PipeId, crate::pipe::PipeSpec>,
421 client_llm_call_limit: u32,
423 client_llm_calls: HashMap<ClientId, u32>,
428 client_tasks: HashMap<ClientId, Vec<tokio::task::JoinHandle<()>>>,
439 server_tasks: HashMap<ServerId, Vec<tokio::task::JoinHandle<()>>>,
453 server_handles: HashMap<ServerId, crate::state::server_handles::ServerHandle>,
461 client_handles: HashMap<ClientId, crate::state::client_handles::ClientHandle>,
465 pub(crate) pending_intercepts: Vec<crate::state::intercepts::PendingIntercept>,
468 pub(crate) next_intercept_id: u64,
470 peer_handles: HashMap<(ServerId, u32), crate::state::client_handles::ClientHandle>,
474}
475
476impl AppStateInner {
477 fn remove_scheduled_task(&mut self, id: TaskId) -> Option<ScheduledTask> {
478 if let Some(handle) = self.scheduled_executions.remove(&id) {
479 handle.abort();
480 }
481 let task = self.tasks.remove(&id)?;
482 if self.task_names.get(&task.name) == Some(&id) {
483 self.task_names.remove(&task.name);
484 }
485 Some(task)
486 }
487
488 fn remove_peer(&mut self, server_id: ServerId, connection_id: u32) {
489 self.peer_handles.remove(&(server_id, connection_id));
490 for handle in self
491 .peer_tasks
492 .remove(&(server_id, connection_id))
493 .unwrap_or_default()
494 {
495 handle.abort();
496 }
497 }
498
499 fn teardown_server(&mut self, server_id: ServerId) -> Vec<tokio::task::JoinHandle<()>> {
511 let aborted = self.server_tasks.remove(&server_id).unwrap_or_default();
514 for handle in &aborted {
515 handle.abort();
516 }
517 self.server_handles.remove(&server_id);
519 self.peer_handles.retain(|(sid, _), _| *sid != server_id);
520 self.peer_tasks.retain(|(sid, _), handles| {
521 if *sid != server_id {
522 return true;
523 }
524 for handle in handles {
525 handle.abort();
526 }
527 false
528 });
529 self.drop_tasks_for_server(server_id);
530
531 let doomed: Vec<crate::pipe::PipeId> = self
534 .pipes
535 .iter()
536 .filter(|(_, p)| p.from == server_id.as_u32() || p.to == server_id.as_u32())
537 .map(|(&pid, _)| pid)
538 .collect();
539 for pid in doomed {
540 self.pipes.remove(&pid);
541 }
542 aborted
543 }
544
545 fn drop_tasks_for_server(&mut self, server_id: ServerId) {
548 use crate::state::task::TaskScope;
549
550 let task_ids_to_remove: Vec<TaskId> = self
551 .tasks
552 .iter()
553 .filter(|(_, task)| match &task.scope {
554 TaskScope::Server(sid) => *sid == server_id,
555 TaskScope::Connection(sid, _) => *sid == server_id,
557 _ => false,
558 })
559 .map(|(&id, _)| id)
560 .collect();
561
562 for id in task_ids_to_remove {
563 self.remove_scheduled_task(id);
564 }
565 }
566}
567
568pub const DEFAULT_CLIENT_LLM_CALL_LIMIT: u32 = 100;
579
580fn client_llm_call_limit_from_env() -> u32 {
583 std::env::var("NETGET_CLIENT_LLM_CALL_LIMIT")
584 .ok()
585 .and_then(|v| v.trim().parse::<u32>().ok())
586 .unwrap_or(DEFAULT_CLIENT_LLM_CALL_LIMIT)
587}
588
589impl AppState {
590 pub fn new() -> Self {
592 Self::new_with_options(false, "http://localhost:11434".to_string())
593 }
594
595 pub fn new_with_options(include_disabled_protocols: bool, ollama_url: String) -> Self {
597 let scripting_env = crate::scripting::ScriptingEnvironment::detect();
599
600 let system_capabilities = crate::privilege::SystemCapabilities::detect();
602
603 let selected_scripting_mode = ScriptingMode::On;
605 let event_handler_mode = EventHandlerMode::default();
606
607 let instance_id = Self::generate_instance_id();
609
610 let rate_limiter = crate::llm::RateLimiter::new(crate::llm::RateLimiterConfig::default());
612
613 Self {
614 llm_activity: super::llm_activity::LlmActivityTracker::default(),
615 inner: Arc::new(RwLock::new(AppStateInner {
616 mode: Mode::Idle,
617 servers: HashMap::new(),
618 clients: HashMap::new(),
619 #[cfg(unix)]
620 tor_clients: HashMap::new(),
621 easy_instances: HashMap::new(),
622 server_to_easy: HashMap::new(),
623 client_to_easy: HashMap::new(),
624 next_unified_id: 1,
625 next_server_id: 1,
626 next_client_id: 1,
627 ollama_model: None,
628 llm_client: None,
629 rate_limiter,
630 scripting_env,
631 selected_scripting_mode,
632 event_handler_mode,
633 web_search_mode: WebSearchMode::On, web_approval_tx: None, include_disabled_protocols,
636 min_stability: None,
637 ollama_url,
638 instance_id,
639 tasks: HashMap::new(),
640 scheduled_executions: HashMap::new(),
641 peer_tasks: HashMap::new(),
642 next_task_id: 1,
643 user_conversation_state: None,
644 task_names: HashMap::new(),
645 system_capabilities,
646 conversations: Vec::new(),
647 total_input_tokens: 0,
648 total_output_tokens: 0,
649 total_llm_calls: 0,
650 _database_manager: crate::state::DatabaseManager::new(),
651 documented_server_protocols: std::collections::HashSet::new(),
652 documented_client_protocols: std::collections::HashSet::new(),
653 access_logs: std::collections::VecDeque::new(),
654 next_access_log_id: 1,
655 pipes: HashMap::new(),
656 client_llm_call_limit: client_llm_call_limit_from_env(),
657 client_llm_calls: HashMap::new(),
658 client_tasks: HashMap::new(),
659 server_tasks: HashMap::new(),
660 server_handles: HashMap::new(),
661 client_handles: HashMap::new(),
662 pending_intercepts: Vec::new(),
663 next_intercept_id: 1,
664 peer_handles: HashMap::new(),
665 })),
666 }
667 }
668
669 fn generate_instance_id() -> String {
672 use crate::utils::clock::{SystemTime, UNIX_EPOCH};
673
674 let pid = crate::utils::clock::process_id();
675 let timestamp = SystemTime::now()
676 .duration_since(UNIX_EPOCH)
677 .unwrap()
678 .as_secs();
679
680 let random_bytes: [u8; 2] = rand::random();
682 let random_hex = hex::encode(random_bytes);
683
684 format!("claude-{}-{}-{}", pid, timestamp, random_hex)
685 }
686
687 pub async fn get_mode(&self) -> Mode {
689 self.inner.read().await.mode
690 }
691
692 pub async fn set_mode(&self, mode: Mode) {
694 self.inner.write().await.mode = mode;
695 }
696
697 pub async fn add_server(&self, mut server: ServerInstance) -> ServerId {
699 let mut inner = self.inner.write().await;
700 let id = ServerId::new(inner.next_unified_id);
701 inner.next_unified_id += 1;
702 server.id = id;
703 inner.servers.insert(id, server);
704
705 if inner.mode == Mode::Idle {
707 inner.mode = Mode::Server;
708 }
709
710 id
711 }
712
713 pub async fn remove_server(&self, id: ServerId) -> Option<ServerInstance> {
720 let (server, aborted) = {
721 let mut inner = self.inner.write().await;
722 let server = inner.servers.remove(&id);
723 let aborted = inner.teardown_server(id);
724 if inner.servers.is_empty() && inner.clients.is_empty() {
726 inner.mode = Mode::Idle;
727 }
728 (server, aborted)
729 };
730 for handle in aborted {
734 let _ = tokio::time::timeout(std::time::Duration::from_secs(5), handle).await;
735 }
736 server
737 }
738
739 pub async fn add_pipe(
745 &self,
746 from: ServerId,
747 on: String,
748 to: ServerId,
749 as_action: String,
750 map: std::collections::BTreeMap<String, String>,
751 ) -> anyhow::Result<crate::pipe::PipeId> {
752 let mut inner = self.inner.write().await;
753 if !inner.servers.contains_key(&from) {
754 anyhow::bail!("pipe source server #{} does not exist", from.as_u32());
755 }
756 if !inner.servers.contains_key(&to) {
757 anyhow::bail!("pipe target server #{} does not exist", to.as_u32());
758 }
759 let existing: Vec<crate::pipe::PipeSpec> = inner.pipes.values().cloned().collect();
760 if crate::pipe::would_create_cycle(&existing, from.as_u32(), to.as_u32()) {
761 anyhow::bail!(
762 "pipe #{} -> #{} refused: it would create a delivery cycle",
763 from.as_u32(),
764 to.as_u32()
765 );
766 }
767 let id = inner.next_unified_id;
768 inner.next_unified_id += 1;
769 inner.pipes.insert(
770 id,
771 crate::pipe::PipeSpec {
772 id,
773 from: from.as_u32(),
774 on,
775 to: to.as_u32(),
776 as_action,
777 map,
778 },
779 );
780 Ok(id)
781 }
782
783 pub async fn remove_pipe(&self, id: crate::pipe::PipeId) -> Option<crate::pipe::PipeSpec> {
785 self.inner.write().await.pipes.remove(&id)
786 }
787
788 pub async fn list_pipes(&self) -> Vec<crate::pipe::PipeSpec> {
790 self.inner.read().await.pipes.values().cloned().collect()
791 }
792
793 pub async fn pipes_matching(
795 &self,
796 from: ServerId,
797 event_id: &str,
798 ) -> Vec<crate::pipe::PipeSpec> {
799 self.inner
800 .read()
801 .await
802 .pipes
803 .values()
804 .filter(|p| p.from == from.as_u32() && p.on == event_id)
805 .cloned()
806 .collect()
807 }
808
809 pub fn register_server_task(
824 &self,
825 id: ServerId,
826 handle: tokio::task::JoinHandle<()>,
827 ) -> impl std::future::Future<Output = ()> + Send + '_ {
828 let pending = PendingTaskRegistration(Some(handle));
831 async move {
832 let mut inner = self.inner.write().await;
833 if !inner.servers.contains_key(&id) {
834 return;
835 }
836 let tasks = inner.server_tasks.entry(id).or_default();
837 tasks.retain(|h| !h.is_finished());
838 tasks.push(pending.into_handle());
839 }
840 }
841
842 pub async fn spawn_server_task<F>(&self, id: ServerId, future: F) -> tokio::task::AbortHandle
862 where
863 F: std::future::Future<Output = ()> + Send + 'static,
864 {
865 let handle = tokio::spawn(future);
866 let abort = handle.abort_handle();
867 self.register_server_task(id, handle).await;
868 abort
869 }
870
871 pub async fn spawn_client_task<F>(&self, id: ClientId, future: F) -> tokio::task::AbortHandle
874 where
875 F: std::future::Future<Output = ()> + Send + 'static,
876 {
877 let handle = tokio::spawn(future);
878 let abort = handle.abort_handle();
879 self.register_client_task(id, handle).await;
880 abort
881 }
882
883 pub async fn register_server_handle<T: std::any::Any + Send + Sync>(
928 &self,
929 id: ServerId,
930 handle: std::sync::Arc<T>,
931 ) {
932 let mut inner = self.inner.write().await;
933 if !inner.servers.contains_key(&id) {
934 return;
935 }
936 inner.server_handles.insert(id, handle);
937 }
938
939 pub async fn server_handle<T: std::any::Any + Send + Sync>(
946 &self,
947 id: ServerId,
948 ) -> Option<std::sync::Arc<T>> {
949 self.inner
950 .read()
951 .await
952 .server_handles
953 .get(&id)
954 .cloned()
955 .and_then(|h| h.downcast::<T>().ok())
956 }
957
958 pub async fn has_server_handle(&self, id: ServerId) -> bool {
960 self.inner.read().await.server_handles.contains_key(&id)
961 }
962
963 pub async fn park_intercept(
968 &self,
969 owner: crate::state::intercepts::InterceptOwner,
970 connection_id: Option<u32>,
971 event_type: &str,
972 description: &str,
973 event_data: Option<serde_json::Value>,
974 timeout_secs: u64,
975 ) -> (u64, tokio::sync::oneshot::Receiver<Vec<serde_json::Value>>) {
976 let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
977 let mut inner = self.inner.write().await;
978 let id = inner.next_intercept_id;
979 inner.next_intercept_id += 1;
980 inner.pending_intercepts.retain(|entry| !entry.is_dead());
981 inner
982 .pending_intercepts
983 .push(crate::state::intercepts::PendingIntercept {
984 id,
985 owner,
986 connection_id,
987 event_type: event_type.to_string(),
988 description: description.to_string(),
989 event_data,
990 created_unix_ms: crate::state::intercepts::now_unix_ms(),
991 timeout_secs,
992 reply_tx: Some(reply_tx),
993 });
994 (id, reply_rx)
995 }
996
997 pub async fn list_intercepts(&self) -> Vec<crate::state::intercepts::InterceptView> {
999 let mut inner = self.inner.write().await;
1000 inner.pending_intercepts.retain(|entry| !entry.is_dead());
1001 inner.pending_intercepts.iter().map(|e| e.view()).collect()
1002 }
1003
1004 pub async fn resolve_intercept(
1011 &self,
1012 id: u64,
1013 actions: Vec<serde_json::Value>,
1014 ) -> std::result::Result<(), String> {
1015 let entry = {
1016 let mut inner = self.inner.write().await;
1017 let Some(index) = inner.pending_intercepts.iter().position(|e| e.id == id) else {
1018 return Err(format!("request #{id} is no longer waiting"));
1019 };
1020 inner.pending_intercepts.remove(index)
1021 };
1022 let Some(tx) = entry.reply_tx else {
1023 return Err(format!("request #{id} was already answered"));
1024 };
1025 tx.send(actions).map_err(|_| {
1026 format!(
1027 "request #{id} timed out before this answer — the peer already got the \
1028 fail-closed reply"
1029 )
1030 })
1031 }
1032
1033 pub async fn dismiss_intercept(&self, id: u64) -> bool {
1036 let mut inner = self.inner.write().await;
1037 let before = inner.pending_intercepts.len();
1038 inner.pending_intercepts.retain(|e| e.id != id);
1041 inner.pending_intercepts.len() != before
1042 }
1043
1044 pub async fn remove_intercept(&self, id: u64) {
1047 let mut inner = self.inner.write().await;
1048 inner.pending_intercepts.retain(|e| e.id != id);
1049 }
1050
1051 pub async fn register_peer_handle(
1057 &self,
1058 server_id: ServerId,
1059 connection_id: u32,
1060 handle: crate::state::client_handles::ClientHandle,
1061 ) {
1062 let mut inner = self.inner.write().await;
1063 if !inner.servers.contains_key(&server_id) {
1064 return;
1065 }
1066 inner
1067 .peer_handles
1068 .insert((server_id, connection_id), handle);
1069 }
1070
1071 pub async fn has_peer_handle(&self, server_id: ServerId, connection_id: u32) -> bool {
1073 self.inner
1074 .read()
1075 .await
1076 .peer_handles
1077 .contains_key(&(server_id, connection_id))
1078 }
1079
1080 pub async fn remove_peer_handle(&self, server_id: ServerId, connection_id: u32) {
1083 self.inner
1084 .write()
1085 .await
1086 .remove_peer(server_id, connection_id);
1087 }
1088
1089 pub fn register_peer_task(
1092 &self,
1093 server_id: ServerId,
1094 connection_id: u32,
1095 handle: tokio::task::JoinHandle<()>,
1096 ) -> impl std::future::Future<Output = ()> + Send + '_ {
1097 let pending = PendingTaskRegistration(Some(handle));
1100 async move {
1101 let mut inner = self.inner.write().await;
1102 if !inner.servers.contains_key(&server_id)
1103 || !inner.peer_handles.contains_key(&(server_id, connection_id))
1104 {
1105 return;
1106 }
1107 let tasks = inner
1108 .peer_tasks
1109 .entry((server_id, connection_id))
1110 .or_default();
1111 tasks.retain(|task| !task.is_finished());
1112 tasks.push(pending.into_handle());
1113 }
1114 }
1115
1116 pub async fn send_to_peer(
1120 &self,
1121 server_id: ServerId,
1122 connection_id: u32,
1123 action: serde_json::Value,
1124 timeout: std::time::Duration,
1125 ) -> anyhow::Result<crate::state::client_handles::ClientSendOutcome> {
1126 use crate::state::client_handles::{ClientCommand, ClientSendOutcome};
1127
1128 let handle = {
1129 let inner = self.inner.read().await;
1130 inner.peer_handles.get(&(server_id, connection_id)).cloned()
1131 };
1132 let Some(handle) = handle else {
1133 anyhow::bail!(
1134 "connection #{connection_id} on server #{} does not accept injected actions",
1135 server_id.as_u32()
1136 );
1137 };
1138 let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
1139 let command = ClientCommand { action, reply_tx };
1140 if handle.command_tx.try_send(command).is_err() {
1141 let mut inner = self.inner.write().await;
1145 if let Some(entry) = inner.peer_handles.get(&(server_id, connection_id)) {
1146 if entry.command_tx.is_closed() {
1147 inner.peer_handles.remove(&(server_id, connection_id));
1148 return Ok(ClientSendOutcome::Disconnected);
1149 }
1150 }
1151 anyhow::bail!("connection #{connection_id} is busy — try again");
1152 }
1153 match tokio::time::timeout(timeout, reply_rx).await {
1154 Ok(Ok(outcome)) => outcome,
1155 Ok(Err(_)) => Ok(ClientSendOutcome::Disconnected),
1156 Err(_) => anyhow::bail!("no outcome within {}s", timeout.as_secs()),
1157 }
1158 }
1159
1160 pub async fn register_client_handle(
1166 &self,
1167 id: ClientId,
1168 handle: crate::state::client_handles::ClientHandle,
1169 ) {
1170 let mut inner = self.inner.write().await;
1171 if !inner.clients.contains_key(&id) {
1172 return;
1173 }
1174 inner.client_handles.insert(id, handle);
1175 }
1176
1177 pub async fn client_handle(
1179 &self,
1180 id: ClientId,
1181 ) -> Option<crate::state::client_handles::ClientHandle> {
1182 self.inner.read().await.client_handles.get(&id).cloned()
1183 }
1184
1185 pub async fn has_client_handle(&self, id: ClientId) -> bool {
1188 self.inner.read().await.client_handles.contains_key(&id)
1189 }
1190
1191 pub async fn remove_client_handle(&self, id: ClientId) {
1194 self.inner.write().await.client_handles.remove(&id);
1195 }
1196
1197 pub async fn send_to_client(
1203 &self,
1204 id: ClientId,
1205 action: serde_json::Value,
1206 timeout: std::time::Duration,
1207 ) -> anyhow::Result<crate::state::client_handles::ClientSendOutcome> {
1208 use crate::state::client_handles::ClientCommand;
1209
1210 let handle = match self.client_handle(id).await {
1211 Some(handle) => handle,
1212 None => {
1213 anyhow::bail!(
1214 "client #{} does not accept injected commands \
1215 (not running, or its protocol has not adopted the command channel)",
1216 id.as_u32()
1217 )
1218 }
1219 };
1220
1221 let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
1222 let command = ClientCommand { action, reply_tx };
1223
1224 match tokio::time::timeout(timeout, handle.command_tx.send(command)).await {
1227 Ok(Ok(())) => {}
1228 Ok(Err(_)) => anyhow::bail!(
1229 "client #{}'s connection loop is not running (channel closed)",
1230 id.as_u32()
1231 ),
1232 Err(_) => anyhow::bail!(
1233 "client #{} is busy (command queue full after {:?})",
1234 id.as_u32(),
1235 timeout
1236 ),
1237 }
1238
1239 match tokio::time::timeout(timeout, reply_rx).await {
1240 Ok(Ok(outcome)) => outcome,
1241 Ok(Err(_)) => anyhow::bail!(
1242 "client #{}'s connection loop dropped the command without executing it \
1243 (loop shutting down)",
1244 id.as_u32()
1245 ),
1246 Err(_) => anyhow::bail!(
1247 "client #{} did not report an outcome within {:?}",
1248 id.as_u32(),
1249 timeout
1250 ),
1251 }
1252 }
1253
1254 pub async fn server_task_count(&self, id: ServerId) -> usize {
1256 self.inner
1257 .read()
1258 .await
1259 .server_tasks
1260 .get(&id)
1261 .map(|t| t.len())
1262 .unwrap_or(0)
1263 }
1264
1265 pub async fn get_server(&self, id: ServerId) -> Option<ServerInstance> {
1267 self.inner.read().await.servers.get(&id).cloned()
1268 }
1269
1270 pub async fn get_all_server_ids(&self) -> Vec<ServerId> {
1272 self.inner.read().await.servers.keys().copied().collect()
1273 }
1274
1275 pub async fn get_all_servers(&self) -> Vec<ServerInstance> {
1277 self.inner.read().await.servers.values().cloned().collect()
1278 }
1279
1280 pub async fn update_server_status(&self, id: ServerId, status: super::server::ServerStatus) {
1282 if let Some(server) = self.inner.write().await.servers.get_mut(&id) {
1283 server.status = status;
1284 server.status_changed_at = crate::utils::clock::Instant::now();
1285 }
1286 }
1287
1288 pub async fn update_server_local_addr(&self, id: ServerId, local_addr: std::net::SocketAddr) {
1290 if let Some(server) = self.inner.write().await.servers.get_mut(&id) {
1291 server.local_addr = Some(local_addr);
1292 }
1293 }
1294
1295 pub async fn get_instruction(&self, server_id: ServerId) -> Option<String> {
1297 self.inner
1298 .read()
1299 .await
1300 .servers
1301 .get(&server_id)
1302 .map(|s| s.instruction.clone())
1303 }
1304
1305 pub async fn set_instruction(&self, server_id: ServerId, instruction: String) {
1307 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
1308 server.instruction = instruction;
1309 }
1310 }
1311
1312 pub async fn get_memory(&self, server_id: ServerId) -> Option<String> {
1314 self.inner
1315 .read()
1316 .await
1317 .servers
1318 .get(&server_id)
1319 .map(|s| s.memory.clone())
1320 }
1321
1322 pub async fn set_memory(&self, server_id: ServerId, memory: String) {
1324 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
1325 server.memory = memory;
1326 }
1327 }
1328
1329 pub async fn append_memory(&self, server_id: ServerId, text: String) {
1331 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
1332 if !server.memory.is_empty() {
1333 server.memory.push('\n');
1334 }
1335 server.memory.push_str(&text);
1336 }
1337 }
1338
1339 pub async fn add_server_feedback(
1344 &self,
1345 server_id: ServerId,
1346 feedback: serde_json::Value,
1347 ) -> anyhow::Result<()> {
1348 let mut inner = self.inner.write().await;
1349 if let Some(server) = inner.servers.get_mut(&server_id) {
1350 if server.feedback_instructions.is_some() {
1352 server.feedback_buffer.push(feedback);
1353 Ok(())
1354 } else {
1355 anyhow::bail!(
1356 "Server {} has no feedback_instructions configured",
1357 server_id
1358 )
1359 }
1360 } else {
1361 anyhow::bail!("Server {} not found", server_id)
1362 }
1363 }
1364
1365 pub async fn take_due_feedback(&self, debounce: std::time::Duration) -> Vec<DueFeedback> {
1376 let now = crate::utils::clock::Instant::now();
1377 let mut due = Vec::new();
1378 let mut inner = self.inner.write().await;
1379
1380 for (id, server) in inner.servers.iter_mut() {
1381 let Some(instructions) = server.feedback_instructions.clone() else {
1382 continue;
1383 };
1384 if server.feedback_buffer.is_empty() {
1385 continue;
1386 }
1387 if let Some(last) = server.last_feedback_processed {
1388 if now.duration_since(last) < debounce {
1389 continue;
1390 }
1391 }
1392 server.last_feedback_processed = Some(now);
1393 due.push(DueFeedback {
1394 server_id: Some(*id),
1395 client_id: None,
1396 instructions,
1397 instruction: server.instruction.clone(),
1398 memory: server.memory.clone(),
1399 entries: std::mem::take(&mut server.feedback_buffer),
1400 });
1401 }
1402
1403 for (id, client) in inner.clients.iter_mut() {
1404 let Some(instructions) = client.feedback_instructions.clone() else {
1405 continue;
1406 };
1407 if client.feedback_buffer.is_empty() {
1408 continue;
1409 }
1410 if let Some(last) = client.last_feedback_processed {
1411 if now.duration_since(last) < debounce {
1412 continue;
1413 }
1414 }
1415 client.last_feedback_processed = Some(now);
1416 due.push(DueFeedback {
1417 server_id: None,
1418 client_id: Some(*id),
1419 instructions,
1420 instruction: client.instruction.clone(),
1421 memory: client.memory.clone(),
1422 entries: std::mem::take(&mut client.feedback_buffer),
1423 });
1424 }
1425
1426 due
1427 }
1428
1429 pub async fn add_client_feedback(
1434 &self,
1435 client_id: super::ClientId,
1436 feedback: serde_json::Value,
1437 ) -> anyhow::Result<()> {
1438 let mut inner = self.inner.write().await;
1439 if let Some(client) = inner.clients.get_mut(&client_id) {
1440 if client.feedback_instructions.is_some() {
1442 client.feedback_buffer.push(feedback);
1443 Ok(())
1444 } else {
1445 anyhow::bail!(
1446 "Client {} has no feedback_instructions configured",
1447 client_id
1448 )
1449 }
1450 } else {
1451 anyhow::bail!("Client {} not found", client_id)
1452 }
1453 }
1454
1455 pub fn get_first_server_id_sync(&self) -> Option<ServerId> {
1460 self.inner.try_read().ok()?.servers.keys().next().copied()
1462 }
1463
1464 pub async fn get_ollama_model(&self) -> Option<String> {
1466 self.inner.read().await.ollama_model.clone()
1467 }
1468
1469 pub async fn set_ollama_model(&self, model: Option<String>) {
1471 self.inner.write().await.ollama_model = model;
1472 }
1473
1474 pub async fn get_scripting_env(&self) -> crate::scripting::ScriptingEnvironment {
1476 self.inner.read().await.scripting_env.clone()
1477 }
1478
1479 pub async fn set_scripting_env(&self, env: crate::scripting::ScriptingEnvironment) {
1484 self.inner.write().await.scripting_env = env;
1485 }
1486
1487 pub async fn get_selected_scripting_mode(&self) -> ScriptingMode {
1489 self.inner.read().await.selected_scripting_mode
1490 }
1491
1492 pub async fn set_selected_scripting_mode(&self, mode: ScriptingMode) {
1494 self.inner.write().await.selected_scripting_mode = mode;
1495 }
1496
1497 pub async fn cycle_scripting_mode(&self) -> (ScriptingMode, bool) {
1500 let mut inner = self.inner.write().await;
1501 let current = inner.selected_scripting_mode;
1502
1503 let next = match current {
1506 ScriptingMode::On
1507 | ScriptingMode::Python
1508 | ScriptingMode::JavaScript
1509 | ScriptingMode::Go
1510 | ScriptingMode::Perl => ScriptingMode::Off,
1511 ScriptingMode::Off => ScriptingMode::On,
1512 };
1513
1514 inner.selected_scripting_mode = next;
1515 (next, true)
1516 }
1517
1518 pub async fn is_server_docs_read(&self) -> bool {
1520 !self
1521 .inner
1522 .read()
1523 .await
1524 .documented_server_protocols
1525 .is_empty()
1526 }
1527
1528 pub async fn is_client_docs_read(&self) -> bool {
1530 !self
1531 .inner
1532 .read()
1533 .await
1534 .documented_client_protocols
1535 .is_empty()
1536 }
1537
1538 pub async fn mark_server_protocols_documented(&self, protocols: &[String]) {
1540 let mut inner = self.inner.write().await;
1541 for protocol in protocols {
1542 if crate::llm::conversation_state::ConversationState::valid_documented_protocol(
1543 protocol,
1544 ) && inner.documented_server_protocols.len()
1545 < crate::llm::conversation_state::ConversationState::MAX_DOCUMENTED_PROTOCOLS
1546 {
1547 inner.documented_server_protocols.insert(protocol.clone());
1548 }
1549 }
1550 }
1551
1552 pub async fn mark_client_protocols_documented(&self, protocols: &[String]) {
1554 let mut inner = self.inner.write().await;
1555 for protocol in protocols {
1556 if crate::llm::conversation_state::ConversationState::valid_documented_protocol(
1557 protocol,
1558 ) && inner.documented_client_protocols.len()
1559 < crate::llm::conversation_state::ConversationState::MAX_DOCUMENTED_PROTOCOLS
1560 {
1561 inner.documented_client_protocols.insert(protocol.clone());
1562 }
1563 }
1564 }
1565
1566 pub async fn get_documented_server_protocols(&self) -> std::collections::HashSet<String> {
1568 self.inner.read().await.documented_server_protocols.clone()
1569 }
1570
1571 pub async fn get_documented_client_protocols(&self) -> std::collections::HashSet<String> {
1573 self.inner.read().await.documented_client_protocols.clone()
1574 }
1575
1576 pub async fn get_event_handler_mode(&self) -> EventHandlerMode {
1578 self.inner.read().await.event_handler_mode
1579 }
1580
1581 pub async fn set_event_handler_mode(&self, mode: EventHandlerMode) {
1583 self.inner.write().await.event_handler_mode = mode;
1584 }
1585
1586 pub async fn cycle_event_handler_mode(&self) -> EventHandlerMode {
1589 let mut inner = self.inner.write().await;
1590 inner.event_handler_mode = match inner.event_handler_mode {
1591 EventHandlerMode::Any => EventHandlerMode::Script,
1592 EventHandlerMode::Script => EventHandlerMode::Static,
1593 EventHandlerMode::Static => EventHandlerMode::Llm,
1594 EventHandlerMode::Llm => EventHandlerMode::Any,
1595 };
1596 inner.event_handler_mode
1597 }
1598
1599 pub async fn get_web_search_mode(&self) -> WebSearchMode {
1601 self.inner.read().await.web_search_mode
1602 }
1603
1604 pub async fn set_web_search_mode(&self, mode: WebSearchMode) {
1606 self.inner.write().await.web_search_mode = mode;
1607 }
1608
1609 pub async fn cycle_web_search_mode(&self) -> WebSearchMode {
1611 let mut inner = self.inner.write().await;
1612 inner.web_search_mode = match inner.web_search_mode {
1613 WebSearchMode::On => WebSearchMode::Ask,
1614 WebSearchMode::Ask => WebSearchMode::Off,
1615 WebSearchMode::Off => WebSearchMode::On,
1616 };
1617 inner.web_search_mode
1618 }
1619
1620 pub async fn set_web_approval_channel(&self, tx: mpsc::UnboundedSender<WebApprovalRequest>) {
1622 self.inner.write().await.web_approval_tx = Some(tx);
1623 }
1624
1625 pub async fn get_web_approval_channel(
1627 &self,
1628 ) -> Option<mpsc::UnboundedSender<WebApprovalRequest>> {
1629 self.inner.read().await.web_approval_tx.clone()
1630 }
1631
1632 pub async fn get_include_disabled_protocols(&self) -> bool {
1634 self.inner.read().await.include_disabled_protocols
1635 }
1636
1637 pub async fn get_min_stability(&self) -> Option<crate::protocol::metadata::DevelopmentState> {
1640 self.inner.read().await.min_stability
1641 }
1642
1643 pub async fn set_min_stability(
1646 &self,
1647 min: Option<crate::protocol::metadata::DevelopmentState>,
1648 ) {
1649 self.inner.write().await.min_stability = min;
1650 }
1651
1652 pub async fn get_ollama_url(&self) -> String {
1654 self.inner.read().await.ollama_url.clone()
1655 }
1656
1657 pub async fn set_llm_client(&self, client: crate::llm::OllamaClient) {
1659 self.inner.write().await.llm_client = Some(client);
1660 }
1661
1662 pub async fn get_llm_client(&self) -> Option<crate::llm::OllamaClient> {
1664 self.inner.read().await.llm_client.clone()
1665 }
1666
1667 pub async fn get_rate_limiter(&self) -> crate::llm::RateLimiter {
1669 self.inner.read().await.rate_limiter.clone()
1670 }
1671
1672 pub async fn configure_rate_limiter(
1674 &self,
1675 config: crate::llm::RateLimiterConfig,
1676 ) -> anyhow::Result<()> {
1677 let rate_limiter = self.inner.read().await.rate_limiter.clone();
1678 rate_limiter.update_config(config).await
1679 }
1680
1681 pub async fn get_instance_id(&self) -> String {
1683 self.inner.read().await.instance_id.clone()
1684 }
1685
1686 pub async fn get_system_capabilities(&self) -> crate::privilege::SystemCapabilities {
1688 self.inner.read().await.system_capabilities.clone()
1689 }
1690
1691 pub async fn get_next_unified_id(&self) -> u32 {
1694 let mut inner = self.inner.write().await;
1695 let id = inner.next_unified_id;
1696 inner.next_unified_id += 1;
1697 id
1698 }
1699
1700 pub async fn set_event_handler_config(
1702 &self,
1703 server_id: ServerId,
1704 config: Option<crate::scripting::EventHandlerConfig>,
1705 ) {
1706 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
1707 server.event_handler_config = config;
1708 }
1709 }
1710
1711 pub async fn get_event_handler_config(
1713 &self,
1714 server_id: ServerId,
1715 ) -> Option<crate::scripting::EventHandlerConfig> {
1716 self.inner
1717 .read()
1718 .await
1719 .servers
1720 .get(&server_id)
1721 .and_then(|s| s.event_handler_config.clone())
1722 }
1723
1724 pub async fn get_summary(&self) -> String {
1726 let inner = self.inner.read().await;
1727 let total_connections: usize = inner.servers.values().map(|s| s.connections.len()).sum();
1728 format!(
1729 "Mode: {}, Servers: {}, Clients: {}, Total Connections: {}",
1730 inner.mode,
1731 inner.servers.len(),
1732 inner.clients.len(),
1733 total_connections
1734 )
1735 }
1736
1737 pub async fn get_protocol_name(&self, server_id: ServerId) -> Option<String> {
1739 self.inner
1740 .read()
1741 .await
1742 .servers
1743 .get(&server_id)
1744 .map(|s| s.protocol_name.clone())
1745 }
1746
1747 pub async fn cleanup_old_connections(&self, max_age_secs: u64) {
1760 let mut inner = self.inner.write().await;
1761 for server in inner.servers.values_mut() {
1762 let connectionless = crate::protocol::server_registry::registry()
1763 .resolve(&server.protocol_name)
1764 .map(|p| p.metadata().connectionless)
1765 .unwrap_or(false);
1766 if connectionless {
1767 server.cleanup_old_connections(max_age_secs);
1768 }
1769 }
1770 }
1771
1772 pub async fn cleanup_closed_connections(&self, max_age_secs: u64) {
1774 use super::server::ConnectionStatus;
1775 let now = crate::utils::clock::Instant::now();
1776
1777 let mut inner = self.inner.write().await;
1778 for server in inner.servers.values_mut() {
1779 let to_remove: Vec<crate::server::connection::ConnectionId> = server
1780 .connections
1781 .iter()
1782 .filter(|(_, conn)| {
1783 conn.status == ConnectionStatus::Closed
1784 && now.duration_since(conn.status_changed_at).as_secs() >= max_age_secs
1785 })
1786 .map(|(id, _)| *id)
1787 .collect();
1788
1789 for id in to_remove {
1790 server.remove_connection(id);
1791 }
1792 }
1793 }
1794
1795 pub async fn cleanup_old_servers(&self, max_age_secs: u64) {
1806 use super::server::ServerStatus;
1807 let now = crate::utils::clock::Instant::now();
1808
1809 let mut inner = self.inner.write().await;
1810 let to_remove: Vec<ServerId> = inner
1811 .servers
1812 .iter()
1813 .filter(|(_, server)| {
1814 let age = now.duration_since(server.status_changed_at).as_secs();
1815 matches!(server.status, ServerStatus::Stopped) && age >= max_age_secs
1818 })
1819 .map(|(id, _)| *id)
1820 .collect();
1821
1822 for id in to_remove {
1823 inner.servers.remove(&id);
1824 drop(inner.teardown_server(id));
1826 }
1827
1828 if inner.servers.is_empty() && inner.clients.is_empty() {
1830 inner.mode = Mode::Idle;
1831 }
1832 }
1833
1834 pub async fn add_connection_to_server(
1836 &self,
1837 server_id: ServerId,
1838 connection: super::server::ConnectionState,
1839 ) {
1840 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
1841 server.add_connection(connection);
1842 }
1843 }
1844
1845 pub async fn close_connection_on_server(
1847 &self,
1848 server_id: ServerId,
1849 connection_id: crate::server::connection::ConnectionId,
1850 ) {
1851 use super::server::ConnectionStatus;
1852 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
1853 if let Some(conn) = server.get_connection_mut(connection_id) {
1854 conn.status = ConnectionStatus::Closed;
1855 conn.status_changed_at = crate::utils::clock::Instant::now();
1856 }
1857 }
1858
1859 self.remove_peer_handle(server_id, connection_id.as_u32())
1860 .await;
1861
1862 self.cleanup_connection_tasks(server_id, connection_id)
1864 .await;
1865 }
1866
1867 pub async fn remove_connection_from_server(
1869 &self,
1870 server_id: ServerId,
1871 connection_id: crate::server::connection::ConnectionId,
1872 ) {
1873 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
1874 server.remove_connection(connection_id);
1875 }
1876
1877 self.remove_peer_handle(server_id, connection_id.as_u32())
1878 .await;
1879
1880 self.cleanup_connection_tasks(server_id, connection_id)
1884 .await;
1885 }
1886
1887 pub async fn get_recent_connections(
1890 &self,
1891 server_id: ServerId,
1892 ) -> Vec<super::server::ClosedConnectionSummary> {
1893 self.inner
1894 .read()
1895 .await
1896 .servers
1897 .get(&server_id)
1898 .map(|server| server.recent_connections.iter().cloned().collect())
1899 .unwrap_or_default()
1900 }
1901
1902 #[cfg(unix)]
1906 pub async fn get_proxy_filter_config(
1907 &self,
1908 server_id: ServerId,
1909 ) -> Option<crate::server::proxy::filter::ProxyFilterConfig> {
1910 self.inner
1911 .read()
1912 .await
1913 .servers
1914 .get(&server_id)
1915 .and_then(|s| {
1916 s.protocol_data
1917 .get("proxy_filter_config")
1918 .and_then(|v| serde_json::from_value(v.clone()).ok())
1919 })
1920 }
1921
1922 #[cfg(unix)]
1924 pub async fn set_proxy_filter_config(
1925 &self,
1926 server_id: ServerId,
1927 config: crate::server::proxy::filter::ProxyFilterConfig,
1928 ) {
1929 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
1930 if let Ok(value) = serde_json::to_value(&config) {
1931 server.set_protocol_field("proxy_filter_config".to_string(), value);
1932 }
1933 }
1934 }
1935
1936 #[cfg(unix)]
1938 pub async fn update_proxy_filter_config(
1939 &self,
1940 server_id: ServerId,
1941 update_fn: impl FnOnce(&mut crate::server::proxy::filter::ProxyFilterConfig),
1942 ) {
1943 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
1944 let mut config = server
1945 .protocol_data
1946 .get("proxy_filter_config")
1947 .and_then(|v| serde_json::from_value(v.clone()).ok())
1948 .unwrap_or_else(crate::server::proxy::filter::ProxyFilterConfig::default);
1949
1950 update_fn(&mut config);
1951
1952 if let Ok(value) = serde_json::to_value(&config) {
1953 server.set_protocol_field("proxy_filter_config".to_string(), value);
1954 }
1955 }
1956 }
1957
1958 #[cfg(not(target_arch = "wasm32"))]
1960 pub async fn get_socks5_filter_config(
1961 &self,
1962 server_id: ServerId,
1963 ) -> Option<crate::server::socks5::filter::Socks5FilterConfig> {
1964 self.inner
1965 .read()
1966 .await
1967 .servers
1968 .get(&server_id)
1969 .and_then(|s| {
1970 s.protocol_data
1971 .get("socks5_filter_config")
1972 .and_then(|v| serde_json::from_value(v.clone()).ok())
1973 })
1974 }
1975
1976 #[cfg(not(target_arch = "wasm32"))]
1978 pub async fn set_socks5_filter_config(
1979 &self,
1980 server_id: ServerId,
1981 config: crate::server::socks5::filter::Socks5FilterConfig,
1982 ) {
1983 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
1984 if let Ok(value) = serde_json::to_value(&config) {
1985 server.set_protocol_field("socks5_filter_config".to_string(), value);
1986 }
1987 }
1988 }
1989
1990 #[cfg(not(target_arch = "wasm32"))]
1992 pub async fn update_socks5_target(
1993 &self,
1994 server_id: ServerId,
1995 connection_id: ConnectionId,
1996 target_addr: Option<String>,
1997 username: Option<String>,
1998 ) {
1999 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
2000 if let Some(conn) = server.connections.get_mut(&connection_id) {
2001 if let Some(obj) = conn.protocol_info.data.as_object_mut() {
2003 if let Some(target) = target_addr {
2004 obj.insert("target_addr".to_string(), serde_json::Value::String(target));
2005 } else {
2006 obj.insert("target_addr".to_string(), serde_json::Value::Null);
2007 }
2008 if let Some(user) = username {
2009 obj.insert("username".to_string(), serde_json::Value::String(user));
2010 } else {
2011 obj.insert("username".to_string(), serde_json::Value::Null);
2012 }
2013 }
2014 }
2015 }
2016 }
2017
2018 #[cfg(not(target_arch = "wasm32"))]
2022 pub async fn update_imap_session_state(
2023 &self,
2024 server_id: ServerId,
2025 connection_id: ConnectionId,
2026 session_state: crate::state::server::ImapSessionState,
2027 ) {
2028 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
2029 if let Some(conn) = server.connections.get_mut(&connection_id) {
2030 if let Some(obj) = conn.protocol_info.data.as_object_mut() {
2031 obj.insert(
2032 "session_state".to_string(),
2033 serde_json::to_value(&session_state).unwrap_or(serde_json::Value::Null),
2034 );
2035 }
2036 }
2037 }
2038 }
2039
2040 #[cfg(not(target_arch = "wasm32"))]
2042 pub async fn update_imap_connection_state(
2043 &self,
2044 server_id: ServerId,
2045 connection_id: ConnectionId,
2046 session_state: Option<crate::state::server::ImapSessionState>,
2047 authenticated_user: Option<Option<String>>,
2048 selected_mailbox: Option<Option<String>>,
2049 mailbox_read_only: Option<bool>,
2050 ) {
2051 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
2052 if let Some(conn) = server.connections.get_mut(&connection_id) {
2053 if let Some(obj) = conn.protocol_info.data.as_object_mut() {
2054 if let Some(s) = session_state {
2055 obj.insert(
2056 "session_state".to_string(),
2057 serde_json::to_value(&s).unwrap_or(serde_json::Value::Null),
2058 );
2059 }
2060 if let Some(u) = authenticated_user {
2061 obj.insert(
2062 "authenticated_user".to_string(),
2063 serde_json::to_value(&u).unwrap_or(serde_json::Value::Null),
2064 );
2065 }
2066 if let Some(m) = selected_mailbox {
2067 obj.insert(
2068 "selected_mailbox".to_string(),
2069 serde_json::to_value(&m).unwrap_or(serde_json::Value::Null),
2070 );
2071 }
2072 if let Some(r) = mailbox_read_only {
2073 obj.insert("mailbox_read_only".to_string(), serde_json::Value::Bool(r));
2074 }
2075 }
2076 }
2077 }
2078 }
2079
2080 #[cfg(not(target_arch = "wasm32"))]
2082 pub async fn get_imap_connection_state(
2083 &self,
2084 server_id: ServerId,
2085 connection_id: ConnectionId,
2086 ) -> Option<(
2087 crate::state::server::ImapSessionState,
2088 Option<String>,
2089 Option<String>,
2090 )> {
2091 let inner = self.inner.read().await;
2092 if let Some(server) = inner.servers.get(&server_id) {
2093 if let Some(conn) = server.connections.get(&connection_id) {
2094 let session_state = conn
2095 .protocol_info
2096 .data
2097 .get("session_state")
2098 .and_then(|v| serde_json::from_value(v.clone()).ok())?;
2099 let authenticated_user = conn
2100 .protocol_info
2101 .data
2102 .get("authenticated_user")
2103 .and_then(|v| serde_json::from_value(v.clone()).ok());
2104 let selected_mailbox = conn
2105 .protocol_info
2106 .data
2107 .get("selected_mailbox")
2108 .and_then(|v| serde_json::from_value(v.clone()).ok());
2109 return Some((
2110 session_state,
2111 authenticated_user.flatten(),
2112 selected_mailbox.flatten(),
2113 ));
2114 }
2115 }
2116 None
2117 }
2118
2119 #[cfg(not(target_arch = "wasm32"))]
2121 pub async fn update_imap_protocol_state(
2122 &self,
2123 server_id: ServerId,
2124 connection_id: ConnectionId,
2125 protocol_state: crate::state::server::ProtocolState,
2126 ) {
2127 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
2128 if let Some(conn) = server.connections.get_mut(&connection_id) {
2129 if let Some(obj) = conn.protocol_info.data.as_object_mut() {
2130 obj.insert(
2131 "state".to_string(),
2132 serde_json::to_value(&protocol_state).unwrap_or(serde_json::Value::Null),
2133 );
2134 }
2135 }
2136 }
2137 }
2138
2139 pub async fn update_connection_stats(
2141 &self,
2142 server_id: ServerId,
2143 connection_id: ConnectionId,
2144 bytes_received: Option<u64>,
2145 bytes_sent: Option<u64>,
2146 packets_received: Option<u64>,
2147 packets_sent: Option<u64>,
2148 ) {
2149 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
2150 if let Some(conn) = server.connections.get_mut(&connection_id) {
2151 if let Some(br) = bytes_received {
2152 conn.bytes_received += br;
2153 }
2154 if let Some(bs) = bytes_sent {
2155 conn.bytes_sent += bs;
2156 }
2157 if let Some(pr) = packets_received {
2158 conn.packets_received += pr;
2159 }
2160 if let Some(ps) = packets_sent {
2161 conn.packets_sent += ps;
2162 }
2163 conn.last_activity = crate::utils::clock::Instant::now();
2164 }
2165 }
2166 }
2167
2168 pub async fn update_connection_status(
2170 &self,
2171 server_id: ServerId,
2172 connection_id: ConnectionId,
2173 status: crate::state::server::ConnectionStatus,
2174 ) {
2175 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
2176 if let Some(conn) = server.connections.get_mut(&connection_id) {
2177 conn.status = status;
2178 conn.status_changed_at = crate::utils::clock::Instant::now();
2179 }
2180 }
2181 }
2182
2183 pub async fn update_vnc_connection_auth(
2185 &self,
2186 server_id: ServerId,
2187 connection_id: ConnectionId,
2188 authenticated: bool,
2189 username: Option<String>,
2190 ) {
2191 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
2192 if let Some(conn) = server.connections.get_mut(&connection_id) {
2193 if let Some(obj) = conn.protocol_info.data.as_object_mut() {
2194 obj.insert(
2195 "authenticated".to_string(),
2196 serde_json::Value::Bool(authenticated),
2197 );
2198 obj.insert(
2199 "username".to_string(),
2200 serde_json::to_value(&username).unwrap_or(serde_json::Value::Null),
2201 );
2202 }
2203 }
2204 }
2205 }
2206
2207 pub async fn update_bitcoin_connection_info(
2209 &self,
2210 server_id: ServerId,
2211 connection_id: ConnectionId,
2212 last_message_type: String,
2213 ) {
2214 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
2215 if let Some(conn) = server.connections.get_mut(&connection_id) {
2216 if let Some(obj) = conn.protocol_info.data.as_object_mut() {
2217 obj.insert(
2218 "last_message_type".to_string(),
2219 serde_json::Value::String(last_message_type.clone()),
2220 );
2221 if last_message_type == "verack" {
2223 obj.insert(
2224 "handshake_complete".to_string(),
2225 serde_json::Value::Bool(true),
2226 );
2227 }
2228 }
2229 }
2230 }
2231 }
2232
2233 pub async fn update_tftp_connection_block(
2235 &self,
2236 server_id: ServerId,
2237 connection_id: ConnectionId,
2238 block_number: u16,
2239 block_bytes: usize,
2240 ) {
2241 if let Some(server) = self.inner.write().await.servers.get_mut(&server_id) {
2242 if let Some(conn) = server.connections.get_mut(&connection_id) {
2243 if let Some(obj) = conn.protocol_info.data.as_object_mut() {
2244 obj.insert(
2245 "current_block".to_string(),
2246 serde_json::Value::Number(block_number.into()),
2247 );
2248 let current_total =
2250 obj.get("total_bytes").and_then(|v| v.as_u64()).unwrap_or(0);
2251 obj.insert(
2252 "total_bytes".to_string(),
2253 serde_json::Value::Number((current_total + block_bytes as u64).into()),
2254 );
2255 }
2256 }
2257 }
2258 }
2259
2260 pub async fn get_vnc_write_half(
2265 &self,
2266 _connection_id: ConnectionId,
2267 ) -> Option<std::sync::Arc<tokio::sync::Mutex<tokio::io::WriteHalf<tokio::net::TcpStream>>>>
2268 {
2269 None
2272 }
2273
2274 pub async fn add_client(&self, mut client: ClientInstance) -> ClientId {
2278 let mut inner = self.inner.write().await;
2279 let id = ClientId::new(inner.next_unified_id);
2280 inner.next_unified_id += 1;
2281 client.id = id;
2282 inner.clients.insert(id, client);
2283
2284 if inner.mode == Mode::Idle {
2286 inner.mode = Mode::Client;
2287 }
2288
2289 id
2290 }
2291
2292 pub async fn disconnect_client(&self, id: ClientId) -> bool {
2304 {
2305 let mut inner = self.inner.write().await;
2306 if !inner.clients.contains_key(&id) {
2307 return false;
2308 }
2309 for handle in inner.client_tasks.remove(&id).unwrap_or_default() {
2310 handle.abort();
2311 }
2312 inner.client_handles.remove(&id);
2313 }
2314 self.update_client_status(id, super::client::ClientStatus::Disconnected)
2315 .await;
2316 true
2317 }
2318
2319 pub async fn remove_client(&self, id: ClientId) -> Option<ClientInstance> {
2320 let client = {
2321 let mut inner = self.inner.write().await;
2322 let client = inner.clients.remove(&id);
2323 inner.client_llm_calls.remove(&id);
2324 inner.client_handles.remove(&id);
2326
2327 for handle in inner.client_tasks.remove(&id).unwrap_or_default() {
2328 handle.abort();
2329 }
2330
2331 let task_ids: Vec<_> = inner
2332 .tasks
2333 .iter()
2334 .filter_map(|(task_id, task)| {
2335 matches!(task.scope, crate::state::task::TaskScope::Client(cid) if cid == id)
2336 .then_some(*task_id)
2337 })
2338 .collect();
2339 for task_id in task_ids {
2340 inner.remove_scheduled_task(task_id);
2341 }
2342
2343 if inner.clients.is_empty() && inner.servers.is_empty() {
2345 inner.mode = Mode::Idle;
2346 }
2347
2348 client
2349 };
2350 crate::scripting::ResidentScriptManager::shutdown_client(id.as_u32()).await;
2353
2354 client
2355 }
2356
2357 pub fn register_client_task(
2369 &self,
2370 id: ClientId,
2371 handle: tokio::task::JoinHandle<()>,
2372 ) -> impl std::future::Future<Output = ()> + Send + '_ {
2373 let pending = PendingTaskRegistration(Some(handle));
2374 async move {
2375 let mut inner = self.inner.write().await;
2376 if !inner.clients.contains_key(&id) {
2377 return;
2378 }
2379 let tasks = inner.client_tasks.entry(id).or_default();
2380 tasks.retain(|h| !h.is_finished());
2382 tasks.push(pending.into_handle());
2383 }
2384 }
2385
2386 pub async fn client_task_count(&self, id: ClientId) -> usize {
2388 self.inner
2389 .read()
2390 .await
2391 .client_tasks
2392 .get(&id)
2393 .map(|t| t.len())
2394 .unwrap_or(0)
2395 }
2396
2397 pub async fn try_consume_client_llm_call(&self, id: ClientId) -> Result<u32, String> {
2409 let mut inner = self.inner.write().await;
2410 let limit = inner.client_llm_call_limit;
2411 if !inner.clients.contains_key(&id) {
2412 return Ok(0);
2415 }
2416 let used = inner.client_llm_calls.entry(id).or_insert(0);
2417
2418 if limit > 0 && *used >= limit {
2419 return Err(format!(
2420 "client {} exceeded its LLM call budget ({} calls). \
2421 The client's event loop is not converging — it keeps invoking the model \
2422 instead of terminating. Refusing further LLM calls; stop the client or \
2423 raise NETGET_CLIENT_LLM_CALL_LIMIT (0 = unlimited).",
2424 id, limit
2425 ));
2426 }
2427
2428 *used += 1;
2429 Ok(*used)
2430 }
2431
2432 pub async fn get_client_llm_calls(&self, id: ClientId) -> u32 {
2434 self.inner
2435 .read()
2436 .await
2437 .client_llm_calls
2438 .get(&id)
2439 .copied()
2440 .unwrap_or(0)
2441 }
2442
2443 pub async fn get_client_llm_call_limit(&self) -> u32 {
2445 self.inner.read().await.client_llm_call_limit
2446 }
2447
2448 pub async fn set_client_llm_call_limit(&self, limit: u32) {
2450 self.inner.write().await.client_llm_call_limit = limit;
2451 }
2452
2453 pub async fn get_client(&self, id: ClientId) -> Option<ClientInstance> {
2455 self.inner.read().await.clients.get(&id).cloned()
2456 }
2457
2458 pub async fn get_all_client_ids(&self) -> Vec<ClientId> {
2460 self.inner.read().await.clients.keys().copied().collect()
2461 }
2462
2463 pub async fn get_all_clients(&self) -> Vec<ClientInstance> {
2465 self.inner.read().await.clients.values().cloned().collect()
2466 }
2467
2468 pub async fn update_client_status(&self, id: ClientId, status: super::client::ClientStatus) {
2470 if let Some(client) = self.inner.write().await.clients.get_mut(&id) {
2471 client.record_status_transition(&status);
2472 client.status = status;
2473 client.status_changed_at = crate::utils::clock::Instant::now();
2474 }
2475 }
2476
2477 pub async fn get_instruction_for_client(&self, client_id: ClientId) -> Option<String> {
2479 self.inner
2480 .read()
2481 .await
2482 .clients
2483 .get(&client_id)
2484 .map(|c| c.instruction.clone())
2485 }
2486
2487 pub async fn set_instruction_for_client(&self, client_id: ClientId, instruction: String) {
2489 if let Some(client) = self.inner.write().await.clients.get_mut(&client_id) {
2490 client.instruction = instruction;
2491 }
2492 }
2493
2494 pub async fn get_memory_for_client(&self, client_id: ClientId) -> Option<String> {
2496 self.inner
2497 .read()
2498 .await
2499 .clients
2500 .get(&client_id)
2501 .map(|c| c.memory.clone())
2502 }
2503
2504 pub async fn set_memory_for_client(&self, client_id: ClientId, memory: String) {
2506 if let Some(client) = self.inner.write().await.clients.get_mut(&client_id) {
2507 client.memory = memory;
2508 }
2509 }
2510
2511 pub async fn append_memory_for_client(&self, client_id: ClientId, text: String) {
2513 if let Some(client) = self.inner.write().await.clients.get_mut(&client_id) {
2514 if !client.memory.is_empty() {
2515 client.memory.push('\n');
2516 }
2517 client.memory.push_str(&text);
2518 }
2519 }
2520
2521 pub async fn get_protocol_name_for_client(&self, client_id: ClientId) -> Option<String> {
2523 self.inner
2524 .read()
2525 .await
2526 .clients
2527 .get(&client_id)
2528 .map(|c| c.protocol_name.clone())
2529 }
2530
2531 pub async fn with_client_mut<F, R>(&self, client_id: ClientId, f: F) -> Option<R>
2533 where
2534 F: FnOnce(&mut ClientInstance) -> R,
2535 {
2536 self.inner.write().await.clients.get_mut(&client_id).map(f)
2537 }
2538
2539 pub async fn set_client_event_handler_config(
2541 &self,
2542 client_id: ClientId,
2543 config: Option<crate::scripting::EventHandlerConfig>,
2544 ) {
2545 if let Some(client) = self.inner.write().await.clients.get_mut(&client_id) {
2546 client.event_handler_config = config;
2547 }
2548 }
2549
2550 pub async fn get_client_event_handler_config(
2552 &self,
2553 client_id: ClientId,
2554 ) -> Option<crate::scripting::EventHandlerConfig> {
2555 self.inner
2556 .read()
2557 .await
2558 .clients
2559 .get(&client_id)
2560 .and_then(|c| c.event_handler_config.clone())
2561 }
2562
2563 pub async fn cleanup_old_clients(&self, max_age_secs: u64) {
2565 use super::client::ClientStatus;
2566 let now = crate::utils::clock::Instant::now();
2567
2568 let mut inner = self.inner.write().await;
2569 let to_remove: Vec<ClientId> = inner
2570 .clients
2571 .iter()
2572 .filter(|(_, client)| {
2573 let age = now.duration_since(client.status_changed_at).as_secs();
2574 matches!(client.status, ClientStatus::Disconnected) && age >= max_age_secs
2576 })
2577 .map(|(id, _)| *id)
2578 .collect();
2579
2580 for id in to_remove {
2581 inner.clients.remove(&id);
2582 }
2583
2584 if inner.clients.is_empty() && inner.servers.is_empty() {
2586 inner.mode = Mode::Idle;
2587 }
2588 }
2589
2590 pub async fn add_easy_instance(&self, mut easy_instance: EasyInstance) -> EasyId {
2594 let mut inner = self.inner.write().await;
2595 let id = EasyId::new(inner.next_unified_id);
2596 inner.next_unified_id += 1;
2597 easy_instance.id = id;
2598 inner.easy_instances.insert(id, easy_instance);
2599 id
2600 }
2601
2602 pub async fn remove_easy_instance(&self, id: EasyId) -> Option<EasyInstance> {
2604 let mut inner = self.inner.write().await;
2605 let instance = inner.easy_instances.remove(&id);
2606
2607 if let Some(ref inst) = instance {
2609 if let Some(server_id) = inst.underlying_server_id {
2610 inner.server_to_easy.remove(&server_id);
2611 }
2612 if let Some(client_id) = inst.underlying_client_id {
2613 inner.client_to_easy.remove(&client_id);
2614 }
2615 }
2616
2617 instance
2618 }
2619
2620 pub async fn link_server_to_easy(&self, server_id: ServerId, easy_id: EasyId) {
2622 let mut inner = self.inner.write().await;
2623 inner.server_to_easy.insert(server_id, easy_id);
2624 if let Some(easy_instance) = inner.easy_instances.get_mut(&easy_id) {
2625 easy_instance.set_underlying_server(server_id);
2626 }
2627 }
2628
2629 pub async fn link_client_to_easy(&self, client_id: ClientId, easy_id: EasyId) {
2631 let mut inner = self.inner.write().await;
2632 inner.client_to_easy.insert(client_id, easy_id);
2633 if let Some(easy_instance) = inner.easy_instances.get_mut(&easy_id) {
2634 easy_instance.set_underlying_client(client_id);
2635 }
2636 }
2637
2638 pub async fn get_easy_for_server(&self, server_id: ServerId) -> Option<EasyId> {
2640 self.inner
2641 .read()
2642 .await
2643 .server_to_easy
2644 .get(&server_id)
2645 .copied()
2646 }
2647
2648 pub async fn get_easy_for_client(&self, client_id: ClientId) -> Option<EasyId> {
2650 self.inner
2651 .read()
2652 .await
2653 .client_to_easy
2654 .get(&client_id)
2655 .copied()
2656 }
2657
2658 pub async fn get_easy_instance(&self, id: EasyId) -> Option<EasyInstance> {
2660 self.inner.read().await.easy_instances.get(&id).map(|inst| {
2663 let mut cloned = EasyInstance::new(
2665 inst.id,
2666 inst.protocol_name.clone(),
2667 inst.underlying_protocol.clone(),
2668 inst.user_instruction.clone(),
2669 );
2670 cloned.underlying_server_id = inst.underlying_server_id;
2671 cloned.underlying_client_id = inst.underlying_client_id;
2672 cloned.status = inst.status.clone();
2673 cloned.created_at = inst.created_at;
2674 cloned.status_changed_at = inst.status_changed_at;
2675 cloned
2677 })
2678 }
2679
2680 pub async fn update_easy_status(&self, id: EasyId, status: EasyStatus) {
2682 if let Some(instance) = self.inner.write().await.easy_instances.get_mut(&id) {
2683 instance.set_status(status);
2684 }
2685 }
2686
2687 pub async fn get_all_easy_instances(&self) -> Vec<EasyInstance> {
2689 self.inner
2690 .read()
2691 .await
2692 .easy_instances
2693 .values()
2694 .map(|inst| {
2695 let mut cloned = EasyInstance::new(
2697 inst.id,
2698 inst.protocol_name.clone(),
2699 inst.underlying_protocol.clone(),
2700 inst.user_instruction.clone(),
2701 );
2702 cloned.underlying_server_id = inst.underlying_server_id;
2703 cloned.underlying_client_id = inst.underlying_client_id;
2704 cloned.status = inst.status.clone();
2705 cloned.created_at = inst.created_at;
2706 cloned.status_changed_at = inst.status_changed_at;
2707 cloned
2708 })
2709 .collect()
2710 }
2711
2712 pub async fn with_easy_mut<F, R>(&self, easy_id: EasyId, f: F) -> Option<R>
2714 where
2715 F: FnOnce(&mut EasyInstance) -> R,
2716 {
2717 self.inner
2718 .write()
2719 .await
2720 .easy_instances
2721 .get_mut(&easy_id)
2722 .map(f)
2723 }
2724
2725 pub async fn get_port(&self) -> Option<u16> {
2731 self.inner
2732 .read()
2733 .await
2734 .servers
2735 .values()
2736 .next()
2737 .map(|s| s.port)
2738 }
2739
2740 pub async fn get_first_protocol_name(&self) -> Option<String> {
2742 self.inner
2743 .read()
2744 .await
2745 .servers
2746 .values()
2747 .next()
2748 .map(|s| s.protocol_name.clone())
2749 }
2750
2751 pub async fn get_first_instruction(&self) -> String {
2753 self.inner
2754 .read()
2755 .await
2756 .servers
2757 .values()
2758 .next()
2759 .map(|s| s.instruction.clone())
2760 .unwrap_or_default()
2761 }
2762
2763 pub async fn get_first_memory(&self) -> String {
2765 self.inner
2766 .read()
2767 .await
2768 .servers
2769 .values()
2770 .next()
2771 .map(|s| s.memory.clone())
2772 .unwrap_or_default()
2773 }
2774
2775 pub async fn get_first_server_id(&self) -> Option<ServerId> {
2777 self.inner.read().await.servers.keys().next().copied()
2778 }
2779
2780 pub async fn get_first_client_id(&self) -> Option<ClientId> {
2782 self.inner.read().await.clients.keys().next().copied()
2783 }
2784
2785 pub async fn get_local_addr(&self) -> Option<std::net::SocketAddr> {
2787 self.inner
2788 .read()
2789 .await
2790 .servers
2791 .values()
2792 .next()
2793 .and_then(|s| s.local_addr)
2794 }
2795
2796 pub async fn with_server_mut<F, R>(&self, server_id: ServerId, f: F) -> Option<R>
2798 where
2799 F: FnOnce(&mut ServerInstance) -> R,
2800 {
2801 self.inner.write().await.servers.get_mut(&server_id).map(f)
2802 }
2803
2804 pub async fn add_task(&self, mut task: ScheduledTask) -> TaskId {
2808 let mut inner = self.inner.write().await;
2809 let id = TaskId::new(inner.next_task_id);
2810 inner.next_task_id += 1;
2811 task.id = id;
2814
2815 inner.task_names.insert(task.name.clone(), id);
2816 inner.tasks.insert(id, task);
2817 id
2818 }
2819
2820 pub async fn get_task(&self, id_or_name: &str) -> Option<ScheduledTask> {
2822 let inner = self.inner.read().await;
2823
2824 if let Some(id) = TaskId::from_string(id_or_name) {
2826 return inner.tasks.get(&id).cloned();
2827 }
2828
2829 if let Some(&id) = inner.task_names.get(id_or_name) {
2831 return inner.tasks.get(&id).cloned();
2832 }
2833
2834 None
2835 }
2836
2837 pub async fn remove_task(&self, id: TaskId) -> Option<ScheduledTask> {
2839 let mut inner = self.inner.write().await;
2840 inner.remove_scheduled_task(id)
2841 }
2842
2843 pub async fn spawn_scheduled_task<F>(&self, id: TaskId, future: F) -> bool
2846 where
2847 F: std::future::Future<Output = ()> + Send + 'static,
2848 {
2849 let mut inner = self.inner.write().await;
2850 if !inner
2851 .tasks
2852 .get(&id)
2853 .is_some_and(|task| task.status == crate::state::task::TaskStatus::Executing)
2854 {
2855 return false;
2856 }
2857 if inner
2858 .scheduled_executions
2859 .get(&id)
2860 .is_some_and(|handle| !handle.is_finished())
2861 {
2862 return false;
2863 }
2864 inner.scheduled_executions.insert(id, tokio::spawn(future));
2865 true
2866 }
2867
2868 pub async fn get_all_tasks(&self) -> Vec<ScheduledTask> {
2870 self.inner.read().await.tasks.values().cloned().collect()
2871 }
2872
2873 pub async fn claim_due_tasks(&self) -> Vec<ScheduledTask> {
2878 use crate::state::task::TaskStatus;
2879 let mut inner = self.inner.write().await;
2880 let now = crate::utils::clock::Instant::now();
2881 let active: std::collections::HashSet<_> = inner
2882 .scheduled_executions
2883 .iter()
2884 .filter_map(|(id, handle)| (!handle.is_finished()).then_some(*id))
2885 .collect();
2886 inner
2887 .scheduled_executions
2888 .retain(|_, handle| !handle.is_finished());
2889 inner
2890 .tasks
2891 .values_mut()
2892 .filter_map(|task| {
2893 if task.status == TaskStatus::Scheduled
2894 && task.next_execution <= now
2895 && !active.contains(&task.id)
2896 {
2897 task.status = TaskStatus::Executing;
2898 Some(task.clone())
2899 } else {
2900 None
2901 }
2902 })
2903 .collect()
2904 }
2905
2906 pub async fn get_server_tasks(&self, server_id: ServerId) -> Vec<ScheduledTask> {
2908 use crate::state::task::TaskScope;
2909
2910 self.inner
2911 .read()
2912 .await
2913 .tasks
2914 .values()
2915 .filter(|task| matches!(&task.scope, TaskScope::Server(sid) if *sid == server_id))
2916 .cloned()
2917 .collect()
2918 }
2919
2920 pub async fn get_connection_tasks(
2922 &self,
2923 server_id: ServerId,
2924 connection_id: crate::server::connection::ConnectionId,
2925 ) -> Vec<ScheduledTask> {
2926 use crate::state::task::TaskScope;
2927
2928 self.inner
2929 .read()
2930 .await
2931 .tasks
2932 .values()
2933 .filter(|task| {
2934 matches!(
2935 &task.scope,
2936 TaskScope::Connection(sid, cid) if *sid == server_id && *cid == connection_id
2937 )
2938 })
2939 .cloned()
2940 .collect()
2941 }
2942
2943 pub async fn update_task_status(&self, id: TaskId, status: crate::state::task::TaskStatus) {
2945 if let Some(task) = self.inner.write().await.tasks.get_mut(&id) {
2946 task.status = status;
2947 }
2948 }
2949
2950 pub async fn update_task_next_execution(&self, id: TaskId, next: crate::utils::clock::Instant) {
2952 if let Some(task) = self.inner.write().await.tasks.get_mut(&id) {
2953 task.next_execution = next;
2954 }
2955 }
2956
2957 pub async fn record_task_execution(
2959 &self,
2960 id: TaskId,
2961 result: &crate::state::task::TaskExecutionResult,
2962 ) {
2963 use crate::state::task::TaskType;
2964
2965 let mut inner = self.inner.write().await;
2966 if let Some(task) = inner.tasks.get_mut(&id) {
2967 if result.success {
2968 task.failure_count = 0;
2969 task.last_error = None;
2970 } else {
2971 task.failure_count = task.failure_count.saturating_add(1);
2972 task.last_error = result.error.clone();
2973 }
2974
2975 if let TaskType::Recurring {
2977 ref mut executions_count,
2978 ..
2979 } = task.task_type
2980 {
2981 *executions_count = executions_count.saturating_add(1);
2982 }
2983 }
2984 }
2985
2986 pub async fn cleanup_server_tasks(&self, server_id: ServerId) {
2996 let mut inner = self.inner.write().await;
2997 inner.drop_tasks_for_server(server_id);
2998 }
2999
3000 pub async fn cleanup_connection_tasks(
3002 &self,
3003 server_id: ServerId,
3004 connection_id: crate::server::connection::ConnectionId,
3005 ) {
3006 use crate::state::task::TaskScope;
3007
3008 let mut inner = self.inner.write().await;
3009
3010 let task_ids_to_remove: Vec<TaskId> = inner
3012 .tasks
3013 .iter()
3014 .filter(|(_, task)| {
3015 matches!(
3016 &task.scope,
3017 TaskScope::Connection(sid, cid) if *sid == server_id && *cid == connection_id
3018 )
3019 })
3020 .map(|(&id, _)| id)
3021 .collect();
3022
3023 for id in task_ids_to_remove {
3025 inner.remove_scheduled_task(id);
3026 }
3027 }
3028
3029 pub async fn cleanup_client_tasks(&self, client_id: ClientId) {
3031 use crate::state::task::TaskScope;
3032
3033 let mut inner = self.inner.write().await;
3034
3035 let task_ids_to_remove: Vec<TaskId> = inner
3037 .tasks
3038 .iter()
3039 .filter(|(_, task)| matches!(&task.scope, TaskScope::Client(cid) if *cid == client_id))
3040 .map(|(&id, _)| id)
3041 .collect();
3042
3043 for id in task_ids_to_remove {
3045 inner.remove_scheduled_task(id);
3046 }
3047 }
3048
3049 pub async fn register_conversation(
3053 &self,
3054 id: String,
3055 source: ConversationSource,
3056 details: String,
3057 ) {
3058 let info = ConversationInfo {
3059 id,
3060 source,
3061 details,
3062 start_time: crate::utils::clock::Instant::now(),
3063 end_time: None,
3064 };
3065
3066 self.inner.write().await.conversations.push(info);
3067 }
3068
3069 pub async fn end_conversation(&self, id: &str) {
3071 let mut inner = self.inner.write().await;
3072 if let Some(conv) = inner.conversations.iter_mut().find(|c| c.id == id) {
3073 conv.end_time = Some(crate::utils::clock::Instant::now());
3074 }
3075 }
3076
3077 pub async fn get_active_conversations(&self) -> Vec<ConversationInfo> {
3079 let inner = self.inner.read().await;
3080 let now = crate::utils::clock::Instant::now();
3081
3082 inner
3083 .conversations
3084 .iter()
3085 .filter(|conv| {
3086 conv.end_time.is_none() ||
3088 conv.end_time.map(|end| now.duration_since(end).as_secs() < 1).unwrap_or(false)
3090 })
3091 .cloned()
3092 .collect()
3093 }
3094
3095 pub async fn get_input_conversations(&self) -> Vec<ConversationInfo> {
3097 let all_convs = self.get_active_conversations().await;
3098 all_convs
3099 .into_iter()
3100 .filter(|conv| {
3101 matches!(
3102 &conv.source,
3103 ConversationSource::User | ConversationSource::Scripting )
3105 })
3106 .collect()
3107 }
3108
3109 pub async fn cleanup_old_conversations(&self) {
3111 let mut inner = self.inner.write().await;
3112 let now = crate::utils::clock::Instant::now();
3113
3114 inner.conversations.retain(|conv| {
3115 conv.end_time.is_none() ||
3117 conv.end_time.map(|end| now.duration_since(end).as_secs() < 1).unwrap_or(false)
3119 });
3120 }
3121
3122 pub async fn get_or_create_user_conversation_state(
3124 &self,
3125 ) -> Arc<std::sync::Mutex<crate::llm::ConversationState>> {
3126 let mut inner = self.inner.write().await;
3127
3128 if inner.user_conversation_state.is_none() {
3129 let mut conversation_state = crate::llm::ConversationState::new(50000);
3131
3132 if !inner.documented_server_protocols.is_empty() {
3136 let protocols: Vec<String> =
3137 inner.documented_server_protocols.iter().cloned().collect();
3138 conversation_state.mark_server_protocols_documented(&protocols);
3139 }
3140 if !inner.documented_client_protocols.is_empty() {
3141 let protocols: Vec<String> =
3142 inner.documented_client_protocols.iter().cloned().collect();
3143 conversation_state.mark_client_protocols_documented(&protocols);
3144 }
3145
3146 inner.user_conversation_state =
3147 Some(Arc::new(std::sync::Mutex::new(conversation_state)));
3148 } else {
3149 if let Some(ref conv_state) = inner.user_conversation_state {
3152 if let Ok(mut state) = conv_state.lock() {
3153 for protocol in &inner.documented_server_protocols {
3155 if !state.documented_server_protocols.contains(protocol) {
3156 state.mark_server_protocols_documented(std::slice::from_ref(protocol));
3157 }
3158 }
3159 for protocol in &inner.documented_client_protocols {
3160 if !state.documented_client_protocols.contains(protocol) {
3161 state.mark_client_protocols_documented(std::slice::from_ref(protocol));
3162 }
3163 }
3164 }
3165 }
3166 }
3167
3168 inner.user_conversation_state.as_ref().unwrap().clone()
3169 }
3170
3171 pub async fn clear_user_conversation_history(&self) {
3173 let inner = self.inner.read().await;
3174 if let Some(conversation_state) = &inner.user_conversation_state {
3175 if let Ok(mut state) = conversation_state.lock() {
3176 state.clear_history();
3177 }
3178 }
3179 }
3180
3181 pub async fn get_user_conversation_history(&self) -> Option<String> {
3183 let inner = self.inner.read().await;
3184 if let Some(conversation_state) = &inner.user_conversation_state {
3185 if let Ok(state) = conversation_state.lock() {
3186 if !state.is_empty() {
3187 return Some(state.get_history_for_prompt());
3188 }
3189 }
3190 }
3191 None
3192 }
3193
3194 pub async fn record_access_log(
3199 &self,
3200 owner: AccessLogOwner,
3201 protocol: &str,
3202 connection_id: Option<u32>,
3203 event_type: &str,
3204 request: serde_json::Value,
3205 response: Vec<serde_json::Value>,
3206 ) -> u64 {
3207 let unix_ms = crate::utils::clock::SystemTime::now()
3208 .duration_since(crate::utils::clock::UNIX_EPOCH)
3209 .map(|d| d.as_millis() as u64)
3210 .unwrap_or(0);
3211 let (server_id, client_id) = match owner {
3212 AccessLogOwner::Server(id) => (Some(id), None),
3213 AccessLogOwner::Client(id) => (None, Some(id)),
3214 };
3215
3216 let mut inner = self.inner.write().await;
3217 let id = inner.next_access_log_id;
3218 inner.next_access_log_id += 1;
3219 inner.access_logs.push_back(AccessLogEntry {
3220 id,
3221 unix_ms,
3222 server_id,
3223 client_id,
3224 protocol: protocol.to_string(),
3225 connection_id,
3226 event_type: event_type.to_string(),
3227 request,
3228 response,
3229 });
3230 while inner.access_logs.len() > ACCESS_LOG_CAPACITY {
3231 inner.access_logs.pop_front();
3232 }
3233 id
3234 }
3235
3236 pub async fn access_log_total(&self) -> u64 {
3241 self.inner.read().await.next_access_log_id.saturating_sub(1)
3242 }
3243
3244 pub async fn list_access_logs(&self, limit: Option<usize>) -> Vec<AccessLogEntry> {
3247 self.list_access_logs_for(None, limit).await
3248 }
3249
3250 pub async fn list_access_logs_for(
3253 &self,
3254 owner: Option<AccessLogOwner>,
3255 limit: Option<usize>,
3256 ) -> Vec<AccessLogEntry> {
3257 let inner = self.inner.read().await;
3258 let take = limit.unwrap_or(ACCESS_LOG_CAPACITY);
3259 inner
3260 .access_logs
3261 .iter()
3262 .rev()
3263 .filter(|entry| owner.is_none() || entry.owner() == owner)
3264 .take(take)
3265 .cloned()
3266 .collect()
3267 }
3268
3269 pub async fn get_access_log(&self, id: u64) -> Option<AccessLogEntry> {
3271 let inner = self.inner.read().await;
3272 inner.access_logs.iter().find(|e| e.id == id).cloned()
3273 }
3274
3275 pub async fn record_llm_tokens(&self, input_tokens: u64, output_tokens: u64) {
3279 let mut inner = self.inner.write().await;
3280 inner.total_input_tokens += input_tokens;
3281 inner.total_output_tokens += output_tokens;
3282 inner.total_llm_calls += 1;
3283 }
3284
3285 pub async fn get_llm_stats(&self) -> (u64, u64, u64) {
3287 let inner = self.inner.read().await;
3288 (
3289 inner.total_input_tokens,
3290 inner.total_output_tokens,
3291 inner.total_llm_calls,
3292 )
3293 }
3294
3295 pub async fn reset_llm_stats(&self) {
3297 let mut inner = self.inner.write().await;
3298 inner.total_input_tokens = 0;
3299 inner.total_output_tokens = 0;
3300 inner.total_llm_calls = 0;
3301 }
3302
3303 #[cfg(feature = "sqlite")]
3307 pub async fn create_database(
3308 &self,
3309 name: String,
3310 path: String,
3311 owner: crate::state::DatabaseOwner,
3312 init_sql: Option<&str>,
3313 ) -> anyhow::Result<crate::state::DatabaseId> {
3314 use anyhow::Context;
3315
3316 let mut inner = self.inner.write().await;
3317 let id = crate::state::DatabaseId::new(inner.next_unified_id);
3318 inner.next_unified_id += 1;
3319
3320 inner
3321 ._database_manager
3322 .create_database(id, name, path, owner, init_sql)
3323 .context("Failed to create database")?;
3324
3325 Ok(id)
3326 }
3327
3328 #[cfg(feature = "sqlite")]
3330 pub async fn execute_sql(
3331 &self,
3332 db_id: crate::state::DatabaseId,
3333 sql: &str,
3334 ) -> anyhow::Result<crate::state::QueryResult> {
3335 let mut inner = self.inner.write().await;
3336 inner._database_manager.execute_query(db_id, sql)
3337 }
3338
3339 #[cfg(feature = "sqlite")]
3341 pub async fn get_all_databases(&self) -> Vec<crate::state::DatabaseInstance> {
3342 let inner = self.inner.read().await;
3343 inner
3344 ._database_manager
3345 .get_all_instances()
3346 .into_iter()
3347 .cloned()
3348 .collect()
3349 }
3350
3351 #[cfg(feature = "sqlite")]
3353 pub async fn get_database(
3354 &self,
3355 db_id: crate::state::DatabaseId,
3356 ) -> Option<crate::state::DatabaseInstance> {
3357 let inner = self.inner.read().await;
3358 inner._database_manager.get_instance(db_id).cloned()
3359 }
3360
3361 #[cfg(feature = "sqlite")]
3363 pub async fn delete_database(&self, db_id: crate::state::DatabaseId) -> anyhow::Result<()> {
3364 let mut inner = self.inner.write().await;
3365 inner._database_manager.delete_database(db_id)
3366 }
3367
3368 #[cfg(feature = "sqlite")]
3370 pub async fn get_databases_by_server(
3371 &self,
3372 server_id: crate::state::ServerId,
3373 ) -> Vec<crate::state::DatabaseInstance> {
3374 let inner = self.inner.read().await;
3375 inner
3376 ._database_manager
3377 .get_databases_by_server(server_id)
3378 .into_iter()
3379 .cloned()
3380 .collect()
3381 }
3382
3383 #[cfg(feature = "sqlite")]
3385 pub async fn get_databases_by_client(
3386 &self,
3387 client_id: crate::state::ClientId,
3388 ) -> Vec<crate::state::DatabaseInstance> {
3389 let inner = self.inner.read().await;
3390 inner
3391 ._database_manager
3392 .get_databases_by_client(client_id)
3393 .into_iter()
3394 .cloned()
3395 .collect()
3396 }
3397
3398 #[cfg(feature = "sqlite")]
3400 pub async fn cleanup_databases_for_server(
3401 &self,
3402 server_id: crate::state::ServerId,
3403 ) -> anyhow::Result<()> {
3404 let mut inner = self.inner.write().await;
3405 inner
3406 ._database_manager
3407 .delete_databases_by_server(server_id)
3408 }
3409
3410 #[cfg(feature = "sqlite")]
3412 pub async fn cleanup_databases_for_client(
3413 &self,
3414 client_id: crate::state::ClientId,
3415 ) -> anyhow::Result<()> {
3416 let mut inner = self.inner.write().await;
3417 inner
3418 ._database_manager
3419 .delete_databases_by_client(client_id)
3420 }
3421
3422 pub async fn add_server_with_id(&self, server: ServerInstance) {
3427 let mut inner = self.inner.write().await;
3428 inner.servers.insert(server.id, server);
3429
3430 if inner.mode == Mode::Idle {
3432 inner.mode = Mode::Server;
3433 }
3434 }
3435
3436 #[cfg(unix)]
3438 pub async fn set_tor_client(
3439 &self,
3440 client_id: ClientId,
3441 tor_client: Arc<arti_client::TorClient<tor_rtcompat::PreferredRuntime>>,
3442 ) {
3443 let mut inner = self.inner.write().await;
3444 inner.tor_clients.insert(client_id, tor_client);
3445 }
3446
3447 #[cfg(unix)]
3449 pub async fn get_tor_client(
3450 &self,
3451 client_id: ClientId,
3452 ) -> Option<Arc<arti_client::TorClient<tor_rtcompat::PreferredRuntime>>> {
3453 let inner = self.inner.read().await;
3454 inner.tor_clients.get(&client_id).cloned()
3455 }
3456
3457 #[cfg(unix)]
3459 pub async fn remove_tor_client(&self, client_id: ClientId) {
3460 let mut inner = self.inner.write().await;
3461 inner.tor_clients.remove(&client_id);
3462 }
3463}
3464
3465impl Default for AppState {
3466 fn default() -> Self {
3467 Self::new()
3468 }
3469}