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}