Skip to main content

plexus_substrate/
builder.rs

1//! Plexus RPC builder - constructs a fully configured DynamicHub instance
2//!
3//! This module is used by both the main binary and examples.
4
5use std::sync::{Arc, Weak};
6
7use crate::activations::arbor::{Arbor, ArborConfig};
8use crate::activations::bash::Bash;
9use crate::activations::changelog::{Changelog, ChangelogStorageConfig};
10use crate::activations::claudecode::{ClaudeCode, ClaudeCodeStorage, ClaudeCodeStorageConfig};
11use crate::activations::claudecode_loopback::{ClaudeCodeLoopback, LoopbackStorageConfig};
12use crate::activations::cone::{Cone, ConeStorageConfig};
13use crate::activations::echo::Echo;
14use crate::activations::health::Health;
15use crate::activations::interactive::Interactive;
16use crate::activations::mustache::{Mustache, MustacheStorageConfig};
17use crate::activations::solar::Solar;
18use crate::plexus::DynamicHub;
19// use plexus_jsexec::{JsExec, JsExecConfig};  // temporarily disabled - needs API updates
20use registry::Registry;
21
22/// Build the Plexus RPC hub with registered activations
23///
24/// The hub implements the Plexus RPC protocol and provides introspection methods:
25/// - substrate.call: Route calls to registered activations
26/// - substrate.hash: Get configuration hash for cache invalidation
27/// - substrate.list_activations: Enumerate registered activations
28/// - substrate.schema: Get full Plexus RPC schema
29///
30/// Hub activations (with nested children) are registered with `register_hub`
31/// to enable direct nested routing like `substrate.solar.mercury.info`.
32///
33/// This function uses `Arc::new_cyclic` to inject a weak reference to the hub
34/// into Cone and ClaudeCode, enabling them to resolve foreign handles through
35/// the hub without creating reference cycles.
36///
37/// This function is async because Arbor, Cone, and ClaudeCode require
38/// async database initialization.
39pub async fn build_plexus_rpc() -> Arc<DynamicHub> {
40    // Initialize Arbor first (other activations depend on its storage)
41    // Use explicit type annotation for Weak<DynamicHub> parent context
42    let arbor: Arbor<Weak<DynamicHub>> = Arbor::with_context_type(ArborConfig::default())
43        .await
44        .expect("Failed to initialize Arbor");
45    let arbor_storage = arbor.storage();
46
47    // Initialize Cone with shared Arbor storage
48    // Use explicit type annotation for Weak<DynamicHub> parent context
49    let cone: Cone<Weak<DynamicHub>> = Cone::with_context_type(ConeStorageConfig::default(), arbor_storage.clone())
50        .await
51        .expect("Failed to initialize Cone");
52
53    // Initialize ClaudeCode with shared Arbor storage
54    // Use explicit type annotation for Weak<DynamicHub> parent context
55    let claudecode_storage = ClaudeCodeStorage::new(
56        ClaudeCodeStorageConfig::default(),
57        arbor_storage,
58    )
59    .await
60    .expect("Failed to initialize ClaudeCode storage");
61    let claudecode: ClaudeCode<Weak<DynamicHub>> = ClaudeCode::with_context_type(Arc::new(claudecode_storage));
62
63    // Initialize Mustache for template rendering
64    let mustache = Mustache::new(MustacheStorageConfig::default())
65        .await
66        .expect("Failed to initialize Mustache");
67
68    // Initialize Changelog for tracking Plexus RPC server changes
69    let changelog = Changelog::new(ChangelogStorageConfig::default())
70        .await
71        .expect("Failed to initialize Changelog");
72
73    // Initialize ClaudeCode Loopback for tool permission routing
74    let loopback = ClaudeCodeLoopback::new(LoopbackStorageConfig::default())
75        .await
76        .expect("Failed to initialize ClaudeCodeLoopback");
77
78    // Initialize JsExec for JavaScript execution in V8 isolates
79    // let jsexec = JsExec::new(JsExecConfig::default());  // temporarily disabled
80
81    // Initialize Registry for backend discovery
82    let registry = Registry::with_defaults()
83        .await
84        .expect("Failed to initialize Registry");
85
86    // Use Arc::new_cyclic to get a Weak<DynamicHub> during construction
87    // This allows us to inject the parent context into Cone and ClaudeCode
88    // before the hub is fully constructed, avoiding reference cycles
89    let hub = Arc::new_cyclic(|weak_hub: &Weak<DynamicHub>| {
90        // Inject parent context into activations that need it
91        arbor.inject_parent(weak_hub.clone());
92        cone.inject_parent(weak_hub.clone());
93        claudecode.inject_parent(weak_hub.clone());
94
95        // Build and return the DynamicHub with "substrate" namespace
96        DynamicHub::new("substrate")
97            .register(Health::new())
98            .register(Echo::new())
99            .register(Bash::new())
100            .register(arbor)
101            .register(cone)
102            .register(claudecode)
103            .register(mustache)
104            .register(changelog.clone())
105            .register(loopback)
106            // .register(jsexec)  // temporarily disabled
107            .register(registry)
108            .register(Interactive::new())  // Bidirectional demo activation
109            .register_hub(Solar::new())
110    });
111
112    // Run changelog startup check
113    let plexus_hash = hub.compute_hash();
114    match changelog.startup_check(&plexus_hash).await {
115        Ok((hash_changed, is_documented, message)) => {
116            if hash_changed && !is_documented {
117                tracing::error!("{}", message);
118            } else if hash_changed {
119                tracing::info!("{}", message);
120            } else {
121                tracing::debug!("{}", message);
122            }
123        }
124        Err(e) => {
125            tracing::error!("Changelog startup check failed: {}", e);
126        }
127    }
128
129    hub
130}