use super::super::{Resolved, refusal, seam, stop_signal};
use super::{DECLINED, Entry, FAILED, Invocation, NAME, OK};
use crate::prompt::Deps;
use crate::prompt::Error;
use crate::prompt::tool::{ExecError, ToolCall};
use std::path::Path;
pub(super) struct Inner<'a> {
pub(super) outer_id: &'a str,
pub(super) k: usize,
pub(super) inv: &'a Invocation,
pub(super) step_dir_abs: &'a Path,
pub(super) conv_repo: &'a Path,
pub(super) conv_id: &'a str,
}
pub(super) fn run_inner(
inner: &Inner<'_>,
resolved: &Resolved<'_>,
deps: &Deps<'_>,
) -> Result<Option<Entry>, Error> {
let inv = inner.inv;
if inv.name == NAME {
return Ok(Some(Entry {
name: inv.name.clone(),
status: DECLINED,
text: format!(
"{NAME:?} may not contain itself (depth 1): \
list the nested invocations in this envelope directly."
),
}));
}
if let Some(decline) = refusal(resolved.grant.role, resolved.grant.tools, &inv.name) {
return Ok(Some(Entry {
name: inv.name.clone(),
status: DECLINED,
text: decline,
}));
}
let inner_id = format!("{}-{}", inner.outer_id, inner.k);
match seam::adjudicate(
resolved.workflow.tool_control.as_ref(),
resolved.grant.role,
&inner_id,
&inv.name,
&inv.input,
inner.conv_repo,
inner.conv_id,
deps.stop,
)? {
seam::Gate::Stopped => return Ok(None),
seam::Gate::Refuse(reason) => {
return Ok(Some(Entry {
name: inv.name.clone(),
status: DECLINED,
text: seam::refusal_text(&inv.name, &reason),
}));
}
seam::Gate::Hold(reason) => {
return Ok(Some(Entry {
name: inv.name.clone(),
status: DECLINED,
text: format!(
"the workflow's tool control held {:?} ({reason}), and a hold cannot \
park mid-envelope: re-issue this invocation as a top-level tool_use \
to have it reviewed (ARCH §3.3 Tool control).",
inv.name
),
}));
}
seam::Gate::Proceed => {}
}
match deps.tool_executor.execute(
ToolCall {
id: &inner_id,
name: &inv.name,
input: &inv.input,
},
inner.step_dir_abs,
deps.stop,
resolved.workflow.tool_output,
) {
Ok(outcome) => Ok(Some(Entry {
name: inv.name.clone(),
status: if outcome.is_error { FAILED } else { OK },
text: String::from_utf8_lossy(&outcome.content).into_owned(),
})),
Err(ExecError::KilledBySignal { .. }) if stop_signal::stopped(deps.stop) => Ok(None),
Err(source) => Err(Error::ToolExec {
tool: inv.name.clone(),
source,
}),
}
}