Skip to main content

tmai_core/api/
core.rs

1//! TmaiCore — the Facade entry-point for all consumers (TUI, Web, MCP, etc.)
2//!
3//! This struct owns every shared service and exposes high-level methods.
4//! Consumers never need to acquire locks or wire services themselves.
5
6use std::sync::Arc;
7
8use tokio::sync::broadcast;
9
10use crate::audit::helper::AuditHelper;
11use crate::audit::AuditEventSender;
12use crate::command_sender::CommandSender;
13use crate::config::Settings;
14use crate::ipc::server::IpcServer;
15use crate::state::SharedState;
16
17use super::events::CoreEvent;
18
19/// Default broadcast channel capacity
20const EVENT_CHANNEL_CAPACITY: usize = 256;
21
22/// The Facade that wraps all tmai-core services.
23///
24/// Constructed via [`TmaiCoreBuilder`](super::builder::TmaiCoreBuilder).
25pub struct TmaiCore {
26    /// Shared application state (agents, teams, UI state)
27    state: SharedState,
28    /// Unified command sender (IPC + tmux fallback)
29    command_sender: Option<Arc<CommandSender>>,
30    /// Application settings
31    settings: Arc<Settings>,
32    /// IPC server for PTY wrapper communication
33    ipc_server: Option<Arc<IpcServer>>,
34    /// Broadcast sender for core events
35    event_tx: broadcast::Sender<CoreEvent>,
36    /// Audit helper for emitting user-input-during-processing events
37    audit_helper: AuditHelper,
38}
39
40impl TmaiCore {
41    /// Create a new TmaiCore instance (prefer `TmaiCoreBuilder`)
42    pub(crate) fn new(
43        state: SharedState,
44        command_sender: Option<Arc<CommandSender>>,
45        settings: Arc<Settings>,
46        ipc_server: Option<Arc<IpcServer>>,
47        audit_tx: Option<AuditEventSender>,
48    ) -> Self {
49        let (event_tx, _) = broadcast::channel(EVENT_CHANNEL_CAPACITY);
50        let audit_helper = AuditHelper::new(audit_tx, state.clone());
51        Self {
52            state,
53            command_sender,
54            settings,
55            ipc_server,
56            event_tx,
57            audit_helper,
58        }
59    }
60
61    // =========================================================
62    // Escape hatches — for gradual migration from raw state access
63    // =========================================================
64
65    /// Access the raw shared state.
66    ///
67    /// **Deprecated**: prefer using typed query/action methods on `TmaiCore`.
68    /// This escape hatch exists for incremental migration only.
69    #[deprecated(note = "Use TmaiCore query/action methods instead of direct state access")]
70    pub fn raw_state(&self) -> &SharedState {
71        &self.state
72    }
73
74    /// Access the raw command sender.
75    ///
76    /// **Deprecated**: prefer using action methods on `TmaiCore`.
77    /// This escape hatch exists for incremental migration only.
78    #[deprecated(note = "Use TmaiCore action methods instead of direct CommandSender access")]
79    pub fn raw_command_sender(&self) -> Option<&Arc<CommandSender>> {
80        self.command_sender.as_ref()
81    }
82
83    /// Access application settings (read-only)
84    pub fn settings(&self) -> &Settings {
85        &self.settings
86    }
87
88    /// Access the IPC server (if configured)
89    pub fn ipc_server(&self) -> Option<&Arc<IpcServer>> {
90        self.ipc_server.as_ref()
91    }
92
93    /// Get a clone of the broadcast event sender.
94    ///
95    /// Used by the Poller to emit TeammateIdle/TaskCompleted events,
96    /// and by the SSE handler to subscribe to events.
97    pub fn event_sender(&self) -> broadcast::Sender<CoreEvent> {
98        self.event_tx.clone()
99    }
100
101    // =========================================================
102    // Internal accessors for query/action impls
103    // =========================================================
104
105    /// Borrow the shared state (for query/action modules)
106    pub(crate) fn state(&self) -> &SharedState {
107        &self.state
108    }
109
110    /// Borrow the command sender (for action modules)
111    pub(crate) fn command_sender_ref(&self) -> Option<&Arc<CommandSender>> {
112        self.command_sender.as_ref()
113    }
114
115    /// Borrow the audit helper (for action modules)
116    pub(crate) fn audit_helper(&self) -> &AuditHelper {
117        &self.audit_helper
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use crate::state::AppState;
125
126    #[test]
127    fn test_tmai_core_creation() {
128        let state = AppState::shared();
129        let settings = Arc::new(Settings::default());
130        let core = TmaiCore::new(state, None, settings.clone(), None, None);
131
132        assert_eq!(core.settings().poll_interval_ms, 500);
133        assert!(core.ipc_server().is_none());
134        assert!(core.command_sender_ref().is_none());
135    }
136
137    #[test]
138    #[allow(deprecated)]
139    fn test_escape_hatches() {
140        let state = AppState::shared();
141        let settings = Arc::new(Settings::default());
142        let core = TmaiCore::new(state.clone(), None, settings, None, None);
143
144        // raw_state should return the same Arc
145        let raw = core.raw_state();
146        assert!(Arc::ptr_eq(raw, &state));
147
148        // raw_command_sender should be None
149        assert!(core.raw_command_sender().is_none());
150    }
151}