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