use std::sync::Arc;
use mentra::{
ContentBlock, DelegationArtifact, DelegationEdge, DelegationKind, DelegationStatus,
SpawnedAgentStatus, SpawnedAgentSummary,
runtime::CommandOutput,
tool::{ToolContext, ToolResult},
};
use crate::runtime::agents::{AgentRegistry, AgentTools};
use super::{child::ChildSpec, depth::Depth};
pub(super) async fn command(
ctx: &ToolContext<'_>,
command: &str,
target: Option<&str>,
) -> ToolResult {
let cwd = ctx.resolve_working_directory(None)?;
let output = ctx
.execute_shell_command_on(
target.map(str::to_string),
command.to_string(),
None,
None,
cwd,
)
.await?;
narrate(ctx, &output);
read_back(output)
}
fn narrate(ctx: &ToolContext<'_>, output: &CommandOutput) {
for line in output.stdout.lines() {
ctx.emit_progress(format!("stdout: {line}"));
}
for line in output.stderr.lines() {
ctx.emit_progress(format!("stderr: {line}"));
}
}
fn read_back(output: CommandOutput) -> ToolResult {
if output.success() {
return Ok(if output.stdout.is_empty() {
output.stderr
} else {
output.stdout
});
}
Err(if !output.stderr.trim().is_empty() {
output.stderr
} else if !output.stdout.trim().is_empty() {
output.stdout
} else if output.timed_out {
"Command timed out after the configured limit".to_string()
} else {
format!(
"Command exited with status {}",
output
.status_code
.map_or_else(|| "unknown".to_string(), |code| code.to_string())
)
})
}
pub(super) async fn delegate(
ledger: &Depth,
ctx: &mut ToolContext<'_>,
prompt: &str,
depth: usize,
spec: ChildSpec,
agents: Option<&Arc<AgentRegistry>>,
) -> ToolResult {
let inherited = agents.and_then(|agents| agents.of(&ctx.agent_id));
let mut child = spawn_child(ctx, spec, inherited.as_deref())
.await
.map_err(|error| format!("spawn could not start a subagent: {error}"))?;
let child_id = child.id().to_string();
let _adopted = agents.and_then(|agents| agents.adopt(&ctx.agent_id, &child_id));
let _entered = ledger.entered(&child_id, depth + 1);
let started = ctx.register_subagent(&child);
let _usage_relay = ctx.relay_subagent_usage(&child);
let edge = DelegationEdge {
kind: DelegationKind::Subagent,
local_agent_id: ctx.agent_id.clone(),
remote_agent_id: child_id.clone(),
};
let requested = ctx.record_delegation_request(
format!(
"<delegation-request agent=\"{}\" model=\"{}\">\n{prompt}\n</delegation-request>",
started.name, started.model
),
artifact(&started, prompt, DelegationStatus::Requested, None),
Some(edge.clone()),
);
note(ctx, requested);
let options = ctx.child_run_options();
let answer = Box::pin(child.run(vec![ContentBlock::text(prompt)], options)).await;
let outcome = answer
.as_ref()
.map(|message| said(message.text()))
.map_err(ToString::to_string);
ctx.finish_subagent(
&child_id,
match &outcome {
Ok(_) => SpawnedAgentStatus::Finished,
Err(error) => SpawnedAgentStatus::Failed(error.clone()),
},
);
let (status, told, summary) = match &outcome {
Ok(text) => (DelegationStatus::Finished, "finished", text),
Err(error) => (DelegationStatus::Failed, "failed", error),
};
let returned = ctx.record_delegation_result(
format!(
"<delegation-result agent=\"{}\" status=\"{told}\">\n{summary}\n</delegation-result>",
started.name
),
artifact(&started, prompt, status, Some(summary.clone())),
Some(edge),
);
note(ctx, returned);
ctx.refresh_tasks().map_err(|error| {
format!("the delegated run finished but its tasks did not load: {error}")
})?;
outcome.map_err(|error| format!("the delegated run failed: {error}"))
}
async fn spawn_child(
ctx: &ToolContext<'_>,
spec: ChildSpec,
inherited: Option<&AgentTools>,
) -> Result<mentra::agent::Agent, mentra::error::RuntimeError> {
if spec.is_inherit() {
return ctx.spawn_subagent();
}
let ChildSpec {
roster,
model,
system,
} = spec;
let mut template = ctx.disposable_subagent_template();
if let Some(roster) = roster {
let mut profile = roster.into_profile();
if let Some(inherited) = inherited {
profile
.hidden_tools
.extend(inherited.hidden.iter().cloned());
}
template = template.with_tool_profile(profile);
}
if let Some(model) = model {
template = template.with_model(model);
}
if let Some(system) = system {
template = template.with_system(system);
}
ctx.spawn_subagent_from(template).await
}
fn said(text: String) -> String {
if text.trim().is_empty() {
return "The subagent finished without saying anything.".to_string();
}
text
}
fn artifact(
child: &SpawnedAgentSummary,
prompt: &str,
status: DelegationStatus,
result_summary: Option<String>,
) -> DelegationArtifact {
DelegationArtifact {
kind: DelegationKind::Subagent,
agent_id: child.id.clone(),
agent_name: child.name.clone(),
role: Some("subagent".to_string()),
status,
task_summary: prompt.to_string(),
result_summary,
artifacts: Vec::new(),
}
}
fn note(ctx: &ToolContext<'_>, written: Result<(), mentra::error::RuntimeError>) {
if let Err(error) = written {
ctx.emit_progress(format!("the delegation was not recorded: {error}"));
}
}