1pub mod ui_protocol;
49
50#[cfg(feature = "runtime")]
52pub mod common;
53#[cfg(feature = "runtime")]
54pub mod effects;
55#[cfg(feature = "runtime")]
56pub mod mcp;
57#[cfg(feature = "runtime")]
58pub mod plugin_manager;
59
60#[cfg(feature = "runtime")]
62pub mod domain;
63#[cfg(feature = "runtime")]
64pub mod error;
65#[cfg(feature = "runtime")]
66pub mod ffi;
67#[cfg(feature = "runtime")]
68pub mod hooks;
69#[cfg(feature = "runtime")]
70pub mod logging;
71#[cfg(feature = "runtime")]
72pub mod protocol;
73#[cfg(feature = "runtime")]
74pub mod util;
75
76#[cfg(feature = "runtime")]
78pub mod handlers;
79#[cfg(feature = "runtime")]
80pub mod layout;
81#[cfg(feature = "runtime")]
82pub mod services;
83
84#[cfg(feature = "runtime")]
86pub use common::{ErrorCode, ErrorContext, HostError, HostResult};
87#[cfg(feature = "runtime")]
88pub use effects::{EffectError, EffectHandler, EffectRegistry, EffectResult};
89#[cfg(feature = "runtime")]
90pub use plugin_manager::PluginManager;
91
92#[cfg(feature = "runtime")]
94pub use domain::{
95 AbsolutePath, DomainError, GithubOwner, GithubRepo, IssueNumber, PathError, Role, SessionId,
96 ToolName, ToolPermission,
97};
98#[cfg(feature = "runtime")]
99pub use error::{ExoMonadError, Result};
100#[cfg(feature = "runtime")]
101pub use ffi::{
102 ErrorCode as FFIErrorCode, ErrorContext as FFIErrorContext, FFIBoundary, FFIError, FFIResult,
103};
104#[cfg(feature = "runtime")]
105pub use hooks::HookConfig;
106#[cfg(feature = "runtime")]
107pub use logging::{init_logging, init_logging_with_default};
108#[cfg(feature = "runtime")]
109pub use protocol::{
110 ClaudePreToolUseOutput, ClaudeStopHookOutput, GeminiStopHookOutput, HookEventType, HookInput,
111 HookSpecificOutput, InternalStopHookOutput, PermissionDecision, Runtime as ProtocolRuntime,
112 StopDecision,
113};
114#[cfg(feature = "runtime")]
115pub use util::{build_prompt, find_exomonad_binary, shell_quote};
116
117#[cfg(feature = "runtime")]
119pub use handlers::{
120 AgentHandler, CopilotHandler, FilePRHandler, FsHandler, GitHandler, GitHubHandler, LogHandler,
121 PopupHandler,
122};
123#[cfg(feature = "runtime")]
124pub use services::{Services, ValidatedServices};
125
126#[cfg(feature = "runtime")]
128pub mod prelude {
129 pub use crate::handlers::*;
130 pub use crate::services::{Services, ValidatedServices};
131}
132
133#[cfg(feature = "runtime")]
134use std::path::PathBuf;
135#[cfg(feature = "runtime")]
136use std::sync::Arc;
137
138#[cfg(feature = "runtime")]
152pub struct RuntimeBuilder {
153 registry: EffectRegistry,
154 wasm_bytes: Option<Vec<u8>>,
155}
156
157#[cfg(feature = "runtime")]
158impl RuntimeBuilder {
159 pub fn new() -> Self {
161 Self {
162 registry: EffectRegistry::new(),
163 wasm_bytes: None,
164 }
165 }
166
167 pub fn with_effect_handler(mut self, handler: impl EffectHandler + 'static) -> Self {
171 self.registry.register_owned(handler);
172 self
173 }
174
175 pub fn with_effect_handler_arc(mut self, handler: Arc<dyn EffectHandler>) -> Self {
177 self.registry.register(handler);
178 self
179 }
180
181 pub fn with_wasm_bytes(mut self, bytes: Vec<u8>) -> Self {
183 self.wasm_bytes = Some(bytes);
184 self
185 }
186
187 pub fn registry(&self) -> &EffectRegistry {
189 &self.registry
190 }
191
192 pub fn into_registry(self) -> EffectRegistry {
194 self.registry
195 }
196
197 pub async fn build(self) -> anyhow::Result<Runtime> {
205 let wasm_bytes = self
206 .wasm_bytes
207 .ok_or_else(|| anyhow::anyhow!("WASM bytes not set"))?;
208
209 let registry = Arc::new(self.registry);
210 let plugin_manager = PluginManager::new(&wasm_bytes, registry.clone()).await?;
211
212 Ok(Runtime {
213 plugin_manager,
214 registry,
215 })
216 }
217}
218
219#[cfg(feature = "runtime")]
220impl Default for RuntimeBuilder {
221 fn default() -> Self {
222 Self::new()
223 }
224}
225
226#[cfg(feature = "runtime")]
228pub struct Runtime {
229 pub plugin_manager: PluginManager,
231
232 pub registry: Arc<EffectRegistry>,
234}
235
236#[cfg(feature = "runtime")]
237impl Runtime {
238 pub fn plugin_manager(&self) -> &PluginManager {
240 &self.plugin_manager
241 }
242
243 pub fn registry(&self) -> &EffectRegistry {
245 &self.registry
246 }
247
248 pub async fn dispatch_effect(
250 &self,
251 effect_type: &str,
252 payload: &[u8],
253 ) -> EffectResult<Vec<u8>> {
254 self.registry.dispatch(effect_type, payload).await
255 }
256
257 pub fn into_mcp_state(self, project_dir: PathBuf) -> mcp::McpState {
259 mcp::McpState {
260 project_dir,
261 plugin: Arc::new(self.plugin_manager),
262 }
263 }
264}
265
266#[cfg(feature = "runtime")]
268pub fn register_builtin_handlers(
269 builder: RuntimeBuilder,
270 services: &Arc<ValidatedServices>,
271) -> RuntimeBuilder {
272 let mut builder = builder;
273
274 builder = builder.with_effect_handler(handlers::GitHandler::new(services.git().clone()));
275
276 if let Some(github) = services.github() {
277 builder = builder.with_effect_handler(handlers::GitHubHandler::new(github.clone()));
278 }
279
280 builder = builder.with_effect_handler(handlers::LogHandler::new());
281
282 builder = builder.with_effect_handler(handlers::AgentHandler::new(
283 services.agent_control().clone(),
284 ));
285
286 builder = builder.with_effect_handler(handlers::FsHandler::new(services.filesystem().clone()));
287
288 if let Some(session) = services.zellij_session() {
289 builder = builder.with_effect_handler(handlers::PopupHandler::new(session.to_string()));
290 }
291
292 builder = builder.with_effect_handler(handlers::FilePRHandler::new());
293
294 builder = builder.with_effect_handler(handlers::CopilotHandler::new());
295
296 builder
297}