1use super::*;
2
3impl AgentSession {
4 pub fn has_queue(&self) -> bool {
9 QueueControl::from_session(self).has_queue()
10 }
11
12 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 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 pub async fn pending_external_tasks(&self) -> Vec<ExternalTask> {
48 QueueControl::from_session(self)
49 .pending_external_tasks()
50 .await
51 }
52
53 pub async fn queue_stats(&self) -> SessionQueueStats {
55 QueueControl::from_session(self).stats().await
56 }
57
58 pub async fn queue_metrics(&self) -> Option<MetricsSnapshot> {
60 QueueControl::from_session(self).metrics().await
61 }
62
63 pub async fn dead_letters(&self) -> Vec<DeadLetter> {
65 QueueControl::from_session(self).dead_letters().await
66 }
67
68 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 for agent in &agents {
84 self.ensure_compatibility_name_available(
85 crate::capability::CapabilityKind::Agent,
86 &agent.name,
87 )?;
88 }
89 let count = agents.len();
90 for agent in agents {
91 tracing::info!(
92 session_id = %self.session_id,
93 agent = agent.name,
94 dir = %dir.display(),
95 "Dynamically registered agent"
96 );
97 self.agent_registry.register(agent);
98 }
99 Ok(count)
100 })?
101 }
102
103 pub fn register_worker_agent(
111 &self,
112 spec: crate::subagent::WorkerAgentSpec,
113 ) -> crate::error::Result<crate::subagent::AgentDefinition> {
114 self.close_handle.mutate_immediate(|| {
115 self.ensure_compatibility_name_available(
116 crate::capability::CapabilityKind::Agent,
117 &spec.name,
118 )?;
119 Ok(SessionExtensionRuntime::from_session(self).register_worker_agent(spec))
120 })?
121 }
122
123 pub fn register_worker_agents<I>(
125 &self,
126 specs: I,
127 ) -> crate::error::Result<Vec<crate::subagent::AgentDefinition>>
128 where
129 I: IntoIterator<Item = crate::subagent::WorkerAgentSpec>,
130 {
131 let specs = specs.into_iter().collect::<Vec<_>>();
132 self.close_handle.mutate_immediate(|| {
133 for spec in &specs {
134 self.ensure_compatibility_name_available(
135 crate::capability::CapabilityKind::Agent,
136 &spec.name,
137 )?;
138 }
139 Ok(SessionExtensionRuntime::from_session(self).register_worker_agents(specs))
140 })?
141 }
142
143 pub fn add_skill(&self, skill: Arc<crate::skills::Skill>) -> crate::error::Result<()> {
150 self.close_handle.mutate_immediate(|| {
151 self.ensure_compatibility_name_available(
152 crate::capability::CapabilityKind::Skill,
153 &skill.name,
154 )?;
155 SessionExtensionRuntime::from_session(self).add_skill(skill)
156 })?
157 }
158
159 pub fn remove_skill(&self, name: &str) -> crate::error::Result<()> {
164 self.close_handle
165 .mutate_immediate(|| SessionExtensionRuntime::from_session(self).remove_skill(name))
166 }
167
168 pub fn skill_names(&self) -> Vec<String> {
170 self.close_handle.skill_registry.list()
171 }
172
173 pub async fn add_mcp_server(
180 &self,
181 config: crate::mcp::McpServerConfig,
182 ) -> crate::error::Result<usize> {
183 SessionExtensionRuntime::from_session(self)
184 .add_mcp_server(config)
185 .await
186 }
187
188 #[cfg(feature = "serve")]
193 pub(crate) fn tool_executor(&self) -> &Arc<crate::tools::ToolExecutor> {
194 &self.tool_executor
195 }
196
197 pub fn register_dynamic_tool(
203 &self,
204 tool: Arc<dyn crate::tools::Tool>,
205 ) -> crate::error::Result<()> {
206 self.close_handle.mutate_immediate(|| {
207 self.ensure_compatibility_name_available(
208 crate::capability::CapabilityKind::Tool,
209 tool.name(),
210 )?;
211 self.tool_executor.register_dynamic_tool(tool);
212 Ok(())
213 })?
214 }
215
216 #[cfg(feature = "dynamic-workflow")]
223 pub fn register_dynamic_workflow_runtime(&self) -> crate::error::Result<()> {
224 self.close_handle.mutate_immediate(|| {
225 self.ensure_compatibility_name_available(
226 crate::capability::CapabilityKind::Tool,
227 "dynamic_workflow",
228 )?;
229 crate::tools::register_dynamic_workflow(self.tool_executor.registry());
230 Ok(())
231 })?
232 }
233
234 #[cfg(not(feature = "dynamic-workflow"))]
237 pub fn register_dynamic_workflow_runtime(&self) -> crate::error::Result<()> {
238 self.close_handle.mutate_immediate(|| {
239 Err(crate::error::CodeError::Config(
240 "dynamic_workflow requires the advanced-harness / dynamic-workflow feature".into(),
241 ))
242 })?
243 }
244
245 pub fn unregister_dynamic_tool(&self, name: &str) -> crate::error::Result<()> {
248 self.close_handle
249 .mutate_immediate(|| self.tool_executor.unregister_dynamic_tool(name))
250 }
251
252 pub async fn remove_mcp_server(&self, server_name: &str) -> crate::error::Result<()> {
257 SessionExtensionRuntime::from_session(self)
258 .remove_mcp_server(server_name)
259 .await
260 }
261
262 pub async fn mcp_status(
264 &self,
265 ) -> std::collections::HashMap<String, crate::mcp::McpServerStatus> {
266 SessionExtensionRuntime::from_session(self)
267 .mcp_status()
268 .await
269 }
270
271 pub fn capability_catalog_stamp(&self) -> crate::capability::CapabilityCatalogStamp {
273 self.capability_catalog.current_stamp()
274 }
275
276 pub fn ensure_recovery_capability_binding(
279 &self,
280 expected: &crate::capability::RunCapabilityBindingV1,
281 ) -> std::result::Result<(), crate::capability::RunCapabilityBindingError> {
282 super::agent_loop_runtime::validate_run_capability_binding(self, expected)
283 }
284
285 pub async fn apply_capability_batch(
292 &self,
293 batch: crate::capability::SessionCapabilityBatch,
294 cancellation: tokio_util::sync::CancellationToken,
295 ) -> std::result::Result<
296 crate::capability::CapabilityCommitReceipt,
297 crate::capability::CapabilityRuntimeError,
298 > {
299 let _mutation = self.close_handle.extension_mutation.lock().await;
300 if self.is_closed() {
301 return Err(crate::capability::CapabilityRuntimeError::SessionClosed);
302 }
303
304 let preparation_cancellation = tokio_util::sync::CancellationToken::new();
305 let prepared = tokio::select! {
306 biased;
307 _ = self.session_cancel.cancelled() => {
308 preparation_cancellation.cancel();
309 return Err(if self.is_closed() {
310 crate::capability::CapabilityRuntimeError::SessionClosed
311 } else {
312 crate::capability::CapabilityRuntimeError::Cancelled
313 });
314 }
315 _ = cancellation.cancelled() => {
316 preparation_cancellation.cancel();
317 return Err(crate::capability::CapabilityRuntimeError::Cancelled);
318 }
319 result = batch.prepare(
320 &self.capability_catalog,
321 preparation_cancellation.clone(),
322 ) => result?,
323 };
324 self.ensure_projected_mcp_server_names_available(prepared.projection()?)
325 .await?;
326
327 let _publication = self
330 .close_handle
331 .immediate_extension_mutation
332 .lock()
333 .unwrap_or_else(std::sync::PoisonError::into_inner);
334 if self.is_closed() {
335 return Err(crate::capability::CapabilityRuntimeError::SessionClosed);
336 }
337 if cancellation.is_cancelled() || self.session_cancel.is_cancelled() {
338 return Err(crate::capability::CapabilityRuntimeError::Cancelled);
339 }
340 let command_registry = self
344 .command_registry
345 .lock()
346 .unwrap_or_else(std::sync::PoisonError::into_inner);
347 let current_projection = self.capability_catalog.pin();
348 super::agent_loop_runtime::validate_capability_projection_runtime(
349 self,
350 prepared.projection()?,
351 &command_registry,
352 )?;
353 super::agent_loop_runtime::validate_capability_projection_transition(
354 self,
355 current_projection.projection(),
356 prepared.projection()?,
357 )?;
358 prepared.commit()
359 }
360
361 pub async fn apply_sdk_capability_batch(
367 &self,
368 batch: crate::capability::SdkCapabilityBatchV1,
369 ) -> std::result::Result<
370 crate::capability::SdkCapabilityCommitReceiptV1,
371 crate::capability::SdkCapabilityBatchError,
372 > {
373 let session_batch = batch.into_session_batch()?;
374 let receipt = self
375 .apply_capability_batch(session_batch, self.session_cancel.child_token())
376 .await?;
377 Ok(crate::capability::SdkCapabilityCommitReceiptV1::from_receipt(&receipt))
378 }
379
380 pub async fn bootstrap_recovery_capability_batch(
390 &self,
391 expected: &crate::capability::RunCapabilityBindingV1,
392 batch: crate::capability::SessionCapabilityBatch,
393 cancellation: tokio_util::sync::CancellationToken,
394 ) -> std::result::Result<
395 crate::capability::CapabilityCommitReceipt,
396 crate::capability::CapabilityRuntimeError,
397 > {
398 let _mutation = self.close_handle.extension_mutation.lock().await;
399 if self.is_closed() {
400 return Err(crate::capability::CapabilityRuntimeError::SessionClosed);
401 }
402
403 let target_ceiling = self.capability_run_ceiling(batch.target())?;
404 expected
405 .ensure_matches(batch.target(), &target_ceiling)
406 .map_err(
407 |error| crate::capability::CapabilityRuntimeError::RecoveryBinding {
408 message: error.to_string(),
409 },
410 )?;
411
412 let preparation_cancellation = tokio_util::sync::CancellationToken::new();
413 let prepared = tokio::select! {
414 biased;
415 _ = self.session_cancel.cancelled() => {
416 preparation_cancellation.cancel();
417 return Err(if self.is_closed() {
418 crate::capability::CapabilityRuntimeError::SessionClosed
419 } else {
420 crate::capability::CapabilityRuntimeError::Cancelled
421 });
422 }
423 _ = cancellation.cancelled() => {
424 preparation_cancellation.cancel();
425 return Err(crate::capability::CapabilityRuntimeError::Cancelled);
426 }
427 result = batch.prepare_recovery_bootstrap(
428 &self.capability_catalog,
429 preparation_cancellation.clone(),
430 ) => result?,
431 };
432 self.ensure_projected_mcp_server_names_available(prepared.projection()?)
433 .await?;
434
435 let _publication = self
436 .close_handle
437 .immediate_extension_mutation
438 .lock()
439 .unwrap_or_else(std::sync::PoisonError::into_inner);
440 if self.is_closed() {
441 return Err(crate::capability::CapabilityRuntimeError::SessionClosed);
442 }
443 if cancellation.is_cancelled() || self.session_cancel.is_cancelled() {
444 return Err(crate::capability::CapabilityRuntimeError::Cancelled);
445 }
446 let command_registry = self
447 .command_registry
448 .lock()
449 .unwrap_or_else(std::sync::PoisonError::into_inner);
450 let current_projection = self.capability_catalog.pin();
451 super::agent_loop_runtime::validate_capability_projection_runtime(
452 self,
453 prepared.projection()?,
454 &command_registry,
455 )?;
456 super::agent_loop_runtime::validate_capability_projection_transition(
457 self,
458 current_projection.projection(),
459 prepared.projection()?,
460 )?;
461 prepared.commit()
462 }
463
464 pub async fn drain_capability_cleanup(&self) -> crate::capability::CapabilityCleanupReport {
466 self.capability_catalog.drain_cleanup().await
467 }
468
469 #[cfg(test)]
470 pub(crate) async fn admit_capability_run(
471 &self,
472 ) -> std::result::Result<
473 crate::capability::SessionCapabilityRun,
474 crate::capability::CapabilityRuntimeError,
475 > {
476 let projection = {
477 let _admission = self
478 .close_handle
479 .immediate_extension_mutation
480 .lock()
481 .unwrap_or_else(std::sync::PoisonError::into_inner);
482 if self.is_closed() {
483 return Err(crate::capability::CapabilityRuntimeError::SessionClosed);
484 }
485 self.capability_catalog.pin()
486 };
487 let ceiling = self.capability_run_ceiling(projection.projection().set())?;
488 crate::capability::SessionCapabilityRun::admit(
489 projection,
490 "active",
491 "active",
492 ceiling,
493 self.session_cancel.child_token(),
494 )
495 .await
496 }
497
498 pub(super) fn capability_run_ceiling(
499 &self,
500 set: &crate::capability::CapabilitySet,
501 ) -> std::result::Result<
502 crate::capability::CapabilityCeiling,
503 crate::capability::CapabilityRuntimeError,
504 > {
505 let mut governance = crate::capability::GovernanceCapabilityCeiling::none_required();
506 if self.config.permission_checker.is_some() || self.config.permission_policy.is_some() {
507 governance = governance.require_permission_guard();
508 }
509 if self.config.confirmation_manager.is_some() || self.config.confirmation_policy.is_some() {
510 governance = governance.require_confirmation_guard();
511 }
512 if self.config.security_provider.is_some() {
513 governance = governance.require_security_guard();
514 }
515 if self.config.budget_guard.is_some() || self.budget_guard().is_some() {
516 governance = governance.require_budget_guard();
517 }
518 if self.config.enforce_active_skill_tool_restrictions {
519 governance = governance.require_active_skill_restrictions();
520 }
521 let execution = crate::capability::CapabilityExecutionCeiling::new(
522 self.config.max_tool_rounds,
523 self.config.max_parallel_tasks,
524 self.config.tool_timeout_ms,
525 self.config.llm_api_timeout_ms,
526 self.config.max_execution_time_ms,
527 )?;
528 crate::capability::CapabilityCeiling::all(
529 set,
530 crate::capability::WorkspaceCapabilityCeiling::all(),
531 governance,
532 execution,
533 )
534 .map_err(Into::into)
535 }
536
537 pub(super) fn ensure_compatibility_name_available(
538 &self,
539 kind: crate::capability::CapabilityKind,
540 public_name: &str,
541 ) -> crate::error::Result<()> {
542 let projection = self.capability_catalog.pin();
543 if projection
544 .projection()
545 .iter()
546 .any(|(_, value)| match value {
547 crate::capability::CapabilityValue::Mcp(binding)
548 if kind == crate::capability::CapabilityKind::Tool =>
549 {
550 binding.contains_public_tool_name(public_name)
551 }
552 crate::capability::CapabilityValue::Agent(agent) => {
553 kind == crate::capability::CapabilityKind::Agent
554 && crate::subagent::agent_names_conflict(&agent.name, public_name)
555 }
556 _ => value.kind() == kind && value.public_name() == Some(public_name),
557 })
558 {
559 return Err(
560 crate::capability::CapabilityRuntimeError::RuntimeNameConflict {
561 kind,
562 public_name: public_name.to_owned(),
563 }
564 .into(),
565 );
566 }
567 Ok(())
568 }
569
570 async fn ensure_projected_mcp_server_names_available(
571 &self,
572 projection: &crate::capability::CapabilityProjection,
573 ) -> std::result::Result<(), crate::capability::CapabilityRuntimeError> {
574 let server_names = projection
575 .iter()
576 .filter_map(|(_, value)| match value {
577 crate::capability::CapabilityValue::Mcp(binding) => {
578 Some(binding.server_name().to_owned())
579 }
580 _ => None,
581 })
582 .collect::<Vec<_>>();
583 for server_name in server_names {
584 for manager in &self.mcp_managers {
585 if manager.contains_server(&server_name).await {
586 return Err(
587 crate::capability::CapabilityRuntimeError::RuntimeNameConflict {
588 kind: crate::capability::CapabilityKind::Mcp,
589 public_name: server_name,
590 },
591 );
592 }
593 }
594 }
595 Ok(())
596 }
597}