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
+42
View File
@@ -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,
}
+6
View File
@@ -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 })
}
}