feat(tui): add usage overlay and sidebar for displaying usage statistics and tasks
feat(tui): implement status bar with connection and turn state indicators feat(tui): create workflow panel for agent status and progress visualization feat(web): introduce web frontend interface with static file serving feat(ws): add WebSocket interface for real-time communication and session management
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
//! Agent spawning tools — launch subagents and pipelines.
|
||||
|
||||
use anyhow::Result;
|
||||
use serde_json::{json, Value};
|
||||
use tracing::info;
|
||||
|
||||
use zesdex_domain::cms::{AppConfigRepository, SettingsRepository};
|
||||
use zesdex_domain::core::Store;
|
||||
|
||||
use crate::persistence::{JsonAppConfigRepository, JsonSettingsRepository};
|
||||
use crate::subagent::context::SubagentContext;
|
||||
use crate::subagent::division::AccessTier;
|
||||
use crate::subagent::engine::run_agent;
|
||||
use crate::subagent::spawn::spawn_subagent;
|
||||
use crate::tools::{Tool, ToolCtx};
|
||||
|
||||
/// Spawn multiple agent instances to work in parallel on subtasks.
|
||||
///
|
||||
/// Flow: parse agents array → load settings → for each agent, build a
|
||||
/// SubagentContext and call spawn_subagent → join all threads → collect results.
|
||||
pub struct SpawnAgents;
|
||||
|
||||
impl Tool for SpawnAgents {
|
||||
fn name(&self) -> &'static str {
|
||||
"spawn_agents"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Spawn multiple agent instances to work in parallel on subtasks"
|
||||
}
|
||||
|
||||
fn parameters(&self) -> Value {
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"agents": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"directive": {"type": "string", "description": "Directive for the agent"},
|
||||
"access": {"type": "string", "enum": ["read", "write", "full"], "description": "Access tier"}
|
||||
},
|
||||
"required": ["directive"]
|
||||
},
|
||||
"description": "List of agents to spawn"
|
||||
}
|
||||
},
|
||||
"required": ["agents"]
|
||||
})
|
||||
}
|
||||
|
||||
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String> {
|
||||
let agents = args
|
||||
.get("agents")
|
||||
.and_then(|v| v.as_array())
|
||||
.ok_or_else(|| anyhow::anyhow!("missing 'agents' array"))?;
|
||||
|
||||
info!("Spawning {} agents", agents.len());
|
||||
|
||||
// Load LLM credentials once for all agents
|
||||
let store = Store::new();
|
||||
let settings = JsonSettingsRepository::new()
|
||||
.load(&store.base_dir)
|
||||
.unwrap_or_default();
|
||||
let app_config = JsonAppConfigRepository::new()
|
||||
.load(&store.base_dir)
|
||||
.unwrap_or_default();
|
||||
|
||||
let (provider, model) =
|
||||
crate::subagent::provider::resolve_subagent_provider(&settings, &app_config);
|
||||
|
||||
let base_url = app_config
|
||||
.providers
|
||||
.get(&provider)
|
||||
.map(|p| p.api_base.clone())
|
||||
.unwrap_or_else(|| "https://opencode.ai/zen/v1".to_string());
|
||||
|
||||
let api_key = crate::llm::provider::resolve_api_key(&settings, &app_config);
|
||||
|
||||
let mut handles = Vec::new();
|
||||
for (i, agent) in agents.iter().enumerate() {
|
||||
let directive = agent
|
||||
.get("directive")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
let access_str = agent
|
||||
.get("access")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("full");
|
||||
|
||||
let access = match access_str {
|
||||
"read" => AccessTier::Read,
|
||||
"write" => AccessTier::Write,
|
||||
_ => AccessTier::Full,
|
||||
};
|
||||
|
||||
let subagent_ctx = SubagentContext::new(
|
||||
directive.clone(),
|
||||
ctx.clone(),
|
||||
access_str.to_string(),
|
||||
base_url.clone(),
|
||||
api_key.clone(),
|
||||
model.clone(),
|
||||
);
|
||||
|
||||
let handle = spawn_subagent(subagent_ctx, directive.clone(), access, ctx.clone());
|
||||
handles.push((i, handle));
|
||||
}
|
||||
|
||||
// Join all handles and collect results
|
||||
let mut results = Vec::new();
|
||||
for (i, handle) in handles {
|
||||
let result = handle
|
||||
.join()
|
||||
.map_err(|e| anyhow::anyhow!("subagent {i} panicked: {e:?}"))??;
|
||||
results.push(format!("Agent {i}: {result}"));
|
||||
}
|
||||
|
||||
Ok(format!(
|
||||
"Spawned {} agents.\n\nResults:\n{}",
|
||||
agents.len(),
|
||||
results.join("\n")
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a sequential pipeline of agent stages.
|
||||
///
|
||||
/// Flow: parse stages → load settings → for each stage, build a
|
||||
/// SubagentContext and call run_agent sequentially → collect results.
|
||||
pub struct SpawnPipeline;
|
||||
|
||||
impl Tool for SpawnPipeline {
|
||||
fn name(&self) -> &'static str {
|
||||
"spawn_pipeline"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Spawn a sequential pipeline of agent stages"
|
||||
}
|
||||
|
||||
fn parameters(&self) -> Value {
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"stages": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"directive": {"type": "string", "description": "Directive for this pipeline stage"}
|
||||
},
|
||||
"required": ["directive"]
|
||||
},
|
||||
"description": "Pipeline stages in order"
|
||||
}
|
||||
},
|
||||
"required": ["stages"]
|
||||
})
|
||||
}
|
||||
|
||||
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String> {
|
||||
let stages = args
|
||||
.get("stages")
|
||||
.and_then(|v| v.as_array())
|
||||
.ok_or_else(|| anyhow::anyhow!("missing 'stages' array"))?;
|
||||
|
||||
info!("Spawning pipeline with {} stages", stages.len());
|
||||
|
||||
// Load LLM credentials once for all stages
|
||||
let store = Store::new();
|
||||
let settings = JsonSettingsRepository::new()
|
||||
.load(&store.base_dir)
|
||||
.unwrap_or_default();
|
||||
let app_config = JsonAppConfigRepository::new()
|
||||
.load(&store.base_dir)
|
||||
.unwrap_or_default();
|
||||
|
||||
let (provider, model) =
|
||||
crate::subagent::provider::resolve_subagent_provider(&settings, &app_config);
|
||||
|
||||
let base_url = app_config
|
||||
.providers
|
||||
.get(&provider)
|
||||
.map(|p| p.api_base.clone())
|
||||
.unwrap_or_else(|| "https://opencode.ai/zen/v1".to_string());
|
||||
|
||||
let api_key = crate::llm::provider::resolve_api_key(&settings, &app_config);
|
||||
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
let mut pipeline_result = String::new();
|
||||
|
||||
for (i, stage) in stages.iter().enumerate() {
|
||||
let directive = stage
|
||||
.get("directive")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
let subagent_ctx = SubagentContext::new(
|
||||
directive.clone(),
|
||||
ctx.clone(),
|
||||
"full".to_string(),
|
||||
base_url.clone(),
|
||||
api_key.clone(),
|
||||
model.clone(),
|
||||
);
|
||||
|
||||
let result = rt.block_on(async {
|
||||
run_agent(subagent_ctx, &directive, AccessTier::Full, ctx.clone()).await
|
||||
})?;
|
||||
|
||||
pipeline_result.push_str(&format!("Stage {}: {}\n", i, result));
|
||||
}
|
||||
|
||||
Ok(format!(
|
||||
"Pipeline with {} stages completed.\n\n{}",
|
||||
stages.len(),
|
||||
pipeline_result
|
||||
))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user