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:
asepharyana
2026-07-20 09:04:57 +07:00
parent bceba665c0
commit da2ed6da25
454 changed files with 13979 additions and 29539 deletions
+225
View File
@@ -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
))
}
}