use std::collections::BTreeMap;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use crate::error::{Error, Result};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct AgentPermissions {
pub approval: Option<crate::config::ApprovalPolicy>,
pub sandbox: Option<crate::tools::SandboxPolicy>,
pub auto_approved_tools: Option<Vec<String>>,
pub deny: Vec<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct NamedAgentDefinition {
pub name: String,
pub system_prompt: String,
pub tools: Option<Vec<String>>,
pub model: Option<String>,
pub permissions: Option<AgentPermissions>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackgroundPromptsPolicy {
AutoPolicy,
Parent,
}
impl BackgroundPromptsPolicy {
pub fn parse(s: &str) -> Option<Self> {
match s {
"auto_policy" => Some(BackgroundPromptsPolicy::AutoPolicy),
"parent" => Some(BackgroundPromptsPolicy::Parent),
_ => None,
}
}
pub fn as_str(self) -> &'static str {
match self {
BackgroundPromptsPolicy::AutoPolicy => "auto_policy",
BackgroundPromptsPolicy::Parent => "parent",
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SubagentLineage {
pub child_agent_id: String,
pub parent_session_id: Option<String>,
pub parent_tool_use_id: String,
pub depth: usize,
pub agent_type: Option<String>,
pub task: String,
pub background: bool,
pub spawned_at_ms: i64,
pub model: String,
}
impl SubagentLineage {
pub fn to_lineage_map(&self) -> BTreeMap<String, String> {
let mut m = BTreeMap::new();
if let Some(p) = &self.parent_session_id {
m.insert("parent_thread_id".to_string(), p.clone());
m.insert("parent_session_id".to_string(), p.clone());
}
m.insert("depth".to_string(), self.depth.to_string());
if let Some(t) = &self.agent_type {
m.insert("agent_role".to_string(), t.clone());
}
m.insert(
"thread_source".to_string(),
"supercode_native_spawn".to_string(),
);
m
}
}
#[derive(Debug, Clone)]
pub struct QueuedApproval {
pub child_agent_id: String,
pub tool: String,
pub subject: Option<String>,
pub queued_at_ms: i64,
pub outcome: Option<QueuedApprovalOutcome>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QueuedApprovalOutcome {
Allowed,
Denied,
}
impl From<crate::permissions::ApprovalOutcome> for QueuedApprovalOutcome {
fn from(outcome: crate::permissions::ApprovalOutcome) -> Self {
match outcome {
crate::permissions::ApprovalOutcome::Deny => Self::Denied,
crate::permissions::ApprovalOutcome::Allow
| crate::permissions::ApprovalOutcome::AllowForSession => Self::Allowed,
}
}
}
pub fn queue_approval(
queue: &Arc<std::sync::Mutex<Vec<QueuedApproval>>>,
record: QueuedApproval,
) -> Option<usize> {
let mut queue = queue
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
queue.push(record);
Some(queue.len() - 1)
}
pub fn record_queued_outcome(
queue: &Arc<std::sync::Mutex<Vec<QueuedApproval>>>,
index: usize,
outcome: QueuedApprovalOutcome,
) {
if let Some(record) = queue
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get_mut(index)
{
record.outcome = Some(outcome);
}
}
pub struct ParentQueueApprovalHandler {
pub child_agent_id: String,
pub queue: Arc<std::sync::Mutex<Vec<QueuedApproval>>>,
}
impl crate::permissions::PermissionsApprovalHandler for ParentQueueApprovalHandler {
fn ask(
&self,
req: &crate::permissions::ApprovalRequest,
) -> crate::permissions::ApprovalOutcome {
let record = QueuedApproval {
child_agent_id: self.child_agent_id.clone(),
tool: req.tool.to_string(),
subject: req.subject.map(String::from),
queued_at_ms: now_ms(),
outcome: Some(QueuedApprovalOutcome::Denied),
};
if let Ok(mut q) = self.queue.lock() {
q.push(record);
}
crate::permissions::ApprovalOutcome::Deny
}
}
fn now_ms() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as i64)
.unwrap_or(0)
}
pub fn check_depth(current_depth: usize, max_depth: usize) -> Result<()> {
if current_depth >= max_depth {
return Err(Error::SubagentDepthExceeded {
max_depth,
attempted_depth: current_depth + 1,
});
}
Ok(())
}
pub struct ConcurrencyGuard(Arc<AtomicUsize>);
impl Drop for ConcurrencyGuard {
fn drop(&mut self) {
self.0.fetch_sub(1, Ordering::SeqCst);
}
}
pub fn try_acquire(gauge: &Arc<AtomicUsize>, max_concurrent: usize) -> Option<ConcurrencyGuard> {
let mut current = gauge.load(Ordering::SeqCst);
loop {
if current >= max_concurrent {
return None;
}
match gauge.compare_exchange(current, current + 1, Ordering::SeqCst, Ordering::SeqCst) {
Ok(_) => return Some(ConcurrencyGuard(gauge.clone())),
Err(actual) => current = actual,
}
}
}