Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,026 changes: 1,008 additions & 18 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ resolver = "2"

[workspace.package]
version = "0.1.0"
edition = "2021"
rust-version = "1.80"
edition = "2024"
rust-version = "1.85"
license = "Apache-2.0"

[workspace.dependencies]
Expand All @@ -20,6 +20,7 @@ csa-resource = { path = "crates/csa-resource" }
csa-scheduler = { path = "crates/csa-scheduler" }
csa-todo = { path = "crates/csa-todo" }
csa-hooks = { path = "crates/csa-hooks" }
agent-teams = { path = "../agent-teams-rs" }

# CLI & Async
clap = { version = "4.5", features = ["derive"] }
Expand All @@ -35,6 +36,7 @@ ulid = { version = "1.1", features = ["serde"] }
# Error handling & logging
anyhow = "1.0"
thiserror = "2.0"
async-trait = "0.1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-appender = "0.2"
Expand Down
25 changes: 21 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ If you only use a single AI tool for simple tasks, the tool's native CLI may suf

## Installation

## Prerequisites

Requires [Rust toolchain](https://rustup.rs/) and a sibling checkout for the local path crate dependency:

```toml
agent-teams = { path = "../agent-teams-rs" }
```

Expected directory layout:

```text
<parent>/
cli-sub-agent/
agent-teams-rs/
```

### Quick Install (macOS / Linux)

```bash
Expand All @@ -50,17 +66,18 @@ curl -sSf https://raw.githubusercontent.com/RyderFreeman4Logos/cli-sub-agent/mai

### Manual Install

Requires [Rust toolchain](https://rustup.rs/):
Clone both repositories into the same parent directory, then install from source:

```bash
cargo install --git https://github.com/RyderFreeman4Logos/cli-sub-agent \
-p cli-sub-agent --all-features --locked
git clone https://github.com/RyderFreeman4Logos/cli-sub-agent.git
git clone https://github.com/RyderFreeman4Logos/agent-teams-rs.git
cd cli-sub-agent
cargo install --all-features --path crates/cli-sub-agent
```

### From Source

```bash
git clone https://github.com/RyderFreeman4Logos/cli-sub-agent
cd cli-sub-agent
cargo install --all-features --path crates/cli-sub-agent
```
Expand Down
4 changes: 2 additions & 2 deletions crates/cli-sub-agent/src/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ async fn execute_parallel_tasks(
let config = config.cloned();

// Check resource availability before spawning (best effort)
if let Some(ref mut guard) = resource_guard {
if let Some(guard) = resource_guard {
let tool_name = parse_tool_name(&task.tool)?;
if let Err(e) = guard.check_availability(tool_name.as_str()) {
warn!(
Expand Down Expand Up @@ -497,7 +497,7 @@ async fn execute_task(
};

// Check resource availability
if let Some(ref mut guard) = resource_guard {
if let Some(guard) = resource_guard {
if let Err(e) = guard.check_availability(executor.tool_name()) {
error!("{} - Resource check failed: {}", task_label, e);
return TaskResult {
Expand Down
36 changes: 35 additions & 1 deletion crates/cli-sub-agent/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use clap::{Parser, Subcommand};
use clap::{Parser, Subcommand, ValueEnum};
use csa_core::types::{OutputFormat, ToolArg, ToolName};

#[derive(Parser)]
Expand Down Expand Up @@ -215,6 +215,18 @@ pub struct ReviewArgs {
#[arg(long)]
pub context: Option<String>,

/// Number of reviewers to run in parallel (default: 1)
#[arg(long, default_value_t = 1, value_parser = clap::value_parser!(u32).range(1..))]
pub reviewers: u32,

/// Consensus strategy for multi-reviewer mode
#[arg(
long,
default_value = "majority",
value_parser = ["majority", "weighted", "unanimous"]
)]
pub consensus: String,

/// Working directory
#[arg(long)]
pub cd: Option<String>,
Expand Down Expand Up @@ -578,4 +590,26 @@ pub enum TodoCommands {
#[arg(long)]
cd: Option<String>,
},

/// Visualize TODO task dependency DAG
Dag {
/// Timestamp of the TODO plan (default: latest)
#[arg(short, long)]
timestamp: Option<String>,

/// DAG output format
#[arg(long, default_value = "mermaid")]
format: TodoDagFormat,

/// Working directory
#[arg(long)]
cd: Option<String>,
},
}

#[derive(Clone, Copy, Debug, ValueEnum)]
pub enum TodoDagFormat {
Mermaid,
Terminal,
Dot,
}
2 changes: 1 addition & 1 deletion crates/cli-sub-agent/src/config_cmds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use anyhow::Result;
use tracing::{error, warn};

use csa_config::init::init_project;
use csa_config::{validate_config, GlobalConfig, ProjectConfig};
use csa_config::{GlobalConfig, ProjectConfig, validate_config};
use csa_core::types::OutputFormat;

pub(crate) fn handle_config_show(cd: Option<String>, format: OutputFormat) -> Result<()> {
Expand Down
64 changes: 55 additions & 9 deletions crates/cli-sub-agent/src/debate_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::run_helpers::read_prompt;
use csa_config::global::{heterogeneous_counterpart, select_heterogeneous_tool};
use csa_config::{GlobalConfig, ProjectConfig};
use csa_core::types::ToolName;
use csa_executor::extract_session_id;

pub(crate) async fn handle_debate(args: DebateArgs, current_depth: u32) -> Result<i32> {
// 1. Determine project root
Expand Down Expand Up @@ -56,7 +57,7 @@ pub(crate) async fn handle_debate(args: DebateArgs, current_depth: u32) -> Resul
"debate: {}",
crate::run_helpers::truncate_prompt(&question, 80)
);
let result = crate::pipeline::execute_with_session(
let execution = crate::pipeline::execute_with_session_and_meta(
&executor,
&tool,
&prompt,
Expand All @@ -71,10 +72,17 @@ pub(crate) async fn handle_debate(args: DebateArgs, current_depth: u32) -> Resul
)
.await?;

let provider_session_id = extract_session_id(&tool, &execution.execution.output);
let output = render_debate_output(
&execution.execution.output,
&execution.meta_session_id,
provider_session_id.as_deref(),
);

// 10. Print result
print!("{}", result.output);
print!("{output}");

Ok(result.exit_code)
Ok(execution.execution.exit_code)
}

fn resolve_debate_tool(
Expand Down Expand Up @@ -203,6 +211,24 @@ fn build_debate_instruction(question: &str, is_continuation: bool) -> String {
}
}

fn render_debate_output(
tool_output: &str,
meta_session_id: &str,
provider_session_id: Option<&str>,
) -> String {
let mut output = match provider_session_id {
Some(provider_id) => tool_output.replace(provider_id, meta_session_id),
None => tool_output.to_string(),
};

if !output.is_empty() && !output.ends_with('\n') {
output.push('\n');
}

output.push_str(&format!("CSA Meta Session ID: {meta_session_id}\n"));
output
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -293,9 +319,10 @@ mod tests {
std::path::Path::new("/tmp/test-project"),
)
.unwrap_err();
assert!(err
.to_string()
.contains("AUTO debate tool selection failed"));
assert!(
err.to_string()
.contains("AUTO debate tool selection failed")
);
}

#[test]
Expand All @@ -310,9 +337,10 @@ mod tests {
std::path::Path::new("/tmp/test-project"),
)
.unwrap_err();
assert!(err
.to_string()
.contains("AUTO debate tool selection failed"));
assert!(
err.to_string()
.contains("AUTO debate tool selection failed")
);
}

#[test]
Expand Down Expand Up @@ -368,4 +396,22 @@ mod tests {
assert!(prompt.contains("continuation=true"));
assert!(prompt.contains("I disagree because X"));
}

#[test]
fn render_debate_output_appends_meta_session_id() {
let output = render_debate_output("debate answer", "01ARZ3NDEKTSV4RRFFQ69G5FAV", None);
assert!(output.contains("debate answer"));
assert!(output.contains("CSA Meta Session ID: 01ARZ3NDEKTSV4RRFFQ69G5FAV"));
}

#[test]
fn render_debate_output_replaces_provider_id_with_meta_id() {
let provider = "019c5589-3c84-7f03-b9c4-9f0a164c4eb2";
let meta = "01ARZ3NDEKTSV4RRFFQ69G5FAV";
let tool_output = format!("session_id={provider}\nresult=ok");

let output = render_debate_output(&tool_output, meta, Some(provider));
assert!(!output.contains(provider));
assert!(output.contains(meta));
}
}
8 changes: 5 additions & 3 deletions crates/cli-sub-agent/src/gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use tracing::{info, warn};
use csa_config::GlobalConfig;
use csa_core::types::OutputFormat;
use csa_session::{
delete_session, get_session_dir, get_session_root, list_sessions, save_session_in, PhaseEvent,
PhaseEvent, delete_session, get_session_dir, get_session_root, list_sessions, save_session_in,
};

/// Default age threshold (in days) for retiring stale Active sessions.
Expand Down Expand Up @@ -198,7 +198,8 @@ pub(crate) fn handle_gc(
if dry_run {
eprintln!(
"[dry-run] Would clean stale slot: {:?} (dead PID {})",
path.file_name(), pid
path.file_name(),
pid
);
stale_slots_cleaned += 1;
} else if fs::remove_file(&path).is_ok() {
Expand Down Expand Up @@ -522,7 +523,8 @@ pub(crate) fn handle_gc_global(
if dry_run {
eprintln!(
"[dry-run] Would clean stale slot: {:?} (dead PID {})",
path.file_name(), pid
path.file_name(),
pid
);
stale_slots_cleaned += 1;
} else if fs::remove_file(&path).is_ok() {
Expand Down
14 changes: 12 additions & 2 deletions crates/cli-sub-agent/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod mcp_server;
mod pipeline;
mod process_tree;
mod review_cmd;
mod review_consensus;
mod run_helpers;
mod self_update;
mod session_cmds;
Expand All @@ -29,7 +30,7 @@ use cli::{
use csa_config::GlobalConfig;
use csa_core::types::{OutputFormat, ToolArg, ToolSelectionStrategy};
use csa_lock::slot::{
format_slot_diagnostic, slot_usage, try_acquire_slot, SlotAcquireResult, ToolSlot,
SlotAcquireResult, ToolSlot, format_slot_diagnostic, slot_usage, try_acquire_slot,
};
use csa_session::{load_session, resolve_session_prefix};
use run_helpers::{
Expand Down Expand Up @@ -244,6 +245,13 @@ async fn main() -> Result<()> {
} => {
todo_cmd::handle_status(timestamp, status, cd)?;
}
TodoCommands::Dag {
timestamp,
format,
cd,
} => {
todo_cmd::handle_dag(timestamp, format, cd)?;
}
},
Commands::SelfUpdate { check } => {
self_update::handle_self_update(check)?;
Expand Down Expand Up @@ -369,7 +377,9 @@ async fn handle_run(
}
} else {
// No parent context/default fallback, fall back to AnyAvailable with warning
warn!("HeterogeneousStrict requested but no parent tool context/defaults.tool found. Falling back to AnyAvailable.");
warn!(
"HeterogeneousStrict requested but no parent tool context/defaults.tool found. Falling back to AnyAvailable."
);
resolve_tool_and_model(
None,
model_spec.as_deref(),
Expand Down
Loading
Loading