feat: implement company pipeline orchestration with user commands for full, quick, and skip modes
This commit is contained in:
+43
-28
@@ -185,7 +185,11 @@ pub fn run_company_pipeline_quick(
|
||||
Ok(build_executive_summary(user_request, &results, &all_findings, quick_divisions))
|
||||
}
|
||||
|
||||
/// Build a consolidated executive summary from pipeline results.
|
||||
/// Build a compressed executive summary from pipeline results.
|
||||
///
|
||||
/// Keeps output brief to save context window space — just division verdicts
|
||||
/// and key findings, not full outputs. Full results are accessible to the
|
||||
/// CEO via the notes/findings that were archived during execution.
|
||||
fn build_executive_summary(
|
||||
request: &str,
|
||||
results: &[String],
|
||||
@@ -193,41 +197,23 @@ fn build_executive_summary(
|
||||
divisions: &[division::Division],
|
||||
) -> String {
|
||||
let mut summary = String::new();
|
||||
summary.push_str(&format!("# Company Pipeline — Executive Summary\n\n"));
|
||||
summary.push_str(&format!("**Request**: {}\n\n", request));
|
||||
summary.push_str("## Division Results\n\n");
|
||||
summary.push_str(&format!("Pipeline for: {}\n", request));
|
||||
|
||||
for (i, div) in divisions.iter().enumerate() {
|
||||
let result_summary = results.get(i)
|
||||
let verdict = results.get(i)
|
||||
.map(|r| {
|
||||
let first_line = r.lines().next().unwrap_or(r);
|
||||
if first_line.len() > 120 {
|
||||
format!("{}...", &first_line[..117])
|
||||
} else {
|
||||
first_line.to_string()
|
||||
}
|
||||
r.lines().next().unwrap_or(r)
|
||||
.chars().take(100).collect::<String>()
|
||||
})
|
||||
.unwrap_or_else(|| "No output".to_string());
|
||||
.unwrap_or_else(|| "—".to_string());
|
||||
|
||||
summary.push_str(&format!("### {} Division\n", div.name));
|
||||
summary.push_str(&format!("- Role: {}\n", div.description));
|
||||
summary.push_str(&format!("- Result: {}\n\n", result_summary));
|
||||
summary.push_str(&format!(" {}: {}\n", div.name, verdict));
|
||||
}
|
||||
|
||||
if !findings.is_empty() {
|
||||
summary.push_str("## Cross-Division Findings\n\n");
|
||||
for (i, f) in findings.iter().enumerate() {
|
||||
summary.push_str(&format!("{}. {}\n", i + 1, f));
|
||||
}
|
||||
summary.push_str("\n");
|
||||
summary.push_str(&format!(" Notes: {} cross-division finding(s)\n", findings.len()));
|
||||
}
|
||||
|
||||
summary.push_str("---\n");
|
||||
summary.push_str(&format!(
|
||||
"Pipeline completed: {} division(s) executed.\n",
|
||||
divisions.len(),
|
||||
));
|
||||
|
||||
summary
|
||||
}
|
||||
|
||||
@@ -239,14 +225,43 @@ fn build_executive_summary(
|
||||
///
|
||||
/// Used by the auto-CEO pipeline trigger in run_agent_turn to decide
|
||||
/// whether to delegate to the full company pipeline or handle directly.
|
||||
///
|
||||
/// Heuristics:
|
||||
/// - Very short requests (< 10 chars) are never complex.
|
||||
/// - Negative keywords (simple/trivial/typo/quick) skip the pipeline.
|
||||
/// - Positive keywords (refactor/api/implement/architecture) trigger it.
|
||||
/// - Multi-line or multi-sentence requests are more likely complex.
|
||||
pub fn is_complex_request(request: &str) -> bool {
|
||||
let trimmed = request.trim();
|
||||
// Very short requests are never complex
|
||||
if trimmed.len() < 10 {
|
||||
return false;
|
||||
}
|
||||
// Single-line simple update patterns
|
||||
let lower = trimmed.to_lowercase();
|
||||
let negative_keywords = [
|
||||
"simple", "trivial", "typo", "just a", "only a", "minor",
|
||||
"quick", "tiny", "small fix", "rename", "nitpick",
|
||||
"cosmetic", "formatting", "spelling", "grammar",
|
||||
"bump", "version bump", "update comment",
|
||||
];
|
||||
if negative_keywords.iter().any(|k| lower.contains(k)) {
|
||||
return false;
|
||||
}
|
||||
// Multi-line/multi-sentence → likely complex
|
||||
let sentences = trimmed.split(|c| c == '.' || c == '!' || c == '?')
|
||||
.filter(|s| !s.trim().is_empty())
|
||||
.count();
|
||||
if sentences >= 3 {
|
||||
return true;
|
||||
}
|
||||
// Positive complexity keywords
|
||||
let complexity_keywords = [
|
||||
"refactor", "redesign", "architecture", "feature", "implement",
|
||||
"migrate", "restructure", "rewrite", "new module", "new component",
|
||||
"scaffold", "multi", "multiple files", "api", "endpoint",
|
||||
"integration", "system", "workflow", "pipeline", "database",
|
||||
"authentication", "authorization",
|
||||
"authentication", "authorization", "full stack",
|
||||
];
|
||||
let lower = request.to_lowercase();
|
||||
complexity_keywords.iter().any(|k| lower.contains(k))
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ pub struct AgentStatus {
|
||||
pub started_at: Option<i64>,
|
||||
pub completed_at: Option<i64>,
|
||||
pub error: Option<String>,
|
||||
/// Human-readable progress message (e.g. "editing src/main.rs",
|
||||
/// "running cargo test"). Shown in the TUI panel alongside the state.
|
||||
pub progress: Option<String>,
|
||||
}
|
||||
|
||||
/// A single agent tracked within a workflow run.
|
||||
@@ -124,6 +127,7 @@ fn spawn_single_agent(
|
||||
started_at: Some(started_at),
|
||||
completed_at: None,
|
||||
error: None,
|
||||
progress: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -156,26 +160,52 @@ fn spawn_single_agent(
|
||||
// long-running agents. No abort mechanism is wired yet at this level;
|
||||
// future work can expose a kill-switch per agent via the live callback.
|
||||
|
||||
// Create an mpsc channel and drain events in a background thread so
|
||||
// run_subagent's blocking_send never blocks (previously the _rx was
|
||||
// dropped immediately, which would cause blocking_send to panic/fail
|
||||
// on a closed channel).
|
||||
// Create an mpsc channel and drain events in a background thread.
|
||||
// The drain thread also pushes intra-division progress updates to the
|
||||
// live callback (current tool being executed), so the TUI panel shows
|
||||
// real-time "editing X" or "running build" instead of just "Running…".
|
||||
let (tx, rx) = tokio::sync::mpsc::channel(64);
|
||||
let drain_agent_id = agent_id.to_string();
|
||||
let drain_agent_name = agent_name.to_string();
|
||||
let drain_live = live.cloned();
|
||||
let drain_started_at = started_at;
|
||||
let _drain_thread = std::thread::spawn(move || {
|
||||
// Drain all events so run_subagent's blocking_send never blocks.
|
||||
// Individual SubagentEvent items are not surfaced to the TUI —
|
||||
// the live state callbacks above handle coarse-grained Running /
|
||||
// Completed / Failed status. ToolCall / ToolResult / StepCompleted
|
||||
// events are traced at debug level for observability.
|
||||
use crate::app::subagent::event::SubagentEvent;
|
||||
let mut rx = rx;
|
||||
while let Some(event) = rx.blocking_recv() {
|
||||
match &event {
|
||||
SubagentEvent::ToolCall { _tool, _args } => {
|
||||
tracing::debug!("[subagent] tool call: {}", _tool);
|
||||
// Push intra-division progress: which tool is running
|
||||
if let Some(ref f) = drain_live {
|
||||
f(
|
||||
drain_agent_id.clone(),
|
||||
drain_agent_name.clone(),
|
||||
AgentStatus {
|
||||
state: AgentState::Running,
|
||||
started_at: Some(drain_started_at),
|
||||
completed_at: None,
|
||||
error: None,
|
||||
progress: Some(format!("tool: {}", _tool)),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
SubagentEvent::ToolResult { _tool, .. } => {
|
||||
tracing::debug!("[subagent] tool result: {}", _tool);
|
||||
if let Some(ref f) = drain_live {
|
||||
f(
|
||||
drain_agent_id.clone(),
|
||||
drain_agent_name.clone(),
|
||||
AgentStatus {
|
||||
state: AgentState::Running,
|
||||
started_at: Some(drain_started_at),
|
||||
completed_at: None,
|
||||
error: None,
|
||||
progress: Some(format!("done: {}", _tool)),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
SubagentEvent::StepCompleted { _step, .. } => {
|
||||
tracing::trace!("[subagent] step {} completed", _step);
|
||||
@@ -226,6 +256,7 @@ fn spawn_single_agent(
|
||||
started_at: Some(started_at),
|
||||
completed_at: Some(completed_at),
|
||||
error: None,
|
||||
progress: None,
|
||||
},
|
||||
),
|
||||
Err(e) => f(
|
||||
@@ -236,6 +267,7 @@ fn spawn_single_agent(
|
||||
started_at: Some(started_at),
|
||||
completed_at: Some(completed_at),
|
||||
error: Some(e.to_string()),
|
||||
progress: None,
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user