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 internally by the poll bridge to emit events.
96    pub(crate) fn event_sender(&self) -> broadcast::Sender<CoreEvent> {
97        self.event_tx.clone()
98    }
99
100    // =========================================================
101    // Internal accessors for query/action impls
102    // =========================================================
103
104    /// Borrow the shared state (for query/action modules)
105    pub(crate) fn state(&self) -> &SharedState {
106        &self.state
107    }
108
109    /// Borrow the command sender (for action modules)
110    pub(crate) fn command_sender_ref(&self) -> Option<&Arc<CommandSender>> {
111        self.command_sender.as_ref()
112    }
113
114    /// Borrow the audit helper (for action modules)
115    pub(crate) fn audit_helper(&self) -> &AuditHelper {
116        &self.audit_helper
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    use crate::state::AppState;
124
125    #[test]
126    fn test_tmai_core_creation() {
127        let state = AppState::shared();
128        let settings = Arc::new(Settings::default());
129        let core = TmaiCore::new(state, None, settings.clone(), None, None);
130
131        assert_eq!(core.settings().poll_interval_ms, 500);
132        assert!(core.ipc_server().is_none());
133        assert!(core.command_sender_ref().is_none());
134    }
135
136    #[test]
137    #[allow(deprecated)]
138    fn test_escape_hatches() {
139        let state = AppState::shared();
140        let settings = Arc::new(Settings::default());
141        let core = TmaiCore::new(state.clone(), None, settings, None, None);
142
143        // raw_state should return the same Arc
144        let raw = core.raw_state();
145        assert!(Arc::ptr_eq(raw, &state));
146
147        // raw_command_sender should be None
148        assert!(core.raw_command_sender().is_none());
149    }
150}