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,42 @@
|
||||
//! Hive-mind convergence documentation — writes deterministic audit trail.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use tracing::info;
|
||||
|
||||
use crate::workflow::hive_mind::types::NodeOutput;
|
||||
|
||||
/// Write a deterministic audit trail for a hive-mind convergence.
|
||||
///
|
||||
/// Flow: create docs/runs/ dir → build markdown content → write file.
|
||||
/// This is deterministic (not an LLM step) and never skippable.
|
||||
pub fn write_hive_mind_convergence(
|
||||
run_dir: &Path,
|
||||
nodes: &[NodeOutput],
|
||||
consensus: &str,
|
||||
) -> Result<PathBuf> {
|
||||
let docs_dir = run_dir.join("docs/runs");
|
||||
std::fs::create_dir_all(&docs_dir)?;
|
||||
|
||||
let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
|
||||
let filename = format!("{timestamp}-hive-mind-convergence.md");
|
||||
let filepath = docs_dir.join(&filename);
|
||||
|
||||
let mut content = String::new();
|
||||
content.push_str(&format!("# Hive Mind Convergence — {timestamp}\n\n"));
|
||||
|
||||
content.push_str("## Node Outputs\n\n");
|
||||
for node in nodes {
|
||||
content.push_str(&format!("### {} — {}\n\n", node.id, node.directive));
|
||||
content.push_str(&format!("{}\n\n", node.output));
|
||||
}
|
||||
|
||||
content.push_str("## Consensus\n\n");
|
||||
content.push_str(consensus);
|
||||
content.push('\n');
|
||||
|
||||
std::fs::write(&filepath, content)?;
|
||||
info!("Hive mind convergence written to {:?}", filepath);
|
||||
Ok(filepath)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//! Workflow execution — runs a parsed workflow script phase by phase.
|
||||
|
||||
use anyhow::Result;
|
||||
use tracing::info;
|
||||
|
||||
use crate::llm::provider::LlmClient;
|
||||
use crate::tools::ToolCtx;
|
||||
use crate::workflow::engine::primitives::execute_primitive;
|
||||
use crate::workflow::script::WorkflowScript;
|
||||
|
||||
/// Execute each phase of a workflow script sequentially.
|
||||
///
|
||||
/// Flow: for each phase → execute_primitive → collect result.
|
||||
pub async fn execute_workflow(
|
||||
script: &WorkflowScript,
|
||||
tool_ctx: &ToolCtx,
|
||||
_llm_client: &LlmClient,
|
||||
) -> Result<Vec<String>> {
|
||||
info!(
|
||||
"Executing workflow: {} ({} phases)",
|
||||
script.name,
|
||||
script.phases.len()
|
||||
);
|
||||
let mut results = Vec::new();
|
||||
for phase in &script.phases {
|
||||
info!("Executing phase: {}", phase.name);
|
||||
let result = execute_primitive(&phase.directive, tool_ctx).await?;
|
||||
results.push(result);
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
//! Workflow execution engine — runs phases, primitives, and manages agent
|
||||
//! lifecycle during workflow runs.
|
||||
|
||||
pub mod execution;
|
||||
pub mod phases;
|
||||
pub mod primitives;
|
||||
@@ -0,0 +1,11 @@
|
||||
//! Workflow phases — individual stages of a multi-phase workflow.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A single phase in a multi-phase workflow.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct WorkflowPhase {
|
||||
pub name: String,
|
||||
pub directive: String,
|
||||
pub parallel_agents: usize,
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//! Primitive execution — runs a single directive as a subagent.
|
||||
//!
|
||||
//! Flow: load settings → resolve LLM credentials → build SubagentContext →
|
||||
//! run_agent with Full access tier → return output.
|
||||
|
||||
use anyhow::Result;
|
||||
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::tools::ToolCtx;
|
||||
|
||||
/// Execute a single directive by spawning a subagent.
|
||||
///
|
||||
/// Flow:
|
||||
/// 1. Load `Settings` and `AppConfig` from the store directory.
|
||||
/// 2. Resolve provider, model, base_url, and api_key.
|
||||
/// 3. Build a `SubagentContext` with all resolved params.
|
||||
/// 4. Call `run_agent` with Full access (all tools available).
|
||||
/// 5. Return the agent's text output.
|
||||
pub async fn execute_primitive(directive: &str, tool_ctx: &ToolCtx) -> Result<String> {
|
||||
info!("Executing primitive: {directive}");
|
||||
|
||||
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 ctx = SubagentContext::new(
|
||||
directive.to_string(),
|
||||
tool_ctx.clone(),
|
||||
"full".to_string(),
|
||||
base_url,
|
||||
api_key,
|
||||
model,
|
||||
);
|
||||
|
||||
run_agent(ctx, directive, AccessTier::Full, tool_ctx.clone()).await
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//! Complexity heuristics — determine whether a request is complex enough to
|
||||
//! warrant hive-mind orchestration.
|
||||
|
||||
/// Heuristics to determine if a request is complex enough for hive-mind.
|
||||
pub fn is_complex_request(task: &str) -> bool {
|
||||
let complexity_indicators = [
|
||||
"refactor",
|
||||
"redesign",
|
||||
"multiple files",
|
||||
"architecture",
|
||||
"migration",
|
||||
"comprehensive",
|
||||
"end-to-end",
|
||||
"full-stack",
|
||||
];
|
||||
|
||||
let task_lower = task.to_lowercase();
|
||||
complexity_indicators
|
||||
.iter()
|
||||
.any(|&indicator| task_lower.contains(indicator))
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
//! Hive-mind cycle execution — run one cycle of parallel nodes.
|
||||
//!
|
||||
//! Flow: load settings → resolve LLM credentials → for each directive,
|
||||
//! build a SubagentContext and call run_agent → collect NodeOutputs.
|
||||
|
||||
use anyhow::Result;
|
||||
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::tools::ToolCtx;
|
||||
use crate::workflow::hive_mind::types::{CognitiveCycle, NodeOutput};
|
||||
|
||||
/// Execute one cycle: run each node directive and collect outputs.
|
||||
///
|
||||
/// Flow:
|
||||
/// 1. Load `Settings` and `AppConfig` from the store directory.
|
||||
/// 2. Resolve provider, model, base_url, and api_key.
|
||||
/// 3. For each directive → build `SubagentContext` → `run_agent` (Full access).
|
||||
/// 4. Collect `NodeOutput` results.
|
||||
pub async fn execute_cycle(
|
||||
cycle: &CognitiveCycle,
|
||||
tool_ctx: &ToolCtx,
|
||||
) -> Result<Vec<NodeOutput>> {
|
||||
info!(
|
||||
"Executing cycle {} with {} directives",
|
||||
cycle.index,
|
||||
cycle.directives.len()
|
||||
);
|
||||
|
||||
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 outputs = Vec::new();
|
||||
for (i, directive) in cycle.directives.iter().enumerate() {
|
||||
let ctx = SubagentContext::new(
|
||||
directive.clone(),
|
||||
tool_ctx.clone(),
|
||||
"full".to_string(),
|
||||
base_url.clone(),
|
||||
api_key.clone(),
|
||||
model.clone(),
|
||||
);
|
||||
|
||||
let result = run_agent(ctx, directive, AccessTier::Full, tool_ctx.clone()).await?;
|
||||
|
||||
outputs.push(NodeOutput {
|
||||
id: format!("Node-{}-{}", cycle.index, i),
|
||||
directive: directive.clone(),
|
||||
output: result,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(outputs)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//! Hive-mind live tracking — track running node statuses in real time.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Mutex;
|
||||
|
||||
/// Real-time status of all running hive-mind nodes.
|
||||
pub struct LiveHiveMind {
|
||||
nodes: Mutex<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl LiveHiveMind {
|
||||
pub fn new() -> Self {
|
||||
LiveHiveMind {
|
||||
nodes: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_status(&self, agent_id: &str, status: &str) {
|
||||
if let Ok(mut guard) = self.nodes.lock() {
|
||||
guard.insert(agent_id.to_string(), status.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_statuses(&self) -> HashMap<String, String> {
|
||||
self.nodes.lock().map(|g| g.clone()).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
//! Hive-mind orchestration — multi-agent parallel convergence cycles.
|
||||
|
||||
pub mod complexity;
|
||||
pub mod cycle;
|
||||
pub mod live;
|
||||
pub mod synthesis;
|
||||
pub mod types;
|
||||
@@ -0,0 +1,38 @@
|
||||
//! Consensus synthesis — reconciles multiple node outputs into one assessment.
|
||||
|
||||
use anyhow::Result;
|
||||
use tracing::info;
|
||||
|
||||
use crate::tools::ToolCtx;
|
||||
use crate::workflow::hive_mind::types::NodeOutput;
|
||||
|
||||
/// Synthesize a consensus from all node outputs.
|
||||
///
|
||||
/// Flow: combine node outputs → return consensus text.
|
||||
/// Uses simple concatenation-based synthesis (avoids LLM call dependency).
|
||||
pub async fn synthesize_consensus(
|
||||
nodes: &[NodeOutput],
|
||||
_tool_ctx: &ToolCtx,
|
||||
) -> Result<String> {
|
||||
info!("Synthesizing consensus from {} nodes", nodes.len());
|
||||
|
||||
let mut combined = String::new();
|
||||
for node in nodes {
|
||||
combined.push_str(&format!(
|
||||
"\n## {} — {}\n\n{}\n",
|
||||
node.id, node.directive, node.output
|
||||
));
|
||||
}
|
||||
|
||||
Ok(format!(
|
||||
"# Consensus Synthesis\n\
|
||||
Nodes synthesized: {}\n\n\
|
||||
## Summary\n\
|
||||
The following node outputs were collected:\n\
|
||||
{}\n\n\
|
||||
## Key Findings\n\
|
||||
Review the individual node outputs above for detailed findings.",
|
||||
nodes.len(),
|
||||
combined
|
||||
))
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//! Hive-mind shared types — node directives, cycle plans, and node outputs.
|
||||
|
||||
/// A directive for a single processing node in the hive mind.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NodeDirective {
|
||||
pub directive: String,
|
||||
pub access_tier: String,
|
||||
}
|
||||
|
||||
/// A cognitive cycle plan — ordered list of cycles, each containing
|
||||
/// parallel node directives.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CognitiveCyclePlan {
|
||||
pub cycles: Vec<Vec<NodeDirective>>,
|
||||
}
|
||||
|
||||
/// A single cycle in a cognitive cycle plan — parallel node directives
|
||||
/// executed together.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CognitiveCycle {
|
||||
pub index: u32,
|
||||
pub directives: Vec<String>,
|
||||
}
|
||||
|
||||
/// Output from a single hive-mind processing node after a cycle completes.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NodeOutput {
|
||||
pub id: String,
|
||||
pub directive: String,
|
||||
pub output: String,
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
//! Workflow engine — hive-mind orchestration and script execution.
|
||||
|
||||
pub mod docs;
|
||||
pub mod engine;
|
||||
pub mod hive_mind;
|
||||
pub mod script;
|
||||
@@ -0,0 +1,65 @@
|
||||
//! Workflow script — parse and execute user-defined workflow scripts.
|
||||
|
||||
use anyhow::Result;
|
||||
use tracing::info;
|
||||
|
||||
/// A single phase in a parsed workflow script.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WorkflowPhase {
|
||||
pub name: String,
|
||||
pub directive: String,
|
||||
}
|
||||
|
||||
/// A parsed workflow script with named phases.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WorkflowScript {
|
||||
pub name: String,
|
||||
pub phases: Vec<WorkflowPhase>,
|
||||
}
|
||||
|
||||
impl WorkflowScript {
|
||||
/// Parse a YAML string into a WorkflowScript.
|
||||
///
|
||||
/// Expected format:
|
||||
/// ```yaml
|
||||
/// name: my-workflow
|
||||
/// phases:
|
||||
/// - name: research
|
||||
/// directive: "Explore the codebase..."
|
||||
/// - name: implement
|
||||
/// directive: "Implement the changes..."
|
||||
/// ```
|
||||
pub fn parse(yaml: &str) -> Result<Self> {
|
||||
let parsed: serde_json::Value = serde_yaml_ng::from_str(yaml)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to parse workflow YAML: {e}"))?;
|
||||
|
||||
let name = parsed
|
||||
.get("name")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("unnamed")
|
||||
.to_string();
|
||||
|
||||
let mut phases = Vec::new();
|
||||
if let Some(phases_arr) = parsed.get("phases").and_then(|v| v.as_array()) {
|
||||
for phase_val in phases_arr {
|
||||
let phase_name = phase_val
|
||||
.get("name")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("phase")
|
||||
.to_string();
|
||||
let directive = phase_val
|
||||
.get("directive")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
phases.push(WorkflowPhase {
|
||||
name: phase_name,
|
||||
directive,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
info!("Parsed workflow script: {name} ({} phases)", phases.len());
|
||||
Ok(WorkflowScript { name, phases })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user