From 2a1945eb5ebabd960086292ec3fd338eeb937883 Mon Sep 17 00:00:00 2001 From: syui Date: Tue, 24 Mar 2026 19:27:52 +0900 Subject: [PATCH] fix cmd --- src/config.rs | 4 ++- src/headless.rs | 58 ++++++++++++++++++++++++++++++-------- src/main.rs | 3 +- src/prompts/protocols.json | 6 ++-- 4 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/config.rs b/src/config.rs index abcb45d..1339223 100644 --- a/src/config.rs +++ b/src/config.rs @@ -63,6 +63,8 @@ pub struct AgentConfig { pub host: Option, #[serde(default)] pub protocol: Option, + #[serde(default)] + pub depends_on: Vec, } fn default_cwd() -> String { @@ -128,7 +130,7 @@ fn load_file_full(path: &Path) -> LoadedConfig { } if let (Some(name), Some(task), Some(cwd)) = (multi.name, multi.task, multi.cwd) { return LoadedConfig { - agents: vec![AgentConfig { name, task, cwd, host: None, protocol: None }], + agents: vec![AgentConfig { name, task, cwd, host: None, protocol: None, depends_on: vec![] }], interval: multi.interval, }; } diff --git a/src/headless.rs b/src/headless.rs index d565c98..446d998 100644 --- a/src/headless.rs +++ b/src/headless.rs @@ -27,7 +27,7 @@ pub fn run(config_or_task: &str, cwd_override: Option<&str>, name_override: Opti .map(|p| p.display().to_string()) .unwrap_or_else(|_| ".".to_string())); let name = name_override.unwrap_or("task").to_string(); - (vec![config::AgentConfig { name, task: config_or_task.to_string(), cwd, host: None, protocol: None }], None) + (vec![config::AgentConfig { name, task: config_or_task.to_string(), cwd, host: None, protocol: None, depends_on: vec![] }], None) }; if configs.is_empty() { @@ -355,28 +355,63 @@ fn strip_dir_listing(text: &str) -> &str { } fn spawn_and_wait(configs: &[config::AgentConfig], running: &Arc) -> Vec { - let mut agents = Vec::new(); + let mut agents: Vec = Vec::new(); + let mut pending: Vec<&config::AgentConfig> = Vec::new(); let mut next_id = 1; + + // Spawn agents without dependencies immediately, defer the rest for cfg in configs { - let cwd = expand_tilde(&cfg.cwd); - match Agent::spawn_with_config(next_id, &cfg.name, &cfg.task, &cwd, cfg.host.as_deref(), cfg.protocol.as_deref()) { - Ok(agent) => { - eprintln!(" started: {} ({})", cfg.name, cwd); - agents.push(agent); - next_id += 1; + if cfg.depends_on.is_empty() { + let cwd = expand_tilde(&cfg.cwd); + match Agent::spawn_with_config(next_id, &cfg.name, &cfg.task, &cwd, cfg.host.as_deref(), cfg.protocol.as_deref()) { + Ok(agent) => { + eprintln!(" started: {} ({})", cfg.name, cwd); + agents.push(agent); + next_id += 1; + } + Err(e) => eprintln!(" failed: {}: {e}", cfg.name), } - Err(e) => eprintln!(" failed: {}: {e}", cfg.name), + } else { + eprintln!(" waiting: {} (depends on: {})", cfg.name, cfg.depends_on.join(", ")); + pending.push(cfg); } } let mut last_status: Vec = Vec::new(); while running.load(Ordering::Relaxed) { + // Poll running agents let mut any_running = false; for agent in &mut agents { agent.poll(); if agent.is_running() { any_running = true; } } + // Check if any pending agents can now start + let mut newly_started = Vec::new(); + pending.retain(|cfg| { + let deps_met = cfg.depends_on.iter().all(|dep| { + agents.iter().any(|a| &a.name == dep && !a.is_running()) + }); + if deps_met { + let cwd = expand_tilde(&cfg.cwd); + match Agent::spawn_with_config(next_id, &cfg.name, &cfg.task, &cwd, cfg.host.as_deref(), cfg.protocol.as_deref()) { + Ok(agent) => { + eprintln!(" started: {} ({})", cfg.name, cwd); + newly_started.push(agent); + } + Err(e) => eprintln!(" failed: {}: {e}", cfg.name), + } + false // remove from pending + } else { + true // keep waiting + } + }); + for a in newly_started { + next_id += 1; + any_running = true; + agents.push(a); + } + if agents.iter().any(|a| a.dirty) { write_state(&agents); for a in &mut agents { a.dirty = false; } @@ -393,7 +428,7 @@ fn spawn_and_wait(configs: &[config::AgentConfig], running: &Arc) -> last_status = current; } - if !any_running { break; } + if !any_running && pending.is_empty() { break; } std::thread::sleep(Duration::from_millis(200)); } agents @@ -426,7 +461,7 @@ fn extract_agent_configs(text: &str) -> Vec { }; if name.is_empty() || task.is_empty() { return None; } - Some(config::AgentConfig { name, task, cwd, host: None, protocol: None }) + Some(config::AgentConfig { name, task, cwd, host: None, protocol: None, depends_on: vec![] }) }) .collect() } @@ -749,6 +784,7 @@ pub fn commit() { cwd, host: None, protocol: None, + depends_on: vec![], }]; let running = Arc::new(AtomicBool::new(true)); diff --git a/src/main.rs b/src/main.rs index 363fc9c..b1be52e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,8 +26,7 @@ fn main() { println!("{}", env!("CARGO_PKG_VERSION")); } Some("help" | "--help" | "-h") => print_help(), - None => print_help(), - Some("tui") => { + None | Some("tui") => { // Show logo before entering alternate screen eprintln!("\x1b[38;5;226m{}\x1b[0m\n\x1b[1m aishell\x1b[0m v{}\n", LOGO, env!("CARGO_PKG_VERSION")); diff --git a/src/prompts/protocols.json b/src/prompts/protocols.json index 82b4f63..ac526d0 100644 --- a/src/prompts/protocols.json +++ b/src/prompts/protocols.json @@ -1,6 +1,6 @@ { - "ssh": "[protocol: ssh]\n[host: {host}]\nExecute commands via: ssh {host} \"command\"", - "https": "[protocol: https]\n[endpoint: {host}]\nAccess via HTTP/HTTPS API calls.", + "ssh": "[protocol: ssh]\n[host: {host}]\nThe host value is an SSH config name or user@host. Execute commands via: ssh {host} \"command\"\nDo not specify port or identity file — they are configured in ~/.ssh/config.\nIf the command fails, report the error. Do not retry with different connection parameters.", + "https": "[protocol: https]\n[endpoint: {host}]\nAccess via HTTP/HTTPS API calls. Use curl or equivalent.", "at": "[protocol: at]\n[did: {host}]\nAccess via AT Protocol. Use ailog or atproto API.", - "git": "[protocol: git]\n[remote: {host}]\nAccess via git commands." + "git": "[protocol: git]\n[remote: {host}]\nAccess via git commands (clone, pull, push, log, etc)." }