Skip to main content

a3s_code_core/agent_api/
capability_facade.rs

1use super::*;
2
3impl AgentSession {
4    // Advanced optional Queue API
5    // ========================================================================
6
7    /// Returns whether this session has an advanced lane queue configured.
8    pub fn has_queue(&self) -> bool {
9        QueueControl::from_session(self).has_queue()
10    }
11
12    /// Configure a lane's handler mode for explicit external/hybrid dispatch.
13    ///
14    /// Only effective when a queue is configured via `SessionOptions::with_queue_config`.
15    pub async fn set_lane_handler(
16        &self,
17        lane: SessionLane,
18        config: LaneHandlerConfig,
19    ) -> crate::error::Result<()> {
20        let _mutation = self.close_handle.extension_mutation.lock().await;
21        if self.is_closed() {
22            return Err(crate::error::CodeError::SessionClosed {
23                session_id: self.session_id.clone(),
24            });
25        }
26        QueueControl::from_session(self)
27            .set_lane_handler(lane, config)
28            .await;
29        if self.is_closed() {
30            return Err(crate::error::CodeError::SessionClosed {
31                session_id: self.session_id.clone(),
32            });
33        }
34        Ok(())
35    }
36
37    /// Complete an external queue task by ID.
38    ///
39    /// Returns `true` if the task was found and completed, `false` if not found.
40    pub async fn complete_external_task(&self, task_id: &str, result: ExternalTaskResult) -> bool {
41        QueueControl::from_session(self)
42            .complete_external_task(task_id, result)
43            .await
44    }
45
46    /// Get pending external queue tasks awaiting completion by an external handler.
47    pub async fn pending_external_tasks(&self) -> Vec<ExternalTask> {
48        QueueControl::from_session(self)
49            .pending_external_tasks()
50            .await
51    }
52
53    /// Get optional queue statistics (pending, active, external counts per lane).
54    pub async fn queue_stats(&self) -> SessionQueueStats {
55        QueueControl::from_session(self).stats().await
56    }
57
58    /// Get a metrics snapshot from the optional queue (if metrics are enabled).
59    pub async fn queue_metrics(&self) -> Option<MetricsSnapshot> {
60        QueueControl::from_session(self).metrics().await
61    }
62
63    /// Get dead letters from the optional queue's DLQ (if DLQ is enabled).
64    pub async fn dead_letters(&self) -> Vec<DeadLetter> {
65        QueueControl::from_session(self).dead_letters().await
66    }
67
68    // ========================================================================
69    // MCP API
70    // ========================================================================
71
72    /// Register all agents found in a directory with the live session.
73    ///
74    /// Scans `dir` for `*.yaml`, `*.yml`, and `*.md` agent definition files,
75    /// parses them, and adds each one to the shared `AgentRegistry` used by the
76    /// `task` tool.  New agents are immediately usable via `task(agent="…")` in
77    /// the same session — no restart required.
78    ///
79    /// Returns the number of agents successfully loaded from the directory.
80    pub fn register_agent_dir(&self, dir: &std::path::Path) -> crate::error::Result<usize> {
81        let agents = crate::subagent::load_agents_from_dir(dir);
82        self.close_handle.mutate_immediate(|| {
83            let count = agents.len();
84            for agent in agents {
85                tracing::info!(
86                    session_id = %self.session_id,
87                    agent = agent.name,
88                    dir = %dir.display(),
89                    "Dynamically registered agent"
90                );
91                self.agent_registry.register(agent);
92            }
93            count
94        })
95    }
96
97    /// Register a disposable worker agent with the live session.
98    ///
99    /// The returned definition is immediately available to the `task` tool by
100    /// worker name, so callers can create many reproducible workers without
101    /// writing temporary agent files or restarting the session.
102    pub fn register_worker_agent(
103        &self,
104        spec: crate::subagent::WorkerAgentSpec,
105    ) -> crate::error::Result<crate::subagent::AgentDefinition> {
106        self.close_handle.mutate_immediate(|| {
107            SessionExtensionRuntime::from_session(self).register_worker_agent(spec)
108        })
109    }
110
111    /// Register multiple disposable worker agents with the live session.
112    pub fn register_worker_agents<I>(
113        &self,
114        specs: I,
115    ) -> crate::error::Result<Vec<crate::subagent::AgentDefinition>>
116    where
117        I: IntoIterator<Item = crate::subagent::WorkerAgentSpec>,
118    {
119        self.close_handle.mutate_immediate(|| {
120            SessionExtensionRuntime::from_session(self).register_worker_agents(specs)
121        })
122    }
123
124    /// Add an MCP server to this session.
125    ///
126    /// Registers, connects, and makes all tools immediately available for the
127    /// agent to call. Tool names follow the convention `mcp__<name>__<tool>`.
128    ///
129    /// Returns the number of tools registered from the server.
130    pub async fn add_mcp_server(
131        &self,
132        config: crate::mcp::McpServerConfig,
133    ) -> crate::error::Result<usize> {
134        SessionExtensionRuntime::from_session(self)
135            .add_mcp_server(config)
136            .await
137    }
138
139    /// The session's tool executor, for installing agent-dir `tools/` entries
140    /// (e.g. a `kind = "script"` tool) into the live registry. Internal seam used
141    /// by [`serve::install_agent_dir_tools`](crate::serve::install_agent_dir_tools)
142    /// (the only caller, hence the `serve` gate).
143    #[cfg(feature = "serve")]
144    pub(crate) fn tool_executor(&self) -> &Arc<crate::tools::ToolExecutor> {
145        &self.tool_executor
146    }
147
148    /// Register a host-provided dynamic tool into the live session. Enables an
149    /// embedding app (e.g. the a3s-code CLI's login-gated `runtime` A3S Runtime
150    /// offload tool) to add a native tool at runtime; it enters the LLM's toolset
151    /// on the next run (`build_agent_loop` re-snapshots `definitions()` per run),
152    /// the same way MCP tools surface after `add_mcp_server`. Idempotent by name.
153    pub fn register_dynamic_tool(
154        &self,
155        tool: Arc<dyn crate::tools::Tool>,
156    ) -> crate::error::Result<()> {
157        self.close_handle
158            .mutate_immediate(|| self.tool_executor.register_dynamic_tool(tool))
159    }
160
161    /// Register the A3S Flow-backed dynamic workflow tool for this live session.
162    ///
163    /// The tool is named `dynamic_workflow`. It accepts a sandboxed JavaScript
164    /// PTC workflow script and executes it through
165    /// [`crate::DynamicWorkflowRuntime`], so A3S Flow owns workflow replay while
166    /// the script can still call A3S Code tools.
167    pub fn register_dynamic_workflow_runtime(&self) -> crate::error::Result<()> {
168        self.close_handle.mutate_immediate(|| {
169            crate::tools::register_dynamic_workflow(self.tool_executor.registry())
170        })
171    }
172
173    /// Remove a previously host-registered dynamic tool by name (e.g. on logout).
174    /// No-op if no tool of that name is registered.
175    pub fn unregister_dynamic_tool(&self, name: &str) -> crate::error::Result<()> {
176        self.close_handle
177            .mutate_immediate(|| self.tool_executor.unregister_dynamic_tool(name))
178    }
179
180    /// Remove an MCP server from this session.
181    ///
182    /// Disconnects the server and unregisters all its tools from the executor.
183    /// No-op if the server was never added.
184    pub async fn remove_mcp_server(&self, server_name: &str) -> crate::error::Result<()> {
185        SessionExtensionRuntime::from_session(self)
186            .remove_mcp_server(server_name)
187            .await
188    }
189
190    /// Return the connection status of all MCP servers registered with this session.
191    pub async fn mcp_status(
192        &self,
193    ) -> std::collections::HashMap<String, crate::mcp::McpServerStatus> {
194        SessionExtensionRuntime::from_session(self)
195            .mcp_status()
196            .await
197    }
198}