Skip to main content

orcs_component/
lib.rs

1//! Component system for ORCS CLI.
2//!
3//! This crate provides the component abstraction layer for the ORCS
4//! (Orchestrated Runtime for Collaborative Systems) architecture.
5//!
6//! # Crate Architecture
7//!
8//! This crate is part of the **Plugin SDK** layer:
9//!
10//! ```text
11//! ┌─────────────────────────────────────────────────────────────┐
12//! │                    Plugin SDK Layer                          │
13//! │  (External, SemVer stable, safe to depend on)               │
14//! ├─────────────────────────────────────────────────────────────┤
15//! │  orcs-types     : ID types, Principal, ErrorCode            │
16//! │  orcs-event     : Signal, Request, Event                    │
17//! │  orcs-component : Component trait (WIT target)  ◄── HERE    │
18//! └─────────────────────────────────────────────────────────────┘
19//! ```
20//!
21//! ## WIT Target
22//!
23//! The [`Component`] trait is designed to be exportable as a
24//! WebAssembly Interface Type (WIT) for WASM component plugins.
25//! This enables:
26//!
27//! - **Language-agnostic plugins**: Write components in any language
28//! - **Sandboxed execution**: WASM provides security isolation
29//! - **Dynamic loading**: Load plugins at runtime without recompilation
30//!
31//! # Component Architecture Overview
32//!
33//! Components are the functional units that communicate via the EventBus:
34//!
35//! ```text
36//! ┌──────────────────────────────────────────────────────────────────────┐
37//! │                            OrcsEngine                                │
38//! │  ┌────────────────────────────────────────────────────────────────┐  │
39//! │  │                          EventBus                              │  │
40//! │  │   - Request/Response routing                                   │  │
41//! │  │   - Signal dispatch (highest priority)                         │  │
42//! │  └────────────────────────────────────────────────────────────────┘  │
43//! └──────────────────────────────────────────────────────────────────────┘
44//!           │ EventBus
45//!           ├──────────────┬──────────────┬──────────────┐
46//!           ▼              ▼              ▼              ▼
47//!     ┌──────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐
48//!     │   LLM    │   │  Tools   │   │   HIL    │   │  Skill   │
49//!     │Component │   │Component │   │Component │   │Component │
50//!     └──────────┘   └──────────┘   └──────────┘   └──────────┘
51//!           │
52//!           │ Internal management
53//!           ▼
54//!     ┌────────────────────────────────────────────────────────┐
55//!     │                Child / Agent / Skill                   │
56//!     │            (managed by parent Component)               │
57//!     └────────────────────────────────────────────────────────┘
58//! ```
59//!
60//! # Core Traits
61//!
62//! ## Required Traits (All Components/Children Must Implement)
63//!
64//! | Trait | Purpose |
65//! |-------|---------|
66//! | [`Identifiable`] | Entity identification |
67//! | [`SignalReceiver`] | Human control (abort, cancel) |
68//! | [`Statusable`] | Status reporting for monitoring |
69//!
70//! ## Component Traits
71//!
72//! | Trait | Purpose |
73//! |-------|---------|
74//! | [`Component`] | EventBus participant with request handling |
75//! | [`Child`] | Managed entity (no direct EventBus access) |
76//! | `Agent` | Reactive child (subscribes to events) |
77//! | `Skill` | Auto-triggered child |
78//!
79//! # Human as Superpower
80//!
81//! A core principle of ORCS: Human controls from above.
82//! All components MUST respond to signals:
83//!
84//! - **Veto**: Stop everything immediately
85//! - **Cancel**: Stop operations in scope
86//!
87//! This is enforced by the [`SignalReceiver`] trait.
88//!
89//! # Example: Simple Component
90//!
91//! ```
92//! use orcs_component::{Component, ComponentError, Status};
93//! use orcs_event::{Request, Signal, SignalResponse};
94//! use orcs_types::ComponentId;
95//! use serde_json::Value;
96//!
97//! struct EchoComponent {
98//!     id: ComponentId,
99//!     status: Status,
100//! }
101//!
102//! impl EchoComponent {
103//!     fn new() -> Self {
104//!         Self {
105//!             id: ComponentId::builtin("echo"),
106//!             status: Status::Idle,
107//!         }
108//!     }
109//! }
110//!
111//! impl Component for EchoComponent {
112//!     fn id(&self) -> &ComponentId {
113//!         &self.id
114//!     }
115//!
116//!     fn status(&self) -> Status {
117//!         self.status
118//!     }
119//!
120//!     fn on_request(&mut self, request: &Request) -> Result<Value, ComponentError> {
121//!         self.status = Status::Running;
122//!         match request.operation.as_str() {
123//!             "echo" => {
124//!                 self.status = Status::Idle;
125//!                 Ok(request.payload.clone())
126//!             }
127//!             op => Err(ComponentError::NotSupported(op.into())),
128//!         }
129//!     }
130//!
131//!     fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
132//!         if signal.is_veto() {
133//!             self.abort();
134//!             SignalResponse::Abort
135//!         } else {
136//!             SignalResponse::Ignored
137//!         }
138//!     }
139//!
140//!     fn abort(&mut self) {
141//!         self.status = Status::Aborted;
142//!     }
143//! }
144//! ```
145//!
146//! # Example: Child Entity
147//!
148//! ```
149//! use orcs_component::{Child, Identifiable, SignalReceiver, Statusable, Status};
150//! use orcs_event::{Signal, SignalResponse};
151//!
152//! struct Worker {
153//!     id: String,
154//!     status: Status,
155//! }
156//!
157//! impl Identifiable for Worker {
158//!     fn id(&self) -> &str {
159//!         &self.id
160//!     }
161//! }
162//!
163//! impl SignalReceiver for Worker {
164//!     fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
165//!         if signal.is_veto() {
166//!             self.abort();
167//!             SignalResponse::Abort
168//!         } else {
169//!             SignalResponse::Ignored
170//!         }
171//!     }
172//!
173//!     fn abort(&mut self) {
174//!         self.status = Status::Aborted;
175//!     }
176//! }
177//!
178//! impl Statusable for Worker {
179//!     fn status(&self) -> Status {
180//!         self.status
181//!     }
182//! }
183//!
184//! // Mark as Child
185//! impl Child for Worker {}
186//! ```
187//!
188//! # Crate Structure
189//!
190//! - [`Component`] - EventBus participant trait
191//! - [`Child`], `Agent`, `Skill` - Managed entity traits
192//! - [`Identifiable`], [`SignalReceiver`], [`Statusable`] - Core traits
193//! - [`Status`], [`StatusDetail`], [`Progress`] - Status types
194//! - [`ComponentError`] - Error types
195//!
196//! # Related Crates
197//!
198//! - [`orcs_types`] - Core identifier types (ComponentId, Principal, etc.)
199//! - [`orcs_event`] - Event types (Signal, Request)
200//! - `orcs-runtime` - Runtime layer (Session, EventBus)
201
202pub mod capability;
203mod child;
204mod component;
205mod context;
206mod emitter;
207mod error;
208mod package;
209mod snapshot;
210mod status;
211pub mod testing;
212pub mod tool;
213mod traits;
214
215// Re-export core traits
216pub use traits::{Identifiable, SignalReceiver, Statusable};
217
218// Re-export emitter trait
219pub use emitter::Emitter;
220
221// Re-export component traits
222pub use child::{
223    AsyncRunnableChild, Child, ChildError, ChildResult, ChildResultDto, RunnableChild,
224};
225pub use component::{Component, RuntimeHints};
226
227// Re-export async_trait for convenience
228pub use async_trait::async_trait;
229
230// Re-export capability type
231pub use capability::Capability;
232
233// Re-export child context types
234pub use context::{
235    AsyncChildContext, AsyncChildHandle, ChildConfig, ChildContext, ChildHandle, CommandPermission,
236    ComponentLoader, RunError, SpawnError,
237};
238
239// Re-export status types
240pub use status::{Progress, Status, StatusDetail};
241
242// Re-export snapshot types
243pub use snapshot::{
244    ComponentSnapshot, SnapshotError, SnapshotSupport, Snapshottable, SNAPSHOT_VERSION,
245};
246
247// Re-export package types
248pub use package::{
249    Package, PackageError, PackageInfo, PackageSupport, Packageable, PACKAGE_VERSION,
250};
251
252// Re-export error types
253pub use error::ComponentError;
254
255// Re-export tool types
256pub use tool::{RustTool, ToolContext, ToolError};
257
258// Re-export EventCategory and SubscriptionEntry for convenience
259pub use orcs_event::{EventCategory, SubscriptionEntry};
260
261#[cfg(test)]
262mod tests {
263    use super::*;
264    use orcs_event::{Request, Signal, SignalResponse};
265    use orcs_types::{ChannelId, ComponentId, ErrorCode, Principal, PrincipalId};
266    use serde_json::Value;
267
268    struct MockComponent {
269        id: ComponentId,
270        status: Status,
271    }
272
273    impl MockComponent {
274        fn new(name: &str) -> Self {
275            Self {
276                id: ComponentId::builtin(name),
277                status: Status::Idle,
278            }
279        }
280    }
281
282    impl Component for MockComponent {
283        fn id(&self) -> &ComponentId {
284            &self.id
285        }
286
287        fn status(&self) -> Status {
288            self.status
289        }
290
291        fn on_request(&mut self, request: &Request) -> Result<Value, ComponentError> {
292            match request.operation.as_str() {
293                "echo" => Ok(request.payload.clone()),
294                _ => Err(ComponentError::NotSupported(request.operation.clone())),
295            }
296        }
297
298        fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
299            if signal.is_veto() {
300                self.abort();
301                SignalResponse::Abort
302            } else {
303                SignalResponse::Ignored
304            }
305        }
306
307        fn abort(&mut self) {
308            self.status = Status::Aborted;
309        }
310    }
311
312    #[test]
313    fn mock_component_echo() {
314        let mut comp = MockComponent::new("echo");
315        let source = ComponentId::builtin("test");
316        let channel = ChannelId::new();
317        let req = Request::new(
318            EventCategory::Echo,
319            "echo",
320            source,
321            channel,
322            Value::String("hello".into()),
323        );
324
325        assert_eq!(
326            comp.on_request(&req).unwrap(),
327            Value::String("hello".into())
328        );
329    }
330
331    #[test]
332    fn mock_component_unknown_operation() {
333        let mut comp = MockComponent::new("test");
334        let source = ComponentId::builtin("test");
335        let channel = ChannelId::new();
336        let req = Request::new(EventCategory::Echo, "unknown", source, channel, Value::Null);
337
338        let result = comp.on_request(&req);
339        assert!(result.is_err());
340        assert_eq!(result.unwrap_err().code(), "COMPONENT_NOT_SUPPORTED");
341    }
342
343    #[test]
344    fn mock_component_abort_on_veto() {
345        let mut comp = MockComponent::new("test");
346        let signal = Signal::veto(Principal::User(PrincipalId::new()));
347
348        let resp = comp.on_signal(&signal);
349        assert_eq!(resp, SignalResponse::Abort);
350        assert_eq!(comp.status(), Status::Aborted);
351    }
352}