2
0
This commit is contained in:
2026-03-24 19:27:52 +09:00
parent 54eb61a4f3
commit 2a1945eb5e
4 changed files with 54 additions and 17 deletions

View File

@@ -63,6 +63,8 @@ pub struct AgentConfig {
pub host: Option<String>,
#[serde(default)]
pub protocol: Option<String>,
#[serde(default)]
pub depends_on: Vec<String>,
}
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,
};
}

View File

@@ -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<AtomicBool>) -> Vec<Agent> {
let mut agents = Vec::new();
let mut agents: Vec<Agent> = 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<String> = 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<AtomicBool>) ->
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<config::AgentConfig> {
};
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));

View File

@@ -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"));

View File

@@ -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)."
}