Run a task container with unified support for both interactive and non-interactive modes. Follows a setup → execute → cleanup structure: 1. **Setup**: Conditionally create an isolated network and connect the proxy 2. **Execute**: Create, configure, and run the container via [`run_container_inner`] 3. **Cleanup**: Always remove the container, tear down the network, and stop the proxy if idle — reg
(
&self,
docker_image_tag: &str,
task: &crate::task::Task,
agent: &dyn Agent,
)
| 633 | /// * `Ok((output, task_result))` - The container output and synthesized task result |
| 634 | /// * `Err(String)` - Error message if container infrastructure fails |
| 635 | pub async fn run_task_container( |
| 636 | &self, |
| 637 | docker_image_tag: &str, |
| 638 | task: &crate::task::Task, |
| 639 | agent: &dyn Agent, |
| 640 | ) -> Result<(String, crate::agent::TaskResult), String> { |
| 641 | // --- Setup: atomically acquire proxy session --- |
| 642 | // When nested inside a TSK container, skip proxy/network setup since the |
| 643 | // outer container already provides network isolation. |
| 644 | let resolved = resolve_config_from_task(task, &self.ctx, &self.event_sender); |
| 645 | let proxy_config = resolved.proxy_config(); |
| 646 | |
| 647 | let proxy_session = if task.network_isolation && !self.is_nested() { |
| 648 | let suppress_stdout = self.event_sender.is_some(); |
| 649 | let proxy_logger = TaskLogger::from_path( |
| 650 | &self |
| 651 | .ctx |
| 652 | .tsk_env() |
| 653 | .task_dir(&task.id) |
| 654 | .join("output") |
| 655 | .join("agent.log"), |
| 656 | suppress_stdout, |
| 657 | ); |
| 658 | match self |
| 659 | .proxy_manager |
| 660 | .acquire_proxy(&task.id, &proxy_config, &proxy_logger) |
| 661 | .await |
| 662 | { |
| 663 | Ok(session) => Some(session), |
| 664 | Err(e) => { |
| 665 | return Err(format!( |
| 666 | "Failed to ensure proxy is running and healthy: {e}. \ |
| 667 | The task should be retried later when the proxy is available. \ |
| 668 | Check the status in Docker." |
| 669 | )); |
| 670 | } |
| 671 | } |
| 672 | } else { |
| 673 | None |
| 674 | }; |
| 675 | |
| 676 | // --- Execute: run the container, capturing its ID for cleanup --- |
| 677 | let (container_id, dind_storage, result) = self |
| 678 | .run_container_inner( |
| 679 | docker_image_tag, |
| 680 | task, |
| 681 | agent, |
| 682 | proxy_session.as_ref().map(|s| s.network_name.as_str()), |
| 683 | proxy_session.as_ref().map(|_| &proxy_config), |
| 684 | proxy_session.as_ref().and_then(|s| s.proxy_ip.as_deref()), |
| 685 | ) |
| 686 | .await; |
| 687 | |
| 688 | // --- Cleanup: always runs regardless of success/failure --- |
| 689 | if let Some(ref id) = container_id { |
| 690 | let _ = self.remove_container(id).await; |
| 691 | } |
| 692 | if let Some(ref session) = proxy_session { |