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 or replace a skill in this live session.
125    ///
126    /// The Skill and `search_skills` tools observe the new definition
127    /// immediately, and the model-visible skills catalog observes it on the
128    /// next turn. Removing the live definition restores any session skill it
129    /// shadowed at installation time.
130    pub fn add_skill(&self, skill: Arc<crate::skills::Skill>) -> crate::error::Result<()> {
131        self.close_handle
132            .mutate_immediate(|| SessionExtensionRuntime::from_session(self).add_skill(skill))?
133    }
134
135    /// Remove a skill previously installed through [`Self::add_skill`].
136    ///
137    /// This is a no-op when the name is not owned by the live session API; base
138    /// session skills and later host registrations are never removed.
139    pub fn remove_skill(&self, name: &str) -> crate::error::Result<()> {
140        self.close_handle
141            .mutate_immediate(|| SessionExtensionRuntime::from_session(self).remove_skill(name))
142    }
143
144    /// Return the names in the session's current live skill registry.
145    pub fn skill_names(&self) -> Vec<String> {
146        self.close_handle.skill_registry.list()
147    }
148
149    /// Add an MCP server to this session.
150    ///
151    /// Registers, connects, and makes all tools immediately available for the
152    /// agent to call. Tool names follow the convention `mcp__<name>__<tool>`.
153    ///
154    /// Returns the number of tools registered from the server.
155    pub async fn add_mcp_server(
156        &self,
157        config: crate::mcp::McpServerConfig,
158    ) -> crate::error::Result<usize> {
159        SessionExtensionRuntime::from_session(self)
160            .add_mcp_server(config)
161            .await
162    }
163
164    /// The session's tool executor, for installing agent-dir `tools/` entries
165    /// (e.g. a `kind = "script"` tool) into the live registry. Internal seam used
166    /// by [`serve::install_agent_dir_tools`](crate::serve::install_agent_dir_tools)
167    /// (the only caller, hence the `serve` gate).
168    #[cfg(feature = "serve")]
169    pub(crate) fn tool_executor(&self) -> &Arc<crate::tools::ToolExecutor> {
170        &self.tool_executor
171    }
172
173    /// Register a host-provided dynamic tool into the live session. Enables an
174    /// embedding app (e.g. the a3s-code CLI's login-gated `runtime` A3S Runtime
175    /// offload tool) to add a native tool at runtime; it enters the LLM's toolset
176    /// on the next run (`build_agent_loop` re-snapshots `definitions()` per run),
177    /// the same way MCP tools surface after `add_mcp_server`. Idempotent by name.
178    pub fn register_dynamic_tool(
179        &self,
180        tool: Arc<dyn crate::tools::Tool>,
181    ) -> crate::error::Result<()> {
182        self.close_handle
183            .mutate_immediate(|| self.tool_executor.register_dynamic_tool(tool))
184    }
185
186    /// Register the A3S Flow-backed dynamic workflow tool for this live session.
187    ///
188    /// The tool is named `dynamic_workflow`. It accepts a sandboxed JavaScript
189    /// PTC workflow script and executes it through
190    /// [`crate::DynamicWorkflowRuntime`], so A3S Flow owns workflow replay while
191    /// the script can still call A3S Code tools.
192    pub fn register_dynamic_workflow_runtime(&self) -> crate::error::Result<()> {
193        self.close_handle.mutate_immediate(|| {
194            crate::tools::register_dynamic_workflow(self.tool_executor.registry())
195        })
196    }
197
198    /// Remove a previously host-registered dynamic tool by name (e.g. on logout).
199    /// No-op if no tool of that name is registered.
200    pub fn unregister_dynamic_tool(&self, name: &str) -> crate::error::Result<()> {
201        self.close_handle
202            .mutate_immediate(|| self.tool_executor.unregister_dynamic_tool(name))
203    }
204
205    /// Remove an MCP server from this session.
206    ///
207    /// Disconnects the server and unregisters all its tools from the executor.
208    /// No-op if the server was never added.
209    pub async fn remove_mcp_server(&self, server_name: &str) -> crate::error::Result<()> {
210        SessionExtensionRuntime::from_session(self)
211            .remove_mcp_server(server_name)
212            .await
213    }
214
215    /// Return the connection status of all MCP servers registered with this session.
216    pub async fn mcp_status(
217        &self,
218    ) -> std::collections::HashMap<String, crate::mcp::McpServerStatus> {
219        SessionExtensionRuntime::from_session(self)
220            .mcp_status()
221            .await
222    }
223}