magi-code 0.96.1

Repository-aware CLI coding agent for terminal work
Documentation
use super::*;

/// Borrowed task identity needed while preparing a child run.
pub(super) struct ChildRunSetup<'a> {
    pub(super) id: &'a str,
    pub(super) task: &'a SubagentTask,
    pub(super) cwd: &'a Path,
    pub(super) config: &'a SubagentRunConfig,
    pub(super) task_activity_id: &'a ActivityId,
    pub(super) batch_id: &'a ActivityId,
    pub(super) finisher: &'a TaskActivityFinisher,
    pub(super) reporter: Option<&'a SchedulerTaskReporter>,
    pub(super) cancellation: &'a AgentCancellation,
}

pub(super) struct PreparedChildRun {
    pub(super) run: ChildAgentRun,
    pub(super) request_agent: AgentSession,
    pub(super) session: Option<Session>,
    pub(super) tools: ToolRuntime,
    pub(super) hooks: Option<HookRuntime>,
}

pub(super) struct ChildSetupFailure {
    pub(super) error: String,
    pub(super) session: Option<Session>,
}

impl ChildRunSetup<'_> {
    /// Resolves the child agent, durable session, tools and hooks before any
    /// child provider work starts.
    pub(super) fn prepare(&self) -> Result<PreparedChildRun, ChildSetupFailure> {
        let config = self.config;
        let run = child_agent_for_task(self.task, self.cwd, config).map_err(|error| {
            ChildSetupFailure {
                error: error.to_string(),
                session: None,
            }
        })?;
        self.finisher.try_enrich(config, || ActivityEvent::Started {
            id: self.task_activity_id.clone(),
            parent_id: Some(self.batch_id.clone()),
            kind: ActivityKind::SubagentTask,
            status: ActivityStatus::Running,
            metadata: subagent_task_activity_metadata(
                self.id,
                self.task,
                config.depth + 1,
                Some(&run),
            ),
        });
        let session = self.open_linked_session()?;
        if let (Some(reporter), Some(session)) = (self.reporter, &session) {
            reporter.session(session.id().to_string(), session.path().to_path_buf());
            reporter.progress();
        }
        let (tools, request_agent) = match self.child_tools(&run, session.as_ref()) {
            Ok(tools) => tools,
            Err(error) => {
                return Err(ChildSetupFailure {
                    error: error.to_string(),
                    session,
                });
            }
        };
        let hooks = match config.inherited_hooks.as_ref() {
            Some(hooks) => match hooks.clone_for_cwd(self.cwd) {
                Ok(hooks) => Some(hooks),
                Err(error) => {
                    return Err(ChildSetupFailure {
                        error: format!("subagent inherited hook setup failed: {error}"),
                        session,
                    });
                }
            },
            None => None,
        };
        Ok(PreparedChildRun {
            run,
            request_agent,
            session,
            tools,
            hooks,
        })
    }

    fn open_linked_session(&self) -> Result<Option<Session>, ChildSetupFailure> {
        let config = self.config;
        let session = match config.sessions_root.as_ref() {
            Some(root) => Some(
                SessionManager::new(root.join("subagents"))
                    .create()
                    .and_then(Session::activate)
                    .map_err(|error| ChildSetupFailure {
                        error: format!("subagent session creation failed: {error}"),
                        session: None,
                    })?,
            ),
            None => None,
        };
        // Publish the durable edge before any child provider work starts. Nested runs
        // share the child store, but append their edge to their immediate parent.
        if let (Some(root), Some(parent_id), Some(child)) =
            (&config.sessions_root, &config.parent_session_id, &session)
        {
            let parent_root = if config.depth == 0 {
                root.clone()
            } else {
                root.join("subagents")
            };
            let linked = SessionManager::new(parent_root)
                .open_existing(parent_id.clone())
                .and_then(|parent| {
                    crate::sessions::record_session_event(
                        Some(&parent),
                        &config.parent_cwd,
                        SessionEventKind::SubagentSession,
                        json!({"session_id": child.id()}),
                    )
                });
            if let Err(error) = linked {
                return Err(ChildSetupFailure {
                    error: format!("subagent session linkage failed: {error}"),
                    session,
                });
            }
        }
        Ok(session)
    }

    /// Clones parent tools for the child scope, records the task scope, and
    /// wires nested subagent dispatch.
    fn child_tools(
        &self,
        run: &ChildAgentRun,
        session: Option<&Session>,
    ) -> anyhow::Result<(ToolRuntime, AgentSession)> {
        let (task, cwd, config) = (self.task, self.cwd, self.config);
        let profile_disabled_tools = task
            .identity
            .as_deref()
            .and_then(|identity| config.profiles.get(identity))
            .map(|profile| &profile.disabled_tools)
            .filter(|disabled_tools| !disabled_tools.is_empty());
        let mut tools = match profile_disabled_tools {
            Some(disabled_tools) => config
                .parent_tools
                .clone_for_cwd_with_subagent_depth_and_additional_disabled_tools(
                    cwd,
                    config.depth + 1,
                    disabled_tools,
                ),
            None => config
                .parent_tools
                .clone_for_cwd_with_subagent_depth(cwd, config.depth + 1),
        }?;
        tools.restrict_to_inspection(task.mode == crate::subagents::SubagentMode::Inspect);
        let scope = crate::sessions::task_scope::TaskScope::new(task, &tools)?;
        crate::sessions::record_session_event(
            session,
            cwd,
            SessionEventKind::TaskScope,
            serde_json::to_value(&scope)?,
        )?;
        let tools = tools.with_task_scope(scope)?;
        let child_agent = run.agent.clone();
        let request_agent = if tools.subagents_available_on_any_surface()
            && let Some(subagent_profiles_prompt) = &config.subagent_profiles_prompt
        {
            child_agent.with_appended_system_prompt(subagent_profiles_prompt)
        } else {
            child_agent.clone()
        };
        let child_config = SubagentRunConfig {
            parent_agent: child_agent,
            provider: Arc::clone(&run.provider),
            provider_override: config.provider_override.clone(),
            parent_tools: tools.clone(),
            parent_cwd: cwd.to_path_buf(),
            cancellation: self.cancellation.clone(),
            subagent_controls: config.subagent_controls.clone(),
            profiles: config.profiles.clone(),
            subagent_profiles_prompt: if tools.subagents_available_on_any_surface() {
                config.subagent_profiles_prompt.clone()
            } else {
                None
            },
            sessions_root: config.sessions_root.clone(),
            parent_session_id: None,
            depth: config.depth + 1,
            parent_activity_id: Some(self.task_activity_id.clone()),
            activity_sender: config.activity_sender.clone(),
            inherited_hooks: config.inherited_hooks.clone(),
            semantic_progress_timeout: config.semantic_progress_timeout,
            schema_validation_max_retries: config.schema_validation_max_retries,
            compaction: run.compaction.clone(),
        };
        let tools = tools.with_subagents(move |arguments, context| {
            let mut config = child_config.clone();
            config.parent_session_id = context.hook_context.session_id;
            // Tool execution uses a local activity ID; activity delivery scopes it
            // through the child sink. Register nested controls under that same ID.
            config.parent_activity_id = context.parent_activity_id.map(|id| {
                match child_config.parent_activity_id.as_ref() {
                    Some(parent) if id != *parent && !id.is_path_descendant_of(parent) => {
                        parent.child(id.as_str())
                    }
                    _ => id,
                }
            });
            config.activity_sender = context.activity_sender;
            dispatch_subagents(arguments, config)
        });
        Ok((tools, request_agent))
    }
}