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