Skip to main content

crabtalk_runtime/
env.rs

1//! Env — trait for node-specific capabilities and tool dispatch.
2//!
3//! The runtime engine talks to a single `Env` implementation. The node
4//! crate provides [`NodeEnv`] which bundles event broadcasting,
5//! instruction discovery, and a composite Hook. Tests use `()`.
6
7use crate::Hook;
8use std::path::{Path, PathBuf};
9use tokio::sync::broadcast;
10use wcore::{AgentEvent, ToolDispatch, ToolFuture, protocol::message};
11
12/// The runtime environment — combines server capabilities with tool dispatch.
13///
14/// Each node/binary defines one implementation that wires together
15/// the composite hook, event broadcasting, CWD management, and
16/// instruction discovery.
17pub trait Env: Send + Sync + 'static {
18    /// The composite hook providing tool schemas, dispatch, and lifecycle.
19    type Hook: Hook;
20
21    /// Access the composite hook.
22    fn hook(&self) -> &Self::Hook;
23
24    /// Called when an agent event occurs. Default: no-op.
25    fn on_agent_event(&self, _agent: &str, _conversation_id: u64, _event: &AgentEvent) {}
26
27    /// Subscribe to agent events. Returns `None` if event broadcasting
28    /// is not supported.
29    fn subscribe_events(&self) -> Option<broadcast::Receiver<message::AgentEventMsg>> {
30        None
31    }
32
33    /// Collect layered instructions (e.g. `Crab.md` files) for the
34    /// given working directory.
35    fn discover_instructions(&self, _cwd: &Path) -> Option<String> {
36        None
37    }
38
39    /// Effective working directory for a conversation. Defaults to the
40    /// process CWD.
41    fn effective_cwd(&self, _conversation_id: u64) -> PathBuf {
42        std::env::current_dir().unwrap_or_default()
43    }
44}
45
46/// Dispatch a tool call through an Env's hook. Utility for Env
47/// implementors building their ToolDispatcher impl.
48pub fn dispatch_tool<'a, E: Env>(
49    env: &'a E,
50    name: &'a str,
51    args: &'a str,
52    agent: &'a str,
53    sender: &'a str,
54    conversation_id: Option<u64>,
55) -> ToolFuture<'a> {
56    let call = ToolDispatch {
57        args: args.to_owned(),
58        agent: agent.to_owned(),
59        sender: sender.to_owned(),
60        conversation_id,
61    };
62
63    match env.hook().dispatch(name, call) {
64        Some(fut) => fut,
65        None => Box::pin(async move { Err(format!("tool not registered: {name}")) }),
66    }
67}
68
69impl Env for () {
70    type Hook = ();
71
72    fn hook(&self) -> &() {
73        &()
74    }
75}