Add subagent prompts and implement company workflow orchestration

- Introduced prompts for various subagent roles: architecture reviewer, code quality reviewer, documentation maintainer, implementation team, testing team, and security reviewer.
- Implemented the auto-subagent orchestration in `auto.rs` to manage inline and background reviews.
- Created a division structure in `division.rs` to define roles and responsibilities for each subagent.
- Developed a company workflow orchestrator in `company.rs` to run the complete division pipeline, consolidating findings and generating executive summaries.
- Added logic to determine whether to run a full or quick pipeline based on request complexity.
This commit is contained in:
asepharyana
2026-07-13 05:23:38 +07:00
parent 2856dd78b8
commit a6eed9e574
21 changed files with 1577 additions and 85 deletions
+88 -28
View File
@@ -4,9 +4,8 @@
//! rich panel showing agent statuses, findings count, session counters,
//! and usage hints.
//!
//! Why: the panel is useful even without a running session (shows engine
//! state and instructions), and only shows non-zero counters to keep it
//! compact.
//! Division-aware: when the workflow is a company pipeline, shows the
//! division pipeline header with visual arrows between stages.
use ratatui::layout::Rect;
use ratatui::style::{Style, Modifier};
@@ -16,21 +15,45 @@ use ratatui::Frame;
use super::theme::Theme;
use crate::app::workflow::engine::AgentState;
/// Icons for division states in the company pipeline.
fn div_icon(state: AgentState) -> &'static str {
match state {
AgentState::Idle => "",
AgentState::Running => "",
AgentState::Completed => "",
AgentState::Failed => "",
}
}
/// Detect if the current workflow looks like a company pipeline by
/// checking agent names for division keywords.
fn is_company_pipeline(agents: &[crate::app::workflow::engine::WorkflowAgent]) -> bool {
if agents.is_empty() {
return false;
}
// Company pipeline agents have names like "Strategy", "Engineering", etc.
let division_keywords = ["Strategy", "Engineering", "Quality", "Security", "Documentation"];
agents.iter().any(|a| {
division_keywords.iter().any(|k| a.name.contains(k))
})
}
/// Render the workflow status panel.
///
/// Flow: build header lines (title + usage hints) → if agents exist,
/// list each with its lifecycle state colour-coded → show findings
/// count and session counters → fall back to an instruction paragraph
/// when no agents have been spawned yet.
///
/// Return: nothing; draws directly into `frame` at `area`.
pub fn draw_workflow_panel(frame: &mut Frame, area: Rect, state: &crate::app::state::rest::AppStateRest) {
use ratatui::layout::{Constraint, Direction, Layout};
let is_company = is_company_pipeline(&state.workflow_engine.agents);
let block = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(Theme::PRIMARY))
.title(Span::styled(" ⚙ Workflow ", Style::default().fg(Theme::PRIMARY).add_modifier(Modifier::BOLD)));
.title({
if is_company {
Span::styled(" 🏢 Company Pipeline ", Style::default().fg(Theme::PRIMARY).add_modifier(Modifier::BOLD))
} else {
Span::styled(" ⚙ Workflow ", Style::default().fg(Theme::PRIMARY).add_modifier(Modifier::BOLD))
}
});
let inner = block.inner(area);
frame.render_widget(block, area);
@@ -44,14 +67,53 @@ pub fn draw_workflow_panel(frame: &mut Frame, area: Rect, state: &crate::app::st
])
.split(inner);
// ── Header: usage hints ─────────────────────────────────────────────────
let hint_lines = vec![
Line::from(vec![
// ── Header ─────────────────────────────────────────────────────────
let mut header_lines = vec![];
if is_company {
// Show the division pipeline header with visual arrows
let agents = &state.workflow_engine.agents;
let mut pipeline_spans: Vec<Span> = Vec::new();
for (i, agent) in agents.iter().enumerate() {
if i > 0 {
pipeline_spans.push(Span::styled("", Style::default().fg(Theme::DIM)));
}
let icon = div_icon(agent.status.state);
let (color, modif) = match agent.status.state {
AgentState::Idle => (Theme::DIM, Modifier::empty()),
AgentState::Running => (Theme::WARNING, Modifier::BOLD),
AgentState::Completed => (Theme::SUCCESS, Modifier::BOLD),
AgentState::Failed => (Theme::ERROR, Modifier::BOLD),
};
pipeline_spans.push(Span::styled(
format!("{} {} ", icon, agent.name.chars().take(12).collect::<String>()),
Style::default().fg(color).add_modifier(modif),
));
}
header_lines.push(Line::from(pipeline_spans));
header_lines.push(Line::from(vec![
Span::styled("Status: ", Style::default().fg(Theme::DIM)),
if state.turn_in_flight() {
Span::styled("● Pipeline Running", Style::default().fg(Theme::WARNING).add_modifier(Modifier::BOLD))
} else {
Span::styled("● Pipeline Complete", Style::default().fg(Theme::SUCCESS))
},
Span::raw(" "),
Span::styled(
format!("Divisions: {} Findings: {}",
state.workflow_engine.agents.len(),
state.workflow_engine.findings.len(),
),
Style::default().fg(Theme::DIM),
),
]));
} else {
header_lines.push(Line::from(vec![
Span::styled("/workflow run ", Style::default().fg(Theme::PRIMARY).add_modifier(Modifier::BOLD)),
Span::styled("<prompt>", Style::default().fg(Theme::DIM)),
Span::styled(" · Esc to close", Style::default().fg(Theme::DIM)),
]),
Line::from(vec![
]));
header_lines.push(Line::from(vec![
Span::styled("Status: ", Style::default().fg(Theme::DIM)),
if state.turn_in_flight() {
Span::styled("● Running", Style::default().fg(Theme::WARNING).add_modifier(Modifier::BOLD))
@@ -66,25 +128,23 @@ pub fn draw_workflow_panel(frame: &mut Frame, area: Rect, state: &crate::app::st
),
Style::default().fg(Theme::DIM),
),
]),
];
let header = Paragraph::new(hint_lines);
]));
}
let header = Paragraph::new(header_lines);
frame.render_widget(header, chunks[0]);
// ── Body: agent list or placeholder ─────────────────────────────────────
// ── Body: agent/division list ──────────────────────────────────────
if state.workflow_engine.agents.is_empty() {
// No agents yet — show session counters and a welcome message
let session_lines = build_session_lines(state);
let placeholder = Paragraph::new(session_lines).wrap(Wrap { trim: false });
frame.render_widget(placeholder, chunks[1]);
} else {
// Build agent status list
let items: Vec<ListItem> = state.workflow_engine.agents.iter().map(|agent| {
let (state_str, state_color) = match agent.status.state {
AgentState::Idle => ("Idle", Theme::DIM),
AgentState::Running => ("Running…", Theme::WARNING),
AgentState::Completed => ("Done", Theme::SUCCESS),
AgentState::Failed => ("Failed", Theme::ERROR),
AgentState::Idle => ("○ Idle", Theme::DIM),
AgentState::Running => ("Running…", Theme::WARNING),
AgentState::Completed => ("Done", Theme::SUCCESS),
AgentState::Failed => ("Failed", Theme::ERROR),
};
let duration_str = match (agent.status.started_at, agent.status.completed_at) {
(Some(s), Some(e)) => format!(" {}ms", e.saturating_sub(s)),
@@ -93,7 +153,7 @@ pub fn draw_workflow_panel(frame: &mut Frame, area: Rect, state: &crate::app::st
};
ListItem::new(Line::from(vec![
Span::styled(
format!(" {:8} ", state_str),
format!(" {:12} ", state_str),
Style::default().fg(state_color).add_modifier(Modifier::BOLD),
),
Span::styled(
@@ -159,7 +219,7 @@ fn build_session_lines(state: &crate::app::state::rest::AppStateRest) -> Vec<Lin
lines.push(Line::from(Span::raw("")));
lines.push(Line::from(Span::styled(
" Run a workflow to see agents here.",
" Complex tasks auto-delegate to the company pipeline.",
Style::default().fg(Theme::DIM).add_modifier(Modifier::ITALIC),
)));